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

Yieldmo bid adapter #1415

Merged
merged 29 commits into from
Sep 8, 2017
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
11b6a54
Create yieldmoBidAdapter file
Jul 17, 2017
4e782b0
Copy over TL adapter and change name spacing
Jul 17, 2017
5e1a577
Update bid adapter naming to Yieldmo
Jul 19, 2017
28d584d
Init tests for yieldmoBidAdapter
Jul 19, 2017
5a31918
Add yeildmo config to pbjs_example_gpt
Jul 19, 2017
4068db6
feature/yieldmoBidAdapter: added request build
Jul 19, 2017
420910f
Update ym response to have placement and impression level info
Jul 24, 2017
5428371
Remove environment param
Jul 24, 2017
0419c0f
Update ym tests for single bid request structure
Jul 24, 2017
22768d2
Update url to use _ instead of camelcase
Jul 24, 2017
50e9087
Update YMCB to handle multiple placements in one response
Jul 24, 2017
2838948
Clean up and remove TODO from adapter
Jul 24, 2017
8c1b0d6
Update response tests to work with single response structure
Jul 24, 2017
53063a7
Remove trailing space
Jul 24, 2017
308cbe1
Cleanup
xlozinguez Jul 25, 2017
8cfc236
Merge pull request #1 from yieldmo/feature/yieldmoBidAdapter
xlozinguez Jul 25, 2017
2bf6d29
Adjusted code according to CR comments
xlozinguez Jul 26, 2017
525f1a9
Update bid adapter with fallback for incorrect bid response
Aug 9, 2017
517536b
Update yeildmo prebid endpoint
Aug 23, 2017
39bfc99
Add check to make sure response is an Array
Aug 23, 2017
2776712
Merge pull request #2 from yieldmo/feature/handle_bid_response_errors
cdoher01 Aug 23, 2017
59ce30b
Remove environment tracking as we are no going to use this
Aug 23, 2017
37124fc
Merge branch 'master' of https://github.com/yieldmo/Prebid.js
Aug 23, 2017
fce2a4c
Update test to pass with new Yieldmo endpoint
Aug 23, 2017
5c7d3a7
Update return obj and remove node debug comments
Aug 31, 2017
98b1589
Remove changes to hello_world and yarn.lock files
Aug 31, 2017
a5f8ed1
Merge pull request #3 from yieldmo/feature/update_return_object
cdoher01 Aug 31, 2017
06bda89
Remove unneeded comments
Sep 5, 2017
b5b9d1f
Merge pull request #4 from yieldmo/feature/cleanup
cdoher01 Sep 5, 2017
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
4 changes: 4 additions & 0 deletions integrationExamples/gpt/pbjs_example_gpt.html
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@
floor: 1.50 // Dynamic bid floor - optional
}
},
{
bidder: 'yieldmo',
params: {}
},
{
bidder: 'adequant',
params: {
Expand Down
142 changes: 142 additions & 0 deletions modules/yieldmoBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
var utils = require('src/utils.js');
var adloader = require('src/adloader.js');
var bidmanager = require('src/bidmanager.js');
var bidfactory = require('src/bidfactory.js');
var adaptermanager = require('src/adaptermanager');

/**
* Adapter for requesting bids from Yieldmo.
*
* @returns {{callBids: _callBids}}
* @constructor
*/

var YieldmoAdapter = function YieldmoAdapter() {
function _callBids(params) {
var bids = params.bids;
adloader.loadScript(buildYieldmoCall(bids));
}

function buildYieldmoCall(bids) {
// build our base tag, based on if we are http or https
var ymURI = '//ads.yieldmo.com/ads?';
var ymCall = document.location.protocol + ymURI;

// Placement specific information
ymCall = _appendPlacementInformation(ymCall, bids);

// General impression params
ymCall = _appendImpressionInformation(ymCall);

// remove the trailing "&"
if (ymCall.lastIndexOf('&') === ymCall.length - 1) {
ymCall = ymCall.substring(0, ymCall.length - 1);
}

// @if NODE_ENV='debug'
Copy link
Contributor

Choose a reason for hiding this comment

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

Can also be removed

// utils.logMessage('ymCall request built: ' + ymCall);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be commented out?

// @endif

return ymCall;
}

function _appendPlacementInformation(url, bids) {
var placements = [];
var placement;
var bid;

for (var i = 0; i < bids.length; i++) {
bid = bids[i];

placement = {};
placement.callback_id = bid.bidId;
placement.placement_id = bid.placementCode;
placement.sizes = bid.sizes;

placements.push(placement);
}

url = utils.tryAppendQueryString(url, 'p', JSON.stringify(placements));
return url;
}

function _appendImpressionInformation(url) {
var page_url = document.location; // page url
var pr = document.referrer || ''; // page's referrer
var dnt = (navigator.doNotTrack || false).toString(); // true if user enabled dnt (false by default)
var _s = document.location.protocol === 'https:' ? 1 : 0; // 1 if page is secure
var description = _getPageDescription();
var title = document.title || ''; // Value of the title from the publisher's page.
var e = 4; // 0 (COP) or 4 (DFP) for now -- ad server should reject other environments (TODO: validate that it will always be the case)
var bust = new Date().getTime().toString(); // cache buster
var scrd = window.devicePixelRatio || 0; // screen pixel density
var ae = 0; // prebid adapter version

Choose a reason for hiding this comment

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

I don't think we need to send this (per Rahul Jain)

Choose a reason for hiding this comment

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

also misleading comment 😸


url = utils.tryAppendQueryString(url, 'callback', '$$PREBID_GLOBAL$$.YMCB');
url = utils.tryAppendQueryString(url, 'page_url', page_url);
url = utils.tryAppendQueryString(url, 'pr', pr);
url = utils.tryAppendQueryString(url, 'bust', bust);
url = utils.tryAppendQueryString(url, '_s', _s);
url = utils.tryAppendQueryString(url, 'scrd', scrd);
url = utils.tryAppendQueryString(url, 'dnt', dnt);
url = utils.tryAppendQueryString(url, 'ae', ae);
url = utils.tryAppendQueryString(url, 'description', description);
url = utils.tryAppendQueryString(url, 'title', title);

return url;
}

function _getPageDescription() {
if (document.querySelector('meta[name="description"]')) {
return document.querySelector('meta[name="description"]').getAttribute('content'); // Value of the description metadata from the publisher's page.
} else {
return '';
}
}

// expose the callback to the global object:
$$PREBID_GLOBAL$$.YMCB = function(ymResponses) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine for right now... but beware that Prebid will be dropping support for JSONP in version 1.0, which should be coming in the next few months.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I'll make a note for us to update this. Do you know if any existing adapters are a good example to look at for 1.0 compatible callback?

Copy link
Contributor

Choose a reason for hiding this comment

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

We won't be letting servers respond with code in 1.0--just data. So you'll be making a GET/POST request, and then unpacking bids from the JSON (or XML, plain text, etc) in the response body.

The exact API for adapters in 1.0 is still under discussion in #1494, if you want to take a look and make suggestions over there. I'm updating the appnexusAst adapter to help prototype it.

if (ymResponses) {
for (var i = 0; i < ymResponses.length; i++) {
_registerPlacementBid(ymResponses[i]);
}
} else {
// no response data
// @if NODE_ENV='debug'
Copy link
Contributor

Choose a reason for hiding this comment

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

Can also be removed

utils.logMessage('No prebid response for placement %%PLACEMENT%%');
// @endif
}
};

function _registerPlacementBid(response) {
var bidObj = utils.getBidRequest(response.callback_id);
var placementCode = bidObj && bidObj.placementCode;
var bid = [];

if (response && response.cpm && response.cpm !== 0) {
bid = bidfactory.createBid(1, bidObj);
bid.bidderCode = 'yieldmo';
bid.cpm = response.cpm;
bid.ad = response.ad;
bid.width = response.width;
bid.height = response.height;
bidmanager.addBidResponse(placementCode, bid);
} else {
// no response data
// @if NODE_ENV='debug'
Copy link
Contributor

Choose a reason for hiding this comment

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

Prebid isn't using preprocess... so it won't respect these annotations.

The utils.logMessage function is a no-op unless debugging is enabled, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

if (bidObj) { utils.logMessage('No prebid response from yieldmo for placementCode: ' + bidObj.placementCode); }
// @endif
bid = bidfactory.createBid(2, bidObj);
bid.bidderCode = 'yieldmo';
bidmanager.addBidResponse(placementCode, bid);
}
}

return {
callBids: _callBids
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs to be:

return Object.assign(this, {
  callBids: _callBids
});

See #1459

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

};

adaptermanager.registerBidAdapter(new YieldmoAdapter(), 'yieldmo');

module.exports = YieldmoAdapter;
209 changes: 209 additions & 0 deletions test/spec/modules/yieldmoBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import {expect} from 'chai';
import Adapter from '../../../modules/yieldmoBidAdapter';
import bidManager from '../../../src/bidmanager';
import adLoader from '../../../src/adloader';
import {parse as parseURL} from '../../../src/url';

describe('Yieldmo adapter', () => {
let bidsRequestedOriginal;
let adapter;
let sandbox;

const bidderRequest = {
bidderCode: 'yieldmo',
bids: [
{
bidId: 'bidId1',
bidder: 'yieldmo',
placementCode: 'foo',
sizes: [[728, 90]]
},
{
bidId: 'bidId2',
bidder: 'yieldmo',
placementCode: 'bar',
sizes: [[300, 600], [300, 250]]
}
]
};

beforeEach(() => {
bidsRequestedOriginal = $$PREBID_GLOBAL$$._bidsRequested;
$$PREBID_GLOBAL$$._bidsRequested = [];

adapter = new Adapter();
sandbox = sinon.sandbox.create();
});

afterEach(() => {
sandbox.restore();

$$PREBID_GLOBAL$$._bidsRequested = bidsRequestedOriginal;
});

describe('callBids', () => {
let bidRequestURL;

beforeEach(() => {
sandbox.stub(adLoader, 'loadScript');
adapter.callBids(bidderRequest);

bidRequestURL = adLoader.loadScript.firstCall.args[0];
});

it('should load a script with passed bid params', () => {
let route = 'http://ads.yieldmo.com/ads?';
let requestParams = parseURL(bidRequestURL).search;
let parsedPlacementParams = JSON.parse(decodeURIComponent(requestParams.p));

sinon.assert.calledOnce(adLoader.loadScript);
expect(bidRequestURL).to.contain(route);

// placement 1
expect(parsedPlacementParams[0]).to.have.property('callback_id', 'bidId1');
expect(parsedPlacementParams[0]).to.have.property('placement_id', 'foo');
expect(parsedPlacementParams[0].sizes[0][0]).to.equal(728);
expect(parsedPlacementParams[0].sizes[0][1]).to.equal(90);

// placement 2
expect(parsedPlacementParams[1]).to.have.property('callback_id', 'bidId2');
expect(parsedPlacementParams[1]).to.have.property('placement_id', 'bar');
expect(parsedPlacementParams[1].sizes[0][0]).to.equal(300);
expect(parsedPlacementParams[1].sizes[0][1]).to.equal(600);
expect(parsedPlacementParams[1].sizes[1][0]).to.equal(300);
expect(parsedPlacementParams[1].sizes[1][1]).to.equal(250);

// impression information
expect(requestParams).to.have.property('callback', '$$PREBID_GLOBAL$$.YMCB');
expect(requestParams).to.have.property('page_url');
});
});

describe('YMCB', () => {
it('should exist and be a function', () => {
expect($$PREBID_GLOBAL$$.YMCB).to.exist.and.to.be.a('function');
});
});

describe('add bids to the manager', () => {
let firstBid;
let secondBid;

beforeEach(() => {
sandbox.stub(bidManager, 'addBidResponse');

$$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest);

// respond
let bidderReponse = [{
'cpm': 3.45455,
'width': 300,
'height': 250,
'callback_id': 'bidId1',
'ad': '<html><head></head><body>HELLO YIELDMO AD</body></html>'
}, {
'cpm': 4.35455,
'width': 400,
'height': 350,
'callback_id': 'bidId2',
'ad': '<html><head></head><body>HELLO YIELDMO AD</body></html>'
}];

$$PREBID_GLOBAL$$.YMCB(bidderReponse);

firstBid = bidManager.addBidResponse.firstCall.args[1];
secondBid = bidManager.addBidResponse.secondCall.args[1];
});

it('should add a bid object for each bid', () => {
sinon.assert.calledTwice(bidManager.addBidResponse);
});

it('should pass the correct placement code as first param', () => {
let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0];
let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0];

expect(firstPlacementCode).to.eql('foo');
expect(secondPlacementCode).to.eql('bar');
});

it('should have a good statusCode', () => {
expect(firstBid.getStatusCode()).to.eql(1);
expect(secondBid.getStatusCode()).to.eql(1);
});

it('should add the CPM to the bid object', () => {
expect(firstBid).to.have.property('cpm', 3.45455);
expect(secondBid).to.have.property('cpm', 4.35455);
});

it('should add the bidder code to the bid object', () => {
expect(firstBid).to.have.property('bidderCode', 'yieldmo');
expect(secondBid).to.have.property('bidderCode', 'yieldmo');
});

it('should include the ad on the bid object', () => {
expect(firstBid).to.have.property('ad');
expect(secondBid).to.have.property('ad');
});

it('should include the size on the bid object', () => {
expect(firstBid).to.have.property('width', 300);
expect(firstBid).to.have.property('height', 250);
expect(secondBid).to.have.property('width', 400);
expect(secondBid).to.have.property('height', 350);
});
});

describe('add empty bids if no bid returned', () => {
let firstBid;
let secondBid;

beforeEach(() => {
sandbox.stub(bidManager, 'addBidResponse');

$$PREBID_GLOBAL$$._bidsRequested.push(bidderRequest);

// respond
let bidderReponse = [{
'status': 'no_bid',
'callback_id': 'bidId1'
}, {
'status': 'no_bid',
'callback_id': 'bidId2'
}];

$$PREBID_GLOBAL$$.YMCB(bidderReponse);

firstBid = bidManager.addBidResponse.firstCall.args[1];
secondBid = bidManager.addBidResponse.secondCall.args[1];
});

it('should add a bid object for each bid', () => {
sinon.assert.calledTwice(bidManager.addBidResponse);
});

it('should include the bid request bidId as the adId', () => {
expect(firstBid).to.have.property('adId', 'bidId1');
expect(secondBid).to.have.property('adId', 'bidId2');
});

it('should have an error statusCode', () => {
expect(firstBid.getStatusCode()).to.eql(2);
expect(secondBid.getStatusCode()).to.eql(2);
});

it('should pass the correct placement code as first param', () => {
let firstPlacementCode = bidManager.addBidResponse.firstCall.args[0];
let secondPlacementCode = bidManager.addBidResponse.secondCall.args[0];

expect(firstPlacementCode).to.eql('foo');
expect(secondPlacementCode).to.eql('bar');
});

it('should add the bidder code to the bid object', () => {
expect(firstBid).to.have.property('bidderCode', 'yieldmo');
expect(secondBid).to.have.property('bidderCode', 'yieldmo');
});
});
});