Skip to content

Commit

Permalink
JwPlayer RTD module: Write to oRTB content segments (#7886)
Browse files Browse the repository at this point in the history
* adds example code

* enriches the ortb2 object

* resolves build errors

* tests content id and data getters

* respects data integrity

* updates documentation
  • Loading branch information
karimMourra authored Jan 13, 2022
1 parent 9783e9f commit 378cf5a
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 19 deletions.
77 changes: 71 additions & 6 deletions modules/jwplayerRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,10 @@ export function enrichAdUnits(adUnits) {
if (!vat) {
return;
}
const contentId = getContentId(vat.mediaID);
const contentData = getContentData(vat.segments);
const targeting = formatTargetingResponse(vat);
addTargetingToBids(adUnit.bids, targeting);
enrichBids(adUnit.bids, targeting, contentId, contentData);
};
loadVat(jwTargeting, onVatResponse);
});
Expand Down Expand Up @@ -235,6 +237,9 @@ export function getVatFromPlayer(playerID, mediaID) {
};
}

/*
deprecated
*/
export function formatTargetingResponse(vat) {
const { segments, mediaID } = vat;
const targeting = {};
Expand All @@ -243,23 +248,83 @@ export function formatTargetingResponse(vat) {
}

if (mediaID) {
const id = 'jw_' + mediaID;
targeting.content = {
id
id: getContentId(mediaID)
}
}
return targeting;
}

function addTargetingToBids(bids, targeting) {
if (!bids || !targeting) {
export function getContentId(mediaID) {
if (!mediaID) {
return;
}

return 'jw_' + mediaID;
}

export function getContentData(segments) {
if (!segments || !segments.length) {
return;
}

const formattedSegments = segments.reduce((convertedSegments, rawSegment) => {
convertedSegments.push({
id: rawSegment,
value: rawSegment
});
return convertedSegments;
}, []);

return {
name: 'jwplayer',
ext: {
segtax: 502
},
segment: formattedSegments
};
}

export function addOrtbSiteContent(bid, contentId, contentData) {
if (!contentId && !contentData) {
return;
}

bids.forEach(bid => addTargetingToBid(bid, targeting));
let ortb2 = bid.ortb2 || {};
let site = ortb2.site = ortb2.site || {};
let content = site.content = site.content || {};

if (contentId) {
content.id = contentId;
}

if (contentData) {
const data = content.data = content.data || [];
data.push(contentData);
}

bid.ortb2 = ortb2;
}

function enrichBids(bids, targeting, contentId, contentData) {
if (!bids) {
return;
}

bids.forEach(bid => {
addTargetingToBid(bid, targeting);
addOrtbSiteContent(bid, contentId, contentData);
});
}

/*
deprecated
*/
export function addTargetingToBid(bid, targeting) {
if (!targeting) {
return;
}

const rtd = bid.rtd || {};
const jwRtd = {};
jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting });
Expand Down
38 changes: 27 additions & 11 deletions modules/jwplayerRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,46 @@ realTimeData = {
# Usage for Bid Adapters:

Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids.
Each bid for which targeting information was found will conform to the following object structure:
Each bid for which targeting information was found will have a ortb2 param conforming to the [oRTB v2 object structure](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf). The `ortb2` object will contain our proprietaty targeting segments in a format compliant with the [IAB's segment taxonomy structure](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/segtax.md).

Example:

```javascript
{
adUnitCode: 'xyz',
bidId: 'abc',
...,
rtd: {
jwplayer: {
targeting: {
segments: ['123', '456'],
content: {
id: 'jw_abc123'
}
ortb2: {
site: {
content: {
id: 'jw_abc123',
data: [{
name: 'jwplayer',
ext: {
segtax: 502
},
segment: [{
id: '123'
}, {
id: '456'
}]
}]
}
}
}
}
```

where:
- `segments` is an array of jwpseg targeting segments, of type string.
- `content` is an object containing metadata for the media. It may contain the following information:
- `id` is a unique identifier for the specific media asset.
- `ortb2` is an object containing first party data
- `site` is an object containing page specific information
- `content` is an object containing metadata for the media. It may contain the following information:
- `id` is a unique identifier for the specific media asset
- `data` is an array containing segment taxonomy objects that have the following parameters:
- `name` is the `jwplayer` string indicating the provider name
- `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy
- `segment` is an array containing the segment taxonomy values as an object where:
- `id` is the string representation of the data segment value.

**Example:**

Expand Down
197 changes: 195 additions & 2 deletions test/spec/modules/jwplayerRtdProvider_spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams,
formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid,
fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js';
fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentData } from 'modules/jwplayerRtdProvider.js';
import { server } from 'test/mocks/xhr.js';
import {addOrtbSiteContent} from '../../../modules/jwplayerRtdProvider';

describe('jwplayerRtdProvider', function() {
const testIdForSuccess = 'test_id_for_success';
Expand Down Expand Up @@ -412,7 +413,7 @@ describe('jwplayerRtdProvider', function() {
});
});

describe(' Extract Publisher Params', function () {
describe('Extract Publisher Params', function () {
const config = { mediaID: 'test' };

it('should exclude adUnits that do not support instream video and do not specify jwTargeting', function () {
Expand Down Expand Up @@ -480,6 +481,198 @@ describe('jwplayerRtdProvider', function() {
})
});

describe('Get content id', function() {
it('prefixes jw_ to the media id', function () {
const mediaId = 'mediaId';
const contentId = getContentId(mediaId);
expect(contentId).to.equal('jw_mediaId');
});

it('returns undefined when media id is empty', function () {
let contentId = getContentId();
expect(contentId).to.be.undefined;
contentId = getContentId('');
expect(contentId).to.be.undefined;
contentId = getContentId(null);
expect(contentId).to.be.undefined;
});
});

describe('Get Content Data', function () {
it('returns undefined when segments are empty', function () {
let data = getContentData(null);
expect(data).to.be.undefined;
data = getContentData(undefined);
expect(data).to.be.undefined;
data = getContentData([]);
expect(data).to.be.undefined;
});

it('returns proper format', function () {
const segment1 = 'segment1';
const segment2 = 'segment2';
const segment3 = 'segment3';
const data = getContentData([segment1, segment2, segment3]);
expect(data).to.have.property('name', 'jwplayer');
expect(data.ext).to.have.property('segtax', 502);
expect(data.segment[0]).to.deep.equal({ id: segment1, value: segment1 });
expect(data.segment[1]).to.deep.equal({ id: segment2, value: segment2 });
expect(data.segment[2]).to.deep.equal({ id: segment3, value: segment3 });
});
});

describe(' Add Ortb Site Content', function () {
it('should maintain object structure when id and data params are empty', function () {
const bid = {
ortb2: {
site: {
content: {
id: 'randomId'
},
random: {
random_sub: 'randomSub'
}
},
app: {
content: {
id: 'appId'
}
}
}
};
addOrtbSiteContent(bid);
expect(bid).to.have.nested.property('ortb2.site.content.id', 'randomId');
expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub');
expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId');
});

it('should create a structure compliant with the oRTB 2 spec', function() {
const bid = {};
const expectedId = 'expectedId';
const expectedData = { datum: 'datum' };
addOrtbSiteContent(bid, expectedId, expectedData);
expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId);
expect(bid).to.have.nested.property('ortb2.site.content.data');
expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData);
});

it('should respect existing structure when adding adding fields', function () {
const bid = {
ortb2: {
site: {
content: {
id: 'oldId'
},
random: {
random_sub: 'randomSub'
}
},
app: {
content: {
id: 'appId'
}
}
}
};

const expectedId = 'expectedId';
const expectedData = { datum: 'datum' };
addOrtbSiteContent(bid, expectedId, expectedData);
expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub');
expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId');
expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId);
expect(bid).to.have.nested.property('ortb2.site.content.data');
expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData);
});

it('should set content id', function () {
const bid = {};
const expectedId = 'expectedId';
addOrtbSiteContent(bid, expectedId);
expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId);
});

it('should override content id', function () {
const bid = {
ortb2: {
site: {
content: {
id: 'oldId'
}
}
}
};

const expectedId = 'expectedId';
addOrtbSiteContent(bid, expectedId);
expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId);
});

it('should keep previous content id when not set', function () {
const previousId = 'oldId';
const bid = {
ortb2: {
site: {
content: {
id: previousId,
data: [{ datum: 'first_datum' }]
}
}
}
};

addOrtbSiteContent(bid, null, { datum: 'new_datum' });
expect(bid).to.have.nested.property('ortb2.site.content.id', previousId);
});

it('should set content data', function () {
const bid = {};
const expectedData = { datum: 'datum' };
addOrtbSiteContent(bid, null, expectedData);
expect(bid).to.have.nested.property('ortb2.site.content.data');
expect(bid.ortb2.site.content.data).to.have.length(1);
expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData);
});

it('should append content data', function () {
const bid = {
ortb2: {
site: {
content: {
data: [{ datum: 'first_datum' }]
}
}
}
};

const expectedData = { datum: 'datum' };
addOrtbSiteContent(bid, null, expectedData);
expect(bid).to.have.nested.property('ortb2.site.content.data');
expect(bid.ortb2.site.content.data).to.have.length(2);
expect(bid.ortb2.site.content.data.pop()).to.be.deep.equal(expectedData);
});

it('should keep previous data when not set', function () {
const expectedId = 'expectedId';
const expectedData = { datum: 'first_datum' };
const bid = {
ortb2: {
site: {
content: {
data: [expectedData]
}
}
}
};

addOrtbSiteContent(bid, expectedId);
expect(bid).to.have.nested.property('ortb2.site.content.data');
expect(bid.ortb2.site.content.data).to.have.length(1);
expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData);
expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId);
});
});

describe('Add Targeting to Bid', function () {
const targeting = {foo: 'bar'};

Expand Down

0 comments on commit 378cf5a

Please sign in to comment.