From 39e19a6e79bbb467167111970168a4837ed80088 Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 14:40:23 +0300 Subject: [PATCH 1/6] feat: enable search with multiple types and statuses --- openapi3.yaml | 58 ++++++++++++++++++ src/DAL/repositories/jobRepository.ts | 39 ++++++++++++ src/common/dataModels/jobs.ts | 15 +++++ src/jobs/controllers/jobController.ts | 11 ++++ src/jobs/models/jobManager.ts | 19 ++++++ src/jobs/routes/jobRouter.ts | 1 + tests/configurations/unit/jest.config.js | 1 + .../jobs/helpers/jobsRequestSender.ts | 4 ++ tests/integration/jobs/jobs.spec.ts | 61 +++++++++++++++++++ 9 files changed, 209 insertions(+) diff --git a/openapi3.yaml b/openapi3.yaml index 28d051b..f02092a 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -70,6 +70,27 @@ paths: application/json: schema: $ref: '#/components/schemas/errorMessage' + /jobs/find: + post: + operationId: findJobsByCriteria + summary: gets jobs by criteria + tags: + - jobs + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/getJobsByCriteria' + responses: + '200': + description: Array of jobs + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/jobResponse' /jobs/{jobId}: parameters: - $ref: '#/components/parameters/jobId' @@ -668,6 +689,43 @@ components: type: string format: uuid schemas: + getJobsByCriteria: + type: object + description: get jobs criteria + properties: + resourceId: + type: string + version: + type: string + isCleaned: + type: boolean + status: + type: array + items: + $ref: '#/components/schemas/status' + type: + type: array + items: + type: string + shouldReturnTasks: + type: boolean + default: false + fromDate: + type: string + format: date-time + tillDate: + type: string + format: date-time + productType: + type: string + internalId: + type: string + format: uuid + domain: + type: string + shouldReturnAvailableActions: + type: boolean + default: false findTaskRequest: type: object description: task find model diff --git a/src/DAL/repositories/jobRepository.ts b/src/DAL/repositories/jobRepository.ts index bdb3304..6150893 100644 --- a/src/DAL/repositories/jobRepository.ts +++ b/src/DAL/repositories/jobRepository.ts @@ -14,6 +14,7 @@ import { IFindJobsRequest, IUpdateJobRequest, IJobsQuery, + IFindJobsByCriteriaBody, } from '../../common/dataModels/jobs'; import { JobModelConvertor } from '../convertors/jobModelConverter'; import { OperationStatus } from '../../common/dataModels/enums'; @@ -69,6 +70,44 @@ export class JobRepository extends GeneralRepository { return models; } + public async findJobsByCriteria(req: IFindJobsByCriteriaBody): Promise { + const filter: Record = { + resourceId: req.resourceId, + version: req.version, + isCleaned: req.isCleaned, + productType: req.productType, + internalId: req.internalId, + domain: req.domain, + }; + + if (req.fromDate != undefined && req.tillDate != undefined) { + filter.updateTime = Between(req.fromDate, req.tillDate); + } else if (req.tillDate != undefined) { + filter.updateTime = LessThanOrEqual(req.tillDate); + } else if (req.fromDate != undefined) { + filter.updateTime = MoreThanOrEqual(req.fromDate); + } + + for (const key of Object.keys(filter)) { + if (filter[key] == undefined) { + delete filter[key]; + } + } + if (req.shouldReturnTasks !== false) { + filter.relations = ['tasks']; + } + const queryBuilder = this.createQueryBuilder().select('job').from(JobEntity, 'job').where(filter); + if ((req.type?.length ?? 0) > 0) { + queryBuilder.andWhere('job.type IN (:...types)', { types: req.type }); + } + if ((req.status?.length ?? 0) > 0) { + queryBuilder.andWhere('job.status IN (:...statuses)', { statuses: req.status }); + } + const entities = await queryBuilder.getMany(); + const models = entities.map((entity) => this.jobConvertor.entityToModel(entity)); + return models; + } + public async getJobByJobParameters(parameters: JobParameters): Promise { this.appLogger.info({ parameters }, 'Getting jobs by jobs parameters'); try { diff --git a/src/common/dataModels/jobs.ts b/src/common/dataModels/jobs.ts index 381796d..2f3df97 100644 --- a/src/common/dataModels/jobs.ts +++ b/src/common/dataModels/jobs.ts @@ -26,6 +26,21 @@ export interface IFindJobsRequest { domain?: string; } +export interface IFindJobsByCriteriaBody { + resourceId?: string; + version?: string; + isCleaned?: boolean; + status?: [OperationStatus]; + type?: [string]; + shouldReturnTasks?: boolean; + shouldReturnAvailableActions?: boolean; + productType?: string; + fromDate?: string; + tillDate?: string; + internalId?: string; + domain?: string; +} + export interface ICreateJobBody { resourceId: string; version: string; diff --git a/src/jobs/controllers/jobController.ts b/src/jobs/controllers/jobController.ts index 45d7c0f..5bda421 100644 --- a/src/jobs/controllers/jobController.ts +++ b/src/jobs/controllers/jobController.ts @@ -7,6 +7,7 @@ import { FindJobsResponse, ICreateJobBody, ICreateJobResponse, + IFindJobsByCriteriaBody, IFindJobsRequest, IGetJobResponse, IIsResettableResponse, @@ -22,6 +23,7 @@ import { JobParameters } from '../../DAL/repositories/jobRepository'; type CreateResourceHandler = RequestHandler; type FindResourceHandler = RequestHandler; +type FindResourceByCriteriaHandler = RequestHandler; type GetJobsByJobsParametersHandler = RequestHandler; type GetResourceHandler = RequestHandler; type DeleteResourceHandler = RequestHandler; @@ -50,6 +52,15 @@ export class JobController { } }; + public findResourceByCriteria: FindResourceByCriteriaHandler = async (req, res, next) => { + try { + const jobsRes = await this.manager.findJobsByCriteria(req.body); + return res.status(httpStatus.OK).json(jobsRes); + } catch (err) { + return next(err); + } + }; + public getJobByJobsParameters: GetJobsByJobsParametersHandler = async (req, res, next) => { try { const jobsRes = await this.manager.getJobsByJobsParameters(req.query); diff --git a/src/jobs/models/jobManager.ts b/src/jobs/models/jobManager.ts index f2fce09..3925f71 100644 --- a/src/jobs/models/jobManager.ts +++ b/src/jobs/models/jobManager.ts @@ -17,6 +17,7 @@ import { IIsResettableResponse, IResetJobRequest, IAvailableActions, + IFindJobsByCriteriaBody, } from '../../common/dataModels/jobs'; import { JobParameters, JobRepository } from '../../DAL/repositories/jobRepository'; import { TransactionActions } from '../../DAL/repositories/transactionActions'; @@ -51,6 +52,24 @@ export class JobManager { return res; } + @withSpanAsyncV4 + public async findJobsByCriteria(req: IFindJobsByCriteriaBody): Promise { + const repo = await this.getRepository(); + let res = await repo.findJobsByCriteria(req); + + if (req.shouldReturnAvailableActions === true) { + if (res.length !== 0) { + res = await Promise.all( + res.map(async (job) => ({ + ...job, + availableActions: await this.getAvailableActions(job), + })) + ); + } + } + return res; + } + @withSpanAsyncV4 public async getJobsByJobsParameters(req: JobParameters): Promise { const repo = await this.getRepository(); diff --git a/src/jobs/routes/jobRouter.ts b/src/jobs/routes/jobRouter.ts index e238267..c98b076 100644 --- a/src/jobs/routes/jobRouter.ts +++ b/src/jobs/routes/jobRouter.ts @@ -9,6 +9,7 @@ const jobRouterFactory: FactoryFunction = (dependencyContainer) => { const tasksController = dependencyContainer.resolve(TaskController); router.get('/', jobsController.findResource); + router.post('/find', jobsController.findResourceByCriteria); router.post('/', jobsController.createResource); router.get('/parameters', jobsController.getJobByJobsParameters); router.get('/:jobId', jobsController.getResource); diff --git a/tests/configurations/unit/jest.config.js b/tests/configurations/unit/jest.config.js index 9c34991..d7f9a9f 100644 --- a/tests/configurations/unit/jest.config.js +++ b/tests/configurations/unit/jest.config.js @@ -18,6 +18,7 @@ module.exports = { '!**/controllers/**', '!**/routes/**', '!/src/*', + '!/src/DAL/*' ], coverageDirectory: '/coverage', reporters: [ diff --git a/tests/integration/jobs/helpers/jobsRequestSender.ts b/tests/integration/jobs/helpers/jobsRequestSender.ts index 995be6a..940d690 100644 --- a/tests/integration/jobs/helpers/jobsRequestSender.ts +++ b/tests/integration/jobs/helpers/jobsRequestSender.ts @@ -41,6 +41,10 @@ export class JobsRequestSender { return supertest.agent(this.app).post(`/jobs`).set('Content-Type', 'application/json').send(body); } + public async findJobs(body: Record): Promise { + return supertest.agent(this.app).post(`/jobs/find`).set('Content-Type', 'application/json').send(body); + } + public async deleteResource(id: string): Promise { return supertest.agent(this.app).delete(`/jobs/${id}`).set('Content-Type', 'application/json'); } diff --git a/tests/integration/jobs/jobs.spec.ts b/tests/integration/jobs/jobs.spec.ts index 49ee071..bd198f6 100644 --- a/tests/integration/jobs/jobs.spec.ts +++ b/tests/integration/jobs/jobs.spec.ts @@ -589,6 +589,67 @@ describe('job', function () { }); }); + describe('findJobsByCriteria', () => { + it('should get all jobs and return 200', async function () { + const filter = { + isCleaned: true, + resourceId: '1', + status: ['Pending'], + type: ['2'], + version: '3', + }; + const jobModel = createJobDataForFind(); + const jobEntity = jobModelToEntity(jobModel); + const select = jobRepositoryMocks.queryBuilder.select; + const from = jobRepositoryMocks.queryBuilder.from; + const where = jobRepositoryMocks.queryBuilder.where; + const andWhere = jobRepositoryMocks.queryBuilder.andWhere; + const getMany = jobRepositoryMocks.queryBuilder.getMany; + getMany.mockResolvedValue([jobEntity]); + + const response = await requestSender.findJobs(filter); + + expect(response.status).toBe(httpStatusCodes.OK); + expect(select).toHaveBeenCalledTimes(1); + expect(from).toHaveBeenCalledTimes(1); + expect(where).toHaveBeenCalledTimes(1); + expect(andWhere).toHaveBeenCalledTimes(2); + expect(getMany).toHaveBeenCalledTimes(1); + + const jobs = response.body as unknown; + expect(jobs).toEqual([jobModel]); + expect(response).toSatisfyApiSpec(); + }); + + it('should no jobs and return 200', async function () { + const filter = { + isCleaned: true, + resourceId: '1', + status: ['Pending'], + type: ['2'], + version: '3', + }; + + const select = jobRepositoryMocks.queryBuilder.select; + const from = jobRepositoryMocks.queryBuilder.from; + const where = jobRepositoryMocks.queryBuilder.where; + const andWhere = jobRepositoryMocks.queryBuilder.andWhere; + const getMany = jobRepositoryMocks.queryBuilder.getMany; + getMany.mockResolvedValue([]); + + const response = await requestSender.findJobs(filter); + + expect(response.status).toBe(httpStatusCodes.OK); + expect(select).toHaveBeenCalledTimes(1); + expect(from).toHaveBeenCalledTimes(1); + expect(where).toHaveBeenCalledTimes(1); + expect(andWhere).toHaveBeenCalledTimes(2); + expect(getMany).toHaveBeenCalledTimes(1); + + expect(response).toSatisfyApiSpec(); + }); + }); + describe('getJob', () => { it('should get specific job and return 200', async function () { const jobModel = createJobDataForGetJob(); From 90fd61d0153ec0ecdcbb8f17d68e1cb7629f3fa8 Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 14:54:16 +0300 Subject: [PATCH 2/6] fix: prettier --- tests/configurations/unit/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/configurations/unit/jest.config.js b/tests/configurations/unit/jest.config.js index d7f9a9f..149e722 100644 --- a/tests/configurations/unit/jest.config.js +++ b/tests/configurations/unit/jest.config.js @@ -18,7 +18,7 @@ module.exports = { '!**/controllers/**', '!**/routes/**', '!/src/*', - '!/src/DAL/*' + '!/src/DAL/*', ], coverageDirectory: '/coverage', reporters: [ From 45d09d84d16e4cf3a0416b1262a1d575000cf34e Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 15:07:10 +0300 Subject: [PATCH 3/6] fix: pr changes --- src/common/dataModels/jobs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/dataModels/jobs.ts b/src/common/dataModels/jobs.ts index 2f3df97..9ad9548 100644 --- a/src/common/dataModels/jobs.ts +++ b/src/common/dataModels/jobs.ts @@ -30,8 +30,8 @@ export interface IFindJobsByCriteriaBody { resourceId?: string; version?: string; isCleaned?: boolean; - status?: [OperationStatus]; - type?: [string]; + status?: OperationStatus[]; + type?: string[]; shouldReturnTasks?: boolean; shouldReturnAvailableActions?: boolean; productType?: string; From 8f6ca7497cc34638da38f9ad414baa7cff4e2aa9 Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 15:24:09 +0300 Subject: [PATCH 4/6] fix: pr changes --- tests/integration/jobs/jobs.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/jobs/jobs.spec.ts b/tests/integration/jobs/jobs.spec.ts index bd198f6..9f7a87f 100644 --- a/tests/integration/jobs/jobs.spec.ts +++ b/tests/integration/jobs/jobs.spec.ts @@ -621,7 +621,7 @@ describe('job', function () { expect(response).toSatisfyApiSpec(); }); - it('should no jobs and return 200', async function () { + it('should not find matched jobs and return status 200 with an empty array', async function () { const filter = { isCleaned: true, resourceId: '1', @@ -645,6 +645,7 @@ describe('job', function () { expect(where).toHaveBeenCalledTimes(1); expect(andWhere).toHaveBeenCalledTimes(2); expect(getMany).toHaveBeenCalledTimes(1); + expect(response.body).toEqual([]); expect(response).toSatisfyApiSpec(); }); From de042406ffd95eccf011b6079e5d91492d23a426 Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 16:50:25 +0300 Subject: [PATCH 5/6] fix: rename to plural --- openapi3.yaml | 4 ++-- src/DAL/repositories/jobRepository.ts | 8 ++++---- src/common/dataModels/jobs.ts | 4 ++-- tests/integration/jobs/jobs.spec.ts | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openapi3.yaml b/openapi3.yaml index f02092a..e4476b8 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -699,11 +699,11 @@ components: type: string isCleaned: type: boolean - status: + statuses: type: array items: $ref: '#/components/schemas/status' - type: + types: type: array items: type: string diff --git a/src/DAL/repositories/jobRepository.ts b/src/DAL/repositories/jobRepository.ts index 6150893..aecc229 100644 --- a/src/DAL/repositories/jobRepository.ts +++ b/src/DAL/repositories/jobRepository.ts @@ -97,11 +97,11 @@ export class JobRepository extends GeneralRepository { filter.relations = ['tasks']; } const queryBuilder = this.createQueryBuilder().select('job').from(JobEntity, 'job').where(filter); - if ((req.type?.length ?? 0) > 0) { - queryBuilder.andWhere('job.type IN (:...types)', { types: req.type }); + if ((req.types?.length ?? 0) > 0) { + queryBuilder.andWhere('job.type IN (:...types)', { types: req.types }); } - if ((req.status?.length ?? 0) > 0) { - queryBuilder.andWhere('job.status IN (:...statuses)', { statuses: req.status }); + if ((req.statuses?.length ?? 0) > 0) { + queryBuilder.andWhere('job.status IN (:...statuses)', { statuses: req.statuses }); } const entities = await queryBuilder.getMany(); const models = entities.map((entity) => this.jobConvertor.entityToModel(entity)); diff --git a/src/common/dataModels/jobs.ts b/src/common/dataModels/jobs.ts index 9ad9548..3fb39c1 100644 --- a/src/common/dataModels/jobs.ts +++ b/src/common/dataModels/jobs.ts @@ -30,8 +30,8 @@ export interface IFindJobsByCriteriaBody { resourceId?: string; version?: string; isCleaned?: boolean; - status?: OperationStatus[]; - type?: string[]; + statuses?: OperationStatus[]; + types?: string[]; shouldReturnTasks?: boolean; shouldReturnAvailableActions?: boolean; productType?: string; diff --git a/tests/integration/jobs/jobs.spec.ts b/tests/integration/jobs/jobs.spec.ts index 9f7a87f..5596c8a 100644 --- a/tests/integration/jobs/jobs.spec.ts +++ b/tests/integration/jobs/jobs.spec.ts @@ -594,8 +594,8 @@ describe('job', function () { const filter = { isCleaned: true, resourceId: '1', - status: ['Pending'], - type: ['2'], + statuses: ['Pending'], + types: ['2'], version: '3', }; const jobModel = createJobDataForFind(); @@ -625,8 +625,8 @@ describe('job', function () { const filter = { isCleaned: true, resourceId: '1', - status: ['Pending'], - type: ['2'], + statuses: ['Pending'], + types: ['2'], version: '3', }; From e50eccc4ef9021ab69312df40a7a5e6321269354 Mon Sep 17 00:00:00 2001 From: RonitKissis Date: Thu, 11 Jul 2024 16:51:06 +0300 Subject: [PATCH 6/6] fix: Define `ts-jest` config under `globals` is deprecated --- tests/configurations/integration/jest.config.js | 7 +------ tests/configurations/unit/jest.config.js | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/configurations/integration/jest.config.js b/tests/configurations/integration/jest.config.js index b7e4b49..08fa4f4 100644 --- a/tests/configurations/integration/jest.config.js +++ b/tests/configurations/integration/jest.config.js @@ -1,11 +1,6 @@ module.exports = { transform: { - '^.+\\.ts$': 'ts-jest', - }, - globals: { - 'ts-jest': { - tsconfig: 'tsconfig.test.json', - }, + '^.+\\.ts$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], }, coverageReporters: ['text', 'html'], collectCoverage: true, diff --git a/tests/configurations/unit/jest.config.js b/tests/configurations/unit/jest.config.js index 149e722..ed3ecad 100644 --- a/tests/configurations/unit/jest.config.js +++ b/tests/configurations/unit/jest.config.js @@ -1,11 +1,6 @@ module.exports = { transform: { - '^.+\\.ts$': 'ts-jest', - }, - globals: { - 'ts-jest': { - tsconfig: 'tsconfig.test.json', - }, + '^.+\\.ts$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], }, testMatch: ['/tests/unit/**/*.spec.ts'], coverageReporters: ['text', 'html'],