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

Update Adform adapter to Prebid v1.0 #1947

Merged
merged 13 commits into from
Jan 10, 2018
2 changes: 1 addition & 1 deletion integrationExamples/gpt/pbjs_example_gpt.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
},
{
bidder: 'adform',
// available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' ]
// available params: [ 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test', priceType ]
params: {
adxDomain: 'adx.adform.net', //optional
mid: 158989,
Expand Down
107 changes: 107 additions & 0 deletions modules/adformBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use strict';

import {registerBidder} from 'src/adapters/bidderFactory';

const BIDDER_CODE = 'adform';
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [],
isBidRequestValid: function (bid) {
return !!(bid.params.mid);
},
buildRequests: function (validBidRequests) {
var i, l, j, k, bid, _key, _value, reqParams;
Copy link
Member

Choose a reason for hiding this comment

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

nit: prefer const | let

var request = [];
var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ];
var netRevenue = 'net';
var bids = JSON.parse(JSON.stringify(validBidRequests));
Copy link
Member

Choose a reason for hiding this comment

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

we have a copy function in utils deepClone that could be used. Although this is probably fine.

for (i = 0, l = bids.length; i < l; i++) {
bid = bids[i];
if (bid.params.priceType === 'gross') {
netRevenue = 'gross';
}
for (j = 0, k = globalParams.length; j < k; j++) {
_key = globalParams[j][0];
_value = bid[_key] || bid.params[_key];
if (_value) {
bid[_key] = bid.params[_key] = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are you mutating the bids? Is there a way you can do this without mutating the original bid objects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, changes were made to avoid mutating original bid objects.

globalParams[j][1] = _value;
}
}
reqParams = bid.params;
reqParams.transactionId = bid.transactionId;
request.push(formRequestUrl(reqParams));
}

request.unshift('//' + globalParams[0][1] + '/adx/?rp=4');

request.push('stid=' + validBidRequests[0].requestId);

for (i = 1, l = globalParams.length; i < l; i++) {
_key = globalParams[i][0];
_value = globalParams[i][1];
if (_value) {
request.push(_key + '=' + encodeURIComponent(_value));
}
}

return {
method: 'GET',
url: request.join('&'),
bids: validBidRequests,
netRevenue: netRevenue,
bidder: 'adform'
};

function formRequestUrl(reqData) {
var key;
var url = [];

for (key in reqData) {
if (reqData.hasOwnProperty(key) && reqData[key]) { url.push(key, '=', reqData[key], '&'); }
}

return encodeURIComponent(btoa(url.join('').slice(0, -1)));
}
},
interpretResponse: function (serverResponse, bidRequest) {
var bidObject, response, bid;
var bidRespones = [];
var bids = bidRequest.bids;
var responses = serverResponse.body;
for (var i = 0; i < responses.length; i++) {
response = responses[i];
bid = bids[i];
if (response.response === 'banner' && verifySize(response, bid.sizes)) {
bidObject = {
requestId: bid.bidId,
cpm: response.win_bid,
width: response.width,
height: response.height,
creativeId: bid.bidId,
dealId: response.deal_id,
currency: response.win_cur,
netRevenue: bidRequest.netRevenue !== 'gross',
ttl: 360,
ad: response.banner,
bidderCode: bidRequest.bidder,
transactionId: bid.transactionId
};
bidRespones.push(bidObject);
}
}

return bidRespones;

function verifySize(adItem, validSizes) {
for (var j = 0, k = validSizes.length; j < k; j++) {
if (adItem.width === validSizes[j][0] &&
adItem.height === validSizes[j][1]) {
return true;
}
}
return false;
}
}
};
registerBidder(spec);
30 changes: 30 additions & 0 deletions modules/adformBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Overview

Module Name: Adform Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]

# Description

Module that connects to Adform demand sources to fetch bids.
Banner formats are supported.

# Test Parameters
```
var adUnits = [
{
code: 'div-gpt-ad-1460505748561-0',
sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], // a display size
bids: [
{
bidder: "adform",
params: {
adxDomain: 'adx.adform.net', //optional
mid: '292063',
priceType: 'gross' // default is 'net'
}
}
]
},
];
```
246 changes: 246 additions & 0 deletions test/spec/modules/adformBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import {assert, expect} from 'chai';
import * as url from 'src/url';
import {spec} from 'modules/adformBidAdapter';

describe('Adform adapter', () => {
let serverResponse, bidRequest, bidResponses;
let bids = [];
describe('isBidRequestValid', () => {
let bid = {
'bidder': 'adform',
'params': {
'mid': '19910113'
}
};

it('should return true when required params found', () => {
assert(spec.isBidRequestValid(bid));
});

it('should return false when required params are missing', () => {
bid.params = {
adxDomain: 'adx.adform.net'
};
assert.isFalse(spec.isBidRequestValid(bid));
})
});

describe('buildRequests', () => {
it('should pass multiple bids via single request', () => {
let request = spec.buildRequests(bids);
let parsedUrl = parseUrl(request.url);
assert.lengthOf(parsedUrl.items, 3);
});

it('should handle global request parameters', () => {
let parsedUrl = parseUrl(spec.buildRequests([bids[0]]).url);
let query = parsedUrl.query;

assert.equal(parsedUrl.path, '//newDomain/adx');
assert.equal(query.tid, 45);
assert.equal(query.rp, 4);
assert.equal(query.fd, 1);
assert.equal(query.stid, '7aefb970-2045');
assert.equal(query.url, encodeURIComponent('some// there'));
});

it('should set correct request method', () => {
let request = spec.buildRequests([bids[0]]);
assert.equal(request.method, 'GET');
});

it('should correctly form bid items', () => {
let bidList = bids;
let request = spec.buildRequests(bidList);
let parsedUrl = parseUrl(request.url);
assert.deepEqual(parsedUrl.items, [
{
mid: '1',
transactionId: '5f33781f-9552-4ca1'
},
{
mid: '2',
someVar: 'someValue',
transactionId: '5f33781f-9552-4iuy',
priceType: 'gross'
},
{
mid: '3',
pdom: 'home',
transactionId: '5f33781f-9552-7ev3'
}
]);
});

it('should not change original validBidRequests object', () => {
var resultBids = JSON.parse(JSON.stringify(bids[0]));
let request = spec.buildRequests([bids[0]]);
assert.deepEqual(resultBids, bids[0]);
});
});

describe('interpretResponse', () => {
it('should respond with empty response when there is empty serverResponse', () => {
let result = spec.interpretResponse({ body: {}}, {});
assert.deepEqual(result, []);
});
it('should respond with empty response when sizes doesn\'t match', () => {
serverResponse.body[0].response = 'banner';
serverResponse.body[0].width = 100;
serverResponse.body[0].height = 150;

serverResponse.body = [serverResponse.body[0]];
bidRequest.bids = [bidRequest.bids[0]];
let result = spec.interpretResponse(serverResponse, bidRequest);

assert.equal(serverResponse.body.length, 1);
assert.equal(serverResponse.body[0].response, 'banner');
assert.deepEqual(result, []);
});
it('should respond with empty response when response from server is not banner', () => {
serverResponse.body[0].response = 'not banner';
serverResponse.body = [serverResponse.body[0]];
bidRequest.bids = [bidRequest.bids[0]];
let result = spec.interpretResponse(serverResponse, bidRequest);

assert.deepEqual(result, []);
});
it('should interpret server response correctly with one bid', () => {
serverResponse.body = [serverResponse.body[0]];
bidRequest.bids = [bidRequest.bids[0]];
let result = spec.interpretResponse(serverResponse, bidRequest)[0];

assert.equal(result.requestId, '2a0cf4e');
assert.equal(result.cpm, 13.9);
assert.equal(result.width, 300);
assert.equal(result.height, 250);
assert.equal(result.creativeId, '2a0cf4e');
assert.equal(result.dealId, '123abc');
assert.equal(result.currency, 'EUR');
assert.equal(result.netRevenue, true);
assert.equal(result.ttl, 360);
assert.equal(result.ad, '<tag1>');
assert.equal(result.bidderCode, 'adform');
assert.equal(result.transactionId, '5f33781f-9552-4ca1');
});

it('should set correct netRevenue', () => {
serverResponse.body = [serverResponse.body[0]];
bidRequest.bids = [bidRequest.bids[1]];
bidRequest.netRevenue = 'gross';
let result = spec.interpretResponse(serverResponse, bidRequest)[0];

assert.equal(result.netRevenue, false);
});

it('should create bid response item for every requested item', () => {
let result = spec.interpretResponse(serverResponse, bidRequest);
assert.lengthOf(result, 3);
});
});

beforeEach(() => {
let sizes = [[250, 300], [300, 250], [300, 600]];
let adUnitCode = ['div-01', 'div-02', 'div-03'];
let placementCode = adUnitCode;
let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', priceType: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }];
bids = [
{
adUnitCode: placementCode[0],
bidId: '2a0cf4e',
bidder: 'adform',
bidderRequestId: '1ab8d9',
params: params[0],
adxDomain: 'newDomain',
tid: 45,
placementCode: placementCode[0],
requestId: '7aefb970-2045',
sizes: [[300, 250], [250, 300], [300, 600], [600, 300]],
transactionId: '5f33781f-9552-4ca1'
},
{
adUnitCode: placementCode[1],
bidId: '2a0cf5b',
bidder: 'adform',
bidderRequestId: '1ab8d9',
params: params[1],
placementCode: placementCode[1],
requestId: '7aefb970-2045',
sizes: [[300, 250], [250, 300], [300, 600], [600, 300]],
transactionId: '5f33781f-9552-4iuy'
},
{
adUnitCode: placementCode[2],
bidId: '2a0cf6n',
bidder: 'adform',
bidderRequestId: '1ab8d9',
params: params[2],
placementCode: placementCode[2],
requestId: '7aefb970-2045',
sizes: [[300, 250], [250, 300], [300, 600], [600, 300]],
transactionId: '5f33781f-9552-7ev3'
}
];
serverResponse = {
body: [
{
banner: '<tag1>',
deal_id: '123abc',
height: 250,
response: 'banner',
width: 300,
win_bid: 13.9,
win_cur: 'EUR'
},
{
banner: '<tag2>',
deal_id: '123abc',
height: 300,
response: 'banner',
width: 250,
win_bid: 13.9,
win_cur: 'EUR'
},
{
banner: '<tag3>',
deal_id: '123abc',
height: 300,
response: 'banner',
width: 600,
win_bid: 10,
win_cur: 'EUR'
}
],
headers: {}
};
bidRequest = {
bidder: 'adform',
bids: bids,
method: 'GET',
url: 'url'
};
});
});

function parseUrl(url) {
const parts = url.split('/');
const query = parts.pop().split('&');
return {
path: parts.join('/'),
items: query
.filter((i) => !~i.indexOf('='))
.map((i) => atob(decodeURIComponent(i))
.split('&')
.reduce(toObject, {})),
query: query
.filter((i) => ~i.indexOf('='))
.map((i) => i.replace('?', ''))
.reduce(toObject, {})
};
}

function toObject(cache, string) {
const keyValue = string.split('=');
cache[keyValue[0]] = keyValue[1];
return cache;
}