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

Media.net Adapter Improvements #2634

Merged
merged 4 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
76 changes: 76 additions & 0 deletions modules/medianetBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { config } from 'src/config';

const BIDDER_CODE = 'medianet';
const BID_URL = '//prebid.media.net/rtb/prebid';
const SLOT_VISIBILITY = {
NOT_DETERMINED: 0,
ABOVE_THE_FOLD: 1,
BELOW_THE_FOLD: 2
};

$$PREBID_GLOBAL$$.medianetGlobals = {};

Expand Down Expand Up @@ -71,6 +76,31 @@ function getSize(size) {
}
}

function getWindowSize() {
return {
w: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || -1,
h: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || -1
}
}

function getCoordinates(id) {
const element = document.getElementById(id);
if (element && element.getBoundingClientRect) {
const rect = element.getBoundingClientRect();
let coordinates = {};
coordinates.top_left = {
y: rect.top,
x: rect.left,
};
coordinates.bottom_right = {
y: rect.bottom,
x: rect.right,
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

To get the distance from the top left corner of the document (as opposed to viewport) this code should be

    coordinates.top_left = {
      y: rect.top + window.pageYOffset,
      x: rect.left + window.pageXOffset,
    };
    coordinates.bottom_right = {
      y: rect.bottom + window.pageYOffset,
      x: rect.right + window.pageXOffset,
    };

return coordinates
}
return null;
}

function extParams(params, gdpr) {
let ext = {
customer_id: params.cid,
Expand All @@ -80,6 +110,10 @@ function extParams(params, gdpr) {
if (ext.gdpr_applies) {
ext.gdpr_consent_string = gdpr.consentString || '';
}
let windowSize = getWindowSize();
if (windowSize.w !== -1 && windowSize.h !== -1) {
ext.screen = windowSize;
}
return ext;
}

Expand All @@ -102,9 +136,51 @@ function slotParams(bidRequest) {
if (bidFloor) {
params.bidfloor = bidFloor;
}
const coordinates = getCoordinates(bidRequest.adUnitCode);
if (coordinates) {
params.ext.coordinates = coordinates;
let viewability = getSlotVisibility(coordinates.top_left, getMinSize(params.banner));
params.ext.viewability = viewability;
if (viewability > 0.5) {
params.ext.visibility = SLOT_VISIBILITY.ABOVE_THE_FOLD;
} else {
params.ext.visibility = SLOT_VISIBILITY.BELOW_THE_FOLD;
}
} else {
params.ext.visibility = SLOT_VISIBILITY.NOT_DETERMINED;
}

return params;
}

function getMinSize(sizes) {
return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min);
}

function getSlotVisibility(topLeft, size) {
let maxArea = size.w * size.h;
let windowSize = getWindowSize();
let bottomRight = {
x: topLeft.x + size.w,
y: topLeft.y + size.h
};
if (maxArea === 0 || windowSize.w === -1 || windowSize.h === -1) {
return 0;
}

return getOverlapArea(topLeft, bottomRight, {x: 0, y: 0}, {x: windowSize.w, y: windowSize.h}) / maxArea;
}

// find the overlapping area between two rectangles
function getOverlapArea(topLeft1, bottomRight1, topLeft2, bottomRight2) {
// If no overlap, return 0
if ((topLeft1.x > bottomRight2.x || bottomRight1.x < topLeft2.x) || (topLeft1.y > bottomRight2.y || bottomRight1.y < topLeft2.y)) {
return 0;
}
// return overlapping area : [ min of rightmost/bottommost co-ordinates ] - [ max of leftmost/topmost co-ordinates ]
return ((Math.min(bottomRight1.x, bottomRight2.x) - Math.max(topLeft1.x, topLeft2.x)) * (Math.min(bottomRight1.y, bottomRight2.y) - Math.max(topLeft1.y, topLeft2.y)));
}

function generatePayload(bidRequests, bidderRequests) {
return {
site: siteDetails(bidRequests[0].params.site),
Expand Down
206 changes: 198 additions & 8 deletions test/spec/modules/medianetBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,28 @@ let VALID_BID_REQUEST = [{
'customer_id': 'customer_id',
'prebid_version': $$PREBID_GLOBAL$$.version,
'gdpr_applies': false,
'screen': {
'w': 1000,
'h': 1000
}
},
'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
'imp': [{
'id': '28f8f8130a583e',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-0'
'dfp_id': 'div-gpt-ad-1460505748561-0',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand All @@ -105,7 +121,19 @@ let VALID_BID_REQUEST = [{
}, {
'id': '3f97ca71b1e5c2',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-123'
'dfp_id': 'div-gpt-ad-1460505748561-123',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand All @@ -131,13 +159,29 @@ let VALID_BID_REQUEST = [{
'ext': {
'customer_id': 'customer_id',
'prebid_version': $$PREBID_GLOBAL$$.version,
'gdpr_applies': false
'gdpr_applies': false,
'screen': {
'w': 1000,
'h': 1000
}
},
'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
'imp': [{
'id': '28f8f8130a583e',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-0'
'dfp_id': 'div-gpt-ad-1460505748561-0',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand All @@ -154,7 +198,19 @@ let VALID_BID_REQUEST = [{
}, {
'id': '3f97ca71b1e5c2',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-123'
'dfp_id': 'div-gpt-ad-1460505748561-123',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand Down Expand Up @@ -339,7 +395,7 @@ let VALID_BID_REQUEST = [{
'consentString': 'consentString',
'gdprApplies': true,
},
'timeout': 3000,
'timeout': 3000
},
VALID_PAYLOAD_FOR_GDPR = {
'site': {
Expand All @@ -352,12 +408,28 @@ let VALID_BID_REQUEST = [{
'prebid_version': $$PREBID_GLOBAL$$.version,
'gdpr_consent_string': 'consentString',
'gdpr_applies': true,
'screen': {
'w': 1000,
'h': 1000
}
},
'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d',
'imp': [{
'id': '28f8f8130a583e',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-0'
'dfp_id': 'div-gpt-ad-1460505748561-0',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand All @@ -374,7 +446,19 @@ let VALID_BID_REQUEST = [{
}, {
'id': '3f97ca71b1e5c2',
'ext': {
'dfp_id': 'div-gpt-ad-1460505748561-123'
'dfp_id': 'div-gpt-ad-1460505748561-123',
'visibility': 1,
'viewability': 1,
'coordinates': {
'top_left': {
x: 50,
y: 50
},
'bottom_right': {
x: 100,
y: 100
}
}
},
'banner': [{
'w': 300,
Expand Down Expand Up @@ -416,6 +500,31 @@ describe('Media.net bid adapter', () => {
});

describe('buildRequests', () => {
let sandbox;
let mock;
before(() => {
sandbox = sinon.sandbox.create();
mock = sinon.mock(window);
window.innerWidth = 1000;
window.innerHeight = 1000;
Copy link
Collaborator

Choose a reason for hiding this comment

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

A mock does not modify the given object.

Does not change the object, but returns a mock object to set expectations on the object’s methods.

This means that setting these read-only values on window will most likely throw errors (in Safari I believe) and also not be reset after your test suite runs. To solve this you can either use a helper function to get the screen dimensions and stub that to get the results you want or you can make your tests more tolerant of various screen sizes (as the browserstack tests will run across multiple devices).

let documentStub = sandbox.stub(document, 'getElementById');
let boundingRect = {
top: 50,
left: 50,
bottom: 100,
right: 100
};
documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({
getBoundingClientRect: () => boundingRect
});
documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({
getBoundingClientRect: () => boundingRect
});
});
after(() => {
sandbox.restore();
mock.restore();
});
it('should build valid payload on bid', () => {
let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD);
Expand Down Expand Up @@ -456,6 +565,87 @@ describe('Media.net bid adapter', () => {
});
});

describe('slot visibility', () => {
let mock;
before(() => {
mock = sinon.mock(window);
window.innerWidth = 1000;
window.innerHeight = 1000;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs to be updated as well not to modify read-only window properties.

});
after(() => {
mock.restore();
});
it('slot visibility should be 2 and ratio 0 when ad unit is BTF', () => {
let sandbox = sinon.sandbox.create();
let documentStub = sandbox.stub(document, 'getElementById');
let boundingRect = {
top: 1010,
left: 1010,
bottom: 1050,
right: 1050
};
documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({
getBoundingClientRect: () => boundingRect
});
documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({
getBoundingClientRect: () => boundingRect
});
let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
let data = JSON.parse(bidReq.data);
expect(data.imp[0].ext.visibility).to.equal(2);
expect(data.imp[0].ext.viewability).to.equal(0);
sandbox.restore();
});
it('slot visibility should be 2 and ratio < 0.5 when ad unit is partially inside viewport', () => {
let sandbox = sinon.sandbox.create();
let documentStub = sandbox.stub(document, 'getElementById');
let boundingRect = {
top: 990,
left: 990,
bottom: 1050,
right: 1050
};
documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({
getBoundingClientRect: () => boundingRect
});
documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({
getBoundingClientRect: () => boundingRect
});
let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
let data = JSON.parse(bidReq.data);
expect(data.imp[0].ext.visibility).to.equal(2);
expect(data.imp[0].ext.viewability).to.equal(100 / 75000);
sandbox.restore();
});
it('slot visibility should be 1 and ratio > 0.5 when ad unit mostly in viewport', () => {
let sandbox = sinon.sandbox.create();
let documentStub = sandbox.stub(document, 'getElementById');
let boundingRect = {
top: 800,
left: 800,
bottom: 1050,
right: 1050
};
documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({
getBoundingClientRect: () => boundingRect
});
documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({
getBoundingClientRect: () => boundingRect
});
let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
let data = JSON.parse(bidReq.data);
expect(data.imp[0].ext.visibility).to.equal(1);
expect(data.imp[0].ext.viewability).to.equal(40000 / 75000);
sandbox.restore();
});
it('co-ordinates should not be sent and slot visibility should be 0 when ad unit is not present', () => {
let bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA);
let data = JSON.parse(bidReq.data);
expect(data.imp[1].ext).to.not.have.ownPropertyDescriptor('viewability');
expect(data.imp[1].ext.visibility).to.equal(0);
});
});

describe('getUserSyncs', () => {
it('should exclude iframe syncs if iframe is disabled', () => {
let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_PIXEL_ENABLED, SERVER_CSYNC_RESPONSE);
Expand Down