Skip to content

Commit

Permalink
Konduit Accelerate module with 'send all bids' support (#5247)
Browse files Browse the repository at this point in the history
* Adding Konduit module

* Removed superfluous arguments passed to obtainVastUrl function

* Removed superfluous arguments passed to obtainVastUrl function.

* Build trigger (empty commit)

* Module documentation updated according to the comments

* Logic in obtainVastUrl function updated according to the review comment.

* Removed hook, enabled eslint

* Merged recent prebid changes

* New method is introduced to process a bid and return dynamic CPM data

* New Konduit Analytics adapter responsible for client auction stats collection

* Updated konduit analytics adapter .md file

* Fixed linter issue with more than 1 blank line used

* Use '$prebid.version$' instead of the $$PREBID_GLOBAL$$.version

* Updated unit tests

* Enable "Send all bids" support

* Updated konduitWrapper.md file

* Updated links in konduitWrapper.md

* Updated spec file (unit tests)

* Added Konduit Prebid module version

Co-authored-by: Max Shevchenko <[email protected]>
Co-authored-by: Alexander Kislitsyn <[email protected]>
  • Loading branch information
3 people authored Jun 8, 2020
1 parent 461fea9 commit acdece8
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 55 deletions.
2 changes: 2 additions & 0 deletions modules/konduitAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { config } from '../src/config.js';
import CONSTANTS from '../src/constants.json';

const TRACKER_HOST = 'tracker.konduit.me';
const KONDUIT_PREBID_MODULE_VERSION = '1.0.0';

const analyticsType = 'endpoint';

Expand Down Expand Up @@ -168,6 +169,7 @@ function composeRequestPayload () {
return {
konduitId,
prebidVersion: '$prebid.version$',
konduitPrebidModuleVersion: KONDUIT_PREBID_MODULE_VERSION,
environment: {
screen: { width, height },
language: navigator.language,
Expand Down
157 changes: 110 additions & 47 deletions modules/konduitWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import { config } from '../src/config.js';
import { ajaxBuilder } from '../src/ajax.js';
import { getPriceBucketString } from '../src/cpmBucketManager.js';
import { getPriceByGranularity } from '../src/auction.js';
import { auctionManager } from '../src/auctionManager.js';

const SERVER_PROTOCOL = 'https';
const SERVER_HOST = 'p.konduit.me';

const KONDUIT_PREBID_MODULE_VERSION = '1.0.0';
const MODULE_NAME = 'Konduit';

const KONDUIT_ID_CONFIG = 'konduit.konduitId';
const SEND_ALL_BIDS_CONFIG = 'enableSendAllBids';

export const errorMessages = {
NO_KONDUIT_ID: 'A konduitId param is required to be in configs',
NO_BIDS: 'No bids received in the auction',
NO_BID: 'A bid was not found',
CACHE_FAILURE: 'A bid was not cached',
};
Expand Down Expand Up @@ -78,14 +82,81 @@ function sendRequest ({ host = SERVER_HOST, protocol = SERVER_PROTOCOL, method =
);
}

function composeBidsProcessorRequestPayload(bid) {
return {
auctionId: bid.auctionId,
vastUrl: bid.vastUrl,
bidderCode: bid.bidderCode,
creativeId: bid.creativeId,
adUnitCode: bid.adUnitCode,
cpm: bid.cpm,
currency: bid.currency,
};
}

function setDefaultKCpmToBid(bid, winnerBid, priceGranularity) {
bid.kCpm = bid.cpm;

if (!bid.adserverTargeting) {
bid.adserverTargeting = {};
}

const kCpm = getPriceByGranularity(priceGranularity)(bid);

if (config.getConfig(SEND_ALL_BIDS_CONFIG)) {
bid.adserverTargeting[`k_cpm_${bid.bidderCode}`] = kCpm;
}

if ((winnerBid.bidderCode === bid.bidderCode) && (winnerBid.creativeId === bid.creativeId)) {
bid.adserverTargeting.k_cpm = kCpm;
}
}

function addKCpmToBid(kCpm, bid, winnerBid, priceGranularity) {
if (utils.isNumber(kCpm)) {
bid.kCpm = kCpm;
const priceStringsObj = getPriceBucketString(
kCpm,
config.getConfig('customPriceBucket'),
config.getConfig('currency.granularityMultiplier')
);

const calculatedKCpm = priceStringsObj.custom || priceStringsObj[priceGranularity] || priceStringsObj.med;

if (config.getConfig(SEND_ALL_BIDS_CONFIG)) {
bid.adserverTargeting[`k_cpm_${bid.bidderCode}`] = calculatedKCpm;
}

if ((winnerBid.bidderCode === bid.bidderCode) && (winnerBid.creativeId === bid.creativeId)) {
bid.adserverTargeting.k_cpm = calculatedKCpm;
}
}
}

function addKonduitCacheKeyToBid(cacheKey, bid, winnerBid) {
if (utils.isStr(cacheKey)) {
bid.konduitCacheKey = cacheKey;

if (config.getConfig(SEND_ALL_BIDS_CONFIG)) {
bid.adserverTargeting[`k_cache_key_${bid.bidderCode}`] = cacheKey;
}

if ((winnerBid.bidderCode === bid.bidderCode) && (winnerBid.creativeId === bid.creativeId)) {
bid.adserverTargeting.k_cache_key = cacheKey;
bid.adserverTargeting.konduit_cache_key = cacheKey;
}
}
}

/**
* This function accepts an object with bid and tries to cache it while generating konduit_cache_key for it.
* This function accepts an object with bid and tries to cache it while generating k_cache_key for it.
* In addition, it returns a list with updated bid objects where k_cpm key is added
* @param {Object} options
* @param {Object} [options.bid] - winner bid from publisher
* @param {string} [options.adUnitCode] - to look for winner bid
* @param {string} [options.timeout] - timeout for bidsProcessor request
* @param {function} [options.callback] - callback will be called in the end of the request
* @param {Object} [options.bid] - a winner bid provided by a client
* @param {Object} [options.bids] - bids array provided by a client for "Send All Bids" scenario
* @param {string} [options.adUnitCode] - ad unit code that is used to get winning bids
* @param {string} [options.timeout] - timeout for Konduit bids processor HTTP request
* @param {function} [options.callback] - callback function to be executed on HTTP request end; the function is invoked with two parameters - error and bids
*/
export function processBids(options = {}) {
const konduitId = config.getConfig(KONDUIT_ID_CONFIG);
Expand All @@ -95,93 +166,85 @@ export function processBids(options = {}) {
logError(errorMessages.NO_KONDUIT_ID);

if (options.callback) {
options.callback(new Error(errorMessages.NO_KONDUIT_ID));
options.callback(new Error(errorMessages.NO_KONDUIT_ID), []);
}

return null;
}

const bid = options.bid || targeting.getWinningBids(options.adUnitCode)[0];
const publisherBids = options.bids || auctionManager.getBidsReceived();

if (!bid) {
logError(errorMessages.NO_BID);
const winnerBid = options.bid || targeting.getWinningBids(options.adUnitCode, publisherBids)[0];
const bids = [];

if (config.getConfig(SEND_ALL_BIDS_CONFIG)) {
bids.push(...publisherBids);
} else if (winnerBid) {
bids.push(winnerBid);
}

if (!bids.length) {
logError(errorMessages.NO_BIDS);

if (options.callback) {
options.callback(new Error(errorMessages.NO_BID));
options.callback(new Error(errorMessages.NO_BIDS), []);
}

return null;
}

const priceGranularity = config.getConfig('priceGranularity');

bid.kCpm = bid.cpm;

if (!bid.adserverTargeting) {
bid.adserverTargeting = {};
}

bid.adserverTargeting.k_cpm = getPriceByGranularity(priceGranularity)(bid);
const bidsToProcess = [];

const bidsToProcess = [{
auctionId: bid.auctionId,
vastUrl: bid.vastUrl,
bidderCode: bid.bidderCode,
creativeId: bid.creativeId,
adUnitCode: bid.adUnitCode,
cpm: bid.cpm,
currency: bid.currency,
}];
bids.forEach((bid) => {
setDefaultKCpmToBid(bid, winnerBid, priceGranularity);
bidsToProcess.push(composeBidsProcessorRequestPayload(bid));
});

sendRequest({
method: 'POST',
path: '/api/bidsProcessor',
timeout: options.timeout || 1000,
payload: {
clientId: konduitId,
konduitPrebidModuleVersion: KONDUIT_PREBID_MODULE_VERSION,
enableSendAllBids: config.getConfig(SEND_ALL_BIDS_CONFIG),
bids: bidsToProcess,
bidResponsesCount: auctionManager.getBidsReceived().length,
},
callbacks: {
success: (data) => {
let error = null;
logInfo('Bids processed successfully ', data);
try {
const { kCpmData, cacheData } = JSON.parse(data);
const processedBidKey = `${bid.bidderCode}:${bid.creativeId}`;

if (!utils.isEmpty(cacheData)) {
bid.adserverTargeting.konduit_id = konduitId;
} else {
error = new Error(errorMessages.CACHE_FAILURE);
if (utils.isEmpty(cacheData)) {
throw new Error(errorMessages.CACHE_FAILURE);
}

if (utils.isNumber(kCpmData[processedBidKey])) {
bid.kCpm = kCpmData[processedBidKey];
const priceStringsObj = getPriceBucketString(
bid.kCpm,
config.getConfig('customPriceBucket'),
config.getConfig('currency.granularityMultiplier')
);
bid.adserverTargeting.k_cpm = priceStringsObj.custom || priceStringsObj[priceGranularity] || priceStringsObj.med;
}
winnerBid.adserverTargeting.konduit_id = konduitId;
winnerBid.adserverTargeting.k_id = konduitId;

if (utils.isStr(cacheData[processedBidKey])) {
bid.konduitCacheKey = cacheData[processedBidKey];
bid.adserverTargeting.konduit_cache_key = cacheData[processedBidKey];
}
bids.forEach((bid) => {
const processedBidKey = `${bid.bidderCode}:${bid.creativeId}`;
addKCpmToBid(kCpmData[processedBidKey], bid, winnerBid, priceGranularity);
addKonduitCacheKeyToBid(cacheData[processedBidKey], bid, winnerBid);
})
} catch (err) {
error = err;
logError('Error parsing JSON response for bidsProcessor data: ', err)
}

if (options.callback) {
options.callback(error, [bid]);
options.callback(error, bids);
}
},
error: (error) => {
logError('Bid was not processed successfully ', error);
logError('Bids were not processed successfully ', error);
if (options.callback) {
options.callback(utils.isStr(error) ? new Error(error) : error, [bid]);
options.callback(utils.isStr(error) ? new Error(error) : error, bids);
}
}
}
Expand Down
20 changes: 17 additions & 3 deletions modules/konduitWrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,40 @@ pbjs.setConfig({
});
```

Please contact [email protected] for assistance.
Konduit module respects the Prebid `enableSendAllBids` flag and supports both ‘Send All Bids’ and ‘Use only a winner bid’ scenarios.

Please contact [email protected] for assistance.

## GAM related configuration

It is important to configure your GAM line items.
Please contact [email protected] for assistance.

In most cases it would require only Creative VAST URL update with the following URL:

Konduit platform supports ‘Send all bids’ scenario and depending on whether this feature is used or not GAM configuration could be slightly different.

- Send all bids is off (a single winner bid is used)
GAM line item creative URL should be updated as:
```
https://p.konduit.me/api/vastProxy?konduit_hb=1&konduit_hb_awarded=1&konduit_cache_key=%%PATTERN:k_cache_key%%&konduit_id=%%PATTERN:k_id%%
```

- Send all bids is on
GAM line item creative URL should be updated as:
```
https://p.konduit.me/api/vastProxy?konduit_hb=1&konduit_hb_awarded=1&konduit_cache_key=%%PATTERN:konduit_cache_key%%&konduit_id=%%PATTERN:konduit_id%%
https://p.konduit.me/api/vastProxy?konduit_hb=1&konduit_hb_awarded=1&konduit_cache_key=%%PATTERN:k_cache_key_BIDDERCODE%%&konduit_id=%%PATTERN:k_id%%
```

k_cache_key_BIDDERCODE is a bidder specific macro and ‘BIDDERCODE’ should be replaced with a bidder code. For instance, k_cache_key_appnexus.

# Usage

Konduit module contains a single function that accepts an `options` parameter.

The `options` parameter can include:
* `bid` - prebid object with VAST url that should be cached (if not passed first winning bid from `auctionManager.getWinningBids()` will be used)
* `bids` - array of prebid objects with VAST url that should be cached (if not passed and `enableSendAllBids: true` bids from `auctionManager.getBidsReceived()` will be used)
* `adUnitCode` - adUnitCode where a winner bid can be found
* `timeout` - max time to wait for Konduit response with cache key and kCpm data
* `callback` - callback function is called once Konduit cache data for the bid. Arguments of this function are - `error` and `bids` (error should be `null` if Konduit request is successful)
Expand All @@ -65,7 +79,7 @@ The function adds two parameters into the passed bid - kCpm and konduitCacheKey.
pbjs.requestBids({
bidsBackHandler: function (bids) {
pbjs.adServers.konduit.processBids({
callback: function (error, bids) {
callback: function (error, processedBids) {
var videoUrl = pbjs.adServers.dfp.buildVideoUrl({
...
});
Expand Down
Loading

0 comments on commit acdece8

Please sign in to comment.