Skip to content

Commit 85c22d0

Browse files
authored
DATA-22937: LI analytics adapter (#4154)
1 parent e3b3f8c commit 85c22d0

File tree

6 files changed

+491
-0
lines changed

6 files changed

+491
-0
lines changed

extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import org.prebid.server.auction.model.AuctionContext;
1818
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
1919
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
20+
import org.prebid.server.hooks.execution.v1.analytics.ActivityImpl;
21+
import org.prebid.server.hooks.execution.v1.analytics.ResultImpl;
22+
import org.prebid.server.hooks.execution.v1.analytics.TagsImpl;
2023
import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl;
2124
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse;
2225
import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties;
@@ -163,6 +166,15 @@ private InvocationResultImpl<AuctionRequestPayload> update(IdResResponse resolut
163166
.status(InvocationStatus.success)
164167
.action(InvocationAction.update)
165168
.payloadUpdate(payload -> updatedPayload(payload, resolutionResult.getEids()))
169+
.analyticsTags(TagsImpl.of(List.of(
170+
ActivityImpl.of(
171+
"liveintent-enriched", "success",
172+
List.of(
173+
ResultImpl.of(
174+
"",
175+
mapper.mapper().createObjectNode()
176+
.put("treatmentRate", config.getTreatmentRate()),
177+
null))))))
166178
.build();
167179
}
168180

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package org.prebid.server.analytics.reporter.liveintent;
2+
3+
import com.iab.openrtb.request.BidRequest;
4+
import com.iab.openrtb.response.Bid;
5+
import com.iab.openrtb.response.BidResponse;
6+
import com.iab.openrtb.response.SeatBid;
7+
import io.vertx.core.Future;
8+
import org.apache.commons.collections4.CollectionUtils;
9+
import org.apache.http.client.utils.URIBuilder;
10+
import org.prebid.server.analytics.AnalyticsReporter;
11+
import org.prebid.server.analytics.model.AuctionEvent;
12+
import org.prebid.server.analytics.model.NotificationEvent;
13+
import org.prebid.server.analytics.reporter.liveintent.model.LiveIntentAnalyticsProperties;
14+
import org.prebid.server.analytics.reporter.liveintent.model.PbsjBid;
15+
import org.prebid.server.auction.model.AuctionContext;
16+
import org.prebid.server.exception.PreBidException;
17+
import org.prebid.server.hooks.execution.model.ExecutionStatus;
18+
import org.prebid.server.hooks.execution.model.GroupExecutionOutcome;
19+
import org.prebid.server.hooks.execution.model.HookExecutionContext;
20+
import org.prebid.server.hooks.execution.model.HookExecutionOutcome;
21+
import org.prebid.server.hooks.execution.model.Stage;
22+
import org.prebid.server.hooks.execution.model.StageExecutionOutcome;
23+
import org.prebid.server.hooks.v1.analytics.Activity;
24+
import org.prebid.server.hooks.v1.analytics.Tags;
25+
import org.prebid.server.json.JacksonMapper;
26+
import org.prebid.server.log.Logger;
27+
import org.prebid.server.log.LoggerFactory;
28+
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
29+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
30+
import org.prebid.server.vertx.httpclient.HttpClient;
31+
32+
import java.net.URISyntaxException;
33+
import java.util.Collection;
34+
import java.util.List;
35+
import java.util.Objects;
36+
import java.util.Optional;
37+
38+
public class LiveIntentAnalyticsReporter implements AnalyticsReporter {
39+
40+
private static final Logger logger = LoggerFactory.getLogger(LiveIntentAnalyticsReporter.class);
41+
42+
private static final String LIVEINTENT_HOOK_ID = "liveintent-omni-channel-identity-enrichment-hook";
43+
44+
private final HttpClient httpClient;
45+
private final LiveIntentAnalyticsProperties properties;
46+
private final JacksonMapper jacksonMapper;
47+
48+
public LiveIntentAnalyticsReporter(
49+
LiveIntentAnalyticsProperties properties,
50+
HttpClient httpClient,
51+
JacksonMapper jacksonMapper) {
52+
53+
this.httpClient = Objects.requireNonNull(httpClient);
54+
this.properties = Objects.requireNonNull(properties);
55+
this.jacksonMapper = Objects.requireNonNull(jacksonMapper);
56+
}
57+
58+
@Override
59+
public <T> Future<Void> processEvent(T event) {
60+
if (event instanceof AuctionEvent auctionEvent) {
61+
return processAuctionEvent(auctionEvent.getAuctionContext());
62+
} else if (event instanceof NotificationEvent notificationEvent) {
63+
return processNotificationEvent(notificationEvent);
64+
}
65+
66+
return Future.succeededFuture();
67+
}
68+
69+
private Future<Void> processAuctionEvent(AuctionContext auctionContext) {
70+
if (auctionContext.getBidRequest() == null) {
71+
return Future.failedFuture(new PreBidException("Bid request should not be empty"));
72+
}
73+
74+
if (auctionContext.getBidResponse() == null) {
75+
return Future.succeededFuture();
76+
}
77+
78+
final BidRequest bidRequest = auctionContext.getBidRequest();
79+
final BidResponse bidResponse = auctionContext.getBidResponse();
80+
81+
final List<Activity> activities = getActivities(auctionContext);
82+
final boolean isEnriched = isEnriched(activities);
83+
final Float treatmentRate = getTreatmentRate(activities);
84+
final Long timestamp = Optional.ofNullable(bidRequest.getExt())
85+
.map(ExtRequest::getPrebid)
86+
.map(ExtRequestPrebid::getAuctiontimestamp)
87+
.orElse(0L);
88+
89+
final List<PbsjBid> pbsjBids = CollectionUtils.emptyIfNull(bidResponse.getSeatbid()).stream()
90+
.map(SeatBid::getBid)
91+
.flatMap(Collection::stream)
92+
.map(bid -> buildPbsjBid(bidRequest, bidResponse, bid, isEnriched, treatmentRate, timestamp))
93+
.filter(Optional::isPresent)
94+
.map(Optional::get)
95+
.toList();
96+
97+
try {
98+
return httpClient.post(
99+
new URIBuilder(properties.getAnalyticsEndpoint())
100+
.setPath("/analytic-events/pbsj-bids")
101+
.build()
102+
.toString(),
103+
jacksonMapper.encodeToString(pbsjBids),
104+
properties.getTimeoutMs())
105+
.mapEmpty();
106+
} catch (Exception e) {
107+
logger.error("Error processing event: {}", e.getMessage());
108+
return Future.failedFuture(e);
109+
}
110+
}
111+
112+
private List<Activity> getActivities(AuctionContext auctionContext) {
113+
return Optional.ofNullable(auctionContext)
114+
.map(AuctionContext::getHookExecutionContext)
115+
.map(HookExecutionContext::getStageOutcomes)
116+
.map(stages -> stages.get(Stage.processed_auction_request))
117+
.stream()
118+
.flatMap(Collection::stream)
119+
.filter(stageExecutionOutcome -> "auction-request".equals(stageExecutionOutcome.getEntity()))
120+
.map(StageExecutionOutcome::getGroups)
121+
.flatMap(Collection::stream)
122+
.map(GroupExecutionOutcome::getHooks)
123+
.flatMap(Collection::stream)
124+
.filter(hook -> LIVEINTENT_HOOK_ID.equals(hook.getHookId().getModuleCode())
125+
&& hook.getStatus() == ExecutionStatus.success)
126+
.map(HookExecutionOutcome::getAnalyticsTags)
127+
.filter(Objects::nonNull)
128+
.map(Tags::activities)
129+
.filter(Objects::nonNull)
130+
.flatMap(Collection::stream)
131+
.filter(Objects::nonNull)
132+
.toList();
133+
}
134+
135+
private boolean isEnriched(List<Activity> activity) {
136+
return activity.stream().anyMatch(a -> "liveintent-enriched".equals(a.name()));
137+
}
138+
139+
private Float getTreatmentRate(List<Activity> activity) {
140+
return activity.stream()
141+
.flatMap(a -> a.results().stream())
142+
.filter(a -> a.values().has("treatmentRate"))
143+
.findFirst()
144+
.map(a -> a.values().get("treatmentRate").floatValue())
145+
.orElse(null);
146+
}
147+
148+
private Optional<PbsjBid> buildPbsjBid(
149+
BidRequest bidRequest,
150+
BidResponse bidResponse,
151+
Bid bid,
152+
boolean isEnriched,
153+
Float treatmentRate,
154+
Long timestamp) {
155+
156+
return bidRequest.getImp().stream()
157+
.filter(impItem -> impItem.getId().equals(bid.getImpid()))
158+
.map(imp -> PbsjBid.builder()
159+
.bidId(bid.getId())
160+
.price(bid.getPrice())
161+
.adUnitId(imp.getTagid())
162+
.enriched(isEnriched)
163+
.currency(bidResponse.getCur())
164+
.treatmentRate(treatmentRate)
165+
.timestamp(timestamp)
166+
.partnerId(properties.getPartnerId())
167+
.build())
168+
.findFirst();
169+
}
170+
171+
private Future<Void> processNotificationEvent(NotificationEvent notificationEvent) {
172+
try {
173+
final String url = new URIBuilder(properties.getAnalyticsEndpoint())
174+
.setPath("/analytic-events/pbsj-winning-bid")
175+
.setParameter("b", notificationEvent.getBidder())
176+
.setParameter("bidId", notificationEvent.getBidId())
177+
.build()
178+
.toString();
179+
return httpClient.get(url, properties.getTimeoutMs()).mapEmpty();
180+
} catch (URISyntaxException e) {
181+
logger.error("Error composing url for notification event: {}", e.getMessage());
182+
return Future.failedFuture(e);
183+
}
184+
}
185+
186+
@Override
187+
public int vendorId() {
188+
return 0;
189+
}
190+
191+
@Override
192+
public String name() {
193+
return "liveintentAnalytics";
194+
}
195+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.prebid.server.analytics.reporter.liveintent.model;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
@Builder(toBuilder = true)
7+
@Value
8+
public class LiveIntentAnalyticsProperties {
9+
10+
String partnerId;
11+
12+
String analyticsEndpoint;
13+
14+
long timeoutMs;
15+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.prebid.server.analytics.reporter.liveintent.model;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
import java.math.BigDecimal;
7+
8+
@Builder(toBuilder = true)
9+
@Value
10+
public class PbsjBid {
11+
12+
String bidId;
13+
14+
boolean enriched;
15+
16+
BigDecimal price;
17+
18+
String adUnitId;
19+
20+
String currency;
21+
22+
Float treatmentRate;
23+
24+
Long timestamp;
25+
26+
String partnerId;
27+
}

src/main/java/org/prebid/server/spring/config/AnalyticsConfiguration.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.prebid.server.analytics.reporter.agma.model.AgmaAnalyticsProperties;
1313
import org.prebid.server.analytics.reporter.greenbids.GreenbidsAnalyticsReporter;
1414
import org.prebid.server.analytics.reporter.greenbids.model.GreenbidsAnalyticsProperties;
15+
import org.prebid.server.analytics.reporter.liveintent.LiveIntentAnalyticsReporter;
16+
import org.prebid.server.analytics.reporter.liveintent.model.LiveIntentAnalyticsProperties;
1517
import org.prebid.server.analytics.reporter.log.LogAnalyticsReporter;
1618
import org.prebid.server.analytics.reporter.pubstack.PubstackAnalyticsReporter;
1719
import org.prebid.server.analytics.reporter.pubstack.model.PubstackAnalyticsProperties;
@@ -302,4 +304,47 @@ private static class PubstackBufferProperties {
302304
Long reportTtlMs;
303305
}
304306
}
307+
308+
@Configuration
309+
@ConditionalOnProperty(prefix = "analytics.liveintent", name = "enabled", havingValue = "true")
310+
public static class LiveIntentAnalyticsConfiguration {
311+
312+
@Bean
313+
LiveIntentAnalyticsReporter liveIntentAnalyticsReporter(
314+
LiveIntentAnalyticsConfigurationProperties properties,
315+
HttpClient httpClient,
316+
JacksonMapper jacksonMapper) {
317+
318+
return new LiveIntentAnalyticsReporter(
319+
properties.toComponentProperties(),
320+
httpClient,
321+
jacksonMapper);
322+
}
323+
324+
@Bean
325+
@ConfigurationProperties(prefix = "analytics.liveintent")
326+
LiveIntentAnalyticsConfigurationProperties liveIntentAnalyticsConfigurationProperties() {
327+
return new LiveIntentAnalyticsConfigurationProperties();
328+
}
329+
330+
@Validated
331+
@NoArgsConstructor
332+
@Data
333+
private static class LiveIntentAnalyticsConfigurationProperties {
334+
335+
String partnerId;
336+
337+
String analyticsEndpoint;
338+
339+
long timeoutMs;
340+
341+
public LiveIntentAnalyticsProperties toComponentProperties() {
342+
return LiveIntentAnalyticsProperties.builder()
343+
.partnerId(this.partnerId)
344+
.analyticsEndpoint(this.analyticsEndpoint)
345+
.timeoutMs(this.timeoutMs)
346+
.build();
347+
}
348+
}
349+
}
305350
}

0 commit comments

Comments
 (0)