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

Audigent RTD configurable per-bidder segment mappings #5903

Merged
merged 26 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from 8 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
41 changes: 33 additions & 8 deletions integrationExamples/gpt/audigentSegments_example.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<html>
<head>
<script>

(function(window, document) {
if (!window.__cmp) {
window.__cmp = (function() {
Expand Down Expand Up @@ -90,11 +91,9 @@
},
bids: [
{
bidder: 'rubicon',
bidder: 'appnexus',
params: {
accountId: '1001',
siteId: '113932',
zoneId: '535510'
placementId: 13144370
}
}
]
Expand All @@ -108,11 +107,25 @@

<script>
var googletag = googletag || {};
var testAuctionDelay = 1000;
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().disableInitialLoad();
});

var bidSegmentMappers = {
appnexus: function(bid, segments) {
if (!bid.params) {
bid.params = {}
}
if (!bid.params.user) {
bid.params.user = {}
}

bid.params.user.segments = segments;
}
}

pbjs.que.push(function() {
pbjs.setConfig({
debug: true,
Expand Down Expand Up @@ -232,17 +245,29 @@
auctionDelay: 1000
},
realTimeData: {
auctionDelay: 1000,
dataProviders: [{name: "audigent", waitForIt: true}]
auctionDelay: testAuctionDelay, // lower in real scenario to meet publisher spec
dataProviders: [
{
name: "audigent",
waitForIt: true,
params: {
mapSegments: ['appnexus'],
antlauzon marked this conversation as resolved.
Show resolved Hide resolved
segmentCache: false,
publisherId: 0
}

}
]
}
});
pbjs.addAdUnits(adUnits);

pbjs.requestBids({bidsBackHandler: sendAdserverRequest});
});

function sendAdserverRequest() {
document.getElementById('tdid').innerHTML = adUnits[0].bids[0].userId['tdid'];
document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].audigent_segments);
document.getElementById('audigent_segments').innerHTML = JSON.stringify(adUnits[0].bids[0].params.user.segments);

if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
Expand Down Expand Up @@ -294,7 +319,7 @@ <h2>Audigent Segments Prebid</h2>
<div id='tdid'>
</div>

Audigent Segments:
Audigent Segments (Appnexus):
<div id='audigent_segments'>
</div>
</body>
Expand Down
94 changes: 73 additions & 21 deletions modules/audigentRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,74 @@ import {submodule} from '../src/hook.js';
import {ajax} from '../src/ajax.js';
import { getStorageManager } from '../src/storageManager.js';

const storage = getStorageManager();
export const storage = getStorageManager();

/** @type {string} */
const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'audigent';
const HALOID_LOCAL_NAME = 'auHaloId';
const SEG_LOCAL_NAME = '__adgntseg';
const ERR_MSG = 'AUDIGENT_ERR';

export const HALOID_LOCAL_NAME = 'auHaloId';
export const SEG_LOCAL_NAME = '__adgntseg';

const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj);
lastObj[lastKey] = lastObj[lastKey] || val;
};

/** bid adapter format segment augmentation functions */
const segmentMappers = {
appnexus: function(bid, segments) {
set(bid, 'params.user.segments', []);
let appnexusSegments = [];
segments.forEach(segment => {
appnexusSegments.push(segment.id);
})
bid.params.user.segments = bid.params.user.segments.concat(appnexusSegments);
},
generic: function(bid, segments) {
bid.segments = segments;
antlauzon marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* decorate adUnits with segment data
* @param {adUnit[]} adUnits
* @param {Object} data
*/
function addSegmentData(adUnits, data) {
export function addSegmentData(adUnits, segmentData, config) {
adUnits.forEach(adUnit => {
if (adUnit.hasOwnProperty('bids')) {
adUnit.bids.forEach(bid => {
bid.audigent_segments = data;
})
try {
set(bid, 'fpd.user.data', []);
if (Array.isArray(bid.fpd.user.data)) {
bid.fpd.user.data.forEach(fpdData => {
if (fpdData.name) { config.params.mapSegments[fpdData.name] = true; }
let segments = segmentData[fpdData.id] || segmentData[fpdData.name] || [];
fpdData.segment = (fpdData.segment || []).concat(segments);
});
}
} catch (err) {
utils.logError(ERR_MSG);
}

try {
if (config.params.mapSegments && config.params.mapSegments[bid.bidder] && segmentData[bid.bidder]) {
if (typeof config.params.mapSegments[bid.bidder] == 'function') {
config.params.mapSegments[bid.bidder](bid, segmentData[bid.bidder]);
} else if (segmentMappers[bid.bidder]) {
segmentMappers[bid.bidder](bid, segmentData[bid.bidder]);
}
}
} catch (err) {
utils.logError(ERR_MSG);
}
});
}
})
});

return adUnits;
}
Expand All @@ -43,16 +90,20 @@ function addSegmentData(adUnits, data) {
* @param {Object} config
* @param {Object} userConsent
*/
function getSegments(reqBidsConfigObj, onDone, config, userConsent) {
export function getSegments(reqBidsConfigObj, onDone, config, userConsent) {
const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits;

let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME);
if (jsonData) {
let data = JSON.parse(jsonData);
if (data.audigent_segments) {
addSegmentData(adUnits, data.audigent_segments);
onDone();
return;
if (config.params.segmentCache) {
let jsonData = storage.getDataFromLocalStorage(SEG_LOCAL_NAME);

if (jsonData) {
let data = JSON.parse(jsonData);

if (data.audigent_segments) {
addSegmentData(adUnits, data.audigent_segments, config);
onDone();
return;
}
}
}

Expand Down Expand Up @@ -88,10 +139,10 @@ function getSegments(reqBidsConfigObj, onDone, config, userConsent) {
* @param {Object} userConsent
* @param {Object} userIds
*/
function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) {
let reqParams = {}
export function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) {
let reqParams = {};
if (typeof config == 'object' && config != null && Object.keys(config).length > 0) {
reqParams = config.params
reqParams = config.params.requestParams;
}

const url = `https://seg.halo.ad.gt/api/v1/rtb_segments`;
Expand All @@ -101,7 +152,7 @@ function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) {
try {
const data = JSON.parse(response);
if (data && data.audigent_segments) {
addSegmentData(adUnits, data.audigent_segments);
addSegmentData(adUnits, data.audigent_segments, config);
onDone();
storage.setDataInLocalStorage(SEG_LOCAL_NAME, JSON.stringify(data));
} else {
Expand All @@ -128,10 +179,11 @@ function getSegmentsAsync(adUnits, onDone, config, userConsent, userIds) {

/**
* module init
* @param {Object} config
* @param {Object} provider
* @param {Objkect} userConsent
* @return {boolean}
*/
export function init(config) {
function init(provider, userConsent) {
return true;
}

Expand Down
84 changes: 45 additions & 39 deletions modules/audigentRtdProvider.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,82 @@
## Audigent Real-time Data Submodule

Audigent is a next-generation data management platform and a first-of-a-kind
"data agency" containing some of the most exclusive content-consuming audiences
Audigent is a next-generation data management platform and a first-of-a-kind
"data agency" containing some of the most exclusive content-consuming audiences
across desktop, mobile and social platforms.

This real-time data module provides quality user segmentation that can be
attached to bid request objects destined for different SSPs in order to optimize
targeting. Audigent maintains a large database of first-party Tradedesk Unified
ID, Audigent Halo ID and other id provider mappings to various third-party
This real-time data module provides quality user segmentation that can be
attached to bid request objects destined for different SSPs in order to optimize
targeting. Audigent maintains a large database of first-party Tradedesk Unified
ID, Audigent Halo ID and other id provider mappings to various third-party
segment types that are utilizable across different SSPs. With this module,
these segments can be retrieved and supplied to the SSP in real-time during
the bid request cycle.

### Usage
### Publisher Usage

Compile the audigent RTD module into your Prebid build:

`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter`
`gulp build --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,appnexusBidAdapter`

Add the Audigent RTD provider to your Prebid config. For any adapters
that you would like to retrieve segments for, add a mapping in the 'mapSegments'
parameter. In this example we will configure publisher 1234 to retrieve
appnexus segments from Audigent. See the "Parameter Descriptions" below for
more detailed information of the configuration parameters.

Configure Prebid to add the Audigent RTD Segment Handler:
```
pbjs.setConfig(
...
...
realTimeData: {
auctionDelay: 1000,
auctionDelay: auctionDelay,
dataProviders: [
{
name: "audigent",
waitForIt: true
}
{
name: "audigent",
waitForIt: true,
params: {
mapSegments: {
'appnexus': true,
antlauzon marked this conversation as resolved.
Show resolved Hide resolved
},
segmentCache: false,
requestParams: {
publisherId: 1234
}
}
}
]
}
...
}
```

Audigent segments will then be attached to each bid request objects in
`bid.realTimeData.audigent_segments`
### Parameter Descriptions for the Audigent `dataProviders` Configuration Section

The format of the segments is a per-SSP mapping:
params.mapSegments | Required | Object
antlauzon marked this conversation as resolved.
Show resolved Hide resolved
Dictionary of bidders you would like to supply Audigent segments for.
Maps to boolean values, but also allows functions for custom mapping logic.
The function signature is (bid, segments) => {}.
antlauzon marked this conversation as resolved.
Show resolved Hide resolved

```
{
'appnexus': ['anseg1', 'anseg2'],
'google': ['gseg1', 'gseg2']
}
```
params.segmentCache | Optional | Boolean
This parameter tells the Audigent RTD module to attempt reading segments
antlauzon marked this conversation as resolved.
Show resolved Hide resolved
from a local storage cache instead of always requesting them from the
Audigent server.

If a given SSP's API backend supports segment fields, they can then be
attached prior to the bid request being sent:
params.requestParams | Optional | Object
Publisher partner specific configuration options, such as optional publisher id
and other segment query related metadata to be submitted to Audigent's
backend with each request. Contact prebid@audigent.com for more information.

```
pbjs.requestBids({bidsBackHandler: addAudigentSegments});

function addAudigentSegments() {
for (i = 0; i < adUnits.length; i++) {
let adUnit = adUnits[i];
for (j = 0; j < adUnit.bids.length; j++) {
adUnit.bids[j].userId.lipb.segments = adUnit.bids[j].audigent_segments['rubicon'];
}
}
}
```

### Testing

To view an example of available segments returned by Audigent's backends:

`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,rubiconBidAdapter`
`gulp serve --modules=userId,unifiedIdSystem,rtdModule,audigentRtdProvider,appnexusBidAdapter`

and then point your browser at:

`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html`




Loading