From 388ddee4b0d1a201d430a2ece5faa4e55616f8a9 Mon Sep 17 00:00:00 2001 From: Aleksei Shashin <90848837+alex-ylb@users.noreply.github.com> Date: Fri, 29 Oct 2021 10:56:37 +0200 Subject: [PATCH] Yieldlab Bid Adapter: add support for native mediatype (#7609) * YL-3989: Accept NATIVE yieldprobe response (#2) * YL-3989: Accept NATIVE response * Fix: 'utils' is not defined no-undef * trigger GitHub actions * Add multi-format example to the Yieldlab bidder documentation * Reformat code * Fix: Object doesn't support 'find' Object doesn't support property or method 'find' in IE 11 * trigger GitHub actions * Chore:Replace `filter` by `find` from ..array/find.js * Fix typo Co-authored-by: Christoph <29540638+kippsterr@users.noreply.github.com> --- modules/yieldlabBidAdapter.js | 51 +++++-- modules/yieldlabBidAdapter.md | 139 ++++++++++++------- test/spec/modules/yieldlabBidAdapter_spec.js | 81 +++++++++++ 3 files changed, 213 insertions(+), 58 deletions(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 994098cf5c8..c2f2b79a3b7 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,7 +1,7 @@ import { _each, isPlainObject, isArray, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import find from 'core-js-pure/features/array/find.js' -import { VIDEO, BANNER } from '../src/mediaTypes.js' +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js' import { Renderer } from '../src/Renderer.js' import { config } from '../src/config.js'; @@ -15,7 +15,7 @@ const GVLID = 70 export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function (bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { @@ -149,6 +149,27 @@ export const spec = { } } + if (isNative(bidRequest, adType)) { + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` + bidResponse.adUrl = url + bidResponse.mediaType = NATIVE + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : {url: '', w: 0, h: 0}; + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + bidResponse.native = { + title: nativeTitleAsset ? nativeTitleAsset.title.text : '', + body: nativeBodyAsset ? nativeBodyAsset.data.value : '', + image: { + url: nativeImageAsset.url, + width: nativeImageAsset.w, + height: nativeImageAsset.h, + }, + clickUrl: matchedBid.native.link.url, + impressionTrackers: matchedBid.native.imptrackers, + }; + } + bidResponses.push(bidResponse) } }) @@ -162,16 +183,26 @@ export const spec = { * @param {String} adtype * @returns {Boolean} */ -function isVideo (format, adtype) { +function isVideo(format, adtype) { return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' } +/** + * Is this a native format? + * @param {Object} format + * @param {String} adtype + * @returns {Boolean} + */ +function isNative(format, adtype) { + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' +} + /** * Is this an outstream context? * @param {Object} format * @returns {Boolean} */ -function isOutstream (format) { +function isOutstream(format) { let context = deepAccess(format, 'mediaTypes.video.context') return (context === 'outstream') } @@ -181,7 +212,7 @@ function isOutstream (format) { * @param {Object} format * @returns {Array} */ -function getPlayerSize (format) { +function getPlayerSize(format) { let playerSize = deepAccess(format, 'mediaTypes.video.playerSize') return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize } @@ -191,7 +222,7 @@ function getPlayerSize (format) { * @param {String} size * @returns {Array} */ -function parseSize (size) { +function parseSize(size) { return size.split('x').map(Number) } @@ -200,7 +231,7 @@ function parseSize (size) { * @param {Array} eids * @returns {String} */ -function createUserIdString (eids) { +function createUserIdString(eids) { let str = [] for (let i = 0; i < eids.length; i++) { str.push(eids[i].source + ':' + eids[i].uids[0].id) @@ -213,7 +244,7 @@ function createUserIdString (eids) { * @param {Object} obj * @returns {String} */ -function createQueryString (obj) { +function createQueryString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { @@ -233,7 +264,7 @@ function createQueryString (obj) { * @param {Object} obj * @returns {String} */ -function createTargetingString (obj) { +function createTargetingString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { @@ -250,7 +281,7 @@ function createTargetingString (obj) { * @param {Object} schain * @returns {String} */ -function createSchainString (schain) { +function createSchainString(schain) { const ver = schain.ver || '' const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index e3360ab10be..1f52e26f5c7 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -11,53 +11,96 @@ Maintainer: solutions@yieldlab.de Module that connects to Yieldlab's demand sources # Test Parameters + +```javascript +const adUnits = [ + { + code: 'banner', + sizes: [ [ 728, 90 ] ], + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220336', + supplyId: '1381604', + targeting: { + key1: 'value1', + key2: 'value2' + }, + extId: 'abc', + iabContent: { + id: 'some_id', + episode: '1', + title: 'some title', + series: 'some series', + season: 's1', + artist: 'John Doe', + genre: 'some genre', + isrc: 'CC-XXX-YY-NNNNN', + url: 'http://foo_url.de', + cat: [ 'IAB1-1', 'IAB1-2', 'IAB2-10' ], + context: '7', + keywords: ['k1', 'k2'], + live: '0' + } + } + }] + }, + { + code: 'video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + context: 'instream' // or 'outstream' + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + }, + { + code: 'native', + mediaTypes: { + native: { + // native config + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + } +]; ``` - var adUnits = [ - { - code: "banner", - sizes: [[728, 90]], - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220336", - supplyId: "1381604", - targeting: { - key1: "value1", - key2: "value2" - }, - extId: "abc", - iabContent: { - id: "some_id", - episode: "1", - title: "some title", - series: "some series", - season: "s1", - artist: "John Doe", - genre: "some genre", - isrc: "CC-XXX-YY-NNNNN", - url: "http://foo_url.de", - cat: ["IAB1-1", "IAB1-2", "IAB2-10"], - context: "7", - keywords: ["k1", "k2"], - live: "0" - } - } - }] - }, { - code: "video", - sizes: [[640, 480]], - mediaTypes: { - video: { - context: "instream" // or "outstream" - } - }, - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220339", - supplyId: "1381604" - } - }] - } - ]; + +# Multi-Format Setup + +A general overview of how to set up multi-format ads can be found in the offical Prebid.js docs. See: [show multi-format ads](https://docs.prebid.org/dev-docs/show-multi-format-ads.html) + +When setting up multi-format ads with Yieldlab make sure to always add at least one eligible Adslot per given media type in the ad unit configuration. + +```javascript +const adUnit = { + code: 'multi-format-adslot', + mediaTypes: { + banner: { + sizes: [ [ 728, 90 ] ] + }, + native: { + // native config + } + }, + bids: [ + // banner Adslot + { bidder: 'yieldlab', params: { adslotId: '1234', supplyId: '42' } }, + // native Adslot + { bidder: 'yieldlab', params: { adslotId: '2345', supplyId: '42' } } + ] +}; ``` diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 698bfb92888..f80cad46d50 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -73,6 +73,12 @@ const VIDEO_REQUEST = Object.assign({}, REQUEST, { } }) +const NATIVE_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'native': { } + } +}) + const RESPONSE = { advertiser: 'yieldlab', curl: 'https://www.yieldlab.de', @@ -84,6 +90,42 @@ const RESPONSE = { adtype: 'BANNER' } +const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { + 'adtype': 'NATIVE', + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'This is a great headline' + } + }, + { + 'id': 2, + 'img': { + 'url': 'https://localhost:8080/yl-logo100x100.jpg', + 'w': 100, + 'h': 100 + } + }, + { + 'id': 3, + 'data': { + 'value': 'Native body value' + } + } + ], + 'imptrackers': [ + 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', + 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' + ] + } +}) + const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { 'adtype': 'VIDEO' }) @@ -297,6 +339,45 @@ describe('yieldlabBidAdapter', function () { expect(result[0].vastUrl).to.include('&id=abc') }) + it('should add adUrl and native assets when type is Native', function () { + const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('This is a great headline') + expect(result[0].native.body).to.equal('Native body value') + expect(result[0].native.image.url).to.equal('https://localhost:8080/yl-logo100x100.jpg') + expect(result[0].native.image.width).to.equal(100) + expect(result[0].native.image.height).to.equal(100) + expect(result[0].native.clickUrl).to.equal('https://www.yieldlab.de') + expect(result[0].native.impressionTrackers.length).to.equal(3) + }) + + it('should add adUrl and default native assets when type is Native', function () { + const NATIVE_RESPONSE_2 = Object.assign({}, NATIVE_RESPONSE, { + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [], + 'imptrackers': [] + } + }) + const result = spec.interpretResponse({body: [NATIVE_RESPONSE_2]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('') + expect(result[0].native.body).to.equal('') + expect(result[0].native.image.url).to.equal('') + expect(result[0].native.image.width).to.equal(0) + expect(result[0].native.image.height).to.equal(0) + }) + it('should append gdpr parameters to vastUrl', function () { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_GDPR})