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

Viewability #10

Merged
merged 7 commits into from
Sep 11, 2018
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
135 changes: 121 additions & 14 deletions modules/33acrossBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { uniques } from 'src/utils';
import * as utils from 'src/utils';

const { registerBidder } = require('../src/adapters/bidderFactory');
const { config } = require('../src/config');

const BIDDER_CODE = '33across';
const END_POINT = 'https://ssc.33across.com/api/v1/hb';
const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html';
Expand Down Expand Up @@ -28,21 +30,28 @@ function _createBidResponse(response) {
function _createServerRequest(bidRequest, gdprConsent) {
const ttxRequest = {};
const params = bidRequest.params;
const element = document.getElementById(bidRequest.adUnitCode);
const sizes = _transformSizes(bidRequest.sizes);
const minSize = _getMinSize(sizes);

const contributeViewability = ViewabilityContributor(
_getPercentInView(element, window.top, minSize)
);

/*
* Infer data for the request payload
*/
ttxRequest.imp = [];
ttxRequest.imp[0] = {
banner: {
format: bidRequest.sizes.map(_getFormatSize)
format: sizes.map(size => Object.assign(size, {ext: {}}))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you not use _transformSizes method here to construct the format sizes?

Copy link
Author

Choose a reason for hiding this comment

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

sizes constant already has transformed sizes. So I just add ext: {} to each of them.

},
ext: {
ttx: {
prod: params.productId
}
}
}
};
ttxRequest.site = { id: params.siteId };

// Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and
Expand All @@ -54,19 +63,18 @@ function _createServerRequest(bidRequest, gdprConsent) {
ext: {
consent: gdprConsent.consentString
}
}
};
ttxRequest.regs = {
ext: {
gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0
}
}
};

// Finally, set the openRTB 'test' param if this is to be a test bid
if (params.test === 1) {
ttxRequest.test = 1;
}


/*
* Now construct the full server request
*/
Expand All @@ -82,7 +90,7 @@ function _createServerRequest(bidRequest, gdprConsent) {
return {
'method': 'POST',
'url': url,
'data': JSON.stringify(ttxRequest),
'data': JSON.stringify(contributeViewability(ttxRequest)),
'options': options
}
}
Expand All @@ -98,12 +106,111 @@ function _createSync(siteId) {
}
}

function _getFormatSize(sizeArr) {
function _getSize(size) {
return {
w: sizeArr[0],
h: sizeArr[1],
ext: {}
w: parseInt(size[0], 10),
h: parseInt(size[1], 10)
}
}

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

function _getBoundingBox(element, { w, h } = {}) {
let { width, height, left, top, right, bottom } = element.getBoundingClientRect();

if ((width === 0 || height === 0) && w && h) {
width = w;
height = h;
right = left + w;
bottom = top + h;
}

return { width, height, left, top, right, bottom };
}

Copy link
Collaborator

@curlyblueeagle curlyblueeagle Sep 11, 2018

Choose a reason for hiding this comment

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

Can you elaborate what _transformSizes does? Assuming that the sizes from the bidRequest object will be something like following:

[[320, 50], [320,100]...]
[[320, 50]]

Copy link
Collaborator

@curlyblueeagle curlyblueeagle Sep 11, 2018

Choose a reason for hiding this comment

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

Also how do you think we should treat an undefined size? Don't recall how Prebid core treats sizes not being set in the ad unit parameters. If it simply throws errors, should we consider including a check for size in isBidRequestValid ?

Copy link
Author

@glebglushtsov glebglushtsov Sep 11, 2018

Choose a reason for hiding this comment

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

_transformSizes() proceeds both size formats GPT supports (array and array of array):

  1. [w, h]
  2. [[w, h], [w, h]]

https://support.google.com/admanager/answer/1638622

function _transformSizes(sizes) {
if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) {
return [_getSize(sizes)];
}

return sizes.map(_getSize);
}

function _getIntersectionOfRects(rects) {
const bbox = {
left: rects[0].left,
right: rects[0].right,
top: rects[0].top,
bottom: rects[0].bottom
};

for (let i = 1; i < rects.length; ++i) {
bbox.left = Math.max(bbox.left, rects[i].left);
bbox.right = Math.min(bbox.right, rects[i].right);

if (bbox.left >= bbox.right) {
return null;
}

bbox.top = Math.max(bbox.top, rects[i].top);
bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);

if (bbox.top >= bbox.bottom) {
return null;
}
}

bbox.width = bbox.right - bbox.left;
bbox.height = bbox.bottom - bbox.top;

return bbox;
}

function _getPercentInView(element, topWin, { w, h } = {}) {
const elementBoundingBox = _getBoundingBox(element, { w, h });

// Obtain the intersection of the element and the viewport
const elementInViewBoundingBox = _getIntersectionOfRects([ {
left: 0,
top: 0,
right: topWin.innerWidth,
bottom: topWin.innerHeight
}, elementBoundingBox ]);

let elementInViewArea, elementTotalArea;

if (elementInViewBoundingBox !== null) {
// Some or all of the element is in view
elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;

return ((elementInViewArea / elementTotalArea) * 100);
}

// No overlap between element and the viewport; therefore, the element
// lies completely out of view
return 0;
}

/**
* Viewability contribution to request..
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting usage of the Contributor Pattern.
Curious to know why 1) you'd want to employ closure to construct the contribution function 2) you'd want to clone ttxRequest and it's descendents. Why not just have a function that returns a new banner.ext object that contains the viewability value?

Copy link
Author

Choose a reason for hiding this comment

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

As I see this is the way contributor pattern is implemented in 33xchange which I was using as a reference.

  1. We may want viewability contributed differently depending on the RevCTRL product (e.g. 'siab' versus 'inview' versus 'infeed').
  2. To secure original request immutability.

*/
function ViewabilityContributor(viewabilityAmount) {
function contributeViewability(ttxRequest) {
const req = Object.assign({}, ttxRequest);
const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem));
const banner = imp[0].banner = Object.assign({}, imp[0].banner);
const ext = banner.ext = Object.assign({}, banner.ext);
const ttx = ext.ttx = Object.assign({}, ext.ttx);

ttx.viewability = { amount: Math.round(viewabilityAmount) };

return req;
}

return contributeViewability;
}

function isBidRequestValid(bid) {
Expand All @@ -123,9 +230,9 @@ function isBidRequestValid(bid) {
// - the server, at this point, also doesn't need the consent string to handle gdpr compliance. So passing
// value whether set or not, for the sake of future dev.
function buildRequests(bidRequests, bidderRequest) {
const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent)
const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent);

adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques);
adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques);

return bidRequests.map((req) => {
return _createServerRequest(req, gdprConsent);
Expand Down Expand Up @@ -160,7 +267,7 @@ const spec = {
buildRequests,
interpretResponse,
getUserSyncs
}
};

registerBidder(spec);

Expand Down
Loading