diff --git a/api/src/nlp/controllers/nlp-entity.controller.spec.ts b/api/src/nlp/controllers/nlp-entity.controller.spec.ts index 1f1a8ae5..5e68e4a7 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.spec.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.spec.ts @@ -6,7 +6,11 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { MethodNotAllowedException, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + MethodNotAllowedException, + NotFoundException, +} from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { MongooseModule } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; @@ -258,4 +262,45 @@ describe('NlpEntityController', () => { ).rejects.toThrow(MethodNotAllowedException); }); }); + describe('deleteMany', () => { + it('should delete multiple nlp entities', async () => { + const entitiesToDelete = [ + ( + await nlpEntityService.findOne({ + name: 'sentiment', + }) + )?.id, + ( + await nlpEntityService.findOne({ + name: 'updated', + }) + )?.id, + ]; + + const result = await nlpEntityController.deleteMany(entitiesToDelete); + + expect(result.deletedCount).toEqual(entitiesToDelete.length); + const remainingEntities = await nlpEntityService.find({ + _id: { $in: entitiesToDelete }, + }); + expect(remainingEntities.length).toBe(0); + }); + + it('should throw BadRequestException when no IDs are provided', async () => { + await expect(nlpEntityController.deleteMany([])).rejects.toThrow( + BadRequestException, + ); + }); + + it('should throw NotFoundException when provided IDs do not exist', async () => { + const nonExistentIds = [ + '614c1b2f58f4f04c876d6b8d', + '614c1b2f58f4f04c876d6b8e', + ]; + + await expect( + nlpEntityController.deleteMany(nonExistentIds), + ).rejects.toThrow(NotFoundException); + }); + }); }); diff --git a/api/src/nlp/controllers/nlp-entity.controller.ts b/api/src/nlp/controllers/nlp-entity.controller.ts index f01353c2..0e729e7d 100644 --- a/api/src/nlp/controllers/nlp-entity.controller.ts +++ b/api/src/nlp/controllers/nlp-entity.controller.ts @@ -7,6 +7,7 @@ */ import { + BadRequestException, Body, Controller, Delete, @@ -27,6 +28,7 @@ import { TFilterQuery } from 'mongoose'; 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'; @@ -211,4 +213,31 @@ export class NlpEntityController extends BaseController< } return result; } + + /** + * Deletes multiple NLP entities by their IDs. + * @param ids - IDs of NLP entities 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.nlpEntityService.deleteMany({ + _id: { $in: ids }, + }); + + if (deleteResult.deletedCount === 0) { + this.logger.warn( + `Unable to delete NLP entities with provided IDs: ${ids}`, + ); + throw new NotFoundException('NLP entities with provided IDs not found'); + } + + this.logger.log(`Successfully deleted NLP entities with IDs: ${ids}`); + return deleteResult; + } } diff --git a/frontend/src/components/nlp/components/NlpEntity.tsx b/frontend/src/components/nlp/components/NlpEntity.tsx index ae366440..4ae9c969 100644 --- a/frontend/src/components/nlp/components/NlpEntity.tsx +++ b/frontend/src/components/nlp/components/NlpEntity.tsx @@ -7,9 +7,11 @@ */ import AddIcon from "@mui/icons-material/Add"; +import DeleteIcon from "@mui/icons-material/Delete"; import { Button, Chip, Grid } from "@mui/material"; -import { GridColDef } from "@mui/x-data-grid"; +import { GridColDef, GridRowSelectionModel } from "@mui/x-data-grid"; import { useRouter } from "next/router"; +import { useState } from "react"; import { DeleteDialog } from "@/app-components/dialogs"; import { FilterTextfield } from "@/app-components/inputs/FilterTextfield"; @@ -20,6 +22,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 { getDisplayDialogs, useDialog } from "@/hooks/useDialog"; import { useHasPermission } from "@/hooks/useHasPermission"; @@ -47,6 +50,20 @@ const NlpEntity = () => { toast.success(t("message.item_delete_success")); }, }); + const { mutateAsync: deleteNlpEntities } = useDeleteMany( + EntityType.NLP_ENTITY, + { + onError: (error) => { + toast.error(error); + }, + onSuccess: () => { + deleteEntityDialogCtl.closeDialog(); + setSelectedNlpEntities([]); + toast.success(t("message.item_delete_success")); + }, + }, + ); + const [selectedNlpEntities, setSelectedNlpEntities] = useState([]); const addDialogCtl = useDialog(false); const { t } = useTranslate(); const { toast } = useToast(); @@ -154,6 +171,9 @@ const NlpEntity = () => { }, actionEntityColumns, ]; + const handleSelectionChange = (selection: GridRowSelectionModel) => { + setSelectedNlpEntities(selection as string[]); + }; return ( @@ -162,18 +182,29 @@ const NlpEntity = () => { { - if (deleteEntityDialogCtl.data) + if (selectedNlpEntities.length > 0) { + deleteNlpEntities(selectedNlpEntities); + setSelectedNlpEntities([]); + deleteEntityDialogCtl.closeDialog(); + } else if (deleteEntityDialogCtl.data) { deleteNlpEntity(deleteEntityDialogCtl.data); + } }} /> - - + + {hasPermission(EntityType.NLP_ENTITY, PermissionAction.CREATE) ? ( - + ) : null} + {selectedNlpEntities.length > 0 && ( + + + + )} - + );