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 5 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
6 changes: 6 additions & 0 deletions src/browser/modules/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import {
updateUdcData
} from 'shared/modules/udc/udcDuck'
import { getTelemetrySettings } from 'shared/utils/selectors'
import { trackPageLoad } from 'shared/modules/preview/previewDuck'

export const MAIN_WRAPPER_DOM_ID = 'MAIN_WRAPPER_DOM_ID'

Expand Down Expand Up @@ -149,6 +150,11 @@ export function App(props: any) {
props.bus && props.bus.send(initAction.type, initAction)
}, [props.bus])

useEffect(() => {
const pageLoadAction = trackPageLoad()
props.bus && props.bus.send(pageLoadAction.type, pageLoadAction)
}, [props.bus, props.telemetrySettings.allowUserStats])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it shouldn't be a problem, but I still worry a little about this effect running extra times and giving us bad stats. I think we could be certain to avoid that if we run dispatch in dbMetaEpics instead, in the section where the server configuration finishes. There's a comment: // side-effects where it'd fit well

Copy link
Contributor Author

@daveajrussell daveajrussell Oct 31, 2024

Choose a reason for hiding this comment

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

thanks! I'll take a look

it seems that when dispatching from the dbMetaEpic, props.telemetrySettings.allowUserStats in App.tsx is still false :(

I'll see what we can do here


const {
browserSyncAuthStatus,
browserSyncConfig,
Expand Down
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
33 changes: 30 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,15 @@ export const PreviewFrame = () => {
</>
)
}

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

export default withBus(
connect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can just pass null here instead of a placeholder function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that works, thanks

return {}
}, mapDispatchToProps)(PreviewFrame)
)
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: '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: '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
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 previewDuck module to create redux actions for our preview switch and page load events, containing logic for setting properties of each action

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: 'ui-switch',
data: {
switchedTo: 'preview',
timeSinceLastSwitch: timeSinceLastSwitch
}
}
}

export const trackPageLoad = (): PreviewEventAction => {
const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI') === 'true'

return {
type: PREVIEW_EVENT,
label: '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