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

fix(openxAnalyticsAdaptor): fixed bug where slotloaded event does not always fire #21

Merged
merged 1 commit into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
141 changes: 86 additions & 55 deletions modules/openxAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const SLOT_LOADED = 'slotOnload';
* @property {Object} utmTagData
* @property {string} adIdKey
* @property {number} payloadWaitTime
* @property {number} payloadWaitTimePadding
* @property {Array<string>}adUnits
*/

Expand All @@ -58,6 +59,7 @@ const DEFAULT_ANALYTICS_CONFIG = {
utmTagData: {},
adUnits: [],
payloadWaitTime: AUCTION_END_WAIT_TIME,
payloadWaitTimePadding: 100
};

let googletag = window.googletag || {};
Expand Down Expand Up @@ -278,7 +280,7 @@ function getAuctionIdByAdId(adId) {
utils._each(eventStack, function(auctionInfo) {
if (auctionInfo && auctionInfo.events) {
auctionInfo.events.forEach(function(eventsInfo) {
if(eventsInfo.eventType === bidWonConst) {
if (eventsInfo.eventType === bidWonConst) {
if (eventsInfo.args && eventsInfo.args.adId && eventsInfo.args.adId === adId) {
auctionId = eventsInfo.args.auctionId;
adUnitCode = eventsInfo.args.adUnitCode;
Expand Down Expand Up @@ -336,7 +338,7 @@ function onSlotLoaded({ slot }) {

let auctionId, adUnitCode;
let adUnitInfo = getAuctionIdByAdId(adId);
if(adUnitInfo && adUnitInfo.auctionId && adUnitInfo.adUnitCode) {
if (adUnitInfo && adUnitInfo.auctionId && adUnitInfo.adUnitCode) {
auctionId = adUnitInfo.auctionId;
adUnitCode = adUnitInfo.adUnitCode;
} else {
Expand Down Expand Up @@ -374,13 +376,13 @@ let openxAdapter = Object.assign(adapter({ urlParam, analyticsType }));

openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics;

openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) {
openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) {
// Backwards compatibility for external documentation
if(adapterConfig.options.slotLoadWaitTime){
if (adapterConfig.options.slotLoadWaitTime) {
adapterConfig.options.payloadWaitTime = adapterConfig.options.slotLoadWaitTime;
}

if(isValidConfig(adapterConfig)){
if (isValidConfig(adapterConfig)) {
analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options};
analyticsConfig.utmTagData = this.buildUtmTagData();
utils.logInfo('OpenX Analytics enabled with config', analyticsConfig);
Expand All @@ -398,7 +400,6 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) {
onSlotLoadedV2(args);
});
});

} else if (analyticsConfig.enableV2) {
// override track method with v2 handlers
openxAdapter.track = prebidAnalyticsEventHandlerV2;
Expand All @@ -422,7 +423,7 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) {
openxAdapter.originEnableAnalytics(adapterConfig);
}

function isValidConfig({options: analyticsOptions}){
function isValidConfig({options: analyticsOptions}) {
const fieldValidations = [
// tuple of property, type, required
['publisherPlatformId', 'string', true],
Expand All @@ -441,10 +442,10 @@ openxAdapter.enableAnalytics = function(adapterConfig = {options:{}}) {
(analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type);
});

if(failedValidation) {
if (failedValidation) {
let [property, type, required] = failedValidation;

if(required){
if (required) {
utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`);
} else {
utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`);
Expand Down Expand Up @@ -666,13 +667,12 @@ function pushEvent(eventType, args, auctionId) {
}

function updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition) {

if(auctionId && adUnitCode) {
if(!loadedAdSlots[auctionId]){
if (auctionId && adUnitCode) {
if (!loadedAdSlots[auctionId]) {
loadedAdSlots[auctionId] = {};
}
loadedAdSlots[auctionId][adUnitCode] = {};
if(adPosition) {
if (adPosition) {
loadedAdSlots[auctionId][adUnitCode] = { adPosition: adPosition };
}
} else {
Expand All @@ -681,28 +681,23 @@ function updateLoadedAdSlotsInfo(auctionId, adUnitCode, adPosition) {
}

function getLoadedAdUnitCodes(auctionId) {

return (!auctionId || !loadedAdSlots[auctionId] || typeof loadedAdSlots[auctionId] !== 'object')
? [] : Object.keys(loadedAdSlots[auctionId]);
}

function pushAdPositionData(auctionId) {

if(auctionId && eventStack[auctionId] && eventStack[auctionId].events) {

if (auctionId && eventStack?.[auctionId]?.events) {
let adUnitPositionMap = loadedAdSlots[auctionId];
if(adUnitPositionMap && JSON.stringify(adUnitPositionMap) !== "{}") {

if (adUnitPositionMap && JSON.stringify(adUnitPositionMap) !== '{}') {
eventStack[auctionId].events.filter(function(event) {
return event.eventType === auctionEndConst;
}).forEach(function (auctionEndEvent) {

if(auctionEndEvent.args && auctionEndEvent.args.adUnits) {
if (auctionEndEvent.args && auctionEndEvent.args.adUnits) {
auctionEndEvent.args.adUnits.forEach(function (adUnitInfo) {
if(adUnitPositionMap[adUnitInfo.code] && adUnitPositionMap[adUnitInfo.code]["adPosition"]) {
adUnitInfo["adPosition"] = adUnitPositionMap[adUnitInfo.code]["adPosition"];
if (adUnitPositionMap[adUnitInfo.code] && adUnitPositionMap[adUnitInfo.code]['adPosition']) {
adUnitInfo['adPosition'] = adUnitPositionMap[adUnitInfo.code]['adPosition'];
} else {
adUnitInfo["adPosition"] = "";
adUnitInfo['adPosition'] = '';
}
})
}
Expand All @@ -712,8 +707,7 @@ function pushAdPositionData(auctionId) {
}

function getAdPositionByElementId(elementId) {

let elem = document.querySelector("#" + elementId);
let elem = document.querySelector('#' + elementId);
let adPosition;
if (elem) {
let bounding = elem.getBoundingClientRect();
Expand All @@ -733,13 +727,13 @@ function getAdPositionByElementId(elementId) {
let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0;
let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top);

if(adSlotArea > 0) {
if (adSlotArea > 0) {
// Atleast 50% of intersection in window
adPosition = (intersectionArea * 2 >= adSlotArea) ? "ATF" : "BTF";
adPosition = (intersectionArea * 2 >= adSlotArea) ? 'ATF' : 'BTF';
}
}
} else {
utils.logWarn("OX: DOM element not for id " + elementId);
utils.logWarn('OX: DOM element not for id ' + elementId);
}
return adPosition;
}
Expand All @@ -751,7 +745,7 @@ adapterManager.registerAnalyticsAdapter({
code: 'openx'
});

function prebidAnalyticsEventHandlerV1({eventType, args}){
function prebidAnalyticsEventHandlerV1({eventType, args}) {
if (!checkInitOptions()) {
send(eventType, {}, null);
return;
Expand Down Expand Up @@ -807,9 +801,15 @@ function prebidAnalyticsEventHandlerV1({eventType, args}){

//* ******* V2 Code *******
const {
EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON }
EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, AUCTION_END, BID_WON }
} = CONSTANTS;

export const AUCTION_STATES = {
INIT: 'initialized', // auction has initialized
ENDED: 'ended', // all auction requests have been accounted for
COMPLETED: 'completed' // all slots have rendered
};

const ENDPOINT = 'https://prebid.openx.net/ox/analytics';
let auctionMap = {};
let auctionOrder = 1; // tracks the number of auctions ran on the page
Expand All @@ -829,6 +829,9 @@ function prebidAnalyticsEventHandlerV2({eventType, args}) {
case BID_TIMEOUT:
onBidTimeout(args);
break;
case AUCTION_END:
onAuctionEnd(args);
break;
case BID_WON:
onBidWon(args);
break;
Expand All @@ -838,31 +841,33 @@ function prebidAnalyticsEventHandlerV2({eventType, args}) {
}
}

/*
TODO: type Auction
auctionId: "526ce090-e42e-4444-996f-ea78cde2244d"
timestamp: 1586675964364
auctionEnd: undefined
auctionStatus: "inProgress"
adUnits: [{…}]
adUnitCodes: ["video1"]
labels: undefined
bidderRequests: (2) [{…}, {…}]
noBids: []
bidsReceived: []
winningBids: []
timeout: 3000
config: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true}
/**
* @typedef {Object} PbAuction
* @property {string} auctionId - Auction ID of the request this bid responded to
* @property {number} timestamp //: 1586675964364
* @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364
* @property {string} auctionStatus //: "inProgress"
* @property {Array<Adunit>} adUnits //: [{…}]
* @property {string} adUnitCodes //: ["video1"]
* @property {string} labels //: undefined
* @property {Array<BidRequest>} bidderRequests //: (2) [{…}, {…}]
* @property {Array<BidRequest>} noBids //: []
* @property {Array<BidResponse>} bidsReceived //: []
* @property {Array<BidResponse>} winningBids //: []
* @property {number} timeout //: 3000
* @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true}/*
*/

function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) {
auctionMap[auctionId] = {
id: auctionId,
startTime,
endTime: void(0),
timeout,
auctionOrder,
adUnitCodesCount: adUnitCodes.length,
adunitCodesRenderedCount: 0,
auctionCompleted: false,
state: AUCTION_STATES.INIT,
auctionSendDelayTimer: void (0),
};

Expand Down Expand Up @@ -956,6 +961,23 @@ function onBidTimeout(args) {
});
}

/**
*
* @param {PbAuction} endedAuction
*/
function onAuctionEnd(endedAuction) {
let auction = auctionMap[endedAuction.auctionId];

if (!auction) {
return;
}

clearAuctionTimer(auction);
auction.endTime = endedAuction.auctionEnd;
auction.state = AUCTION_STATES.ENDED;
delayedSend(auction);
}

/**
*
* @param {BidResponse} bidResponse
Expand All @@ -978,11 +1000,7 @@ function onSlotLoadedV2({ slot }) {
return; // slot is not participating in a prebid auction
}

// reset the delay timer to send the auction data
if (auction.auctionSendDelayTimer) {
clearTimeout(auction.auctionSendDelayTimer);
auction.auctionSendDelayTimer = void (0);
}
clearAuctionTimer(auction);

// track that an adunit code has completed within an auction
auction.adunitCodesRenderedCount++;
Expand All @@ -998,9 +1016,14 @@ function onSlotLoadedV2({ slot }) {

// prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked
// add additional padding when not all slots are rendered
delayedSend(auction);
auction.state = AUCTION_STATES.COMPLETED;
}

function delayedSend(auction) {
const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount
? analyticsConfig.payloadWaitTime
: analyticsConfig.payloadWaitTime + 500;
: analyticsConfig.payloadWaitTime + analyticsConfig.payloadWaitTimePadding;

auction.auctionSendDelayTimer = setTimeout(() => {
let payload = JSON.stringify([buildAuctionPayload(auction)]);
Expand All @@ -1010,8 +1033,14 @@ function onSlotLoadedV2({ slot }) {
delete auctionMap[auction.id];
}
}, delayTime);
}

auction.auctionCompleted = true;
function clearAuctionTimer(auction) {
// reset the delay timer to send the auction data
if (auction.auctionSendDelayTimer) {
clearTimeout(auction.auctionSendDelayTimer);
auction.auctionSendDelayTimer = void (0);
}
}

function getAuctionByGoogleTagSLot(slot) {
Expand All @@ -1030,12 +1059,14 @@ function getAuctionByGoogleTagSLot(slot) {
}

function buildAuctionPayload(auction) {
let {startTime, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction;
let {startTime, endTime, state, timeout, auctionOrder, adUnitCodeToBidderRequestMap} = auction;

return {
publisherPlatformId: analyticsConfig.publisherPlatformId,
publisherAccountId: analyticsConfig.publisherAccountId,
state,
startTime,
endTime,
timeLimit: timeout,
auctionOrder,
deviceType: detectMob() ? 'Mobile' : 'Desktop',
Expand Down
Loading