From 418baed6193f8309d4db627a67e24ec67c9ba525 Mon Sep 17 00:00:00 2001 From: Sanoska Date: Thu, 12 Jul 2018 16:09:23 -0400 Subject: [PATCH 1/7] initial commit --- integrationExamples/gpt/pbjs_example_gpt.html | 6 + modules/zedoBidAdapter.js | 188 ++++++++++++++++++ modules/zedoBidAdapter.md | 27 +++ test/spec/modules/zedoBidAdapter_spec.js | 167 ++++++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 modules/zedoBidAdapter.js create mode 100644 modules/zedoBidAdapter.md create mode 100644 test/spec/modules/zedoBidAdapter_spec.js diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index e54a604e281..0cc7aca7cc4 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -288,6 +288,12 @@ pubId: 50357, //REQUIRED host: 'dsp-staging.adkernel.com' //OPTIONAL } + }, + { + bidder: 'zedo', + params: { + channelCode: 2264002816, //REQUIRED + } } ] }, { diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js new file mode 100644 index 00000000000..966cc03b45f --- /dev/null +++ b/modules/zedoBidAdapter.js @@ -0,0 +1,188 @@ +import * as utils from 'src/utils'; +import { registerBidder } from 'src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; + +const BIDDER_CODE = 'zedo'; +const URL = '//z2.zedo.com/asw/fmb.json'; +const SECURE_URL = '//z2.zedo.com/asw/fmb.json'; +const SIZE = { + '300x250': 9, + '160x600': 88, + '640x480': 85 // TODO check for 1x1 +}; + +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.channelCode); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests) { + let data = { + placements: [] + }; + bidRequests.map(bidRequest => { + let channelCode = parseInt(bidRequest.params.channelCode); + let network = parseInt(channelCode / 1000000); + let channel = channelCode % 1000000; + let dims = getSizes(bidRequest.sizes); + let placement = { + id: bidRequest.bidId, + network: network, + channel: channel, + width: dims[0][0] ? dims[0][0] : 468, + height: dims[0][1] ? dims[0][1] : 60, + dimension: dims[0][2] ? dims[0][2] : 9, // TODO : check default + version: '$prebid.version$', + transactionId: bidRequest.transactionId + } + const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`); + if (bidRequest.mediaType === VIDEO || videoMediaType) { + placement['renderers'] = [{ + 'name': 'Inarticle' + }] + } else { + placement['renderers'] = [{ + 'name': 'display' + }] + } + data['placements'].push(placement); + }); + let reqUrl = utils.getTopWindowLocation().protocol === 'http:' ? URL : SECURE_URL; + return { + method: 'GET', + url: reqUrl, + data: 'g=' + JSON.stringify(data) + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${request.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.ad) { + serverResponse.ad.forEach(ad => { + const creativeBid = getCreative(ad); + if (creativeBid) { + if (parseInt(creativeBid.cpm) !== 0) { + const bid = newBid(ad, creativeBid, request); + bid.mediaType = parseMediaType(creativeBid); + bids.push(bid); + } + } + }); + } + return bids; + }, + + getUserSyncs: function (syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + // TODO implement user sync + }]; + } + } +}; + +function getCreative(ad) { + return ad && ad.creatives && ad.creatives.length && find(ad.creatives, creative => creative.adId); +} +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, creativeBid, bidderRequest) { + const bid = { + requestId: serverBid.slotId, + cpm: creativeBid.cpm, + creativeId: creativeBid.adId, + dealId: 99999999, + currency: 'USD', + netRevenue: true, + ttl: 300 + }; + + if (creativeBid.creativeDetails.type === 'Vast') { + Object.assign(bid, { + width: creativeBid.width, + height: creativeBid.height, + vastXml: creativeBid.creativeDetails.adContent, + ttl: 3600 + }); + } else { + Object.assign(bid, { + width: creativeBid.width, + height: creativeBid.height, + ad: creativeBid.creativeDetails.adContent + }); + } + + return bid; +} +/* Turn bid request sizes into ut-compatible format */ +function getSizes(requestSizes) { + let dims = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; + if (dim) { + dims.push([sizeObj.width, sizeObj.height, dim]); + } + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; + dims.push([sizeObj.width, sizeObj.height, dim]); + } + } + return dims; +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else { + return BANNER; + } +} + +registerBidder(spec); diff --git a/modules/zedoBidAdapter.md b/modules/zedoBidAdapter.md new file mode 100644 index 00000000000..203fc0b9c7e --- /dev/null +++ b/modules/zedoBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: ZEDO Bidder Adapter +Module Type: Bidder Adapter +Maintainer: + +# Description + +Module that connects to ZEDO's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'zedo', + params: { + code: 2264002816 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js new file mode 100644 index 00000000000..05a75ab5d0e --- /dev/null +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -0,0 +1,167 @@ +import { expect } from 'chai'; +import { spec } from 'modules/zedoBidAdapter'; + +describe('The ZEDO bidding adapter', () => { + describe('isBidRequestValid', () => { + it('should return false when given an invalid bid', () => { + const bid = { + bidder: 'zedo', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a channelcode bid', () => { + const bid = { + bidder: 'zedo', + params: { + channelCode: 20000000, + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', () => { + const bidderRequest = { + timeout: 3000, + }; + + it('should properly build a channelCode request for banner', () => { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 250]], + params: { + channelCode: 20000000, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":250,"dimension":9,"version":"$prebid.version$","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); + }); + + it('should properly build a channelCode request for video', () => { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream', + }, + }, + params: { + channelCode: 20000000, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); + }); + }); + describe('interpretResponse', () => { + it('should return an empty array when there is bid response', () => { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with no valid creative', () => { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': { + 'focImage': { + 'url': 'https://c13.zedo.com/OzoDB/0/0/0/blank.gif', + 'target': '_blank', + } + } + }, + 'cpm': '0' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'p12345', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with valid creative', () => { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': '' + }, + 'cpm': '1.2' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal('1.2'); + expect(bids[0].width).to.equal('160'); + expect(bids[0].height).to.equal('600'); + }); + }); +}); From 4381ccddf158260f86536c8d531bd4b22660f4d9 Mon Sep 17 00:00:00 2001 From: Sanoska Date: Fri, 13 Jul 2018 17:14:30 -0400 Subject: [PATCH 2/7] updated contact and tag details --- modules/zedoBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/zedoBidAdapter.md b/modules/zedoBidAdapter.md index 203fc0b9c7e..259c4c02dee 100644 --- a/modules/zedoBidAdapter.md +++ b/modules/zedoBidAdapter.md @@ -2,7 +2,7 @@ Module Name: ZEDO Bidder Adapter Module Type: Bidder Adapter -Maintainer: +Maintainer: prebidsupport@zedo.com # Description @@ -18,7 +18,7 @@ Module that connects to ZEDO's demand sources. { bidder: 'zedo', params: { - code: 2264002816 + code: 2264004118 } } ] From cd09dbcaf687fc0f7314ad70a63ec26cffddbe5d Mon Sep 17 00:00:00 2001 From: Sanoska Date: Mon, 16 Jul 2018 17:45:14 -0400 Subject: [PATCH 3/7] changes ti support the renderers --- modules/zedoBidAdapter.js | 26 ++++++++++++++++-------- test/spec/modules/zedoBidAdapter_spec.js | 8 ++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js index 966cc03b45f..38fca275de2 100644 --- a/modules/zedoBidAdapter.js +++ b/modules/zedoBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from 'src/utils'; import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; +import { BANNER, VIDEO } from 'src/mediaTypes'; import find from 'core-js/library/fn/array/find'; const BIDDER_CODE = 'zedo'; @@ -48,14 +48,15 @@ export const spec = { channel: channel, width: dims[0][0] ? dims[0][0] : 468, height: dims[0][1] ? dims[0][1] : 60, - dimension: dims[0][2] ? dims[0][2] : 9, // TODO : check default + dimension: dims[0][2] ? dims[0][2] : 0, // default to 0 version: '$prebid.version$', + keyword: '', transactionId: bidRequest.transactionId } const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`); if (bidRequest.mediaType === VIDEO || videoMediaType) { placement['renderers'] = [{ - 'name': 'Inarticle' + 'name': 'Pre/Mid/Post roll' }] } else { placement['renderers'] = [{ @@ -123,9 +124,10 @@ function getCreative(ad) { * @return Bid */ function newBid(serverBid, creativeBid, bidderRequest) { + let prShr = (parseInt(creativeBid.cpm) * 0.7) / 1000000; const bid = { requestId: serverBid.slotId, - cpm: creativeBid.cpm, + cpm: prShr, creativeId: creativeBid.adId, dealId: 99999999, currency: 'USD', @@ -133,7 +135,7 @@ function newBid(serverBid, creativeBid, bidderRequest) { ttl: 300 }; - if (creativeBid.creativeDetails.type === 'Vast') { + if (creativeBid.creativeDetails.type === 'VAST') { Object.assign(bid, { width: creativeBid.width, height: creativeBid.height, @@ -162,6 +164,8 @@ function getSizes(requestSizes) { let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; if (dim) { dims.push([sizeObj.width, sizeObj.height, dim]); + } else { + dims.push([sizeObj.width, sizeObj.height, 0]); } } else if (typeof requestSizes === 'object') { for (let i = 0; i < requestSizes.length; i++) { @@ -170,15 +174,19 @@ function getSizes(requestSizes) { sizeObj.width = parseInt(size[0], 10); sizeObj.height = parseInt(size[1], 10); let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; - dims.push([sizeObj.width, sizeObj.height, dim]); + if (dim) { + dims.push([sizeObj.width, sizeObj.height, dim]); + } else { + dims.push([sizeObj.width, sizeObj.height, 0]); + } } } return dims; } -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { +function parseMediaType(creativeBid) { + const adType = creativeBid.creativeDetails.type; + if (adType === 'VAST') { return VIDEO; } else { return BANNER; diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js index 05a75ab5d0e..8656eee39b6 100644 --- a/test/spec/modules/zedoBidAdapter_spec.js +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -44,7 +44,7 @@ describe('The ZEDO bidding adapter', () => { expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); expect(request.method).to.equal('GET'); const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":250,"dimension":9,"version":"$prebid.version$","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":250,"dimension":9,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); }); it('should properly build a channelCode request for video', () => { @@ -68,7 +68,7 @@ describe('The ZEDO bidding adapter', () => { expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); expect(request.method).to.equal('GET'); const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Pre/Mid/Post roll"}]}]}'); }); }); describe('interpretResponse', () => { @@ -139,7 +139,7 @@ describe('The ZEDO bidding adapter', () => { 'type': 'StdBanner', 'adContent': '' }, - 'cpm': '1.2' + 'cpm': '1200000' } ] } @@ -159,7 +159,7 @@ describe('The ZEDO bidding adapter', () => { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('ad1d762'); - expect(bids[0].cpm).to.equal('1.2'); + expect(bids[0].cpm).to.equal(0.84); expect(bids[0].width).to.equal('160'); expect(bids[0].height).to.equal('600'); }); From f134156bff05219f1fbf7c8579314017f11362e5 Mon Sep 17 00:00:00 2001 From: Sanoska Date: Tue, 17 Jul 2018 16:48:23 -0400 Subject: [PATCH 4/7] changes to pass dimId --- modules/zedoBidAdapter.js | 68 +++++++++++------------- modules/zedoBidAdapter.md | 3 ++ test/spec/modules/zedoBidAdapter_spec.js | 7 ++- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js index 38fca275de2..6a33749e1ef 100644 --- a/modules/zedoBidAdapter.js +++ b/modules/zedoBidAdapter.js @@ -6,10 +6,10 @@ import find from 'core-js/library/fn/array/find'; const BIDDER_CODE = 'zedo'; const URL = '//z2.zedo.com/asw/fmb.json'; const SECURE_URL = '//z2.zedo.com/asw/fmb.json'; -const SIZE = { - '300x250': 9, - '160x600': 88, - '640x480': 85 // TODO check for 1x1 +const RENDERER_TYPE = { + '9': 'display', + '88': 'display', + '85': 'Pre/Mid/Post roll', }; export const spec = { @@ -24,7 +24,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params && bid.params.channelCode); + return !!(bid.params && bid.params.channelCode && bid.params.dimId); }, /** @@ -41,24 +41,24 @@ export const spec = { let channelCode = parseInt(bidRequest.params.channelCode); let network = parseInt(channelCode / 1000000); let channel = channelCode % 1000000; - let dims = getSizes(bidRequest.sizes); + let dim = getSizes(bidRequest.sizes); let placement = { id: bidRequest.bidId, network: network, channel: channel, - width: dims[0][0] ? dims[0][0] : 468, - height: dims[0][1] ? dims[0][1] : 60, - dimension: dims[0][2] ? dims[0][2] : 0, // default to 0 + width: dim[0], + height: dim[1], + dimension: bidRequest.params.dimId, version: '$prebid.version$', keyword: '', transactionId: bidRequest.transactionId } - const videoMediaType = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`); - if (bidRequest.mediaType === VIDEO || videoMediaType) { + let renderName = RENDERER_TYPE[String(bidRequest.params.dimId)] + if (renderName) { placement['renderers'] = [{ - 'name': 'Pre/Mid/Post roll' + 'name': renderName }] - } else { + } else { // default to display placement['renderers'] = [{ 'name': 'display' }] @@ -106,8 +106,10 @@ export const spec = { getUserSyncs: function (syncOptions) { if (syncOptions.iframeEnabled) { + let url = utils.getTopWindowLocation().protocol === 'http:' ? 'http://d3.zedo.com/rs/us/fcs.html' : 'https://tt3.zedo.com/rs/us/fcs.html'; return [{ - // TODO implement user sync + type: 'iframe', + url: url }]; } } @@ -143,10 +145,17 @@ function newBid(serverBid, creativeBid, bidderRequest) { ttl: 3600 }); } else { + let tracker = ''; + if (creativeBid.trackingData && creativeBid.trackingData.impressionTrackers) { + let trackerArr = creativeBid.trackingData.impressionTrackers; + for (let i in trackerArr) { + tracker = tracker + ''; + } + } Object.assign(bid, { width: creativeBid.width, height: creativeBid.height, - ad: creativeBid.creativeDetails.adContent + ad: creativeBid.creativeDetails.adContent + tracker }); } @@ -154,34 +163,21 @@ function newBid(serverBid, creativeBid, bidderRequest) { } /* Turn bid request sizes into ut-compatible format */ function getSizes(requestSizes) { - let dims = []; - let sizeObj = {}; - + let width = 0; + let height = 0; if (utils.isArray(requestSizes) && requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; - if (dim) { - dims.push([sizeObj.width, sizeObj.height, dim]); - } else { - dims.push([sizeObj.width, sizeObj.height, 0]); - } + width = parseInt(requestSizes[0], 10); + height = parseInt(requestSizes[1], 10); } else if (typeof requestSizes === 'object') { for (let i = 0; i < requestSizes.length; i++) { let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - let dim = SIZE[sizeObj.width + 'x' + sizeObj.height]; - if (dim) { - dims.push([sizeObj.width, sizeObj.height, dim]); - } else { - dims.push([sizeObj.width, sizeObj.height, 0]); - } + width = parseInt(size[0], 10); + height = parseInt(size[1], 10); + break; } } - return dims; + return [width, height]; } function parseMediaType(creativeBid) { diff --git a/modules/zedoBidAdapter.md b/modules/zedoBidAdapter.md index 259c4c02dee..9ffcd61f164 100644 --- a/modules/zedoBidAdapter.md +++ b/modules/zedoBidAdapter.md @@ -8,6 +8,8 @@ Maintainer: prebidsupport@zedo.com Module that connects to ZEDO's demand sources. +For video integration, ZEDO returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction + # Test Parameters ``` var adUnits = [ @@ -19,6 +21,7 @@ Module that connects to ZEDO's demand sources. bidder: 'zedo', params: { code: 2264004118 + dimId: 9 } } ] diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js index 8656eee39b6..7531ae2fe5e 100644 --- a/test/spec/modules/zedoBidAdapter_spec.js +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -16,6 +16,7 @@ describe('The ZEDO bidding adapter', () => { bidder: 'zedo', params: { channelCode: 20000000, + dimId: 9 }, }; const isValid = spec.isBidRequestValid(bid); @@ -37,6 +38,7 @@ describe('The ZEDO bidding adapter', () => { sizes: [[300, 250]], params: { channelCode: 20000000, + dimId: 9 }, }, ]; @@ -61,6 +63,7 @@ describe('The ZEDO bidding adapter', () => { }, params: { channelCode: 20000000, + dimId: 85 }, }, ]; @@ -115,6 +118,7 @@ describe('The ZEDO bidding adapter', () => { bidId: 'test-bidId', params: { channelCode: 2000000, + dimId: 9 } }] }; @@ -152,7 +156,8 @@ describe('The ZEDO bidding adapter', () => { adUnitCode: 'test-requestId', bidId: 'test-bidId', params: { - zoneId: 123, + channelCode: 2000000, + dimId: 9 }, }] }; From c5293b7d6ad717935610a9c37258219f9d59508c Mon Sep 17 00:00:00 2001 From: Sanoska Date: Thu, 19 Jul 2018 16:30:31 -0400 Subject: [PATCH 5/7] fixed names of internal mapping --- integrationExamples/gpt/pbjs_example_gpt.html | 1 + modules/zedoBidAdapter.js | 29 ++++---- test/spec/modules/zedoBidAdapter_spec.js | 70 +++++++++++++++++-- 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 0cc7aca7cc4..b3c776e101c 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -293,6 +293,7 @@ bidder: 'zedo', params: { channelCode: 2264002816, //REQUIRED + dimId: 9 } } ] diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js index 6a33749e1ef..d97c1fc1de2 100644 --- a/modules/zedoBidAdapter.js +++ b/modules/zedoBidAdapter.js @@ -6,10 +6,16 @@ import find from 'core-js/library/fn/array/find'; const BIDDER_CODE = 'zedo'; const URL = '//z2.zedo.com/asw/fmb.json'; const SECURE_URL = '//z2.zedo.com/asw/fmb.json'; -const RENDERER_TYPE = { +const DIM_TYPE = { + '7': 'display', '9': 'display', - '88': 'display', - '85': 'Pre/Mid/Post roll', + '14': 'display', + '70': 'SBR', + '83': 'CurtainRaiser', + '85': 'Inarticle', + '86': 'pswipeup', + '88': 'Inview', + // '85': 'pre-mid-post-roll', }; export const spec = { @@ -53,10 +59,10 @@ export const spec = { keyword: '', transactionId: bidRequest.transactionId } - let renderName = RENDERER_TYPE[String(bidRequest.params.dimId)] - if (renderName) { + let dimType = DIM_TYPE[String(bidRequest.params.dimId)] + if (dimType) { placement['renderers'] = [{ - 'name': renderName + 'name': dimType }] } else { // default to display placement['renderers'] = [{ @@ -145,23 +151,16 @@ function newBid(serverBid, creativeBid, bidderRequest) { ttl: 3600 }); } else { - let tracker = ''; - if (creativeBid.trackingData && creativeBid.trackingData.impressionTrackers) { - let trackerArr = creativeBid.trackingData.impressionTrackers; - for (let i in trackerArr) { - tracker = tracker + ''; - } - } Object.assign(bid, { width: creativeBid.width, height: creativeBid.height, - ad: creativeBid.creativeDetails.adContent + tracker + ad: creativeBid.creativeDetails.adContent }); } return bid; } -/* Turn bid request sizes into ut-compatible format */ +/* Turn bid request sizes into compatible format */ function getSizes(requestSizes) { let width = 0; let height = 0; diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js index 7531ae2fe5e..3834e672bfe 100644 --- a/test/spec/modules/zedoBidAdapter_spec.js +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -29,16 +29,16 @@ describe('The ZEDO bidding adapter', () => { timeout: 3000, }; - it('should properly build a channelCode request for banner', () => { + it('should properly build a channelCode request for dim Id with type not defined', () => { const bidRequests = [ { bidder: 'zedo', adUnitCode: 'p12345', transactionId: '12345667', - sizes: [[300, 250]], + sizes: [[300, 200]], params: { channelCode: 20000000, - dimId: 9 + dimId: 10 }, }, ]; @@ -46,10 +46,10 @@ describe('The ZEDO bidding adapter', () => { expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); expect(request.method).to.equal('GET'); const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":250,"dimension":9,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); }); - it('should properly build a channelCode request for video', () => { + it('should properly build a channelCode request for video with type defined', () => { const bidRequests = [ { bidder: 'zedo', @@ -71,7 +71,7 @@ describe('The ZEDO bidding adapter', () => { expect(request.url).to.match(/^\/\/z2.zedo.com\/asw\/fmb.json/); expect(request.method).to.equal('GET'); const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Pre/Mid/Post roll"}]}]}'); + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); }); }); describe('interpretResponse', () => { @@ -126,7 +126,7 @@ describe('The ZEDO bidding adapter', () => { expect(bids).to.have.lengthOf(0); }); - it('should properly parse a bid response with valid creative', () => { + it('should properly parse a bid response with valid display creative', () => { const response = { body: { ad: [ @@ -168,5 +168,61 @@ describe('The ZEDO bidding adapter', () => { expect(bids[0].width).to.equal('160'); expect(bids[0].height).to.equal('600'); }); + + it('should properly parse a bid response with valid video creative', () => { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '480', + 'width': '640', + 'isFoc': true, + 'creativeDetails': { + 'type': 'VAST', + 'adContent': '' + }, + 'cpm': '1200000' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 85 + }, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal(0.84); + expect(bids[0].width).to.equal('640'); + expect(bids[0].height).to.equal('480'); + expect(bids[0].vastXml).to.not.equal(''); + expect(bids[0].ad).to.be.an('undefined'); + }); + }); + + describe('user sync', () => { + it('should register the iframe sync url', () => { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); }); }); From bf996ec4cc126a3b4936e62c2657ab6950352a97 Mon Sep 17 00:00:00 2001 From: Sanoska Date: Fri, 20 Jul 2018 10:56:18 -0400 Subject: [PATCH 6/7] added comment --- integrationExamples/gpt/pbjs_example_gpt.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index b3c776e101c..fbf21f1f856 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -293,7 +293,7 @@ bidder: 'zedo', params: { channelCode: 2264002816, //REQUIRED - dimId: 9 + dimId: 9 //REQUIRED } } ] From c768fe50142215cc604609d3cb5cf8a8e015f048 Mon Sep 17 00:00:00 2001 From: Sanoska Date: Mon, 23 Jul 2018 16:03:29 -0400 Subject: [PATCH 7/7] added gdpr param to request and other fixes --- modules/zedoBidAdapter.js | 22 +++++++++--- test/spec/modules/zedoBidAdapter_spec.js | 44 ++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js index d97c1fc1de2..0420c479ae9 100644 --- a/modules/zedoBidAdapter.js +++ b/modules/zedoBidAdapter.js @@ -39,7 +39,7 @@ export const spec = { * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (bidRequests) { + buildRequests: function (bidRequests, bidderRequest) { let data = { placements: [] }; @@ -59,6 +59,12 @@ export const spec = { keyword: '', transactionId: bidRequest.transactionId } + if (bidderRequest && bidderRequest.gdprConsent) { + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + data.gdpr = Number(bidderRequest.gdprConsent.gdprApplies); + } + data.gdpr_consent = bidderRequest.gdprConsent.consentString; + } let dimType = DIM_TYPE[String(bidRequest.params.dimId)] if (dimType) { placement['renderers'] = [{ @@ -110,9 +116,17 @@ export const spec = { return bids; }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function (syncOptions, responses, gdprConsent) { if (syncOptions.iframeEnabled) { let url = utils.getTopWindowLocation().protocol === 'http:' ? 'http://d3.zedo.com/rs/us/fcs.html' : 'https://tt3.zedo.com/rs/us/fcs.html'; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `?gdpr_consent=${gdprConsent.consentString}`; + } + } return [{ type: 'iframe', url: url @@ -132,10 +146,8 @@ function getCreative(ad) { * @return Bid */ function newBid(serverBid, creativeBid, bidderRequest) { - let prShr = (parseInt(creativeBid.cpm) * 0.7) / 1000000; const bid = { requestId: serverBid.slotId, - cpm: prShr, creativeId: creativeBid.adId, dealId: 99999999, currency: 'USD', @@ -148,12 +160,14 @@ function newBid(serverBid, creativeBid, bidderRequest) { width: creativeBid.width, height: creativeBid.height, vastXml: creativeBid.creativeDetails.adContent, + cpm: (parseInt(creativeBid.cpm) * 0.65) / 1000000, ttl: 3600 }); } else { Object.assign(bid, { width: creativeBid.width, height: creativeBid.height, + cpm: (parseInt(creativeBid.cpm) * 0.6) / 1000000, ad: creativeBid.creativeDetails.adContent }); } diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js index 3834e672bfe..6d0ab7c68f6 100644 --- a/test/spec/modules/zedoBidAdapter_spec.js +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -73,6 +73,36 @@ describe('The ZEDO bidding adapter', () => { const zedoRequest = request.data; expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); }); + + describe('buildGDPRRequests', () => { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + timeout: 3000, + gdprConsent: { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + it('should properly build request with gdpr consent', () => { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 200]], + params: { + channelCode: 20000000, + dimId: 10 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}],"gdpr":1,"gdpr_consent":"BOJ8RZsOJ8RZsABAB8AAAAAZ+A=="}'); + }); + }); }); describe('interpretResponse', () => { it('should return an empty array when there is bid response', () => { @@ -164,7 +194,7 @@ describe('The ZEDO bidding adapter', () => { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('ad1d762'); - expect(bids[0].cpm).to.equal(0.84); + expect(bids[0].cpm).to.equal(0.72); expect(bids[0].width).to.equal('160'); expect(bids[0].height).to.equal('600'); }); @@ -207,7 +237,7 @@ describe('The ZEDO bidding adapter', () => { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('ad1d762'); - expect(bids[0].cpm).to.equal(0.84); + expect(bids[0].cpm).to.equal(0.78); expect(bids[0].width).to.equal('640'); expect(bids[0].height).to.equal('480'); expect(bids[0].vastXml).to.not.equal(''); @@ -224,5 +254,15 @@ describe('The ZEDO bidding adapter', () => { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); }); + + it('should pass gdpr params', () => { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); + }); }); });