diff --git a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx index 4486b69949..612fdfba91 100644 --- a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx +++ b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx @@ -5,6 +5,7 @@ import { renderWithProviders } from 'shared/utils/renderWithProviders'; import { mockedApplet, mockedCurrentWorkspace } from 'shared/mock'; import { Roles } from 'shared/consts'; import { initialStateData } from 'shared/state'; +import { Mixpanel, MixpanelEventType, MixpanelProps } from 'shared/utils'; import { HeaderOptions } from './HeaderOptions'; @@ -47,8 +48,11 @@ vi.mock('react-router-dom', async () => { }; }); +const spyMixpanelTrack = vi.spyOn(Mixpanel, 'track'); + describe('HeaderOptions', () => { beforeEach(() => { + spyMixpanelTrack.mockReset(); renderWithProviders(, { preloadedState: getPreloadedState() }); }); @@ -70,6 +74,16 @@ describe('HeaderOptions', () => { expect(screen.queryByTestId('header-option-audit-logs-button')).toBeInTheDocument(); }); + test('should track ExportAuditLogsClick when audit logs option is clicked', () => { + fireEvent.click(screen.getByTestId('header-option-export-button')); + fireEvent.click(screen.getByTestId('header-option-audit-logs-button')); + + expect(spyMixpanelTrack).toHaveBeenCalledWith({ + action: MixpanelEventType.ExportAuditLogsClick, + [MixpanelProps.AppletId]: testAppletId, + }); + }); + test('should contain link to settings page', () => { expect(screen.getByTestId('header-option-settings-link')).toHaveAttribute( 'href', diff --git a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx index bd3eeb1e98..001da65e13 100644 --- a/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx +++ b/src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx @@ -12,6 +12,7 @@ import { checkIfCanAccessData, checkIfCanEdit, MixpanelEventType, + MixpanelProps, checkIfFullAccess, } from 'shared/utils'; import { workspaces } from 'shared/state'; @@ -42,7 +43,10 @@ export const HeaderOptions = () => { const handleOpenAuditLogs = () => { setIsAuditLogsExportOpen(true); - Mixpanel.track({ action: MixpanelEventType.ExportAuditLogsClick }); + Mixpanel.track({ + action: MixpanelEventType.ExportAuditLogsClick, + [MixpanelProps.AppletId]: appletId, + }); }; const handleOpenResponseData = () => { diff --git a/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.test.tsx b/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.test.tsx index cf29313d18..e9c759d201 100644 --- a/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.test.tsx +++ b/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.test.tsx @@ -4,6 +4,7 @@ import { vi } from 'vitest'; import { initialStateData } from 'redux/modules'; import { mockedApplet } from 'shared/mock'; import { renderWithProviders } from 'shared/utils/renderWithProviders'; +import { Mixpanel, MixpanelEventType, MixpanelProps } from 'shared/utils'; import { AuditLogsExportSetting } from './AuditLogsExportSetting'; @@ -29,6 +30,8 @@ vi.mock('shared/utils/exportTemplate', () => ({ exportTemplate: vi.fn().mockResolvedValue(true), })); +const spyMixpanelTrack = vi.spyOn(Mixpanel, 'track'); + const dataTestId = 'audit-logs-export'; describe('AuditLogsExportSetting', () => { @@ -36,6 +39,7 @@ describe('AuditLogsExportSetting', () => { vi.useFakeTimers({ shouldAdvanceTime: true }); vi.setSystemTime(mockedNow); mockedExportAuditLogsApi.mockClear(); + spyMixpanelTrack.mockReset(); }); afterEach(() => { @@ -169,4 +173,75 @@ describe('AuditLogsExportSetting', () => { expect(mockedExportAuditLogsApi).toHaveBeenCalledTimes(2); }); }); + + it('should track ExportAuditLogsDownload when the download button is clicked', async () => { + mockedExportAuditLogsApi.mockResolvedValueOnce({ + data: { result: [], count: 0 }, + }); + + renderWithProviders( + , + { preloadedState }, + ); + + fireEvent.click(screen.getByTestId(`${dataTestId}-settings-download-button`)); + + expect(spyMixpanelTrack).toHaveBeenCalledWith({ + action: MixpanelEventType.ExportAuditLogsDownload, + [MixpanelProps.AppletId]: mockedApplet.id, + }); + }); + + it('should track ExportAuditLogsSuccessful when the export completes', async () => { + mockedExportAuditLogsApi.mockResolvedValueOnce({ + data: { result: [], count: 0 }, + }); + + renderWithProviders( + , + { preloadedState }, + ); + + fireEvent.click(screen.getByTestId(`${dataTestId}-settings-download-button`)); + + await waitFor(() => { + expect(spyMixpanelTrack).toHaveBeenCalledWith({ + action: MixpanelEventType.ExportAuditLogsSuccessful, + [MixpanelProps.AppletId]: mockedApplet.id, + }); + }); + }); + + it('should track ExportAuditLogsFailed when the export fails', async () => { + mockedExportAuditLogsApi.mockRejectedValueOnce(new Error('Network error')); + + renderWithProviders( + , + { preloadedState }, + ); + + fireEvent.click(screen.getByTestId(`${dataTestId}-settings-download-button`)); + + await waitFor(() => { + expect(spyMixpanelTrack).toHaveBeenCalledWith({ + action: MixpanelEventType.ExportAuditLogsFailed, + [MixpanelProps.AppletId]: mockedApplet.id, + }); + }); + }); }); diff --git a/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.tsx b/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.tsx index 6307140720..ee492ecfa5 100644 --- a/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.tsx +++ b/src/shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting.tsx @@ -7,6 +7,7 @@ import { ObjectSchema } from 'yup'; import { getNormalizedTimezoneDate } from 'shared/utils/dateTimezone'; import { DateRangePickerType } from 'shared/components/DateRangePicker'; import { applet } from 'shared/state/Applet'; +import { Mixpanel, MixpanelEventType, MixpanelProps } from 'shared/utils'; import { AuditLogsExportFormValues, @@ -66,6 +67,10 @@ export const AuditLogsExportSetting = ({ onExportSettingsClose(); }} onExport={() => { + Mixpanel.track({ + action: MixpanelEventType.ExportAuditLogsDownload, + [MixpanelProps.AppletId]: appletId, + }); setDataIsExporting(true); onExportSettingsClose(); }} diff --git a/src/shared/features/AppletSettings/AuditLogsExportSetting/Popups/AuditLogsExportPopup/useAuditLogsExport.tsx b/src/shared/features/AppletSettings/AuditLogsExportSetting/Popups/AuditLogsExportPopup/useAuditLogsExport.tsx index d13cfff438..26fcadb91c 100644 --- a/src/shared/features/AppletSettings/AuditLogsExportSetting/Popups/AuditLogsExportPopup/useAuditLogsExport.tsx +++ b/src/shared/features/AppletSettings/AuditLogsExportSetting/Popups/AuditLogsExportPopup/useAuditLogsExport.tsx @@ -6,6 +6,7 @@ import { getExportAuditLogsApi } from 'api'; import { getExportPageAmount } from 'modules/Dashboard/api/api.utils'; import { DateRangePickerType } from 'shared/components/DateRangePicker'; import { DateFormats } from 'shared/consts'; +import { Mixpanel, MixpanelEventType, MixpanelProps } from 'shared/utils'; import { getLast24hUTCRange } from 'shared/utils'; import { AuditLogsExportFormValues } from '../../AuditLogsExportSetting.types'; @@ -71,8 +72,16 @@ export const useAuditLogsExport = ( setCurrentPage(page); } + Mixpanel.track({ + action: MixpanelEventType.ExportAuditLogsSuccessful, + [MixpanelProps.AppletId]: appletId, + }); handlePopupCloseRef.current(); } catch (e) { + Mixpanel.track({ + action: MixpanelEventType.ExportAuditLogsFailed, + [MixpanelProps.AppletId]: appletId, + }); setError(e as Error); } finally { isExportingRef.current = false; diff --git a/src/shared/utils/mixpanel/mixpanel.types.ts b/src/shared/utils/mixpanel/mixpanel.types.ts index 66ea170897..d8051e5b0a 100644 --- a/src/shared/utils/mixpanel/mixpanel.types.ts +++ b/src/shared/utils/mixpanel/mixpanel.types.ts @@ -65,6 +65,9 @@ export enum MixpanelEventType { AppletCreatedSuccessfully = 'Applet Created Successfully', ExportDataClick = 'Export Data click', ExportAuditLogsClick = 'Export Audit Logs click', + ExportAuditLogsDownload = 'Export Audit Logs download', + ExportAuditLogsSuccessful = 'Export Audit Logs Successful', + ExportAuditLogsFailed = 'Export Audit Logs Failed', TakeNowDialogClosed = 'Take Now dialogue closed', MultiInformantStartActivityClick = 'Multi-informant Start Activity click', ProvidingResponsesDropdownOpened = '"Who will be providing responses" dropdown opened', @@ -249,6 +252,18 @@ export type ExportAuditLogsClickEvent = WithAppletId<{ action: MixpanelEventType.ExportAuditLogsClick; }>; +export type ExportAuditLogsDownloadEvent = WithAppletId<{ + action: MixpanelEventType.ExportAuditLogsDownload; +}>; + +export type ExportAuditLogsSuccessfulEvent = WithAppletId<{ + action: MixpanelEventType.ExportAuditLogsSuccessful; +}>; + +export type ExportAuditLogsFailedEvent = WithAppletId<{ + action: MixpanelEventType.ExportAuditLogsFailed; +}>; + type TakeNowEvent = WithFeature< WithAppletId<{ [MixpanelProps.MultiInformantAssessmentId]?: string | null; @@ -571,6 +586,9 @@ export type MixpanelEvent = | AppletCreatedSuccessfullyEvent | ExportDataClickEvent | ExportAuditLogsClickEvent + | ExportAuditLogsDownloadEvent + | ExportAuditLogsSuccessfulEvent + | ExportAuditLogsFailedEvent | TakeNowDialogClosedEvent | MultiInformantStartActivityClickEvent | ProvidingResponsesDropdownOpenedEvent