From a3386df56da5cca3e2f511f20e1409e4336976f7 Mon Sep 17 00:00:00 2001 From: David Russell Date: Fri, 25 Oct 2024 15:31:08 +0100 Subject: [PATCH 1/7] add analytics events for preview --- src/browser/modules/App/App.tsx | 14 +++++ src/browser/modules/Stream/PlayFrame.tsx | 22 ++++++-- .../modules/Stream/StartPreviewFrame.tsx | 56 ++++++++++++++++++- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/browser/modules/App/App.tsx b/src/browser/modules/App/App.tsx index 8e81fac46c6..611726acb46 100644 --- a/src/browser/modules/App/App.tsx +++ b/src/browser/modules/App/App.tsx @@ -149,6 +149,20 @@ export function App(props: any) { props.bus && props.bus.send(initAction.type, initAction) }, [props.bus]) + useEffect(() => { + if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { + const hasTriedPreviewUI = + localStorage.getItem('hasTriedPreviewUI') === 'true' + segmentTrackCallback && + segmentTrackCallback.current && + segmentTrackCallback.current({ + category: 'preview', + label: 'PREVIEW_PAGE_LOAD', + data: { previewUI: false, hasTriedPreviewUI } + }) + } + }, [props.telemetrySettings.allowUserStats]) + const { browserSyncAuthStatus, browserSyncConfig, diff --git a/src/browser/modules/Stream/PlayFrame.tsx b/src/browser/modules/Stream/PlayFrame.tsx index 52ce627ce84..560d6db1739 100644 --- a/src/browser/modules/Stream/PlayFrame.tsx +++ b/src/browser/modules/Stream/PlayFrame.tsx @@ -48,6 +48,7 @@ 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 { getTelemetrySettings, TelemetrySettings } from 'shared/utils/selectors' const AuraPromotion = () => { const theme = useContext(ThemeContext) @@ -89,13 +90,15 @@ type PlayFrameProps = { showPromotion: boolean isFullscreen: boolean isCollapsed: boolean + telemetrySettings: TelemetrySettings } export function PlayFrame({ stack, bus, showPromotion, isFullscreen, - isCollapsed + isCollapsed, + telemetrySettings }: PlayFrameProps): JSX.Element { const [stackIndex, setStackIndex] = useState(0) const [atSlideStart, setAtSlideStart] = useState(null) @@ -124,7 +127,8 @@ export function PlayFrame({ bus, onSlide, initialPlay, - showPromotion + showPromotion, + telemetrySettings ) if (stillMounted) { setInitialPlay(false) @@ -207,7 +211,8 @@ function generateContent( bus: Bus, onSlide: any, shouldUseSlidePointer: boolean, - showPromotion = false + showPromotion = false, + telemetrySettings: TelemetrySettings ): Content | Promise { // Not found if (stackFrame.response && stackFrame.response.status === 404) { @@ -296,11 +301,15 @@ function generateContent( const updatedContent = isPlayStart && showPromotion ? ( <> - {isPreviewAvailable ? : content} + {isPreviewAvailable ? ( + + ) : ( + content + )} ) : isPreviewAvailable ? ( - + ) : ( content ) @@ -383,7 +392,8 @@ const mapStateToProps = (state: GlobalState) => ({ (getEdition(state) !== null && !isEnterprise(state) && !isConnectedAuraHost(state)) || - inDesktop(state) + inDesktop(state), + telemetrySettings: getTelemetrySettings(state) }) export default connect(mapStateToProps)(withBus(PlayFrame)) diff --git a/src/browser/modules/Stream/StartPreviewFrame.tsx b/src/browser/modules/Stream/StartPreviewFrame.tsx index e43569948a8..ae3c36d01a2 100644 --- a/src/browser/modules/Stream/StartPreviewFrame.tsx +++ b/src/browser/modules/Stream/StartPreviewFrame.tsx @@ -17,7 +17,10 @@ * 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, { useRef } from 'react' +import { isRunningE2ETest } from 'services/utils' +import { TelemetrySettings } from 'shared/utils/selectors' +import { MetricsData } from '../Segment' export const navigateToPreview = (): void => { const path = window.location.pathname @@ -26,7 +29,51 @@ export const navigateToPreview = (): void => { } } -export const PreviewFrame = () => { +const useTrackAndNavigateToPreview = ( + telemetrySettings: TelemetrySettings +): (() => void) => { + const segmentTrackCallback = useRef((_: MetricsData) => _) + const path = window.location.pathname + + return () => { + if (!path.endsWith('/preview/')) { + if (!isRunningE2ETest() && telemetrySettings.allowUserStats) { + const now = Date.now() + localStorage.setItem('hasTriedPreviewUI', 'true') + + const timeSinceLastSwitchMs = + localStorage.getItem('timeSinceLastSwitchMs') ?? null + localStorage.setItem('timeSinceLastSwitchMs', now.toString()) + + let timeSinceLastSwitch = null + if (timeSinceLastSwitchMs !== null) { + timeSinceLastSwitch = now - parseInt(timeSinceLastSwitchMs) + } + + segmentTrackCallback && + segmentTrackCallback.current && + segmentTrackCallback.current({ + category: 'preview', + label: 'PREVIEW_UI_SWITCH', + data: { + switchedTo: 'preview', + timeSinceLastSwitch: timeSinceLastSwitch ?? 0 + } + }) + } + + navigateToPreview() + } + } +} + +type PreviewFrameProps = { + telemetrySettings: TelemetrySettings +} +export const PreviewFrame = ({ telemetrySettings }: PreviewFrameProps) => { + const trackAndNavigateToPreview = + useTrackAndNavigateToPreview(telemetrySettings) + return ( <>
@@ -36,7 +83,10 @@ export const PreviewFrame = () => {

Switch to the preview experience to access all the latest features.

-
From 83eb87fb55853a503acc2a3bd78c71633b35c508 Mon Sep 17 00:00:00 2001 From: David Russell Date: Wed, 30 Oct 2024 16:57:39 +0000 Subject: [PATCH 2/7] refactor preview events to use redux --- src/browser/modules/App/App.tsx | 77 +++++---- src/browser/modules/Stream/PlayFrame.tsx | 24 +-- .../modules/Stream/StartPreviewFrame.tsx | 69 +++----- .../modules/preview/previewDuck.test.ts | 156 ++++++++++++++++++ src/shared/modules/preview/previewDuck.ts | 74 +++++++++ 5 files changed, 304 insertions(+), 96 deletions(-) create mode 100644 src/shared/modules/preview/previewDuck.test.ts create mode 100644 src/shared/modules/preview/previewDuck.ts diff --git a/src/browser/modules/App/App.tsx b/src/browser/modules/App/App.tsx index 611726acb46..1d07c5e67a2 100644 --- a/src/browser/modules/App/App.tsx +++ b/src/browser/modules/App/App.tsx @@ -18,7 +18,7 @@ * along with this program. If not, see . */ import { setEditorTheme } from 'neo4j-arc/cypher-language-support' -import React, { useEffect, useRef } from 'react' +import React, { useCallback, useEffect, useRef } from 'react' import { connect } from 'react-redux' import { withBus } from 'react-suber' import { ThemeProvider } from 'styled-components' @@ -96,6 +96,10 @@ import { updateUdcData } from 'shared/modules/udc/udcDuck' import { getTelemetrySettings } from 'shared/utils/selectors' +import { + PREVIEW_EVENT, + trackPageLoad +} from 'shared/modules/preview/previewDuck' export const MAIN_WRAPPER_DOM_ID = 'MAIN_WRAPPER_DOM_ID' @@ -119,30 +123,45 @@ export function App(props: any) { const eventMetricsCallback = useRef((_: MetricsData) => _) const segmentTrackCallback = useRef((_: MetricsData) => _) + const invokeTrackingCallbacks = useCallback( + ({ category, label, data }: MetricsData) => { + if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { + const extendedData = { + browserVersion: version, + neo4jEdition: props.edition, + connectedTo: props.connectedTo, + ...data + } + + eventMetricsCallback && + eventMetricsCallback.current && + eventMetricsCallback.current({ category, label, data: extendedData }) + + segmentTrackCallback && + segmentTrackCallback.current && + segmentTrackCallback.current({ category, label, data: extendedData }) + } + }, + [props.telemetrySettings.allowUserStats] + ) + useEffect(() => { const unsub = props.bus && - props.bus.take( - METRICS_EVENT, - ({ category, label, data: originalData }: MetricsData) => { - if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { - const data = { - browserVersion: version, - neo4jEdition: props.edition, - connectedTo: props.connectedTo, - ...originalData - } - eventMetricsCallback && - eventMetricsCallback.current && - eventMetricsCallback.current({ category, label, data }) - segmentTrackCallback && - segmentTrackCallback.current && - segmentTrackCallback.current({ category, label, data }) - } - } - ) + props.bus.take(METRICS_EVENT, (metricsData: MetricsData) => { + invokeTrackingCallbacks(metricsData) + }) return () => unsub && unsub() - }, [props.telemetrySettings.allowUserStats, props.bus]) + }, [props.bus, invokeTrackingCallbacks]) + + useEffect(() => { + const unsub = + props.bus && + props.bus.take(PREVIEW_EVENT, (metricsData: MetricsData) => { + invokeTrackingCallbacks(metricsData) + }) + return () => unsub && unsub() + }, [props.bus, invokeTrackingCallbacks]) useEffect(() => { const initAction = udcInit() @@ -150,18 +169,9 @@ export function App(props: any) { }, [props.bus]) useEffect(() => { - if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { - const hasTriedPreviewUI = - localStorage.getItem('hasTriedPreviewUI') === 'true' - segmentTrackCallback && - segmentTrackCallback.current && - segmentTrackCallback.current({ - category: 'preview', - label: 'PREVIEW_PAGE_LOAD', - data: { previewUI: false, hasTriedPreviewUI } - }) - } - }, [props.telemetrySettings.allowUserStats]) + const pageLoadAction = trackPageLoad() + props.bus && props.bus.send(pageLoadAction.type, pageLoadAction) + }, [props.bus, props.telemetrySettings.allowUserStats]) const { browserSyncAuthStatus, @@ -195,6 +205,7 @@ export function App(props: any) { }, [titleString]) const wrapperClassNames = codeFontLigatures ? '' : 'disable-font-ligatures' + return ( { const theme = useContext(ThemeContext) @@ -90,15 +89,13 @@ type PlayFrameProps = { showPromotion: boolean isFullscreen: boolean isCollapsed: boolean - telemetrySettings: TelemetrySettings } export function PlayFrame({ stack, bus, showPromotion, isFullscreen, - isCollapsed, - telemetrySettings + isCollapsed }: PlayFrameProps): JSX.Element { const [stackIndex, setStackIndex] = useState(0) const [atSlideStart, setAtSlideStart] = useState(null) @@ -127,8 +124,7 @@ export function PlayFrame({ bus, onSlide, initialPlay, - showPromotion, - telemetrySettings + showPromotion ) if (stillMounted) { setInitialPlay(false) @@ -211,8 +207,7 @@ function generateContent( bus: Bus, onSlide: any, shouldUseSlidePointer: boolean, - showPromotion = false, - telemetrySettings: TelemetrySettings + showPromotion = false ): Content | Promise { // Not found if (stackFrame.response && stackFrame.response.status === 404) { @@ -301,15 +296,11 @@ function generateContent( const updatedContent = isPlayStart && showPromotion ? ( <> - {isPreviewAvailable ? ( - - ) : ( - content - )} + {isPreviewAvailable ? : content} ) : isPreviewAvailable ? ( - + ) : ( content ) @@ -392,8 +383,7 @@ const mapStateToProps = (state: GlobalState) => ({ (getEdition(state) !== null && !isEnterprise(state) && !isConnectedAuraHost(state)) || - inDesktop(state), - telemetrySettings: getTelemetrySettings(state) + inDesktop(state) }) export default connect(mapStateToProps)(withBus(PlayFrame)) diff --git a/src/browser/modules/Stream/StartPreviewFrame.tsx b/src/browser/modules/Stream/StartPreviewFrame.tsx index ae3c36d01a2..904d8ff2709 100644 --- a/src/browser/modules/Stream/StartPreviewFrame.tsx +++ b/src/browser/modules/Stream/StartPreviewFrame.tsx @@ -17,10 +17,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import React, { useRef } from 'react' -import { isRunningE2ETest } from 'services/utils' -import { TelemetrySettings } from 'shared/utils/selectors' -import { MetricsData } from '../Segment' +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 @@ -29,50 +30,14 @@ export const navigateToPreview = (): void => { } } -const useTrackAndNavigateToPreview = ( - telemetrySettings: TelemetrySettings -): (() => void) => { - const segmentTrackCallback = useRef((_: MetricsData) => _) - const path = window.location.pathname - - return () => { - if (!path.endsWith('/preview/')) { - if (!isRunningE2ETest() && telemetrySettings.allowUserStats) { - const now = Date.now() - localStorage.setItem('hasTriedPreviewUI', 'true') - - const timeSinceLastSwitchMs = - localStorage.getItem('timeSinceLastSwitchMs') ?? null - localStorage.setItem('timeSinceLastSwitchMs', now.toString()) - - let timeSinceLastSwitch = null - if (timeSinceLastSwitchMs !== null) { - timeSinceLastSwitch = now - parseInt(timeSinceLastSwitchMs) - } - - segmentTrackCallback && - segmentTrackCallback.current && - segmentTrackCallback.current({ - category: 'preview', - label: 'PREVIEW_UI_SWITCH', - data: { - switchedTo: 'preview', - timeSinceLastSwitch: timeSinceLastSwitch ?? 0 - } - }) - } - - navigateToPreview() - } - } -} - type PreviewFrameProps = { - telemetrySettings: TelemetrySettings + executeTrackNavigateToPreview: () => void } -export const PreviewFrame = ({ telemetrySettings }: PreviewFrameProps) => { - const trackAndNavigateToPreview = - useTrackAndNavigateToPreview(telemetrySettings) +const PreviewFrame = ({ executeTrackNavigateToPreview }: PreviewFrameProps) => { + function trackAndNavigateToPreview() { + executeTrackNavigateToPreview() + navigateToPreview() + } return ( <> @@ -136,3 +101,15 @@ export const PreviewFrame = ({ telemetrySettings }: PreviewFrameProps) => { ) } + +const mapDispatchToProps = (dispatch: Dispatch) => { + return { + executeTrackNavigateToPreview: () => dispatch(trackNavigateToPreview()) + } +} + +export default withBus( + connect(() => { + return {} + }, mapDispatchToProps)(PreviewFrame) +) diff --git a/src/shared/modules/preview/previewDuck.test.ts b/src/shared/modules/preview/previewDuck.test.ts new file mode 100644 index 00000000000..a70f01f82d4 --- /dev/null +++ b/src/shared/modules/preview/previewDuck.test.ts @@ -0,0 +1,156 @@ +/* + * 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, + category: 'preview', + 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, + category: 'preview', + 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) + }) +}) diff --git a/src/shared/modules/preview/previewDuck.ts b/src/shared/modules/preview/previewDuck.ts new file mode 100644 index 00000000000..d420a46ba3a --- /dev/null +++ b/src/shared/modules/preview/previewDuck.ts @@ -0,0 +1,74 @@ +/* + * 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 PreviewUiSwitchAction { + type: typeof PREVIEW_EVENT + category: 'preview' + label: string + data: { + switchedTo: 'preview' | 'classic' + timeSinceLastSwitch: number | null + } +} + +interface PreviewPageLoadAction { + type: typeof PREVIEW_EVENT + category: 'preview' + label: string + data: { + previewUI: boolean + hasTriedPreviewUI: boolean + } +} + +export const trackNavigateToPreview = (): PreviewUiSwitchAction => { + 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, + category: 'preview', + label: 'ui-switch', + data: { + switchedTo: 'preview', + timeSinceLastSwitch: timeSinceLastSwitch + } + } +} + +export const trackPageLoad = (): PreviewPageLoadAction => { + const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI') === 'true' + + return { + type: PREVIEW_EVENT, + category: 'preview', + label: 'page-load', + data: { previewUI: false, hasTriedPreviewUI } + } +} From 9d212e79097ba9281a210cdb1dbecc5b55c9488e Mon Sep 17 00:00:00 2001 From: David Russell Date: Wed, 30 Oct 2024 17:14:05 +0000 Subject: [PATCH 3/7] revert changes in app and move to udcduck --- src/browser/modules/App/App.tsx | 67 ++++++++++--------------------- src/shared/modules/udc/udcDuck.ts | 11 +++++ src/shared/rootEpic.ts | 4 +- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/browser/modules/App/App.tsx b/src/browser/modules/App/App.tsx index 1d07c5e67a2..8e81fac46c6 100644 --- a/src/browser/modules/App/App.tsx +++ b/src/browser/modules/App/App.tsx @@ -18,7 +18,7 @@ * along with this program. If not, see . */ import { setEditorTheme } from 'neo4j-arc/cypher-language-support' -import React, { useCallback, useEffect, useRef } from 'react' +import React, { useEffect, useRef } from 'react' import { connect } from 'react-redux' import { withBus } from 'react-suber' import { ThemeProvider } from 'styled-components' @@ -96,10 +96,6 @@ import { updateUdcData } from 'shared/modules/udc/udcDuck' import { getTelemetrySettings } from 'shared/utils/selectors' -import { - PREVIEW_EVENT, - trackPageLoad -} from 'shared/modules/preview/previewDuck' export const MAIN_WRAPPER_DOM_ID = 'MAIN_WRAPPER_DOM_ID' @@ -123,56 +119,36 @@ export function App(props: any) { const eventMetricsCallback = useRef((_: MetricsData) => _) const segmentTrackCallback = useRef((_: MetricsData) => _) - const invokeTrackingCallbacks = useCallback( - ({ category, label, data }: MetricsData) => { - if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { - const extendedData = { - browserVersion: version, - neo4jEdition: props.edition, - connectedTo: props.connectedTo, - ...data - } - - eventMetricsCallback && - eventMetricsCallback.current && - eventMetricsCallback.current({ category, label, data: extendedData }) - - segmentTrackCallback && - segmentTrackCallback.current && - segmentTrackCallback.current({ category, label, data: extendedData }) - } - }, - [props.telemetrySettings.allowUserStats] - ) - - useEffect(() => { - const unsub = - props.bus && - props.bus.take(METRICS_EVENT, (metricsData: MetricsData) => { - invokeTrackingCallbacks(metricsData) - }) - return () => unsub && unsub() - }, [props.bus, invokeTrackingCallbacks]) - useEffect(() => { const unsub = props.bus && - props.bus.take(PREVIEW_EVENT, (metricsData: MetricsData) => { - invokeTrackingCallbacks(metricsData) - }) + props.bus.take( + METRICS_EVENT, + ({ category, label, data: originalData }: MetricsData) => { + if (!isRunningE2ETest() && props.telemetrySettings.allowUserStats) { + const data = { + browserVersion: version, + neo4jEdition: props.edition, + connectedTo: props.connectedTo, + ...originalData + } + eventMetricsCallback && + eventMetricsCallback.current && + eventMetricsCallback.current({ category, label, data }) + segmentTrackCallback && + segmentTrackCallback.current && + segmentTrackCallback.current({ category, label, data }) + } + } + ) return () => unsub && unsub() - }, [props.bus, invokeTrackingCallbacks]) + }, [props.telemetrySettings.allowUserStats, props.bus]) useEffect(() => { const initAction = udcInit() 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]) - const { browserSyncAuthStatus, browserSyncConfig, @@ -205,7 +181,6 @@ export function App(props: any) { }, [titleString]) const wrapperClassNames = codeFontLigatures ? '' : 'disable-font-ligatures' - return ( = ( } }) .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 ) From 3ce6ec8151fb24f4a1424ba1f82a4cf7c93de097 Mon Sep 17 00:00:00 2001 From: David Russell Date: Wed, 30 Oct 2024 17:23:20 +0000 Subject: [PATCH 4/7] refactor previewduck --- .../modules/preview/previewDuck.test.ts | 2 -- src/shared/modules/preview/previewDuck.ts | 32 +++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/shared/modules/preview/previewDuck.test.ts b/src/shared/modules/preview/previewDuck.test.ts index a70f01f82d4..33f09caee11 100644 --- a/src/shared/modules/preview/previewDuck.test.ts +++ b/src/shared/modules/preview/previewDuck.test.ts @@ -48,7 +48,6 @@ describe('previewDuck tests', () => { const [action] = store.getActions() expect(action).toEqual({ type: PREVIEW_EVENT, - category: 'preview', label: 'ui-switch', data: { switchedTo: 'preview', @@ -114,7 +113,6 @@ describe('previewDuck tests', () => { const [action] = store.getActions() expect(action).toEqual({ type: PREVIEW_EVENT, - category: 'preview', label: 'page-load', data: { previewUI: false, hasTriedPreviewUI: false } }) diff --git a/src/shared/modules/preview/previewDuck.ts b/src/shared/modules/preview/previewDuck.ts index d420a46ba3a..9758b8adbc4 100644 --- a/src/shared/modules/preview/previewDuck.ts +++ b/src/shared/modules/preview/previewDuck.ts @@ -19,27 +19,21 @@ */ export const PREVIEW_EVENT = 'preview/PREVIEW_EVENT' -interface PreviewUiSwitchAction { +interface PreviewEventAction { type: typeof PREVIEW_EVENT - category: 'preview' label: string - data: { - switchedTo: 'preview' | 'classic' - timeSinceLastSwitch: number | null - } -} - -interface PreviewPageLoadAction { - type: typeof PREVIEW_EVENT - category: 'preview' - label: string - data: { - previewUI: boolean - hasTriedPreviewUI: boolean - } + data: + | { + switchedTo: 'preview' | 'classic' + timeSinceLastSwitch: number | null + } + | { + previewUI: boolean + hasTriedPreviewUI: boolean + } } -export const trackNavigateToPreview = (): PreviewUiSwitchAction => { +export const trackNavigateToPreview = (): PreviewEventAction => { const now = Date.now() localStorage.setItem('hasTriedPreviewUI', 'true') @@ -53,7 +47,6 @@ export const trackNavigateToPreview = (): PreviewUiSwitchAction => { return { type: PREVIEW_EVENT, - category: 'preview', label: 'ui-switch', data: { switchedTo: 'preview', @@ -62,12 +55,11 @@ export const trackNavigateToPreview = (): PreviewUiSwitchAction => { } } -export const trackPageLoad = (): PreviewPageLoadAction => { +export const trackPageLoad = (): PreviewEventAction => { const hasTriedPreviewUI = localStorage.getItem('hasTriedPreviewUI') === 'true' return { type: PREVIEW_EVENT, - category: 'preview', label: 'page-load', data: { previewUI: false, hasTriedPreviewUI } } From 924814e8c33dab125913862898d683ede022c348 Mon Sep 17 00:00:00 2001 From: David Russell Date: Wed, 30 Oct 2024 17:41:17 +0000 Subject: [PATCH 5/7] add pageload effecthook to app --- src/browser/modules/App/App.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browser/modules/App/App.tsx b/src/browser/modules/App/App.tsx index 8e81fac46c6..98dbf7a9aa7 100644 --- a/src/browser/modules/App/App.tsx +++ b/src/browser/modules/App/App.tsx @@ -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' @@ -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]) + const { browserSyncAuthStatus, browserSyncConfig, From e9a0971c7dded045b6b19c4afca2be48ce42aa4e Mon Sep 17 00:00:00 2001 From: David Russell Date: Fri, 1 Nov 2024 09:47:01 +0000 Subject: [PATCH 6/7] move trackpageload call to dbmetaepics --- src/browser/modules/App/App.tsx | 6 ------ src/shared/modules/dbMeta/dbMetaEpics.ts | 7 +++++++ src/shared/modules/preview/previewDuck.test.ts | 4 ++-- src/shared/modules/preview/previewDuck.ts | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/browser/modules/App/App.tsx b/src/browser/modules/App/App.tsx index 98dbf7a9aa7..8e81fac46c6 100644 --- a/src/browser/modules/App/App.tsx +++ b/src/browser/modules/App/App.tsx @@ -96,7 +96,6 @@ 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' @@ -150,11 +149,6 @@ 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]) - const { browserSyncAuthStatus, browserSyncConfig, 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 index 33f09caee11..c86c21c145a 100644 --- a/src/shared/modules/preview/previewDuck.test.ts +++ b/src/shared/modules/preview/previewDuck.test.ts @@ -48,7 +48,7 @@ describe('previewDuck tests', () => { const [action] = store.getActions() expect(action).toEqual({ type: PREVIEW_EVENT, - label: 'ui-switch', + label: 'PREVIEW_UI_SWITCH', data: { switchedTo: 'preview', timeSinceLastSwitch: null @@ -113,7 +113,7 @@ describe('previewDuck tests', () => { const [action] = store.getActions() expect(action).toEqual({ type: PREVIEW_EVENT, - label: 'page-load', + label: 'PREVIEW_PAGE_LOAD', data: { previewUI: false, hasTriedPreviewUI: false } }) done() diff --git a/src/shared/modules/preview/previewDuck.ts b/src/shared/modules/preview/previewDuck.ts index 9758b8adbc4..ce828d88010 100644 --- a/src/shared/modules/preview/previewDuck.ts +++ b/src/shared/modules/preview/previewDuck.ts @@ -47,7 +47,7 @@ export const trackNavigateToPreview = (): PreviewEventAction => { return { type: PREVIEW_EVENT, - label: 'ui-switch', + label: 'PREVIEW_UI_SWITCH', data: { switchedTo: 'preview', timeSinceLastSwitch: timeSinceLastSwitch @@ -60,7 +60,7 @@ export const trackPageLoad = (): PreviewEventAction => { return { type: PREVIEW_EVENT, - label: 'page-load', + label: 'PREVIEW_PAGE_LOAD', data: { previewUI: false, hasTriedPreviewUI } } } From 39fc3e7c9981a6eedd4d7a203e8b86e73587ad36 Mon Sep 17 00:00:00 2001 From: David Russell Date: Fri, 1 Nov 2024 12:49:43 +0000 Subject: [PATCH 7/7] remove placeholder fn, replace with null --- src/browser/modules/Stream/StartPreviewFrame.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/browser/modules/Stream/StartPreviewFrame.tsx b/src/browser/modules/Stream/StartPreviewFrame.tsx index 904d8ff2709..7935d8b7606 100644 --- a/src/browser/modules/Stream/StartPreviewFrame.tsx +++ b/src/browser/modules/Stream/StartPreviewFrame.tsx @@ -108,8 +108,4 @@ const mapDispatchToProps = (dispatch: Dispatch) => { } } -export default withBus( - connect(() => { - return {} - }, mapDispatchToProps)(PreviewFrame) -) +export default withBus(connect(null, mapDispatchToProps)(PreviewFrame))