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(greenbids,analytics): fetch bidder params when bid is valid #6

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
167 changes: 97 additions & 70 deletions modules/greenbidsAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import {ajax} from '../src/ajax.js';
import { ajax } from '../src/ajax.js';
import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import { EVENTS } from '../src/constants.js';
import adapterManager from '../src/adapterManager.js';
import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} from '../src/utils.js';
import { deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName } from '../src/utils.js';

/**
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
*/

/**
* @typedef {object} Message Payload message sent to the Greenbids API
*/

/**
* @typedef AuctionEndArgs
* @type {object}
* @property {string} auctionId
* @property {number} timestamp - Auction start epoch
* @property {number} auctionEnd - Auction end epoch
* @property {Bid[]} bidsReceived
* @property {BidRequest[]} noBids
*/

/**
* @typedef GreenbidsCachedOption
* @type {object}
* @property {any[]} timeoutBids
* @property {?string} greenbidsId
* @property {?string} billingId
* @property {boolean} isSampled
*/

const analyticsType = 'endpoint';

export const ANALYTICS_VERSION = '2.3.2';
export const ANALYTICS_VERSION = '2.3.3';

const ANALYTICS_SERVER = 'https://a.greenbids.ai';

Expand All @@ -33,7 +53,7 @@ export const BIDDER_STATUS = {

const analyticsOptions = {};

export const isSampled = function(greenbidsId, samplingRate, exploratorySamplingSplit) {
export const isSampled = function (greenbidsId, samplingRate, exploratorySamplingSplit) {
const isSamplingForced = getParameterByName('greenbids_force_sampling');
if (isSamplingForced) {
logInfo('Greenbids Analytics: sampling flag detected, forcing analytics');
Expand All @@ -52,7 +72,7 @@ export const isSampled = function(greenbidsId, samplingRate, exploratorySampling
return isExtraSampled;
}

export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), {
export const greenbidsAnalyticsAdapter = Object.assign(adapter({ ANALYTICS_SERVER, analyticsType }), {

cachedAuctions: {},
exploratorySamplingSplit: 0.9,
Expand Down Expand Up @@ -125,83 +145,87 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER
};
},
/**
* @param {Bid} bid
* @param {BIDDER_STATUS} status
*/
serializeBidResponse(bid, status) {
return {
bidder: bid.bidder,
isTimeout: (status === BIDDER_STATUS.TIMEOUT),
hasBid: (status === BIDDER_STATUS.BID),
params: (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {},
...(status === BIDDER_STATUS.BID ? {
cpm: bid.cpm,
currency: bid.currency
} : {}),
};
},
/**
* @param {*} message Greenbids API payload
* @param {Bid} bid Bid to add to the payload
* @param {BIDDER_STATUS} status Bidding status
* @param {AuctionEndArgs} auctionEndArgs
* @param {GreenbidsCachedOption} cachedAuction
* @returns {Message}
*/
addBidResponseToMessage(message, bid, status) {
const adUnitCode = bid.adUnitCode.toLowerCase();
const adUnitIndex = message.adUnits.findIndex((adUnit) => {
return adUnit.code === adUnitCode;
});
if (adUnitIndex === -1) {
logError('Trying to add to non registered adunit');
return;
}
const bidderIndex = message.adUnits[adUnitIndex].bidders.findIndex((bidder) => {
return bidder.bidder === bid.bidder;
});
if (bidderIndex === -1) {
message.adUnits[adUnitIndex].bidders.push(this.serializeBidResponse(bid, status));
} else {
message.adUnits[adUnitIndex].bidders[bidderIndex].params = (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {};
if (status === BIDDER_STATUS.BID) {
message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true;
message.adUnits[adUnitIndex].bidders[bidderIndex].cpm = bid.cpm;
message.adUnits[adUnitIndex].bidders[bidderIndex].currency = bid.currency;
} else if (status === BIDDER_STATUS.TIMEOUT) {
message.adUnits[adUnitIndex].bidders[bidderIndex].isTimeout = true;
}
}
},
createBidMessage(auctionEndArgs) {
const {auctionId, timestamp, auctionEnd, adUnits, bidsReceived, noBids} = auctionEndArgs;
const cachedAuction = this.getCachedAuction(auctionId);
const message = this.createCommonMessage(auctionId);
const timeoutBids = cachedAuction.timeoutBids || [];
createBidMessage(auctionEndArgs, cachedAuction) {
const {
auctionId,
timestamp,
auctionEnd,
adUnits,
bidsReceived,
noBids
} = auctionEndArgs;
// Prepare bidders sub-messages
const unfilteredBidderMessages = new Map(adUnits.map((adUnit) => [
adUnit.code,
new Map((adUnit.bids || []).map((bid) => [
bid.bidder,
{
bidder: bid.bidder,
params: (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {},
hasBid: false,
isTimeout: false,
}
]))
]))

// Keep only bidders that received a bid request and enrich the message
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this line relates to the comment, and why it's needed

const bidderMessages = new Map(adUnits.map((adUnit) => [adUnit.code, new Map()]))
// We enrich no bids, then bids, then timeouts, because in case of a timeout, one response from a bidder
// Can be in all the arrays, and we want that case reflected in the call
noBids.forEach((rqst) => {
bidderMessages.get(rqst.adUnitCode).set(rqst.bidder, unfilteredBidderMessages.get(rqst.adUnitCode).get(rqst.bidder))
})
bidsReceived.forEach((bid) => {
bidderMessages.get(bid.adUnitCode).set(bid.bidder,
Object.assign(
bidderMessages.get(bid.adUnitCode).get(bid.bidder) || unfilteredBidderMessages.get(bid.adUnitCode).get(bid.bidder),
{
hasBid: true,
cpm: bid.cpm,
currency: bid.currency
}
)
)
})
cachedAuction.timeoutBids.forEach((noBid) => {
bidderMessages.get(noBid.adUnitCode).set(noBid.bidder,
Object.assign(
bidderMessages.get(noBid.adUnitCode).get(noBid.bidder) || unfilteredBidderMessages.get(noBid.adUnitCode).get(noBid.bidder),
{
isTimeout: true,
}
)
)
})

// Build complete message
const message = this.createCommonMessage(auctionId);
message.auctionElapsed = (auctionEnd - timestamp);

adUnits.forEach((adUnit) => {
const adUnitCode = adUnit.code?.toLowerCase() || 'unknown_adunit_code';
message.adUnits.push({
code: adUnitCode,
mediaTypes: {
...(adUnit.mediaTypes?.banner !== undefined) && {banner: adUnit.mediaTypes.banner},
...(adUnit.mediaTypes?.video !== undefined) && {video: adUnit.mediaTypes.video},
...(adUnit.mediaTypes?.native !== undefined) && {native: adUnit.mediaTypes.native}
...(adUnit.mediaTypes?.banner !== undefined) && { banner: adUnit.mediaTypes.banner },
...(adUnit.mediaTypes?.video !== undefined) && { video: adUnit.mediaTypes.video },
...(adUnit.mediaTypes?.native !== undefined) && { native: adUnit.mediaTypes.native }
},
ortb2Imp: adUnit.ortb2Imp || {},
bidders: [],
});
});

// We enrich noBid then bids, then timeouts, because in case of a timeout, one response from a bidder
// Can be in the 3 arrays, and we want that case reflected in the call
noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID));

bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID));

timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT));
bidders: Array.from(bidderMessages.get(adUnitCode).values())
})
})

return message;
},
/**
* @param {string} auctionId
* @returns {GreenbidsCachedOption}
*/
getCachedAuction(auctionId) {
this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || {
timeoutBids: [],
Expand All @@ -221,6 +245,9 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER
}
cachedAuction.isSampled = isSampled(cachedAuction.greenbidsId, analyticsOptions.options.greenbidsSampling, this.exploratorySamplingSplit);
},
/**
* @param {AuctionEndArgs} auctionEndArgs
*/
handleAuctionEnd(auctionEndArgs) {
const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId);
const isFilteringForced = getParameterByName('greenbids_force_filtering');
Expand All @@ -243,7 +270,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER
cachedAuction.billingId = billableArgs.billingId || 'unknown_billing_id';
}
},
track({eventType, args}) {
track({ eventType, args }) {
try {
if (eventType === AUCTION_INIT) {
this.handleAuctionInit(args);
Expand Down Expand Up @@ -273,7 +300,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER

greenbidsAnalyticsAdapter.originEnableAnalytics = greenbidsAnalyticsAdapter.enableAnalytics;

greenbidsAnalyticsAdapter.enableAnalytics = function(config) {
greenbidsAnalyticsAdapter.enableAnalytics = function (config) {
this.initConfig(config);
if (typeof config.options.sampling === 'number') {
// Set sampling to 1 to prevent prebid analytics integrated sampling to happen
Expand Down
Loading