Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configs/test-utils/src/__mocks__/ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export = {
get: createMockKyRequestFn(),
post: createMockKyRequestFn(),
patch: createMockKyRequestFn(),
delete: createMockKyRequestFn(),
};
80 changes: 80 additions & 0 deletions packages/common/src/state/actions/deletedOneImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { MonkState } from '../state';
import { MonkAction, MonkActionType } from './monkAction';

/**
* The payload of a MonkDeletedOneImagePayload.
*/
export interface MonkDeletedOneImagePayload {
/**
* The ID of the inspection to which the image was deleted.
*/
inspectionId: string;
/**
* The image ID deleted.
*/
imageId: string;
}

/**
* Action dispatched when an image have been deleted.
*/
export interface MonkDeletedOneImageAction extends MonkAction {
/**
* The type of the action : `MonkActionType.DELETED_ONE_IMAGE`.
*/
type: MonkActionType.DELETED_ONE_IMAGE;
/**
* The payload of the action containing the fetched entities.
*/
payload: MonkDeletedOneImagePayload;
}

/**
* Matcher function that matches a DeletedOneImage while also inferring its type using TypeScript's type predicate
* feature.
*/
export function isDeletedOneImageAction(action: MonkAction): action is MonkDeletedOneImageAction {
return action.type === MonkActionType.DELETED_ONE_IMAGE;
}

/**
* Reducer function for a deletedOneImage action.
*/
export function deletedOneImage(state: MonkState, action: MonkDeletedOneImageAction): MonkState {
const { images, inspections, damages, parts, renderedOutputs, views } = state;
const { payload } = action;

const inspection = inspections.find((value) => value.id === payload.inspectionId);
if (inspection) {
inspection.images = inspection.images?.filter((imageId) => imageId !== payload.imageId);
}
const deletedImage = images.find((image) => image.id === payload.imageId);
const newImages = images.filter((image) => image.id !== payload.imageId);
const newDamages = damages.map((damage) => ({
...damage,
relatedImages: damage.relatedImages.filter((imageId) => imageId !== payload.imageId),
}));
const newParts = parts.map((part) => ({
...part,
relatedImages: part.relatedImages.filter((imageId) => imageId !== payload.imageId),
}));
const newViews = views.map((view) => ({
...view,
renderedOutputs: view.renderedOutputs.filter(
(outputId) => !deletedImage?.renderedOutputs.includes(outputId),
),
}));
const newRenderedOutputs = renderedOutputs.filter(
(output) => !deletedImage?.renderedOutputs.includes(output.id),
);

return {
...state,
images: [...newImages],
inspections: [...inspections],
damages: [...newDamages],
parts: [...newParts],
renderedOutputs: [...newRenderedOutputs],
views: [...newViews],
};
}
1 change: 1 addition & 0 deletions packages/common/src/state/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './monkAction';
export * from './resetState';
export * from './gotOneInspection';
export * from './createdOneImage';
export * from './deletedOneImage';
export * from './updatedManyTasks';
export * from './updatedVehicle';
export * from './createdOnePricing';
Expand Down
4 changes: 4 additions & 0 deletions packages/common/src/state/actions/monkAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export enum MonkActionType {
* An image has been uploaded to the API.
*/
CREATED_ONE_IMAGE = 'created_one_image',
/**
* An image has been deleted.
*/
DELETED_ONE_IMAGE = 'deleted_one_image',
/**
* One or more tasks have been updated.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/state/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
isDeletedOneDamageAction,
isGotOneInspectionPdfAction,
gotOneInspectionPdf,
deletedOneImage,
isDeletedOneImageAction,
} from './actions';
import { MonkState } from './state';

Expand All @@ -43,6 +45,9 @@ export function monkReducer(state: MonkState, action: MonkAction): MonkState {
if (isCreatedOneImageAction(action)) {
return createdOneImage(state, action);
}
if (isDeletedOneImageAction(action)) {
return deletedOneImage(state, action);
}
if (isUpdatedManyTasksAction(action)) {
return updatedManyTasks(state, action);
}
Expand Down
97 changes: 97 additions & 0 deletions packages/common/test/state/actions/deletedOneImage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Damage, Image, Inspection, Part, RenderedOutput, View } from '@monkvision/types';
import {
createEmptyMonkState,
deletedOneImage,
isDeletedOneImageAction,
MonkActionType,
MonkDeletedOneImageAction,
} from '../../../src';

const action: MonkDeletedOneImageAction = {
type: MonkActionType.DELETED_ONE_IMAGE,
payload: {
inspectionId: 'inspections-test',
imageId: 'image-id-test',
},
};

describe('DeletedOneImage action handlers', () => {
describe('Action matcher', () => {
it('should return true if the action has the proper type', () => {
expect(isDeletedOneImageAction({ type: MonkActionType.DELETED_ONE_IMAGE })).toBe(true);
});

it('should return false if the action does not have the proper type', () => {
expect(isDeletedOneImageAction({ type: MonkActionType.RESET_STATE })).toBe(false);
});
});

describe('Action handler', () => {
it('should return a new state', () => {
const state = createEmptyMonkState();
expect(Object.is(deletedOneImage(state, action), state)).toBe(false);
});

it('should delete image in the state', () => {
const state = createEmptyMonkState();
const outputId = 'rendered-output-id-test';
const damageId = 'damage-id-test';
const partId = 'part-id-test';
const viewId = 'view-id-test';
state.inspections.push({
id: action.payload.inspectionId,
images: [action.payload.imageId] as string[],
} as Inspection);
state.images.push({
id: action.payload.imageId,
inspectionId: action.payload.inspectionId,
views: [viewId],
renderedOutputs: [outputId],
} as Image);
state.damages.push({
id: damageId,
relatedImages: [action.payload.imageId],
inspectionId: action.payload.inspectionId,
} as Damage);
state.parts.push({
id: partId,
relatedImages: [action.payload.imageId],
inspectionId: action.payload.inspectionId,
} as Part);
state.renderedOutputs.push({
id: outputId,
baseImageId: action.payload.imageId,
} as RenderedOutput);
state.views.push({
id: viewId,
renderedOutputs: [outputId],
} as View);
const newState = deletedOneImage(state, action);
const inspectionImages = newState.inspections.find(
(ins) => ins.id === action.payload.inspectionId,
)?.images;

expect(inspectionImages?.length).toBe(0);
expect(inspectionImages).not.toContainEqual(action.payload.imageId);
expect(newState.images.length).toBe(0);
expect(newState.damages.length).toBe(1);
expect(newState.parts.length).toBe(1);
expect(newState.views.length).toBe(1);
expect(
newState.damages.find((damage) => damage.relatedImages.includes(action.payload.imageId)),
).toBeUndefined();
expect(
newState.parts.find((part) => part.relatedImages.includes(action.payload.imageId)),
).toBeUndefined();
expect(newState.renderedOutputs.length).toBe(0);
expect(
newState.renderedOutputs.find((output) => output.baseImageId === action.payload.imageId),
).toBeUndefined();
expect(
newState.views.find((view) =>
view.renderedOutputs.find((id) => id === action.payload.imageId),
),
).toBeUndefined();
});
});
});
5 changes: 5 additions & 0 deletions packages/common/test/state/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
jest.mock('../../src/state/actions', () => ({
isCreatedOneImageAction: jest.fn(() => false),
isDeletedOneImageAction: jest.fn(() => false),
isGotOneInspectionAction: jest.fn(() => false),
isResetStateAction: jest.fn(() => false),
isUpdatedManyTasksAction: jest.fn(() => false),
Expand All @@ -12,6 +13,7 @@ jest.mock('../../src/state/actions', () => ({
isUpdatedVehicleAction: jest.fn(() => false),
isGotOneInspectionPdfAction: jest.fn(() => false),
createdOneImage: jest.fn(() => null),
deletedOneImage: jest.fn(() => null),
gotOneInspection: jest.fn(() => null),
resetState: jest.fn(() => null),
updatedManyTasks: jest.fn(() => null),
Expand All @@ -27,6 +29,7 @@ jest.mock('../../src/state/actions', () => ({

import {
createdOneImage,
deletedOneImage,
gotOneInspection,
createdOnePricing,
deletedOnePricing,
Expand All @@ -37,6 +40,7 @@ import {
updatedVehicle,
gotOneInspectionPdf,
isCreatedOneImageAction,
isDeletedOneImageAction,
isGotOneInspectionAction,
isResetStateAction,
isUpdatedManyTasksAction,
Expand All @@ -59,6 +63,7 @@ const actions = [
{ matcher: isResetStateAction, handler: resetState, noParams: true },
{ matcher: isGotOneInspectionAction, handler: gotOneInspection },
{ matcher: isCreatedOneImageAction, handler: createdOneImage },
{ matcher: isDeletedOneImageAction, handler: deletedOneImage },
{ matcher: isUpdatedManyTasksAction, handler: updatedManyTasks },
{ matcher: isCreatedOnePricingAction, handler: createdOnePricing },
{ matcher: isDeletedOnePricingAction, handler: deletedOnePricing },
Expand Down
15 changes: 15 additions & 0 deletions packages/network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ been created in the API.
|-----------|-----------------|----------------------------------------------------------|----------|
| options | AddImageOptions | The options used to specify how to upload the image. | ✔️ |

### deleteImage

```typescript
import { MonkApi } from '@monkvision/network';

MonkApi.deleteImage(options,apiConfig, dispatch);
```

Delete an image from an inspection. The resulting action of this request will contain the ID of the image that has
been deleted in the API.

| Parameter | Type | Description | Required |
| ----------|--------------------|----------------------------------------------------|----------|
| options | DeleteImageOptions | The options used to specify which image to delete. | ✔️ |

### updateTaskStatus
```typescript
import { MonkApi } from '@monkvision/network';
Expand Down
3 changes: 2 additions & 1 deletion packages/network/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getAllInspections,
getAllInspectionsCount,
} from './inspection';
import { addImage } from './image';
import { addImage, deleteImage } from './image';
import { startInspectionTasks, updateTaskStatus } from './task';
import { getLiveConfig } from './liveConfigs';
import { updateInspectionVehicle } from './vehicle';
Expand All @@ -23,6 +23,7 @@ export const MonkApi = {
getAllInspectionsCount,
createInspection,
addImage,
deleteImage,
updateTaskStatus,
startInspectionTasks,
getLiveConfig,
Expand Down
62 changes: 61 additions & 1 deletion packages/network/src/api/image/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MonkActionType,
MonkCreatedOneImageAction,
vehiclePartLabels,
MonkDeletedOneImageAction,
} from '@monkvision/common';
import {
ComplianceOptions,
Expand All @@ -15,14 +16,21 @@ import {
ImageType,
MonkEntityType,
MonkPicture,
ProgressStatus,
TaskName,
TranslationObject,
VehiclePart,
} from '@monkvision/types';
import { v4 } from 'uuid';
import { labels, sights } from '@monkvision/sights';
import { getDefaultOptions, MonkApiConfig } from '../config';
import { ApiCenterOnElement, ApiImage, ApiImagePost, ApiImagePostTask } from '../models';
import {
ApiCenterOnElement,
ApiIdColumn,
ApiImage,
ApiImagePost,
ApiImagePostTask,
} from '../models';
import { MonkApiResponse } from '../types';
import { mapApiImage } from './mappers';

Expand Down Expand Up @@ -593,3 +601,55 @@ export async function addImage(
throw err;
}
}

/**
* Options specified when deleting an image from an inspection.
*/
export interface DeleteImageOptions {
/**
* The ID of the inspection to update via the API.
*/
id: string;
/**
* Image ID that will be deleted.
*/
imageId: string;
}

/**
* Delete an image from an inspection.
*
* @param options Deletion options for the image.
* @param config The API config.
* @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React
* state management for you.
*/
export async function deleteImage(
options: DeleteImageOptions,
config: MonkApiConfig,
dispatch?: Dispatch<MonkDeletedOneImageAction>,
): Promise<MonkApiResponse> {
const kyOptions = getDefaultOptions(config);
try {
const response = await ky.delete(`inspections/${options.id}/images/${options.imageId}`, {
...kyOptions,
json: { authorized_tasks_statuses: [ProgressStatus.NOT_STARTED] },
});
const body = await response.json<ApiIdColumn>();
dispatch?.({
type: MonkActionType.DELETED_ONE_IMAGE,
payload: {
inspectionId: options.id,
imageId: body.id,
},
});
return {
id: body.id,
response,
body,
};
} catch (err) {
console.error(`Failed to delete image: ${(err as Error).message}`);
throw err;
}
}
1 change: 1 addition & 0 deletions packages/network/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export {
type AddBeautyShotImageOptions,
type Add2ShotCloseUpImageOptions,
type AddImageOptions,
type DeleteImageOptions,
type AddVideoFrameOptions,
ImageUploadType,
} from './image';
Expand Down
Loading