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

[O2B-719] Add range based filtering for run number #1810

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
33 changes: 32 additions & 1 deletion lib/domain/dtos/filters/RunFilterDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,39 @@ const EorReasonFilterDto = Joi.object({
description: Joi.string(),
});

/**
* Validates run numbers ranges to not exceed 100 runs
*
* @param {*} value The value to validate
* @param {*} helpers The helpers object
* @returns {Object} The value if validation passes
*/
const validateRange = (value, helpers) => {
const MAX_RANGE_SIZE = 100;

const runNumbers = value.split(',').map((runNumber) => runNumber.trim());

for (const runNumber of runNumbers) {
if (runNumber.includes('-')) {
const [start, end] = runNumber.split('-').map((n) => parseInt(n, 10));
if (Number.isNaN(start) || Number.isNaN(end) || start > end) {
return helpers.error('any.invalid', { message: `Invalid range: ${runNumber}` });
}
const rangeSize = end - start + 1;

if (rangeSize > MAX_RANGE_SIZE) {
return helpers.error('any.invalid', { message: `Given range(${rangeSize}), exceeds max size of 100 runs: ${runNumber}` });
}
}
}

return value;
};

exports.RunFilterDto = Joi.object({
runNumbers: Joi.string().trim(),
runNumbers: Joi.string().trim().custom(validateRange).messages({
'any.invalid': '{{#message}}',
}),
calibrationStatuses: Joi.array().items(...RUN_CALIBRATION_STATUS),
definitions: CustomJoi.stringArray().items(Joi.string().uppercase().trim().valid(...RUN_DEFINITIONS)),
eorReason: EorReasonFilterDto,
Expand Down
35 changes: 26 additions & 9 deletions lib/usecases/run/GetAllRunsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,34 @@ class GetAllRunsUseCase {
} = filter;

if (runNumbers) {
const runNumberList = runNumbers.split(SEARCH_ITEMS_SEPARATOR)
.map((runNumber) => parseInt(runNumber, 10))
.filter((runNumber) => !Number.isNaN(runNumber));
const runNumberCriteria = runNumbers.split(SEARCH_ITEMS_SEPARATOR)
.map((runNumbers) => runNumbers.trim())
.filter(Boolean);

if (runNumberList.length) {
if (runNumberList.length > 1) {
// Multiple run numbers.
filteringQueryBuilder.where('runNumber').oneOf(...runNumberList);
const runNumberSet = new Set();

runNumberCriteria.forEach((runNumber) => {
if (runNumber.includes('-')) {
const [start, end] = runNumber.split('-').map((n) => parseInt(n, 10));
if (!Number.isNaN(start) && !Number.isNaN(end)) {
for (let i = start; i <= end; i++) {
runNumberSet.add(i);
}
}
} else {
const parsedRunNumber = parseInt(runNumber, 10);
if (!Number.isNaN(parsedRunNumber)) {
runNumberSet.add(parsedRunNumber);
}
}
});

const finalRunNumberList = Array.from(runNumberSet);
if (finalRunNumberList.length) {
if (finalRunNumberList.length > 1) {
filteringQueryBuilder.where('runNumber').oneOf(...finalRunNumberList);
} else {
// One run number.
const [runNumber] = runNumberList;
const [runNumber] = finalRunNumberList;
filteringQueryBuilder.where('runNumber').substring(`${runNumber}`);
}
}
Expand Down
19 changes: 19 additions & 0 deletions test/api/runs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ module.exports = () => {
expect(runs).to.lengthOf(2);
});

it('should successfully filter on run number range', async () => {
const response = await request(server).get('/api/runs?filter[runNumbers]=1-5,8,12,20-30');

expect(response.status).to.equal(200);
const { data: runs } = response.body;
expect(runs).to.lengthOf(18);
});

it('should return 400 if range exceeds maximum of 100', async () => {
const runNumberRange = '1-108';
const rangeSize = '108';
const response = await request(server).get(`/api/runs?filter[runNumbers]=${runNumberRange}`);

expect(response.status).to.equal(400);
const { errors: [error] } = response.body;
expect(error.title).to.equal('Invalid Attribute');
expect(error.detail).to.equal(`Given range(${rangeSize}), exceeds max size of 100 runs: ${runNumberRange}`);
});

it('should return 400 if the calibration status filter is invalid', async () => {
{
const response = await request(server).get('/api/runs?filter[calibrationStatuses]=invalid');
Expand Down
8 changes: 8 additions & 0 deletions test/lib/usecases/run/GetAllRunsUseCase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ module.exports = () => {
expect(runs[1].runNumber).to.equal(17);
});

it('should return an array, only containing runs with specified run number and ranges', async () => {
getAllRunsDto.query = { filter: { runNumbers: '1-5,8,12,20-30' } };
const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto);

expect(runs).to.be.an('array');
expect(runs).to.have.lengthOf(18);
});

it('should return runs sorted by runNumber', async () => {
{
const { runs } = await new GetAllRunsUseCase().execute({ query: { sort: { runNumber: 'ASC' } } });
Expand Down
Loading