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/posthog.ts b/apps/demo-app/src/posthog.ts new file mode 100644 index 000000000..3facb2d48 --- /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_iNzK7jyK2bLtRi9vNWnzQqy74rIPlXPdgGs0qgJrSfL', + 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 6ddd3b11e..d4e1fe923 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx +++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx @@ -2,6 +2,7 @@ import { Camera, CameraHUDProps, CompressionOptions, CameraProps } from '@monkvi import { Sight, TaskName } from '@monkvision/types'; import { useI18nSync, useLoadingState } from '@monkvision/common'; import { ComplianceOptions, MonkAPIConfig } from '@monkvision/network'; +import { useAnalytics } from '@monkvision/analytics'; import { useMonitoring } from '@monkvision/monitoring'; import { PhotoCaptureHUD, PhotoCaptureHUDProps } from './PhotoCaptureHUD'; import { styles } from './PhotoCapture.styles'; @@ -90,6 +91,7 @@ export function PhotoCapture({ }: PhotoCaptureProps) { useI18nSync(lang); const { handleError } = useMonitoring(); + const analytics = useAnalytics(); const loading = useLoadingState(); const addDamageHandle = useAddDamageMode(); const startTasks = useStartTasksOnComplete({ @@ -103,6 +105,11 @@ export function PhotoCapture({ const onLastSightTaken = () => { startTasks() .then(() => { + analytics.trackEvent('inspection completed', { + inspectionId, + haveToCaptureSights: sights.length, + category: 'inspection_completed', + }); 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 df94007ca..52ef95ae6 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, MonkPicture } 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'; @@ -63,6 +64,10 @@ export interface PhotoCaptureHUDProps extends CameraHUDProps { * displayed. */ onClose?: () => void; + /** + * Callback called when the user clicks on the gallery button. + */ + onOpenGallery?: () => void; /** * Boolean indicating if the close button should be displayed in the HUD on top of the Camera preview. * @@ -88,6 +93,7 @@ export function PhotoCaptureHUD({ onCancelAddDamage, onRetry, onClose, + onOpenGallery, showCloseButton, loading, handle, @@ -96,12 +102,25 @@ 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?.(); }; + const handleOpenGallery = () => { + trackEvent('gallery opened', { + inspectionId, + category: 'gallery_opened', + }); + onOpenGallery?.(); + }; + return (
@@ -122,6 +141,7 @@ export function PhotoCaptureHUD({ setShowCloseModal(true)} + onOpenGallery={handleOpenGallery} galleryPreview={lastPictureTaken ?? undefined} closeDisabled={!!loading.error || !!handle.error} galleryDisabled={!!loading.error || !!handle.error} diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/useAddDamageMode.ts index 5662fd1b8..8b953b106 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/usePhotoCaptureSightState.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts index 752aa2749..4e469eab6 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureSightState.ts @@ -5,6 +5,7 @@ import { LoadingState, MonkGotOneInspectionAction, useAsyncEffect } from '@monkv import { Image, Sight, TaskName } from '@monkvision/types'; import { sights } from '@monkvision/sights'; import { MonkPicture } from '@monkvision/camera-web'; +import { useAnalytics } from '@monkvision/analytics'; import { PhotoCaptureErrorName } from '../errors'; /** @@ -162,10 +163,26 @@ 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)?.id ?? null; + + setSelectedSight(s); + analytics.trackEvent(`sight selected`, { + inspectionId, + sight: s.id, + nextSight, + category: 'sight_selected', + }); + }; useAsyncEffect( () => { loading.start(); + analytics.setUserProperties({ inspectionId }); + analytics.setEventsProperties({ inspectionId }); return getInspection(inspectionId); }, [inspectionId, retryCount], @@ -174,9 +191,15 @@ export function usePhotoCaptureSightState({ try { const alreadyTakenSights = getSightsTaken(inspectionId, response); setSightsTaken(alreadyTakenSights); - setSelectedSight(captureSights.filter((s) => !alreadyTakenSights.includes(s))[0]); + selectSight(captureSights.filter((s) => !alreadyTakenSights.includes(s))[0]); setLastPictureTaken(getLastPictureTaken(inspectionId, response)); assertInspectionIsValid(inspectionId, response, captureSights, tasksBySight); + analytics.trackEvent('capture started', { + newInspection: !!alreadyTakenSights, + remainingSights: alreadyTakenSights.length, + haveToCaptureSights: captureSights.length, + category: 'capture_started', + }); loading.onSuccess(); } catch (err) { handleError(err); @@ -198,8 +221,14 @@ export function usePhotoCaptureSightState({ const updatedSightsTaken = [...sightsTaken, selectedSight]; setSightsTaken(updatedSightsTaken); const nextSight = captureSights.filter((s) => !updatedSightsTaken.includes(s))[0]; + analytics.trackEvent(`sight captured`, { + inspectionId, + sight: selectedSight.id, + nextSight: nextSight?.id ?? null, + category: 'take_picture', + }); if (nextSight) { - setSelectedSight(nextSight); + selectSight(nextSight); } else { onLastSightTaken(); } @@ -209,7 +238,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 56eebdffe..9a52c8bbb 100644 --- a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts +++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePictureTaken.ts @@ -2,6 +2,7 @@ import { MonkPicture } from '@monkvision/camera-web'; import { 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'; @@ -43,6 +44,7 @@ export function usePictureTaken({ uploadQueue, tasksBySight, }: UseTakePictureParams): HandleTakePictureFunction { + const { trackEvent } = useAnalytics(); return useCallback( (picture: MonkPicture) => { sightState.setLastPictureTaken(picture); @@ -58,6 +60,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(); },