Skip to content

Commit

Permalink
Bid Viewability Module (#6206)
Browse files Browse the repository at this point in the history
* introducing a new event, bidViewable

* new module: bidViewability

* details in bidViewability.md
  • Loading branch information
pm-harshad-mane authored Feb 5, 2021
1 parent 0a33349 commit 8f77660
Show file tree
Hide file tree
Showing 6 changed files with 481 additions and 1 deletion.
97 changes: 97 additions & 0 deletions modules/bidViewability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Bidders and Analytics adapters
// GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent
// Does not work with other than GPT integration

import { config } from '../src/config.js';
import * as events from '../src/events.js';
import { EVENTS } from '../src/constants.json';
import { logWarn, isFn, triggerPixel } from '../src/utils.js';
import { getGlobal } from '../src/prebidGlobal.js';
import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js';
import find from 'core-js-pure/features/array/find.js';

const MODULE_NAME = 'bidViewability';
const CONFIG_ENABLED = 'enabled';
const CONFIG_FIRE_PIXELS = 'firePixels';
const CONFIG_CUSTOM_MATCH = 'customMatchFunction';
const BID_VURL_ARRAY = 'vurls';
const GPT_IMPRESSION_VIEWABLE_EVENT = 'impressionViewable';

export let isBidAdUnitCodeMatchingSlot = (bid, slot) => {
return (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode);
}

export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => {
return find(getGlobal().getAllWinningBids(),
// supports custom match function from config
bid => isFn(globalModuleConfig[CONFIG_CUSTOM_MATCH])
? globalModuleConfig[CONFIG_CUSTOM_MATCH](bid, slot)
: isBidAdUnitCodeMatchingSlot(bid, slot)
) || null;
};

export let fireViewabilityPixels = (globalModuleConfig, bid) => {
if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) {
let queryParams = {};

const gdprConsent = gdprDataHandler.getConsentData();
if (gdprConsent) {
if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); }
if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; }
if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; }
}

const uspConsent = uspDataHandler.getConsentData();
if (uspConsent) { queryParams.us_privacy = uspConsent; }

bid[BID_VURL_ARRAY].forEach(url => {
// add '?' if not present in URL
if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) {
url += '?';
}
// append all query params, `&key=urlEncoded(value)`
url += Object.keys(queryParams).reduce((prev, key) => prev += `&${key}=${encodeURIComponent(queryParams[key])}`, '');
triggerPixel(url)
});
}
};

export let logWinningBidNotFound = (slot) => {
logWarn(`bid details could not be found for ${slot.getSlotElementId()}, probable reasons: a non-prebid bid is served OR check the prebid.AdUnit.code to GPT.AdSlot relation.`);
};

export let impressionViewableHandler = (globalModuleConfig, slot, event) => {
let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot);
if (respectiveBid === null) {
logWinningBidNotFound(slot);
} else {
// if config is enabled AND VURL array is present then execute each pixel
fireViewabilityPixels(globalModuleConfig, respectiveBid);
// trigger respective bidder's onBidViewable handler
adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid);
// emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels
events.emit(EVENTS.BID_VIEWABLE, respectiveBid);
}
};

export let init = () => {
events.on(EVENTS.AUCTION_INIT, () => {
// read the config for the module
const globalModuleConfig = config.getConfig(MODULE_NAME) || {};
// do nothing if module-config.enabled is not set to true
// this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled
if (globalModuleConfig[CONFIG_ENABLED] !== true) {
return;
}
// add the GPT event listener
window.googletag = window.googletag || {};
window.googletag.cmd = window.googletag.cmd || [];
window.googletag.cmd.push(() => {
window.googletag.pubads().addEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, function(event) {
impressionViewableHandler(globalModuleConfig, event.slot, event);
});
});
});
}

init()
49 changes: 49 additions & 0 deletions modules/bidViewability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Overview

Module Name: bidViewability

Purpose: Track when a bid is viewable

Maintainer: [email protected]

# Description
- This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Analytics adapters, bidders will need to implement `onBidViewable` method to capture this event
- Bidderes can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewability')```
- GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent . This event is fired when an impression becomes viewable, according to the Active View criteria.
Refer: https://support.google.com/admanager/answer/4524488
- The module does not work with adserver other than GAM with GPT integration
- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using param ```customMatchFunction```
- When a rendered PBJS bid is viewable the module will trigger BID_VIEWABLE event, which can be consumed by bidders and analytics adapters
- For the viewable bid if ```bid.vurls type array``` param is and module config ``` firePixels: true ``` is set then the URLs mentioned in bid.vurls will be executed. Please note that GDPR and USP related parameters will be added to the given URLs

# Params
- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable
- firePixels [optional] [type: boolean], when set to true, will fire the urls mentioned in bid.vurls which should be array of urls
- customMatchFunction [optional] [type: function(bid, slot)], when passed this function will be used to `find` the matching winning bid for the GPT slot. Default value is ` (bid, slot) => (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) `

# Example of consuming BID_VIEWABLE event
```
pbjs.onEvent('bidViewable', function(bid){
console.log('got bid details in bidViewable event', bid);
});
```

# Example of using config
```
pbjs.setConfig({
bidViewability: {
enabled: true,
firePixels: true,
customMatchFunction: function(bid, slot){
console.log('using custom match function....');
return bid.adUnitCode === slot.getAdUnitPath();
}
}
});
```

# Please Note:
- Doesn't seems to work with Instream Video, https://docs.prebid.org/dev-docs/examples/instream-banner-mix.html as GPT's impressionViewable event is not triggered for instream-video-creative
- Works with Banner, Outsteam, Native creatives

4 changes: 4 additions & 0 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -606,4 +606,8 @@ adapterManager.callSetTargetingBidder = function(bidder, bid) {
tryCallBidderMethod(bidder, 'onSetTargeting', bid);
};

adapterManager.callBidViewableBidder = function(bidder, bid) {
tryCallBidderMethod(bidder, 'onBidViewable', bid);
};

export default adapterManager;
3 changes: 2 additions & 1 deletion src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"ADD_AD_UNITS": "addAdUnits",
"AD_RENDER_FAILED": "adRenderFailed",
"TCF2_ENFORCEMENT": "tcf2Enforcement",
"AUCTION_DEBUG": "auctionDebug"
"AUCTION_DEBUG": "auctionDebug",
"BID_VIEWABLE": "bidViewable"
},
"AD_RENDER_FAILED_REASON" : {
"PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument",
Expand Down
Loading

0 comments on commit 8f77660

Please sign in to comment.