From 41177b97a5b0d88e643f9ef61dfe6d5fd3e75bf5 Mon Sep 17 00:00:00 2001
From: Samy Ouyahia <103439265+souyahia-monk@users.noreply.github.com>
Date: Tue, 30 Apr 2024 17:36:53 +0200
Subject: [PATCH] Fix SightSlider icons & some Drive fixes (#728)
* Disable login and inspection creation in Drive app
* Added HinL task in create inspection in Drive App
* Fixed SightSlider status icons
---
.../CreateInspectionPage.module.css | 1 +
apps/drive-app/.env-cmdrc.json | 1 +
.../CreateInspectionPage.module.css | 6 +-
.../CreateInspectionPage.tsx | 51 ++++--
.../src/pages/LogInPage/LogInPage.tsx | 12 +-
apps/drive-app/src/translations/de.json | 6 +-
apps/drive-app/src/translations/en.json | 6 +-
apps/drive-app/src/translations/fr.json | 6 +-
.../test/pages/CreateInspectionPage.test.tsx | 57 +------
apps/drive-app/test/pages/LogInPage.test.tsx | 148 +-----------------
.../src/PhotoCapture/PhotoCapture.tsx | 3 +
.../PhotoCaptureHUD/PhotoCaptureHUD.tsx | 21 ++-
.../PhotoCaptureHUDPreview.tsx | 12 +-
.../PhotoCaptureHUDPreviewSight.tsx | 14 +-
.../SightSlider/SightSlider.tsx | 26 ++-
.../PhotoCaptureHUD/hooks/index.ts | 1 -
.../hooks/useComplianceNotification.ts | 19 ---
.../src/PhotoCapture/hooks/index.ts | 1 +
.../hooks/usePhotoCaptureImages.ts | 15 ++
.../test/PhotoCapture/PhotoCapture.test.tsx | 36 +++--
.../PhotoCaptureHUD/PhotoCaptureHUD.test.tsx | 34 ++--
.../PhotoCaptureHUDPreview.test.tsx | 8 +-
.../PhotoCaptureHUDPreviewSight.test.tsx | 9 +-
.../SightSlider/SightSlider.test.tsx | 37 +----
.../hooks/useComplianceNotification.test.ts | 98 ------------
.../hooks/usePhotoCaptureImages.test.ts | 24 +++
.../network/src/api/inspection/mappers.ts | 60 ++++++-
packages/network/src/api/models/task.ts | 8 +-
28 files changed, 291 insertions(+), 429 deletions(-)
delete mode 100644 packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.ts
create mode 100644 packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts
delete mode 100644 packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.test.ts
create mode 100644 packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts
diff --git a/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css b/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
index 8b5fde5ec..d6d8803dc 100644
--- a/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
+++ b/apps/demo-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
@@ -4,6 +4,7 @@
display: flex;
align-items: center;
justify-content: center;
+ flex-direction: column;
}
.error-message {
diff --git a/apps/drive-app/.env-cmdrc.json b/apps/drive-app/.env-cmdrc.json
index e884c01aa..5e7484998 100644
--- a/apps/drive-app/.env-cmdrc.json
+++ b/apps/drive-app/.env-cmdrc.json
@@ -3,6 +3,7 @@
"PORT": "17200",
"HTTPS": "true",
"ESLINT_NO_DEV_ERRORS": "true",
+ "REACT_APP_ALLOW_CREATE_INSPECTION": "true",
"REACT_APP_ENVIRONMENT": "local",
"REACT_APP_API_DOMAIN": "api.preview.monk.ai/v1",
"REACT_APP_AUTH_DOMAIN": "idp.preview.monk.ai",
diff --git a/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css b/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
index 8b5fde5ec..902f8b37f 100644
--- a/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
+++ b/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.module.css
@@ -4,9 +4,13 @@
display: flex;
align-items: center;
justify-content: center;
+ flex-direction: column;
}
.error-message {
text-align: center;
- padding-bottom: 20px;
+}
+
+.retry-btn-container {
+ padding-top: 20px;
}
diff --git a/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx b/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx
index befea8dd7..4bbf944b3 100644
--- a/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx
+++ b/apps/drive-app/src/pages/CreateInspectionPage/CreateInspectionPage.tsx
@@ -2,13 +2,35 @@ import { useTranslation } from 'react-i18next';
import { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { Button, Spinner } from '@monkvision/common-ui-web';
-import { useMonkApi } from '@monkvision/network';
+import { CreateInspectionOptions, useMonkApi } from '@monkvision/network';
import { getEnvOrThrow, useLoadingState, useMonkAppParams } from '@monkvision/common';
import { useMonitoring } from '@monkvision/monitoring';
import { TaskName } from '@monkvision/types';
import { Page } from '../pages';
import styles from './CreateInspectionPage.module.css';
+const options: CreateInspectionOptions = {
+ tasks: [
+ TaskName.DAMAGE_DETECTION,
+ TaskName.WHEEL_ANALYSIS,
+ {
+ name: TaskName.HUMAN_IN_THE_LOOP,
+ callbacks: [
+ {
+ url: 'https://webhook.site/15f8682f-91a8-4df0-8e73-74adc6c74ca4',
+ headers: {},
+ params: {},
+ },
+ ],
+ },
+ ],
+};
+
+enum CreateInspectionPageError {
+ CREATE_INSPECTION = 'create-inspection.errors.create-inspection',
+ MISSING_INSPECTION_ID = 'create-inspection.errors.missing-inspection-id',
+}
+
export function CreateInspectionPage() {
const loading = useLoadingState();
const { t } = useTranslation();
@@ -21,21 +43,26 @@ export function CreateInspectionPage() {
const handleCreateInspection = () => {
loading.start();
- createInspection({ tasks: [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS] })
+ createInspection(options)
.then((res) => {
loading.onSuccess();
setInspectionId(res.id);
})
.catch((err) => {
- loading.onError(err);
+ loading.onError(CreateInspectionPageError.CREATE_INSPECTION);
handleError(err);
});
};
useEffect(() => {
if (!inspectionId) {
- loading.start();
- handleCreateInspection();
+ if (process.env['REACT_APP_ALLOW_CREATE_INSPECTION'] === 'true') {
+ // On local environment, we allow creating the inspection if the ID is not provided.
+ loading.start();
+ handleCreateInspection();
+ } else {
+ loading.onError(CreateInspectionPageError.MISSING_INSPECTION_ID);
+ }
}
}, [inspectionId]);
@@ -48,12 +75,14 @@ export function CreateInspectionPage() {
{loading.isLoading && }
{!loading.isLoading && loading.error && (
<>
-
- {t('create-inspection.errors.create-inspection')}
-
-
+ {t(loading.error)}
+ {loading.error === CreateInspectionPageError.CREATE_INSPECTION && (
+
+
+
+ )}
>
)}
diff --git a/apps/drive-app/src/pages/LogInPage/LogInPage.tsx b/apps/drive-app/src/pages/LogInPage/LogInPage.tsx
index 5b5fd5cb1..f8d8f63a6 100644
--- a/apps/drive-app/src/pages/LogInPage/LogInPage.tsx
+++ b/apps/drive-app/src/pages/LogInPage/LogInPage.tsx
@@ -18,6 +18,8 @@ function getLoginErrorMessage(err: unknown): string {
return 'login.errors.unknown';
}
+const allowCreateInspection = process.env['REACT_APP_ALLOW_CREATE_INSPECTION'] === 'true';
+
export function LogInPage() {
const [isExpired, setIsExpired] = useState(false);
const loading = useLoadingState();
@@ -28,6 +30,11 @@ export function LogInPage() {
const navigate = useNavigate();
useEffect(() => {
+ if (!authToken && !allowCreateInspection) {
+ setAuthToken(null);
+ setIsExpired(false);
+ loading.onError('login.errors.missing-token');
+ }
if (authToken && !isUserAuthorized(authToken, REQUIRED_AUTHORIZATIONS)) {
loading.onError('login.errors.insufficient-authorization');
}
@@ -62,11 +69,12 @@ export function LogInPage() {
{t('login.errors.token-expired')}
)}
{loading.error && {t(loading.error)}
}
- {authToken ? (
+ {authToken && allowCreateInspection && (
- ) : (
+ )}
+ {!authToken && allowCreateInspection && (
diff --git a/apps/drive-app/src/translations/de.json b/apps/drive-app/src/translations/de.json
index 63276e924..5d0593a5b 100644
--- a/apps/drive-app/src/translations/de.json
+++ b/apps/drive-app/src/translations/de.json
@@ -8,13 +8,15 @@
"popup-closed": "Huch! Wir konnten Sie nicht anmelden, weil das Popup geschlossen wurde. Versuchen wir es noch einmal!",
"token-expired": "Ihr Authentifizierungstoken ist abgelaufen. Bitte melden Sie sich erneut an.",
"insufficient-authorization": "Sie haben nicht die erforderlichen Berechtigungen, um diese Anwendung zu nutzen. Bitte melden Sie sich ab und verwenden Sie ein anderes Konto.",
- "unknown": "Huch! Beim Einloggen ist ein unerwarteter Fehler aufgetreten. Versuchen wir es noch einmal!"
+ "unknown": "Huch! Beim Einloggen ist ein unerwarteter Fehler aufgetreten. Versuchen wir es noch einmal!",
+ "missing-token": "Die URL, auf die Sie zuzugreifen versuchen, ist ungültig."
}
},
"create-inspection": {
"errors": {
"retry": "Wiederholung",
- "create-inspection": "Bei der Erstellung der Inspektion ist ein unerwarteter Fehler aufgetreten."
+ "create-inspection": "Bei der Erstellung der Inspektion ist ein unerwarteter Fehler aufgetreten.",
+ "missing-inspection-id": "Die URL, auf die Sie zuzugreifen versuchen, ist ungültig."
}
},
"inspection-complete": {
diff --git a/apps/drive-app/src/translations/en.json b/apps/drive-app/src/translations/en.json
index 2bb81663f..f023597ee 100644
--- a/apps/drive-app/src/translations/en.json
+++ b/apps/drive-app/src/translations/en.json
@@ -8,13 +8,15 @@
"popup-closed": "Oops! We couldn't log you in because the popup was closed. Let's try again!",
"token-expired": "Your authentication token is expired. Please log-in again.",
"insufficient-authorization": "You do not have the required authorizations to use this application. Please log out and use a different account.",
- "unknown": "Oops! An unexpected error occurred during the log in. Let's try again!"
+ "unknown": "Oops! An unexpected error occurred during the log in. Let's try again!",
+ "missing-token": "The URL you are trying to access is invalid."
}
},
"create-inspection": {
"errors": {
"retry": "Retry",
- "create-inspection": "An unexpected error occurred while creating the inspection."
+ "create-inspection": "An unexpected error occurred while creating the inspection.",
+ "missing-inspection-id": "The URL you are trying to access is invalid."
}
},
"inspection-complete": {
diff --git a/apps/drive-app/src/translations/fr.json b/apps/drive-app/src/translations/fr.json
index 36b3ec0c7..72669631f 100644
--- a/apps/drive-app/src/translations/fr.json
+++ b/apps/drive-app/src/translations/fr.json
@@ -8,13 +8,15 @@
"popup-closed": "Oups ! Nous n'avons pas pu vous connecter car la pop-up s'est fermée. Essayons à nouveau !",
"token-expired": "Votre token d'authentification est expiré. Veuillez vous reconnecter.",
"insufficient-authorization": "Vous n'avez pas les autorisations nécessaires pour utiliser cette application. Veuillez vous déconnecter et utiliser un autre compte.",
- "unknown": "Oups ! Une erreur inattendue est survenue lors de la connection. Essayons à nouveau !"
+ "unknown": "Oups ! Une erreur inattendue est survenue lors de la connection. Essayons à nouveau !",
+ "missing-token": "L'URL à laquelle vous essayez d'accéder est invalide."
}
},
"create-inspection": {
"errors": {
"retry": "Réessayer",
- "create-inspection": "Une erreur inattendue est survenue lors de la création de l'inspection."
+ "create-inspection": "Une erreur inattendue est survenue lors de la création de l'inspection.",
+ "missing-inspection-id": "L'URL à laquelle vous essayez d'accéder est invalide."
}
},
"inspection-complete": {
diff --git a/apps/drive-app/test/pages/CreateInspectionPage.test.tsx b/apps/drive-app/test/pages/CreateInspectionPage.test.tsx
index 051f5a844..4fd316a3d 100644
--- a/apps/drive-app/test/pages/CreateInspectionPage.test.tsx
+++ b/apps/drive-app/test/pages/CreateInspectionPage.test.tsx
@@ -1,11 +1,7 @@
import { render, screen, waitFor } from '@testing-library/react';
import { Navigate } from 'react-router-dom';
-import { act } from 'react-dom/test-utils';
import { useLoadingState, useMonkAppParams } from '@monkvision/common';
import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { useMonkApi } from '@monkvision/network';
-import { TaskName } from '@monkvision/types';
-import { Button } from '@monkvision/common-ui-web';
import { CreateInspectionPage, Page } from '../../src/pages';
const appParams = {
@@ -31,63 +27,18 @@ describe('CreateInspection page', () => {
unmount();
});
- it('should create the inspection and then redirect to the PhotoCapture page if the inspectionId is not defined', async () => {
- const id = 'test-id-test';
- const createInspection = jest.fn(() => Promise.resolve({ id }));
- (useMonkApi as jest.Mock).mockImplementation(() => ({ createInspection }));
- (useMonkAppParams as jest.Mock).mockImplementation(() => appParams);
- const { unmount } = render();
-
- expect(createInspection).toHaveBeenCalledWith({
- tasks: [TaskName.DAMAGE_DETECTION, TaskName.WHEEL_ANALYSIS],
- });
- await waitFor(() => {
- expect(appParams.setInspectionId).toHaveBeenCalledWith(id);
- });
-
- unmount();
- });
-
- it('should display an error message if the API call fails', async () => {
- const createInspection = jest.fn(() => Promise.reject());
- (useMonkApi as jest.Mock).mockImplementation(() => ({ createInspection }));
- (useMonkAppParams as jest.Mock).mockImplementation(() => appParams);
+ it('should display an error message if the inspection ID is not defined', async () => {
+ (useMonkAppParams as jest.Mock).mockImplementation(() => ({ inspectionId: null }));
const onError = jest.fn();
const error = 'test-error';
(useLoadingState as jest.Mock).mockImplementation(() => ({ onError, error, start: jest.fn() }));
const { unmount } = render();
await waitFor(() => {
- expect(appParams.setInspectionId).not.toHaveBeenCalled();
- expect(onError).toHaveBeenCalled();
- expect(screen.queryByText('create-inspection.errors.create-inspection')).not.toBeNull();
+ expect(onError).toHaveBeenCalledWith('create-inspection.errors.missing-inspection-id');
+ expect(screen.getByText(error)).not.toBeNull();
});
unmount();
});
-
- it('should display a retry button if the API call fails', async () => {
- const createInspection = jest.fn(() => Promise.reject());
- (useMonkApi as jest.Mock).mockImplementation(() => ({ createInspection }));
- (useMonkAppParams as jest.Mock).mockImplementation(() => appParams);
- (useLoadingState as jest.Mock).mockImplementation(() => ({
- onError: jest.fn(),
- error: 'test-error',
- start: jest.fn(),
- }));
- const { unmount } = render();
-
- expectPropsOnChildMock(Button, {
- variant: 'outline',
- icon: 'refresh',
- onClick: expect.any(Function),
- });
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
-
- createInspection.mockClear();
- act(() => onClick());
- expect(createInspection).toHaveBeenCalled();
-
- unmount();
- });
});
diff --git a/apps/drive-app/test/pages/LogInPage.test.tsx b/apps/drive-app/test/pages/LogInPage.test.tsx
index 1ab2088e2..6b274f003 100644
--- a/apps/drive-app/test/pages/LogInPage.test.tsx
+++ b/apps/drive-app/test/pages/LogInPage.test.tsx
@@ -1,11 +1,7 @@
-import { act } from 'react-dom/test-utils';
import { useNavigate } from 'react-router-dom';
import { render, screen, waitFor } from '@testing-library/react';
import { useLoadingState, useMonkAppParams } from '@monkvision/common';
-import { isTokenExpired, isUserAuthorized, useAuth } from '@monkvision/network';
-import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { Button } from '@monkvision/common-ui-web';
-import { LogInPage, Page } from '../../src/pages';
+import { LogInPage } from '../../src/pages';
const appParams = {
authToken: 'test-auth-token',
@@ -18,160 +14,22 @@ describe('Log In page', () => {
jest.clearAllMocks();
});
- it('should display a login button on the screen', async () => {
+ it('should display an error message if the token is not defined', async () => {
(useMonkAppParams as jest.Mock).mockImplementation(() => ({ ...appParams, authToken: null }));
- const { unmount } = render();
-
- expectPropsOnChildMock(Button, {
- onClick: expect.any(Function),
- children: 'login.actions.log-in',
- });
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
- expect(useAuth).toHaveBeenCalled();
- const { login } = (useAuth as jest.Mock).mock.results[0].value;
-
- act(() => onClick());
- expect(login).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should redirect to the PhotoCapture page after the login if the inspectionId is defined', async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => ({ ...appParams, authToken: null }));
- const { unmount } = render();
-
- expect(Button).toHaveBeenCalled();
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
- expect(useNavigate).toHaveBeenCalled();
- const navigate = (useNavigate as jest.Mock).mock.results[0].value;
-
- act(() => onClick());
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(Page.PHOTO_CAPTURE);
- });
-
- unmount();
- });
-
- it('should redirect to the CreateInspection page after the login if the inspectionId is not defined', async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => ({
- ...appParams,
- authToken: null,
- inspectionId: null,
- }));
- const { unmount } = render();
-
- expect(Button).toHaveBeenCalled();
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
- expect(useNavigate).toHaveBeenCalled();
- const navigate = (useNavigate as jest.Mock).mock.results[0].value;
-
- act(() => onClick());
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(Page.CREATE_INSPECTION);
- });
-
- unmount();
- });
-
- it('should not redirect after log in if the user does not have sufficient authorization', async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => ({ ...appParams, authToken: null }));
- (isUserAuthorized as jest.Mock).mockImplementation(() => false);
const onError = jest.fn();
const error = 'test-error';
(useLoadingState as jest.Mock).mockImplementation(() => ({ onError, error, start: jest.fn() }));
const { unmount } = render();
- expect(Button).toHaveBeenCalled();
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
expect(useNavigate).toHaveBeenCalled();
const navigate = (useNavigate as jest.Mock).mock.results[0].value;
- act(() => onClick());
await waitFor(() => {
- expect(onError).toHaveBeenCalledWith('login.errors.insufficient-authorization');
+ expect(onError).toHaveBeenCalledWith('login.errors.missing-token');
expect(navigate).not.toHaveBeenCalled();
expect(screen.queryByText(error)).not.toBeNull();
});
unmount();
});
-
- it('should display an error message and a log out button if the user does not have sufficient authorizations', async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => appParams);
- (isUserAuthorized as jest.Mock).mockImplementation(() => false);
- const onError = jest.fn();
- const error = 'test-error';
- (useLoadingState as jest.Mock).mockImplementation(() => ({ onError, error }));
- const { unmount } = render();
-
- expect(onError).toHaveBeenCalledWith('login.errors.insufficient-authorization');
- expect(screen.queryByText(error)).not.toBeNull();
-
- expectPropsOnChildMock(Button, {
- primaryColor: 'alert',
- onClick: expect.any(Function),
- children: 'login.actions.log-out',
- });
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
- expect(useAuth).toHaveBeenCalled();
- const { logout } = (useAuth as jest.Mock).mock.results[0].value;
- expect(logout).not.toHaveBeenCalled();
-
- await act(() => onClick());
- expect(logout).toHaveBeenCalled();
-
- unmount();
- });
-
- it('should display an error message on the screen if the token was expired', async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => appParams);
- (isTokenExpired as jest.Mock).mockImplementation(() => true);
- const { unmount } = render();
-
- expect(isTokenExpired).toHaveBeenCalledWith(appParams.authToken);
- expect(appParams.setAuthToken).toHaveBeenCalledWith(null);
- expect(screen.queryByText('login.errors.token-expired')).not.toBeNull();
-
- unmount();
- });
-
- [
- {
- testCase: 'when the user closes the log in pop up',
- err: new Error('Popup closed'),
- label: 'login.errors.popup-closed',
- },
- {
- testCase: 'when an unexpected error occurrs during the log in',
- err: new Error(),
- label: 'login.errors.unknown',
- },
- ].forEach(({ testCase, err, label }) => {
- it(`should not redirect and display the proper error message ${testCase}`, async () => {
- (useMonkAppParams as jest.Mock).mockImplementation(() => ({ ...appParams, authToken: null }));
- (useAuth as jest.Mock).mockImplementation(() => ({
- login: jest.fn(() => Promise.reject(err)),
- }));
- const onError = jest.fn();
- const error = 'test-error';
- (useLoadingState as jest.Mock).mockImplementation(() => ({
- onError,
- error,
- start: jest.fn(),
- }));
- const { unmount } = render();
-
- expect(Button).toHaveBeenCalled();
- const { onClick } = (Button as unknown as jest.Mock).mock.calls[0][0];
-
- act(() => onClick());
- await waitFor(() => {
- expect(onError).toHaveBeenCalledWith(label);
- expect(screen.queryByText(error)).not.toBeNull();
- });
-
- unmount();
- });
- });
});
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
index 1676d2cee..1e311cb13 100644
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
+++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCapture.tsx
@@ -13,6 +13,7 @@ import {
import { useTranslation } from 'react-i18next';
import {
useAddDamageMode,
+ usePhotoCaptureImages,
usePhotoCaptureSightState,
usePictureTaken,
useStartTasksOnComplete,
@@ -149,6 +150,7 @@ export function PhotoCapture({
complianceIssues,
useLiveCompliance,
});
+ const images = usePhotoCaptureImages(inspectionId);
const handlePictureTaken = usePictureTaken({
sightState,
addDamageHandle,
@@ -201,6 +203,7 @@ export function PhotoCapture({
onClose,
inspectionId,
showCloseButton,
+ images,
};
return (
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx
index 957dada44..155f3066d 100644
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx
+++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.tsx
@@ -1,11 +1,11 @@
-import { useState } from 'react';
-import { Sight, MonkPicture } from '@monkvision/types';
+import { useMemo, useState } from 'react';
+import { Sight, MonkPicture, Image, ImageStatus } from '@monkvision/types';
import { useTranslation } from 'react-i18next';
import { BackdropDialog } from '@monkvision/common-ui-web';
import { CameraHUDProps } from '@monkvision/camera-web';
import { LoadingState } from '@monkvision/common';
import { PhotoCaptureHUDButtons } from './PhotoCaptureHUDButtons';
-import { useComplianceNotification, usePhotoCaptureHUDStyle } from './hooks';
+import { usePhotoCaptureHUDStyle } from './hooks';
import { PhotoCaptureMode } from '../hooks';
import { PhotoCaptureHUDOverlay } from './PhotoCaptureHUDOverlay';
import { PhotoCaptureHUDPreview } from './PhotoCaptureHUDPreview';
@@ -73,6 +73,10 @@ export interface PhotoCaptureHUDProps extends CameraHUDProps {
* @default false
*/
showCloseButton?: boolean;
+ /**
+ * The current images taken by the user (ignoring retaken pictures etc.).
+ */
+ images: Image[];
}
/**
@@ -97,11 +101,18 @@ export function PhotoCaptureHUD({
loading,
handle,
cameraPreview,
+ images,
}: PhotoCaptureHUDProps) {
const { t } = useTranslation();
const [showCloseModal, setShowCloseModal] = useState(false);
const style = usePhotoCaptureHUDStyle();
- const showGalleryBadge = useComplianceNotification(inspectionId);
+ const showGalleryBadge = useMemo(
+ () =>
+ images.some((image) =>
+ [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED].includes(image.status),
+ ),
+ [images],
+ );
const handleCloseConfirm = () => {
setShowCloseModal(false);
@@ -113,7 +124,6 @@ export function PhotoCaptureHUD({
);
}
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx
index 0be423b87..9fc21d71c 100644
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx
+++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.tsx
@@ -1,4 +1,4 @@
-import { PixelDimensions, Sight } from '@monkvision/types';
+import { Image, PixelDimensions, Sight } from '@monkvision/types';
import { SightOverlay } from '@monkvision/common-ui-web';
import { SightSlider } from './SightSlider';
import { styles } from './PhotoCaptureHUDPreviewSight.styles';
@@ -11,10 +11,6 @@ import { PhotoCaptureMode } from '../../hooks';
* Props of the PhotoCaptureHUDPreviewSight component.
*/
export interface PhotoCaptureHUDSightPreviewProps {
- /**
- * The ID of the current inspection.
- */
- inspectionId: string;
/**
* The list of sights provided to the PhotoCapture component.
*/
@@ -39,6 +35,10 @@ export interface PhotoCaptureHUDSightPreviewProps {
* The dimensions of the Camera video stream.
*/
streamDimensions?: PixelDimensions | null;
+ /**
+ * The current images taken by the user (ignoring retaken pictures etc.).
+ */
+ images: Image[];
}
/**
@@ -46,13 +46,13 @@ export interface PhotoCaptureHUDSightPreviewProps {
* mode is SIGHT.
*/
export function PhotoCaptureHUDPreviewSight({
- inspectionId,
sights,
selectedSight,
onSelectedSight = () => {},
onAddDamage = () => {},
sightsTaken,
streamDimensions,
+ images,
}: PhotoCaptureHUDSightPreviewProps) {
const style = usePhotoCaptureHUDSightPreviewStyle();
const aspectRatio = `${streamDimensions?.width}/${streamDimensions?.height}`;
@@ -71,11 +71,11 @@ export function PhotoCaptureHUDPreviewSight({
);
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.tsx b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.tsx
index 38b1563c5..f7638043c 100644
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.tsx
+++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.tsx
@@ -1,6 +1,6 @@
-import { ImageStatus, Sight } from '@monkvision/types';
+import { Image, ImageStatus, Sight } from '@monkvision/types';
import { RefObject, useEffect, useMemo, useRef } from 'react';
-import { useMonkState, useSightLabel } from '@monkvision/common';
+import { useSightLabel } from '@monkvision/common';
import { labels } from '@monkvision/sights';
import { SightSliderButton } from './SightSliderButton';
import { useSightSliderStyles } from './hooks';
@@ -9,10 +9,6 @@ import { useSightSliderStyles } from './hooks';
* Props of the SightSlider component.
*/
export interface SightSliderProps {
- /**
- * The ID of the current inspection.
- */
- inspectionId: string;
/**
* The list of sights provided to the PhotoCapture component.
*/
@@ -29,6 +25,10 @@ export interface SightSliderProps {
* Callback called when the user manually select a new sight.
*/
onSelectedSight?: (sight: Sight) => void;
+ /**
+ * The current images taken by the user (ignoring retaken pictures etc.).
+ */
+ images: Image[];
}
const scrollToSelectedSight = (
@@ -49,18 +49,14 @@ interface SightSliderItem {
status?: ImageStatus;
}
-function useSightSliderItems(inspectionId: string, sights: Sight[]): SightSliderItem[] {
- const { state } = useMonkState();
+function useSightSliderItems(sights: Sight[], images: Image[]): SightSliderItem[] {
return useMemo(
() =>
sights.map((sight) => ({
sight,
- status: state.images.find(
- (image) =>
- image.inspectionId === inspectionId && image.additionalData?.sight_id === sight.id,
- )?.status,
+ status: images.find((image) => image.additionalData?.sight_id === sight.id)?.status,
})),
- [state.images, sights],
+ [sights, images],
);
}
@@ -70,13 +66,13 @@ function useSightSliderItems(inspectionId: string, sights: Sight[]): SightSlider
* sight.
*/
export function SightSlider({
- inspectionId,
sights,
selectedSight,
sightsTaken,
onSelectedSight = () => {},
+ images,
}: SightSliderProps) {
- const items = useSightSliderItems(inspectionId, sights);
+ const items = useSightSliderItems(sights, images);
const { label } = useSightLabel({ labels });
const { container } = useSightSliderStyles();
const ref = useRef(null);
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts
index 6b6b8297d..3153a7936 100644
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts
+++ b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/index.ts
@@ -1,3 +1,2 @@
export * from './usePhotoCaptureHUDStyle';
export * from './usePhotoCaptureHUDButtonBackground';
-export * from './useComplianceNotification';
diff --git a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.ts b/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.ts
deleted file mode 100644
index d537fc139..000000000
--- a/packages/inspection-capture-web/src/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { ImageStatus } from '@monkvision/types';
-import { getInspectionImages, useMonkState } from '@monkvision/common';
-import { useMemo } from 'react';
-
-/**
- * Custom hook used to check if the notification indicating if there are some pictures to be retaken should be displayed
- * or not.
- */
-export function useComplianceNotification(inspectionId: string): boolean {
- const { state } = useMonkState();
-
- return useMemo(
- () =>
- getInspectionImages(inspectionId, state.images, true).some((image) =>
- [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED].includes(image.status),
- ),
- [state, inspectionId],
- );
-}
diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/index.ts
index 0fc6cf00d..f9d117ed9 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 './usePhotoCaptureImages';
diff --git a/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts
new file mode 100644
index 000000000..24de30bb2
--- /dev/null
+++ b/packages/inspection-capture-web/src/PhotoCapture/hooks/usePhotoCaptureImages.ts
@@ -0,0 +1,15 @@
+import { Image } from '@monkvision/types';
+import { getInspectionImages, useMonkState } from '@monkvision/common';
+import { useMemo } from 'react';
+
+/**
+ * Custom hook that returns the current images taken in the inspection (it filters retakes etc.).
+ */
+export function usePhotoCaptureImages(inspectionId: string): Image[] {
+ const { state } = useMonkState();
+
+ return useMemo(
+ () => getInspectionImages(inspectionId, state.images, true),
+ [state.images, inspectionId],
+ );
+}
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
index 5e2ddf067..d3e1986b6 100644
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
+++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCapture.test.tsx
@@ -1,4 +1,20 @@
import { sights } from '@monkvision/sights';
+import { act, render, waitFor } from '@testing-library/react';
+import { Camera, CameraResolution, CompressionFormat } from '@monkvision/camera-web';
+import { expectPropsOnChildMock } from '@monkvision/test-utils';
+import { PhotoCapture, PhotoCaptureHUD, PhotoCaptureProps } from '../../src';
+import {
+ useAddDamageMode,
+ usePhotoCaptureImages,
+ usePhotoCaptureSightState,
+ usePictureTaken,
+ useStartTasksOnComplete,
+ useUploadQueue,
+} from '../../src/PhotoCapture/hooks';
+import { useI18nSync, useLoadingState } from '@monkvision/common';
+import { ComplianceIssue, TaskName } from '@monkvision/types';
+import { InspectionGallery } from '@monkvision/common-ui-web';
+import { useMonitoring } from '@monkvision/monitoring';
const { PhotoCaptureMode } = jest.requireActual('../../src/PhotoCapture/hooks');
@@ -28,24 +44,9 @@ jest.mock('../../src/PhotoCapture/hooks', () => ({
length: 3,
})),
useStartTasksOnComplete: jest.fn(() => jest.fn()),
+ usePhotoCaptureImages: jest.fn(() => [{ id: 'test' }]),
}));
-import { act, render, waitFor } from '@testing-library/react';
-import { Camera, CameraResolution, CompressionFormat } from '@monkvision/camera-web';
-import { expectPropsOnChildMock } from '@monkvision/test-utils';
-import { PhotoCapture, PhotoCaptureHUD, PhotoCaptureProps } from '../../src';
-import {
- useAddDamageMode,
- usePhotoCaptureSightState,
- usePictureTaken,
- useStartTasksOnComplete,
- useUploadQueue,
-} from '../../src/PhotoCapture/hooks';
-import { useI18nSync, useLoadingState } from '@monkvision/common';
-import { ComplianceIssue, TaskName } from '@monkvision/types';
-import { InspectionGallery } from '@monkvision/common-ui-web';
-import { useMonitoring } from '@monkvision/monitoring';
-
function createProps(): PhotoCaptureProps {
return {
sights: [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']],
@@ -213,6 +214,8 @@ describe('PhotoCapture component', () => {
const sightState = (usePhotoCaptureSightState as jest.Mock).mock.results[0].value;
expect(useLoadingState).toHaveBeenCalled();
const loading = (useLoadingState as jest.Mock).mock.results[0].value;
+ expect(usePhotoCaptureImages).toHaveBeenCalledWith(props.inspectionId);
+ const images = (usePhotoCaptureImages as jest.Mock).mock.results[0].value;
expectPropsOnChildMock(Camera, {
hudProps: {
sights: props.sights,
@@ -229,6 +232,7 @@ describe('PhotoCapture component', () => {
inspectionId: props.inspectionId,
showCloseButton: props.showCloseButton,
onOpenGallery: expect.any(Function),
+ images,
},
});
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx
index 478a629bd..ad7693145 100644
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx
+++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUD.test.tsx
@@ -1,3 +1,5 @@
+import { Image, ImageStatus } from '@monkvision/types';
+
jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/hooks', () => ({
...jest.requireActual('../../../src/PhotoCapture/PhotoCaptureHUD/hooks'),
useComplianceNotification: jest.fn(() => false),
@@ -25,7 +27,6 @@ import {
PhotoCaptureHUDOverlay,
PhotoCaptureHUDPreview,
PhotoCaptureHUDProps,
- useComplianceNotification,
} from '../../../src';
import { PhotoCaptureMode } from '../../../src/PhotoCapture/hooks';
@@ -58,6 +59,9 @@ function createProps(): PhotoCaptureHUDProps {
dimensions: { height: 2, width: 4 },
} as unknown as CameraHandle,
cameraPreview: ,
+ images: [
+ { additionalData: { sight_id: 'test-sight-1' }, status: ImageStatus.NOT_COMPLIANT },
+ ] as Image[],
};
}
@@ -80,7 +84,6 @@ describe('PhotoCaptureHUD component', () => {
const { unmount } = render();
expectPropsOnChildMock(PhotoCaptureHUDPreview, {
- inspectionId: props.inspectionId,
selectedSight: props.selectedSight,
sights: props.sights,
sightsTaken: props.sightsTaken,
@@ -91,6 +94,7 @@ describe('PhotoCaptureHUD component', () => {
isLoading: props.loading.isLoading || props.handle.isLoading,
error: props.loading.error ?? props.handle.error,
streamDimensions: props.handle.dimensions,
+ images: props.images,
});
unmount();
@@ -163,16 +167,28 @@ describe('PhotoCaptureHUD component', () => {
unmount();
});
- it('shoulddisplay the gallery badge if there are compliance notifications', () => {
- (useComplianceNotification as jest.Mock).mockImplementationOnce(() => false);
+ const RETAKE_STATUSES = [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED];
+
+ RETAKE_STATUSES.forEach((status) => {
+ it(`should display the gallery badge if there are images with the ${status} status`, () => {
+ const props = createProps();
+ props.images = [{ status }] as Image[];
+ const { unmount } = render();
+
+ expectPropsOnChildMock(PhotoCaptureHUDButtons, { showGalleryBadge: true });
+
+ unmount();
+ });
+ });
+
+ it('should not display the gallery badge if there are no images with retake statuses', () => {
const props = createProps();
- const { unmount, rerender } = render();
+ props.images = Object.values(ImageStatus)
+ .filter((status) => !RETAKE_STATUSES.includes(status))
+ .map((status) => ({ status } as Image));
+ const { unmount } = render();
expectPropsOnChildMock(PhotoCaptureHUDButtons, { showGalleryBadge: false });
- (useComplianceNotification as jest.Mock).mockImplementationOnce(() => true);
-
- rerender();
- expectPropsOnChildMock(PhotoCaptureHUDButtons, { showGalleryBadge: true });
unmount();
});
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx
index 28d415005..89656cb90 100644
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx
+++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreview.test.tsx
@@ -1,3 +1,5 @@
+import { Image, ImageStatus } from '@monkvision/types';
+
jest.mock('../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight', () => ({
PhotoCaptureHUDPreviewSight: jest.fn(() => <>>),
}));
@@ -30,7 +32,6 @@ import {
function createProps(): PhotoCaptureHUDPreviewProps {
const captureSights = [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']];
return {
- inspectionId: 'test-inspection-id-test',
selectedSight: captureSights[1],
sights: captureSights,
sightsTaken: [captureSights[0]],
@@ -41,6 +42,9 @@ function createProps(): PhotoCaptureHUDPreviewProps {
streamDimensions: { height: 1234, width: 45678 },
isLoading: false,
error: null,
+ images: [
+ { additionalData: { sight_id: 'test-sight-1' }, status: ImageStatus.NOT_COMPLIANT },
+ ] as Image[],
};
}
@@ -75,13 +79,13 @@ describe('PhotoCaptureHUDPreview component', () => {
const { unmount } = render();
expectPropsOnChildMock(PhotoCaptureHUDPreviewSight, {
- inspectionId: props.inspectionId,
sights: props.sights,
selectedSight: props.selectedSight,
onSelectedSight: props.onSelectSight,
sightsTaken: props.sightsTaken,
onAddDamage: props.onAddDamage,
streamDimensions: props.streamDimensions,
+ images: props.images,
});
expect(PhotoCaptureHUDPreviewAddDamage1stShot).not.toHaveBeenCalled();
expect(PhotoCaptureHUDPreviewAddDamage2ndShot).not.toHaveBeenCalled();
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx
index 06a10eb71..df03cd5ee 100644
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx
+++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/PhotoCaptureHUDPreviewSight.test.tsx
@@ -1,3 +1,5 @@
+import { Image, ImageStatus } from '@monkvision/types';
+
jest.mock('../../../../src/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDCounter', () => ({
PhotoCaptureHUDCounter: jest.fn(() => <>>),
}));
@@ -35,13 +37,16 @@ function createProps(): PhotoCaptureHUDSightPreviewProps {
sights['test-sight-4'],
];
return {
- inspectionId: 'inspection-id-test',
sights: captureSights,
selectedSight: captureSights[2],
sightsTaken: [captureSights[0], captureSights[1]],
onSelectedSight: jest.fn(),
onAddDamage: jest.fn(),
streamDimensions: { width: 4563, height: 992 },
+ images: [
+ { additionalData: { sight_id: 'test-sight-1' }, status: ImageStatus.NOT_COMPLIANT },
+ { additionalData: { sight_id: 'test-sight-2' }, status: ImageStatus.SUCCESS },
+ ] as Image[],
};
}
@@ -89,11 +94,11 @@ describe('PhotoCaptureHUDPreviewSight component', () => {
const { unmount } = render();
expectPropsOnChildMock(SightSlider, {
- inspectionId: props.inspectionId,
sights: props.sights,
selectedSight: props.selectedSight,
sightsTaken: props.sightsTaken,
onSelectedSight: props.onSelectedSight,
+ images: props.images,
});
unmount();
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.test.tsx b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.test.tsx
index 96650c5d6..063ace168 100644
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.test.tsx
+++ b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/PhotoCaptureHUDPreviewSight/SightSlider/SightSlider.test.tsx
@@ -7,12 +7,12 @@ jest.mock(
import { sights } from '@monkvision/sights';
import { render } from '@testing-library/react';
-import { useMonkState, useSightLabel } from '@monkvision/common';
-import { ImageStatus, Sight } from '@monkvision/types';
+import { useSightLabel } from '@monkvision/common';
+import { Image, ImageStatus, Sight } from '@monkvision/types';
import {
+ SightSlider,
SightSliderButton,
SightSliderButtonProps,
- SightSlider,
SightSliderProps,
} from '../../../../../src';
@@ -25,11 +25,14 @@ function createProps(): SightSliderProps {
sights['test-sight-5'],
];
return {
- inspectionId: 'test-inspection-id-test',
sights: captureSights,
selectedSight: captureSights[2],
sightsTaken: [captureSights[0], captureSights[1]],
onSelectedSight: jest.fn(),
+ images: [
+ { additionalData: { sight_id: 'test-sight-1' }, status: ImageStatus.NOT_COMPLIANT },
+ { additionalData: { sight_id: 'test-sight-2' }, status: ImageStatus.SUCCESS },
+ ] as Image[],
};
}
@@ -39,26 +42,6 @@ describe('SightSlider component', () => {
});
it('should display a Button for each sight with the proper props', () => {
- const state = {
- images: [
- {
- inspectionId: createProps().inspectionId,
- additionalData: { sight_id: 'test-sight-1' },
- status: ImageStatus.SUCCESS,
- },
- {
- inspectionId: createProps().inspectionId,
- additionalData: { sight_id: 'test-sight-4' },
- status: ImageStatus.COMPLIANCE_RUNNING,
- },
- {
- inspectionId: createProps().inspectionId,
- additionalData: { sight_id: 'test-sight-5' },
- status: ImageStatus.NOT_COMPLIANT,
- },
- ],
- };
- (useMonkState as jest.Mock).mockImplementation(() => ({ state }));
(useSightLabel as jest.Mock).mockImplementation(() => ({ label: (sight: Sight) => sight.id }));
const props = createProps();
const { unmount } = render();
@@ -73,11 +56,7 @@ describe('SightSlider component', () => {
expect(buttonProps).toBeDefined();
expect(buttonProps.isSelected).toEqual(props.selectedSight === sight);
expect(buttonProps.status).toEqual(
- state.images.find(
- (image) =>
- image.inspectionId === props.inspectionId &&
- image.additionalData?.sight_id === sight.id,
- )?.status,
+ props.images.find((image) => image.additionalData?.sight_id === sight.id)?.status,
);
expect(typeof buttonProps.onClick).toBe('function');
buttonProps.onClick?.();
diff --git a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.test.ts b/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.test.ts
deleted file mode 100644
index 873e4a422..000000000
--- a/packages/inspection-capture-web/test/PhotoCapture/PhotoCaptureHUD/hooks/useComplianceNotification.test.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { useComplianceNotification } from '../../../../src';
-import { renderHook } from '@testing-library/react-hooks';
-import { useMonkState } from '@monkvision/common';
-import { ImageStatus } from '@monkvision/types';
-
-const inspectionId = 'test-inspection-id';
-
-describe('useComplianceNotification hook', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- [ImageStatus.NOT_COMPLIANT, ImageStatus.UPLOAD_FAILED].forEach((status) => {
- it(`should return true if there are some pictures with the ${status} status`, () => {
- (useMonkState as jest.Mock).mockImplementationOnce(() => ({
- state: {
- images: [
- { id: '0', inspectionId: 'test', status: ImageStatus.SUCCESS },
- { id: '1', inspectionId, status: ImageStatus.SUCCESS },
- { id: '2', inspectionId, status: ImageStatus.SUCCESS },
- {
- id: '3',
- inspectionId,
- status: ImageStatus.SUCCESS,
- additionalData: {
- sight_id: 'sight-1',
- created_at: Date.parse('1998-01-01T01:01:01.001Z'),
- },
- },
- {
- id: '4',
- inspectionId,
- status,
- additionalData: {
- sight_id: 'sight-1',
- created_at: Date.parse('2024-01-01T01:01:01.001Z'),
- },
- },
- ],
- },
- }));
- const { result, unmount } = renderHook(useComplianceNotification, {
- initialProps: inspectionId,
- });
-
- expect(result.current).toBe(true);
-
- unmount();
- });
- });
-
- it('should return false if there are no pictures to retake', () => {
- (useMonkState as jest.Mock).mockImplementationOnce(() => ({
- state: {
- images: [
- { id: '0', inspectionId: 'test', status: ImageStatus.UPLOAD_FAILED },
- { id: '1', inspectionId, status: ImageStatus.SUCCESS },
- { id: '2', inspectionId, status: ImageStatus.UPLOADING },
- { id: '3', inspectionId, status: ImageStatus.COMPLIANCE_RUNNING },
- {
- id: '4',
- inspectionId,
- status: ImageStatus.UPLOAD_FAILED,
- additionalData: {
- sight_id: 'sight-1',
- created_at: Date.parse('1998-01-01T01:01:01.001Z'),
- },
- },
- {
- id: '5',
- inspectionId,
- status: ImageStatus.UPLOADING,
- additionalData: {
- sight_id: 'sight-1',
- created_at: Date.parse('2024-01-01T01:01:01.001Z'),
- },
- },
- {
- id: '6',
- inspectionId,
- status: ImageStatus.NOT_COMPLIANT,
- additionalData: {
- sight_id: 'sight-1',
- created_at: Date.parse('2000-01-01T01:01:01.001Z'),
- },
- },
- ],
- },
- }));
- const { result, unmount } = renderHook(useComplianceNotification, {
- initialProps: inspectionId,
- });
-
- expect(result.current).toBe(false);
-
- unmount();
- });
-});
diff --git a/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts
new file mode 100644
index 000000000..dcacc5ea3
--- /dev/null
+++ b/packages/inspection-capture-web/test/PhotoCapture/hooks/usePhotoCaptureImages.test.ts
@@ -0,0 +1,24 @@
+const stateMock = { images: [{ id: 'test' }] };
+const inspectionImagesMock = [{ id: 'test-hello' }];
+
+jest.mock('@monkvision/common', () => ({
+ useMonkState: jest.fn(() => ({ state: stateMock })),
+ getInspectionImages: jest.fn(() => inspectionImagesMock),
+}));
+
+import { usePhotoCaptureImages } from '../../../src/PhotoCapture/hooks';
+import { renderHook } from '@testing-library/react-hooks';
+import { getInspectionImages, useMonkState } from '@monkvision/common';
+
+describe('usePhotoCaptureImages hook', () => {
+ it('should return the images of the inspection using the getInspectionImages function', () => {
+ const inspectionId = 'test-inspection-id-test';
+ const { result, unmount } = renderHook(usePhotoCaptureImages, { initialProps: inspectionId });
+
+ expect(useMonkState).toHaveBeenCalled();
+ expect(getInspectionImages).toHaveBeenCalledWith(inspectionId, stateMock.images, true);
+ expect(result.current).toEqual(inspectionImagesMock);
+
+ unmount();
+ });
+});
diff --git a/packages/network/src/api/inspection/mappers.ts b/packages/network/src/api/inspection/mappers.ts
index a3b62a591..4b16d52ed 100644
--- a/packages/network/src/api/inspection/mappers.ts
+++ b/packages/network/src/api/inspection/mappers.ts
@@ -33,6 +33,7 @@ import {
import {
ApiCommentSeverityValue,
ApiDamageDetectionTaskPostComponent,
+ ApiHinlTaskPostComponent,
ApiImageRegion,
ApiImagesOCRTaskPostComponent,
ApiInspectionGet,
@@ -402,6 +403,28 @@ export function mapApiInspectionGet(
};
}
+/**
+ * Options used to specify a callback that will be called by the API when a task is complete.
+ */
+export interface TaskCallbackOptions {
+ /**
+ * The URL of the callback.
+ */
+ url: string;
+ /**
+ * The headers of the request.
+ */
+ headers: Record;
+ /**
+ * The parameters of the callback.
+ */
+ params?: Record;
+ /**
+ * The event of the callback.
+ */
+ event?: string;
+}
+
/**
* Additional options that you can specify when adding the damage detection task to an inspection.
*/
@@ -440,11 +463,28 @@ export interface CreateDamageDetectionTaskOptions {
generateSubimageParts?: boolean;
}
+/**
+ * Additional options that you can specify when adding the human in the loop task to an inspection.
+ */
+export interface CreateHinlTaskOptions {
+ /**
+ * The name of the task : `TaskName.HUMAN_IN_THE_LOOP`.
+ */
+ name: TaskName.HUMAN_IN_THE_LOOP;
+ /**
+ * The callbacks called at the end of the Human in the Loop task.
+ */
+ callbacks?: TaskCallbackOptions[];
+}
+
/**
* The tasks of the inspection to be created. It is either simply the name of the task to add, or an object with the
* task name as well as additional configuration options for the task.
*/
-export type InspectionCreateTask = TaskName | CreateDamageDetectionTaskOptions;
+export type InspectionCreateTask =
+ | TaskName
+ | CreateDamageDetectionTaskOptions
+ | CreateHinlTaskOptions;
/**
* Options that can be specified when creating a new inspection.
@@ -467,6 +507,23 @@ export interface CreateInspectionOptions {
useDynamicCrops?: boolean;
}
+function getHumanInTheLoopOptions(
+ options: CreateInspectionOptions,
+): ApiHinlTaskPostComponent | undefined {
+ if (options.tasks.includes(TaskName.HUMAN_IN_THE_LOOP)) {
+ return { status: ProgressStatus.NOT_STARTED };
+ }
+ const taskOptions = options.tasks.find(
+ (task) => typeof task === 'object' && task.name === TaskName.HUMAN_IN_THE_LOOP,
+ ) as CreateHinlTaskOptions | undefined;
+ return taskOptions
+ ? {
+ status: ProgressStatus.NOT_STARTED,
+ callbacks: taskOptions.callbacks,
+ }
+ : undefined;
+}
+
function getDamageDetectionOptions(
options: CreateInspectionOptions,
): ApiDamageDetectionTaskPostComponent | undefined {
@@ -527,6 +584,7 @@ function getTasksOptions(options: CreateInspectionOptions): ApiTasksComponent {
damage_detection: getDamageDetectionOptions(options),
wheel_analysis: getWheelAnalysisOptions(options),
images_ocr: getImagesOCROptions(options),
+ human_in_the_loop: getHumanInTheLoopOptions(options),
};
}
diff --git a/packages/network/src/api/models/task.ts b/packages/network/src/api/models/task.ts
index 908bb7b08..ee4da4512 100644
--- a/packages/network/src/api/models/task.ts
+++ b/packages/network/src/api/models/task.ts
@@ -61,7 +61,7 @@ export interface ApiCallback {
url: string;
headers: Record;
callback_event?: ApiCallbackEvent;
- params: Record;
+ params?: Record;
}
export type ApiCallbacks = ApiCallback[];
@@ -102,8 +102,14 @@ export interface ApiImagesOCRTaskPostComponent {
callbacks?: ApiCallbacks;
}
+export interface ApiHinlTaskPostComponent {
+ status?: ApiTaskPostProgressStatus;
+ callbacks?: ApiCallbacks;
+}
+
export interface ApiTasksComponent {
damage_detection?: ApiDamageDetectionTaskPostComponent;
wheel_analysis?: ApiWheelAnalysisTaskPostComponent;
images_ocr?: ApiImagesOCRTaskPostComponent;
+ human_in_the_loop?: ApiHinlTaskPostComponent;
}