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

Gumgum throttle #944

Merged
merged 2 commits into from
Feb 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 36 additions & 9 deletions src/adapters/gumgum.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const GumgumAdapter = function GumgumAdapter() {
let topWindow;
let topScreen;
let pageViewId;
const requestCache = {};
const throttleTable = {};
const defaultThrottle = 3e4;

try {
topWindow = global.top;
Expand All @@ -21,6 +24,10 @@ const GumgumAdapter = function GumgumAdapter() {
return utils.logError(error);
}

function _getTimeStamp() {
return new Date().getTime();
}

function _callBids({ bids }) {
const browserParams = {
vw: topWindow.innerWidth,
Expand All @@ -36,6 +43,7 @@ const GumgumAdapter = function GumgumAdapter() {
, params = {}
, placementCode
} = bidRequest;
const timestamp = _getTimeStamp();
const trackingId = params.inScreen;
const nativeId = params.native;
const slotId = params.inSlot;
Expand All @@ -52,6 +60,21 @@ const GumgumAdapter = function GumgumAdapter() {
', please check your implementation.'
);
}

/* throttle based on the latest request for this product */
const productId = bid.pi;
const requestKey = productId + '|' + placementCode;
const throttle = throttleTable[productId];
const latestRequest = requestCache[requestKey];
if (latestRequest && throttle && (timestamp - latestRequest) < throttle) {
return utils.logWarn(
`[GumGum] The refreshes for "${ placementCode }" with the params ` +
`${ JSON.stringify(params) } should be at least ${ throttle / 1e3 }s apart.`
);
}
/* update the last request */
requestCache[requestKey] = timestamp;

/* tracking id is required for in-image and in-screen */
if (trackingId) bid.t = trackingId;
/* native ads require a native placement id */
Expand All @@ -75,26 +98,30 @@ const GumgumAdapter = function GumgumAdapter() {
});
}

const _handleGumGumResponse = cachedBidRequest => bidResponse => {
const ad = bidResponse && bidResponse.ad;
const pag = bidResponse && bidResponse.pag;
const _handleGumGumResponse = cachedBidRequest => (bidResponse = {}) => {
const { pi: productId
} = cachedBidRequest;
const { ad = {}
, pag = {}
, thms: throttle
} = bidResponse;
/* cache the pageViewId */
if (pag && pag.pvid) pageViewId = pag.pvid;
/* create the bid */
if (ad && ad.id) {
/* set the new throttle */
throttleTable[productId] = throttle || defaultThrottle;
/* create the bid */
const bid = bidfactory.createBid(1);
const { t: trackingId
, pi: productId
, placementCode
} = cachedBidRequest;
bidResponse.placementCode = placementCode;
} = pag;
bidResponse.request = cachedBidRequest;
const encodedResponse = encodeURIComponent(JSON.stringify(bidResponse));
const gumgumAdLoader = `<script>
(function (context, topWindow, d, s, G) {
G = topWindow.GUMGUM;
d = topWindow.document;
function loadAd() {
topWindow.GUMGUM.pbjs("${ trackingId }", ${ productId }, "${ encodedResponse }" , context, topWindow);
topWindow.GUMGUM.pbjs("${ trackingId }", ${ productId }, "${ encodedResponse }" , context);
}
if (G) {
loadAd();
Expand Down
61 changes: 41 additions & 20 deletions test/spec/adapters/gumgum_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {expect} from 'chai';
import Adapter from '../../../src/adapters/gumgum';
import bidManager from '../../../src/bidmanager';
import adLoader from '../../../src/adloader';
import * as utils from '../../../src/utils';
import { STATUS } from '../../../src/constants';

describe('gumgum adapter', () => {
Expand Down Expand Up @@ -73,10 +74,6 @@ describe('gumgum adapter', () => {
},
"pag": pageParams
};
const emptyResponse = {
"ad": {},
"pag": pageParams
}

function mockBidResponse(response) {
sandbox.stub(bidManager, 'addBidResponse');
Expand All @@ -102,24 +99,24 @@ describe('gumgum adapter', () => {
adapter.callBids(bidderRequest);
});

it('should call the endpoint once per valid bid', () => {
it('calls the endpoint once per valid bid', () => {
sinon.assert.callCount(adLoader.loadScript, 4);
});

it('should include required browser data', () => {
it('includes required browser data', () => {
const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]);
endpointRequest.to.include('vw');
endpointRequest.to.include('vh');
endpointRequest.to.include('sw');
endpointRequest.to.include('sh');
});

it('should include the global bid timeout', () => {
it('includes the global bid timeout', () => {
const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]);
endpointRequest.to.include(`tmax=${$$PREBID_GLOBAL$$.cbTimeout}`);
});

it('should include the publisher identity', () => {
it('includes the publisher identity', () => {
const endpointRequest = expect(adLoader.loadScript.firstCall.args[0]);
endpointRequest.to.include('t=' + TEST.PUBLISHER_IDENTITY);
});
Expand All @@ -143,7 +140,7 @@ describe('gumgum adapter', () => {
});

describe('handleGumGumCB[...]', () => {
it('should exist and be a function', () => {
it('exists and is function', () => {
expect(pbjs.handleGumGumCB['InScreenBidId']).to.exist.and.to.be.a('function');
});
});
Expand All @@ -156,29 +153,29 @@ describe('gumgum adapter', () => {
successfulBid = mockBidResponse(bidderResponse);
});

it('should add one bid', () => {
it('adds one bid', () => {
sinon.assert.calledOnce(bidManager.addBidResponse);
});

it('should pass the correct placement code as the first param', () => {
it('passes the correct placement code as the first param', () => {
const [ placementCode ] = bidManager.addBidResponse.firstCall.args;
expect(placementCode).to.eql(TEST.PLACEMENT);
});

it('should have a GOOD status code', () => {
it('has a GOOD status code', () => {
const STATUS_CODE = successfulBid.getStatusCode();
expect(STATUS_CODE).to.eql(STATUS.GOOD);
});

it('should use the CPM returned by the server', () => {
it('uses the CPM returned by the server', () => {
expect(successfulBid).to.have.property('cpm', TEST.CPM);
});

it('should have an ad', () => {
it('has an ad', () => {
expect(successfulBid).to.have.property('ad');
});

it('should have the size specified by the server', () => {
it('has the size specified by the server', () => {
expect(successfulBid).to.have.property('width', 728);
expect(successfulBid).to.have.property('height', 90);
});
Expand All @@ -190,26 +187,50 @@ describe('gumgum adapter', () => {
let noBid;

beforeEach(() => {
noBid = mockBidResponse(emptyResponse);
noBid = mockBidResponse(undefined);
});

it('should add one bid', () => {
it('adds one bid', () => {
sinon.assert.calledOnce(bidManager.addBidResponse);
});

it('should have a NO_BID status code', () => {
it('has a NO_BID status code', () => {
expect(noBid.getStatusCode()).to.eql(STATUS.NO_BID);
});

it('should pass the correct placement code as the first parameter', () => {
it('passes the correct placement code as the first parameter', () => {
const [ placementCode ] = bidManager.addBidResponse.firstCall.args;
expect(placementCode).to.eql(TEST.PLACEMENT);
});

it('should add the bidder code to the bid object', () => {
it('adds the bidder code to the bid object', () => {
expect(noBid).to.have.property('bidderCode', TEST.BIDDER_CODE);
});

});

describe('refresh throttle', () => {

beforeEach(() => {
mockBidResponse(bidderResponse);
});

afterEach(() => {
if (utils.logWarn.restore) {
utils.logWarn.restore();
}
});

it('warns about the throttle limit', function() {
sinon.spy(utils, 'logWarn');
// call all the binds again
adapter.callBids(bidderRequest);
// the timeout for in-screen should stop one bid request
const warning = expect(utils.logWarn.args[0][0]);
warning.to.include(TEST.PLACEMENT);
warning.to.include('inScreen');
});

});

});