Skip to content

Commit

Permalink
IX Adapter: buildRequests refactor (prebid#7364)
Browse files Browse the repository at this point in the history
* buildRequests refactor

* remove use of Array.includes

Co-authored-by: Love Sharma <love.sharma@indexexchange.com>
Co-authored-by: Kajan Umakanthan <umakajan@umakajan.com>
  • Loading branch information
3 people authored and Chris Pabst committed Jan 10, 2022
1 parent fd00dd5 commit 453f26d
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 93 deletions.
180 changes: 115 additions & 65 deletions modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { config } from '../src/config.js';
import find from 'core-js-pure/features/array/find.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { INSTREAM, OUTSTREAM } from '../src/video.js';
import includes from 'core-js-pure/features/array/includes.js';

const BIDDER_CODE = 'ix';
const ALIAS_BIDDER_CODE = 'roundel';
Expand Down Expand Up @@ -108,8 +109,8 @@ function bidToVideoImp(bid) {
const imp = bidToImp(bid);
const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video');
const videoParamRef = utils.deepAccess(bid, 'params.video');

if (!checkVideoParams(bid, videoAdUnitRef, videoParamRef)) {
const videoParamErrors = checkVideoParams(videoAdUnitRef, videoParamRef);
if (videoParamErrors.length) {
return {};
}

Expand Down Expand Up @@ -305,7 +306,7 @@ function isValidSize(size) {
* @return {boolean} True if the size object is an element of the size array, and false
* otherwise.
*/
function includesSize(sizeArray = [], size) {
function includesSize(sizeArray = [], size = []) {
if (isValidSize(sizeArray)) {
return sizeArray[0] === size[0] && sizeArray[1] === size[1];
}
Expand All @@ -319,13 +320,12 @@ function includesSize(sizeArray = [], size) {

/**
* Checks if all required video params are present
* @param {object} bid Bid Object
* @param {object} mediaTypeVideoRef Ad unit level mediaTypes object
* @param {object} paramsVideoRef IX bidder params level video object
* @returns bool Are the required video params available
* @returns {string[]} Are the required video params available
*/
function checkVideoParams(bid, mediaTypeVideoRef, paramsVideoRef) {
let reqParamsPresent = true;
function checkVideoParams(mediaTypeVideoRef, paramsVideoRef) {
const errorList = [];

if (!mediaTypeVideoRef) {
utils.logWarn('IX Bid Adapter: mediaTypes.video is the preferred location for video params in ad unit');
Expand All @@ -336,23 +336,21 @@ function checkVideoParams(bid, mediaTypeVideoRef, paramsVideoRef) {
const propInVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty(property);

if (!propInMediaType && !propInVideoRef) {
utils.logError(`IX Bid Adapter: ${property} is not included in either the adunit or params level`);
reqParamsPresent = false;
errorList.push(`IX Bid Adapter: ${property} is not included in either the adunit or params level`);
}
}

// early return
if (!reqParamsPresent) {
return false;
}

// check protocols/protocol
const protocolMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocol');
const protocolsMediaType = mediaTypeVideoRef && mediaTypeVideoRef.hasOwnProperty('protocols');
const protocolVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocol');
const protocolsVideoRef = paramsVideoRef && paramsVideoRef.hasOwnProperty('protocols');

return protocolMediaType || protocolsMediaType || protocolVideoRef || protocolsVideoRef;
if (!(protocolMediaType || protocolsMediaType || protocolVideoRef || protocolsVideoRef)) {
errorList.push('IX Bid Adapter: protocol/protcols is not included in either the adunit or params level');
}

return errorList;
}

/**
Expand Down Expand Up @@ -825,6 +823,66 @@ function removeFromSizes(bannerSizeList, bannerSize) {
}
}

/**
* Creates IX Video impressions based on validBidRequests
* @param {object} validBidRequest valid request provided by prebid
* @param {object} videoImps reference to created video impressions
*/
function createVideoImps(validBidRequest, videoImps) {
const imp = bidToVideoImp(validBidRequest);
if (Object.keys(imp).length != 0) {
videoImps[validBidRequest.transactionId] = {};
videoImps[validBidRequest.transactionId].ixImps = [];
videoImps[validBidRequest.transactionId].ixImps.push(imp);
}
}

/**
* Creates IX banner impressions based on validBidRequests
* @param {object} validBidRequest valid request provided by prebid
* @param {object} missingBannerSizes reference to missing banner config sizes
* @param {object} bannerImps reference to created banner impressions
*/
function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) {
const DEFAULT_IX_CONFIG = {
detectMissingSizes: true,
};

const ixConfig = { ...DEFAULT_IX_CONFIG, ...config.getConfig('ix') };

let imp = bidToBannerImp(validBidRequest);

const bannerSizeDefined = includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), utils.deepAccess(validBidRequest, 'params.size'));

// Create IX imps from params.size
if (bannerSizeDefined) {
if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) {
bannerImps[validBidRequest.transactionId] = {};
}
if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) {
bannerImps[validBidRequest.transactionId].ixImps = []
}
bannerImps[validBidRequest.transactionId].ixImps.push(imp);
}

if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) {
updateMissingSizes(validBidRequest, missingBannerSizes, imp);
}
}

/**
* Determines IX configuration type based on IX params
* @param {object} valid IX configured param
* @returns {string}
*/
function detectParamsType(validBidRequest) {
if (utils.deepAccess(validBidRequest, 'params.video') && utils.deepAccess(validBidRequest, 'mediaTypes.video')) {
return VIDEO;
}

return BANNER;
}

/**
* Updates the Object to track missing banner sizes.
*
Expand Down Expand Up @@ -933,9 +991,15 @@ export const spec = {
return false;
}
}
// For multi format unit
if (!mediaTypeBannerSizes && (mediaTypeVideoRef || paramsVideoRef)) {
return checkVideoParams(bid, mediaTypeVideoRef, paramsVideoRef);

if (mediaTypeVideoRef && paramsVideoRef) {
const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef);
if (errorList.length) {
errorList.forEach((err) => {
utils.logError(err);
});
return false;
}
}
return true;
},
Expand All @@ -948,58 +1012,43 @@ export const spec = {
* @return {object} Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
let reqs = [];
let bannerImps = {};
let videoImps = {};
let validBidRequest = null;

// To capture the missing sizes i.e not configured for ix
let missingBannerSizes = {};
const reqs = []; // Stores banner + video requests
const bannerImps = {}; // Stores created banner impressions
const videoImps = {}; // Stores created video impressions
const multiFormatAdUnits = {}; // Stores references identified multi-format adUnits
const missingBannerSizes = {}; // To capture the missing sizes i.e not configured for ix

// Step 1: Create impresssions from IX params
validBidRequests.forEach((validBidRequest) => {
const adUnitMediaTypes = Object.keys(utils.deepAccess(validBidRequest, 'mediaTypes', {}))

switch (detectParamsType(validBidRequest)) {
case BANNER:
createBannerImps(validBidRequest, missingBannerSizes, bannerImps);
break;
case VIDEO:
createVideoImps(validBidRequest, videoImps)
break;
}

const DEFAULT_IX_CONFIG = {
detectMissingSizes: true,
};
if (includes(adUnitMediaTypes, BANNER) && includes(adUnitMediaTypes, VIDEO)) {
multiFormatAdUnits[validBidRequest.transactionId] = validBidRequest;
}
});

const ixConfig = { ...DEFAULT_IX_CONFIG, ...config.getConfig('ix') };

for (let i = 0; i < validBidRequests.length; i++) {
validBidRequest = validBidRequests[i];
const videoAdUnitRef = utils.deepAccess(validBidRequest, 'mediaTypes.video');
const videoParamRef = utils.deepAccess(validBidRequest, 'params.video');

// identify video ad unit
if (validBidRequest.mediaType === VIDEO || videoAdUnitRef || videoParamRef) {
if (!videoImps.hasOwnProperty(validBidRequest.transactionId)) {
const imp = bidToVideoImp(validBidRequest);
if (Object.keys(imp).length != 0) {
videoImps[validBidRequest.transactionId] = {};
videoImps[validBidRequest.transactionId].ixImps = [];
videoImps[validBidRequest.transactionId].ixImps.push(imp);
}
}
// Step 2: Create impressions for multi-format adunits missing configurations
Object.keys(multiFormatAdUnits).forEach((transactionId) => {
const validBidRequest = multiFormatAdUnits[transactionId];
if (!bannerImps[transactionId]) {
createBannerImps(validBidRequest, missingBannerSizes, bannerImps);
}

if (validBidRequest.mediaType === BANNER ||
(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes')) ||
(!validBidRequest.mediaType && !validBidRequest.mediaTypes)) {
let imp = bidToBannerImp(validBidRequest);
// Create IX imps from params.size
if (utils.deepAccess(validBidRequest, 'params.size')) {
if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) {
bannerImps[validBidRequest.transactionId] = {};
}
if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) {
bannerImps[validBidRequest.transactionId].ixImps = []
}
bannerImps[validBidRequest.transactionId].ixImps.push(imp);
}
if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) {
updateMissingSizes(validBidRequest, missingBannerSizes, imp);
}
if (!videoImps[transactionId]) {
createVideoImps(validBidRequest, videoImps)
}
}
});

// Finding the missing banner sizes, and making impressions for them
// Step 3: Update banner impressions with missing sizes
for (var transactionId in missingBannerSizes) {
if (missingBannerSizes.hasOwnProperty(transactionId)) {
let missingSizes = missingBannerSizes[transactionId].missingSizes;
Expand All @@ -1014,13 +1063,14 @@ export const spec = {

let origImp = missingBannerSizes[transactionId].impression;
for (let i = 0; i < missingSizes.length; i++) {
let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]);
let newImp = createMissingBannerImp(validBidRequests[0], origImp, missingSizes[i]);
bannerImps[transactionId].missingImps.push(newImp);
bannerImps[transactionId].missingCount++;
}
}
}

// Step 4: Build banner & video requests
if (Object.keys(bannerImps).length > 0) {
reqs.push(...buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION));
}
Expand Down
29 changes: 1 addition & 28 deletions test/spec/modules/ixBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1372,12 +1372,6 @@ describe('IndexexchangeAdapter', function () {
const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0];
const queryWithoutSchain = requestWithoutSchain.data;

const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID);
delete bidWithoutMediaType[0].mediaTypes;
bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]];
const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_OPTION)[0];
const queryWithoutMediaType = requestWithoutMediaType.data;

it('request should be made to IX endpoint with GET method', function () {
expect(requestMethod).to.equal('GET');
expect(requestUrl).to.equal(IX_SECURE_ENDPOINT);
Expand Down Expand Up @@ -1598,27 +1592,6 @@ describe('IndexexchangeAdapter', function () {
});
});

it('payload without mediaType should have correct format and value', function () {
const payload = JSON.parse(queryWithoutMediaType.r);

expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId);
expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer);
expect(payload.site.ref).to.equal(document.referrer);
expect(payload.ext.source).to.equal('prebid');
expect(payload.imp).to.be.an('array');
expect(payload.imp).to.have.lengthOf(1);
});

it('impression without mediaType should have correct format and value', function () {
const impression = JSON.parse(queryWithoutMediaType.r).imp[0];

expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId);
expect(impression.banner.format).to.be.length(1);
expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]);
expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]);
expect(impression.banner.topframe).to.be.oneOf([0, 1]);
});

it('impression should have sid if id is configured as number', function () {
const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]);
bid.params.id = 50;
Expand Down Expand Up @@ -2005,7 +1978,7 @@ describe('IndexexchangeAdapter', function () {

it('should handle unexpected context', function () {
const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]);
bid.mediaTypes.video.context = 'VaccineJanssen';
bid.mediaTypes.video.context = 'not-valid';
const request = spec.buildRequests([bid])[0];
const impression = JSON.parse(request.data.r).imp[0];
expect(impression.video.placement).to.be.undefined;
Expand Down

0 comments on commit 453f26d

Please sign in to comment.