Skip to content

Commit

Permalink
Analytics adapters: attach arbitrary labels to analytics events (#12597)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgirardi authored Dec 27, 2024
1 parent a88e801 commit 376a491
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 12 deletions.
18 changes: 16 additions & 2 deletions libraries/analyticsAdapter/AnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ import { EVENTS } from '../../src/constants.js';
import {ajax} from '../../src/ajax.js';
import {logError, logMessage} from '../../src/utils.js';
import * as events from '../../src/events.js';
import {config} from '../../src/config.js';

export const _internal = {
ajax
};
const ENDPOINT = 'endpoint';
const BUNDLE = 'bundle';
const LABELS_KEY = 'analyticsLabels';

let labels = {};

config.getConfig(LABELS_KEY, (cfg) => {
labels = cfg[LABELS_KEY]
});

export const DEFAULT_INCLUDE_EVENTS = Object.values(EVENTS)
.filter(ev => ev !== EVENTS.AUCTION_DEBUG);
Expand Down Expand Up @@ -90,12 +98,18 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler }
}

function _callEndpoint({ eventType, args, callback }) {
_internal.ajax(url, callback, JSON.stringify({ eventType, args }));
_internal.ajax(url, callback, JSON.stringify({ eventType, args, labels }));
}

function _enqueue({eventType, args}) {
queue.push(() => {
this.track({eventType, args});
if (Object.keys(labels || []).length > 0) {
args = {
[LABELS_KEY]: labels,
...args,
}
}
this.track({eventType, labels, args});
});
emptyQueue();
}
Expand Down
89 changes: 80 additions & 9 deletions test/spec/AnalyticsAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
DEFAULT_INCLUDE_EVENTS,
setDebounceDelay
} from '../../libraries/analyticsAdapter/AnalyticsAdapter.js';
import {config} from 'src/config.js';

const BID_WON = EVENTS.BID_WON;
const NO_BID = EVENTS.NO_BID;

const AnalyticsAdapter = require('libraries/analyticsAdapter/AnalyticsAdapter.js').default;
const config = {
const adapterConfig = {
url: 'https://localhost:9999/endpoint',
analyticsType: 'endpoint'
};
Expand All @@ -29,7 +30,7 @@ FEATURE: Analytics Adapters API
after(disableAjaxForAnalytics);

beforeEach(function () {
adapter = new AnalyticsAdapter(config);
adapter = new AnalyticsAdapter(adapterConfig);
});

afterEach(function () {
Expand All @@ -52,7 +53,7 @@ FEATURE: Analytics Adapters API
adapter.track({eventType, args});

let result = JSON.parse(server.requests[0].requestBody);
expect(result).to.deep.equal({args: {some: 'data'}, eventType});
sinon.assert.match(result, {args: {some: 'data'}, eventType})
});

it(`SHOULD queue the event first and then track it WHEN an event occurs before tracking library is available`, function () {
Expand All @@ -65,9 +66,79 @@ FEATURE: Analytics Adapters API
// As now AUCTION_DEBUG is triggered for WARNINGS too, the BID_RESPONSE goes last in the array
const index = server.requests.length - 1;
let result = JSON.parse(server.requests[index].requestBody);
expect(result).to.deep.equal({eventType, args: {wat: 'wot'}});
sinon.assert.match(result, {eventType, args: {wat: 'wot'}})
});

describe('analyticsLabels', () => {
let analyticsLabels;
beforeEach(() => {
analyticsLabels = {
experiment_1: 'group_a',
experiment_2: 'group_b'
}
config.setConfig({
analyticsLabels
})
})

it('should be attached to payloads (type: endpoint)', () => {
events.emit(BID_WON, {foo: 'bar'});
adapter.enableAnalytics();
server.requests
.map(req => JSON.parse(req.requestBody))
.forEach(payload => sinon.assert.match(payload, {labels: analyticsLabels, args: sinon.match({analyticsLabels})}))
});

it('should be attached payloads (type: bundle)', () => {
adapter = new AnalyticsAdapter({
analyticsType: 'bundle',
global: 'analytics'
})
window.analytics = sinon.stub();
try {
events.emit(BID_WON, {foo: 'bar'})
adapter.enableAnalytics();
sinon.assert.calledWith(window.analytics, sinon.match.any, BID_WON, sinon.match({analyticsLabels}))
} finally {
delete window.analytics;
}
});

it('should be passed to custom track', () => {
Object.assign(adapter, {
track: sinon.stub()
});
events.emit(BID_WON, {foo: 'bar'});
adapter.enableAnalytics();
sinon.assert.calledWith(adapter.track, sinon.match({
eventType: BID_WON,
args: sinon.match({analyticsLabels}),
labels: analyticsLabels
}))
})

it('should not override the "analyticsLabels" property an event payload may have', () => {
adapter.track = sinon.stub();
events.emit(BID_WON, {analyticsLabels: 'not these ones'});
adapter.enableAnalytics();
sinon.assert.calledWith(adapter.track, sinon.match({
args: {analyticsLabels: 'not these ones'}
}));
});

it('should not modify event payloads when there are no labels', () => {
config.resetConfig();
adapter.track = sinon.stub();
events.emit(BID_WON, {'foo': 'bar'});
adapter.enableAnalytics();
sinon.assert.calledWith(adapter.track, {
labels: {},
args: {foo: 'bar'},
eventType: BID_WON
})
})
})

describe('event filters', () => {
function fireEvents() {
events.emit(BID_WON, {});
Expand Down Expand Up @@ -112,7 +183,7 @@ FEATURE: Analytics Adapters API
events.emit(BID_WON, {})
}
})(adapter.track);
adapter.enableAnalytics(config);
adapter.enableAnalytics(adapterConfig);
events.emit(BID_WON, {});
expect(i >= 100).to.eql(false);
})
Expand Down Expand Up @@ -176,7 +247,7 @@ FEATURE: Analytics Adapters API

expect(server.requests.length).to.equal(1);
let result = JSON.parse(server.requests[0].requestBody);
expect(result).to.deep.equal({args: {more: 'info'}, eventType: 'bidWon'});
sinon.assert.match(result, {args: {more: 'info'}, eventType: 'bidWon'})
});

it(`THEN should disable analytics when random number is outside sample range`, function () {
Expand Down Expand Up @@ -205,7 +276,7 @@ describe('Analytics asynchronous event tracking', () => {

beforeEach(() => {
clock = sinon.useFakeTimers();
adapter = new AnalyticsAdapter(config);
adapter = new AnalyticsAdapter(adapterConfig);
adapter.track = sinon.stub();
adapter.enableAnalytics({});
});
Expand All @@ -224,7 +295,7 @@ describe('Analytics asynchronous event tracking', () => {
sinon.assert.notCalled(adapter.track);
clock.tick(100);
sinon.assert.calledTwice(adapter.track);
sinon.assert.calledWith(adapter.track.firstCall, {eventType: BID_WON, args: {i: 0}});
sinon.assert.calledWith(adapter.track.secondCall, {eventType: BID_WON, args: {i: 1}});
sinon.assert.calledWith(adapter.track.firstCall, sinon.match({eventType: BID_WON, args: {i: 0}}));
sinon.assert.calledWith(adapter.track.secondCall, sinon.match({eventType: BID_WON, args: {i: 1}}));
});
})
2 changes: 1 addition & 1 deletion test/spec/modules/genericAnalyticsAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe('Generic analytics', () => {
recv = arg;
});
events.emit(BID_RESPONSE, {i: 1});
expect(recv).to.eql([{eventType: BID_RESPONSE, args: {i: 1}}]);
sinon.assert.match(recv, [sinon.match({eventType: BID_RESPONSE, args: {i: 1}})])
});

it('should not cause infinite recursion, if handler triggers more events', () => {
Expand Down

0 comments on commit 376a491

Please sign in to comment.