Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl;
import org.prebid.server.hooks.execution.v1.analytics.ResultImpl;
import org.prebid.server.hooks.execution.v1.analytics.TagsImpl;
import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
Expand Down Expand Up @@ -163,6 +166,15 @@ private InvocationResultImpl<AuctionRequestPayload> update(IdResResponse resolut
.status(InvocationStatus.success)
.action(InvocationAction.update)
.payloadUpdate(payload -> updatedPayload(payload, resolutionResult.getEids()))
.analyticsTags(TagsImpl.of(List.of(
ActivityImpl.of(
"liveintent-enriched", "success",
List.of(
ResultImpl.of(
"",
mapper.mapper().createObjectNode()
.put("treatmentRate", config.getTreatmentRate()),
null))))))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package org.prebid.server.analytics.reporter.liveintent;

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.Future;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.http.client.utils.URIBuilder;
import org.prebid.server.analytics.AnalyticsReporter;
import org.prebid.server.analytics.model.AuctionEvent;
import org.prebid.server.analytics.model.NotificationEvent;
import org.prebid.server.analytics.reporter.liveintent.model.LiveIntentAnalyticsProperties;
import org.prebid.server.analytics.reporter.liveintent.model.PbsjBid;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.hooks.execution.model.ExecutionStatus;
import org.prebid.server.hooks.execution.model.GroupExecutionOutcome;
import org.prebid.server.hooks.execution.model.HookExecutionContext;
import org.prebid.server.hooks.execution.model.HookExecutionOutcome;
import org.prebid.server.hooks.execution.model.Stage;
import org.prebid.server.hooks.execution.model.StageExecutionOutcome;
import org.prebid.server.hooks.v1.analytics.Activity;
import org.prebid.server.hooks.v1.analytics.Tags;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
import org.prebid.server.vertx.httpclient.HttpClient;

import java.net.URISyntaxException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class LiveIntentAnalyticsReporter implements AnalyticsReporter {

private static final Logger logger = LoggerFactory.getLogger(LiveIntentAnalyticsReporter.class);

private static final String LIVEINTENT_HOOK_ID = "liveintent-omni-channel-identity-enrichment-hook";

private final HttpClient httpClient;
private final LiveIntentAnalyticsProperties properties;
private final JacksonMapper jacksonMapper;

public LiveIntentAnalyticsReporter(
LiveIntentAnalyticsProperties properties,
HttpClient httpClient,
JacksonMapper jacksonMapper) {

this.httpClient = Objects.requireNonNull(httpClient);
this.properties = Objects.requireNonNull(properties);
this.jacksonMapper = Objects.requireNonNull(jacksonMapper);
}

@Override
public <T> Future<Void> processEvent(T event) {
if (event instanceof AuctionEvent auctionEvent) {
return processAuctionEvent(auctionEvent.getAuctionContext());
} else if (event instanceof NotificationEvent notificationEvent) {
return processNotificationEvent(notificationEvent);
}

return Future.succeededFuture();
}

private Future<Void> processAuctionEvent(AuctionContext auctionContext) {
if (auctionContext.getBidRequest() == null) {
return Future.failedFuture(new PreBidException("Bid request should not be empty"));
}

if (auctionContext.getBidResponse() == null) {
return Future.succeededFuture();
}

final BidRequest bidRequest = auctionContext.getBidRequest();
final BidResponse bidResponse = auctionContext.getBidResponse();

final List<Activity> activities = getActivities(auctionContext);
final boolean isEnriched = isEnriched(activities);
final Float treatmentRate = getTreatmentRate(activities);
final Long timestamp = Optional.ofNullable(bidRequest.getExt())
.map(ExtRequest::getPrebid)
.map(ExtRequestPrebid::getAuctiontimestamp)
.orElse(0L);

final List<PbsjBid> pbsjBids = CollectionUtils.emptyIfNull(bidResponse.getSeatbid()).stream()
.map(SeatBid::getBid)
.flatMap(Collection::stream)
.map(bid -> buildPbsjBid(bidRequest, bidResponse, bid, isEnriched, treatmentRate, timestamp))
.filter(Optional::isPresent)
.map(Optional::get)
.toList();

try {
return httpClient.post(
new URIBuilder(properties.getAnalyticsEndpoint())
.setPath("/analytic-events/pbsj-bids")
.build()
.toString(),
jacksonMapper.encodeToString(pbsjBids),
properties.getTimeoutMs())
.mapEmpty();
} catch (Exception e) {
logger.error("Error processing event: {}", e.getMessage());
return Future.failedFuture(e);
}
}

private List<Activity> getActivities(AuctionContext auctionContext) {
return Optional.ofNullable(auctionContext)
.map(AuctionContext::getHookExecutionContext)
.map(HookExecutionContext::getStageOutcomes)
.map(stages -> stages.get(Stage.processed_auction_request))
.stream()
.flatMap(Collection::stream)
.filter(stageExecutionOutcome -> "auction-request".equals(stageExecutionOutcome.getEntity()))
.map(StageExecutionOutcome::getGroups)
.flatMap(Collection::stream)
.map(GroupExecutionOutcome::getHooks)
.flatMap(Collection::stream)
.filter(hook -> LIVEINTENT_HOOK_ID.equals(hook.getHookId().getModuleCode())
&& hook.getStatus() == ExecutionStatus.success)
.map(HookExecutionOutcome::getAnalyticsTags)
.filter(Objects::nonNull)
.map(Tags::activities)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.toList();
}

private boolean isEnriched(List<Activity> activity) {
return activity.stream().anyMatch(a -> "liveintent-enriched".equals(a.name()));
}

private Float getTreatmentRate(List<Activity> activity) {
return activity.stream()
.flatMap(a -> a.results().stream())
.filter(a -> a.values().has("treatmentRate"))
.findFirst()
.map(a -> a.values().get("treatmentRate").floatValue())
.orElse(null);
}

private Optional<PbsjBid> buildPbsjBid(
BidRequest bidRequest,
BidResponse bidResponse,
Bid bid,
boolean isEnriched,
Float treatmentRate,
Long timestamp) {

return bidRequest.getImp().stream()
.filter(impItem -> impItem.getId().equals(bid.getImpid()))
.map(imp -> PbsjBid.builder()
.bidId(bid.getId())
.price(bid.getPrice())
.adUnitId(imp.getTagid())
.enriched(isEnriched)
.currency(bidResponse.getCur())
.treatmentRate(treatmentRate)
.timestamp(timestamp)
.partnerId(properties.getPartnerId())
.build())
.findFirst();
}

private Future<Void> processNotificationEvent(NotificationEvent notificationEvent) {
try {
final String url = new URIBuilder(properties.getAnalyticsEndpoint())
.setPath("/analytic-events/pbsj-winning-bid")
.setParameter("b", notificationEvent.getBidder())
.setParameter("bidId", notificationEvent.getBidId())
.build()
.toString();
return httpClient.get(url, properties.getTimeoutMs()).mapEmpty();
} catch (URISyntaxException e) {
logger.error("Error composing url for notification event: {}", e.getMessage());
return Future.failedFuture(e);
}
}

@Override
public int vendorId() {
return 0;
}

@Override
public String name() {
return "liveintentAnalytics";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.prebid.server.analytics.reporter.liveintent.model;

import lombok.Builder;
import lombok.Value;

@Builder(toBuilder = true)
@Value
public class LiveIntentAnalyticsProperties {

String partnerId;

String analyticsEndpoint;

long timeoutMs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.prebid.server.analytics.reporter.liveintent.model;

import lombok.Builder;
import lombok.Value;

import java.math.BigDecimal;

@Builder(toBuilder = true)
@Value
public class PbsjBid {

String bidId;

boolean enriched;

BigDecimal price;

String adUnitId;

String currency;

Float treatmentRate;

Long timestamp;

String partnerId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.prebid.server.analytics.reporter.agma.model.AgmaAnalyticsProperties;
import org.prebid.server.analytics.reporter.greenbids.GreenbidsAnalyticsReporter;
import org.prebid.server.analytics.reporter.greenbids.model.GreenbidsAnalyticsProperties;
import org.prebid.server.analytics.reporter.liveintent.LiveIntentAnalyticsReporter;
import org.prebid.server.analytics.reporter.liveintent.model.LiveIntentAnalyticsProperties;
import org.prebid.server.analytics.reporter.log.LogAnalyticsReporter;
import org.prebid.server.analytics.reporter.pubstack.PubstackAnalyticsReporter;
import org.prebid.server.analytics.reporter.pubstack.model.PubstackAnalyticsProperties;
Expand Down Expand Up @@ -302,4 +304,47 @@ private static class PubstackBufferProperties {
Long reportTtlMs;
}
}

@Configuration
@ConditionalOnProperty(prefix = "analytics.liveintent", name = "enabled", havingValue = "true")
public static class LiveIntentAnalyticsConfiguration {

@Bean
LiveIntentAnalyticsReporter liveIntentAnalyticsReporter(
LiveIntentAnalyticsConfigurationProperties properties,
HttpClient httpClient,
JacksonMapper jacksonMapper) {

return new LiveIntentAnalyticsReporter(
properties.toComponentProperties(),
httpClient,
jacksonMapper);
}

@Bean
@ConfigurationProperties(prefix = "analytics.liveintent")
LiveIntentAnalyticsConfigurationProperties liveIntentAnalyticsConfigurationProperties() {
return new LiveIntentAnalyticsConfigurationProperties();
}

@Validated
@NoArgsConstructor
@Data
private static class LiveIntentAnalyticsConfigurationProperties {

String partnerId;

String analyticsEndpoint;

long timeoutMs;

public LiveIntentAnalyticsProperties toComponentProperties() {
return LiveIntentAnalyticsProperties.builder()
.partnerId(this.partnerId)
.analyticsEndpoint(this.analyticsEndpoint)
.timeoutMs(this.timeoutMs)
.build();
}
}
}
}
Loading
Loading