From 78d79bbd0d9032517b70f435637e06fd593df466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Obernd=C3=B6rfer?= Date: Sun, 8 Dec 2024 09:42:59 +0100 Subject: [PATCH 1/3] Added possibility to filter by multiple taskIds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Florian Oberndörfer --- .../app/backupData/backupData.controller.ts | 14 +++++++++--- .../src/app/backupData/backupData.service.ts | 22 ++++++++++++++++--- .../dto/backupDataFilterByTaskIds.dto.ts | 10 +++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 apps/backend/src/app/backupData/dto/backupDataFilterByTaskIds.dto.ts diff --git a/apps/backend/src/app/backupData/backupData.controller.ts b/apps/backend/src/app/backupData/backupData.controller.ts index a83e069..32cdd59 100644 --- a/apps/backend/src/app/backupData/backupData.controller.ts +++ b/apps/backend/src/app/backupData/backupData.controller.ts @@ -9,6 +9,7 @@ import { Query, } from '@nestjs/common'; import { + ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, @@ -21,6 +22,7 @@ import { PaginationDto } from '../utils/pagination/PaginationDto'; import { PaginationOptionsDto } from '../utils/pagination/PaginationOptionsDto'; import { BackupDataFilterDto } from './dto/backupDataFilter.dto'; import { BackupDataOrderOptionsDto } from './dto/backupDataOrderOptions.dto'; +import { BackupDataFilterByTaskIdsDto } from './dto/backupDataFilterByTaskIds.dto'; @ApiTags('Backup Data') @Controller('backupData') @@ -41,18 +43,24 @@ export class BackupDataController { return entity; } - @Get() + @Post('filter') @ApiOperation({ summary: 'Returns all backupData Objects.' }) @ApiOkResponse() + @ApiBody({ + type: BackupDataFilterByTaskIdsDto, + required: false, + }) async findAll( @Query() paginationOptionsDto: PaginationOptionsDto, @Query() backupDataFilterDto: BackupDataFilterDto, - @Query() backupDataOrderOptionsDto: BackupDataOrderOptionsDto + @Query() backupDataOrderOptionsDto: BackupDataOrderOptionsDto, + @Body() backupDataFilterByTaskIdsDto?: BackupDataFilterByTaskIdsDto ): Promise> { return this.backupDataService.findAll( paginationOptionsDto, backupDataOrderOptionsDto, - backupDataFilterDto + backupDataFilterDto, + backupDataFilterByTaskIdsDto ); } diff --git a/apps/backend/src/app/backupData/backupData.service.ts b/apps/backend/src/app/backupData/backupData.service.ts index 1a837f2..f69b835 100644 --- a/apps/backend/src/app/backupData/backupData.service.ts +++ b/apps/backend/src/app/backupData/backupData.service.ts @@ -17,6 +17,8 @@ import { BackupDataDto } from './dto/backupData.dto'; import { BackupDataFilterDto } from './dto/backupDataFilter.dto'; import { BackupDataOrderOptionsDto } from './dto/backupDataOrderOptions.dto'; import { BackupType } from './dto/backupType'; +import { BackupDataFilterByTaskIdsDto } from './dto/backupDataFilterByTaskIds.dto'; +import { TaskEntity } from '../tasks/entity/task.entity'; @Injectable() export class BackupDataService extends PaginationService { @@ -41,12 +43,13 @@ export class BackupDataService extends PaginationService { async findAll( paginationOptionsDto: PaginationOptionsDto, backupDataOrderOptionsDto: BackupDataOrderOptionsDto, - backupDataFilterDto: BackupDataFilterDto + backupDataFilterDto: BackupDataFilterDto, + backupDataFilterByTaskIdsDto?: BackupDataFilterByTaskIdsDto ): Promise> { return await this.paginate( this.backupDataRepository, this.createOrderClause(backupDataOrderOptionsDto), - this.createWhereClause(backupDataFilterDto), + this.createWhereClause(backupDataFilterDto, backupDataFilterByTaskIdsDto), paginationOptionsDto ); } @@ -79,7 +82,10 @@ export class BackupDataService extends PaginationService { * Create where clause. * @param backupDataFilterDto */ - createWhereClause(backupDataFilterDto: BackupDataFilterDto) { + createWhereClause( + backupDataFilterDto: BackupDataFilterDto, + backupDataFilterByTaskIdsDto?: BackupDataFilterByTaskIdsDto + ) { const where: FindOptionsWhere = {}; //ID search @@ -144,6 +150,16 @@ export class BackupDataService extends PaginationService { where.taskId = { id: backupDataFilterDto.taskId }; } + //Multiple Task ids from body search + if (backupDataFilterByTaskIdsDto?.taskIds) { + const taskIdFilter: FindOptionsWhere[] = []; + const taskIds = backupDataFilterByTaskIdsDto.taskIds; + for (const taskId of taskIds) { + taskIdFilter.push({ id: taskId }); + } + where.taskId = taskIdFilter; + } + //Task name search if (backupDataFilterDto.taskName) { where.taskId = { diff --git a/apps/backend/src/app/backupData/dto/backupDataFilterByTaskIds.dto.ts b/apps/backend/src/app/backupData/dto/backupDataFilterByTaskIds.dto.ts new file mode 100644 index 0000000..f4f15e3 --- /dev/null +++ b/apps/backend/src/app/backupData/dto/backupDataFilterByTaskIds.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +export class BackupDataFilterByTaskIdsDto { + @ApiProperty({ + description: 'Array of task ids to be filtered by', + }) + @IsOptional() + taskIds?: string[]; +} From a570a49b8179ac284ebbed5fefaf906689be571a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Obernd=C3=B6rfer?= Date: Sun, 8 Dec 2024 09:59:56 +0100 Subject: [PATCH 2/3] Adjusted frontend to backend changes to avoid breaking changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Florian Oberndörfer --- .../backup-service.service.spec.ts | 37 ++++++++++--------- .../backup-service/backup-service.service.ts | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.spec.ts b/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.spec.ts index 5a9e790..fcedddf 100644 --- a/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.spec.ts +++ b/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.spec.ts @@ -11,13 +11,13 @@ import { fail } from 'assert'; describe('BackupService', () => { let service: BackupService; let httpClientMock: { - get: ReturnType; + post: ReturnType; }; const baseUrl = 'http://test-url'; beforeEach(() => { httpClientMock = { - get: vi.fn(), + post: vi.fn(), }; service = new BackupService( @@ -46,16 +46,17 @@ describe('BackupService', () => { }, }; - httpClientMock.get.mockReturnValue(of(mockResponse)); + httpClientMock.post.mockReturnValue(of(mockResponse)); service.getAllBackups(mockFilterParams).subscribe((response) => { - expect(httpClientMock.get).toHaveBeenCalledWith( - `${baseUrl}/backupData`, + expect(httpClientMock.post).toHaveBeenCalledWith( + `${baseUrl}/backupData/filter`, + null, { params: expect.any(HttpParams), } ); - const passedParams = httpClientMock.get.mock.calls[0][1].params; + const passedParams = httpClientMock.post.mock.calls[0][2].params; expect(passedParams.keys()).toEqual(['offset', 'limit']); }); }); @@ -79,17 +80,18 @@ describe('BackupService', () => { }, }; - httpClientMock.get.mockReturnValue(of(mockResponse)); + httpClientMock.post.mockReturnValue(of(mockResponse)); service.getAllBackups(mockFilterParams).subscribe((response) => { - expect(httpClientMock.get).toHaveBeenCalledWith( - `${baseUrl}/backupData`, + expect(httpClientMock.post).toHaveBeenCalledWith( + `${baseUrl}/backupData/filter`, + null, { params: expect.any(HttpParams), } ); - const passedParams = httpClientMock.get.mock.calls[0][1].params; + const passedParams = httpClientMock.post.mock.calls[0][2].params; expect(passedParams.get('limit')).toBe('10'); expect(passedParams.get('offset')).toBe('0'); expect(passedParams.get('orderBy')).toBe('createdAt'); @@ -114,14 +116,14 @@ describe('BackupService', () => { }, }; - httpClientMock.get.mockReturnValue(of(mockResponse)); + httpClientMock.post.mockReturnValue(of(mockResponse)); const observable = service.getAllBackups(mockFilterParams); observable.subscribe(); observable.subscribe(); - expect(httpClientMock.get).toHaveBeenCalledTimes(1); + expect(httpClientMock.post).toHaveBeenCalledTimes(1); }); it('should handle empty filter params', () => { @@ -134,17 +136,18 @@ describe('BackupService', () => { }, }; - httpClientMock.get.mockReturnValue(of(mockResponse)); + httpClientMock.post.mockReturnValue(of(mockResponse)); service.getAllBackups(mockFilterParams).subscribe((response) => { - expect(httpClientMock.get).toHaveBeenCalledWith( - `${baseUrl}/backupData`, + expect(httpClientMock.post).toHaveBeenCalledWith( + `${baseUrl}/backupData/filter`, + null, { params: expect.any(HttpParams), } ); - const passedParams = httpClientMock.get.mock.calls[0][1].params; + const passedParams = httpClientMock.post.mock.calls[0][2].params; expect(passedParams.keys().length).toBe(0); }); }); @@ -157,7 +160,7 @@ describe('BackupService', () => { const mockError = new Error('Network error'); - httpClientMock.get.mockReturnValue(throwError(() => mockError)); + httpClientMock.post.mockReturnValue(throwError(() => mockError)); service.getAllBackups(mockFilterParams).subscribe({ next: () => fail('expected an error, not backups'), error: (error) => expect(error).toBe(mockError), diff --git a/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.ts b/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.ts index b4c3aa3..1e78f3c 100644 --- a/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.ts +++ b/apps/frontend/src/app/backups-overview/service/backup-service/backup-service.service.ts @@ -33,7 +33,7 @@ export class BackupService { const params = new HttpParams({ fromObject: cleanParams }); return this.http - .get>(`${this.baseUrl}/backupData`, { + .post>(`${this.baseUrl}/backupData/filter`, null, { params: params, }) .pipe(shareReplay(1)); From b8a13518d1a55e8522720d8c5379e4d28a2ffe4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Obernd=C3=B6rfer?= Date: Sun, 8 Dec 2024 10:06:32 +0100 Subject: [PATCH 3/3] Addes tests for filtering by multiple taksIds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Florian Oberndörfer --- .../backupData/backupData.controller.spec.ts | 51 ++++++++++++++----- .../app/backupData/backupData.service.spec.ts | 10 ++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/apps/backend/src/app/backupData/backupData.controller.spec.ts b/apps/backend/src/app/backupData/backupData.controller.spec.ts index d5cc3ee..bb3505a 100644 --- a/apps/backend/src/app/backupData/backupData.controller.spec.ts +++ b/apps/backend/src/app/backupData/backupData.controller.spec.ts @@ -90,10 +90,10 @@ describe('BackupDataController (e2e)', () => { }); }); - it('/backupData (GET) with pagination should return paginated backup data entries', async () => { + it('/backupData/filter (POST) with pagination should return paginated backup data entries', async () => { const response = await request(app.getHttpServer()) - .get('/backupData?offset=0&limit=1') - .expect(200); + .post('/backupData/filter?offset=0&limit=1') + .expect(201); expect(response.body).toEqual({ data: [ @@ -117,10 +117,10 @@ describe('BackupDataController (e2e)', () => { }); }); - it('/backupData (GET) with date range should return backup data entries within date range', async () => { + it('/backupData/filter (POST) with date range should return backup data entries within date range', async () => { const response = await request(app.getHttpServer()) - .get('/backupData?fromDate=2023-12-01&toDate=2023-12-31') - .expect(200); + .post('/backupData/filter?fromDate=2023-12-01&toDate=2023-12-31') + .expect(201); expect(response.body).toEqual({ data: [ @@ -139,10 +139,10 @@ describe('BackupDataController (e2e)', () => { where: { creationDate: expect.any(Object), type: BackupType.FULL }, }); }); - it('/backupData (GET) with taskId should return backup data entries with the specified taskId', async () => { + it('/backupData/filter (POST) with taskId should return backup data entries with the specified taskId', async () => { await request(app.getHttpServer()) - .get('/backupData?taskId=task-123') - .expect(200); + .post('/backupData/filter?taskId=task-123') + .expect(201); expect(mockBackupDataRepository.findAndCount).toBeCalledWith({ order: { creationDate: 'DESC' }, @@ -150,10 +150,10 @@ describe('BackupDataController (e2e)', () => { }); }); - it('/backupData (GET) with taskName should return backup data entries with the specified taskName', async () => { + it('/backupData/filter (POST) with taskName should return backup data entries with the specified taskName', async () => { await request(app.getHttpServer()) - .get('/backupData?taskName=backup-task') - .expect(200); + .post('/backupData/filter?taskName=backup-task') + .expect(201); expect(mockBackupDataRepository.findAndCount).toBeCalledWith({ order: { creationDate: 'DESC' }, @@ -163,4 +163,31 @@ describe('BackupDataController (e2e)', () => { }, }); }); + + it('/backupData/filter (POST) with multiple taskIds should return backup data entries with the specified taskIds', async () => { + const response = await request(app.getHttpServer()) + .post('/backupData/filter') + .send({ taskIds: ['task-1', 'task-2'] }) + .expect(201); + + expect(response.body).toEqual({ + data: [ + { + ...mockBackupDataEntity, + creationDate: mockBackupDataEntity.creationDate.toISOString(), + }, + ], + paginationData: { + total: 1, + }, + }); + + expect(mockBackupDataRepository.findAndCount).toBeCalledWith({ + order: { creationDate: 'DESC' }, + where: { + taskId: [{ id: 'task-1' }, { id: 'task-2' }], + type: BackupType.FULL, + }, + }); + }); }); diff --git a/apps/backend/src/app/backupData/backupData.service.spec.ts b/apps/backend/src/app/backupData/backupData.service.spec.ts index 732c1d0..728a3f6 100644 --- a/apps/backend/src/app/backupData/backupData.service.spec.ts +++ b/apps/backend/src/app/backupData/backupData.service.spec.ts @@ -142,6 +142,16 @@ describe('BackupDataService', () => { type: BackupType.FULL, }); }); + + it('should create a where clause for multiple taskIds', () => { + const filterDto: BackupDataFilterDto = {}; + const filterByTaskIdsDto = { taskIds: ['task-1', 'task-2'] }; + const where = service.createWhereClause(filterDto, filterByTaskIdsDto); + expect(where).toEqual({ + taskId: [{ id: 'task-1' }, { id: 'task-2' }], + type: BackupType.FULL, + }); + }); }); describe('createOrderClause', () => {