Skip to content

Commit

Permalink
Add adapter for IAS (#2056)
Browse files Browse the repository at this point in the history
* PET-201: got a working version

* PET-201: encoded query string

* PET-201: added unit tests

* PET-201: added missing keyword

* PET-201: corrected coding styles

* PET-201: decreased cpm so real bidders could win per code review
  • Loading branch information
katzhang-ias authored and matthewlane committed Feb 8, 2018
1 parent 7e1abbe commit ace5903
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 0 deletions.
117 changes: 117 additions & 0 deletions modules/iasBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';

const BIDDER_CODE = 'ias';

function isBidRequestValid(bid) {
const { pubId, adUnitPath } = bid.params;
return !!(pubId && adUnitPath);
}

/**
* Converts GPT-style size array into a string
* @param {Array} sizes: list of GPT-style sizes, e.g. [[300, 250], [300, 300]]
* @return {String} a string containing sizes, e.g. '[300.250,300.300]'
*/
function stringifySlotSizes(sizes) {
let result = '';
if (utils.isArray(sizes)) {
result = sizes.reduce((acc, size) => {
acc.push(size.join('.'));
return acc;
}, []);
result = '[' + result.join(',') + ']';
}
return result;
}

function stringifySlot(bidRequest) {
const id = bidRequest.adUnitCode;
const ss = stringifySlotSizes(bidRequest.sizes);
const p = bidRequest.params.adUnitPath;
const slot = { id, ss, p };
const keyValues = utils.getKeys(slot).map(function(key) {
return [key, slot[key]].join(':');
});
return '{' + keyValues.join(',') + '}';
}

function stringifyWindowSize() {
return [window.innerWidth || -1, window.innerHeight || -1].join('.');
}

function stringifyScreenSize() {
return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.');
}

function buildRequests(bidRequests) {
const IAS_HOST = '//pixel.adsafeprotected.com/services/pub';
const anId = bidRequests[0].params.pubId;

let queries = [];
queries.push(['anId', anId]);
queries = queries.concat(bidRequests.reduce(function(acc, request) {
acc.push(['slot', stringifySlot(request)]);
return acc;
}, []));

queries.push(['wr', stringifyWindowSize()]);
queries.push(['sr', stringifyScreenSize()]);

const queryString = encodeURI(queries.map(qs => qs.join('=')).join('&'));

return {
method: 'GET',
url: IAS_HOST,
data: queryString,
bidRequest: bidRequests[0]
}
}

function getPageLevelKeywords(response) {
let result = {};
shallowMerge(result, response.brandSafety);
result.fr = response.fr;
return result;
}

function shallowMerge(dest, src) {
utils.getKeys(src).reduce((dest, srcKey) => {
dest[srcKey] = src[srcKey];
return dest;
}, dest);
}

function interpretResponse(serverResponse, request) {
const iasResponse = serverResponse.body;
const bidResponses = [];

// Keys in common bid response are not used;
// Necessary to get around with prebid's common bid response check
const commonBidResponse = {
requestId: request.bidRequest.bidId,
cpm: 0.01,
width: 100,
height: 200,
creativeId: 434,
dealId: 42,
currency: 'usd',
netRevenue: true,
ttl: 360
};

shallowMerge(commonBidResponse, getPageLevelKeywords(iasResponse));
commonBidResponse.slots = iasResponse.slots;
bidResponses.push(commonBidResponse);
return bidResponses;
}

export const spec = {
code: BIDDER_CODE,
aliases: [],
isBidRequestValid: isBidRequestValid,
buildRequests: buildRequests,
interpretResponse: interpretResponse
};

registerBidder(spec);
30 changes: 30 additions & 0 deletions modules/iasBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Overview

```
Module Name: Integral Ad Science(IAS) Bidder Adapter
Module Type: Bidder Adapter
Maintainer: kat@integralads.com
```

# Description

This module is an integration with prebid.js with an IAS product, pet.js. It is not a bidder per se but works in a similar way: retrieve data that publishers might be interested in setting keyword targeting.

# Test Parameters
```
var adUnits = [
{
code: 'ias-dfp-test-async',
sizes: [[300, 250]], // a display size
bids: [
{
bidder: "ias",
params: {
pubId: '99',
adUnitPath: '/57514611/news.com'
}
}
]
}
];
```
190 changes: 190 additions & 0 deletions test/spec/modules/iasBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { expect } from 'chai';
import { spec } from 'modules/iasBidAdapter';

describe('iasBidAdapter is an adapter that', () => {
it('has the correct bidder code', () => {
expect(spec.code).to.equal('ias');
});
describe('has a method `isBidRequestValid` that', () => {
it('exists', () => {
expect(spec.isBidRequestValid).to.be.a('function');
});
it('returns false if bid params misses `pubId`', () => {
expect(spec.isBidRequestValid(
{
params: {
adUnitPath: 'someAdUnitPath'
}
})).to.equal(false);
});
it('returns false if bid params misses `adUnitPath`', () => {
expect(spec.isBidRequestValid(
{
params: {
pubId: 'somePubId'
}
})).to.equal(false);
});
it('returns true otherwise', () => {
expect(spec.isBidRequestValid(
{
params: {
adUnitPath: 'someAdUnitPath',
pubId: 'somePubId',
someOtherParam: 'abc'
}
})).to.equal(true);
});
});

describe('has a method `buildRequests` that', () => {
it('exists', () => {
expect(spec.buildRequests).to.be.a('function');
});
describe('given bid requests, returns a `ServerRequest` instance that', () => {
let bidRequests, IAS_HOST;
beforeEach(() => {
IAS_HOST = '//pixel.adsafeprotected.com/services/pub';
bidRequests = [
{
adUnitCode: 'one-div-id',
auctionId: 'someAuctionId',
bidId: 'someBidId',
bidder: 'ias',
bidderRequestId: 'someBidderRequestId',
params: {
pubId: '1234',
adUnitPath: '/a/b/c'
},
sizes: [
[10, 20],
[300, 400]
],
transactionId: 'someTransactionId'
},
{
adUnitCode: 'two-div-id',
auctionId: 'someAuctionId',
bidId: 'someBidId',
bidder: 'ias',
bidderRequestId: 'someBidderRequestId',
params: {
pubId: '1234',
adUnitPath: '/d/e/f'
},
sizes: [
[50, 60]
],
transactionId: 'someTransactionId'
}
];
});
it('has property `method` of `GET`', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
method: 'GET'
});
});
it('has property `url` to be the correct IAS endpoint', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
url: IAS_HOST
});
});
describe('has property `data` that is an encode query string containing information such as', () => {
let val;
const ANID_PARAM = 'anId';
const SLOT_PARAM = 'slot';
const SLOT_ID_PARAM = 'id';
const SLOT_SIZE_PARAM = 'ss';
const SLOT_AD_UNIT_PATH_PARAM = 'p';

beforeEach(() => val = decodeURI(spec.buildRequests(bidRequests).data));
it('publisher id', () => {
expect(val).to.have.string(`${ANID_PARAM}=1234`);
});
it('ad slot`s id, size and ad unit path', () => {
expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:one-div-id,${SLOT_SIZE_PARAM}:[10.20,300.400],${SLOT_AD_UNIT_PATH_PARAM}:/a/b/c}`);
expect(val).to.have.string(`${SLOT_PARAM}={${SLOT_ID_PARAM}:two-div-id,${SLOT_SIZE_PARAM}:[50.60],${SLOT_AD_UNIT_PATH_PARAM}:/d/e/f}`);
});
it('window size', () => {
expect(val).to.match(/.*wr=[0-9]*\.[0-9]*/);
});
it('screen size', () => {
expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/);
});
});
it('has property `bidRequest` that is the first passed in bid request', () => {
expect(spec.buildRequests(bidRequests)).to.deep.include({
bidRequest: bidRequests[0]
});
});
});
});
describe('has a method `interpretResponse` that', () => {
it('exists', () => {
expect(spec.interpretResponse).to.be.a('function');
});
describe('returns a list of bid response that', () => {
let bidResponse, slots;
beforeEach(() => {
const request = {
bidRequest: {
bidId: '102938'
}
};
slots = {};
slots['test-div-id'] = {
id: '1234',
vw: ['60', '70']
};
slots['test-div-id-two'] = {
id: '5678',
vw: ['80', '90']
};
const serverResponse = {
body: {
brandSafety: {
adt: 'adtVal',
alc: 'alcVal',
dlm: 'dlmVal',
drg: 'drgVal',
hat: 'hatVal',
off: 'offVal',
vio: 'vioVal'
},
fr: 'false',
slots: slots
},
headers: {}
};
bidResponse = spec.interpretResponse(serverResponse, request);
});
it('has IAS keyword `adt` as property', () => {
expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' });
});
it('has IAS keyword `alc` as property', () => {
expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' });
});
it('has IAS keyword `dlm` as property', () => {
expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' });
});
it('has IAS keyword `drg` as property', () => {
expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' });
});
it('has IAS keyword `hat` as property', () => {
expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' });
});
it('has IAS keyword `off` as property', () => {
expect(bidResponse[0]).to.deep.include({ off: 'offVal' });
});
it('has IAS keyword `vio` as property', () => {
expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' });
});
it('has IAS keyword `fr` as property', () => {
expect(bidResponse[0]).to.deep.include({ fr: 'false' });
});
it('has property `slots`', () => {
expect(bidResponse[0]).to.deep.include({ slots: slots });
});
});
});
});

0 comments on commit ace5903

Please sign in to comment.