Skip to content

Commit 8dd931d

Browse files
Merge pull request #1984 from daveajrussell/preview-analytics-events
Add analytics events for browser preview
2 parents 323e84c + 39fc3e7 commit 8dd931d

File tree

7 files changed

+268
-5
lines changed

7 files changed

+268
-5
lines changed

src/browser/modules/Stream/PlayFrame.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { isConnectedAuraHost } from 'shared/modules/connections/connectionsDuck'
4747
import { getEdition, isEnterprise } from 'shared/modules/dbMeta/dbMetaDuck'
4848
import { DARK_THEME } from 'shared/modules/settings/settingsDuck'
4949
import { LAST_GUIDE_SLIDE } from 'shared/modules/udc/udcDuck'
50-
import { PreviewFrame } from './StartPreviewFrame'
50+
import PreviewFrame from './StartPreviewFrame'
5151

5252
const AuraPromotion = () => {
5353
const theme = useContext(ThemeContext)

src/browser/modules/Stream/StartPreviewFrame.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
import React from 'react'
20+
import React, { Dispatch } from 'react'
21+
import { Action } from 'redux'
22+
import { trackNavigateToPreview } from 'shared/modules/preview/previewDuck'
23+
import { connect } from 'react-redux'
24+
import { withBus } from 'react-suber'
2125

2226
export const navigateToPreview = (): void => {
2327
const path = window.location.pathname
@@ -26,7 +30,15 @@ export const navigateToPreview = (): void => {
2630
}
2731
}
2832

29-
export const PreviewFrame = () => {
33+
type PreviewFrameProps = {
34+
executeTrackNavigateToPreview: () => void
35+
}
36+
const PreviewFrame = ({ executeTrackNavigateToPreview }: PreviewFrameProps) => {
37+
function trackAndNavigateToPreview() {
38+
executeTrackNavigateToPreview()
39+
navigateToPreview()
40+
}
41+
3042
return (
3143
<>
3244
<div className="teasers">
@@ -36,7 +48,10 @@ export const PreviewFrame = () => {
3648
<p>
3749
Switch to the preview experience to access all the latest features.
3850
</p>
39-
<button onClick={navigateToPreview} className="btn btn-advertise">
51+
<button
52+
onClick={trackAndNavigateToPreview}
53+
className="btn btn-advertise"
54+
>
4055
{"Let's go"}
4156
</button>
4257
</div>
@@ -86,3 +101,11 @@ export const PreviewFrame = () => {
86101
</>
87102
)
88103
}
104+
105+
const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
106+
return {
107+
executeTrackNavigateToPreview: () => dispatch(trackNavigateToPreview())
108+
}
109+
}
110+
111+
export default withBus(connect(null, mapDispatchToProps)(PreviewFrame))

src/shared/modules/dbMeta/dbMetaEpics.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import {
9797
getCurrentDatabase
9898
} from 'shared/utils/selectors'
9999
import { isBoltConnectionErrorCode } from 'services/bolt/boltConnectionErrors'
100+
import { trackPageLoad } from '../preview/previewDuck'
100101

101102
function handleConnectionError(store: any, e: any) {
102103
if (!e.code || isBoltConnectionErrorCode(e.code)) {
@@ -546,6 +547,12 @@ export const serverConfigEpic = (some$: any, store: any) =>
546547
store.dispatch(triggerCredentialsTimeout())
547548
}
548549

550+
setTimeout(() => {
551+
// Track page load after server config is done
552+
// setTimeout ensures telemetry settings have been propagated to the App
553+
store.dispatch(trackPageLoad())
554+
})
555+
549556
return Rx.Observable.of(null)
550557
})
551558
})
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
import { createBus, createReduxMiddleware } from 'suber'
21+
import configureMockStore, { MockStoreEnhanced } from 'redux-mock-store'
22+
import {
23+
PREVIEW_EVENT,
24+
trackNavigateToPreview,
25+
trackPageLoad
26+
} from './previewDuck'
27+
28+
describe('previewDuck tests', () => {
29+
let store: MockStoreEnhanced<unknown, unknown>
30+
const bus = createBus()
31+
const mockStore = configureMockStore([createReduxMiddleware(bus)])
32+
33+
beforeAll(() => {
34+
store = mockStore()
35+
})
36+
37+
afterEach(() => {
38+
bus.reset()
39+
store.clearActions()
40+
localStorage.clear()
41+
})
42+
43+
test('trackNavigateToPreview sends a PREVIEW_EVENT', done => {
44+
const action = trackNavigateToPreview()
45+
46+
bus.take(PREVIEW_EVENT, () => {
47+
// Then
48+
const [action] = store.getActions()
49+
expect(action).toEqual({
50+
type: PREVIEW_EVENT,
51+
label: 'PREVIEW_UI_SWITCH',
52+
data: {
53+
switchedTo: 'preview',
54+
timeSinceLastSwitch: null
55+
}
56+
})
57+
done()
58+
})
59+
60+
// When
61+
store.dispatch(action)
62+
})
63+
64+
test('trackNavigateToPreview sets hasTriedPreviewUI', done => {
65+
localStorage.setItem('hasTriedPreviewUI', 'false')
66+
const action = trackNavigateToPreview()
67+
68+
bus.take(PREVIEW_EVENT, () => {
69+
// Then
70+
const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI')
71+
expect(hasTriedPreviewUI).toBe('true')
72+
done()
73+
})
74+
75+
// When
76+
store.dispatch(action)
77+
})
78+
79+
test('trackNavigateToPreview sends correct timeSinceLastSwitch when timeSinceLastSwitchMs is unset', done => {
80+
const action = trackNavigateToPreview()
81+
82+
bus.take(PREVIEW_EVENT, () => {
83+
// Then
84+
const [action] = store.getActions()
85+
expect(action.data.timeSinceLastSwitch).toBeNull()
86+
done()
87+
})
88+
89+
// When
90+
store.dispatch(action)
91+
})
92+
93+
test('trackNavigateToPreview sends correct timeSinceLastSwitch when timeSinceLastSwitchMs has been set', done => {
94+
localStorage.setItem('timeSinceLastSwitchMs', Date.now().toString())
95+
const action = trackNavigateToPreview()
96+
97+
bus.take(PREVIEW_EVENT, () => {
98+
// Then
99+
const [action] = store.getActions()
100+
expect(action.data.timeSinceLastSwitch).not.toBeNull()
101+
done()
102+
})
103+
104+
// When
105+
store.dispatch(action)
106+
})
107+
108+
test('trackPageLoad sends a PREVIEW_EVENT', done => {
109+
const action = trackPageLoad()
110+
111+
bus.take(PREVIEW_EVENT, () => {
112+
// Then
113+
const [action] = store.getActions()
114+
expect(action).toEqual({
115+
type: PREVIEW_EVENT,
116+
label: 'PREVIEW_PAGE_LOAD',
117+
data: { previewUI: false, hasTriedPreviewUI: false }
118+
})
119+
done()
120+
})
121+
122+
// When
123+
store.dispatch(action)
124+
})
125+
126+
test('trackPageLoad sends correct hasTriedPreviewUI value when flag is unset', done => {
127+
const action = trackPageLoad()
128+
129+
bus.take(PREVIEW_EVENT, () => {
130+
// Then
131+
const [action] = store.getActions()
132+
expect(action.data.hasTriedPreviewUI).toBeFalsy()
133+
done()
134+
})
135+
136+
// When
137+
store.dispatch(action)
138+
})
139+
140+
test('trackPageLoad sends correct hasTriedPreviewUI value when flag is set', done => {
141+
localStorage.setItem('hasTriedPreviewUI', 'true')
142+
const action = trackPageLoad()
143+
144+
bus.take(PREVIEW_EVENT, () => {
145+
// Then
146+
const [action] = store.getActions()
147+
expect(action.data.hasTriedPreviewUI).toBeTruthy()
148+
done()
149+
})
150+
151+
// When
152+
store.dispatch(action)
153+
})
154+
})
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
export const PREVIEW_EVENT = 'preview/PREVIEW_EVENT'
21+
22+
interface PreviewEventAction {
23+
type: typeof PREVIEW_EVENT
24+
label: string
25+
data:
26+
| {
27+
switchedTo: 'preview' | 'classic'
28+
timeSinceLastSwitch: number | null
29+
}
30+
| {
31+
previewUI: boolean
32+
hasTriedPreviewUI: boolean
33+
}
34+
}
35+
36+
export const trackNavigateToPreview = (): PreviewEventAction => {
37+
const now = Date.now()
38+
localStorage.setItem('hasTriedPreviewUI', 'true')
39+
40+
const timeSinceLastSwitchMs = localStorage.getItem('timeSinceLastSwitchMs')
41+
localStorage.setItem('timeSinceLastSwitchMs', now.toString())
42+
43+
let timeSinceLastSwitch = null
44+
if (timeSinceLastSwitchMs !== null) {
45+
timeSinceLastSwitch = now - parseInt(timeSinceLastSwitchMs)
46+
}
47+
48+
return {
49+
type: PREVIEW_EVENT,
50+
label: 'PREVIEW_UI_SWITCH',
51+
data: {
52+
switchedTo: 'preview',
53+
timeSinceLastSwitch: timeSinceLastSwitch
54+
}
55+
}
56+
}
57+
58+
export const trackPageLoad = (): PreviewEventAction => {
59+
const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI') === 'true'
60+
61+
return {
62+
type: PREVIEW_EVENT,
63+
label: 'PREVIEW_PAGE_LOAD',
64+
data: { previewUI: false, hasTriedPreviewUI }
65+
}
66+
}

src/shared/modules/udc/udcDuck.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
TRACK_CANNY_FEATURE_REQUEST
5858
} from 'shared/modules/sidebar/sidebarDuck'
5959
import cmdHelper from 'shared/services/commandInterpreterHelper'
60+
import { PREVIEW_EVENT } from '../preview/previewDuck'
6061

6162
// Action types
6263
export const NAME = 'udc'
@@ -324,3 +325,13 @@ export const trackErrorFramesEpic: Epic<Action, GlobalState> = (
324325
}
325326
})
326327
.ignoreElements()
328+
329+
export const trackPreviewEpic: Epic<Action, GlobalState> = action$ => {
330+
return action$.ofType(PREVIEW_EVENT).map((action: any) => {
331+
return metricsEvent({
332+
category: 'preview',
333+
label: action.label,
334+
data: action.data
335+
})
336+
})
337+
}

src/shared/rootEpic.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
import {
9090
trackCommandUsageEpic,
9191
trackErrorFramesEpic,
92+
trackPreviewEpic,
9293
trackReduxActionsEpic,
9394
udcStartupEpic
9495
} from './modules/udc/udcDuck'
@@ -148,5 +149,6 @@ export default combineEpics(
148149
trackReduxActionsEpic,
149150
initializeCypherEditorEpic,
150151
updateEditorSupportSchemaEpic,
151-
fetchRemoteGuideEpic
152+
fetchRemoteGuideEpic,
153+
trackPreviewEpic
152154
)

0 commit comments

Comments
 (0)