From a81924626dbaa1519c705e62099e9ae7a8efd371 Mon Sep 17 00:00:00 2001 From: Liam Cardenas Date: Fri, 16 Feb 2018 00:47:52 -0800 Subject: [PATCH 1/2] Revamped analytics --- flow-typed/npm/mixpanel-browser_v2.11.x.js | 37 ++++++++++++++++ package.json | 2 +- src/renderer/analytics.js | 31 ++++++++++++++ src/renderer/index.js | 47 +++++++-------------- src/renderer/redux/actions/navigation.js | 3 ++ src/renderer/redux/actions/user.js | 3 ++ yarn.lock | 49 +++++----------------- 7 files changed, 100 insertions(+), 72 deletions(-) create mode 100644 flow-typed/npm/mixpanel-browser_v2.11.x.js create mode 100644 src/renderer/analytics.js diff --git a/flow-typed/npm/mixpanel-browser_v2.11.x.js b/flow-typed/npm/mixpanel-browser_v2.11.x.js new file mode 100644 index 000000000..d6575b407 --- /dev/null +++ b/flow-typed/npm/mixpanel-browser_v2.11.x.js @@ -0,0 +1,37 @@ +// flow-typed signature: 89b1643389fe61f97995bdeb2f68d847 +// flow-typed version: dcf8c1c580/mixpanel-browser_v2.11.x/flow_>=v0.25.x + +declare module 'mixpanel-browser' { + declare type People = { + set(prop: Object|String, to?: any, callback?: Function): void; + set_once(prop: Object|String, to?: any, callback?: Function): void; + increment(prop: Object|string, by?: number, callback?: Function): void; + append(prop: Object|string, value?: any, callback?: Function): void; + union(prop: Object|string, value?: any, callback?: Function): void; + track_charge(amount: number, properties?: Object, callback?: Function): void; + clear_charges(callback?: Function): void; + delete_user(): void; + } + + declare type MixpanelBrowser = { + init(token: string, config?: Object, name?: string): void; + push(item: [string, Object]): void; + disable(events?: string[]): void; + track(event_name: string, properties?: Object, callback?: Function): void; + track_links(query: Object|string, event_name: string, properties?: Object|Function): void; + track_forms(query: Object|string, event_name: string, properties?: Object|Function): void; + time_event(event_name: string): void; + register(properties: Object, days?: number): void; + register_once(properties: Object, default_value?: any, days?: number): void; + unregister(property: string): void; + identify(unique_id: string): void; + reset(): void; + get_distinct_id(): string; + alias(alias: string, original?: string): void; + set_config(config: Object): void; + get_config(): Object; + people: People; + } + + declare module.exports: MixpanelBrowser; +} diff --git a/package.json b/package.json index 151127d30..8b82a3891 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "lbry" ], "dependencies": { - "amplitude-js": "^4.0.0", "bluebird": "^3.5.1", "classnames": "^2.2.5", "country-data": "^0.0.31", @@ -45,6 +44,7 @@ "jshashes": "^1.0.7", "keytar-prebuild": "^4.0.4", "localforage": "^1.5.0", + "mixpanel-browser": "^2.17.1", "moment": "^2.20.1", "npm": "^5.5.1", "qrcode.react": "^0.7.2", diff --git a/src/renderer/analytics.js b/src/renderer/analytics.js new file mode 100644 index 000000000..15db084c3 --- /dev/null +++ b/src/renderer/analytics.js @@ -0,0 +1,31 @@ +// @flow +import mixpanel from 'mixpanel-browser'; + +mixpanel.init('691723e855cabb9d27a7a79002216967'); + +type Analytics = { + track: (string, ?Object) => void, + setUser: (Object) => void, +} + +const analytics: Analytics = { + track: (name: string, payload: ?Object): void => { + if(payload) { + mixpanel.track(name, payload); + } else { + mixpanel.track(name); + } + }, + setUser: (user: Object): void => { + if(user.id) { + mixpanel.identify(user.id); + } + if(user.primary_email) { + mixpanel.people.set({ + "$email": user.primary_email + }); + } + } +} + +export default analytics; diff --git a/src/renderer/index.js b/src/renderer/index.js index 40408a8a5..a67444b93 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -1,5 +1,4 @@ /* eslint-disable react/jsx-filename-extension */ -import amplitude from 'amplitude-js'; import App from 'component/app'; import SnackBar from 'component/snackBar'; import SplashScreen from 'component/splash'; @@ -17,6 +16,7 @@ import { doUserEmailVerify } from 'redux/actions/user'; import 'scss/all.scss'; import store from 'store'; import app from './app'; +import analytics from './analytics'; const { autoUpdater } = remote.require('electron-updater'); @@ -62,15 +62,6 @@ ipcRenderer.on('window-is-focused', () => { dock.setBadge(''); }); -((history, ...args) => { - const { replaceState } = history; - const newHistory = history; - newHistory.replaceState = (_, __, path) => { - amplitude.getInstance().logEvent('NAVIGATION', { destination: path ? path.slice(1) : path }); - return replaceState.apply(history, args); - }; -})(window.history); - document.addEventListener('click', event => { let { target } = event; while (target && target !== document) { @@ -79,12 +70,12 @@ document.addEventListener('click', event => { const hrefParts = window.location.href.split('#'); const element = target.title || (target.textContent && target.textContent.trim()); if (element) { - amplitude.getInstance().logEvent('CLICK', { + analytics.track('CLICK', { target: element, location: hrefParts.length > 1 ? hrefParts[hrefParts.length - 1] : '/', }); } else { - amplitude.getInstance().logEvent('UNMARKED_CLICK', { + analytics.track('UNMARKED_CLICK', { location: hrefParts.length > 1 ? hrefParts[hrefParts.length - 1] : '/', source: target.outerHTML, }); @@ -120,28 +111,18 @@ const init = () => { app.store.dispatch(doDownloadLanguages()); function onDaemonReady() { - lbry.status().then(info => { - amplitude.getInstance().init( - // Amplitude API Key - '0b130efdcbdbf86ec2f7f9eff354033e', - info.lbry_id, - null, - () => { - window.sessionStorage.setItem('loaded', 'y'); // once we've made it here once per session, we don't need to show splash again - app.store.dispatch(doDaemonReady()); + window.sessionStorage.setItem('loaded', 'y'); // once we've made it here once per session, we don't need to show splash again + app.store.dispatch(doDaemonReady()); - ReactDOM.render( - -
- - -
-
, - document.getElementById('app') - ); - } - ); - }); + ReactDOM.render( + +
+ + +
+
, + document.getElementById('app') + ); } if (window.sessionStorage.getItem('loaded') === 'y') { diff --git a/src/renderer/redux/actions/navigation.js b/src/renderer/redux/actions/navigation.js index 78e874376..81be11261 100644 --- a/src/renderer/redux/actions/navigation.js +++ b/src/renderer/redux/actions/navigation.js @@ -1,6 +1,7 @@ import * as ACTIONS from 'constants/action_types'; import { selectHistoryIndex, selectHistoryStack } from 'redux/selectors/navigation'; import { toQueryString } from 'util/query_params'; +import analytics from 'analytics'; export function doNavigate(path, params = {}, options = {}) { return dispatch => { @@ -13,6 +14,8 @@ export function doNavigate(path, params = {}, options = {}) { url += `?${toQueryString(params)}`; } + analytics.track('NAVIGATION', { destination: url }); + const { scrollY } = options; dispatch({ diff --git a/src/renderer/redux/actions/user.js b/src/renderer/redux/actions/user.js index 4ae9567c2..2c2e1a09e 100644 --- a/src/renderer/redux/actions/user.js +++ b/src/renderer/redux/actions/user.js @@ -9,6 +9,7 @@ import { selectUserCountryCode, } from 'redux/selectors/user'; import rewards from 'rewards'; +import analytics from 'analytics'; export function doFetchInviteStatus() { return dispatch => { @@ -42,6 +43,7 @@ export function doAuthenticate() { }); Lbryio.authenticate() .then(user => { + analytics.setUser(user); dispatch({ type: ACTIONS.AUTHENTICATION_SUCCESS, data: { user }, @@ -66,6 +68,7 @@ export function doUserFetch() { }); Lbryio.getCurrentUser() .then(user => { + analytics.setUser(user); dispatch(doRewardList()); dispatch({ diff --git a/yarn.lock b/yarn.lock index 1def2f7e4..7b2d79f2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,13 +79,6 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@segment/top-domain@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@segment/top-domain/-/top-domain-3.0.0.tgz#02e5a5a4fd42a9f6cf886b05e82f104012a3c3a7" - dependencies: - component-cookie "^1.1.2" - component-url "^0.2.1" - "@types/node@^7.0.18": version "7.0.52" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.52.tgz#8990d3350375542b0c21a83cd0331e6a8fc86716" @@ -217,16 +210,6 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -amplitude-js@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/amplitude-js/-/amplitude-js-4.0.0.tgz#70bbc0ec893b01d00453d3765f78bc0f32a395cc" - dependencies: - "@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#ed538f1" - ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -1400,10 +1383,6 @@ bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@~3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" -blueimp-md5@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.10.0.tgz#02f0843921f90dca14f5b8920a38593201d6964d" - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -2081,16 +2060,6 @@ compare-version@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" -component-cookie@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/component-cookie/-/component-cookie-1.1.3.tgz#053e14a3bd7748154f55724fd39a60c01994ebed" - dependencies: - debug "*" - -component-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/component-url/-/component-url-0.2.1.tgz#4e4f4799c43ead9fd3ce91b5a305d220208fee47" - compressible@~2.0.11: version "2.0.12" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66" @@ -2451,18 +2420,18 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@*, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - debug@2, debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" +debug@^3.0.0, debug@^3.0.1, debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -5799,6 +5768,10 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mixpanel-browser@^2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.17.1.tgz#1b90a0478ec912f35f761c52e08d4228fc37867f" + mkdirp@0.5, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -8795,7 +8768,7 @@ typo-js@*: version "1.0.3" resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.0.3.tgz#54d8ebc7949f1a7810908b6002c6841526c99d5a" -ua-parser-js@^0.7.9, "ua-parser-js@github:amplitude/ua-parser-js#ed538f1": +ua-parser-js@^0.7.9: version "0.7.10" resolved "https://codeload.github.com/amplitude/ua-parser-js/tar.gz/ed538f16f5c6ecd8357da989b617d4f156dcf35d" From c765ebd28f54268a5a2133992f8ed95caa6cacca Mon Sep 17 00:00:00 2001 From: Liam Cardenas Date: Fri, 16 Feb 2018 01:16:50 -0800 Subject: [PATCH 2/2] Diagnostic checkbox analytics integration --- src/renderer/analytics.js | 19 +++++++++++++++---- src/renderer/redux/actions/settings.js | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/renderer/analytics.js b/src/renderer/analytics.js index 15db084c3..7d426e4f9 100644 --- a/src/renderer/analytics.js +++ b/src/renderer/analytics.js @@ -6,14 +6,19 @@ mixpanel.init('691723e855cabb9d27a7a79002216967'); type Analytics = { track: (string, ?Object) => void, setUser: (Object) => void, + toggle: (boolean, ?boolean) => void } +let analyticsEnabled: boolean = false; + const analytics: Analytics = { track: (name: string, payload: ?Object): void => { - if(payload) { - mixpanel.track(name, payload); - } else { - mixpanel.track(name); + if(analyticsEnabled) { + if(payload) { + mixpanel.track(name, payload); + } else { + mixpanel.track(name); + } } }, setUser: (user: Object): void => { @@ -25,6 +30,12 @@ const analytics: Analytics = { "$email": user.primary_email }); } + }, + toggle: (enabled: boolean, logDisabled: ?boolean): void => { + if(!enabled && logDisabled) { + mixpanel.track('DISABLED'); + } + analyticsEnabled = enabled; } } diff --git a/src/renderer/redux/actions/settings.js b/src/renderer/redux/actions/settings.js index 661ff10ba..9196dca44 100644 --- a/src/renderer/redux/actions/settings.js +++ b/src/renderer/redux/actions/settings.js @@ -2,15 +2,16 @@ import * as ACTIONS from 'constants/action_types'; import * as SETTINGS from 'constants/settings'; import Fs from 'fs'; import Http from 'http'; - import Lbry from 'lbry'; import moment from 'moment'; +import analytics from 'analytics'; const UPDATE_IS_NIGHT_INTERVAL = 10 * 60 * 1000; export function doFetchDaemonSettings() { return dispatch => { Lbry.settings_get().then(settings => { + analytics.toggle(settings.share_usage_data); dispatch({ type: ACTIONS.DAEMON_SETTINGS_RECEIVED, data: { @@ -27,6 +28,7 @@ export function doSetDaemonSetting(key, value) { newSettings[key] = value; Lbry.settings_set(newSettings).then(newSettings); Lbry.settings_get().then(settings => { + analytics.toggle(settings.share_usage_data, true); dispatch({ type: ACTIONS.DAEMON_SETTINGS_RECEIVED, data: {