Skip to content

Commit

Permalink
PBjs Core : send native targetings for ortb response
Browse files Browse the repository at this point in the history
  • Loading branch information
JulieLorin committed Nov 17, 2022
1 parent e7b981d commit a299784
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 77 deletions.
20 changes: 17 additions & 3 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ import {
timestamp
} from './utils.js';
import {getPriceBucketString} from './cpmBucketManager.js';
import {getNativeTargeting} from './native.js';
import {getNativeTargeting, toLegacyResponse} from './native.js';
import {getCacheUrl, store} from './videoCache.js';
import {Renderer} from './Renderer.js';
import {config} from './config.js';
import {userSync} from './userSync.js';
import {hook} from './hook.js';
import {find, includes} from './polyfill.js';
import {OUTSTREAM} from './video.js';
import {VIDEO} from './mediaTypes.js';
import {VIDEO, NATIVE} from './mediaTypes.js';
import {auctionManager} from './auctionManager.js';
import {bidderSettings} from './bidderSettings.js';
import * as events from './events.js';
Expand Down Expand Up @@ -462,9 +462,12 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM
handleBidResponse(adUnitCode, bid, (done) => {
let bidResponse = getPreparedBidForAuction(bid);

if (bidResponse.mediaType === 'video') {
if (bidResponse.mediaType === VIDEO) {
tryAddVideoBid(auctionInstance, bidResponse, done);
} else {
if (FEATURES.NATIVE && bidResponse.mediaType === NATIVE) {
addLegacyFieldsIfNeeded(bidResponse);
}
addBidToAuction(auctionInstance, bidResponse);
done();
}
Expand Down Expand Up @@ -593,6 +596,17 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au
}
}

// Native bid response might be in ortb2 format - adds legacy field for backward compatibility
function addLegacyFieldsIfNeeded(bidResponse) {
const nativeOrtbRequest = auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest;
const nativeOrtbResponse = bidResponse.native?.ortb

if (nativeOrtbRequest && nativeOrtbResponse) {
const legacyResponse = toLegacyResponse(nativeOrtbResponse, nativeOrtbRequest);
Object.assign(bidResponse.native, legacyResponse);
}
}

const storeInCache = (batch) => {
store(batch.map(entry => entry.bidResponse), function (error, cacheIds) {
cacheIds.forEach((cacheId, i) => {
Expand Down
49 changes: 30 additions & 19 deletions src/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,26 +385,14 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) {
return keyValues;
}

const getNativeRequest = (bidResponse) => auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest;

function assetsMessage(data, adObject, keys, {getNativeReq = getNativeRequest} = {}) {
function assetsMessage(data, adObject, keys) {
const message = {
message: 'assetResponse',
adId: data.adId,
};

// Pass to Prebid Universal Creative all assets, the legacy ones + the ortb ones (under ortb property)
const ortbRequest = getNativeReq(adObject);
let nativeResp = adObject.native;
const ortbResponse = adObject.native?.ortb;
let legacyResponse = {};
if (ortbRequest && ortbResponse) {
legacyResponse = toLegacyResponse(ortbResponse, ortbRequest);
nativeResp = {
...adObject.native,
...legacyResponse
};
}

if (adObject.native.ortb) {
message.ortb = adObject.native.ortb;
}
Expand Down Expand Up @@ -435,13 +423,13 @@ function assetsMessage(data, adObject, keys, {getNativeReq = getNativeRequest} =
* Constructs a message object containing asset values for each of the
* requested data keys.
*/
export function getAssetMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) {
export function getAssetMessage(data, adObject) {
const keys = data.assets.map((k) => getKeyByValue(CONSTANTS.NATIVE_KEYS, k));
return assetsMessage(data, adObject, keys, {getNativeReq});
return assetsMessage(data, adObject, keys);
}

export function getAllAssetsMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) {
return assetsMessage(data, adObject, null, {getNativeReq});
export function getAllAssetsMessage(data, adObject) {
return assetsMessage(data, adObject, null);
}

/**
Expand Down Expand Up @@ -749,7 +737,7 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) {
* @param {*} ortbRequest the ortb request, useful to match ids.
* @returns an object containing the response in legacy native format: { title: "this is a title", image: ... }
*/
function toLegacyResponse(ortbResponse, ortbRequest) {
export function toLegacyResponse(ortbResponse, ortbRequest) {
const legacyResponse = {};
const requestAssets = ortbRequest?.assets || [];
legacyResponse.clickUrl = ortbResponse.link.url;
Expand All @@ -764,6 +752,29 @@ function toLegacyResponse(ortbResponse, ortbRequest) {
legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value;
}
}

// Handle trackers
legacyResponse.impressionTrackers = [];
let jsTrackers = [];

if (ortbRequest?.imptrackers) {
legacyResponse.impressionTrackers.push(ortbRequest.imptrackers);
}
for (const eventTracker of ortbResponse?.eventtrackers || []) {
if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.img) {
legacyResponse.impressionTrackers.push(eventTracker.url);
}
if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.js) {
jsTrackers.push(eventTracker.url);
}
}

jsTrackers = jsTrackers.map(url => `<script async src="${url}"></script>`);
if (ortbResponse?.jstracker) { jsTrackers.push(ortbResponse.jstracker); }
if (jsTrackers.length) {
legacyResponse.javascriptTrackers = jsTrackers.join('\n');
}

return legacyResponse;
}

Expand Down
122 changes: 67 additions & 55 deletions test/spec/native_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
nativeBidIsValid,
getAssetMessage,
getAllAssetsMessage,
toLegacyResponse,
decorateAdUnitsWithNativeParams,
isOpenRTBBidRequestValid,
isNativeOpenRTBBidValid,
Expand Down Expand Up @@ -37,6 +38,7 @@ const bid = {
clickTrackers: ['https://tracker.example'],
impressionTrackers: ['https://impression.example'],
javascriptTrackers: '<script src="http://www.foobar.js"></script>',
privacyLink: 'https://privacy-link.example',
ext: {
foo: 'foo-value',
baz: 'baz-value',
Expand Down Expand Up @@ -98,9 +100,18 @@ const ortbBid = {
privacy: 'https://privacy-link.example',
ver: '1.2'
}
},
}
};

const completeNativeBid = {
adId: '123',
transactionId: 'au',
native: {
...bid.native,
...ortbBid.native
}
}

const ortbRequest = {
assets: [
{
Expand Down Expand Up @@ -224,6 +235,14 @@ describe('native.js', function () {
expect(targeting.hb_native_baz).to.equal('hb_native_baz:123');
});

it('sends placeholdes targetings with ortb native response', function () {
const targeting = getNativeTargeting(completeNativeBid);

expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal('Native Creative');
expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('Cool description great stuff');
expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('https://www.link.example');
});

it('should only include native targeting keys with values', function () {
const adUnit = {
transactionId: 'au',
Expand Down Expand Up @@ -302,6 +321,10 @@ describe('native.js', function () {
required: false,
sendTargetingKeys: false,
},
privacyLink: {
required: false,
sendTargetingKeys: false,
},
ext: {
foo: {
required: false,
Expand Down Expand Up @@ -348,6 +371,7 @@ describe('native.js', function () {
CONSTANTS.NATIVE_KEYS.icon,
CONSTANTS.NATIVE_KEYS.sponsoredBy,
CONSTANTS.NATIVE_KEYS.clickUrl,
CONSTANTS.NATIVE_KEYS.privacyLink,
CONSTANTS.NATIVE_KEYS.rendererUrl,
]);

Expand Down Expand Up @@ -380,6 +404,7 @@ describe('native.js', function () {
CONSTANTS.NATIVE_KEYS.icon,
CONSTANTS.NATIVE_KEYS.sponsoredBy,
CONSTANTS.NATIVE_KEYS.clickUrl,
CONSTANTS.NATIVE_KEYS.privacyLink,
]);

expect(bid.native.adTemplate).to.deep.equal(
Expand Down Expand Up @@ -437,9 +462,9 @@ describe('native.js', function () {
adId: '123',
};

const message = getAllAssetsMessage(messageRequest, bid, {getNativeReq: () => null});
const message = getAllAssetsMessage(messageRequest, bid);

expect(message.assets.length).to.equal(9);
expect(message.assets.length).to.equal(10);
expect(message.assets).to.deep.include({
key: 'body',
value: bid.native.body,
Expand Down Expand Up @@ -485,7 +510,7 @@ describe('native.js', function () {
adId: '123',
};

const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields, {getNativeReq: () => null});
const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields);

expect(message.assets.length).to.equal(4);
expect(message.assets).to.deep.include({
Expand All @@ -506,16 +531,16 @@ describe('native.js', function () {
});
});

it('creates native all asset message with OpenRTB format', function () {
it('creates native all asset message with complete format', function () {
const messageRequest = {
message: 'Prebid Native',
action: 'allAssetRequest',
adId: '123',
};

const message = getAllAssetsMessage(messageRequest, ortbBid, {getNativeReq: () => ortbRequest});
const message = getAllAssetsMessage(messageRequest, completeNativeBid);

expect(message.assets.length).to.equal(8);
expect(message.assets.length).to.equal(10);
expect(message.assets).to.deep.include({
key: 'body',
value: bid.native.body,
Expand Down Expand Up @@ -548,67 +573,54 @@ describe('native.js', function () {
key: 'privacyLink',
value: ortbBid.native.ortb.privacy,
});
expect(message.assets).to.deep.include({
key: 'foo',
value: bid.native.ext.foo,
});
expect(message.assets).to.deep.include({
key: 'baz',
value: bid.native.ext.baz,
});
});

const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({
title: 'vtitle',
body: 'vbody'
});
const SAMPLE_ORTB_RESPONSE = {
native: {
ortb: {
link: {
url: 'url'
},
assets: [
{
id: 0,
title: {
text: 'vtitle'
}
},
{
id: 1,
data: {
value: 'vbody'
}
}
]
link: {
url: 'url'
},
assets: [
{
id: 0,
title: {
text: 'vtitle'
}
},
{
id: 1,
data: {
value: 'vbody'
}
}
}
],
eventtrackers: [
{ event: 1, method: 1, url: 'https://sampleurl.com' },
{ event: 1, method: 2, url: 'https://sampleurljs.com' }
]
}
describe('getAllAssetsMessage', () => {
it('returns assets in legacy format for ortb responses', () => {
const actual = getAllAssetsMessage({}, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST});
expect(actual.assets).to.eql([
{
key: 'clickUrl',
value: 'url'
},
{
key: 'title',
value: 'vtitle'
},
{
key: 'body',
value: 'vbody'
},
])
const actual = toLegacyResponse(SAMPLE_ORTB_RESPONSE, SAMPLE_ORTB_REQUEST);
expect(actual.body).to.equal('vbody');
expect(actual.title).to.equal('vtitle');
expect(actual.clickUrl).to.equal('url');
expect(actual.javascriptTrackers).to.equal('<script async src="https://sampleurljs.com"></script>');
expect(actual.impressionTrackers.length).to.equal(1);
expect(actual.impressionTrackers[0]).to.equal('https://sampleurl.com');
});
});
describe('getAssetsMessage', () => {
Object.entries({
'hb_native_title': {key: 'title', value: 'vtitle'},
'hb_native_body': {key: 'body', value: 'vbody'}
}).forEach(([tkey, assetVal]) => {
it(`returns ${tkey} asset in legacy format for ortb responses`, () => {
const actual = getAssetMessage({
assets: [tkey]
}, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST})
expect(actual.assets).to.eql([assetVal])
})
})
})
});

describe('validate native openRTB', function () {
Expand Down

0 comments on commit a299784

Please sign in to comment.