Selaa lähdekoodia

feat(Amplitude): Integration.

master
Hristo Terezov 6 vuotta sitten
vanhempi
commit
e5a8d95f1f

+ 4
- 0
Makefile Näytä tiedosto

@@ -43,6 +43,10 @@ deploy-appbundle:
43 43
 		$(BUILD_DIR)/alwaysontop.min.js \
44 44
 		$(BUILD_DIR)/alwaysontop.min.map \
45 45
 		$(OUTPUT_DIR)/analytics-ga.js \
46
+		$(BUILD_DIR)/analytics-ga.min.js \
47
+		$(BUILD_DIR)/analytics-ga.min.map \
48
+		$(BUILD_DIR)/analytics-amplitude.min.js \
49
+		$(BUILD_DIR)/analytics-amplitude.min.map \
46 50
 		$(DEPLOY_DIR)
47 51
 
48 52
 deploy-lib-jitsi-meet:

+ 61
- 2
package-lock.json Näytä tiedosto

@@ -2191,6 +2191,15 @@
2191 2191
         "isomorphic-fetch": "^2.2.1"
2192 2192
       }
2193 2193
     },
2194
+    "@segment/top-domain": {
2195
+      "version": "3.0.0",
2196
+      "resolved": "http://registry.npmjs.org/@segment/top-domain/-/top-domain-3.0.0.tgz",
2197
+      "integrity": "sha1-AuWlpP1CqfbPiGsF6C8QQBKjw6c=",
2198
+      "requires": {
2199
+        "component-cookie": "^1.1.2",
2200
+        "component-url": "^0.2.1"
2201
+      }
2202
+    },
2194 2203
     "@webassemblyjs/ast": {
2195 2204
       "version": "1.7.11",
2196 2205
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
@@ -2460,6 +2469,24 @@
2460 2469
       "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
2461 2470
       "dev": true
2462 2471
     },
2472
+    "amplitude-js": {
2473
+      "version": "4.5.2",
2474
+      "resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-4.5.2.tgz",
2475
+      "integrity": "sha512-J075hRBuhCuBqwrhmuGIXg7zCLRO6TvONTJUESpTkM1LVL5bMcTx9BczW4Hh6p6kjBQjs2fD4rNbhPs48kYZwA==",
2476
+      "requires": {
2477
+        "@segment/top-domain": "^3.0.0",
2478
+        "blueimp-md5": "^2.10.0",
2479
+        "json3": "^3.3.2",
2480
+        "lodash": "^4.17.4",
2481
+        "ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
2482
+      },
2483
+      "dependencies": {
2484
+        "ua-parser-js": {
2485
+          "version": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d",
2486
+          "from": "github:amplitude/ua-parser-js#ed538f1"
2487
+        }
2488
+      }
2489
+    },
2463 2490
     "ansi": {
2464 2491
       "version": "0.3.1",
2465 2492
       "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz",
@@ -3295,6 +3322,11 @@
3295 3322
       "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
3296 3323
       "dev": true
3297 3324
     },
3325
+    "blueimp-md5": {
3326
+      "version": "2.10.0",
3327
+      "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.10.0.tgz",
3328
+      "integrity": "sha512-EkNUOi7tpV68TqjpiUz9D9NcT8um2+qtgntmMbi5UKssVX2m/2PLqotcric0RE63pB3HPN/fjf3cKHN2ufGSUQ=="
3329
+    },
3298 3330
     "bn.js": {
3299 3331
       "version": "4.11.8",
3300 3332
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
@@ -4070,11 +4102,39 @@
4070 4102
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
4071 4103
       "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
4072 4104
     },
4105
+    "component-cookie": {
4106
+      "version": "1.1.4",
4107
+      "resolved": "https://registry.npmjs.org/component-cookie/-/component-cookie-1.1.4.tgz",
4108
+      "integrity": "sha512-j6rzl+vHDTowvYz7Al3V0ud84O2l4YqGdA9qMj1W1nlZ5yWi7EhOd7ZSPzWFM25gZgv2OxWh6JlJYfsz2+XYow==",
4109
+      "requires": {
4110
+        "debug": "2.2.0"
4111
+      },
4112
+      "dependencies": {
4113
+        "debug": {
4114
+          "version": "2.2.0",
4115
+          "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
4116
+          "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
4117
+          "requires": {
4118
+            "ms": "0.7.1"
4119
+          }
4120
+        },
4121
+        "ms": {
4122
+          "version": "0.7.1",
4123
+          "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
4124
+          "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
4125
+        }
4126
+      }
4127
+    },
4073 4128
     "component-emitter": {
4074 4129
       "version": "1.2.1",
4075 4130
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
4076 4131
       "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
4077 4132
     },
4133
+    "component-url": {
4134
+      "version": "0.2.1",
4135
+      "resolved": "https://registry.npmjs.org/component-url/-/component-url-0.2.1.tgz",
4136
+      "integrity": "sha1-Tk9HmcQ+rZ/TzpG1owXSICCP7kc="
4137
+    },
4078 4138
     "compressible": {
4079 4139
       "version": "2.0.15",
4080 4140
       "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz",
@@ -8247,8 +8307,7 @@
8247 8307
     "json3": {
8248 8308
       "version": "3.3.2",
8249 8309
       "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
8250
-      "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
8251
-      "dev": true
8310
+      "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE="
8252 8311
     },
8253 8312
     "json5": {
8254 8313
       "version": "0.5.1",

+ 1
- 0
package.json Näytä tiedosto

@@ -35,6 +35,7 @@
35 35
     "@atlaskit/tooltip": "12.1.13",
36 36
     "@microsoft/microsoft-graph-client": "1.1.0",
37 37
     "@webcomponents/url": "0.7.1",
38
+    "amplitude-js": "4.5.2",
38 39
     "dropbox": "4.0.9",
39 40
     "i18next": "8.4.3",
40 41
     "i18next-browser-languagedetector": "2.0.0",

+ 7
- 2
react/features/analytics/functions.js Näytä tiedosto

@@ -43,10 +43,15 @@ export function initAnalytics({ getState }: { getState: Function }) {
43 43
 
44 44
     const state = getState();
45 45
     const config = state['features/base/config'];
46
-    const { analyticsScriptUrls, deploymentInfo, googleAnalyticsTrackingId }
47
-        = config;
46
+    const {
47
+        amplitudeAPPKey,
48
+        analyticsScriptUrls,
49
+        deploymentInfo,
50
+        googleAnalyticsTrackingId
51
+    } = config;
48 52
     const { group, server, user } = state['features/base/jwt'];
49 53
     const handlerConstructorOptions = {
54
+        amplitudeAPPKey,
50 55
         envType: (deploymentInfo && deploymentInfo.envType) || 'dev',
51 56
         googleAnalyticsTrackingId,
52 57
         group,

+ 67
- 0
react/features/analytics/handlers/AbstractHandler.js Näytä tiedosto

@@ -0,0 +1,67 @@
1
+/**
2
+ * Abstract implementation of analytics handler
3
+ */
4
+export default class AbstractHandler {
5
+    /**
6
+     * Creates new instance.
7
+     */
8
+    constructor() {
9
+        this._enabled = false;
10
+    }
11
+
12
+    /**
13
+     * Extracts a name for the event from the event properties.
14
+     *
15
+     * @param {Object} event - The analytics event.
16
+     * @returns {string} - The extracted name.
17
+     */
18
+    _extractName(event) {
19
+        // Page events have a single 'name' field.
20
+        if (event.type === 'page') {
21
+            return event.name;
22
+        }
23
+
24
+        const {
25
+            action,
26
+            actionSubject,
27
+            source
28
+        } = event;
29
+
30
+        // All events have action, actionSubject, and source fields. All
31
+        // three fields are required, and often jitsi-meet and
32
+        // lib-jitsi-meet use the same value when separate values are not
33
+        // necessary (i.e. event.action == event.actionSubject).
34
+        // Here we concatenate these three fields, but avoid adding the same
35
+        // value twice, because it would only make the event's name harder
36
+        // to read.
37
+        let name = action;
38
+
39
+        if (actionSubject && actionSubject !== action) {
40
+            name = `${actionSubject}.${action}`;
41
+        }
42
+        if (source && source !== action) {
43
+            name = `${source}.${name}`;
44
+        }
45
+
46
+        return name;
47
+    }
48
+
49
+    /**
50
+     * Checks if an event should be ignored or not.
51
+     *
52
+     * @param {Object} event - The event.
53
+     * @returns {boolean}
54
+     */
55
+    _shouldIgnore(event) {
56
+        if (!event || !this._enabled) {
57
+            return true;
58
+        }
59
+
60
+        const ignoredEvents
61
+            = [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
62
+                'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
63
+
64
+        // Temporary removing some of the events that are too noisy.
65
+        return ignoredEvents.indexOf(event.action) !== -1;
66
+    }
67
+}

+ 71
- 0
react/features/analytics/handlers/AmplitudeHandler.js Näytä tiedosto

@@ -0,0 +1,71 @@
1
+import amplitude from 'amplitude-js';
2
+
3
+import { getJitsiMeetGlobalNS } from '../../base/util';
4
+
5
+import AbstractHandler from './AbstractHandler';
6
+
7
+const logger = require('jitsi-meet-logger').getLogger(__filename);
8
+
9
+/**
10
+ * Analytics handler for Amplitude.
11
+ */
12
+class AmplitudeHandler extends AbstractHandler {
13
+    /**
14
+     * Creates new instance of the Amplitude analytics handler.
15
+     *
16
+     * @param {Object} options -
17
+     * @param {string} options.amplitudeAPPKey - The Amplitude app key required
18
+     * by the Amplitude API.
19
+     */
20
+    constructor(options) {
21
+        super();
22
+
23
+        const { amplitudeAPPKey } = options;
24
+
25
+        if (!amplitudeAPPKey) {
26
+            logger.warn(
27
+                'Failed to initialize Amplitude handler, no tracking ID');
28
+
29
+            return;
30
+        }
31
+
32
+        this._enabled = true;
33
+
34
+        amplitude.getInstance().init(amplitudeAPPKey);
35
+    }
36
+
37
+    /**
38
+     * Sets the Amplitude user properties.
39
+     *
40
+     * @param {Object} props - The user portperties.
41
+     * @returns {void}
42
+     */
43
+    setUserProperties(props) {
44
+        if (this._enabled) {
45
+            amplitude.getInstance().setUserProperties(props);
46
+        }
47
+    }
48
+
49
+    /**
50
+     * Sends an event to Amplitude. The format of the event is described
51
+     * in AnalyticsAdapter in lib-jitsi-meet.
52
+     *
53
+     * @param {Object} event - The event in the format specified by
54
+     * lib-jitsi-meet.
55
+     * @returns {void}
56
+     */
57
+    sendEvent(event) {
58
+        if (this._shouldIgnore(event)) {
59
+            return;
60
+        }
61
+
62
+        amplitude.getInstance().logEvent(
63
+            this._extractName(event),
64
+            event);
65
+    }
66
+}
67
+
68
+const globalNS = getJitsiMeetGlobalNS();
69
+
70
+globalNS.analyticsHandlers = globalNS.analyticsHandlers || [];
71
+globalNS.analyticsHandlers.push(AmplitudeHandler);

+ 153
- 0
react/features/analytics/handlers/GoogleAnalyticsHandler.js Näytä tiedosto

@@ -0,0 +1,153 @@
1
+/* global ga */
2
+
3
+import { getJitsiMeetGlobalNS } from '../../base/util';
4
+
5
+import AbstractHandler from './AbstractHandler';
6
+
7
+const logger = require('jitsi-meet-logger').getLogger(__filename);
8
+
9
+/**
10
+ * Analytics handler for Google Analytics.
11
+ */
12
+class GoogleAnalyticsHandler extends AbstractHandler {
13
+
14
+    /**
15
+     * Creates new instance of the GA analytics handler.
16
+     *
17
+     * @param {Object} options -
18
+     * @param {string} options.googleAnalyticsTrackingId - The GA track id
19
+     * required by the GA API.
20
+     */
21
+    constructor(options) {
22
+        super();
23
+
24
+        this._userProperties = {};
25
+
26
+        if (!options.googleAnalyticsTrackingId) {
27
+            logger.warn(
28
+                'Failed to initialize Google Analytics handler, no tracking ID'
29
+            );
30
+
31
+            return;
32
+        }
33
+
34
+        this._enabled = true;
35
+        this._initGoogleAnalytics(options);
36
+    }
37
+
38
+    /**
39
+     * Initializes the ga object.
40
+     *
41
+     * @param {Object} options -
42
+     * @param {string} options.googleAnalyticsTrackingId - The GA track id
43
+     * required by the GA API.
44
+     * @returns {void}
45
+     */
46
+    _initGoogleAnalytics(options) {
47
+        /**
48
+         * TODO: Keep this local, there's no need to add it to window.
49
+         */
50
+        /* eslint-disable */
51
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
52
+            (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)
53
+        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
54
+        /* eslint-enable */
55
+        ga('create', options.googleAnalyticsTrackingId, 'auto');
56
+        ga('send', 'pageview');
57
+    }
58
+
59
+    /**
60
+     * Extracts the integer to use for a Google Analytics event's value field
61
+     * from a lib-jitsi-meet analytics event.
62
+     *
63
+     * @param {Object} event - The lib-jitsi-meet analytics event.
64
+     * @returns {number} - The integer to use for the 'value' of a Google
65
+     * analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
66
+     * suitable value.
67
+     * @private
68
+     */
69
+    _extractValue(event) {
70
+        let value = event && event.attributes && event.attributes.value;
71
+
72
+        // Try to extract an integer from the "value" attribute.
73
+        value = Math.round(parseFloat(value));
74
+
75
+        return value;
76
+    }
77
+
78
+    /**
79
+     * Extracts the string to use for a Google Analytics event's label field
80
+     * from a lib-jitsi-meet analytics event.
81
+     *
82
+     * @param {Object} event - The lib-jitsi-meet analytics event.
83
+     * @returns {string} - The string to use for the 'label' of a Google
84
+     * analytics event.
85
+     * @private
86
+     */
87
+    _extractLabel(event) {
88
+        const { attributes = {} } = event;
89
+        const labelsArray
90
+            = Object.keys(attributes).map(key => `${key}=${attributes[key]}`);
91
+
92
+        labelsArray.push(this._userPropertiesString);
93
+
94
+        return labelsArray.join('&');
95
+    }
96
+
97
+    /**
98
+     * Sets the permanent properties for the current session.
99
+     *
100
+     * @param {Object} props - The permanent portperties.
101
+     * @returns {void}
102
+     */
103
+    setUserProperties(props = {}) {
104
+        if (!this._enabled) {
105
+            return;
106
+        }
107
+
108
+        // The label field is limited to 500B. We will concatenate all
109
+        // attributes of the event, except the user agent because it may be
110
+        // lengthy and is probably included from elsewhere.
111
+        const filter = [ 'user_agent', 'callstats_name' ];
112
+
113
+        this._userPropertiesString
114
+            = Object.keys(props)
115
+                .filter(key => filter.indexOf(key) === -1)
116
+                .map(key => `permanent_${key}=${props[key]}`)
117
+                .join('&');
118
+    }
119
+
120
+    /**
121
+     * This is the entry point of the API. The function sends an event to
122
+     * google analytics. The format of the event is described in
123
+     * analyticsAdapter in lib-jitsi-meet.
124
+     *
125
+     * @param {Object} event - The event in the format specified by
126
+     * lib-jitsi-meet.
127
+     * @returns {void}
128
+     */
129
+    sendEvent(event) {
130
+        if (this._shouldIgnore(event)) {
131
+            return;
132
+        }
133
+
134
+        const gaEvent = {
135
+            'eventCategory': 'jitsi-meet',
136
+            'eventAction': this._extractName(event),
137
+            'eventLabel': this._extractLabel(event)
138
+        };
139
+        const value = this._extractValue(event);
140
+
141
+        if (!isNaN(value)) {
142
+            gaEvent.eventValue = value;
143
+        }
144
+
145
+        ga('send', 'event', gaEvent);
146
+    }
147
+
148
+}
149
+
150
+const globalNS = getJitsiMeetGlobalNS();
151
+
152
+globalNS.analyticsHandlers = globalNS.analyticsHandlers || [];
153
+globalNS.analyticsHandlers.push(GoogleAnalyticsHandler);

+ 5
- 1
webpack.config.js Näytä tiedosto

@@ -133,7 +133,11 @@ module.exports = [
133 133
 
134 134
             'flacEncodeWorker':
135 135
                 './react/features/local-recording/'
136
-                    + 'recording/flac/flacEncodeWorker.js'
136
+                    + 'recording/flac/flacEncodeWorker.js',
137
+            'analytics-ga':
138
+                './react/features/analytics/handlers/GoogleAnalyticsHandler.js',
139
+            'analytics-amplitude':
140
+                './react/features/analytics/handlers/AmplitudeHandler.js'
137 141
         }
138 142
     }),
139 143
 

Loading…
Peruuta
Tallenna