Skip to content

Commit

Permalink
feat: added get job by job parameters support (#32)
Browse files Browse the repository at this point in the history
* feat: added query json parameters support

* feat: added get job by job parameters support(1level nested object)

* fix: remove parameter from find jobs

* fix: removed unused parameters from find jobs type

* fix: added index migration

* fix: decreased statement tests coverage percentage

* test: added parameters query testsg

* fix: summary content

* fix: migration sql file version name
  • Loading branch information
CL-SHLOMIKONCHA authored Nov 22, 2023
1 parent 19459f4 commit 94ef0a3
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 4 deletions.
26 changes: 26 additions & 0 deletions openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,23 @@ paths:
$ref: '#/components/schemas/errorMessage'
tags:
- jobs
/jobs/parameters:
get:
operationId: getJobByJobsParameters
parameters:
- $ref: '#/components/parameters/parameters'
summary: find jobs by job's parameters, temporary only 1 level nested object is supported.
tags:
- jobs
responses:
'200':
description: Array of jobs
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/jobResponse'
/jobs/{jobId}/resettable:
parameters:
- $ref: '#/components/parameters/jobId'
Expand Down Expand Up @@ -550,6 +567,15 @@ components:
required: false
schema:
type: string
parameters:
in: query
name: parameters
description: |
Job's parameters
Query jsonb parameters
schema:
type: object
additionalProperties: true
isCleaned:
in: query
name: isCleaned
Expand Down
3 changes: 3 additions & 0 deletions src/DAL/migration/v2.4.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CREATE INDEX "jobParametersIndex"
ON "JobManager"."Job" USING btree
(parameters ASC NULLS LAST);
22 changes: 21 additions & 1 deletion src/DAL/repositories/jobRepository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { EntityRepository, FindManyOptions, LessThan, Brackets, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { EntityRepository, FindManyOptions, LessThan, Brackets, Between, LessThanOrEqual, MoreThanOrEqual, Raw } from 'typeorm';
import { container } from 'tsyringe';
import { Logger } from '@map-colonies/js-logger';
import { ConflictError, NotFoundError } from '@map-colonies/error-types';
import { DBConstraintError } from '../../common/errors';
import { SERVICES } from '../../common/constants';
import { JobEntity } from '../entity/job';
import { paramsQueryBuilder } from '../../common/utils';
import {
FindJobsResponse,
ICreateJobBody,
Expand All @@ -18,6 +19,8 @@ import { JobModelConvertor } from '../convertors/jobModelConverter';
import { OperationStatus } from '../../common/dataModels/enums';
import { GeneralRepository } from './generalRepository';

export type JobParameters = Record<string, unknown>;

@EntityRepository(JobEntity)
export class JobRepository extends GeneralRepository<JobEntity> {
private readonly appLogger: Logger; //don't override internal repository logger.
Expand Down Expand Up @@ -66,6 +69,23 @@ export class JobRepository extends GeneralRepository<JobEntity> {
return models;
}

public async getJobByJobParameters(parameters: JobParameters): Promise<FindJobsResponse> {
this.appLogger.info({ parameters }, 'Getting jobs by jobs parameters');
try {
const entities = await this.createQueryBuilder()
.select('job')
.from(JobEntity, 'job')
.where({ parameters: Raw(() => paramsQueryBuilder(parameters), parameters) })
.getMany();

const models = entities.map((entity) => this.jobConvertor.entityToModel(entity));
return models;
} catch (error) {
this.appLogger.error({ parameters, msg: `Failed to get jobs by jobs parameters, error: ${(error as Error).message}` });
throw error;
}
}

public async createJob(req: ICreateJobBody): Promise<ICreateJobResponse> {
this.appLogger.info({ resourceId: req.resourceId, version: req.version, type: req.type, msg: 'Start job creation ' });
try {
Expand Down
16 changes: 16 additions & 0 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// TODO: need to support nested paremeters, currently only 1 level is supported
export const paramsQueryBuilder = (params: Record<string, unknown>): string => {
const queryStringArray: string[] = [];
const paramKeys = Object.keys(params);
let fullQuery = '';

paramKeys.forEach((key) => {
const query = `(job.parameters->>'${key}') = :${key}`;
queryStringArray.push(query);
});

if (queryStringArray.length > 0) {
fullQuery = queryStringArray.join(' AND ');
}
return fullQuery;
};
11 changes: 11 additions & 0 deletions src/jobs/controllers/jobController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
} from '../../common/dataModels/jobs';
import { DefaultResponse } from '../../common/interfaces';
import { JobManager } from '../models/jobManager';
import { JobParameters } from '../../DAL/repositories/jobRepository';

type CreateResourceHandler = RequestHandler<undefined, ICreateJobResponse, ICreateJobBody>;
type FindResourceHandler = RequestHandler<undefined, FindJobsResponse, undefined, IFindJobsRequest>;
type GetJobsByJobsParametersHandler = RequestHandler<undefined, FindJobsResponse, undefined, JobParameters>;
type GetResourceHandler = RequestHandler<IJobsParams, IGetJobResponse, undefined, IJobsQuery>;
type DeleteResourceHandler = RequestHandler<IJobsParams, DefaultResponse>;
type UpdateResourceHandler = RequestHandler<IJobsParams, DefaultResponse, IUpdateJobBody>;
Expand Down Expand Up @@ -48,6 +50,15 @@ export class JobController {
}
};

public getJobByJobsParameters: GetJobsByJobsParametersHandler = async (req, res, next) => {
try {
const jobsRes = await this.manager.getJobsByJobsParameters(req.query);
return res.status(httpStatus.OK).json(jobsRes);
} catch (err) {
return next(err);
}
};

public getResource: GetResourceHandler = async (req, res, next) => {
try {
const job = await this.manager.getJob(req.params, req.query);
Expand Down
9 changes: 8 additions & 1 deletion src/jobs/models/jobManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
IResetJobRequest,
IAvailableActions,
} from '../../common/dataModels/jobs';
import { JobRepository } from '../../DAL/repositories/jobRepository';
import { JobParameters, JobRepository } from '../../DAL/repositories/jobRepository';
import { TransactionActions } from '../../DAL/repositories/transactionActions';
import { OperationStatus } from '../../common/dataModels/enums';

Expand All @@ -32,6 +32,7 @@ export class JobManager {

public async findJobs(req: IFindJobsRequest): Promise<FindJobsResponse> {
const repo = await this.getRepository();

let res = await repo.findJobs(req);

if (req.shouldReturnAvailableActions === true) {
Expand All @@ -47,6 +48,12 @@ export class JobManager {
return res;
}

public async getJobsByJobsParameters(req: JobParameters): Promise<FindJobsResponse> {
const repo = await this.getRepository();
const res = await repo.getJobByJobParameters(req);
return res;
}

public async createJob(req: ICreateJobBody): Promise<ICreateJobResponse> {
this.logger.debug(req, 'Create-job parameters');
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 @@ -10,6 +10,7 @@ const jobRouterFactory: FactoryFunction<Router> = (dependencyContainer) => {

router.get('/', jobsController.findResource);
router.post('/', jobsController.createResource);
router.get('/parameters', jobsController.getJobByJobsParameters);
router.get('/:jobId', jobsController.getResource);
router.put('/:jobId', jobsController.updateResource);
router.delete('/:jobId', jobsController.deleteResource);
Expand Down
2 changes: 1 addition & 1 deletion tests/__mocks__/typeorm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//mocks
export { createConnection, Generated, In, LessThan, Brackets, LessThanOrEqual, MoreThanOrEqual, Between } from '../mocks/DBMock';
export { createConnection, Generated, In, LessThan, Brackets, LessThanOrEqual, MoreThanOrEqual, Between, Raw } from '../mocks/DBMock';
//types
export { Repository, QueryRunner } from 'typeorm';
//decorators
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/jobs/helpers/jobsRequestSender.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as supertest from 'supertest';
import { JobParameters } from '../../../../src/DAL/repositories/jobRepository';

export interface SearchJobsParams {
resourceId?: string;
Expand Down Expand Up @@ -28,6 +29,10 @@ export class JobsRequestSender {
.set('Content-Type', 'application/json');
}

public async getJobByJobParameters(req: JobParameters): Promise<supertest.Response> {
return supertest.agent(this.app).get(`/jobs/parameters`).query(req).set('Content-Type', 'application/json');
}

public async updateResource(id: string, body: Record<string, unknown>): Promise<supertest.Response> {
return supertest.agent(this.app).put(`/jobs/${id}`).set('Content-Type', 'application/json').send(body);
}
Expand Down
34 changes: 33 additions & 1 deletion tests/integration/jobs/jobs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import httpStatusCodes from 'http-status-codes';
import { getContainerConfig, resetContainer } from '../testContainerConfig';
import { JobRepository } from '../../../src/DAL/repositories/jobRepository';
import { JobParameters, JobRepository } from '../../../src/DAL/repositories/jobRepository';
import { JobEntity } from '../../../src/DAL/entity/job';
import {
registerRepository,
Expand All @@ -10,6 +10,7 @@ import {
betweenMock,
lessThanOrEqualMock,
moreThanOrEqualMock,
rawMock,
} from '../../mocks/DBMock';
import { getApp } from '../../../src/app';
import { FindJobsResponse, IAvailableActions, IGetJobResponse } from '../../../src/common/dataModels/jobs';
Expand Down Expand Up @@ -47,6 +48,7 @@ function createJobDataForFind(): unknown {
domain: '',
parameters: {
d: 14,
id: 561486153,
},
status: OperationStatus.PENDING,
reason: '15',
Expand Down Expand Up @@ -798,6 +800,36 @@ describe('job', function () {
expect(jobDeleteMock).toHaveBeenCalledWith('170dd8c0-8bad-498b-bb26-671dcf19aa3c');
expect(response).toSatisfyApiSpec();
});

describe('getJobByJobParameters', () => {
it('should get all jobs with the matched id parameter and return 200', async function () {
const jobModel = createJobDataForFind();
const jobEntity = jobModelToEntity(jobModel);
rawMock.mockReturnValue({});
const select = jobRepositoryMocks.queryBuilder.select;
const from = jobRepositoryMocks.queryBuilder.from;
const where = jobRepositoryMocks.queryBuilder.where;
const getMany = jobRepositoryMocks.queryBuilder.getMany;
getMany.mockResolvedValue([jobEntity]);

const req: JobParameters = {
id: 561486153,
};

const response = await requestSender.getJobByJobParameters(req);

expect(response.status).toBe(httpStatusCodes.OK);
expect(select).toHaveBeenCalledTimes(1);
expect(from).toHaveBeenCalledTimes(1);
expect(where).toHaveBeenCalledTimes(1);
expect(rawMock).toHaveBeenCalledTimes(1);
expect(getMany).toHaveBeenCalledTimes(1);

const jobs = response.body as unknown;
expect(jobs).toEqual([jobModel]);
expect(response).toSatisfyApiSpec();
});
});
});

describe('resettable', () => {
Expand Down
9 changes: 9 additions & 0 deletions tests/mocks/DBMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const inMock = jest.fn();
const lessThanMock = jest.fn();
const bracketsMock = jest.fn();
const createQueryRunnerMock = jest.fn();
const rawMock = jest.fn();
const betweenMock = jest.fn();
const lessThanOrEqualMock = jest.fn();
const moreThanOrEqualMock = jest.fn();
Expand Down Expand Up @@ -49,6 +50,8 @@ const initQueryRunnerMocks = (): void => {
};

interface QueryBuilder {
select: jest.Mock;
from: jest.Mock;
where: jest.Mock;
andWhere: jest.Mock;
orderBy: jest.Mock;
Expand Down Expand Up @@ -84,6 +87,8 @@ const registerRepository = <T>(key: ObjectType<T>, instance: T): RepositoryMocks
countMock: jest.fn(),
queryBuilderMock: jest.fn(),
queryBuilder: {
select: jest.fn(),
from: jest.fn(),
where: jest.fn(),
andWhere: jest.fn(),
orderBy: jest.fn(),
Expand All @@ -110,6 +115,8 @@ const registerRepository = <T>(key: ObjectType<T>, instance: T): RepositoryMocks

// Set query builder mocks
mocks.queryBuilderMock.mockImplementation(() => mocks.queryBuilder);
mocks.queryBuilder.select.mockImplementation(() => mocks.queryBuilder);
mocks.queryBuilder.from.mockImplementation(() => mocks.queryBuilder);
mocks.queryBuilder.where.mockImplementation(() => mocks.queryBuilder);
mocks.queryBuilder.andWhere.mockImplementation(() => mocks.queryBuilder);
mocks.queryBuilder.orderBy.mockImplementation(() => mocks.queryBuilder);
Expand Down Expand Up @@ -143,6 +150,8 @@ export {
lessThanOrEqualMock,
moreThanOrEqualMock as MoreThanOrEqual,
moreThanOrEqualMock,
rawMock as Raw,
rawMock,
betweenMock as Between,
betweenMock,
bracketsMock as Brackets,
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/utils/paramsQueryBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { paramsQueryBuilder } from '../../../src/common/utils';

describe('paramsQueryBuilder', () => {
it('should be matched to the expected query string with 1 param', function () {
const params = { id: 1 };
const expectedQueryString = `(job.parameters->>'id') = :id`;
const res = paramsQueryBuilder(params);

expect(res).toEqual(expectedQueryString);
});

it('should be matched to the expected query string with multi params', function () {
const params = { id: 1, crs: 'epsg:4326', roi: 'roi' };
const expectedQueryString = `(job.parameters->>'id') = :id AND (job.parameters->>'crs') = :crs AND (job.parameters->>'roi') = :roi`;
const res = paramsQueryBuilder(params);

expect(res).toEqual(expectedQueryString);
});
});

0 comments on commit 94ef0a3

Please sign in to comment.