diff --git a/src/browser/modules/Stream/PlayFrame.tsx b/src/browser/modules/Stream/PlayFrame.tsx
index 52ce627ce84..247210fa0fe 100644
--- a/src/browser/modules/Stream/PlayFrame.tsx
+++ b/src/browser/modules/Stream/PlayFrame.tsx
@@ -47,7 +47,7 @@ import { isConnectedAuraHost } from 'shared/modules/connections/connectionsDuck'
import { getEdition, isEnterprise } from 'shared/modules/dbMeta/dbMetaDuck'
import { DARK_THEME } from 'shared/modules/settings/settingsDuck'
import { LAST_GUIDE_SLIDE } from 'shared/modules/udc/udcDuck'
-import { PreviewFrame } from './StartPreviewFrame'
+import PreviewFrame from './StartPreviewFrame'
const AuraPromotion = () => {
const theme = useContext(ThemeContext)
diff --git a/src/browser/modules/Stream/StartPreviewFrame.tsx b/src/browser/modules/Stream/StartPreviewFrame.tsx
index e43569948a8..7935d8b7606 100644
--- a/src/browser/modules/Stream/StartPreviewFrame.tsx
+++ b/src/browser/modules/Stream/StartPreviewFrame.tsx
@@ -17,7 +17,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-import React from 'react'
+import React, { Dispatch } from 'react'
+import { Action } from 'redux'
+import { trackNavigateToPreview } from 'shared/modules/preview/previewDuck'
+import { connect } from 'react-redux'
+import { withBus } from 'react-suber'
export const navigateToPreview = (): void => {
const path = window.location.pathname
@@ -26,7 +30,15 @@ export const navigateToPreview = (): void => {
}
}
-export const PreviewFrame = () => {
+type PreviewFrameProps = {
+ executeTrackNavigateToPreview: () => void
+}
+const PreviewFrame = ({ executeTrackNavigateToPreview }: PreviewFrameProps) => {
+ function trackAndNavigateToPreview() {
+ executeTrackNavigateToPreview()
+ navigateToPreview()
+ }
+
return (
<>
@@ -36,7 +48,10 @@ export const PreviewFrame = () => {
Switch to the preview experience to access all the latest features.
-
@@ -86,3 +101,11 @@ export const PreviewFrame = () => {
>
)
}
+
+const mapDispatchToProps = (dispatch: Dispatch) => {
+ return {
+ executeTrackNavigateToPreview: () => dispatch(trackNavigateToPreview())
+ }
+}
+
+export default withBus(connect(null, mapDispatchToProps)(PreviewFrame))
diff --git a/src/shared/modules/dbMeta/dbMetaEpics.ts b/src/shared/modules/dbMeta/dbMetaEpics.ts
index 3bfeaf7472f..c3ee5ca6750 100644
--- a/src/shared/modules/dbMeta/dbMetaEpics.ts
+++ b/src/shared/modules/dbMeta/dbMetaEpics.ts
@@ -97,6 +97,7 @@ import {
getCurrentDatabase
} from 'shared/utils/selectors'
import { isBoltConnectionErrorCode } from 'services/bolt/boltConnectionErrors'
+import { trackPageLoad } from '../preview/previewDuck'
function handleConnectionError(store: any, e: any) {
if (!e.code || isBoltConnectionErrorCode(e.code)) {
@@ -546,6 +547,12 @@ export const serverConfigEpic = (some$: any, store: any) =>
store.dispatch(triggerCredentialsTimeout())
}
+ setTimeout(() => {
+ // Track page load after server config is done
+ // setTimeout ensures telemetry settings have been propagated to the App
+ store.dispatch(trackPageLoad())
+ })
+
return Rx.Observable.of(null)
})
})
diff --git a/src/shared/modules/preview/previewDuck.test.ts b/src/shared/modules/preview/previewDuck.test.ts
new file mode 100644
index 00000000000..c86c21c145a
--- /dev/null
+++ b/src/shared/modules/preview/previewDuck.test.ts
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+import { createBus, createReduxMiddleware } from 'suber'
+import configureMockStore, { MockStoreEnhanced } from 'redux-mock-store'
+import {
+ PREVIEW_EVENT,
+ trackNavigateToPreview,
+ trackPageLoad
+} from './previewDuck'
+
+describe('previewDuck tests', () => {
+ let store: MockStoreEnhanced
+ const bus = createBus()
+ const mockStore = configureMockStore([createReduxMiddleware(bus)])
+
+ beforeAll(() => {
+ store = mockStore()
+ })
+
+ afterEach(() => {
+ bus.reset()
+ store.clearActions()
+ localStorage.clear()
+ })
+
+ test('trackNavigateToPreview sends a PREVIEW_EVENT', done => {
+ const action = trackNavigateToPreview()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action).toEqual({
+ type: PREVIEW_EVENT,
+ label: 'PREVIEW_UI_SWITCH',
+ data: {
+ switchedTo: 'preview',
+ timeSinceLastSwitch: null
+ }
+ })
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackNavigateToPreview sets hasTriedPreviewUI', done => {
+ localStorage.setItem('hasTriedPreviewUI', 'false')
+ const action = trackNavigateToPreview()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI')
+ expect(hasTriedPreviewUI).toBe('true')
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackNavigateToPreview sends correct timeSinceLastSwitch when timeSinceLastSwitchMs is unset', done => {
+ const action = trackNavigateToPreview()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action.data.timeSinceLastSwitch).toBeNull()
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackNavigateToPreview sends correct timeSinceLastSwitch when timeSinceLastSwitchMs has been set', done => {
+ localStorage.setItem('timeSinceLastSwitchMs', Date.now().toString())
+ const action = trackNavigateToPreview()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action.data.timeSinceLastSwitch).not.toBeNull()
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackPageLoad sends a PREVIEW_EVENT', done => {
+ const action = trackPageLoad()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action).toEqual({
+ type: PREVIEW_EVENT,
+ label: 'PREVIEW_PAGE_LOAD',
+ data: { previewUI: false, hasTriedPreviewUI: false }
+ })
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackPageLoad sends correct hasTriedPreviewUI value when flag is unset', done => {
+ const action = trackPageLoad()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action.data.hasTriedPreviewUI).toBeFalsy()
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+
+ test('trackPageLoad sends correct hasTriedPreviewUI value when flag is set', done => {
+ localStorage.setItem('hasTriedPreviewUI', 'true')
+ const action = trackPageLoad()
+
+ bus.take(PREVIEW_EVENT, () => {
+ // Then
+ const [action] = store.getActions()
+ expect(action.data.hasTriedPreviewUI).toBeTruthy()
+ done()
+ })
+
+ // When
+ store.dispatch(action)
+ })
+})
diff --git a/src/shared/modules/preview/previewDuck.ts b/src/shared/modules/preview/previewDuck.ts
new file mode 100644
index 00000000000..ce828d88010
--- /dev/null
+++ b/src/shared/modules/preview/previewDuck.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Neo4j is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+export const PREVIEW_EVENT = 'preview/PREVIEW_EVENT'
+
+interface PreviewEventAction {
+ type: typeof PREVIEW_EVENT
+ label: string
+ data:
+ | {
+ switchedTo: 'preview' | 'classic'
+ timeSinceLastSwitch: number | null
+ }
+ | {
+ previewUI: boolean
+ hasTriedPreviewUI: boolean
+ }
+}
+
+export const trackNavigateToPreview = (): PreviewEventAction => {
+ const now = Date.now()
+ localStorage.setItem('hasTriedPreviewUI', 'true')
+
+ const timeSinceLastSwitchMs = localStorage.getItem('timeSinceLastSwitchMs')
+ localStorage.setItem('timeSinceLastSwitchMs', now.toString())
+
+ let timeSinceLastSwitch = null
+ if (timeSinceLastSwitchMs !== null) {
+ timeSinceLastSwitch = now - parseInt(timeSinceLastSwitchMs)
+ }
+
+ return {
+ type: PREVIEW_EVENT,
+ label: 'PREVIEW_UI_SWITCH',
+ data: {
+ switchedTo: 'preview',
+ timeSinceLastSwitch: timeSinceLastSwitch
+ }
+ }
+}
+
+export const trackPageLoad = (): PreviewEventAction => {
+ const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI') === 'true'
+
+ return {
+ type: PREVIEW_EVENT,
+ label: 'PREVIEW_PAGE_LOAD',
+ data: { previewUI: false, hasTriedPreviewUI }
+ }
+}
diff --git a/src/shared/modules/udc/udcDuck.ts b/src/shared/modules/udc/udcDuck.ts
index a0827ac90a1..ac84f6eaf63 100644
--- a/src/shared/modules/udc/udcDuck.ts
+++ b/src/shared/modules/udc/udcDuck.ts
@@ -57,6 +57,7 @@ import {
TRACK_CANNY_FEATURE_REQUEST
} from 'shared/modules/sidebar/sidebarDuck'
import cmdHelper from 'shared/services/commandInterpreterHelper'
+import { PREVIEW_EVENT } from '../preview/previewDuck'
// Action types
export const NAME = 'udc'
@@ -324,3 +325,13 @@ export const trackErrorFramesEpic: Epic = (
}
})
.ignoreElements()
+
+export const trackPreviewEpic: Epic = action$ => {
+ return action$.ofType(PREVIEW_EVENT).map((action: any) => {
+ return metricsEvent({
+ category: 'preview',
+ label: action.label,
+ data: action.data
+ })
+ })
+}
diff --git a/src/shared/rootEpic.ts b/src/shared/rootEpic.ts
index e7e4f9a9d49..2b375b6e55f 100644
--- a/src/shared/rootEpic.ts
+++ b/src/shared/rootEpic.ts
@@ -89,6 +89,7 @@ import {
import {
trackCommandUsageEpic,
trackErrorFramesEpic,
+ trackPreviewEpic,
trackReduxActionsEpic,
udcStartupEpic
} from './modules/udc/udcDuck'
@@ -148,5 +149,6 @@ export default combineEpics(
trackReduxActionsEpic,
initializeCypherEditorEpic,
updateEditorSupportSchemaEpic,
- fetchRemoteGuideEpic
+ fetchRemoteGuideEpic,
+ trackPreviewEpic
)