Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analytics events for browser preview #1984

Merged
merged 7 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/browser/modules/Stream/PlayFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 26 additions & 3 deletions src/browser/modules/Stream/StartPreviewFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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
Expand All @@ -26,7 +30,15 @@ export const navigateToPreview = (): void => {
}
}

export const PreviewFrame = () => {
type PreviewFrameProps = {
executeTrackNavigateToPreview: () => void
}
const PreviewFrame = ({ executeTrackNavigateToPreview }: PreviewFrameProps) => {
function trackAndNavigateToPreview() {
executeTrackNavigateToPreview()
navigateToPreview()
}

return (
<>
<div className="teasers">
Expand All @@ -36,7 +48,10 @@ export const PreviewFrame = () => {
<p>
Switch to the preview experience to access all the latest features.
</p>
<button onClick={navigateToPreview} className="btn btn-advertise">
<button
onClick={trackAndNavigateToPreview}
className="btn btn-advertise"
>
{"Let's go"}
</button>
</div>
Expand Down Expand Up @@ -86,3 +101,11 @@ export const PreviewFrame = () => {
</>
)
}

const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
return {
executeTrackNavigateToPreview: () => dispatch(trackNavigateToPreview())
}
}

export default withBus(connect(null, mapDispatchToProps)(PreviewFrame))
7 changes: 7 additions & 0 deletions src/shared/modules/dbMeta/dbMetaEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -546,6 +547,12 @@ export const serverConfigEpic = (some$: any, store: any) =>
store.dispatch(triggerCredentialsTimeout())
}

setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting that it works even with a timeout of 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed - it's where I start to get a bit iffy with the eventloop/callback queue - but I guess the callback just going to the back of the queue is enough for react's internals to finish and resolve props in the app

// 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)
})
})
Expand Down
154 changes: 154 additions & 0 deletions src/shared/modules/preview/previewDuck.test.ts
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<unknown, unknown>
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)
})
})
66 changes: 66 additions & 0 deletions src/shared/modules/preview/previewDuck.ts
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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 }
}
}
11 changes: 11 additions & 0 deletions src/shared/modules/udc/udcDuck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -324,3 +325,13 @@ export const trackErrorFramesEpic: Epic<Action, GlobalState> = (
}
})
.ignoreElements()

export const trackPreviewEpic: Epic<Action, GlobalState> = action$ => {
return action$.ofType(PREVIEW_EVENT).map((action: any) => {
return metricsEvent({
category: 'preview',
label: action.label,
data: action.data
})
})
}
Comment on lines +329 to +337
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding this epic for tracking preview events here so that we can consume them in the existing subscriber in the App and dispatch to segment

4 changes: 3 additions & 1 deletion src/shared/rootEpic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import {
import {
trackCommandUsageEpic,
trackErrorFramesEpic,
trackPreviewEpic,
trackReduxActionsEpic,
udcStartupEpic
} from './modules/udc/udcDuck'
Expand Down Expand Up @@ -148,5 +149,6 @@ export default combineEpics(
trackReduxActionsEpic,
initializeCypherEditorEpic,
updateEditorSupportSchemaEpic,
fetchRemoteGuideEpic
fetchRemoteGuideEpic,
trackPreviewEpic
)
Loading