From fc572e48d1248bee1c59becdf3e440da7913133d Mon Sep 17 00:00:00 2001 From: hexastack Date: Thu, 10 Oct 2024 07:51:46 +0100 Subject: [PATCH] feat: nlp samples bulk delete --- .../controllers/nlp-sample.controller.spec.ts | 41 ++++++++++++++++ .../nlp/controllers/nlp-sample.controller.ts | 28 +++++++++++ .../components/nlp/components/NlpSample.tsx | 47 +++++++++++++++++-- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/api/src/nlp/controllers/nlp-sample.controller.spec.ts b/api/src/nlp/controllers/nlp-sample.controller.spec.ts index ba3f7b71..8a401a70 100644 --- a/api/src/nlp/controllers/nlp-sample.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-sample.controller.spec.ts @@ -429,4 +429,45 @@ describe('NlpSampleController', () => { expect(result).toEqual({ success: true }); }); }); + describe('deleteMany', () => { + it('should delete multiple nlp samples', async () => { + const samplesToDelete = [ + ( + await nlpSampleService.findOne({ + text: 'How much does a BMW cost?', + }) + ).id, + ( + await nlpSampleService.findOne({ + text: 'text1', + }) + ).id, + ]; + + const result = await nlpSampleController.deleteMany(samplesToDelete); + + expect(result.deletedCount).toEqual(samplesToDelete.length); + const remainingSamples = await nlpSampleService.find({ + _id: { $in: samplesToDelete }, + }); + expect(remainingSamples.length).toBe(0); + }); + + it('should throw BadRequestException when no IDs are provided', async () => { + await expect(nlpSampleController.deleteMany([])).rejects.toThrow( + BadRequestException, + ); + }); + + it('should throw NotFoundException when provided IDs do not exist', async () => { + const nonExistentIds = [ + '614c1b2f58f4f04c876d6b8d', + '614c1b2f58f4f04c876d6b8e', + ]; + + await expect( + nlpSampleController.deleteMany(nonExistentIds), + ).rejects.toThrow(NotFoundException); + }); + }); }); diff --git a/api/src/nlp/controllers/nlp-sample.controller.ts b/api/src/nlp/controllers/nlp-sample.controller.ts index fa1c4171..940912cb 100644 --- a/api/src/nlp/controllers/nlp-sample.controller.ts +++ b/api/src/nlp/controllers/nlp-sample.controller.ts @@ -37,6 +37,7 @@ import { LanguageService } from '@/i18n/services/language.service'; import { CsrfInterceptor } from '@/interceptors/csrf.interceptor'; import { LoggerService } from '@/logger/logger.service'; import { BaseController } from '@/utils/generics/base-controller'; +import { DeleteResult } from '@/utils/generics/base-repository'; import { PageQueryDto } from '@/utils/pagination/pagination-query.dto'; import { PageQueryPipe } from '@/utils/pagination/pagination-query.pipe'; import { PopulatePipe } from '@/utils/pipes/populate.pipe'; @@ -321,6 +322,33 @@ export class NlpSampleController extends BaseController< return result; } + /** + * Deletes multiple NLP samples by their IDs. + * @param ids - IDs of NLP samples to be deleted. + * @returns A Promise that resolves to the deletion result. + */ + @CsrfCheck(true) + @Delete('') + @HttpCode(204) + async deleteMany(@Body('ids') ids: string[]): Promise { + if (!ids || ids.length === 0) { + throw new BadRequestException('No IDs provided for deletion.'); + } + const deleteResult = await this.nlpSampleService.deleteMany({ + _id: { $in: ids }, + }); + + if (deleteResult.deletedCount === 0) { + this.logger.warn( + `Unable to delete NLP samples with provided IDs: ${ids}`, + ); + throw new NotFoundException('NLP samples with provided IDs not found'); + } + + this.logger.log(`Successfully deleted NLP samples with IDs: ${ids}`); + return deleteResult; + } + /** * Imports NLP samples from a CSV file. * diff --git a/frontend/src/components/nlp/components/NlpSample.tsx b/frontend/src/components/nlp/components/NlpSample.tsx index 4afe4722..160c6832 100644 --- a/frontend/src/components/nlp/components/NlpSample.tsx +++ b/frontend/src/components/nlp/components/NlpSample.tsx @@ -20,7 +20,7 @@ import { MenuItem, Stack, } from "@mui/material"; -import { GridColDef } from "@mui/x-data-grid"; +import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import { useState } from "react"; import { DeleteDialog } from "@/app-components/dialogs"; @@ -35,6 +35,7 @@ import { import { renderHeader } from "@/app-components/tables/columns/renderHeader"; import { DataGrid } from "@/app-components/tables/DataGrid"; import { useDelete } from "@/hooks/crud/useDelete"; +import { useDeleteMany } from "@/hooks/crud/useDeleteMany"; import { useFind } from "@/hooks/crud/useFind"; import { useGetFromCache } from "@/hooks/crud/useGet"; import { useConfig } from "@/hooks/useConfig"; @@ -91,6 +92,20 @@ export default function NlpSample() { toast.success(t("message.item_delete_success")); }, }); + const { mutateAsync: deleteNlpSamples } = useDeleteMany( + EntityType.NLP_SAMPLE, + { + onError: (error) => { + toast.error(error); + }, + onSuccess: () => { + deleteDialogCtl.closeDialog(); + setSelectedNlpSamples([]); + toast.success(t("message.item_delete_success")); + }, + }, + ); + const [selectedNlpSamples, setSelectedNlpSamples] = useState([]); const { dataGridProps } = useFind( { entity: EntityType.NLP_SAMPLE, format: Format.FULL }, { @@ -242,6 +257,9 @@ export default function NlpSample() { }, actionColumns, ]; + const handleSelectionChange = (selection: GridRowSelectionModel) => { + setSelectedNlpSamples(selection as string[]); + }; return ( @@ -249,7 +267,13 @@ export default function NlpSample() { { - if (deleteDialogCtl.data) deleteNlpSample(deleteDialogCtl.data); + if (selectedNlpSamples.length > 0) { + deleteNlpSamples(selectedNlpSamples); + setSelectedNlpSamples([]); + deleteDialogCtl.closeDialog(); + } else if (deleteDialogCtl.data) { + deleteNlpSample(deleteDialogCtl.data); + } }} /> @@ -346,12 +370,29 @@ export default function NlpSample() { {t("button.export")} ) : null} + {selectedNlpSamples.length > 0 && ( + + + + )} - + );