From 25b93f741a98e41de92997388f0a756f74291b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BARBIER?= Date: Mon, 7 May 2018 17:53:40 +0700 Subject: [PATCH] Implement all GA events (#1033) --- docs/metrics.md | 4 +-- native/app/actions.js | 18 +++++++++-- native/app/background.js | 3 +- native/app/components/DrawerItems.js | 3 ++ native/app/components/EditorPanel.js | 2 +- native/app/components/ListPanel.js | 6 ++++ native/app/components/MoreMenu.js | 2 +- native/app/utils/metrics.js | 47 ++++++++++++++++++++++++++-- native/app/utils/sync.js | 10 +++++- native/index.js | 3 ++ 10 files changed, 86 insertions(+), 12 deletions(-) diff --git a/docs/metrics.md b/docs/metrics.md index 160a2015..edca6048 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -34,7 +34,7 @@ Data will be collected with Google Analytics and follow [Test Pilot standards](h - `cd4` - whether the user has, anywhere in their active notepad, italicized text. One of `true` or `false`. - `cd5` - whether the user has, anywhere in their active notepad, strikethrough text. One of `true` or `false`. - `cd6` - whether the user has, anywhere in their active notepad, a list. One of `true` or `false`. -- `cd7` - the UI element used to open or close the notepad. Possible values TBD, but may include `closeButton`, `sidebarButton`, and `sidebarSwitcher`. +- `cd7` - the UI element used to open or close the notepad. Possible values TBD, but may include `closeButton`, `sidebarButton`, `sidebarSwitcher`, `appBackground`, `appInactive`. - `cd8` - the reason an editing session ended. One of `timeout` or `closed`. - `cd9` - whether the user was able to load the note panel or not. One of `true` or `false`. - `cd10` - provide current user state. Possible values are: 'error', 'isSyncing', 'synced', 'openLogin', 'verifyAccount', 'reconnectSync', and 'signIn'. @@ -220,5 +220,3 @@ deleted on server side. Those were deleted before v4.0.0-beta.4 (during multi-no - `ec` - `notes` - `ea` - `delete-deleted-notes` - - diff --git a/native/app/actions.js b/native/app/actions.js index 9efff382..71688f93 100644 --- a/native/app/actions.js +++ b/native/app/actions.js @@ -20,17 +20,20 @@ import { SYNC_AUTHENTICATED, import browser from './browser'; import { v4 as uuid4 } from 'uuid'; +import { trackEvent } from './utils/metrics'; import sync from './utils/sync'; export function pleaseLogin() { return { type: PLEASE_LOGIN }; } -export function kintoLoad(from) { +export function kintoLoad(origin) { // Return id to callback using promises return (dispatch, getState) => { return new Promise((resolve, reject) => { - dispatch({ type: TEXT_SYNCING, from: from }); + + dispatch({ type: TEXT_SYNCING, from: origin }); + sync.loadFromKinto(kintoClient, getState().sync.loginDetails).then(result => { if (result && result.data) { dispatch({ type: KINTO_LOADED, notes: result.data }); @@ -55,6 +58,10 @@ export function createNote(note = {}) { return (dispatch, getState) => { return new Promise((resolve, reject) => { + trackEvent('new-note', { + el: 'list-view' + }); + note.id = uuid4(); if (!note.lastModified) note.lastModified = new Date(); @@ -83,6 +90,7 @@ export function createNote(note = {}) { } export function updateNote(id, content, lastModified) { + browser.runtime.sendMessage({ action: UPDATE_NOTE, id, @@ -92,7 +100,11 @@ export function updateNote(id, content, lastModified) { return { type: UPDATE_NOTE, id, content, lastModified }; } -export function deleteNote(id) { +export function deleteNote(id, origin) { + trackEvent('new-note', { + el: origin + }); + browser.runtime.sendMessage({ action: DELETE_NOTE, id diff --git a/native/app/background.js b/native/app/background.js index 23fd4fbe..d94bc495 100644 --- a/native/app/background.js +++ b/native/app/background.js @@ -1,5 +1,6 @@ import kintoClient from './vendor/kinto-client'; import fxaUtils from './vendor/fxa-utils'; +import { trackEvent } from './utils/metrics'; import { SYNC_AUTHENTICATED, KINTO_LOADED, @@ -52,7 +53,7 @@ browser.runtime.onMessage.addListener(eventData => { store.dispatch({ type: ERROR, message: eventData.message }); break; case RECONNECT_SYNC: - console.log('Implement me (background.js RECONNECT_SYNC message)'); + trackEvent('reconnect-sync'); store.dispatch({ type: ERROR, message: 'Reconnect to Sync' }); default: break; diff --git a/native/app/components/DrawerItems.js b/native/app/components/DrawerItems.js index 944948ce..0828f50e 100644 --- a/native/app/components/DrawerItems.js +++ b/native/app/components/DrawerItems.js @@ -58,6 +58,7 @@ class DrawerItems extends React.Component { { label : 'Feedback', action : () => { + trackEvent('give-feedback'); return Linking.openURL(SURVEY_PATH); } } @@ -84,6 +85,8 @@ class DrawerItems extends React.Component { }; this._requestSync = () => { + + trackEvent('webext-button-authenticate'); props.dispatch(kintoLoad('drawer')).then(_ => { // If load succeed, we close drawer this.props.navigation.dispatch(DrawerActions.closeDrawer()); diff --git a/native/app/components/EditorPanel.js b/native/app/components/EditorPanel.js index 192c4aee..59a9cbf4 100644 --- a/native/app/components/EditorPanel.js +++ b/native/app/components/EditorPanel.js @@ -44,7 +44,7 @@ class RichTextExample extends Component { this.props.dispatch(setFocusedNote(note.id)); }); } else if (this.note && e === '') { // if we delete all caracters from a note - this.props.dispatch(deleteNote(this.note.id)); + this.props.dispatch(deleteNote(this.note.id, 'blank-note')); this.note = null; this.props.dispatch(setFocusedNote()); } else if (this.note && e !== '') { // default case, on modification we save diff --git a/native/app/components/ListPanel.js b/native/app/components/ListPanel.js index 089f0975..fef99c3e 100644 --- a/native/app/components/ListPanel.js +++ b/native/app/components/ListPanel.js @@ -11,6 +11,8 @@ import { View, FlatList, StyleSheet, RefreshControl, AppState, Animated } from ' import { COLOR_DARK_SYNC, COLOR_NOTES_BLUE, COLOR_NOTES_WHITE, KINTO_LOADED } from '../utils/constants'; import { kintoLoad, createNote } from "../actions"; import browser from '../browser'; +import { trackEvent } from '../utils/metrics'; + import ListPanelEmpty from './ListPanelEmpty'; import ListPanelLoading from './ListPanelLoading'; @@ -40,6 +42,7 @@ class ListPanel extends React.Component { } this._onRefresh = () => { + trackEvent('webext-button-authenticate'); this.setState({ refreshing: true }); props.dispatch(kintoLoad()).then(() => { this.setState({ refreshing: false }); @@ -48,9 +51,12 @@ class ListPanel extends React.Component { this._handleAppStateChange = (nextAppState) => { if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') { + trackEvent('open'); props.dispatch(kintoLoad()).then(() => { this.setState({ refreshing: false }); }); + } else { + trackEvent('close', { state: nextAppState }); } this.setState({ appState: nextAppState }); } diff --git a/native/app/components/MoreMenu.js b/native/app/components/MoreMenu.js index 8d11fd99..1a31ef15 100644 --- a/native/app/components/MoreMenu.js +++ b/native/app/components/MoreMenu.js @@ -28,7 +28,7 @@ class MoreMenu extends Component { return note.id === this.props.state.sync.focusedNoteId }); if (deletedNote) { - this.props.dispatch(deleteNote(deletedNote.id)); + this.props.dispatch(deleteNote(deletedNote.id, 'in-note')); } navigation.navigate('ListPanel', { deletedNote }); break; diff --git a/native/app/utils/metrics.js b/native/app/utils/metrics.js index a3bbbd1a..58ecab91 100644 --- a/native/app/utils/metrics.js +++ b/native/app/utils/metrics.js @@ -1,9 +1,52 @@ import { GoogleAnalyticsTracker } from "react-native-google-analytics-bridge"; +import { store } from '../store'; // See https://github.com/mozilla/notes/blob/master/docs/metrics.md for details const tracker = new GoogleAnalyticsTracker('UA-35433268-79'); const EVENT_CATEGORY = 'notes'; -export const trackEvent = (action) => { - tracker.trackEvent(EVENT_CATEGORY, action); +export const trackEvent = (action, optionalValues = {}) => { + + const state = store.getState(); + + if (action === 'changed') { + optionalValues.cd1 = state.profile.email !== null; + } + + if (action === 'close') { + // See about appState : https://facebook.github.io/react-native/docs/appstate.html + if (optionalValues.state === 'inactive') { + optionalValues.cd7 = 'appInactive'; + } else if (optionalValues.state === 'background') { + optionalValues.cd7 = 'appBackground'; + } + } + + optionalValues.cd9 = optionalValues.cd9 || 'true'; // if panel is loaded + // Generate cd10 based on footer.js rules. Same in webext. + if (state.sync && ['open', 'close', 'changed', 'drag-n-drop', 'new-note', 'export', + 'delete-note', 'give-feedback', 'limit-reached'].includes(action)) { + if (state.sync.email) { // If user is authenticated + if (state.sync.error) { + optionalValues.cd10 = 'error'; + } else if (state.sync.isSyncing) { + optionalValues.cd10 = 'isSyncing'; + } else { + optionalValues.cd10 = 'synced'; + } + } else { + if (state.sync.isOpeningLogin) { // eslint-disable-line no-lonely-if + optionalValues.cd10 = 'openLogin'; + } else if (state.sync.isPleaseLogin) { + optionalValues.cd10 = 'verifyAccount'; + } else if (state.sync.isReconnectSync) { + optionalValues.cd10 = 'reconnectSync'; + } else { + optionalValues.cd10 = 'signIn'; + } + } + } + + optionalValues.cd11 = state.notes.length; + tracker.trackEvent(EVENT_CATEGORY, action, optionalValues); }; diff --git a/native/app/utils/sync.js b/native/app/utils/sync.js index 4d0f477d..bc29deb7 100644 --- a/native/app/utils/sync.js +++ b/native/app/utils/sync.js @@ -3,6 +3,8 @@ import { } from '../actions'; import { store } from '../store'; import browser from '../browser'; +import { trackEvent } from './metrics'; +import striptags from 'striptags'; const fxaUtils = require('../vendor/fxa-utils'); const fxaCryptoRelier = require('../vendor/fxa-crypto-relier'); @@ -272,7 +274,7 @@ function syncKinto(client, loginDetails) { resolution.deleted = true; } client.conflict = true; - // sendMetrics('handle-conflict'); // eslint-disable-line no-undef + trackEvent('handle-conflict'); // eslint-disable-line no-undef } return collection.resolve(conflict, resolution); })) @@ -390,6 +392,12 @@ function saveToKinto(client, loginDetails, note) { // eslint-disable-line no-unu action: TEXT_SYNCING }); + trackEvent('changed', { + cm1: striptags(note.content).length, + cm2: (striptags(note.content.replace(/<\/p>|<\/li>/gi, '\n')).match(/\n/g) || []).length, + cm3: null, // Size of change + }); + client.conflict = false; syncDebounce = null; return syncKinto(client, loginDetails) diff --git a/native/index.js b/native/index.js index 34849406..d4453583 100644 --- a/native/index.js +++ b/native/index.js @@ -27,6 +27,8 @@ import { Toolbar, ToolbarContent, ToolbarAction, Provider as PaperProvider } fro import { COLOR_APP_BAR, COLOR_STATUS_BAR } from './app/utils/constants'; import { store, persistor } from './app/store'; +import { trackEvent } from './app/utils/metrics'; + import DrawerItems from './app/components/DrawerItems'; import EditorPanel from './app/components/EditorPanel'; import EditorPanelHeader from './app/components/EditorPanelHeader'; @@ -137,6 +139,7 @@ class Notes extends React.Component { StatusBar.setBackgroundColor('rgba(249, 249, 250, 0.3)'); StatusBar.setTranslucent(true); StatusBar.setBarStyle('dark-content'); + trackEvent('open'); } render () {