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

JwPlayer RTD module: Write to oRTB content segments #7886

Merged
merged 6 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
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
*/
Rothalack marked this conversation as resolved.
Show resolved Hide resolved
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