Skip to content

Commit

Permalink
Floors new signals (#5295)
Browse files Browse the repository at this point in the history
* Price Floors skipRate debug by query string

Rubicon Analytics log floors skipRate

* new floor signal fetchFailed

* change fetchFailed to fetchStatus

* rubi analytics looks for fetchStatus
  • Loading branch information
robertrmartinez authored May 27, 2020
1 parent 874c482 commit 883662a
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 17 deletions.
34 changes: 22 additions & 12 deletions modules/priceFloors.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,22 @@ export function getFloorDataFromAdUnits(adUnits) {
/**
* @summary This function takes the adUnits for the auction and update them accordingly as well as returns the rules hashmap for the auction
*/
export function updateAdUnitsForAuction(adUnits, floorData, skipped, auctionId) {
export function updateAdUnitsForAuction(adUnits, floorData, auctionId) {
adUnits.forEach((adUnit) => {
adUnit.bids.forEach(bid => {
if (skipped) {
if (floorData.skipped) {
delete bid.getFloor;
} else {
bid.getFloor = getFloor;
}
// information for bid and analytics adapters
bid.auctionId = auctionId;
bid.floorData = {
skipped,
modelVersion: utils.deepAccess(floorData, 'data.modelVersion') || '',
location: floorData.data.location,
skipped: floorData.skipped,
modelVersion: utils.deepAccess(floorData, 'data.modelVersion'),
location: utils.deepAccess(floorData, 'data.location'),
skipRate: floorData.skipRate,
fetchStatus: _floorsConfig.fetchStatus
}
});
});
Expand All @@ -306,14 +308,17 @@ export function createFloorsDataForAuction(adUnits, auctionId) {
} else {
resolvedFloorsData.data = getFloorsDataForAuction(resolvedFloorsData.data);
}
// if we still do not have a valid floor data then floors is not on for this auction
// if we still do not have a valid floor data then floors is not on for this auction, so skip
if (Object.keys(utils.deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) {
return;
resolvedFloorsData.skipped = true;
} else {
// determine the skip rate now
const auctionSkipRate = utils.getParameterByName('pbjs_skipRate') || resolvedFloorsData.skipRate;
const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate);
resolvedFloorsData.skipped = isSkipped;
}
// determine the skip rate now
const isSkipped = Math.random() * 100 < parseFloat(utils.deepAccess(resolvedFloorsData, 'data.skipRate') || 0);
resolvedFloorsData.skipped = isSkipped;
updateAdUnitsForAuction(adUnits, resolvedFloorsData, isSkipped, auctionId);
// add floorData to bids
updateAdUnitsForAuction(adUnits, resolvedFloorsData, auctionId);
return resolvedFloorsData;
}

Expand Down Expand Up @@ -389,6 +394,7 @@ export function isFloorsDataValid(floorsData) {
*/
export function parseFloorData(floorsData, location) {
if (floorsData && typeof floorsData === 'object' && isFloorsDataValid(floorsData)) {
utils.logInfo(`${MODULE_NAME}: A ${location} set the auction floor data set to `, floorsData);
return {
...floorsData,
location
Expand Down Expand Up @@ -416,6 +422,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) {
if (_floorsConfig.auctionDelay > 0 && fetching) {
hookConfig.timer = setTimeout(() => {
utils.logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`);
_floorsConfig.fetchStatus = 'timeout';
continueAuction(hookConfig);
}, _floorsConfig.auctionDelay);
_delayedAuctions.push(hookConfig);
Expand Down Expand Up @@ -443,6 +450,7 @@ function resumeDelayedAuctions() {
*/
export function handleFetchResponse(fetchResponse) {
fetching = false;
_floorsConfig.fetchStatus = 'success';
let floorResponse;
try {
floorResponse = JSON.parse(fetchResponse);
Expand All @@ -458,7 +466,8 @@ export function handleFetchResponse(fetchResponse) {

function handleFetchError(status) {
fetching = false;
utils.logError(`${MODULE_NAME}: Fetch errored with: ${status}`);
_floorsConfig.fetchStatus = 'error';
utils.logError(`${MODULE_NAME}: Fetch errored with: `, status);

// if any auctions are waiting for fetch to finish, we need to continue them!
resumeDelayedAuctions();
Expand Down Expand Up @@ -505,6 +514,7 @@ export function handleSetFloorsConfig(config) {
'enabled', enabled => enabled !== false, // defaults to true
'auctionDelay', auctionDelay => auctionDelay || 0,
'endpoint', endpoint => endpoint || {},
'skipRate', () => !isNaN(utils.deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0,
'enforcement', enforcement => utils.pick(enforcement || {}, [
'enforceJS', enforceJS => enforceJS !== false, // defaults to true
'enforcePBS', enforcePBS => enforcePBS === true, // defaults to false
Expand Down
6 changes: 4 additions & 2 deletions modules/rubiconAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,12 @@ function sendMessage(auctionId, bidWonId) {
if (auctionCache.floorData) {
auction.floors = utils.pick(auctionCache.floorData, [
'location',
'modelName', () => auctionCache.floorData.modelVersion || '',
'modelName', () => auctionCache.floorData.modelVersion,
'skipped',
'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'),
'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals')
'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'),
'skipRate', skipRate => !isNaN(skipRate) ? skipRate : 0,
'fetchStatus'
]);
}

Expand Down
89 changes: 88 additions & 1 deletion test/spec/modules/priceFloors_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,13 @@ describe('the price floors module', function () {
data: undefined
});
runStandardAuction();
validateBidRequests(false, undefined);
validateBidRequests(false, {
skipped: true,
modelVersion: undefined,
location: undefined,
skipRate: 0,
fetchStatus: undefined
});
});
it('should use adUnit level data if not setConfig or fetch has occured', function () {
handleSetFloorsConfig({
Expand Down Expand Up @@ -344,6 +350,8 @@ describe('the price floors module', function () {
skipped: false,
modelVersion: 'adUnit Model Version',
location: 'adUnit',
skipRate: 0,
fetchStatus: undefined
});
});
it('bidRequests should have getFloor function and flooring meta data when setConfig occurs', function () {
Expand All @@ -353,6 +361,53 @@ describe('the price floors module', function () {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: undefined
});
});
it('should take the right skipRate depending on input', function () {
// first priority is data object
sandbox.stub(Math, 'random').callsFake(() => 0.99);
let inputFloors = {
...basicFloorConfig,
skipRate: 10,
data: {
...basicFloorData,
skipRate: 50
}
};
handleSetFloorsConfig(inputFloors);
runStandardAuction();
validateBidRequests(true, {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 50,
fetchStatus: undefined
});

// if that does not exist uses topLevel skipRate setting
delete inputFloors.data.skipRate;
handleSetFloorsConfig(inputFloors);
runStandardAuction();
validateBidRequests(true, {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 10,
fetchStatus: undefined
});

// if that is not there defaults to zero
delete inputFloors.skipRate;
handleSetFloorsConfig(inputFloors);
runStandardAuction();
validateBidRequests(true, {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: undefined
});
});
it('should not overwrite previous data object if the new one is bad', function () {
Expand All @@ -378,6 +433,8 @@ describe('the price floors module', function () {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: undefined
});
});
it('should dynamically add new schema fileds and functions if added via setConfig', function () {
Expand Down Expand Up @@ -453,6 +510,8 @@ describe('the price floors module', function () {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: 'timeout'
});
fakeFloorProvider.respond();
});
Expand Down Expand Up @@ -482,10 +541,33 @@ describe('the price floors module', function () {
expect(exposedAdUnits).to.not.be.undefined;

// the exposedAdUnits should be from the fetch not setConfig level data
// and fetchStatus is success since fetch worked
validateBidRequests(true, {
skipped: false,
modelVersion: 'fetch model name',
location: 'fetch',
skipRate: 0,
fetchStatus: 'success'
});
});
it('Should not break if floor provider returns 404', function () {
// run setConfig indicating fetch
handleSetFloorsConfig({...basicFloorConfig, auctionDelay: 250, endpoint: {url: 'http://www.fakeFloorProvider.json'}});

// run the auction and make server respond with 404
fakeFloorProvider.respond();
runStandardAuction();

// error should have been called for fetch error
expect(logErrorSpy.calledOnce).to.equal(true);
// should have caught the response error and still used setConfig data
// and fetch failed is true
validateBidRequests(true, {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: 'error'
});
});
it('Should not break if floor provider returns non json', function () {
Expand All @@ -498,11 +580,16 @@ describe('the price floors module', function () {
fakeFloorProvider.respond();
runStandardAuction();

// error should have been called for response floor data not being valid
expect(logErrorSpy.calledOnce).to.equal(true);
// should have caught the response error and still used setConfig data
// and fetchStatus is 'success' but location is setConfig since it had bad data
validateBidRequests(true, {
skipped: false,
modelVersion: 'basic model',
location: 'setConfig',
skipRate: 0,
fetchStatus: 'success'
});
});
it('should handle not using fetch correctly', function () {
Expand Down
8 changes: 6 additions & 2 deletions test/spec/modules/rubiconAnalyticsAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,9 @@ describe('rubicon analytics adapter', function () {
auctionInit.bidderRequests[0].bids[0].floorData = {
skipped: false,
modelVersion: 'someModelName',
location: 'setConfig'
location: 'setConfig',
skipRate: 15,
fetchStatus: 'error'
};
let flooredResponse = {
...BID,
Expand Down Expand Up @@ -733,7 +735,9 @@ describe('rubicon analytics adapter', function () {
modelName: 'someModelName',
skipped: false,
enforcement: true,
dealsEnforced: false
dealsEnforced: false,
skipRate: 15,
fetchStatus: 'error'
});
// first adUnit's adSlot
expect(message.auctions[0].adUnits[0].adSlot).to.equal('12345/sports');
Expand Down

0 comments on commit 883662a

Please sign in to comment.