-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
7e1abbe
commit ace5903
Showing
3 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} | ||
} | ||
] | ||
} | ||
]; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
}); | ||
}); | ||
}); | ||
}); |