Skip to content

Commit

Permalink
Yandex Bid Adapter: initial release (#8183)
Browse files Browse the repository at this point in the history
* add Yandex Bidder Adapter

* add support for adomains

Co-authored-by: Taras Saveliev <t-saveliev@yandex-team.ru>
  • Loading branch information
Saveliev and Taras Saveliev authored Apr 2, 2022
1 parent 2cf1896 commit 02ea759
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 0 deletions.
107 changes: 107 additions & 0 deletions modules/yandexBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {parseUrl, formatQS, deepAccess} from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';

const BIDDER_CODE = 'yandex';
const BIDDER_URL = 'https://bs-metadsp.yandex.ru/metadsp';
const DEFAULT_TTL = 180;
const SSP_ID = 10500;

export const spec = {
code: BIDDER_CODE,
aliases: ['ya'], // short code

isBidRequestValid: function(bid) {
return !!(bid.params && bid.params.pageId && bid.params.impId);
},

buildRequests: function(validBidRequests, bidderRequest) {
const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies');
const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString');

let referrer = '';
if (bidderRequest && bidderRequest.refererInfo) {
const url = parseUrl(bidderRequest.refererInfo.referer);
referrer = url.hostname;
}

return validBidRequests.map((bidRequest) => {
const { params } = bidRequest;
const { pageId, impId, targetRef, withCredentials = true } = params;

const queryParams = {
'imp-id': impId,
'target-ref': targetRef || referrer,
'ssp-id': SSP_ID,
};
if (gdprApplies !== undefined) {
queryParams['gdpr'] = 1;
queryParams['tcf-consent'] = consentString;
}
const imp = {
id: impId,
};

const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner');
if (bannerParams) {
const [ w, h ] = bannerParams.sizes[0];
imp.banner = {
w,
h,
};
}

const queryParamsString = formatQS(queryParams);
return {
method: 'POST',
url: BIDDER_URL + `/${pageId}?${queryParamsString}`,
data: {
id: bidRequest.bidId,
imp: [imp],
site: {
page: referrer,
},
},
options: {
withCredentials,
},
bidRequest,
}
});
},

interpretResponse: function(serverResponse, {bidRequest}) {
let response = serverResponse.body;
if (!response.seatbid) {
return [];
}

const { cur, seatbid } = serverResponse.body;
const rtbBids = seatbid
.map(seatbid => seatbid.bid)
.reduce((a, b) => a.concat(b), []);

return rtbBids.map(rtbBid => {
let prBid = {
requestId: bidRequest.bidId,
cpm: rtbBid.price,
currency: cur || 'USD',
width: rtbBid.w,
height: rtbBid.h,
creativeId: rtbBid.adid,

netRevenue: true,
ttl: DEFAULT_TTL,

meta: {
advertiserDomains: rtbBid.adomain && rtbBid.adomain.length > 0 ? rtbBid.adomain : [],
}
};

prBid.ad = rtbBid.adm;

return prBid;
});
},
}

registerBidder(spec);
40 changes: 40 additions & 0 deletions modules/yandexBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Overview

```
Module Name: Yandex Bidder Adapter
Module Type: Bidder Adapter
Maintainer: prebid@yandex-team.com
```

# Description

Yandex Bidder Adapter for Prebid.js.

# Parameters

| Name | Scope | Description | Example | Type |
|---------------|----------|-------------------------|-----------|-----------|
| `pageId` | required | Page ID | `123` | `Integer` |
| `impId` | required | Block ID | `1` | `Integer` |

# Test Parameters

```
var adUnits = [{
code: 'banner-1',
mediaTypes: {
banner: {
sizes: [[240, 400]],
}
},
bids: [{
{
bidder: 'yandex',
params: {
pageId: 346580,
impId: 143,
},
}
}]
}];
```
166 changes: 166 additions & 0 deletions test/spec/modules/yandexBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {assert, expect} from 'chai';
import {spec} from 'modules/yandexBidAdapter.js';
import {parseUrl} from 'src/utils.js';
import {BANNER} from '../../../src/mediaTypes';

describe('Yandex adapter', function () {
function getBidConfig() {
return {
bidder: 'yandex',
params: {
pageId: 123,
impId: 1,
},
};
}

function getBidRequest() {
return {
...getBidConfig(),
bidId: 'bidid-1',
adUnitCode: 'adUnit-123',
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600]
],
},
},
};
}

describe('isBidRequestValid', function () {
it('should return true when required params found', function () {
const bid = getBidConfig();
assert(spec.isBidRequestValid(bid));
});

it('should return false when required params not found', function () {
expect(spec.isBidRequestValid({})).to.be.false;
});

it('should return false when required params.pageId are not passed', function () {
const bid = getBidConfig();
delete bid.params.pageId;

expect(spec.isBidRequestValid(bid)).to.be.false
});

it('should return false when required params.impId are not passed', function () {
const bid = getBidConfig();
delete bid.params.impId;

expect(spec.isBidRequestValid(bid)).to.be.false
});
});

describe('buildRequests', function () {
const refererUrl = 'https://yandex.ru/secure-ads';

const gdprConsent = {
gdprApplies: 1,
consentString: 'concent-string',
apiVersion: 1,
};

const bidderRequest = {
refererInfo: {
referer: refererUrl
},
gdprConsent
};

it('creates a valid banner request', function () {
const bannerRequest = getBidRequest();
bannerRequest.getFloor = () => ({
currency: 'USD',
// floor: 0.5
});

const requests = spec.buildRequests([bannerRequest], bidderRequest);

expect(requests).to.have.lengthOf(1);
const request = requests[0];

expect(request).to.exist;
const { method, url, data } = request;

expect(method).to.equal('POST');

const parsedRequestUrl = parseUrl(url);
const { search: query } = parsedRequestUrl

expect(parsedRequestUrl.hostname).to.equal('bs-metadsp.yandex.ru');
expect(parsedRequestUrl.pathname).to.equal('/metadsp/123');

expect(query['imp-id']).to.equal('1');
expect(query['target-ref']).to.equal('yandex.ru');
expect(query['ssp-id']).to.equal('10500');

expect(query['gdpr']).to.equal('1');
expect(query['tcf-consent']).to.equal('concent-string');

expect(request.data).to.exist;
expect(data.site).to.not.equal(null);
expect(data.site.page).to.equal('yandex.ru');

// expect(data.device).to.not.equal(null);
// expect(data.device.w).to.equal(window.innerWidth);
// expect(data.device.h).to.equal(window.innerHeight);

expect(data.imp).to.have.lengthOf(1);
expect(data.imp[0].banner).to.not.equal(null);
expect(data.imp[0].banner.w).to.equal(300);
expect(data.imp[0].banner.h).to.equal(250);
});
});

describe('response handler', function () {
const bannerRequest = getBidRequest();

const bannerResponse = {
body: {
seatbid: [{
bid: [
{
impid: '1',
price: 0.3,
crid: 321,
adm: '<!-- HTML/JS -->',
w: 300,
h: 250,
adomain: [
'example.com'
],
adid: 'yabs.123=',
}
]
}],
cur: 'USD',
},
};

it('handles banner responses', function () {
bannerRequest.bidRequest = {
mediaType: BANNER,
bidId: 'bidid-1',
};
const result = spec.interpretResponse(bannerResponse, bannerRequest);

expect(result).to.have.lengthOf(1);
expect(result[0]).to.exist;

const rtbBid = result[0];
expect(rtbBid.width).to.equal(300);
expect(rtbBid.height).to.equal(250);
expect(rtbBid.cpm).to.be.within(0.1, 0.5);
expect(rtbBid.ad).to.equal('<!-- HTML/JS -->');
expect(rtbBid.currency).to.equal('USD');
expect(rtbBid.netRevenue).to.equal(true);
expect(rtbBid.ttl).to.equal(180);

expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']);
});
});
});

0 comments on commit 02ea759

Please sign in to comment.