Skip to content

Commit

Permalink
Oxxion Rtd Module: add bid filtering (prebid#10100)
Browse files Browse the repository at this point in the history
* oxxion Filtering

* fix test while video is activated

* fix init

* add details

* fix typo

* remove md5
  • Loading branch information
matthieularere-msq authored and Michele Nasti committed Aug 25, 2023
1 parent 9e6f4b1 commit 7708f16
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 16 deletions.
135 changes: 124 additions & 11 deletions modules/oxxionRtdProvider.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,70 @@
import { submodule } from '../src/hook.js'
import { deepAccess, logInfo } from '../src/utils.js'
import { deepAccess, logInfo, logError } from '../src/utils.js'
import { ajax } from '../src/ajax.js';
import adapterManager from '../src/adapterManager.js';

const oxxionRtdSearchFor = [ 'adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'userId', 'labelAny', 'adId' ];
const LOG_PREFIX = 'oxxionRtdProvider submodule: ';

const allAdUnits = [];
const bidderAliasRegistry = adapterManager.aliasRegistry || {};

/** @type {RtdSubmodule} */
export const oxxionSubmodule = {
name: 'oxxionRtd',
init: init,
getBidRequestData: getAdUnits,
onBidResponseEvent: insertVideoTracking,
getRequestsList: getRequestsList,
getFilteredAdUnitsOnBidRates: getFilteredAdUnitsOnBidRates,
};

function init(config, userConsent) {
if (!config.params || !config.params.domain || !config.params.contexts || !Array.isArray(config.params.contexts) || config.params.contexts.length == 0) {
return false
}
return true;
if (!config.params || !config.params.domain) { return false }
if (config.params.contexts && Array.isArray(config.params.contexts) && config.params.contexts.length > 0) { return true; }
if (typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') { return true }
return false;
}

function getAdUnits(reqBidsConfigObj, callback, config, userConsent) {
const reqAdUnits = reqBidsConfigObj.adUnits;
if (Array.isArray(reqAdUnits)) {
reqAdUnits.forEach(adunit => {
if (config.params.contexts.includes(deepAccess(adunit, 'mediaTypes.video.context'))) {
allAdUnits.push(adunit);
logInfo(LOG_PREFIX + 'started with ', config);
if (typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') {
let filteredBids;
const requests = getRequestsList(reqBidsConfigObj);
const gdpr = userConsent && userConsent.gdpr ? userConsent.gdpr.consentString : null;
const payload = {
gdpr,
requests
};
const endpoint = 'https://' + config.params.domain + '.oxxion.io/analytics/bid_rate_interests';
getPromisifiedAjax(endpoint, JSON.stringify(payload), {
method: 'POST',
withCredentials: true
}).then(bidsRateInterests => {
if (bidsRateInterests.length) {
[reqBidsConfigObj.adUnits, filteredBids] = getFilteredAdUnitsOnBidRates(bidsRateInterests, reqBidsConfigObj.adUnits, config.params, true);
}
});
if (filteredBids.length > 0) {
getPromisifiedAjax('https://' + config.params.domain + '.oxxion.io/analytics/request_rejecteds', JSON.stringify({'bids': filteredBids, 'gdpr': gdpr}), {
method: 'POST',
withCredentials: true
});
}
if (typeof callback == 'function') { callback(); }
}).catch(error => logError(LOG_PREFIX, 'bidInterestError', error));
}
if (config.params.contexts && Array.isArray(config.params.contexts) && config.params.contexts.length > 0) {
const reqAdUnits = reqBidsConfigObj.adUnits;
if (Array.isArray(reqAdUnits)) {
reqAdUnits.forEach(adunit => {
if (config.params.contexts.includes(deepAccess(adunit, 'mediaTypes.video.context'))) {
allAdUnits.push(adunit);
}
});
}
if (!(typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') && typeof callback == 'function') {
callback();
}
}
}

Expand Down Expand Up @@ -94,4 +130,81 @@ function getImpUrl(config, data, maxCpm) {
return trackingImpUrl + 'cpmIncrement=' + cpmIncrement + '&context=' + context;
}

function getPromisifiedAjax (url, data = {}, options = {}) {
return new Promise((resolve, reject) => {
const callbacks = {
success(responseText, { response }) {
resolve(JSON.parse(response));
},
error(error) {
reject(error);
}
};
ajax(url, callbacks, data, options);
})
}

function getFilteredAdUnitsOnBidRates (bidsRateInterests, adUnits, params, useSampling) {
const { threshold, samplingRate } = params;
const filteredBids = [];
// Separate bidsRateInterests in two groups against threshold & samplingRate
const { interestingBidsRates, uninterestingBidsRates } = bidsRateInterests.reduce((acc, interestingBid) => {
const isBidRateUpper = typeof threshold == 'number' ? interestingBid.rate === true || interestingBid.rate > threshold : interestingBid.suggestion;
const isBidInteresting = isBidRateUpper || (getRandomNumber(100) < samplingRate && useSampling);
const key = isBidInteresting ? 'interestingBidsRates' : 'uninterestingBidsRates';
acc[key].push(interestingBid);
return acc;
}, {
interestingBidsRates: [],
uninterestingBidsRates: [] // Do something with later
});
logInfo(LOG_PREFIX, 'getFilteredAdUnitsOnBidRates()', interestingBidsRates, uninterestingBidsRates);
// Filter bids and adUnits against interesting bids rates
const newAdUnits = adUnits.filter(({ bids = [] }, adUnitIndex) => {
adUnits[adUnitIndex].bids = bids.filter(bid => {
if (!params.bidders || params.bidders.includes(bid.bidder)) {
const index = interestingBidsRates.findIndex(({ id }) => id === bid._id);
if (index == -1) {
let tmpBid = bid;
tmpBid['code'] = adUnits[adUnitIndex].code;
tmpBid['mediaTypes'] = adUnits[adUnitIndex].mediaTypes;
tmpBid['originalBidder'] = bidderAliasRegistry[bid.bidder] || bid.bidder;
if (tmpBid.floorData) {
delete tmpBid.floorData;
}
filteredBids.push(tmpBid);
}
delete bid._id;
return index !== -1;
} else {
return true;
}
});
return !!adUnits[adUnitIndex].bids.length;
});
return [newAdUnits, filteredBids];
}

function getRandomNumber (max = 10) {
return Math.round(Math.random() * max);
}

function getRequestsList(reqBidsConfigObj) {
let count = 0;
return reqBidsConfigObj.adUnits.flatMap(({
bids = [],
mediaTypes = {},
code = ''
}) => bids.reduce((acc, { bidder = '', params = {} }, index) => {
const id = count++;
bids[index]._id = id;
return acc.concat({
id,
adUnit: code,
bidder,
mediaTypes,
});
}, []));
}

submodule('realTimeData', oxxionSubmodule);
23 changes: 19 additions & 4 deletions modules/oxxionRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Maintainer: [email protected]
# Oxxion Real-Time-Data submodule

Oxxion helps you to understand how your prebid stack performs.
This Rtd module is to use in order to improve video events tracking.
This Rtd module is to use in order to improve video events tracking and/or to filter bidder requested.

# Integration

Make sure to have the following modules listed while building prebid : `rtdModule,oxxionRtdProvider`
`rtbModule` is required to activate real-time-data submodules.
`rtdModule` is required to activate real-time-data submodules.
For example :
```
gulp build --modules=schain,priceFloors,currency,consentManagement,appnexusBidAdapter,rubiconBidAdapter,rtdModule,oxxionRtdProvider
Expand All @@ -23,14 +23,16 @@ Then add the oxxion Rtd module to your prebid configuration :
pbjs.setConfig(
...
realTimeData: {
auctionDelay: 200,
auctionDelay: 300,
dataProviders: [
{
name: "oxxionRtd",
waitForIt: true,
params: {
domain: "test.endpoint",
contexts: ["instream"],
threshold: false,
samplingRate: 10,
}
}
]
Expand All @@ -39,10 +41,23 @@ pbjs.setConfig(
)
```

# setConfig Parameters
# setConfig Parameters General

| Name | Type | Description |
|:---------------------------------|:---------|:------------------------------------------------------------------------------------------------------------|
| domain | String | This string identifies yourself in Oxxion's systems and is provided to you by your Oxxion representative. |

# setConfig Parameters for Video Tracking

| Name | Type | Description |
|:---------------------------------|:---------|:------------------------------------------------------------------------------------------------------------|
| contexts | Array | Array defining which video contexts to add tracking events into. Values can be instream and/or outstream. |

# setConfig Parameters for bidder filtering

| Name | Type | Description |
|:---------------------------------|:-----------|:------------------------------------------------------------------------------------------------------------|
| threshold | Float/Bool | False or minimum expected bid rate to call a bidder (ex: 1.0 for 1% bid rate). |
| samplingRate | Integer | Percentage of request not meeting the criterias to run anyway in order to check for any change. |
| bidders | Array | Optional: If set, filtering will only be applied to bidders listed.

114 changes: 113 additions & 1 deletion test/spec/modules/oxxionRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const utils = require('src/utils.js');
const moduleConfig = {
params: {
domain: 'test.endpoint',
contexts: ['instream', 'outstream']
contexts: ['instream', 'outstream'],
samplingRate: 10,
threshold: false,
bidders: ['appnexus', 'mediasquare'],
}
};

Expand All @@ -31,6 +34,14 @@ let request = {
],
'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41'
},
{
'code': 'msq_tag_200125_banner',
'mediaTypes': { 'banner': { 'sizes': [[300, 250]] } },
'bids': [
{'bidder': 'appnexusAst', 'params': {'placementId': 345678}},
],
'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41'
}
]
};

Expand Down Expand Up @@ -102,6 +113,93 @@ let bids = [{
},
];

let originalBidderRequests = [{
'bidderCode': 'rubicon',
'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce',
'bidderRequestId': '16c2bceb2e891a',
'bids': [
{
'bidder': 'rubicon',
'params': {
'accountId': 1234,
'siteId': 2345,
'zoneId': 3456
},
'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce',
'mediaTypes': {'banner': {'sizes': [[970, 250]]}},
'adUnitCode': 'adunit1',
'transactionId': '8f20b49c-5e47-4bb5-a7d5-0b816cf527f3',
'bidId': '2d9920072ab028',
'bidderRequestId': '16c2bceb2e891a',
},
{
'bidder': 'rubicon',
'params': {
'accountId': 1234,
'siteId': 2345,
'zoneId': 4567
},
'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce',
'mediaTypes': {'banner': {'sizes': [[300, 250]]}},
'adUnitCode': 'adunit2',
'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4',
'bidId': '331c3d708f4864',
'bidderRequestId': '16c2bceb2e891a',
'src': 'client',
}
],
'auctionStart': 1683383333809,
'timeout': 3000,
'gdprConsent': {
'consentString': 'consent_hash',
'gdprApplies': true,
'apiVersion': 2
}
},
{
'bidderCode': 'appnexusAst',
'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce',
'bidderRequestId': '4d83b8c60d45e7',
'bids': [
{
'bidder': 'appnexusAst',
'params': {
'placementId': 10471298
},
'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce',
'mediaTypes': {'banner': {'sizes': [[300, 250]]}},
'adUnitCode': 'adunit2',
'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4',
'bidId': '5b7cd5abc6aea3',
'bidderRequestId': '4d83b8c60d45e7',
}
],
'auctionStart': 1683383333809,
'timeout': 3000,
'gdprConsent': {
'consentString': 'consent_hash',
'gdprApplies': true,
'apiVersion': 2
}
}
];

let bidInterests = [
{'id': 0, 'rate': 50.0, 'suggestion': true},
{'id': 1, 'rate': 12.0, 'suggestion': false},
{'id': 2, 'rate': 0.0, 'suggestion': true},
{'id': 3, 'rate': 0.0, 'suggestion': false},
];

const userConsent = {
'gdpr': {
'consentString': 'consent_hash'
},
'usp': null,
'gpp': null,
'coppa': false
};

describe('oxxionRtdProvider', () => {
describe('Oxxion RTD sub module', () => {
it('should init, return true, and set the params', () => {
Expand All @@ -117,6 +215,20 @@ describe('oxxionRtdProvider', () => {
oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[0], moduleConfig);
oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[1], moduleConfig);
});
it('check bid filtering', function() {
let requestsList = oxxionSubmodule.getRequestsList(request);
expect(requestsList.length).to.equal(4);
expect(requestsList[0]).to.have.property('id');
expect(request.adUnits[0].bids[0]).to.have.property('_id');
expect(requestsList[0].id).to.equal(request.adUnits[0].bids[0]._id);
const [filteredBiddderRequests, filteredBids] = oxxionSubmodule.getFilteredAdUnitsOnBidRates(bidInterests, request.adUnits, moduleConfig.params, false);
expect(filteredBids.length).to.equal(1);
expect(filteredBiddderRequests.length).to.equal(3);
expect(filteredBiddderRequests[0]).to.have.property('bids');
expect(filteredBiddderRequests[0].bids.length).to.equal(1);
expect(filteredBiddderRequests[1]).to.have.property('bids');
expect(filteredBiddderRequests[1].bids.length).to.equal(1);
});
it('check vastImpUrl', function() {
expect(auctionEnd.bidsReceived[0]).to.have.property('vastImpUrl');
let expectVastImpUrl = 'https://' + moduleConfig.params.domain + '.oxxion.io/analytics/vast_imp?';
Expand Down

0 comments on commit 7708f16

Please sign in to comment.