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

Core: Sync between ortb2Imp and mediaTypes #12423

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 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
21 changes: 21 additions & 0 deletions src/banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { isArrayOfNums, isInteger, isStr } from './utils.js';

/**
* List of OpenRTB 2.x banner object properties with simple validators.
* Not included: `ext`
* reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md
*/
export const ORTB_BANNER_PARAMS = new Map([
[ 'format', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'object') ],
[ 'w', isInteger ],
[ 'h', isInteger ],
[ 'btype', isArrayOfNums ],
[ 'battr', isArrayOfNums ],
[ 'pos', isInteger ],
[ 'mimes', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string') ],
[ 'topframe', value => [1, 0].includes(value) ],
[ 'expdir', isArrayOfNums ],
[ 'api', isArrayOfNums ],
[ 'id', isStr ],
[ 'vcm', value => [1, 0].includes(value) ]
]);
14 changes: 14 additions & 0 deletions src/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
deepClone, getDefinedParams,
insertHtmlIntoIframe,
isArray,
isArrayOfNums,
isBoolean,
isInteger,
isNumber,
isPlainObject,
isStr,
logError,
pick,
triggerPixel
Expand All @@ -18,6 +20,18 @@ import {NATIVE} from './mediaTypes.js';
import {getRenderingData} from './adRendering.js';
import {getCreativeRendererSource} from './creativeRenderers.js';

/**
* List of OpenRTB 2.x native object properties with simple validators.
* Not included: `ext`
* reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md
*/
export const ORTB_NATIVE_PARAMS = new Map([
[ 'request', isStr ],
[ 'ver', isStr ],
[ 'api', isArrayOfNums ],
[ 'battr', isArrayOfNums ]
]);

/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
Expand Down
49 changes: 36 additions & 13 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ import {enrichFPD} from './fpd/enrichment.js';
import {allConsent} from './consentHandler.js';
import {insertLocatorFrame, markBidAsRendered, renderAdDirect, renderIfDeferred} from './adRendering.js';
import {getHighestCpm} from './utils/reducers.js';
import {fillVideoDefaults, validateOrtbVideoFields} from './video.js';
import {ORTB_VIDEO_PARAMS, fillVideoDefaults, validateOrtbVideoFields} from './video.js';
import { ORTB_NATIVE_PARAMS } from './native.js';
import { ORTB_BANNER_PARAMS } from './banner.js';

const pbjsInstance = getGlobal();
const { triggerUserSyncs } = userSync;
Expand Down Expand Up @@ -100,20 +102,41 @@ function validateSizes(sizes, targLength) {
return cleanSizes;
}

export function setBattrForAdUnit(adUnit, mediaType) {
const ortb2Imp = adUnit.ortb2Imp || {};
const mediaTypes = adUnit.mediaTypes || {};
// synchronize fields between mediaTypes[mediaType] and ortb2Imp[mediaType]
export function syncOrtb2(adUnit, mediaType) {
const ortb2Imp = deepAccess(adUnit, `ortb2Imp.${mediaType}`);
const mediaTypes = deepAccess(adUnit, `mediaTypes.${mediaType}`);

if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) {
logWarn(`Ad unit ${adUnit.code} specifies conflicting ortb2Imp.${mediaType}.battr and mediaTypes.${mediaType}.battr, the latter will be ignored`, adUnit);
if (!ortb2Imp && !mediaTypes) {
// omitting sync due to not present mediaType
return;
}

const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr;
const fields = {
'video': ORTB_VIDEO_PARAMS,
'native': ORTB_NATIVE_PARAMS,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not deal with native here, the mediaType and imp fields have no overlap, and it gets complicated with mediaType.native.ortb.

'banner': ORTB_BANNER_PARAMS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be broken out with feature tags (FEATURE.NATIVE & FEATURE.VIDEO)

}[mediaType];

if (battr != null) {
deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr);
deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr);
if (!fields) {
return;
}

[...fields].forEach(([key, validator]) => {
const mediaTypesFieldValue = deepAccess(adUnit, `mediaTypes.${mediaType}.${key}`);
const ortbFieldValue = deepAccess(adUnit, `ortb2Imp.${mediaType}.${key}`);

if (mediaTypesFieldValue == undefined && ortbFieldValue == undefined) {
// omitting the params if it's not defined on either of sides
} else if (mediaTypesFieldValue == undefined) {
deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue);
} else if (ortbFieldValue == undefined) {
deepSetValue(adUnit, `ortb2Imp.${mediaType}.${key}`, mediaTypesFieldValue);
} else {
logWarn(`adUnit ${adUnit.code}: specifies conflicting ortb2Imp.${mediaType}.${key} and mediaTypes.${mediaType}.${key}, the latter will be ignored`, adUnit);
deepSetValue(adUnit, `mediaTypes.${mediaType}.${key}`, ortbFieldValue);
}
});
}

function validateBannerMediaType(adUnit) {
Expand All @@ -128,7 +151,7 @@ function validateBannerMediaType(adUnit) {
logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.');
delete validatedAdUnit.mediaTypes.banner
}
setBattrForAdUnit(validatedAdUnit, 'banner');
syncOrtb2(validatedAdUnit, 'banner')
return validatedAdUnit;
}

Expand All @@ -152,7 +175,7 @@ function validateVideoMediaType(adUnit) {
}
}
validateOrtbVideoFields(validatedAdUnit);
setBattrForAdUnit(validatedAdUnit, 'video');
syncOrtb2(validatedAdUnit, 'video');
return validatedAdUnit;
}

Expand Down Expand Up @@ -202,7 +225,7 @@ function validateNativeMediaType(adUnit) {
logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.');
delete validatedAdUnit.mediaTypes.native.icon.sizes;
}
setBattrForAdUnit(validatedAdUnit, 'native');
syncOrtb2(validatedAdUnit, 'native');
return validatedAdUnit;
}

Expand Down
183 changes: 183 additions & 0 deletions test/spec/banner_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as utils from '../../src/utils.js';
import { syncOrtb2 } from '../../src/prebid.js';

describe('banner', () => {
describe('syncOrtb2', () => {
let logWarnSpy;

beforeEach(function () {
logWarnSpy = sinon.spy(utils, 'logWarn');
});

afterEach(function () {
utils.logWarn.restore();
});

it('should properly sync fields if both present', () => {
const adUnit = {
mediaTypes: {
banner: {
format: [{w: 100, h: 100}],
btype: [1, 2, 34], // should be overwritten with value from ortb2Imp
battr: [3, 4],
maxduration: 'omitted_value' // should be omitted during copying - not part of banner obj spec
}
},
ortb2Imp: {
banner: {
request: '{payload: true}',
pos: 5,
btype: [999, 999],
vcm: 0,
foobar: 'omitted_value' // should be omitted during copying - not part of banner obj spec
}
}
};

const expected = {
mediaTypes: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
maxduration: 'omitted_value'
}
},
ortb2Imp: {
banner: {
format: [{w: 100, h: 100}],
request: '{payload: true}',
pos: 5,
btype: [999, 999],
battr: [3, 4],
vcm: 0,
foobar: 'omitted_value'
}
}
};

syncOrtb2(adUnit, 'banner');
expect(adUnit).to.deep.eql(expected);

assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting btype');
});

it('should omit sync if mediaType not present on adUnit', () => {
const adUnit = {
mediaTypes: {
video: {
fieldToOmit: 'omitted_value'
}
},
ortb2Imp: {
video: {
fieldToOmit2: 'omitted_value'
}
}
};

syncOrtb2(adUnit, 'banner');

expect(adUnit.ortb2Imp.banner).to.be.undefined;
expect(adUnit.mediaTypes.banner).to.be.undefined;
});

it('should properly sync if mediaTypes is not present on any of side', () => {
const adUnit = {
mediaTypes: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
maxduration: 'omitted_value'
}
},
ortb2Imp: {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ortb2Imp: {}

};

const expected1 = {
mediaTypes: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
maxduration: 'omitted_value'
}
},
ortb2Imp: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
}
}
};

syncOrtb2(adUnit, 'banner');
expect(adUnit).to.deep.eql(expected1);

const adUnit2 = {
mediaTypes: {},
ortb2Imp: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
maxduration: 'omitted_value'
}
}
};

const expected2 = {
mediaTypes: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
}
},
ortb2Imp: {
banner: {
format: [{w: 100, h: 100}],
btype: [999, 999],
pos: 5,
battr: [3, 4],
vcm: 0,
maxduration: 'omitted_value'
}
}
};

syncOrtb2(adUnit2, 'banner');
expect(adUnit2).to.deep.eql(expected2);
});

it('should not create empty banner object on ortb2Imp if there was nothing to copy', () => {
const adUnit2 = {
mediaTypes: {
banner: {
noOrtbBannerField1: 'value',
noOrtbBannerField2: 'value'
}
},
ortb2Imp: {
// lack of banner field
}
};
syncOrtb2(adUnit2, 'banner');
expect(adUnit2.ortb2Imp.banner).to.be.undefined
});
});
})
Loading