diff --git a/frontend/src/components/ExternalLink.tsx b/frontend/src/components/ExternalLink.tsx index a76cc72b4a..1249e56de0 100644 --- a/frontend/src/components/ExternalLink.tsx +++ b/frontend/src/components/ExternalLink.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import { Button } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; -import { fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { LinkTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; type ExternalLinkProps = { text: string; @@ -14,7 +15,10 @@ const ExternalLink: React.FC = ({ text, to }) => ( isInline onClick={() => { window.open(to); - fireTrackingEventRaw('ExternalLink Clicked', { href: to, from: window.location.pathname }); + fireTrackingEvent('ExternalLink Clicked', { + href: to, + from: window.location.pathname, + } as LinkTrackingEventProperties); }} icon={} iconPosition="right" diff --git a/frontend/src/components/OdhDocCard.tsx b/frontend/src/components/OdhDocCard.tsx index d1b1703034..76c82c895c 100644 --- a/frontend/src/components/OdhDocCard.tsx +++ b/frontend/src/components/OdhDocCard.tsx @@ -22,6 +22,7 @@ import { LaunchStatusEnum, } from '~/utilities/quickStartUtils'; import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { DocCardTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; import BrandImage from './BrandImage'; import DocCardBadges from './DocCardBadges'; import { useQuickStartCardSelected } from './useQuickStartCardSelected'; @@ -46,7 +47,7 @@ const fireResourceAccessedEvent = { name, type, - }, + } as DocCardTrackingEventProperties, ); }; diff --git a/frontend/src/concepts/analyticsTracking/trackingProperties.ts b/frontend/src/concepts/analyticsTracking/trackingProperties.ts new file mode 100644 index 0000000000..db33c0b0ae --- /dev/null +++ b/frontend/src/concepts/analyticsTracking/trackingProperties.ts @@ -0,0 +1,71 @@ +import { + ConfigMapCategory, + EnvironmentVariableType, + SecretCategory, + StorageType, +} from '~/pages/projects/types'; + +export interface IdentifyEventProperties { + anonymousID?: string; +} + +export enum TrackingOutcome { + submit = 'submit', + cancel = 'cancel', +} + +export interface BaseTrackingEventProperties { + name?: string; + anonymousID?: string; + outcome?: TrackingOutcome; + success?: boolean; + error?: string; +} + +export interface WorkbenchTrackingEventProperties extends BaseTrackingEventProperties { + type?: string; + term?: string; + imageName?: string; + accelerator?: string; + acceleratorCount?: number; + lastSelectedSize?: string; + lastSelectedImage?: string; + projectName?: string; + notebookName?: string; + lastActivity?: string; + storageType?: StorageType; + storageDataSize?: string; + dataConnectionType?: EnvironmentVariableType | null; + dataConnectionCategory?: ConfigMapCategory | SecretCategory | null; + dataConnectionEnabled?: boolean; +} + +export interface ProjectTrackingEventProperties extends BaseTrackingEventProperties { + projectName: string; +} + +export interface LinkTrackingEventProperties extends BaseTrackingEventProperties { + from: string; + href: string; +} + +export interface SearchTrackingEventProperties extends BaseTrackingEventProperties { + term: string; +} + +export interface NotebookTrackingEventProperties extends BaseTrackingEventProperties { + accelerator?: string; + acceleratorCount?: number; + lastSelectedSize?: string; + lastSelectedImage?: string; +} + +export interface DocCardTrackingEventProperties extends BaseTrackingEventProperties { + type: string; +} + +export interface HomeCardTrackingEventProperties extends BaseTrackingEventProperties { + to: string; + type: string; + section: string; +} diff --git a/frontend/src/pages/home/projects/ProjectCard.tsx b/frontend/src/pages/home/projects/ProjectCard.tsx index a0c955d33a..2f4cc3eaf3 100644 --- a/frontend/src/pages/home/projects/ProjectCard.tsx +++ b/frontend/src/pages/home/projects/ProjectCard.tsx @@ -23,7 +23,8 @@ import { getProjectDisplayName, getProjectOwner, } from '~/concepts/projects/utils'; -import { fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { HomeCardTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; interface ProjectCardProps { project: ProjectKind; @@ -41,10 +42,10 @@ const ProjectCard: React.FC = ({ project }) => { isInline onClick={() => { navigate(`/projects/${project.metadata.name}`); - fireTrackingEventRaw('HomeCardClicked', { + fireTrackingEvent('HomeCardClicked', { to: `/projects/${project.metadata.name}`, type: 'project', - }); + } as HomeCardTrackingEventProperties); }} style={{ fontSize: 'var(--pf-v5-global--FontSize--md)' }} > diff --git a/frontend/src/pages/home/useEnableTeamSection.tsx b/frontend/src/pages/home/useEnableTeamSection.tsx index 53550b10fb..c566951022 100644 --- a/frontend/src/pages/home/useEnableTeamSection.tsx +++ b/frontend/src/pages/home/useEnableTeamSection.tsx @@ -13,7 +13,8 @@ import InfoGalleryItem from '~/concepts/design/InfoGalleryItem'; import { useBrowserStorage } from '~/components/browserStorage'; import { SupportedArea } from '~/concepts/areas'; import useIsAreaAvailable from '~/concepts/areas/useIsAreaAvailable'; -import { fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { HomeCardTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; export const useEnableTeamSection = (): React.ReactNode => { const navigate = useNavigate(); @@ -34,11 +35,11 @@ export const useEnableTeamSection = (): React.ReactNode => { } const trackAndNavigate = (section: string, to: string): void => { - fireTrackingEventRaw('HomeCardClicked', { + fireTrackingEvent('HomeCardClicked', { to: `${to}`, type: 'enableTeam', section: `${section}`, - }); + } as HomeCardTrackingEventProperties); navigate(to); }; diff --git a/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx b/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx index fda12ae799..c0a0a09dd9 100644 --- a/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx +++ b/frontend/src/pages/learningCenter/LearningCenterToolbar.tsx @@ -26,6 +26,7 @@ import { import { removeQueryArgument, setQueryArgument } from '~/utilities/router'; import { useQueryParams } from '~/utilities/useQueryParams'; import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { SearchTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; import { SEARCH_FILTER_KEY, DOC_SORT_KEY, @@ -60,7 +61,7 @@ const fireSearchedEvent = _.debounce((val: string) => { if (val) { fireTrackingEvent('Resource Searched', { term: val, - }); + } as SearchTrackingEventProperties); } }, 1000); diff --git a/frontend/src/pages/notebookController/screens/server/SpawnerPage.tsx b/frontend/src/pages/notebookController/screens/server/SpawnerPage.tsx index 52e800f27c..d407c79ebd 100644 --- a/frontend/src/pages/notebookController/screens/server/SpawnerPage.tsx +++ b/frontend/src/pages/notebookController/screens/server/SpawnerPage.tsx @@ -51,6 +51,7 @@ import StartServerModal from './StartServerModal'; import AcceleratorProfileSelectField from './AcceleratorProfileSelectField'; import '~/pages/notebookController/NotebookController.scss'; +import { NotebookTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; const SpawnerPage: React.FC = () => { const navigate = useNavigate(); @@ -240,7 +241,7 @@ const SpawnerPage: React.FC = () => { acceleratorCount: acceleratorProfile.useExisting ? undefined : acceleratorProfile.count, lastSelectedSize: selectedSize.name, lastSelectedImage: `${selectedImageTag.image?.name}:${selectedImageTag.tag?.name}`, - }); + } as NotebookTrackingEventProperties); }; const handleNotebookAction = async () => { diff --git a/frontend/src/pages/projects/notebook/NotebookRouteLink.tsx b/frontend/src/pages/projects/notebook/NotebookRouteLink.tsx index 4ef94551d3..777578a8df 100644 --- a/frontend/src/pages/projects/notebook/NotebookRouteLink.tsx +++ b/frontend/src/pages/projects/notebook/NotebookRouteLink.tsx @@ -3,7 +3,7 @@ import { Button, ButtonVariant, Flex, FlexItem, Icon, Tooltip } from '@patternfl import { ExclamationCircleIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; import { NotebookKind } from '~/k8sTypes'; import { getNotebookDisplayName } from '~/pages/projects/utils'; -import { fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; import useRouteForNotebook from './useRouteForNotebook'; import { hasStopAnnotation } from './utils'; @@ -52,7 +52,7 @@ const NotebookRouteLink: React.FC = ({ : 'var(--pf-v5-global--FontSize--sm)', }} onClick={() => - fireTrackingEventRaw('Workbench Opened', { wbName: getNotebookDisplayName(notebook) }) + fireTrackingEvent('Workbench Opened', { name: getNotebookDisplayName(notebook) }) } > {label ?? getNotebookDisplayName(notebook)} diff --git a/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx b/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx index 81e9784b3b..e3581a8bf7 100644 --- a/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx +++ b/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx @@ -12,6 +12,7 @@ import useRefreshNotebookUntilStartOrStop from './useRefreshNotebookUntilStartOr import StopNotebookConfirmModal from './StopNotebookConfirmModal'; import useStopNotebookModalAvailability from './useStopNotebookModalAvailability'; import NotebookStatusText from './NotebookStatusText'; +import { WorkbenchTrackingEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; type NotebookStatusToggleProps = { notebookState: NotebookState; @@ -69,7 +70,7 @@ const NotebookStatusToggle: React.FC = ({ ...(action === 'stopped' && { lastActivity: notebook.metadata.annotations?.['notebooks.kubeflow.org/last-activity'], }), - }); + } as WorkbenchTrackingEventProperties); }, [acceleratorProfile, notebook, size], ); diff --git a/frontend/src/pages/projects/screens/projects/DeleteProjectModal.tsx b/frontend/src/pages/projects/screens/projects/DeleteProjectModal.tsx index 7172f8f4b2..f6ca6be457 100644 --- a/frontend/src/pages/projects/screens/projects/DeleteProjectModal.tsx +++ b/frontend/src/pages/projects/screens/projects/DeleteProjectModal.tsx @@ -4,7 +4,8 @@ import { getProjectDisplayName } from '~/concepts/projects/utils'; import { deleteProject } from '~/api'; import DeleteModal from '~/pages/projects/components/DeleteModal'; import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; -import { TrackingOutcome } from '~/types'; + +import { TrackingOutcome } from '~/concepts/analyticsTracking/trackingProperties'; type DeleteProjectModalProps = { onClose: (deleted: boolean) => void; diff --git a/frontend/src/pages/projects/screens/projects/ManageProjectModal.tsx b/frontend/src/pages/projects/screens/projects/ManageProjectModal.tsx index dffd7752c3..87eb730549 100644 --- a/frontend/src/pages/projects/screens/projects/ManageProjectModal.tsx +++ b/frontend/src/pages/projects/screens/projects/ManageProjectModal.tsx @@ -8,8 +8,12 @@ import { isValidK8sName } from '~/concepts/k8s/utils'; import NameDescriptionField from '~/concepts/k8s/NameDescriptionField'; import { NameDescType } from '~/pages/projects/types'; import { ProjectsContext } from '~/concepts/projects/ProjectsContext'; -import { fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; -import { TrackingOutcome } from '~/types'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; + +import { + ProjectTrackingEventProperties, + TrackingOutcome, +} from '~/concepts/analyticsTracking/trackingProperties'; type ManageProjectModalProps = { editProjectData?: ProjectKind; @@ -49,23 +53,23 @@ const ManageProjectModal: React.FC = ({ const onBeforeClose = (newProjectName?: string) => { onClose(newProjectName); if (newProjectName) { - fireTrackingEventRaw(editProjectData ? 'Project Edited' : 'NewProject Created', { + fireTrackingEvent(editProjectData ? 'Project Edited' : 'NewProject Created', { outcome: TrackingOutcome.submit, success: true, projectName: newProjectName, - }); + } as ProjectTrackingEventProperties); } setFetching(false); setError(undefined); setNameDesc({ name: '', k8sName: undefined, description: '' }); }; const handleError = (e: Error) => { - fireTrackingEventRaw(editProjectData ? 'Project Edited' : 'NewProject Created', { + fireTrackingEvent(editProjectData ? 'Project Edited' : 'NewProject Created', { outcome: TrackingOutcome.submit, success: false, projectName: '', error: e.message, - }); + } as ProjectTrackingEventProperties); setError(e); setFetching(false); }; @@ -105,7 +109,7 @@ const ManageProjectModal: React.FC = ({ variant="link" onClick={() => { onBeforeClose(); - fireTrackingEventRaw(editProjectData ? 'Project Edited' : 'NewProject Created', { + fireTrackingEvent(editProjectData ? 'Project Edited' : 'NewProject Created', { outcome: TrackingOutcome.cancel, }); }} diff --git a/frontend/src/pages/projects/screens/spawner/SpawnerFooter.tsx b/frontend/src/pages/projects/screens/spawner/SpawnerFooter.tsx index ffddcf729c..e9ee0941db 100644 --- a/frontend/src/pages/projects/screens/spawner/SpawnerFooter.tsx +++ b/frontend/src/pages/projects/screens/spawner/SpawnerFooter.tsx @@ -20,8 +20,11 @@ import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext'; import { AppContext } from '~/app/AppContext'; import usePreferredStorageClass from '~/pages/projects/screens/spawner/storage/usePreferredStorageClass'; import { ProjectSectionID } from '~/pages/projects/screens/detail/types'; -import { fireTrackingEvent, fireTrackingEventRaw } from '~/utilities/segmentIOUtils'; -import { TrackingOutcome } from '~/types'; +import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; +import { + WorkbenchTrackingEventProperties, + TrackingOutcome, +} from '~/concepts/analyticsTracking/trackingProperties'; import { createConfigMapsAndSecretsForNotebook, createPvcDataForNotebook, @@ -84,7 +87,7 @@ const SpawnerFooter: React.FC = ({ const afterStart = (name: string, type: 'created' | 'updated') => { const { acceleratorProfile, notebookSize, image } = startNotebookData; - fireTrackingEventRaw(`Workbench ${type === 'created' ? 'Created' : 'Updated'}`, { + const tep: WorkbenchTrackingEventProperties = { acceleratorCount: acceleratorProfile.useExisting ? undefined : acceleratorProfile.count, accelerator: acceleratorProfile.acceleratorProfile ? `${acceleratorProfile.acceleratorProfile.spec.displayName} (${acceleratorProfile.acceleratorProfile.metadata.name}): ${acceleratorProfile.acceleratorProfile.spec.identifier}` @@ -107,16 +110,18 @@ const SpawnerFooter: React.FC = ({ dataConnectionEnabled: dataConnection.enabled, outcome: TrackingOutcome.submit, success: true, - }); + }; + fireTrackingEvent(`Workbench ${type === 'created' ? 'Created' : 'Updated'}`, tep); refreshAllProjectData(); navigate(`/projects/${projectName}?section=${ProjectSectionID.WORKBENCHES}`); }; const handleError = (e: Error) => { - fireTrackingEvent('Workbench Created', { + const ep: WorkbenchTrackingEventProperties = { outcome: TrackingOutcome.submit, success: false, error: e.message, - }); + }; + fireTrackingEvent('Workbench Created', ep); setErrorMessage(e.message || 'Error creating workbench'); setCreateInProgress(false); }; diff --git a/frontend/src/redux/actions/actions.ts b/frontend/src/redux/actions/actions.ts index 5d292b1870..dd60a3725d 100644 --- a/frontend/src/redux/actions/actions.ts +++ b/frontend/src/redux/actions/actions.ts @@ -70,3 +70,11 @@ export const forceComponentsUpdate = type: Actions.FORCE_COMPONENTS_UPDATE, }); }; + +export const segmentReady = + (): ThunkAction> => + (dispatch) => { + dispatch({ + type: Actions.SEGMENT_READY, + }); + }; diff --git a/frontend/src/redux/reducers/appReducer.ts b/frontend/src/redux/reducers/appReducer.ts index 4c56b7af9b..3d151f8d68 100755 --- a/frontend/src/redux/reducers/appReducer.ts +++ b/frontend/src/redux/reducers/appReducer.ts @@ -104,6 +104,11 @@ const appReducer = (state: AppState = initialState, action: GetUserAction): AppS ...state, forceComponentsUpdate: state.forceComponentsUpdate + 1, }; + case Actions.SEGMENT_READY: + return { + ...state, + segmentInitialised: true, + }; default: return state; } diff --git a/frontend/src/redux/types.ts b/frontend/src/redux/types.ts index b678114914..5b78be3a1b 100644 --- a/frontend/src/redux/types.ts +++ b/frontend/src/redux/types.ts @@ -10,6 +10,7 @@ export enum Actions { ACK_NOTIFICATION = 'ACK_NOTIFICATION', REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION', FORCE_COMPONENTS_UPDATE = 'FORCE_COMPONENTS_UPDATE', + SEGMENT_READY = 'SEGMENT_READY', } export interface AppNotification { @@ -53,6 +54,7 @@ export type AppState = { dashboardNamespace?: string; notifications: AppNotification[]; forceComponentsUpdate: number; + segmentInitialised?: boolean; }; export type StatusResponse = { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 8040e9d084..b05627d9df 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -265,28 +265,6 @@ export type Section = { actions: ApplicationAction[]; }; -export enum TrackingOutcome { - submit = 'submit', - cancel = 'cancel', -} - -export type TrackingEventProperties = { - name?: string; - anonymousID?: string; - type?: string; - term?: string; - accelerator?: string; - acceleratorCount?: number; - lastSelectedSize?: string; - lastSelectedImage?: string; - projectName?: string; - notebookName?: string; - lastActivity?: string; - outcome?: TrackingOutcome; - success?: boolean; - error?: string; -}; - export type NotebookPort = { name: string; containerPort: number; diff --git a/frontend/src/utilities/segmentIOUtils.tsx b/frontend/src/utilities/segmentIOUtils.tsx index 3f4484c7ec..809b15a829 100644 --- a/frontend/src/utilities/segmentIOUtils.tsx +++ b/frontend/src/utilities/segmentIOUtils.tsx @@ -1,27 +1,12 @@ -import { TrackingEventProperties } from '~/types'; +import { + BaseTrackingEventProperties, + IdentifyEventProperties, +} from '~/concepts/analyticsTracking/trackingProperties'; import { DEV_MODE, INTERNAL_DASHBOARD_VERSION } from './const'; -// The following is like the original method below, but allows for more 'free form' properties. -// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types -export const fireTrackingEventRaw = (eventType: string, properties?: any): void => { - const clusterID = window.clusterID ?? ''; - if (DEV_MODE) { - /* eslint-disable-next-line no-console */ - console.log( - `Telemetry event triggered: ${eventType}${ - properties - ? ` - ${JSON.stringify(properties)} for version ${INTERNAL_DASHBOARD_VERSION}` - : '' - }`, - ); - } else if (window.analytics) { - window.analytics.track(eventType, { ...properties, clusterID }); - } -}; - export const fireTrackingEvent = ( eventType: string, - properties?: TrackingEventProperties, + properties?: BaseTrackingEventProperties, ): void => { const clusterID = window.clusterID ?? ''; if (DEV_MODE) { @@ -33,12 +18,20 @@ export const fireTrackingEvent = ( : '' }`, ); + if (!(eventType === 'track')) { + /* eslint-disable-next-line no-console */ + console.warn(`Event of type ${eventType} is not supported`); + } } else if (window.analytics) { switch (eventType) { case 'identify': window.analytics.identify(properties?.anonymousID, { clusterID }); + /* eslint-disable-next-line no-console */ + console.warn('Identify event triggered through fireTrackingEvent, must not happen'); break; case 'page': + /* eslint-disable-next-line no-console */ + console.warn('Page event triggered through fireTrackingEvent, must not happen'); window.analytics.page(undefined, { clusterID }); break; default: @@ -47,6 +40,30 @@ export const fireTrackingEvent = ( } }; +export const firePageEvent = (): void => { + if (DEV_MODE) { + /* eslint-disable-next-line no-console */ + console.log(`Page event triggered: for version ${INTERNAL_DASHBOARD_VERSION}`); + } else if (window.analytics) { + const clusterID = window.clusterID ?? ''; + window.analytics.page(undefined, { clusterID }); + } +}; + +export const fireIdentifyEvent = (properties: IdentifyEventProperties): void => { + if (DEV_MODE) { + /* eslint-disable-next-line no-console */ + console.log( + `Identify event triggered: ${JSON.stringify( + properties, + )} for version ${INTERNAL_DASHBOARD_VERSION}`, + ); + } else if (window.analytics) { + const clusterID = window.clusterID ?? ''; + window.analytics.identify(properties.anonymousID, { clusterID }); + } +}; + export const initSegment = async (props: { segmentKey: string; username: string; @@ -120,13 +137,13 @@ export const initSegment = async (props: { }, }); } - const anonymousIDBuffer = await crypto.subtle.digest( - 'SHA-1', - new TextEncoder().encode(username), - ); - const anonymousIDArray = Array.from(new Uint8Array(anonymousIDBuffer)); - const anonymousID = anonymousIDArray.map((b) => b.toString(16).padStart(2, '0')).join(''); - fireTrackingEvent('identify', { anonymousID }); - fireTrackingEvent('page'); + // const anonymousIDBuffer = await crypto.subtle.digest( + // 'SHA-1', + // new TextEncoder().encode(username), + // ); + // const anonymousIDArray = Array.from(new Uint8Array(anonymousIDBuffer)); + // const anonymousID = anonymousIDArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + // fireIdentifyEvent({ anonymousID }); + // firePageEvent(); } }; diff --git a/frontend/src/utilities/useSegmentTracking.ts b/frontend/src/utilities/useSegmentTracking.ts index da4e7cae9c..75fb857c57 100644 --- a/frontend/src/utilities/useSegmentTracking.ts +++ b/frontend/src/utilities/useSegmentTracking.ts @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { useAppContext } from '~/app/AppContext'; -import { initSegment } from '~/utilities/segmentIOUtils'; -import { useAppSelector } from '~/redux/hooks'; +import { fireIdentifyEvent, initSegment } from '~/utilities/segmentIOUtils'; +import { useAppDispatch, useAppSelector } from '~/redux/hooks'; +import { IdentifyEventProperties } from '~/concepts/analyticsTracking/trackingProperties'; +import { segmentReady } from '~/redux/actions/actions'; import { useWatchSegmentKey } from './useWatchSegmentKey'; export const useSegmentTracking = (): void => { @@ -9,6 +11,9 @@ export const useSegmentTracking = (): void => { const { dashboardConfig } = useAppContext(); const username = useAppSelector((state) => state.user); const clusterID = useAppSelector((state) => state.clusterID); + const segmentInitialised = useAppSelector((state) => state.segmentInitialised); + const dispatch = useAppDispatch(); + const initDone = useRef(false); React.useEffect(() => { if (segmentKey && loaded && !loadError && username && clusterID) { @@ -18,6 +23,33 @@ export const useSegmentTracking = (): void => { username, enabled: !dashboardConfig.spec.dashboardConfig.disableTracking, }); + + dispatch(segmentReady()); + } + }, [clusterID, loadError, loaded, segmentKey, username, dashboardConfig, dispatch]); + + // TODO temporary. We want to swap this this function depending on context + // TODO also this makes the fireIdentify below trigger endlessly via useEffect-deps + // eslint-disable-next-line react-hooks/exhaustive-deps + async function encodeUsername() { + const anonymousIDBuffer = await crypto.subtle.digest( + 'SHA-1', + new TextEncoder().encode(username), + ); + const anonymousIDArray = Array.from(new Uint8Array(anonymousIDBuffer)); + return anonymousIDArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + } + + // Init user + React.useEffect(() => { + if (initDone.current) { + return; + } + if (segmentKey && loaded && !loadError && username && clusterID && segmentInitialised) { + encodeUsername().then((anonymousID) => + fireIdentifyEvent({ anonymousID } as IdentifyEventProperties), + ); + initDone.current = true; } - }, [clusterID, loadError, loaded, segmentKey, username, dashboardConfig]); + }, [clusterID, segmentKey, username, segmentInitialised, loaded, loadError, encodeUsername]); }; diff --git a/frontend/src/utilities/useTrackHistory.ts b/frontend/src/utilities/useTrackHistory.ts index c0f31d3d7b..27b821bda2 100644 --- a/frontend/src/utilities/useTrackHistory.ts +++ b/frontend/src/utilities/useTrackHistory.ts @@ -1,12 +1,12 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; -import { fireTrackingEvent } from './segmentIOUtils'; +import { firePageEvent } from './segmentIOUtils'; export const useTrackHistory = (): void => { const { pathname } = useLocation(); // notify url change events React.useEffect(() => { - fireTrackingEvent('page'); + firePageEvent(); }, [pathname]); };