Skip to content

Commit eb50bd9

Browse files
hamsterreadymike-chowla
authored andcommitted
LiveYield Analytics Adapter DfP Support (prebid#3506)
* LiveYield Analytics Adapter * tests corrections * fixed getPlacementOrAdUnitCode function * DFP support * SRTA-399 WIP * DFP handler corrections, tests added * test added * docs added * SRTA-399 LiveYield DfP support SRTA-399 Minor reformats impression value fixed changed path to rta params corrections resolveSlot fixed postprocess() param fixed docs update imprVal(), imprPartner() impl added SRTA-399 Docs, no console.log * SRTA-410 Default implementations based on hb_adid * SRTA-399 chai usage fix
1 parent edf6f40 commit eb50bd9

File tree

2 files changed

+581
-34
lines changed

2 files changed

+581
-34
lines changed

modules/liveyieldAnalyticsAdapter.js

+272-30
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,174 @@ const adapterConfig = {
2626
},
2727

2828
/**
29-
* Function used to extract placement/adUnitCode (depending on prebid version).
29+
* Function used to extract placement/adUnitCode (depending on prebid
30+
* version).
3031
*
3132
* The extracted value will be passed to the `getAdUnitName()` for mapping into
3233
* human friendly value.
3334
*/
3435
getPlacementOrAdUnitCode: function(bid, version) {
3536
return version[0] === '0' ? bid.placementCode : bid.adUnitCode;
37+
},
38+
39+
/**
40+
* Optional reference to Google Publisher Tag (gpt)
41+
*/
42+
googlePublisherTag: false,
43+
44+
/**
45+
* Do not override unless instructed. Useful for testing. Allows to redefined
46+
* the event that triggers the ad impression event.
47+
*/
48+
wireGooglePublisherTag: function(gpt, cb) {
49+
gpt.pubads().addEventListener('slotRenderEnded', function(event) {
50+
cb(event.slot);
51+
});
52+
},
53+
54+
/**
55+
* Map which keeps BID_WON events. Keyed by adId property.
56+
*/
57+
prebidWinnersCache: {},
58+
59+
/**
60+
* Map which keeps all BID_RESPONSE events. Keyed by adId property.
61+
*/
62+
prebidBidResponsesCache: {},
63+
64+
/**
65+
* Decides if the GPT slot contains prebid ad impression or not.
66+
*
67+
* When BID_WON event is emitted adid is added to prebidWinnersCache,
68+
* then we check if prebidWinnersCache contains slot.hb_adid.
69+
*
70+
* This function is optional and used only when googlePublisherTag is provided.
71+
*
72+
* Default implementation uses slot's `hb_adid` targeting parameter.
73+
*
74+
* @param slot the gpt slot
75+
*/
76+
isPrebidAdImpression: function(slot) {
77+
const hbAdIdTargeting = slot.getTargeting('hb_adid');
78+
if (hbAdIdTargeting.length > 0) {
79+
const hbAdId = hbAdIdTargeting[0];
80+
return typeof this.prebidWinnersCache[hbAdId] !== 'undefined';
81+
}
82+
return false;
83+
},
84+
85+
/**
86+
* If isPrebidAdImpression decides that slot contain prebid ad impression,
87+
* this function should return prebids highest ad impression partner for that
88+
* slot.
89+
*
90+
* Default implementation uses slot's `hb_adid` targeting value to find
91+
* highest bid response and when present then returns `bidder`.
92+
*
93+
* @param instanceConfig merged analytics adapter instance configuration
94+
* @param slot the gpt slot for which the name of the highest bidder shall be
95+
* returned
96+
* @param version the version of the prebid.js library
97+
*/
98+
getHighestPrebidAdImpressionPartner: function(instanceConfig, slot, version) {
99+
const bid = getHighestPrebidBidResponseBySlotTargeting(
100+
instanceConfig,
101+
slot,
102+
version
103+
);
104+
105+
// this is bid response event has `bidder` while bid won has bidderCode property
106+
return bid ? bid.bidderCode || bid.bidder : null;
107+
},
108+
109+
/**
110+
* If isPrebidAdImpression decides that slot contain prebid ad impression,
111+
* this function should return prebids highest ad impression value for that
112+
* slot.
113+
*
114+
* Default implementation uses slot's `hb_adid` targeting value to find
115+
* highest bid response and when present then returns `cpm`.
116+
*
117+
* @param instanceConfig merged analytics adapter instance configuration
118+
* @param slot the gpt slot for which the highest ad impression value shall be
119+
* returned
120+
* @param version the version of the prebid.js library
121+
*/
122+
getHighestPrebidAdImpressionValue: function(instanceConfig, slot, version) {
123+
const bid = getHighestPrebidBidResponseBySlotTargeting(
124+
instanceConfig,
125+
slot,
126+
version
127+
);
128+
129+
return bid ? bid.cpm : null;
130+
},
131+
132+
/**
133+
* This function should return proper ad unit name for slot given as a
134+
* parameter. Unit names returned by this function should be meaningful, for
135+
* example 'FOO_728x90_TOP'. The values returned shall be inline with
136+
* `getAdUnitName`.
137+
*
138+
* Required when googlePublisherTag is defined.
139+
*
140+
* @param slot the gpt slot to translate into friendly name
141+
* @param version the version of the prebid.js library
142+
*/
143+
getAdUnitNameByGooglePublisherTagSlot: (slot, version) => {
144+
throw 'Required when googlePublisherTag is defined.';
145+
},
146+
147+
/**
148+
* Function used to prepare and return parameters provided to rta.
149+
* More information will be in docs given by LiveYield team.
150+
*
151+
* When googlePublisherTag is not provided, second parameter(slot) will always
152+
* equal null.
153+
*
154+
* @param resolution the original ad impression details
155+
* @param slot gpt slot, will be empty in pure Prebid.js-case (when
156+
* googlePublisherTag is not provided)
157+
* @param hbPartner the name of the highest bidding partner
158+
* @param hbValue the value of the highest bid
159+
* @param version version of the prebid.js library
160+
*/
161+
postProcessResolution: (resolution, slot, hbPartner, hbValue, version) => {
162+
return resolution;
36163
}
37164
};
38165

39166
const cpmToMicroUSD = v => (isNaN(v) ? 0 : Math.round(v * 1000));
40167

168+
const getHighestPrebidBidResponseBySlotTargeting = function(
169+
instanceConfig,
170+
slot,
171+
version
172+
) {
173+
const hbAdIdTargeting = slot.getTargeting('hb_adid');
174+
if (hbAdIdTargeting.length > 0) {
175+
const hbAdId = hbAdIdTargeting[0];
176+
return (
177+
instanceConfig.prebidWinnersCache[hbAdId] ||
178+
instanceConfig.prebidBidResponsesCache[hbAdId]
179+
);
180+
}
181+
return null;
182+
};
183+
41184
const liveyield = Object.assign(adapter({ analyticsType: 'bundle' }), {
42185
track({ eventType, args }) {
43186
switch (eventType) {
44187
case BID_REQUESTED:
45188
args.bids.forEach(function(b) {
46189
try {
47-
window[adapterConfig.rtaFunctionName](
190+
window[liveyield.instanceConfig.rtaFunctionName](
48191
'bidRequested',
49-
adapterConfig.getAdUnitName(
50-
adapterConfig.getPlacementOrAdUnitCode(b, prebidVersion)
192+
liveyield.instanceConfig.getAdUnitName(
193+
liveyield.instanceConfig.getPlacementOrAdUnitCode(
194+
b,
195+
prebidVersion
196+
)
51197
),
52198
args.bidderCode
53199
);
@@ -57,52 +203,82 @@ const liveyield = Object.assign(adapter({ analyticsType: 'bundle' }), {
57203
});
58204
break;
59205
case BID_RESPONSE:
206+
liveyield.instanceConfig.prebidBidResponsesCache[args.adId] = args;
60207
var cpm = args.statusMessage === 'Bid available' ? args.cpm : null;
61208
try {
62-
window[adapterConfig.rtaFunctionName](
209+
window[liveyield.instanceConfig.rtaFunctionName](
63210
'addBid',
64-
adapterConfig.getAdUnitName(
65-
adapterConfig.getPlacementOrAdUnitCode(args, prebidVersion)
211+
liveyield.instanceConfig.getAdUnitName(
212+
liveyield.instanceConfig.getPlacementOrAdUnitCode(
213+
args,
214+
prebidVersion
215+
)
66216
),
67217
args.bidder || 'unknown',
68218
cpmToMicroUSD(cpm),
69219
typeof args.bidder === 'undefined',
70220
args.statusMessage !== 'Bid available'
71-
)
221+
);
72222
} catch (e) {
73223
utils.logError(e);
74224
}
75225
break;
76226
case BID_TIMEOUT:
77-
window[adapterConfig.rtaFunctionName]('biddersTimeout', args);
227+
window[liveyield.instanceConfig.rtaFunctionName](
228+
'biddersTimeout',
229+
args
230+
);
78231
break;
79232
case BID_WON:
233+
liveyield.instanceConfig.prebidWinnersCache[args.adId] = args;
234+
if (liveyield.instanceConfig.googlePublisherTag) {
235+
break;
236+
}
237+
80238
try {
81-
const ad = adapterConfig.getAdUnitName(
82-
adapterConfig.getPlacementOrAdUnitCode(args, prebidVersion)
239+
const ad = liveyield.instanceConfig.getAdUnitName(
240+
liveyield.instanceConfig.getPlacementOrAdUnitCode(
241+
args,
242+
prebidVersion
243+
)
83244
);
84245
if (!ad) {
85-
utils.logError('Cannot find ad by unit name: ' +
86-
adapterConfig.getAdUnitName(
87-
adapterConfig.getPlacementOrAdUnitCode(args, prebidVersion)
88-
));
246+
utils.logError(
247+
'Cannot find ad by unit name: ' +
248+
liveyield.instanceConfig.getAdUnitName(
249+
liveyield.instanceConfig.getPlacementOrAdUnitCode(
250+
args,
251+
prebidVersion
252+
)
253+
)
254+
);
89255
break;
90256
}
91257
if (!args.bidderCode || !args.cpm) {
92258
utils.logError('Bidder code or cpm is not valid');
93259
break;
94260
}
95-
window[adapterConfig.rtaFunctionName](
261+
const resolution = { targetings: [] };
262+
resolution.prebidWon = true;
263+
resolution.prebidPartner = args.bidderCode;
264+
resolution.prebidValue = cpmToMicroUSD(parseFloat(args.cpm));
265+
const resolutionToUse = liveyield.instanceConfig.postProcessResolution(
266+
resolution,
267+
null,
268+
resolution.prebidPartner,
269+
resolution.prebidValue,
270+
prebidVersion
271+
);
272+
window[liveyield.instanceConfig.rtaFunctionName](
96273
'resolveSlot',
97-
adapterConfig.getAdUnitName(
98-
adapterConfig.getPlacementOrAdUnitCode(args, prebidVersion)
274+
liveyield.instanceConfig.getAdUnitName(
275+
liveyield.instanceConfig.getPlacementOrAdUnitCode(
276+
args,
277+
prebidVersion
278+
)
99279
),
100-
{
101-
prebidWon: true,
102-
prebidPartner: args.bidderCode,
103-
prebidValue: cpmToMicroUSD(args.cpm)
104-
}
105-
)
280+
resolutionToUse
281+
);
106282
} catch (e) {
107283
utils.logError(e);
108284
}
@@ -157,12 +333,25 @@ liveyield.enableAnalytics = function(config) {
157333
utils.logError('options.sessionTimezoneOffset is required');
158334
return;
159335
}
160-
Object.assign(adapterConfig, config.options);
161-
if (typeof window[adapterConfig.rtaFunctionName] !== 'function') {
162-
utils.logError(`Function ${adapterConfig.rtaFunctionName} is not defined.` +
163-
`Make sure that LiveYield snippet in included before the Prebid Analytics configuration.`);
336+
liveyield.instanceConfig = Object.assign(
337+
{ prebidWinnersCache: {}, prebidBidResponsesCache: {} },
338+
adapterConfig,
339+
config.options
340+
);
341+
342+
if (typeof window[liveyield.instanceConfig.rtaFunctionName] !== 'function') {
343+
utils.logError(
344+
`Function ${liveyield.instanceConfig.rtaFunctionName} is not defined.` +
345+
`Make sure that LiveYield snippet in included before the Prebid Analytics configuration.`
346+
);
164347
return;
165348
}
349+
if (liveyield.instanceConfig.googlePublisherTag) {
350+
liveyield.instanceConfig.wireGooglePublisherTag(
351+
liveyield.instanceConfig.googlePublisherTag,
352+
onSlotRenderEnded(liveyield.instanceConfig)
353+
);
354+
}
166355

167356
const additionalParams = {
168357
customerTimezone: config.options.customerTimezone,
@@ -192,18 +381,71 @@ liveyield.enableAnalytics = function(config) {
192381
key => additionalParams[key] == null && delete additionalParams[key]
193382
);
194383

195-
window[adapterConfig.rtaFunctionName](
384+
window[liveyield.instanceConfig.rtaFunctionName](
196385
'create',
197386
config.options.customerId,
198387
config.options.customerName,
199388
config.options.customerSite,
200389
config.options.sessionTimezoneOffset,
201390
additionalParams
202391
);
203-
204392
liveyield.originEnableAnalytics(config);
205393
};
206394

395+
const onSlotRenderEnded = function(instanceConfig) {
396+
const addDfpDetails = (resolution, slot) => {
397+
const responseInformation = slot.getResponseInformation();
398+
if (responseInformation) {
399+
resolution.dfpAdvertiserId = responseInformation.advertiserId;
400+
resolution.dfpLineItemId = responseInformation.sourceAgnosticLineItemId;
401+
resolution.dfpCreativeId = responseInformation.creativeId;
402+
}
403+
};
404+
405+
const addPrebidDetails = (resolution, slot) => {
406+
if (instanceConfig.isPrebidAdImpression(slot)) {
407+
resolution.prebidWon = true;
408+
}
409+
const highestPrebidAdImpPartner = instanceConfig.getHighestPrebidAdImpressionPartner(
410+
instanceConfig,
411+
slot,
412+
prebidVersion
413+
);
414+
const highestPrebidAdImpValue = instanceConfig.getHighestPrebidAdImpressionValue(
415+
instanceConfig,
416+
slot,
417+
prebidVersion
418+
);
419+
if (highestPrebidAdImpPartner) {
420+
resolution.prebidPartner = highestPrebidAdImpPartner;
421+
}
422+
if (highestPrebidAdImpValue) {
423+
resolution.prebidValue = cpmToMicroUSD(
424+
parseFloat(highestPrebidAdImpValue)
425+
);
426+
}
427+
};
428+
return slot => {
429+
const resolution = { targetings: [] };
430+
431+
addDfpDetails(resolution, slot);
432+
addPrebidDetails(resolution, slot);
433+
434+
const resolutionToUse = instanceConfig.postProcessResolution(
435+
resolution,
436+
slot,
437+
resolution.highestPrebidAdImpPartner,
438+
resolution.highestPrebidAdImpValue,
439+
prebidVersion
440+
);
441+
window[instanceConfig.rtaFunctionName](
442+
'resolveSlot',
443+
instanceConfig.getAdUnitNameByGooglePublisherTagSlot(slot, prebidVersion),
444+
resolutionToUse
445+
);
446+
};
447+
};
448+
207449
adapterManager.registerAnalyticsAdapter({
208450
adapter: liveyield,
209451
code: 'liveyield'

0 commit comments

Comments
 (0)