feat(Amplitude): Integration.
This commit is contained in:
parent
2d57d22a3f
commit
e5a8d95f1f
4
Makefile
4
Makefile
|
@ -43,6 +43,10 @@ deploy-appbundle:
|
|||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.map \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(BUILD_DIR)/analytics-amplitude.min.js \
|
||||
$(BUILD_DIR)/analytics-amplitude.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
|
|
|
@ -2191,6 +2191,15 @@
|
|||
"isomorphic-fetch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"@segment/top-domain": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "http://registry.npmjs.org/@segment/top-domain/-/top-domain-3.0.0.tgz",
|
||||
"integrity": "sha1-AuWlpP1CqfbPiGsF6C8QQBKjw6c=",
|
||||
"requires": {
|
||||
"component-cookie": "^1.1.2",
|
||||
"component-url": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
|
||||
|
@ -2460,6 +2469,24 @@
|
|||
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
|
||||
"dev": true
|
||||
},
|
||||
"amplitude-js": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-4.5.2.tgz",
|
||||
"integrity": "sha512-J075hRBuhCuBqwrhmuGIXg7zCLRO6TvONTJUESpTkM1LVL5bMcTx9BczW4Hh6p6kjBQjs2fD4rNbhPs48kYZwA==",
|
||||
"requires": {
|
||||
"@segment/top-domain": "^3.0.0",
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"json3": "^3.3.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": {
|
||||
"version": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d",
|
||||
"from": "github:amplitude/ua-parser-js#ed538f1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ansi": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz",
|
||||
|
@ -3295,6 +3322,11 @@
|
|||
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
|
||||
"dev": true
|
||||
},
|
||||
"blueimp-md5": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.10.0.tgz",
|
||||
"integrity": "sha512-EkNUOi7tpV68TqjpiUz9D9NcT8um2+qtgntmMbi5UKssVX2m/2PLqotcric0RE63pB3HPN/fjf3cKHN2ufGSUQ=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
||||
|
@ -4070,11 +4102,39 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
||||
},
|
||||
"component-cookie": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/component-cookie/-/component-cookie-1.1.4.tgz",
|
||||
"integrity": "sha512-j6rzl+vHDTowvYz7Al3V0ud84O2l4YqGdA9qMj1W1nlZ5yWi7EhOd7ZSPzWFM25gZgv2OxWh6JlJYfsz2+XYow==",
|
||||
"requires": {
|
||||
"debug": "2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||
"requires": {
|
||||
"ms": "0.7.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
|
||||
}
|
||||
}
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
},
|
||||
"component-url": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-url/-/component-url-0.2.1.tgz",
|
||||
"integrity": "sha1-Tk9HmcQ+rZ/TzpG1owXSICCP7kc="
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.15",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
|
||||
|
@ -8247,8 +8307,7 @@
|
|||
"json3": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
|
||||
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE="
|
||||
},
|
||||
"json5": {
|
||||
"version": "0.5.1",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"@atlaskit/tooltip": "12.1.13",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
"@webcomponents/url": "0.7.1",
|
||||
"amplitude-js": "4.5.2",
|
||||
"dropbox": "4.0.9",
|
||||
"i18next": "8.4.3",
|
||||
"i18next-browser-languagedetector": "2.0.0",
|
||||
|
|
|
@ -43,10 +43,15 @@ export function initAnalytics({ getState }: { getState: Function }) {
|
|||
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const { analyticsScriptUrls, deploymentInfo, googleAnalyticsTrackingId }
|
||||
= config;
|
||||
const {
|
||||
amplitudeAPPKey,
|
||||
analyticsScriptUrls,
|
||||
deploymentInfo,
|
||||
googleAnalyticsTrackingId
|
||||
} = config;
|
||||
const { group, server, user } = state['features/base/jwt'];
|
||||
const handlerConstructorOptions = {
|
||||
amplitudeAPPKey,
|
||||
envType: (deploymentInfo && deploymentInfo.envType) || 'dev',
|
||||
googleAnalyticsTrackingId,
|
||||
group,
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Abstract implementation of analytics handler
|
||||
*/
|
||||
export default class AbstractHandler {
|
||||
/**
|
||||
* Creates new instance.
|
||||
*/
|
||||
constructor() {
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a name for the event from the event properties.
|
||||
*
|
||||
* @param {Object} event - The analytics event.
|
||||
* @returns {string} - The extracted name.
|
||||
*/
|
||||
_extractName(event) {
|
||||
// Page events have a single 'name' field.
|
||||
if (event.type === 'page') {
|
||||
return event.name;
|
||||
}
|
||||
|
||||
const {
|
||||
action,
|
||||
actionSubject,
|
||||
source
|
||||
} = event;
|
||||
|
||||
// All events have action, actionSubject, and source fields. All
|
||||
// three fields are required, and often jitsi-meet and
|
||||
// lib-jitsi-meet use the same value when separate values are not
|
||||
// necessary (i.e. event.action == event.actionSubject).
|
||||
// Here we concatenate these three fields, but avoid adding the same
|
||||
// value twice, because it would only make the event's name harder
|
||||
// to read.
|
||||
let name = action;
|
||||
|
||||
if (actionSubject && actionSubject !== action) {
|
||||
name = `${actionSubject}.${action}`;
|
||||
}
|
||||
if (source && source !== action) {
|
||||
name = `${source}.${name}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an event should be ignored or not.
|
||||
*
|
||||
* @param {Object} event - The event.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_shouldIgnore(event) {
|
||||
if (!event || !this._enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ignoredEvents
|
||||
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
|
||||
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
|
||||
|
||||
// Temporary removing some of the events that are too noisy.
|
||||
return ignoredEvents.indexOf(event.action) !== -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import amplitude from 'amplitude-js';
|
||||
|
||||
import { getJitsiMeetGlobalNS } from '../../base/util';
|
||||
|
||||
import AbstractHandler from './AbstractHandler';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Analytics handler for Amplitude.
|
||||
*/
|
||||
class AmplitudeHandler extends AbstractHandler {
|
||||
/**
|
||||
* Creates new instance of the Amplitude analytics handler.
|
||||
*
|
||||
* @param {Object} options -
|
||||
* @param {string} options.amplitudeAPPKey - The Amplitude app key required
|
||||
* by the Amplitude API.
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
const { amplitudeAPPKey } = options;
|
||||
|
||||
if (!amplitudeAPPKey) {
|
||||
logger.warn(
|
||||
'Failed to initialize Amplitude handler, no tracking ID');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
|
||||
amplitude.getInstance().init(amplitudeAPPKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Amplitude user properties.
|
||||
*
|
||||
* @param {Object} props - The user portperties.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserProperties(props) {
|
||||
if (this._enabled) {
|
||||
amplitude.getInstance().setUserProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an event to Amplitude. The format of the event is described
|
||||
* in AnalyticsAdapter in lib-jitsi-meet.
|
||||
*
|
||||
* @param {Object} event - The event in the format specified by
|
||||
* lib-jitsi-meet.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendEvent(event) {
|
||||
if (this._shouldIgnore(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
amplitude.getInstance().logEvent(
|
||||
this._extractName(event),
|
||||
event);
|
||||
}
|
||||
}
|
||||
|
||||
const globalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
globalNS.analyticsHandlers = globalNS.analyticsHandlers || [];
|
||||
globalNS.analyticsHandlers.push(AmplitudeHandler);
|
|
@ -0,0 +1,153 @@
|
|||
/* global ga */
|
||||
|
||||
import { getJitsiMeetGlobalNS } from '../../base/util';
|
||||
|
||||
import AbstractHandler from './AbstractHandler';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Analytics handler for Google Analytics.
|
||||
*/
|
||||
class GoogleAnalyticsHandler extends AbstractHandler {
|
||||
|
||||
/**
|
||||
* Creates new instance of the GA analytics handler.
|
||||
*
|
||||
* @param {Object} options -
|
||||
* @param {string} options.googleAnalyticsTrackingId - The GA track id
|
||||
* required by the GA API.
|
||||
*/
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
this._userProperties = {};
|
||||
|
||||
if (!options.googleAnalyticsTrackingId) {
|
||||
logger.warn(
|
||||
'Failed to initialize Google Analytics handler, no tracking ID'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
this._initGoogleAnalytics(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the ga object.
|
||||
*
|
||||
* @param {Object} options -
|
||||
* @param {string} options.googleAnalyticsTrackingId - The GA track id
|
||||
* required by the GA API.
|
||||
* @returns {void}
|
||||
*/
|
||||
_initGoogleAnalytics(options) {
|
||||
/**
|
||||
* TODO: Keep this local, there's no need to add it to window.
|
||||
*/
|
||||
/* eslint-disable */
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
/* eslint-enable */
|
||||
ga('create', options.googleAnalyticsTrackingId, 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
*
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {number} - The integer to use for the 'value' of a Google
|
||||
* analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
|
||||
* suitable value.
|
||||
* @private
|
||||
*/
|
||||
_extractValue(event) {
|
||||
let value = event && event.attributes && event.attributes.value;
|
||||
|
||||
// Try to extract an integer from the "value" attribute.
|
||||
value = Math.round(parseFloat(value));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the string to use for a Google Analytics event's label field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
*
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {string} - The string to use for the 'label' of a Google
|
||||
* analytics event.
|
||||
* @private
|
||||
*/
|
||||
_extractLabel(event) {
|
||||
const { attributes = {} } = event;
|
||||
const labelsArray
|
||||
= Object.keys(attributes).map(key => `${key}=${attributes[key]}`);
|
||||
|
||||
labelsArray.push(this._userPropertiesString);
|
||||
|
||||
return labelsArray.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permanent properties for the current session.
|
||||
*
|
||||
* @param {Object} props - The permanent portperties.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserProperties(props = {}) {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The label field is limited to 500B. We will concatenate all
|
||||
// attributes of the event, except the user agent because it may be
|
||||
// lengthy and is probably included from elsewhere.
|
||||
const filter = [ 'user_agent', 'callstats_name' ];
|
||||
|
||||
this._userPropertiesString
|
||||
= Object.keys(props)
|
||||
.filter(key => filter.indexOf(key) === -1)
|
||||
.map(key => `permanent_${key}=${props[key]}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the entry point of the API. The function sends an event to
|
||||
* google analytics. The format of the event is described in
|
||||
* analyticsAdapter in lib-jitsi-meet.
|
||||
*
|
||||
* @param {Object} event - The event in the format specified by
|
||||
* lib-jitsi-meet.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendEvent(event) {
|
||||
if (this._shouldIgnore(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gaEvent = {
|
||||
'eventCategory': 'jitsi-meet',
|
||||
'eventAction': this._extractName(event),
|
||||
'eventLabel': this._extractLabel(event)
|
||||
};
|
||||
const value = this._extractValue(event);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
gaEvent.eventValue = value;
|
||||
}
|
||||
|
||||
ga('send', 'event', gaEvent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const globalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
globalNS.analyticsHandlers = globalNS.analyticsHandlers || [];
|
||||
globalNS.analyticsHandlers.push(GoogleAnalyticsHandler);
|
|
@ -133,7 +133,11 @@ module.exports = [
|
|||
|
||||
'flacEncodeWorker':
|
||||
'./react/features/local-recording/'
|
||||
+ 'recording/flac/flacEncodeWorker.js'
|
||||
+ 'recording/flac/flacEncodeWorker.js',
|
||||
'analytics-ga':
|
||||
'./react/features/analytics/handlers/GoogleAnalyticsHandler.js',
|
||||
'analytics-amplitude':
|
||||
'./react/features/analytics/handlers/AmplitudeHandler.js'
|
||||
}
|
||||
}),
|
||||
|
||||
|
|
Loading…
Reference in New Issue