Skip to content

Commit

Permalink
Mobian RTD Provider: Adds prefix to ortb data as per config (prebid#1…
Browse files Browse the repository at this point in the history
…2596)

* Adds prefix to ortb data

* Updates

* Fixes tests

* Updates

* Fixes AP values not setting in GAM

* Fixes tests

* Updates types
  • Loading branch information
arielmtk authored Dec 30, 2024
1 parent 376a491 commit 97594d9
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 81 deletions.
125 changes: 74 additions & 51 deletions modules/mobianRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,24 @@ import { setKeyValue } from '../libraries/gptUtils/gptUtils.js';

export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async';

export const AP_VALUES = 'apValues';
export const CATEGORIES = 'categories';
export const EMOTIONS = 'emotions';
export const GENRES = 'genres';
export const RISK = 'risk';
export const SENTIMENT = 'sentiment';
export const THEMES = 'themes';
export const TONES = 'tones';

export const CONTEXT_KEYS = [
'apValues',
'categories',
'emotions',
'genres',
'risk',
'sentiment',
'themes',
'tones'
AP_VALUES,
CATEGORIES,
EMOTIONS,
GENRES,
RISK,
SENTIMENT,
THEMES,
TONES
];

const AP_KEYS = ['a0', 'a1', 'p0', 'p1'];
Expand Down Expand Up @@ -73,6 +82,24 @@ function makeMemoizedFetch() {

export const getContextData = makeMemoizedFetch();

const entriesToObjectReducer = (acc, [key, value]) => ({ ...acc, [key]: value });

export function makeContextDataToKeyValuesReducer(config) {
const { prefix } = config;
return function contextDataToKeyValuesReducer(keyValues, [key, value]) {
if (key === AP_VALUES) {
AP_KEYS.forEach((apKey) => {
if (!value?.[apKey]?.length) return;
keyValues.push([`${prefix}_ap_${apKey}`, value[apKey].map((v) => String(v))]);
});
}
if (value?.length) {
keyValues.push([`${prefix}_${key}`, value]);
}
return keyValues;
}
}

export async function fetchContextData() {
const pageUrl = encodeURIComponent(window.location.href);
const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`;
Expand Down Expand Up @@ -101,84 +128,80 @@ export function getConfig(config) {
}

/**
* @param {MobianConfigParams} parsedConfig
* @param {MobianConfig} config
* @param {MobianContextData} contextData
* @returns {function}
*/
export function setTargeting(parsedConfig, contextData) {
const { publisherTargeting, prefix } = parsedConfig;
export function setTargeting(config, contextData) {
logMessage('context', contextData);
const keyValues = Object.entries(contextData)
.filter(([key]) => config.publisherTargeting.includes(key))
.reduce(makeContextDataToKeyValuesReducer(config), [])

CONTEXT_KEYS.forEach((key) => {
if (!publisherTargeting.includes(key)) return;

if (key === 'apValues') {
AP_KEYS.forEach((apKey) => {
if (!contextData[key]?.[apKey]?.length) return;
logMessage(`${prefix}_ap_${apKey}`, contextData[key][apKey]);
setKeyValue(`${prefix}_ap_${apKey}`, contextData[key][apKey]);
});
return;
}

if (contextData[key]?.length) {
logMessage(`${prefix}_${key}`, contextData[key]);
setKeyValue(`${prefix}_${key}`, contextData[key]);
}
});
keyValues.forEach(([key, value]) => setKeyValue(key, value));
}

/**
* @param {Object|string} contextData
* @returns {MobianContextData}
*/
export function makeDataFromResponse(contextData) {
const data = typeof contextData === 'string' ? safeJSONParse(contextData) : contextData;
const results = data.results;
if (!results) {
return {};
}
return {
apValues: results.ap || {},
categories: results.mobianContentCategories,
emotions: results.mobianEmotions,
genres: results.mobianGenres,
risk: results.mobianRisk || 'unknown',
sentiment: results.mobianSentiment || 'unknown',
themes: results.mobianThemes,
tones: results.mobianTones,
[AP_VALUES]: results.ap || {},
[CATEGORIES]: results.mobianContentCategories,
[EMOTIONS]: results.mobianEmotions,
[GENRES]: results.mobianGenres,
[RISK]: results.mobianRisk || 'unknown',
[SENTIMENT]: results.mobianSentiment || 'unknown',
[THEMES]: results.mobianThemes,
[TONES]: results.mobianTones,
};
}

export function extendBidRequestConfig(bidReqConfig, contextData) {
/**
* @param {Object} bidReqConfig
* @param {MobianContextData} contextData
* @param {MobianConfig} config
*/
export function extendBidRequestConfig(bidReqConfig, contextData, config) {
logMessage('extendBidRequestConfig', bidReqConfig, contextData);
const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global;
const keyValues = Object.entries(contextData)
.filter(([key]) => config.advertiserTargeting.includes(key))
.reduce(makeContextDataToKeyValuesReducer(config), [])
.reduce(entriesToObjectReducer, {});

ortb2Site.ext = ortb2Site.ext || {};
ortb2Site.ext.data = {
...(ortb2Site.ext.data || {}),
...contextData
...keyValues
};

return bidReqConfig;
}

/**
* @param {MobianConfig} config
* @param {MobianConfig} rawConfig
* @returns {boolean}
*/
function init(config) {
logMessage('init', config);

const parsedConfig = getConfig(config);

if (parsedConfig.publisherTargeting.length) {
getContextData().then((contextData) => setTargeting(parsedConfig, contextData));
function init(rawConfig) {
logMessage('init', rawConfig);
const config = getConfig(rawConfig);
if (config.publisherTargeting.length) {
getContextData().then((contextData) => setTargeting(config, contextData));
}

return true;
}

function getBidRequestData(bidReqConfig, callback, config) {
function getBidRequestData(bidReqConfig, callback, rawConfig) {
logMessage('getBidRequestData', bidReqConfig);

const { advertiserTargeting } = getConfig(config);
const config = getConfig(rawConfig);
const { advertiserTargeting } = config;

if (!advertiserTargeting.length) {
callback();
Expand All @@ -187,7 +210,7 @@ function getBidRequestData(bidReqConfig, callback, config) {

getContextData()
.then((contextData) => {
extendBidRequestConfig(bidReqConfig, contextData);
extendBidRequestConfig(bidReqConfig, contextData, config);
})
.catch(() => {})
.finally(() => callback());
Expand Down
96 changes: 66 additions & 30 deletions test/spec/modules/mobianRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import * as ajax from 'src/ajax.js';
import * as gptUtils from 'libraries/gptUtils/gptUtils.js';
import {
CONTEXT_KEYS,
AP_VALUES,
CATEGORIES,
EMOTIONS,
GENRES,
RISK,
SENTIMENT,
THEMES,
TONES,
extendBidRequestConfig,
fetchContextData,
getConfig,
getContextData,
makeContextDataToKeyValuesReducer,
makeDataFromResponse,
setTargeting,
} from 'modules/mobianRtdProvider.js';
Expand Down Expand Up @@ -35,14 +44,29 @@ describe('Mobian RTD Submodule', function () {
});

const mockContextData = {
apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] },
categories: [],
emotions: ['affection'],
genres: [],
risk: 'low',
sentiment: 'positive',
themes: [],
tones: [],
[AP_VALUES]: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] },
[CATEGORIES]: [],
[EMOTIONS]: ['affection'],
[GENRES]: [],
[RISK]: 'low',
[SENTIMENT]: 'positive',
[THEMES]: [],
[TONES]: [],
}

const mockKeyValues = {
'mobian_ap_a1': ['2313', '12'],
'mobian_ap_p0': ['1231231', '212'],
'mobian_ap_p1': ['231', '419'],
'mobian_emotions': ['affection'],
'mobian_risk': 'low',
'mobian_sentiment': 'positive',
}

const mockConfig = {
prefix: 'mobian',
publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES],
advertiserTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES],
}

beforeEach(function () {
Expand Down Expand Up @@ -79,10 +103,6 @@ describe('Mobian RTD Submodule', function () {

describe('makeDataFromResponse', function () {
it('should format context data response', async function () {
ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) {
callbacks.success(mockResponse);
});

const data = makeDataFromResponse(mockResponse);
expect(data).to.deep.equal(mockContextData);
});
Expand All @@ -103,14 +123,14 @@ describe('Mobian RTD Submodule', function () {
it('should set targeting key-value pairs as per config', function () {
const parsedConfig = {
prefix: 'mobian',
publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'],
publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES],
};
setTargeting(parsedConfig, mockContextData);

expect(setKeyValueSpy.callCount).to.equal(6);
expect(setKeyValueSpy.calledWith('mobian_ap_a1', [2313, 12])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_ap_p0', [1231231, 212])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_ap_p1', [231, 419])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_ap_a1', ['2313', '12'])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_ap_p0', ['1231231', '212'])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_ap_p1', ['231', '419'])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true);
expect(setKeyValueSpy.calledWith('mobian_sentiment', 'positive')).to.equal(true);
Expand All @@ -124,7 +144,7 @@ describe('Mobian RTD Submodule', function () {
it('should not set key-value pairs if context data is empty', function () {
const parsedConfig = {
prefix: 'mobian',
publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'],
publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES],
};
setTargeting(parsedConfig, {});

Expand All @@ -134,7 +154,7 @@ describe('Mobian RTD Submodule', function () {
it('should only set key-value pairs for the keys specified in config', function () {
const parsedConfig = {
prefix: 'mobian',
publisherTargeting: ['emotions', 'risk'],
publisherTargeting: [EMOTIONS, RISK],
};

setTargeting(parsedConfig, mockContextData);
Expand All @@ -155,26 +175,26 @@ describe('Mobian RTD Submodule', function () {

describe('extendBidRequestConfig', function () {
it('should extend bid request config with context data', function () {
const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData);
const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig);
expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues);
});

it('should not override existing data', function () {
bidReqConfig.ortb2Fragments.global.site.ext.data = {
existing: 'data'
};

const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig);
expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal({
existing: 'data',
...mockContextData
...mockKeyValues
});
});

it('should create data object if missing', function () {
delete bidReqConfig.ortb2Fragments.global.site.ext.data;
const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData);
expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData);
const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig);
expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues);
});
});

Expand All @@ -184,27 +204,27 @@ describe('Mobian RTD Submodule', function () {
name: 'mobianBrandSafety',
params: {
prefix: 'mobiantest',
publisherTargeting: ['apValues'],
advertiserTargeting: ['emotions'],
publisherTargeting: [AP_VALUES],
advertiserTargeting: [EMOTIONS],
}
});
expect(config).to.deep.equal({
prefix: 'mobiantest',
publisherTargeting: ['apValues'],
advertiserTargeting: ['emotions'],
publisherTargeting: [AP_VALUES],
advertiserTargeting: [EMOTIONS],
});
});

it('should set default values for configs not set', function () {
const config = getConfig({
name: 'mobianBrandSafety',
params: {
publisherTargeting: ['apValues'],
publisherTargeting: [AP_VALUES],
}
});
expect(config).to.deep.equal({
prefix: 'mobian',
publisherTargeting: ['apValues'],
publisherTargeting: [AP_VALUES],
advertiserTargeting: [],
});
});
Expand Down Expand Up @@ -242,4 +262,20 @@ describe('Mobian RTD Submodule', function () {
});
});
});

describe('makeContextDataToKeyValuesReducer', function () {
it('should format context data to key-value pairs', function () {
const config = getConfig({
name: 'mobianBrandSafety',
params: {
prefix: 'mobian',
publisherTargeting: true,
advertiserTargeting: true,
}
});
const keyValues = Object.entries(mockContextData).reduce(makeContextDataToKeyValuesReducer(config), []);
const keyValuesObject = Object.fromEntries(keyValues);
expect(keyValuesObject).to.deep.equal(mockKeyValues);
});
});
});

0 comments on commit 97594d9

Please sign in to comment.