Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added get job by job parameters support #32

Merged
merged 9 commits into from
Nov 22, 2023
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:
asafMasa marked this conversation as resolved.
Show resolved Hide resolved
operationId: getJobByJobsParameters
parameters:
- $ref: '#/components/parameters/parameters'
summary: find jobs by job 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/v3.0.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[] = [];
asafMasa marked this conversation as resolved.
Show resolved Hide resolved
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) => {
asafMasa marked this conversation as resolved.
Show resolved Hide resolved
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);
});
});
Loading