Skip to content

Commit 6891e13

Browse files
Gabriel Tiragabrieltmonkai
authored andcommitted
Implemented deleteImage API, state and tests
Reset state objects containing the deleted image Updated objects to be deleted in State Updated API export and added more tests Added docs Updated Tests Added provisory getSortedImagesBySight function Removed CleanupObserver Added UploadSuccessPayload
1 parent 7f4e79a commit 6891e13

File tree

9 files changed

+273
-20
lines changed

9 files changed

+273
-20
lines changed

packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
usePhotoCaptureSightTutorial,
4444
useInspectionComplete,
4545
} from './hooks';
46-
// import { SessionTimeTrackerDemo } from '../components/SessionTimeTrackerDemo';
46+
import { useImagesCleanup } from '../hooks/useImagesCleanup';
4747

4848
/**
4949
* Props of the PhotoCapture component.
@@ -218,12 +218,17 @@ export function PhotoCapture({
218218
closeBadConnectionWarningDialog,
219219
uploadEventHandlers: badConnectionWarningUploadEventHandlers,
220220
} = useBadConnectionWarning({ maxUploadDurationWarning });
221+
const { cleanupEventHandlers } = useImagesCleanup({ inspectionId, apiConfig });
221222
const uploadQueue = useUploadQueue({
222223
inspectionId,
223224
apiConfig,
224225
additionalTasks,
225226
complianceOptions,
226-
eventHandlers: [adaptiveUploadEventHandlers, badConnectionWarningUploadEventHandlers],
227+
eventHandlers: [
228+
adaptiveUploadEventHandlers,
229+
badConnectionWarningUploadEventHandlers,
230+
cleanupEventHandlers,
231+
],
227232
});
228233
const images = usePhotoCaptureImages(inspectionId);
229234
const handlePictureTaken = usePictureTaken({

packages/inspection-capture-web/src/hooks/useAdaptiveCameraConfig.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '@monkvision/types';
77
import { useCallback, useMemo, useState } from 'react';
88
import { useObjectMemo } from '@monkvision/common';
9-
import { UploadEventHandlers } from './useUploadQueue';
9+
import { UploadEventHandlers, UploadSuccessPayload } from './useUploadQueue';
1010

1111
const DEFAULT_CAMERA_CONFIG: Required<CameraConfig> = {
1212
quality: 0.6,
@@ -75,8 +75,8 @@ export function useAdaptiveCameraConfig({
7575
setIsImageUpscalingAllowed(false);
7676
};
7777

78-
const onUploadSuccess = useCallback((durationMs: number) => {
79-
if (durationMs > MAX_UPLOAD_DURATION_MS) {
78+
const onUploadSuccess = useCallback(({ durationMs }: UploadSuccessPayload) => {
79+
if (durationMs && durationMs > MAX_UPLOAD_DURATION_MS) {
8080
lowerMaxImageQuality();
8181
}
8282
}, []);

packages/inspection-capture-web/src/hooks/useBadConnectionWarning.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useRef, useState } from 'react';
22
import { useObjectMemo } from '@monkvision/common';
33
import { PhotoCaptureAppConfig } from '@monkvision/types';
4-
import { UploadEventHandlers } from './useUploadQueue';
4+
import { UploadEventHandlers, UploadSuccessPayload } from './useUploadQueue';
55

66
/**
77
* Parameters accepted by the useBadConnectionWarning hook.
@@ -44,8 +44,9 @@ export function useBadConnectionWarning({
4444
);
4545

4646
const onUploadSuccess = useCallback(
47-
(durationMs: number) => {
47+
({ durationMs }: UploadSuccessPayload) => {
4848
if (
49+
durationMs &&
4950
maxUploadDurationWarning >= 0 &&
5051
durationMs > maxUploadDurationWarning &&
5152
!hadDialogBeenDisplayed.current
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useMonkState, useObjectMemo } from '@monkvision/common';
2+
import { MonkApiConfig, useMonkApi } from '@monkvision/network';
3+
import { useCallback } from 'react';
4+
import { Image } from '@monkvision/types';
5+
import { UploadEventHandlers, UploadSuccessPayload } from './useUploadQueue';
6+
7+
/**
8+
* Parameters accepted by the useImagesCleanup hook.
9+
*/
10+
export interface ImagesCleanupParams {
11+
/**
12+
* The inspection ID.
13+
*/
14+
inspectionId: string;
15+
/**
16+
* The api config used to communicate with the API.
17+
*/
18+
apiConfig: MonkApiConfig;
19+
}
20+
21+
/**
22+
* Handle used to manage the images cleanup after a new one uploads.
23+
*/
24+
export interface ImagesCleanupHandle {
25+
/**
26+
* A set of event handlers listening to upload events.
27+
*/
28+
cleanupEventHandlers: UploadEventHandlers;
29+
}
30+
31+
function extractOtherImagesToDelete(imagesBySight: Record<string, Image[]>) {
32+
const imagesToDelete: Image[] = [];
33+
34+
Object.values(imagesBySight)
35+
.filter((images) => images.length > 1)
36+
.forEach((images) => {
37+
const sortedImages = images.sort((a, b) =>
38+
b.createdAt && a.createdAt ? b.createdAt - a.createdAt : 0,
39+
);
40+
imagesToDelete.push(...sortedImages.slice(1));
41+
});
42+
43+
return imagesToDelete;
44+
}
45+
46+
function groupImagesBySightId(images: Image[], sightIdToSkip: string) {
47+
return images.reduce((acc, image) => {
48+
if (!image.sightId || image.sightId === sightIdToSkip) {
49+
return acc;
50+
}
51+
if (!acc[image.sightId]) {
52+
acc[image.sightId] = [];
53+
}
54+
55+
acc[image.sightId].push(image);
56+
return acc;
57+
}, {} as Record<string, Image[]>);
58+
}
59+
60+
/**
61+
* Custom hook used to cleanup sights' images of the inspection by deleting the old ones
62+
* when a new image is added.
63+
*/
64+
export function useImagesCleanup(props: ImagesCleanupParams): ImagesCleanupHandle {
65+
const { deleteImage } = useMonkApi(props.apiConfig);
66+
const { state } = useMonkState();
67+
68+
const onUploadSuccess = useCallback(
69+
({ sightId, imageId }: UploadSuccessPayload) => {
70+
if (!sightId) {
71+
return;
72+
}
73+
74+
const otherImagesToDelete = extractOtherImagesToDelete(
75+
groupImagesBySightId(state.images, sightId),
76+
);
77+
78+
const sightImagesToDelete = state.images.filter(
79+
(image) =>
80+
image.inspectionId === props.inspectionId &&
81+
image.sightId === sightId &&
82+
image.id !== imageId,
83+
);
84+
85+
const imagesToDelete = [...otherImagesToDelete, ...sightImagesToDelete];
86+
87+
if (imagesToDelete.length > 0) {
88+
imagesToDelete.forEach((image) =>
89+
deleteImage({ imageId: image.id, id: props.inspectionId }),
90+
);
91+
}
92+
},
93+
[state.images, props.inspectionId],
94+
);
95+
96+
return useObjectMemo({
97+
cleanupEventHandlers: {
98+
onUploadSuccess,
99+
},
100+
});
101+
}

packages/inspection-capture-web/src/hooks/useUploadQueue.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,32 @@ import { useRef } from 'react';
1111
import { useMonitoring } from '@monkvision/monitoring';
1212
import { CaptureMode } from '../types';
1313

14+
/**
15+
* Payload for the onUploadSuccess event handler.
16+
*/
17+
export interface UploadSuccessPayload {
18+
/**
19+
* The total elapsed time in milliseconds between the start of the upload and the end of the upload.
20+
*/
21+
durationMs?: number;
22+
/**
23+
* The sight ID associated with the uploaded picture, if applicable.
24+
*/
25+
sightId?: string;
26+
/**
27+
* The ID of the uploaded image.
28+
*/
29+
imageId?: string;
30+
}
31+
1432
/**
1533
* Type definition for upload event handlers.
1634
*/
1735
export interface UploadEventHandlers {
1836
/**
1937
* Callback called when a picture upload successfully completes.
20-
*
21-
* @param durationMs The total elapsed time in milliseconds between the start of the upload and the end of the upload.
2238
*/
23-
onUploadSuccess?: (durationMs: number) => void;
39+
onUploadSuccess?: (payload: UploadSuccessPayload) => void;
2440
/**
2541
* Callback called when a picture upload fails because of a timeout.
2642
*/
@@ -193,7 +209,7 @@ export function useUploadQueue({
193209
}
194210
try {
195211
const startTs = Date.now();
196-
await addImage(
212+
const result = await addImage(
197213
createAddImageOptions(
198214
upload,
199215
inspectionId,
@@ -205,7 +221,14 @@ export function useUploadQueue({
205221
),
206222
);
207223
const uploadDurationMs = Date.now() - startTs;
208-
eventHandlers?.forEach((handlers) => handlers.onUploadSuccess?.(uploadDurationMs));
224+
const sightId = upload.mode === CaptureMode.SIGHT ? upload.sightId : undefined;
225+
eventHandlers?.forEach((handlers) =>
226+
handlers.onUploadSuccess?.({
227+
durationMs: uploadDurationMs,
228+
sightId,
229+
imageId: result?.image?.id,
230+
}),
231+
);
209232
} catch (err) {
210233
if (
211234
err instanceof Error &&

packages/inspection-capture-web/test/hooks/useAdaptiveCameraConfig.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('useAdaptiveCameraConfigTest hook', () => {
4545
expect(result.current.adaptiveCameraConfig.resolution).toEqual(
4646
initialProps.initialCameraConfig.resolution,
4747
);
48-
act(() => result.current.uploadEventHandlers.onUploadSuccess?.(15001));
48+
act(() => result.current.uploadEventHandlers.onUploadSuccess?.({ durationMs: 15001 }));
4949
expect(result.current.adaptiveCameraConfig.resolution).toEqual(CameraResolution.QHD_2K);
5050
expect(result.current.adaptiveCameraConfig.quality).toEqual(0.6);
5151
expect(result.current.adaptiveCameraConfig.allowImageUpscaling).toEqual(false);
@@ -63,7 +63,7 @@ describe('useAdaptiveCameraConfigTest hook', () => {
6363
expect(result.current.adaptiveCameraConfig.resolution).toEqual(
6464
initialProps.initialCameraConfig.resolution,
6565
);
66-
act(() => result.current.uploadEventHandlers.onUploadSuccess?.(200));
66+
act(() => result.current.uploadEventHandlers.onUploadSuccess?.({ durationMs: 200 }));
6767
expect(result.current.adaptiveCameraConfig).toEqual(
6868
expect.objectContaining(initialProps.initialCameraConfig),
6969
);

packages/inspection-capture-web/test/hooks/useBadConnectionWarning.test.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ describe('useBadConnectionWarning hook', () => {
2626
});
2727

2828
act(() => {
29-
result.current.uploadEventHandlers.onUploadSuccess?.(maxUploadDurationWarning + 1);
29+
result.current.uploadEventHandlers.onUploadSuccess?.({
30+
durationMs: maxUploadDurationWarning + 1,
31+
});
3032
});
3133
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(true);
3234

@@ -40,7 +42,9 @@ describe('useBadConnectionWarning hook', () => {
4042
});
4143

4244
act(() => {
43-
result.current.uploadEventHandlers.onUploadSuccess?.(maxUploadDurationWarning - 1);
45+
result.current.uploadEventHandlers.onUploadSuccess?.({
46+
durationMs: maxUploadDurationWarning - 1,
47+
});
4448
});
4549
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(false);
4650

@@ -68,7 +72,7 @@ describe('useBadConnectionWarning hook', () => {
6872
});
6973

7074
act(() => {
71-
result.current.uploadEventHandlers.onUploadSuccess?.(100000);
75+
result.current.uploadEventHandlers.onUploadSuccess?.({ durationMs: 100000 });
7276
result.current.uploadEventHandlers.onUploadTimeout?.();
7377
});
7478
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(false);
@@ -87,7 +91,9 @@ describe('useBadConnectionWarning hook', () => {
8791
});
8892
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(true);
8993
act(() => {
90-
result.current.uploadEventHandlers.onUploadSuccess?.(maxUploadDurationWarning + 1);
94+
result.current.uploadEventHandlers.onUploadSuccess?.({
95+
durationMs: maxUploadDurationWarning + 1,
96+
});
9197
});
9298
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(true);
9399

@@ -131,7 +137,9 @@ describe('useBadConnectionWarning hook', () => {
131137
});
132138
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(false);
133139
act(() => {
134-
result.current.uploadEventHandlers.onUploadSuccess?.(maxUploadDurationWarning + 1);
140+
result.current.uploadEventHandlers.onUploadSuccess?.({
141+
durationMs: maxUploadDurationWarning + 1,
142+
});
135143
});
136144
expect(result.current.isBadConnectionWarningDialogDisplayed).toBe(false);
137145

0 commit comments

Comments
 (0)