Skip to content

Commit a1fe64e

Browse files
authored
Events: Add new request.ext.prebid.events.enabled toggle (#4479)
1 parent 78dc73d commit a1fe64e

5 files changed

Lines changed: 182 additions & 14 deletions

File tree

src/main/java/org/prebid/server/auction/BidResponseCreator.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,7 +1719,11 @@ private static boolean eventsEnabledForAccount(AuctionContext auctionContext) {
17191719
}
17201720

17211721
private static boolean eventsEnabledForRequest(AuctionContext auctionContext) {
1722-
return eventsEnabledForChannel(auctionContext) || eventsAllowedByRequest(auctionContext);
1722+
return Optional.ofNullable(auctionContext.getBidRequest().getExt())
1723+
.map(ExtRequest::getPrebid)
1724+
.map(ExtRequestPrebid::getEvents)
1725+
.map(eventsNode -> eventsNode.at("/enabled").asBoolean(true))
1726+
.orElseGet(() -> eventsEnabledForChannel(auctionContext));
17231727
}
17241728

17251729
private static boolean eventsEnabledForChannel(AuctionContext auctionContext) {
@@ -1754,13 +1758,6 @@ private static String recogniseChannelName(String channelName) {
17541758
return channelName;
17551759
}
17561760

1757-
private static boolean eventsAllowedByRequest(AuctionContext auctionContext) {
1758-
final ExtRequest ext = auctionContext.getBidRequest().getExt();
1759-
final ExtRequestPrebid prebid = ext != null ? ext.getPrebid() : null;
1760-
1761-
return prebid != null && prebid.getEvents() != null;
1762-
}
1763-
17641761
private long auctionTimestamp(AuctionContext auctionContext) {
17651762
final ExtRequest ext = auctionContext.getBidRequest().getExt();
17661763
final ExtRequestPrebid prebid = ext != null ? ext.getPrebid() : null;

src/test/groovy/org/prebid/server/functional/model/config/AccountAnalyticsConfig.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import com.fasterxml.jackson.annotation.JsonProperty
44
import com.fasterxml.jackson.databind.PropertyNamingStrategies
55
import com.fasterxml.jackson.databind.annotation.JsonNaming
66
import groovy.transform.ToString
7+
import org.prebid.server.functional.model.ChannelType
78

89
@ToString(includeNames = true, ignoreNulls = true)
910
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy)
1011
class AccountAnalyticsConfig {
1112

12-
Map<String, Boolean> auctionEvents
13+
Map<ChannelType, Boolean> auctionEvents
1314
Boolean allowClientDetails
1415
AnalyticsModule modules
1516

src/test/groovy/org/prebid/server/functional/model/request/auction/Events.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize
55
@JsonSerialize
66
class Events {
77

8+
Boolean enabled
89
}

src/test/groovy/org/prebid/server/functional/tests/EventsSpec.groovy

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package org.prebid.server.functional.tests
22

3+
import org.prebid.server.functional.model.ChannelType
4+
import org.prebid.server.functional.model.config.AccountAnalyticsConfig
5+
import org.prebid.server.functional.model.config.AccountConfig
36
import org.prebid.server.functional.model.db.Account
47
import org.prebid.server.functional.model.db.StoredRequest
58
import org.prebid.server.functional.model.request.auction.BidRequest
9+
import org.prebid.server.functional.model.request.auction.Events
610
import org.prebid.server.functional.model.request.auction.PrebidStoredRequest
711
import org.prebid.server.functional.util.PBSUtils
812

913
import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP
14+
import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH
1015
import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE
1116

1217
class EventsSpec extends BaseSpec {
@@ -31,7 +36,7 @@ class EventsSpec extends BaseSpec {
3136
assert bidResponse.seatbid[0].bid[0].ext.prebid.events.imp
3237

3338
where:
34-
distributionChannel << [SITE, APP]
39+
distributionChannel << [SITE, APP, DOOH]
3540
}
3641

3742
def "PBS should not generate event tracker URLs when events are disabled for account"() {
@@ -54,7 +59,7 @@ class EventsSpec extends BaseSpec {
5459
assert !bidResponse.seatbid[0].bid[0].ext.prebid.events?.imp
5560

5661
where:
57-
distributionChannel << [SITE, APP]
62+
distributionChannel << [SITE, APP, DOOH]
5863
}
5964

6065
def "PBS should resolve publisher id for events when events are enabled for account"() {
@@ -78,7 +83,7 @@ class EventsSpec extends BaseSpec {
7883
assert bidResponseEvents.imp.contains("a=${accountId}")
7984

8085
where:
81-
distributionChannel << [SITE, APP]
86+
distributionChannel << [SITE, APP, DOOH]
8287
}
8388

8489
def "PBS should resolve publisher id from stored request for events when events enabled"() {
@@ -110,6 +115,124 @@ class EventsSpec extends BaseSpec {
110115
assert bidResponseEvents.imp.contains("a=${accountId}")
111116

112117
where:
113-
distributionChannel << [SITE, APP]
118+
distributionChannel << [SITE, APP, DOOH]
119+
}
120+
121+
def "Account-level analytics settings should apply when request events config is absent"() {
122+
given: "BidRequest without events config"
123+
def accountId = PBSUtils.randomNumber as String
124+
def bidRequest = BidRequest.getDefaultBidRequest(requestType).tap {
125+
setAccountId(accountId)
126+
}
127+
128+
and: "Account with analytics events disabled for corresponding channel"
129+
def analyticsConfig = new AccountAnalyticsConfig(auctionEvents: [(accountConfigChannelType): false])
130+
def account = new Account(uuid: accountId, eventsEnabled: true, config: new AccountConfig(analytics: analyticsConfig))
131+
accountDao.save(account)
132+
133+
when: "Auction request is processed"
134+
def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
135+
136+
then: "Events should not be present in response due to stored request disablement"
137+
assert !bidResponse.seatbid[0].bid[0].ext?.prebid?.events
138+
139+
where:
140+
requestType | accountConfigChannelType
141+
SITE | ChannelType.WEB
142+
APP | ChannelType.APP
143+
DOOH | ChannelType.DOOH
144+
}
145+
146+
def "Request level events config should override account-level analytics settings"() {
147+
given: "BidRequest with events config"
148+
def accountId = PBSUtils.randomNumber as String
149+
def bidRequest = BidRequest.getDefaultBidRequest(requestType).tap {
150+
setAccountId(accountId)
151+
ext.prebid.events = new Events(enabled: requestEventEnablement)
152+
}
153+
154+
and: "Account with analytics events disabled for corresponding channel"
155+
def analyticsConfig = new AccountAnalyticsConfig(auctionEvents: [(accountConfigChannelType): false])
156+
def account = new Account(uuid: accountId, eventsEnabled: true, config: new AccountConfig(analytics: analyticsConfig))
157+
accountDao.save(account)
158+
159+
when: "Auction request is processed"
160+
def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
161+
162+
then: "Events should be present in response despite account-level disablement"
163+
def bidResponseEvents = bidResponse.seatbid[0].bid[0].ext.prebid.events
164+
assert bidResponseEvents.win.contains("a=${accountId}")
165+
assert bidResponseEvents.imp.contains("a=${accountId}")
166+
167+
where:
168+
requestEventEnablement | requestType | accountConfigChannelType
169+
null | SITE | ChannelType.WEB
170+
null | APP | ChannelType.APP
171+
null | DOOH | ChannelType.DOOH
172+
173+
true | SITE | ChannelType.WEB
174+
true | APP | ChannelType.APP
175+
true | DOOH | ChannelType.DOOH
176+
}
177+
178+
def "Request-level events disabled should override account-level analytics settings"() {
179+
given: "BidRequest with events explicitly disabled at request level"
180+
def accountId = PBSUtils.randomNumber as String
181+
182+
def bidRequest = BidRequest.getDefaultBidRequest(requestType).tap {
183+
setAccountId(accountId)
184+
ext.prebid.events = new Events(enabled: false)
185+
}
186+
187+
and: "Account with analytics events enabled for corresponding channel"
188+
def analyticsConfig = new AccountAnalyticsConfig(auctionEvents: [(accountConfigChannelType): true])
189+
def account = new Account(uuid: accountId, eventsEnabled: true, config: new AccountConfig(analytics: analyticsConfig))
190+
accountDao.save(account)
191+
192+
when: "Auction request is processed"
193+
def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
194+
195+
then: "Events should not be present in response due to request-level disablement"
196+
assert !bidResponse.seatbid[0].bid[0].ext?.prebid?.events
197+
198+
where:
199+
requestType | accountConfigChannelType
200+
SITE | ChannelType.WEB
201+
APP | ChannelType.APP
202+
DOOH | ChannelType.DOOH
203+
}
204+
205+
def "Stored request events config should override account-level analytics settings when request config is absent"() {
206+
given: "BidRequest referencing stored request without events config"
207+
def storedRequestId = PBSUtils.randomString
208+
def bidRequest = BidRequest.getDefaultBidRequest(distributionChannel).tap {
209+
ext.prebid.storedRequest = new PrebidStoredRequest(id: storedRequestId)
210+
setAccountId(null)
211+
}
212+
213+
and: "Stored request with account id and events disabled"
214+
def accountId = PBSUtils.randomNumber as String
215+
def storedRequest = BidRequest.getDefaultBidRequest(distributionChannel).tap {
216+
setAccountId(accountId)
217+
ext.prebid.events = new Events(enabled: false)
218+
}
219+
storedRequestDao.save(StoredRequest.getStoredRequest(accountId, storedRequestId, storedRequest))
220+
221+
and: "Account with analytics events enabled for corresponding channel"
222+
def analyticsConfig = new AccountAnalyticsConfig(auctionEvents: [(accountConfigChannelType): true])
223+
def account = new Account(uuid: accountId, eventsEnabled: true, config: new AccountConfig(analytics: analyticsConfig))
224+
accountDao.save(account)
225+
226+
when: "Auction request is processed"
227+
def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
228+
229+
then: "Events should not be present in response due to stored request disablement"
230+
assert !bidResponse.seatbid[0].bid[0].ext?.prebid?.events
231+
232+
where:
233+
distributionChannel | accountConfigChannelType
234+
SITE | ChannelType.WEB
235+
APP | ChannelType.APP
236+
DOOH | ChannelType.DOOH
114237
}
115238
}

src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
import org.prebid.server.auction.model.BidderResponse;
4444
import org.prebid.server.auction.model.CachedDebugLog;
4545
import org.prebid.server.auction.model.CategoryMappingResult;
46+
import org.prebid.server.auction.model.ImpRejection;
4647
import org.prebid.server.auction.model.MultiBidConfig;
4748
import org.prebid.server.auction.model.PaaFormat;
48-
import org.prebid.server.auction.model.ImpRejection;
4949
import org.prebid.server.auction.model.TargetingInfo;
5050
import org.prebid.server.auction.model.TimeoutContext;
5151
import org.prebid.server.auction.model.debug.DebugContext;
@@ -163,6 +163,7 @@
163163
import static org.mockito.Mockito.mock;
164164
import static org.mockito.Mockito.never;
165165
import static org.mockito.Mockito.verify;
166+
import static org.mockito.Mockito.verifyNoInteractions;
166167
import static org.prebid.server.auction.model.BidRejectionReason.NO_BID;
167168
import static org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAdservertargetingRule.Source.xStatic;
168169
import static org.prebid.server.proto.openrtb.ext.response.BidType.audio;
@@ -2719,6 +2720,8 @@ public void shouldNotAddExtPrebidEventsIfEventsAreNotEnabled() {
27192720
.flatExtracting(SeatBid::getBid)
27202721
.extracting(responseBid -> toExtBidPrebid(responseBid.getExt()).getEvents())
27212722
.containsNull();
2723+
2724+
verifyNoInteractions(eventsService);
27222725
}
27232726

27242727
@Test
@@ -2753,6 +2756,47 @@ public void shouldNotAddExtPrebidEventsIfExtRequestPrebidEventsNull() {
27532756
.flatExtracting(SeatBid::getBid)
27542757
.extracting(responseBid -> toExtBidPrebid(responseBid.getExt()).getEvents())
27552758
.containsNull();
2759+
2760+
verifyNoInteractions(eventsService);
2761+
}
2762+
2763+
@Test
2764+
public void shouldNotAddExtPrebidEventsIfExtRequestPrebidEventsEnabledIsFalse() {
2765+
// given
2766+
final Account account = Account.builder()
2767+
.id("accountId")
2768+
.auction(AccountAuctionConfig.builder()
2769+
.events(AccountEventsConfig.of(true))
2770+
.build())
2771+
.build();
2772+
2773+
final Bid bid = Bid.builder()
2774+
.id("bidId1")
2775+
.price(BigDecimal.valueOf(5.67))
2776+
.impid(IMP_ID)
2777+
.build();
2778+
final List<BidderResponse> bidderResponses = singletonList(
2779+
BidderResponse.of("bidder1", givenSeatBid(BidderBid.of(bid, banner, "seat", "USD")), 100));
2780+
2781+
final AuctionContext auctionContext = givenAuctionContext(
2782+
givenBidRequest(
2783+
identity(),
2784+
extBuilder -> extBuilder.events(mapper.createObjectNode().put("enabled", false)),
2785+
givenImp()),
2786+
contextBuilder -> contextBuilder
2787+
.account(account)
2788+
.auctionParticipations(toAuctionParticipant(bidderResponses)));
2789+
2790+
// when
2791+
final BidResponse bidResponse = target.create(auctionContext, CACHE_INFO, MULTI_BIDS).result();
2792+
2793+
// then
2794+
assertThat(bidResponse.getSeatbid()).hasSize(1)
2795+
.flatExtracting(SeatBid::getBid)
2796+
.extracting(responseBid -> toExtBidPrebid(responseBid.getExt()).getEvents())
2797+
.containsNull();
2798+
2799+
verifyNoInteractions(eventsService);
27562800
}
27572801

27582802
@Test
@@ -2794,6 +2838,8 @@ public void shouldNotAddExtPrebidEventsIfAccountDoesNotSupportEventsForChannel()
27942838
.flatExtracting(SeatBid::getBid)
27952839
.extracting(responseBid -> toExtBidPrebid(responseBid.getExt()).getEvents())
27962840
.containsNull();
2841+
2842+
verifyNoInteractions(eventsService);
27972843
}
27982844

27992845
@Test

0 commit comments

Comments
 (0)