Skip to content

Commit

Permalink
feat: enable search with multiple types and statuses (MAPCO-4427, MAP…
Browse files Browse the repository at this point in the history
…CO-4430) (#41)

* feat: enable search with multiple types and statuses

* fix: prettier

* fix: pr changes

* fix: pr changes

* fix: rename to plural

* fix: Define `ts-jest` config under `globals` is deprecated
  • Loading branch information
RonitKissis authored Jul 14, 2024
1 parent 3f1a9cb commit 17a4724
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 12 deletions.
58 changes: 58 additions & 0 deletions openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
statuses:
type: array
items:
$ref: '#/components/schemas/status'
types:
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
Expand Down
39 changes: 39 additions & 0 deletions src/DAL/repositories/jobRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IFindJobsRequest,
IUpdateJobRequest,
IJobsQuery,
IFindJobsByCriteriaBody,
} from '../../common/dataModels/jobs';
import { JobModelConvertor } from '../convertors/jobModelConverter';
import { OperationStatus } from '../../common/dataModels/enums';
Expand Down Expand Up @@ -69,6 +70,44 @@ export class JobRepository extends GeneralRepository<JobEntity> {
return models;
}

public async findJobsByCriteria(req: IFindJobsByCriteriaBody): Promise<FindJobsResponse> {
const filter: Record<string, unknown> = {
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.types?.length ?? 0) > 0) {
queryBuilder.andWhere('job.type IN (:...types)', { types: req.types });
}
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));
return models;
}

public async getJobByJobParameters(parameters: JobParameters): Promise<FindJobsResponse> {
this.appLogger.info({ parameters }, 'Getting jobs by jobs parameters');
try {
Expand Down
15 changes: 15 additions & 0 deletions src/common/dataModels/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ export interface IFindJobsRequest {
domain?: string;
}

export interface IFindJobsByCriteriaBody {
resourceId?: string;
version?: string;
isCleaned?: boolean;
statuses?: OperationStatus[];
types?: string[];
shouldReturnTasks?: boolean;
shouldReturnAvailableActions?: boolean;
productType?: string;
fromDate?: string;
tillDate?: string;
internalId?: string;
domain?: string;
}

export interface ICreateJobBody {
resourceId: string;
version: string;
Expand Down
11 changes: 11 additions & 0 deletions src/jobs/controllers/jobController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
FindJobsResponse,
ICreateJobBody,
ICreateJobResponse,
IFindJobsByCriteriaBody,
IFindJobsRequest,
IGetJobResponse,
IIsResettableResponse,
Expand All @@ -22,6 +23,7 @@ import { JobParameters } from '../../DAL/repositories/jobRepository';

type CreateResourceHandler = RequestHandler<undefined, ICreateJobResponse, ICreateJobBody>;
type FindResourceHandler = RequestHandler<undefined, FindJobsResponse, undefined, IFindJobsRequest>;
type FindResourceByCriteriaHandler = RequestHandler<undefined, FindJobsResponse, IFindJobsByCriteriaBody>;
type GetJobsByJobsParametersHandler = RequestHandler<undefined, FindJobsResponse, undefined, JobParameters>;
type GetResourceHandler = RequestHandler<IJobsParams, IGetJobResponse, undefined, IJobsQuery>;
type DeleteResourceHandler = RequestHandler<IJobsParams, DefaultResponse>;
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions src/jobs/models/jobManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,6 +52,24 @@ export class JobManager {
return res;
}

@withSpanAsyncV4
public async findJobsByCriteria(req: IFindJobsByCriteriaBody): Promise<FindJobsResponse> {
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<FindJobsResponse> {
const repo = await this.getRepository();
Expand Down
1 change: 1 addition & 0 deletions src/jobs/routes/jobRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const jobRouterFactory: FactoryFunction<Router> = (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);
Expand Down
7 changes: 1 addition & 6 deletions tests/configurations/integration/jest.config.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
8 changes: 2 additions & 6 deletions tests/configurations/unit/jest.config.js
Original file line number Diff line number Diff line change
@@ -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: ['<rootDir>/tests/unit/**/*.spec.ts'],
coverageReporters: ['text', 'html'],
Expand All @@ -18,6 +13,7 @@ module.exports = {
'!**/controllers/**',
'!**/routes/**',
'!<rootDir>/src/*',
'!<rootDir>/src/DAL/*',
],
coverageDirectory: '<rootDir>/coverage',
reporters: [
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/jobs/helpers/jobsRequestSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>): Promise<supertest.Response> {
return supertest.agent(this.app).post(`/jobs/find`).set('Content-Type', 'application/json').send(body);
}

public async deleteResource(id: string): Promise<supertest.Response> {
return supertest.agent(this.app).delete(`/jobs/${id}`).set('Content-Type', 'application/json');
}
Expand Down
62 changes: 62 additions & 0 deletions tests/integration/jobs/jobs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,68 @@ describe('job', function () {
});
});

describe('findJobsByCriteria', () => {
it('should get all jobs and return 200', async function () {
const filter = {
isCleaned: true,
resourceId: '1',
statuses: ['Pending'],
types: ['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 not find matched jobs and return status 200 with an empty array', async function () {
const filter = {
isCleaned: true,
resourceId: '1',
statuses: ['Pending'],
types: ['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.body).toEqual([]);

expect(response).toSatisfyApiSpec();
});
});

describe('getJob', () => {
it('should get specific job and return 200', async function () {
const jobModel = createJobDataForGetJob();
Expand Down

0 comments on commit 17a4724

Please sign in to comment.