Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenX: Native support #3649

Merged
merged 19 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
49 changes: 39 additions & 10 deletions src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OpenxBidder implements Bidder<BidRequest> {

Expand Down Expand Up @@ -72,9 +73,12 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequ
.collect(Collectors.groupingBy(OpenxBidder::resolveImpType));

final List<BidderError> processingErrors = new ArrayList<>();
final List<BidRequest> outgoingRequests = makeRequests(bidRequest,
final List<BidRequest> outgoingRequests = makeRequests(
bidRequest,
differentiatedImps.get(OpenxImpType.banner),
differentiatedImps.get(OpenxImpType.video), processingErrors);
differentiatedImps.get(OpenxImpType.video),
differentiatedImps.get(OpenxImpType.xNative),
processingErrors);

final List<BidderError> errors = errors(differentiatedImps.get(OpenxImpType.other), processingErrors);

Expand All @@ -101,13 +105,21 @@ public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequ
return Result.withError(BidderError.generic("Deprecated adapter method invoked"));
}

private List<BidRequest> makeRequests(BidRequest bidRequest, List<Imp> bannerImps, List<Imp> videoImps,
List<BidderError> errors) {
private List<BidRequest> makeRequests(
BidRequest bidRequest,
List<Imp> bannerImps,
List<Imp> videoImps,
List<Imp> nativeImps,
List<BidderError> errors) {
final List<BidRequest> bidRequests = new ArrayList<>();
// single request for all banner imps
final BidRequest bannerRequest = createSingleRequest(bannerImps, bidRequest, errors);
if (bannerRequest != null) {
bidRequests.add(bannerRequest);
// single request for all banner and native imps
final List<Imp> bannerAndNativeImps = Stream.of(bannerImps, nativeImps)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
final BidRequest bannerAndNativeImpsRequest = createSingleRequest(bannerAndNativeImps, bidRequest, errors);
if (bannerAndNativeImpsRequest != null) {
bidRequests.add(bannerAndNativeImpsRequest);
}

if (CollectionUtils.isNotEmpty(videoImps)) {
Expand All @@ -128,16 +140,33 @@ private static OpenxImpType resolveImpType(Imp imp) {
if (imp.getVideo() != null) {
return OpenxImpType.video;
}
if (imp.getXNative() != null) {
return OpenxImpType.xNative;
}
return OpenxImpType.other;
}

private static BidType resolveBidType(Imp imp) {
if (imp.getBanner() != null) {
return BidType.banner;
}
if (imp.getVideo() != null) {
return BidType.video;
}
if (imp.getXNative() != null) {
return BidType.xNative;
}
return BidType.banner;
}

private List<BidderError> errors(List<Imp> notSupportedImps, List<BidderError> processingErrors) {
final List<BidderError> errors = new ArrayList<>();
// add errors for imps with unsupported media types
if (CollectionUtils.isNotEmpty(notSupportedImps)) {
errors.addAll(
notSupportedImps.stream()
.map(imp -> "OpenX only supports banner and video imps. Ignoring imp id=" + imp.getId())
.map(imp ->
"OpenX only supports banner, video and native imps. Ignoring imp id=" + imp.getId())
.map(BidderError::badInput)
.toList());
}
Expand Down Expand Up @@ -276,7 +305,7 @@ private static ExtBidPrebidVideo getVideoInfo(Bid bid) {

private static Map<String, BidType> impIdToBidType(BidRequest bidRequest) {
return bidRequest.getImp().stream()
.collect(Collectors.toMap(Imp::getId, imp -> imp.getBanner() != null ? BidType.banner : BidType.video));
.collect(Collectors.toMap(Imp::getId, OpenxBidder::resolveBidType));
}

private static BidType getBidType(Bid bid, Map<String, BidType> impIdToBidType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public enum OpenxImpType {

// supported
banner, video,
banner, video, xNative,
// not supported
other
}
2 changes: 2 additions & 0 deletions src/main/resources/bidder-config/openx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ adapters:
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 69
usersync:
Expand Down
220 changes: 197 additions & 23 deletions src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,10 @@ public void makeHttpRequestsShouldReturnResultWithErrorWhenAudioImpsPresent() {
assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(2)
.containsExactly(
BidderError.badInput("OpenX only supports banner and video imps. Ignoring imp id=impId1"),
BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId2"));
}

@Test
public void makeHttpRequestsShouldReturnResultWithErrorWhenNativeImpsPresent() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(asList(
Imp.builder().id("impId1").xNative(Native.builder().build()).build(),
Imp.builder().id("impId2").xNative(Native.builder().build()).build()))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(2)
.containsExactly(
BidderError.badInput("OpenX only supports banner and video imps. Ignoring imp id=impId1"),
"OpenX only supports banner, video and native imps. Ignoring imp id=impId1"),
BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId2"));
"OpenX only supports banner, video and native imps. Ignoring imp id=impId2"));
}

@Test
Expand Down Expand Up @@ -254,7 +234,7 @@ public void makeHttpRequestsShouldReturnResultWithExpectedFieldsSet() {
// then
assertThat(result.getErrors()).hasSize(1)
.containsExactly(BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId1"));
"OpenX only supports banner, video and native imps. Ignoring imp id=impId1"));

assertThat(result.getValue()).hasSize(3)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
Expand Down Expand Up @@ -344,6 +324,158 @@ public void makeHttpRequestsShouldReturnResultWithExpectedFieldsSet() {
.build());
}

@Test
public void makeHttpRequestsShouldReturnResultWithSingleBidRequestForMultipleBannerAndNativeImps() {
// given
final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId4")
.banner(Banner.builder().build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo4", "bar4"))
.delDomain("se-demo-d.openx.net")
.unit("4").build()))).build(),
Imp.builder()
.id("impId5")
.xNative(Native.builder().request("{\"testreq\":1}").build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo5", "bar5"))
.delDomain("se-demo-d.openx.net")
.unit("5").build()))).build(),
Imp.builder()
.id("impId6")
.xNative(Native.builder().build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo6", "bar6"))
.delDomain("se-demo-d.openx.net")
.unit("6").build()))).build()))
.user(User.builder().ext(ExtUser.builder().consent("consent").build()).build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();

assertThat(result.getValue()).hasSize(1)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.containsExactly(
// check if all native and banner imps are part of single bidRequest
BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId4")
.tagid("4")
.banner(Banner.builder().build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo4", "bar4"))
.build()))
.build(),
Imp.builder()
.id("impId5")
.tagid("5")
.xNative(Native.builder().request("{\"testreq\":1}").build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo5", "bar5"))
.build()))
.build(),
Imp.builder()
.id("impId6")
.tagid("6")
.xNative(Native.builder().build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo6", "bar6"))
.build()))
.build()))
.ext(jacksonMapper.fillExtension(
ExtRequest.empty(),
OpenxRequestExt.of("se-demo-d.openx.net", null, "hb_pbs_1.0.0")))
.user(User.builder()
.ext(ExtUser.builder().consent("consent").build())
.build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build());
}

@Test
public void makeHttpRequestsShouldReturnResultWithSingleBidRequestForMultiFormatImps() {
// given
final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId1")
.banner(Banner.builder().w(320).h(200).build())
.video(Video.builder().maxduration(10).build())
.ext(mapper.valueToTree(
ExtPrebid.of(null, ExtImpOpenx.builder().unit("1").build())))
.build(),
Imp.builder()
.id("impId2")
.banner(Banner.builder().w(300).h(150).build())
.xNative(Native.builder().request("{\"version\":1}").build())
.ext(mapper.valueToTree(
ExtPrebid.of(null, ExtImpOpenx.builder().unit("2").build())))
.build()))
.user(User.builder().ext(ExtUser.builder().consent("consent").build()).build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();

assertThat(result.getValue()).hasSize(1)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.containsExactly(
// check if all native and banner imps are part of single bidRequest
BidRequest.builder()
.id("bidRequestId")
.imp(asList(
// verify banner and video media types are preserved in a single imp
Imp.builder()
.id("impId1")
.tagid("1")
.banner(Banner.builder().w(320).h(200).build())
.video(Video.builder().maxduration(10).build())
.ext(mapper.valueToTree(ExtImpOpenx.builder().build())).build(),
// verify banner and native media types are preserved in a single imp
Imp.builder()
.id("impId2")
.tagid("2")
.banner(Banner.builder().w(300).h(150).build())
.xNative(Native.builder().request("{\"version\":1}").build())
.ext(mapper.valueToTree(ExtImpOpenx.builder().build()))
.build()))
.ext(jacksonMapper.fillExtension(
ExtRequest.empty(),
OpenxRequestExt.of(null, null, "hb_pbs_1.0.0")))
.user(User.builder()
.ext(ExtUser.builder().consent("consent").build())
.build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build());
}

@Test
public void makeHttpRequestsShouldPassThroughImpExt() {
// given
Expand Down Expand Up @@ -523,6 +655,48 @@ public void makeBidsShouldReturnResultWithExpectedFields() throws JsonProcessing
.build());
}

@Test
public void makeBidsShouldReturnResultForNativeBidsWithExpectedFields() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(mapper.writeValueAsString(OpenxBidResponse.builder()
.seatbid(singletonList(SeatBid.builder()
.bid(singletonList(Bid.builder()
.w(200)
.h(150)
.price(BigDecimal.ONE)
.impid("impId1")
.adm("{\"ver\":\"1.2\"}")
.build()))
.build()))
.cur("UAH")
.ext(OpenxBidResponseExt.of(Map.of("impId1", mapper.createObjectNode().put("somevalue", 1))))
.build()));

final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(singletonList(Imp.builder()
.id("impId1")
.xNative(Native.builder().request("{\"ver\":\"1.2\",\"plcmttype\":3}").build())
.build()))
.build();

// when
final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getBids()).hasSize(1)
.containsOnly(BidderBid.of(
Bid.builder()
.impid("impId1")
.price(BigDecimal.ONE)
.w(200)
.h(150)
.adm("{\"ver\":\"1.2\"}")
.build(),
BidType.xNative, "UAH"));
}

@Test
public void makeBidsShouldReturnVideoInfoWhenAvailable() throws JsonProcessingException {
// given
Expand Down
Loading