diff --git a/apps/demo-app/package.json b/apps/demo-app/package.json index 74dbabafd..1a11da701 100644 --- a/apps/demo-app/package.json +++ b/apps/demo-app/package.json @@ -24,11 +24,13 @@ }, "dependencies": { "@auth0/auth0-react": "^2.2.4", + "@monkvision/analytics": "4.0.0", "@monkvision/common": "4.0.0", "@monkvision/common-ui-web": "4.0.0", "@monkvision/inspection-capture-web": "4.0.0", "@monkvision/monitoring": "4.0.0", "@monkvision/network": "4.0.0", + "@monkvision/posthog": "4.0.0", "@monkvision/sentry": "4.0.0", "@monkvision/sights": "4.0.0", "@monkvision/types": "4.0.0", diff --git a/apps/demo-app/src/index.tsx b/apps/demo-app/src/index.tsx index 80681b31d..b29dfeb03 100644 --- a/apps/demo-app/src/index.tsx +++ b/apps/demo-app/src/index.tsx @@ -1,28 +1,31 @@ -import React from 'react'; import ReactDOM from 'react-dom'; import { MonitoringProvider } from '@monkvision/monitoring'; +import { AnalyticsProvider } from '@monkvision/analytics'; import { Auth0Provider } from '@auth0/auth0-react'; import { getEnvOrThrow, MonkThemeProvider } from '@monkvision/common'; import { sentryMonitoringAdapter } from './sentry'; +import { posthogAnalyticsAdapter } from './posthog'; import { AppRouter } from './components'; import './index.css'; import './i18n'; ReactDOM.render( - - - - - + + + + + + + , document.getElementById('root'), ); diff --git a/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx b/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx index befea8dd7..ed9dc0723 100644 --- a/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx +++ b/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx @@ -6,6 +6,7 @@ import { useMonkApi } from '@monkvision/network'; import { getEnvOrThrow, useLoadingState, useMonkAppParams } from '@monkvision/common'; import { useMonitoring } from '@monkvision/monitoring'; import { TaskName } from '@monkvision/types'; +import { useAnalytics } from '@monkvision/analytics'; import { Page } from '../pages'; import styles from './CreateInspectionPage.module.css'; @@ -18,6 +19,7 @@ export function CreateInspectionPage() { authToken: authToken ?? '', apiDomain: getEnvOrThrow('REACT_APP_API_DOMAIN'), }); + const { setUserId } = useAnalytics(); const handleCreateInspection = () => { loading.start(); @@ -25,6 +27,7 @@ export function CreateInspectionPage() { .then((res) => { loading.onSuccess(); setInspectionId(res.id); + setUserId(res.id); }) .catch((err) => { loading.onError(err); diff --git a/apps/demo-app/src/posthog.ts b/apps/demo-app/src/posthog.ts new file mode 100644 index 000000000..850480b64 --- /dev/null +++ b/apps/demo-app/src/posthog.ts @@ -0,0 +1,9 @@ +import { PosthogAnalyticsAdapter } from '@monkvision/posthog'; + +export const posthogAnalyticsAdapter = new PosthogAnalyticsAdapter({ + token: 'phc_azbiXVxUvUjxUAVLb5zrrlzNCFpf0RSClkiJ9RxTDGU', + api_host: 'https://eu.posthog.com', + environnement: 'development', + projectName: 'test', + release: '1.0.0', +}); diff --git a/documentation/docs/packages/analytics.md b/documentation/docs/packages/analytics.md new file mode 100644 index 000000000..37f254b14 --- /dev/null +++ b/documentation/docs/packages/analytics.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 1 +--- + +# analytics +Monk analytics abstraction package. + +![npm latest package](https://img.shields.io/npm/v/@monkvision/analytics/latest.svg) + +## Overview +This package provides an abstraction layer for the analytics features in the MonkJs ecosystem. If you plan on using any +of these features, you can use this package to properly set up the analytics inside your application. + +In the MonkJs SDK, we use this "abstraction" package as a way of offering the possibility to developers to choose their +own Analytics tools and platform if they don't want to use Posthog (the platform used internally by Monk). + +## Complete Documentation +As every other package in the SDK, please refer to +[its README file](https://github.com/monkvision/monkjs/blob/main/packages/analytics/README.md) for a complete +documentation on how this package works. diff --git a/documentation/docs/packages/camera-web.md b/documentation/docs/packages/camera-web.md index 879132c72..ef7e5cefe 100644 --- a/documentation/docs/packages/camera-web.md +++ b/documentation/docs/packages/camera-web.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # camera-web diff --git a/documentation/docs/packages/common-ui-web.md b/documentation/docs/packages/common-ui-web.md index b39e33bc2..2c3dd9459 100644 --- a/documentation/docs/packages/common-ui-web.md +++ b/documentation/docs/packages/common-ui-web.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- # common-ui-web diff --git a/documentation/docs/packages/common.md b/documentation/docs/packages/common.md index 278aa2671..c3c1512ff 100644 --- a/documentation/docs/packages/common.md +++ b/documentation/docs/packages/common.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # common diff --git a/documentation/docs/packages/inspection-capture-web.md b/documentation/docs/packages/inspection-capture-web.md index 7b24830d2..c96b1e5e5 100644 --- a/documentation/docs/packages/inspection-capture-web.md +++ b/documentation/docs/packages/inspection-capture-web.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- # inspection-capture-web diff --git a/documentation/docs/packages/inspection-report-web.md b/documentation/docs/packages/inspection-report-web.md index 097012b0d..94d765c07 100644 --- a/documentation/docs/packages/inspection-report-web.md +++ b/documentation/docs/packages/inspection-report-web.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 --- # inspection-report-web diff --git a/documentation/docs/packages/monitoring.md b/documentation/docs/packages/monitoring.md index 74476ee70..5e19eb3d1 100644 --- a/documentation/docs/packages/monitoring.md +++ b/documentation/docs/packages/monitoring.md @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 7 --- # monitoring diff --git a/documentation/docs/packages/network.md b/documentation/docs/packages/network.md index cd7f3d3ec..ae8958eef 100644 --- a/documentation/docs/packages/network.md +++ b/documentation/docs/packages/network.md @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 --- # network diff --git a/documentation/docs/packages/posthog.md b/documentation/docs/packages/posthog.md new file mode 100644 index 000000000..e57e62bc1 --- /dev/null +++ b/documentation/docs/packages/posthog.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 9 +--- + +# posthog +Posthog implementation used to analyze MonkJs SDK and applications. + +![npm latest package](https://img.shields.io/npm/v/@monkvision/posthog/latest.svg) + +## Overview +This package provides an implementation of the analytics abstraction layers exposed by the +[analytics package](docs/packages/analytics.md) using the [Posthog](https://posthog.com/) analytics platform. Posthog is +the platform used internally by Monk to analyze the user's behaviour in our applications. Unless you already have your own analytics +platform, and you really don't want to add Posthog as a dependency in your app, we highly recommend installing this +analytics adapter in your app. + +## Complete Documentation +As every other package in the SDK, please refer to +[its README file](https://github.com/monkvision/monkjs/blob/main/packages/posthog/README.md) for a complete +documentation on how this package works. diff --git a/documentation/docs/packages/sentry.md b/documentation/docs/packages/sentry.md index 22107467b..c6aa9cfef 100644 --- a/documentation/docs/packages/sentry.md +++ b/documentation/docs/packages/sentry.md @@ -1,5 +1,5 @@ --- -sidebar_position: 8 +sidebar_position: 10 --- # sentry diff --git a/documentation/docs/packages/sights.md b/documentation/docs/packages/sights.md index 974ba262c..81e516e29 100644 --- a/documentation/docs/packages/sights.md +++ b/documentation/docs/packages/sights.md @@ -1,5 +1,5 @@ --- -sidebar_position: 9 +sidebar_position: 11 --- # sights diff --git a/documentation/docs/packages/types.md b/documentation/docs/packages/types.md index b0f367d6a..aeddc81b0 100644 --- a/documentation/docs/packages/types.md +++ b/documentation/docs/packages/types.md @@ -1,5 +1,5 @@ --- -sidebar_position: 10 +sidebar_position: 12 --- # types diff --git a/packages/inspection-capture-web/package.json b/packages/inspection-capture-web/package.json index 2245bd52a..3b13797bb 100644 --- a/packages/inspection-capture-web/package.json +++ b/packages/inspection-capture-web/package.json @@ -25,6 +25,7 @@ "lint:fix": "yarn run prettier:fix && yarn run eslint:fix" }, "dependencies": { + "@monkvision/analytics": "4.0.0", "@monkvision/camera-web": "4.0.0", "@monkvision/common": "4.0.0", "@monkvision/common-ui-web": "4.0.0", diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx index b0bf02f36..71103586e 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx @@ -3,6 +3,7 @@ import { Camera, CameraHUDProps, CameraProps, CompressionOptions } from '@monkvi import { ComplianceOptions, DeviceOrientation, Sight, TaskName } from '@monkvision/types'; import { useI18nSync, useLoadingState, useWindowDimensions } from '@monkvision/common'; import { MonkApiConfig } from '@monkvision/network'; +import { useAnalytics } from '@monkvision/analytics'; import { useMonitoring } from '@monkvision/monitoring'; import { Icon, @@ -13,6 +14,7 @@ import { import { useTranslation } from 'react-i18next'; import { useAddDamageMode, + useComplianceAnalytics, usePhotoCaptureSightState, usePictureTaken, useStartTasksOnComplete, @@ -117,8 +119,10 @@ export function PhotoCapture({ const { handleError } = useMonitoring(); const [currentScreen, setCurrentScreen] = useState(PhotoCaptureScreen.CAMERA); const dimensions = useWindowDimensions(); + const analytics = useAnalytics(); const loading = useLoadingState(); const addDamageHandle = useAddDamageMode(); + useComplianceAnalytics({ inspectionId, sights }); const startTasks = useStartTasksOnComplete({ inspectionId, apiConfig, @@ -152,7 +156,13 @@ export function PhotoCapture({ uploadQueue, tasksBySight, }); - const handleOpenGallery = () => setCurrentScreen(PhotoCaptureScreen.GALLERY); + const handleOpenGallery = () => { + setCurrentScreen(PhotoCaptureScreen.GALLERY); + analytics.trackEvent('Capture Closed', { + inspectionId, + category: 'capture_closed', + }); + }; const handleGalleryBack = () => setCurrentScreen(PhotoCaptureScreen.CAMERA); const handleNavigateToCapture = (options: NavigateToCaptureOptions) => { if (options.reason === NavigateToCaptureReason.ADD_DAMAGE) { @@ -171,6 +181,11 @@ export function PhotoCapture({ const handleGalleryValidate = () => { startTasks() .then(() => { + analytics.trackEvent('Capture Completed', { + inspectionId, + category: 'capture_completed', + }); + analytics.setUserProperties({ captureCompleted: true }); onComplete?.(); }) .catch((err) => { diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx index 006539c96..4593fc0aa 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { BackdropDialog } from '@monkvision/common-ui-web'; import { CameraHUDProps } from '@monkvision/camera-web'; import { LoadingState } from '@monkvision/common'; +import { useAnalytics } from '@monkvision/analytics'; import { PhotoCaptureHUDButtons } from './PhotoCaptureHUDButtons'; import { usePhotoCaptureHUDStyle } from './hooks'; import { PhotoCaptureMode } from '../hooks'; @@ -101,9 +102,14 @@ export function PhotoCaptureHUD({ const { t } = useTranslation(); const [showCloseModal, setShowCloseModal] = useState(false); const style = usePhotoCaptureHUDStyle(); + const { trackEvent } = useAnalytics(); const handleCloseConfirm = () => { setShowCloseModal(false); + trackEvent('Capture Closed', { + inspectionId, + category: 'capture_closed', + }); onClose?.(); }; diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts index 0fc6cf00d..341133db2 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts @@ -3,3 +3,4 @@ export * from './useStartTasksOnComplete'; export * from './useAddDamageMode'; export * from './useUploadQueue'; export * from './usePictureTaken'; +export * from './useComplianceAnalytics'; diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts index 5662fd1b8..1b417ca67 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts @@ -1,3 +1,4 @@ +import { useAnalytics } from '@monkvision/analytics'; import { useCallback, useMemo, useState } from 'react'; /** @@ -45,20 +46,31 @@ export interface AddDamageHandle { */ export function useAddDamageMode(): AddDamageHandle { const [mode, setMode] = useState(PhotoCaptureMode.SIGHT); + const { trackEvent } = useAnalytics(); - const handleAddDamage = useCallback(() => setMode(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT), []); + const handleAddDamage = useCallback(() => { + setMode(PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT); + trackEvent('AddDamage Selected', { + mode: PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT, + category: 'addDamage_selected', + }); + }, []); - const updatePhotoCaptureModeAfterPictureTaken = useCallback( - () => - setMode((currentMode) => - currentMode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT - ? PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT - : PhotoCaptureMode.SIGHT, - ), - [], - ); + const updatePhotoCaptureModeAfterPictureTaken = useCallback(() => { + setMode((currentMode) => + currentMode === PhotoCaptureMode.ADD_DAMAGE_1ST_SHOT + ? PhotoCaptureMode.ADD_DAMAGE_2ND_SHOT + : PhotoCaptureMode.SIGHT, + ); + }, []); - const handleCancelAddDamage = useCallback(() => setMode(PhotoCaptureMode.SIGHT), []); + const handleCancelAddDamage = useCallback(() => { + trackEvent('AddDamage Canceled', { + mode, + category: 'addDamage_canceled', + }); + setMode(PhotoCaptureMode.SIGHT); + }, []); return useMemo( () => ({ diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useComplianceAnalytics.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/useComplianceAnalytics.ts new file mode 100644 index 000000000..a9dda62a7 --- /dev/null +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/useComplianceAnalytics.ts @@ -0,0 +1,60 @@ +import { useAnalytics } from '@monkvision/analytics'; +import { useMonkState } from '@monkvision/common'; +import { ComplianceIssue, Image, ImageStatus, ImageType, Sight } from '@monkvision/types'; +import { useEffect, useState } from 'react'; + +interface ImageEventTracking extends Image { + isAlreadySent: boolean; +} + +/** + * Parameters of the useComplianceAnalytics hook. + */ +export interface ComplianceAnalyticsParams { + /** + * The inspection ID. + */ + inspectionId: string; + /** + * The list of sights passed to the PhotoCapture component. + */ + sights: Sight[]; +} + +/** + * Custom hook used for the compliance analytics by sending an event for 'non_compliant' image. + */ +export function useComplianceAnalytics({ inspectionId, sights }: ComplianceAnalyticsParams) { + const [imagesEventTracking, setImagesEventTracking] = useState([]); + const { state } = useMonkState(); + const { trackEvent } = useAnalytics(); + + useEffect(() => { + const newImagesEventTracking = state.images + .filter( + (image) => image.inspectionId === inspectionId && image.type === ImageType.BEAUTY_SHOT, + ) + .map((image) => { + const imageEventTracking = imagesEventTracking.find((i) => i.id === image.id); + if (imageEventTracking?.isAlreadySent) { + return imageEventTracking; + } + if (image.status === ImageStatus.NOT_COMPLIANT && image.complianceIssues) { + trackEvent('Compliance Issue', { + ...Object.fromEntries( + Object.values(ComplianceIssue).map((issue) => [ + issue, + image.complianceIssues?.includes(issue), + ]), + ), + sightId: image.additionalData?.sight_id, + sightLabel: sights.find((sight) => sight.id === image.additionalData?.sight_id)?.label, + }); + return { ...image, isAlreadySent: true }; + } + return { ...image, isAlreadySent: false }; + }); + + setImagesEventTracking(newImagesEventTracking); + }, [state]); +} diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts index 429bee5ad..5b4c31c6c 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts @@ -9,6 +9,7 @@ import { useMonitoring } from '@monkvision/monitoring'; import { LoadingState, useAsyncEffect } from '@monkvision/common'; import { ComplianceOptions, Image, Sight, TaskName, MonkPicture } from '@monkvision/types'; import { sights } from '@monkvision/sights'; +import { useAnalytics } from '@monkvision/analytics'; import { PhotoCaptureErrorName } from '../errors'; /** @@ -176,10 +177,28 @@ export function usePhotoCaptureSightState({ const [sightsTaken, setSightsTaken] = useState([]); const { getInspection } = useMonkApi(apiConfig); const { handleError } = useMonitoring(); + const analytics = useAnalytics(); + + const selectSight = (s: Sight) => { + const sightsNotTaken = captureSights.filter((sight) => !sightsTaken.includes(sight)) ?? []; + const nextSight = sightsNotTaken.at(sightsNotTaken.indexOf(s) + 1) ?? null; + + setSelectedSight(s); + analytics.trackEvent(`Sight Selected`, { + inspectionId, + sightId: s.id, + sightLabel: s.label, + nextSightId: nextSight?.id, + nextSightLabel: nextSight?.label, + category: 'sight_selected', + }); + }; useAsyncEffect( () => { loading.start(); + analytics.setUserProperties({ inspectionId, captureCompleted: false }); + analytics.setEventsProperties({ inspectionId }); return getInspection({ id: inspectionId, compliance: { enableCompliance, complianceIssues }, @@ -191,7 +210,22 @@ export function usePhotoCaptureSightState({ try { const alreadyTakenSights = getSightsTaken(inspectionId, response); setSightsTaken(alreadyTakenSights); - setSelectedSight(captureSights.filter((s) => !alreadyTakenSights.includes(s))[0]); + const updatedSelectedSight = captureSights.filter( + (s) => !alreadyTakenSights.includes(s), + )[0]; + setSelectedSight(updatedSelectedSight); + analytics.setUserProperties({ + alreadyTakenSights: alreadyTakenSights.length, + totalSights: captureSights.length, + sightSelected: updatedSelectedSight.label, + }); + analytics.setEventsProperties({ totalSights: captureSights.length }); + analytics.trackEvent('Capture Started', { + newInspection: !!alreadyTakenSights, + alreadyTakenSights: alreadyTakenSights.length, + totalSights: captureSights.length, + category: 'capture_started', + }); setLastPictureTaken(getLastPictureTaken(inspectionId, response)); assertInspectionIsValid(inspectionId, response, captureSights, tasksBySight); loading.onSuccess(); @@ -212,9 +246,26 @@ export function usePhotoCaptureSightState({ }, []); const takeSelectedSight = useCallback(() => { - const updatedSightsTaken = [...sightsTaken, selectedSight]; + const isRetake = sightsTaken.includes(selectedSight); + const updatedSightsTaken = isRetake ? sightsTaken : [...sightsTaken, selectedSight]; setSightsTaken(updatedSightsTaken); const nextSight = captureSights.filter((s) => !updatedSightsTaken.includes(s))[0]; + analytics.trackEvent(`Sight Captured`, { + inspectionId, + order: captureSights.indexOf(selectedSight) + 1, + alreadyTakenSights: updatedSightsTaken.length, + totalSights: captureSights.length, + sightId: selectedSight.id, + sightLabel: selectedSight.label, + nextSightId: nextSight?.id ?? null, + nextSightLabel: nextSight?.label ?? null, + retake: isRetake, + category: 'sight_captured', + }); + analytics.setUserProperties({ + alreadyTakenSights: updatedSightsTaken.length, + sightSelected: nextSight?.label ?? null, + }); if (nextSight) { setSelectedSight(nextSight); } else { @@ -226,7 +277,6 @@ export function usePhotoCaptureSightState({ (id: string) => { const sightToRetake = captureSights.find((sight) => sight.id === id); if (sightToRetake) { - setSightsTaken((value) => value.filter((sight) => sight.id !== id)); setSelectedSight(sightToRetake); } }, @@ -237,7 +287,7 @@ export function usePhotoCaptureSightState({ () => ({ selectedSight, sightsTaken, - selectSight: setSelectedSight, + selectSight, takeSelectedSight, lastPictureTaken, setLastPictureTaken, diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts index fdafc1208..38ecb8df1 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts @@ -1,6 +1,7 @@ import { MonkPicture, TaskName } from '@monkvision/types'; import { Queue } from '@monkvision/common'; import { useCallback } from 'react'; +import { useAnalytics } from '@monkvision/analytics'; import { PictureUpload } from './useUploadQueue'; import { AddDamageHandle, PhotoCaptureMode } from './useAddDamageMode'; import { PhotoCaptureSightState } from './usePhotoCaptureSightState'; @@ -42,6 +43,7 @@ export function usePictureTaken({ uploadQueue, tasksBySight, }: UseTakePictureParams): HandleTakePictureFunction { + const { trackEvent } = useAnalytics(); return useCallback( (picture: MonkPicture) => { sightState.setLastPictureTaken(picture); @@ -57,6 +59,11 @@ export function usePictureTaken({ uploadQueue.push(upload); if (addDamageHandle.mode === PhotoCaptureMode.SIGHT) { sightState.takeSelectedSight(); + } else { + trackEvent('Damage Captured', { + mode: addDamageHandle.mode, + category: 'damage_captured', + }); } addDamageHandle.updatePhotoCaptureModeAfterPictureTaken(); }, diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx index 6c7253abc..599d42a9f 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx +++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx @@ -28,6 +28,7 @@ jest.mock('../../src/PhotoCapture/hooks', () => ({ length: 3, })), useStartTasksOnComplete: jest.fn(() => jest.fn()), + useComplianceAnalytics: jest.fn(), })); import { act, render, waitFor } from '@testing-library/react'; diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/useComplianceAnalytics.test.ts b/packages/inspection-capture-web/test/PhotoCapture/hooks/useComplianceAnalytics.test.ts new file mode 100644 index 000000000..3f5d40de2 --- /dev/null +++ b/packages/inspection-capture-web/test/PhotoCapture/hooks/useComplianceAnalytics.test.ts @@ -0,0 +1,63 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { sights } from '@monkvision/sights'; +import { createEmptyMonkState, useMonkState } from '@monkvision/common'; +import { ComplianceIssue, Image, ImageStatus, ImageType, Inspection } from '@monkvision/types'; +import { useComplianceAnalytics, ComplianceAnalyticsParams } from '../../../src/PhotoCapture/hooks'; +import { useAnalytics } from '@monkvision/analytics'; + +function createParams(): ComplianceAnalyticsParams { + return { + inspectionId: 'test-inspection-id', + sights: [ + sights['test-sight-1'], + sights['test-sight-2'], + sights['test-sight-3'], + sights['test-sight-4'], + ], + }; +} + +describe('useComplianceAnalytics hook', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should only track non compliant image', () => { + const initialProps = createParams(); + const state = createEmptyMonkState(); + state.inspections.push({ + id: initialProps.inspectionId, + images: ['image-1', 'image-2', 'image-3'], + } as unknown as Inspection); + state.images.push( + { + id: 'image-1', + inspectionId: initialProps.inspectionId, + type: ImageType.BEAUTY_SHOT, + additionalData: { sight_id: 'sight1' }, + status: ImageStatus.SUCCESS, + } as unknown as Image, + { + id: 'image-2', + inspectionId: initialProps.inspectionId, + type: ImageType.BEAUTY_SHOT, + additionalData: { sight_id: 'sight2' }, + status: ImageStatus.SUCCESS, + } as unknown as Image, + { + id: 'image-3', + inspectionId: initialProps.inspectionId, + type: ImageType.BEAUTY_SHOT, + additionalData: { sight_id: 'sight3' }, + status: ImageStatus.NOT_COMPLIANT, + complianceIssues: [ComplianceIssue.NO_VEHICLE], + } as unknown as Image, + ); + (useMonkState as jest.Mock).mockImplementation(() => ({ state })); + const { unmount } = renderHook(useComplianceAnalytics, { initialProps }); + const { trackEvent } = (useAnalytics as jest.Mock).mock.results[0].value; + expect(trackEvent).toBeCalledTimes(1); + + unmount(); + }); +}); diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts index 215753839..be3ff9389 100644 --- a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts +++ b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureSightState.test.ts @@ -229,14 +229,13 @@ describe('usePhotoCaptureSightState hook', () => { unmount(); }); - it('should remove the sight from the taken sights and select it when retaking a sight', () => { + it('should change the sightSelected when retaking a sight', () => { const initialProps = createParams(); const { result, unmount } = renderHook(usePhotoCaptureSightState, { initialProps }); act(() => result.current.takeSelectedSight()); act(() => result.current.takeSelectedSight()); act(() => result.current.retakeSight('test-sight-1')); - expect(result.current.sightsTaken).not.toContain(sights['test-sight-1']); expect(result.current.selectedSight).toEqual(sights['test-sight-1']); unmount();