Skip to content

Commit 3d8cd82

Browse files
andreafassinafasenderos
authored andcommitted
Nativery Bid Adapter: track auction events (prebid#13990)
* feat: track auction events * style: lint files * perf: add keepalive on tracking request * fix: ajax content type * test: ajax content type * perf: remove json content type to avoid preflight request --------- Co-authored-by: Andrea Fassina <[email protected]>
1 parent 74e4bc2 commit 3d8cd82

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

modules/nativeryBidAdapter.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
deepSetValue,
77
logError,
88
logWarn,
9+
safeJSONEncode,
910
} from '../src/utils.js';
11+
import { ajax } from '../src/ajax.js';
1012
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
1113
import { registerBidder } from '../src/adapters/bidderFactory.js';
1214

@@ -18,6 +20,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js';
1820
const BIDDER_CODE = 'nativery';
1921
const BIDDER_ALIAS = ['nat'];
2022
const ENDPOINT = 'https://hb.nativery.com/openrtb2/auction';
23+
const EVENT_TRACKER_URL = 'https://hb.nativery.com/openrtb2/track-event';
24+
// Currently we log every event
25+
const DEFAULT_SAMPLING_RATE = 1;
26+
const EVENT_LOG_RANDOM_NUMBER = Math.random();
2127
const DEFAULT_CURRENCY = 'EUR';
2228
const TTL = 30;
2329
const MAX_IMPS_PER_REQUEST = 10;
@@ -86,7 +92,7 @@ export const spec = {
8692
);
8793
if (Array.isArray(responseErrors) && responseErrors.length > 0) {
8894
logWarn(
89-
'Nativery: Error in bid response ' + JSON.stringify(responseErrors)
95+
'Nativery: Error in bid response ' + safeJSONEncode(responseErrors)
9096
);
9197
}
9298
const ortb = converter.fromORTB({
@@ -96,12 +102,45 @@ export const spec = {
96102
return ortb.bids ?? [];
97103
}
98104
} catch (error) {
99-
const errMsg = error?.message ?? JSON.stringify(error);
105+
const errMsg = error?.message ?? safeJSONEncode(error);
100106
logError('Nativery: unhandled error in bid response ' + errMsg);
101107
return [];
102108
}
103109
return [];
104110
},
111+
/**
112+
* Register bidder specific code, which will execute if a bid from this bidder won the auction
113+
* @param {Bid} bid The bid that won the auction
114+
*/
115+
onBidWon: function(bid) {
116+
if (bid == null || Object.keys(bid).length === 0) return
117+
reportEvent('NAT_BID_WON', bid)
118+
},
119+
/**
120+
* Register bidder specific code, which will execute if the ad
121+
* has been rendered successfully
122+
* @param {Bid} bid Bid request object
123+
*/
124+
onAdRenderSucceeded: function (bid) {
125+
if (bid == null || Object.keys(bid).length === 0) return
126+
reportEvent('NAT_AD_RENDERED', bid)
127+
},
128+
/**
129+
* Register bidder specific code, which will execute if bidder timed out after an auction
130+
* @param {Object} timeoutData Containing timeout specific data
131+
*/
132+
onTimeout: function (timeoutData) {
133+
if (!Array.isArray(timeoutData) || timeoutData.length === 0) return
134+
reportEvent('NAT_TIMEOUT', timeoutData)
135+
},
136+
/**
137+
* Register bidder specific code, which will execute if the bidder responded with an error
138+
* @param {Object} errorData An object with the XMLHttpRequest error and the bid request object
139+
*/
140+
onBidderError: function (errorData) {
141+
if (errorData == null || Object.keys(errorData).length === 0) return
142+
reportEvent('NAT_BIDDER_ERROR', errorData)
143+
}
105144
};
106145

107146
function formatRequest(ortbPayload) {
@@ -132,4 +171,19 @@ function formatRequest(ortbPayload) {
132171
return request;
133172
}
134173

174+
function reportEvent(event, data, sampling = null) {
175+
// Currently this condition is always true since DEFAULT_SAMPLING_RATE = 1,
176+
// meaning we log every event. In the future, we may want to implement event
177+
// sampling by lowering the sampling rate.
178+
const samplingRate = sampling ?? DEFAULT_SAMPLING_RATE;
179+
if (samplingRate > EVENT_LOG_RANDOM_NUMBER) {
180+
const payload = {
181+
prebidVersion: '$prebid.version$',
182+
event,
183+
data,
184+
};
185+
ajax(EVENT_TRACKER_URL, undefined, safeJSONEncode(payload), { method: 'POST', withCredentials: true, keepalive: true });
186+
}
187+
}
188+
135189
registerBidder(spec);

test/spec/modules/nativeryBidAdapter_spec.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import { spec, converter } from 'modules/nativeryBidAdapter';
33
import { newBidder } from 'src/adapters/bidderFactory.js';
44
import * as utils from 'src/utils.js';
5+
import * as ajax from 'src/ajax.js';
56

67
const ENDPOINT = 'https://hb.nativery.com/openrtb2/auction';
78
const MAX_IMPS_PER_REQUEST = 10;
@@ -192,4 +193,102 @@ describe('NativeryAdapter', function () {
192193
logErrorSpy.restore();
193194
});
194195
});
196+
197+
describe('onBidWon callback', () => {
198+
it('should exists and be a function', () => {
199+
expect(spec.onBidWon).to.exist.and.to.be.a('function');
200+
});
201+
it('should NOT call ajax when invalid or empty data is provided', () => {
202+
const ajaxStub = sandBox.stub(ajax, 'ajax');
203+
spec.onBidWon(null);
204+
spec.onBidWon({});
205+
spec.onBidWon(undefined);
206+
expect(ajaxStub.called).to.be.false;
207+
});
208+
it('should call ajax with correct payload when valid data is provided', () => {
209+
const ajaxStub = sandBox.stub(ajax, 'ajax');
210+
const validData = { bidder: 'nativery', adUnitCode: 'div-1' };
211+
spec.onBidWon(validData);
212+
assertTrackEvent(ajaxStub, 'NAT_BID_WON', validData)
213+
});
214+
});
215+
216+
describe('onAdRenderSucceeded callback', () => {
217+
it('should exists and be a function', () => {
218+
expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function');
219+
});
220+
it('should NOT call ajax when invalid or empty data is provided', () => {
221+
const ajaxStub = sandBox.stub(ajax, 'ajax');
222+
spec.onAdRenderSucceeded(null);
223+
spec.onAdRenderSucceeded({});
224+
spec.onAdRenderSucceeded(undefined);
225+
expect(ajaxStub.called).to.be.false;
226+
});
227+
it('should call ajax with correct payload when valid data is provided', () => {
228+
const ajaxStub = sandBox.stub(ajax, 'ajax');
229+
const validData = { bidder: 'nativery', adUnitCode: 'div-1' };
230+
spec.onAdRenderSucceeded(validData);
231+
assertTrackEvent(ajaxStub, 'NAT_AD_RENDERED', validData)
232+
});
233+
});
234+
235+
describe('onTimeout callback', () => {
236+
it('should exists and be a function', () => {
237+
expect(spec.onTimeout).to.exist.and.to.be.a('function');
238+
});
239+
it('should NOT call ajax when invalid or empty data is provided', () => {
240+
const ajaxStub = sandBox.stub(ajax, 'ajax');
241+
spec.onTimeout(null);
242+
spec.onTimeout({});
243+
spec.onTimeout([]);
244+
spec.onTimeout(undefined);
245+
expect(ajaxStub.called).to.be.false;
246+
});
247+
it('should call ajax with correct payload when valid data is provided', () => {
248+
const ajaxStub = sandBox.stub(ajax, 'ajax');
249+
const validData = [{ bidder: 'nativery', adUnitCode: 'div-1' }];
250+
spec.onTimeout(validData);
251+
assertTrackEvent(ajaxStub, 'NAT_TIMEOUT', validData)
252+
});
253+
});
254+
255+
describe('onBidderError callback', () => {
256+
it('should exists and be a function', () => {
257+
expect(spec.onBidderError).to.exist.and.to.be.a('function');
258+
});
259+
it('should NOT call ajax when invalid or empty data is provided', () => {
260+
const ajaxStub = sandBox.stub(ajax, 'ajax');
261+
spec.onBidderError(null);
262+
spec.onBidderError({});
263+
spec.onBidderError(undefined);
264+
expect(ajaxStub.called).to.be.false;
265+
});
266+
it('should call ajax with correct payload when valid data is provided', () => {
267+
const ajaxStub = sandBox.stub(ajax, 'ajax');
268+
const validData = {
269+
error: 'error',
270+
bidderRequest: {
271+
bidder: 'nativery',
272+
}
273+
};
274+
spec.onBidderError(validData);
275+
assertTrackEvent(ajaxStub, 'NAT_BIDDER_ERROR', validData)
276+
});
277+
});
195278
});
279+
280+
const assertTrackEvent = (ajaxStub, event, data) => {
281+
expect(ajaxStub.calledOnce).to.be.true;
282+
283+
const [url, callback, body, options] = ajaxStub.firstCall.args;
284+
285+
expect(url).to.equal('https://hb.nativery.com/openrtb2/track-event');
286+
expect(callback).to.be.undefined;
287+
expect(body).to.be.a('string');
288+
expect(options).to.deep.equal({ method: 'POST', withCredentials: true, keepalive: true });
289+
290+
const payload = JSON.parse(body);
291+
expect(payload.event).to.equal(event);
292+
expect(payload.prebidVersion).to.exist.and.to.be.a('string')
293+
expect(payload.data).to.deep.equal(data);
294+
}

0 commit comments

Comments
 (0)