-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Yandex Analytics Adapter: initial release (#10876)
* Yandex Analytics Adapter: Initial release * Release preparations * Updated trackable events * Updated trackable events * tag URL * Added tests and chanded init logic * Fixed already loaded script scenario * One level of object destruction * Global domain, yandex.com * Removed script insertion logic * Update yandexAnalyticsAdapter.md --------- Co-authored-by: Stanislavsky34200 <stanislavsky34@gmail.com>
- Loading branch information
1 parent
c161e0c
commit eee58b0
Showing
3 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; | ||
import adapterManager from '../src/adapterManager.js'; | ||
import { logError, logInfo } from '../src/utils.js'; | ||
import CONSTANTS from '../src/constants.json'; | ||
import * as events from '../src/events.js'; | ||
|
||
const timeoutIds = {}; | ||
const tryUntil = (operationId, conditionCb, cb) => { | ||
if (!conditionCb()) { | ||
cb(); | ||
timeoutIds[operationId] = setTimeout( | ||
() => tryUntil(conditionCb, conditionCb, cb), | ||
100 | ||
); | ||
} | ||
}; | ||
|
||
const clearTryUntilTimeouts = (timeouts) => { | ||
timeouts.forEach((timeoutID) => { | ||
if (timeoutIds[timeoutID]) { | ||
clearTimeout(timeoutIds[timeoutID]); | ||
} | ||
}); | ||
}; | ||
|
||
const SEND_EVENTS_BUNDLE_TIMEOUT = 1500; | ||
const { | ||
BID_REQUESTED, | ||
BID_RESPONSE, | ||
BID_ADJUSTMENT, | ||
BID_WON, | ||
BIDDER_DONE, | ||
AUCTION_END, | ||
BID_TIMEOUT, | ||
} = CONSTANTS.EVENTS; | ||
|
||
export const EVENTS_TO_TRACK = [ | ||
BID_REQUESTED, | ||
BID_RESPONSE, | ||
BID_ADJUSTMENT, | ||
BID_WON, | ||
BIDDER_DONE, | ||
AUCTION_END, | ||
BID_TIMEOUT, | ||
]; | ||
|
||
const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' }), { | ||
bufferedEvents: [], | ||
initTimeoutId: 0, | ||
counters: {}, | ||
counterInitTimeouts: {}, | ||
oneCounterInited: false, | ||
|
||
onEvent: (eventName, eventData) => { | ||
const innerEvent = { | ||
event: eventName, | ||
data: eventData, | ||
}; | ||
yandexAnalytics.bufferedEvents.push(innerEvent); | ||
}, | ||
|
||
sendEvents: () => { | ||
if (yandexAnalytics.bufferedEvents.length) { | ||
const data = yandexAnalytics.bufferedEvents.splice( | ||
0, | ||
yandexAnalytics.bufferedEvents.length | ||
); | ||
|
||
Object.keys(yandexAnalytics.counters).forEach((counterId) => { | ||
yandexAnalytics.counters[counterId].pbjs(data); | ||
}); | ||
} | ||
setTimeout(yandexAnalytics.sendEvents, SEND_EVENTS_BUNDLE_TIMEOUT); | ||
}, | ||
|
||
onCounterInit: (counterId) => { | ||
yandexAnalytics.counters[counterId] = window[`yaCounter${counterId}`]; | ||
logInfo(`Found metrika counter ${counterId}`); | ||
if (!yandexAnalytics.oneCounterInited) { | ||
yandexAnalytics.oneCounterInited = true; | ||
setTimeout(() => { | ||
yandexAnalytics.sendEvents(); | ||
}, SEND_EVENTS_BUNDLE_TIMEOUT); | ||
clearTimeout(yandexAnalytics.initTimeoutId); | ||
} | ||
}, | ||
|
||
enableAnalytics: (config) => { | ||
yandexAnalytics.options = (config && config.options) || {}; | ||
const { counters } = yandexAnalytics.options || {}; | ||
const validCounters = counters.filter((counterId) => { | ||
if (!counterId) { | ||
return false; | ||
} | ||
|
||
if (isNaN(counterId)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}); | ||
|
||
if (!validCounters.length) { | ||
logError('options.counters contains no valid counter ids'); | ||
return; | ||
} | ||
|
||
const unsubscribeCallbacks = [ | ||
() => clearTryUntilTimeouts(['countersInit']), | ||
]; | ||
|
||
yandexAnalytics.initTimeoutId = setTimeout(() => { | ||
yandexAnalytics.bufferedEvents = []; | ||
unsubscribeCallbacks.forEach((cb) => cb()); | ||
logError(`Can't find metrika counter after 25 seconds.`); | ||
logError('Aborting yandex analytics provider initialization.'); | ||
}, 25000); | ||
|
||
events.getEvents().forEach((event) => { | ||
if (event && EVENTS_TO_TRACK.indexOf(event.eventType) >= 0) { | ||
yandexAnalytics.onEvent(event.eventType, event); | ||
} | ||
}); | ||
|
||
EVENTS_TO_TRACK.forEach((eventName) => { | ||
const eventCallback = yandexAnalytics.onEvent.bind(null, eventName); | ||
unsubscribeCallbacks.push(() => events.off(eventName, eventCallback)); | ||
events.on(eventName, eventCallback); | ||
}); | ||
|
||
let allCountersInited = false; | ||
tryUntil('countersInit', () => allCountersInited, () => { | ||
allCountersInited = validCounters.reduce((result, counterId) => { | ||
if (yandexAnalytics.counters[counterId]) { | ||
return result && true; | ||
} | ||
|
||
if (window[`yaCounter${counterId}`]) { | ||
yandexAnalytics.onCounterInit(counterId); | ||
return result && true; | ||
} | ||
|
||
return false; | ||
}, true); | ||
}); | ||
}, | ||
}); | ||
|
||
adapterManager.registerAnalyticsAdapter({ | ||
adapter: yandexAnalytics, | ||
code: 'yandexAnalytics' | ||
}); | ||
|
||
export default yandexAnalytics; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Overview | ||
|
||
``` | ||
Module Name: Yandex Analytics Adapter | ||
Module Type: Analytics Adapter | ||
Maintainer: prebid@yandex-team.com | ||
``` | ||
|
||
# Description | ||
|
||
This adapter is designed to work with [Yandex Metrica](https://metrica.yandex.com/about) - Top-5 worldwide web analytics tool. | ||
|
||
Disclosure: provider use Metrica Tag build based on https://github.com/yandex/metrica-tag, ~60 kB gzipped. | ||
|
||
## How to setup provider | ||
|
||
Register your application on https://metrica.yandex.com/ and get counter id. | ||
Insert counter initialization code obtained from the page https://metrica.yandex.com/settings?id={counterId} into your html code. | ||
Init provider like this, where `123` is your counter id. | ||
|
||
Note: If you have Single Page Application (SPA), [configure your tag](https://yandex.com/support/metrica/code/counter-spa-setup.html). | ||
|
||
```javascript | ||
pbjs.enableAnalytics({ | ||
provider: 'yandexAnalytics', | ||
options: { | ||
counters: [ | ||
123, | ||
], | ||
}, | ||
}); | ||
``` | ||
|
||
## Where to find data | ||
|
||
Go to https://metrika.yandex.com/dashboard -> Prebid Analytics |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import * as sinon from 'sinon'; | ||
import yandexAnalytics, { EVENTS_TO_TRACK } from 'modules/yandexAnalyticsAdapter.js'; | ||
import * as log from '../../../src/utils.js' | ||
import * as events from '../../../src/events.js'; | ||
|
||
describe('Yandex analytics adapter testing', () => { | ||
const sandbox = sinon.createSandbox(); | ||
let clock; | ||
let logError; | ||
let getEvents; | ||
let onEvent; | ||
const counterId = 123; | ||
const counterWindowKey = 'yaCounter123'; | ||
|
||
beforeEach(() => { | ||
yandexAnalytics.counters = {}; | ||
yandexAnalytics.counterInitTimeouts = {}; | ||
yandexAnalytics.bufferedEvents = []; | ||
yandexAnalytics.oneCounterInited = false; | ||
clock = sinon.useFakeTimers(); | ||
logError = sandbox.stub(log, 'logError'); | ||
sandbox.stub(log, 'logInfo'); | ||
getEvents = sandbox.stub(events, 'getEvents').returns([]); | ||
onEvent = sandbox.stub(events, 'on'); | ||
sandbox.stub(window.document, 'createElement').callsFake((tag) => { | ||
const element = { | ||
tag, | ||
events: {}, | ||
attributes: {}, | ||
addEventListener: (event, cb) => { | ||
element.events[event] = cb; | ||
}, | ||
removeEventListener: (event, cb) => { | ||
chai.expect(element.events[event]).to.equal(cb); | ||
}, | ||
setAttribute: (attr, val) => { | ||
element.attributes[attr] = val; | ||
}, | ||
}; | ||
return element; | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
window.Ya = null; | ||
window[counterWindowKey] = null; | ||
sandbox.restore(); | ||
clock.restore(); | ||
}); | ||
|
||
it('fails if timeout for counter insertion is exceeded', () => { | ||
yandexAnalytics.enableAnalytics({ | ||
options: { | ||
counters: [ | ||
123, | ||
], | ||
}, | ||
}); | ||
clock.tick(25001); | ||
chai.expect(yandexAnalytics.bufferedEvents).to.deep.equal([]); | ||
sinon.assert.calledWith(logError, `Can't find metrika counter after 25 seconds.`); | ||
sinon.assert.calledWith(logError, `Aborting yandex analytics provider initialization.`); | ||
}); | ||
|
||
it('fails if no valid counters provided', () => { | ||
yandexAnalytics.enableAnalytics({ | ||
options: { | ||
counters: [ | ||
'abc', | ||
], | ||
}, | ||
}); | ||
sinon.assert.calledWith(logError, 'options.counters contains no valid counter ids'); | ||
}); | ||
|
||
it('subscribes to events if counter is already present', () => { | ||
window[counterWindowKey] = { | ||
pbjs: sandbox.stub(), | ||
}; | ||
|
||
getEvents.returns([ | ||
{ | ||
eventType: EVENTS_TO_TRACK[0], | ||
}, | ||
{ | ||
eventType: 'Some_untracked_event', | ||
} | ||
]); | ||
const eventsToSend = [{ | ||
event: EVENTS_TO_TRACK[0], | ||
data: { | ||
eventType: EVENTS_TO_TRACK[0], | ||
} | ||
}]; | ||
|
||
yandexAnalytics.enableAnalytics({ | ||
options: { | ||
counters: [ | ||
counterId, | ||
], | ||
}, | ||
}); | ||
|
||
EVENTS_TO_TRACK.forEach((eventName, i) => { | ||
const [event, callback] = onEvent.getCall(i).args; | ||
chai.expect(event).to.equal(eventName); | ||
callback(i); | ||
eventsToSend.push({ | ||
event: eventName, | ||
data: i, | ||
}); | ||
}); | ||
|
||
clock.tick(1501); | ||
|
||
const [ sentEvents ] = window[counterWindowKey].pbjs.getCall(0).args; | ||
chai.expect(sentEvents).to.deep.equal(eventsToSend); | ||
}); | ||
|
||
it('waits for counter initialization', () => { | ||
window.Ya = {}; | ||
// Simulatin metrika script initialization | ||
yandexAnalytics.enableAnalytics({ | ||
options: { | ||
counters: [ | ||
counterId, | ||
], | ||
}, | ||
}); | ||
|
||
// Sending event | ||
const [event, eventCallback] = onEvent.getCall(0).args; | ||
eventCallback({}); | ||
|
||
const counterPbjsMethod = sandbox.stub(); | ||
window[`yaCounter${counterId}`] = { | ||
pbjs: counterPbjsMethod, | ||
}; | ||
clock.tick(2001); | ||
|
||
const [ sentEvents ] = counterPbjsMethod.getCall(0).args; | ||
chai.expect(sentEvents).to.deep.equal([{ | ||
event, | ||
data: {}, | ||
}]); | ||
}); | ||
}); |