-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Yandex Analytics Adapter: initial release #10876
Changes from 7 commits
ef8737b
4928ac2
d1a3861
a04fc31
09bfc62
b338084
44b7c15
54f7c8f
a9d14a5
d524479
1f7cf7e
27dbb4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
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 tagTlds = ['com', 'ru']; | ||
const fileNames = ['tag', 'int'] | ||
const tagUrlTemplate = 'https://mc.yandex.{tld}/metrika/{file}.js'; | ||
export const tagURLs = tagTlds.flatMap((tld) => { | ||
const partialTemplate = tagUrlTemplate.replace('{tld}', tld); | ||
return fileNames.map((file) => partialTemplate.replace('{file}', file)); | ||
}); | ||
|
||
const timeoutIds = {}; | ||
const tryUntil = (operationId, conditionCb, cb) => { | ||
if (!conditionCb()) { | ||
cb(); | ||
timeoutIds[operationId] = setTimeout( | ||
() => tryUntil(conditionCb, conditionCb, cb), | ||
100 | ||
); | ||
} | ||
}; | ||
const waitFor = (operationId, conditionCb, cb) => { | ||
if (conditionCb()) { | ||
cb(); | ||
} else { | ||
timeoutIds[operationId] = setTimeout(() => waitFor(conditionCb, cb), 100); | ||
} | ||
}; | ||
|
||
const clearWaitForTimeouts = (timeouts) => { | ||
timeouts.forEach((timeoutID) => { | ||
if (timeoutIds[timeoutID]) { | ||
clearTimeout(timeoutIds[timeoutID]); | ||
} | ||
}); | ||
}; | ||
|
||
const SEND_EVENTS_BUNDLE_TIMEOUT = 1500; | ||
const { | ||
EVENTS: { | ||
BID_REQUESTED, | ||
BID_RESPONSE, | ||
BID_ADJUSTMENT, | ||
BID_WON, | ||
BIDDER_DONE, | ||
AUCTION_END, | ||
BID_TIMEOUT, | ||
}, | ||
} = CONSTANTS; | ||
|
||
export const EVENTS_TO_TRACK = [ | ||
BID_REQUESTED, | ||
BID_RESPONSE, | ||
BID_ADJUSTMENT, | ||
BID_WON, | ||
BIDDER_DONE, | ||
AUCTION_END, | ||
BID_TIMEOUT, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't these all be undefined since they're declared inside the EVENTS object above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There would be ["bidRequested", "bidResponse", "bidAdjustment", ...]. lines 42-52 have destruction syntax, so for example BID_REQUESTED will be CONSTANTS.EVENTS.BID_REQUESTED, that is "bidRequested" as defined here and so on |
||
]; | ||
|
||
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(`Initialized metrika counter ${counterId}`); | ||
if (!yandexAnalytics.oneCounterInited) { | ||
yandexAnalytics.oneCounterInited = true; | ||
setTimeout(() => { | ||
yandexAnalytics.sendEvents(); | ||
}, SEND_EVENTS_BUNDLE_TIMEOUT); | ||
clearTimeout(yandexAnalytics.initTimeoutId); | ||
} | ||
}, | ||
|
||
getCounterScript: () => { | ||
const presentScript = document.querySelector( | ||
tagURLs.map((tagUrl) => `script[src="${tagUrl}"]`).join(',') | ||
); | ||
if (presentScript) { | ||
return presentScript; | ||
} | ||
|
||
const script = window.document.createElement('script'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this isnt allowed, please remove the script. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
script.setAttribute('src', tagURLs[0]); | ||
window.document.body.appendChild(script); | ||
return script; | ||
}, | ||
|
||
enableAnalytics: (config) => { | ||
yandexAnalytics.options = (config && config.options) || {}; | ||
const { counters } = yandexAnalytics.options || {}; | ||
const validCounters = counters.filter((counterOptions) => { | ||
if (!counterOptions) { | ||
return false; | ||
} | ||
|
||
if (isNaN(counterOptions) && isNaN(counterOptions.id)) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}); | ||
|
||
if (!validCounters.length) { | ||
logError('options.counters contains no valid counter ids'); | ||
return; | ||
} | ||
|
||
const unsubscribeCallbacks = [ | ||
() => clearWaitForTimeouts(['body', 'countersInit']), | ||
]; | ||
|
||
yandexAnalytics.initTimeoutId = setTimeout(() => { | ||
yandexAnalytics.bufferedEvents = []; | ||
unsubscribeCallbacks.forEach((cb) => cb()); | ||
logError(`Can't find metrika counter after 25 seconds.`); | ||
logError('Aborting analytics provider initialization.'); | ||
}, 25000); | ||
|
||
events.getEvents().forEach((event) => { | ||
if (event && EVENTS_TO_TRACK.indexOf(event.eventType) >= 0) { | ||
yandexAnalytics.onEvent(event.eventType, event); | ||
} | ||
}); | ||
|
||
const eventsCallbacks = []; | ||
EVENTS_TO_TRACK.forEach((eventName) => { | ||
const eventCallback = yandexAnalytics.onEvent.bind(null, eventName); | ||
eventsCallbacks.push([eventName, eventCallback]); | ||
events.on(eventName, eventCallback); | ||
}); | ||
|
||
unsubscribeCallbacks.push(() => { | ||
eventsCallbacks.forEach(([eventName, cb]) => events.off(eventName, cb)); | ||
}); | ||
|
||
// Waiting for counters appearing on the page | ||
const presentCounters = validCounters.map( | ||
(options) => typeof options === 'object' ? options.id : options | ||
); | ||
let allCountersInited = false; | ||
if (presentCounters.length) { | ||
tryUntil('countersInit', () => allCountersInited, () => { | ||
allCountersInited = presentCounters.reduce((result, counterId) => { | ||
if (yandexAnalytics.counters[counterId]) { | ||
return result && true; | ||
} | ||
|
||
if (window[`yaCounter${counterId}`]) { | ||
yandexAnalytics.onCounterInit(counterId); | ||
return result && true; | ||
} | ||
|
||
return false; | ||
}, true); | ||
}); | ||
} | ||
|
||
// Inserting counter script and initializing counters | ||
if (!allCountersInited) { | ||
const coutnersToInit = validCounters.filter((options) => { | ||
const id = options === 'object' ? options.id : options; | ||
return !yandexAnalytics.counters[id]; | ||
}); | ||
waitFor('body', () => window.document.body, () => { | ||
const tag = yandexAnalytics.getCounterScript(); | ||
const onScriptLoad = () => { | ||
coutnersToInit.forEach((counterOptions) => { | ||
const id = counterOptions === 'object' | ||
? counterOptions.id | ||
: counterOptions; | ||
window[`yaCounter${id}`] = | ||
new window.Ya.Metrika2(counterOptions); | ||
yandexAnalytics.onCounterInit(id); | ||
}); | ||
}; | ||
unsubscribeCallbacks.push(() => { | ||
tag.removeEventListener('load', onScriptLoad); | ||
}); | ||
tag.addEventListener('load', onScriptLoad); | ||
logInfo('Inserting metrika tag script'); | ||
}); | ||
} | ||
}, | ||
}); | ||
|
||
adapterManager.registerAnalyticsAdapter({ | ||
adapter: yandexAnalytics, | ||
code: 'yandexAnalytics' | ||
}); | ||
|
||
export default yandexAnalytics; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# 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 loads 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 | ||
|
||
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, | ||
{ | ||
id: 1234, | ||
clickmap: true, | ||
} | ||
], | ||
}, | ||
}); | ||
``` | ||
|
||
## Where to find data | ||
|
||
Go to https://metrika.yandex.ru/dashboard -> Prebid Analytics |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this declaration work? I get a type error when I try this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it works, in fact it passes autotests + has been tested manually. Please share error text with me, so we can debug it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot nest destructuring assignment. You need to removed the EVENTS object layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for improvement, done.