Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/database/adapters/RunAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class RunAdapter {
*/
this.userAdapter = null;

/**
* @type {QcFlagAdapter|null}
*/
this.qcFlagAdapter = null;

this.toEntity = this.toEntity.bind(this);
this.toDatabase = this.toDatabase.bind(this);
this.toMinifiedEntity = this.toMinifiedEntity.bind(this);
Expand Down Expand Up @@ -167,6 +172,7 @@ class RunAdapter {
phaseShiftAtEndBeam2,
userStart,
userStop,
qcFlags,
} = databaseObject;

/**
Expand Down Expand Up @@ -285,6 +291,13 @@ class RunAdapter {
? lhcFill.collidingBunchesCount ?? extractNumberOfCollidingLhcBunchCrossings(lhcFill.fillingSchemeName)
: null;

const adaptedQcFlags = qcFlags ? qcFlags.map(this.qcFlagAdapter.toEntity) : [];
entityObject.qcFlags = adaptedQcFlags.reduce((acc, qcFlag) => {
acc[qcFlag.dplDetectorId] = acc[qcFlag.dplDetectorId] ?? [];
acc[qcFlag.dplDetectorId].push(qcFlag);
return acc;
}, {});

return entityObject;
}

Expand Down
1 change: 1 addition & 0 deletions lib/database/adapters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ runAdapter.logAdapter = logAdapter;
runAdapter.runTypeAdapter = runTypeAdapter;
runAdapter.tagAdapter = tagAdapter;
runAdapter.userAdapter = userAdapter;
runAdapter.qcFlagAdapter = qcFlagAdapter;

simulationPassQcFlagAdapter.simulationPassAdapter = simulationPassAdapter;
simulationPassQcFlagAdapter.qcFlagAdapter = qcFlagAdapter;
Expand Down
6 changes: 6 additions & 0 deletions lib/database/seeders/20240404100811-qc-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ module.exports = {
from: '2019-08-08 13:46:40',
to: '2019-08-09 07:50:00',
},
{
id: 6,
flag_id: 6,
from: '2019-08-09 08:50:00',
to: null,
},
{
id: 7,
flag_id: 7,
Expand Down
10 changes: 10 additions & 0 deletions lib/domain/dtos/GetAllRunsDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ const QueryDto = Joi.object({
filter: RunFilterDto,
page: PaginationDto,
sort: DtoFactory.order(['id', 'runNumber', 'text']),
include: Joi.object({ effectiveQcFlags: Joi.boolean().custom((effectiveQcFlags, helpers) => {
const [, { filter: { dataPassIds, simulationPassIds, lhcPeriodIds } = {} }] = helpers.state.ancestors;
const runsCollectionFilters = [dataPassIds, simulationPassIds, lhcPeriodIds].filter(({ length } = {}) => length >= 1);
Comment thread
xsalonx marked this conversation as resolved.
Outdated
if (runsCollectionFilters.length !== 1 || runsCollectionFilters[0].length !== 1) {
return helpers.message('Including effectiveQcFlags is allowed only when filtering by one and exactly one of: ' +
'dataPassIds, simulationPassIds, lhcPeriodIds with exactly one ID.');
}

return effectiveQcFlags;
}) }),
token: Joi.string(),
});

Expand Down
71 changes: 69 additions & 2 deletions lib/usecases/run/GetAllRunsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class GetAllRunsUseCase {
async execute(dto = {}) {
const filteringQueryBuilder = new QueryBuilder();
const { query = {} } = dto;
const { filter, page = {}, sort = { runNumber: 'desc' } } = query;
const { filter, page = {}, sort = { runNumber: 'desc' }, include: { effectiveQcFlags = false } = {} } = query;

const SEARCH_ITEMS_SEPARATOR = ',';

Expand Down Expand Up @@ -343,6 +343,19 @@ class GetAllRunsUseCase {
});
}

if (lhcPeriodIds) {
const runNumbers = (await RunRepository.findAll({
attributes: ['runNumber'],
raw: true,
include: {
association: 'lhcPeriod',
attributes: [],
where: { id: { [Op.in]: lhcPeriodIds } },
},
})).map(({ runNumber }) => runNumber);
filteringQueryBuilder.where('runNumber').oneOf(...runNumbers);
}

if (dataPassIds) {
const runNumbers = (await RunRepository.findAll({
attributes: ['runNumber'],
Expand Down Expand Up @@ -445,7 +458,7 @@ class GetAllRunsUseCase {
attributes: ['runNumber'],
raw: true,
include: {
association: 'lhcPeriods',
association: 'lhcPeriod',
attributes: [],
where: { id: { [Op.in]: lhcPeriodIds } },
},
Expand Down Expand Up @@ -516,6 +529,60 @@ class GetAllRunsUseCase {
fetchQueryBuilder.orderBy(property, sort[property]);
}

if (effectiveQcFlags) {
const [dataPassId] = filter?.dataPassIds ?? [];
const [simulationPassId] = filter?.simulationPassIds ?? [];
const [lhcPeriodId] = filter?.lhcPeriodIds ?? [];

if (Boolean(dataPassId) + Boolean(simulationPassId) + Boolean(lhcPeriodId) !== 1) {
throw new BadParameterError('If including QC flags, one and exactly one'
+ 'of `dataPassId`, `simulationPassId` or `lhcPeriodId` is required');
}

const commondQcFlagsAssociationDef = {
association: 'qcFlags',
required: false,
where: { deleted: false },
};

if (dataPassId) {
fetchQueryBuilder.include({
...commondQcFlagsAssociationDef,
include: [
{ association: 'dataPasses', required: true, where: { id: dataPassId } },
{ association: 'effectivePeriods', required: true },
],
});
} else if (simulationPassId) {
fetchQueryBuilder.include({
...commondQcFlagsAssociationDef,
include: [
{ association: 'simulationPasses', required: true, where: { id: simulationPassId } },
{ association: 'effectivePeriods', required: true },
],
});
} else {
fetchQueryBuilder.include({
...commondQcFlagsAssociationDef,
include: [
{ association: 'simulationPasses', required: false },
{ association: 'dataPasses', required: false },
{ association: 'effectivePeriods', required: true },
],
where: {
deleted: false,
[Op.or]: [
{ '$qcFlags.id$': null },
{
'$qcFlags.dataPasses.id$': null,
'$qcFlags.simulationPasses.id$': null,
},
],
},
});
}
}

const runs = (await RunRepository.findAll(fetchQueryBuilder)).map(runAdapter.toEntity);

return { count, runs };
Expand Down
4 changes: 2 additions & 2 deletions test/api/qcFlags.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ module.exports = () => {
expect(data).to.be.eql({
106: {
1: {
missingVerificationsCount: 1,
missingVerificationsCount: 2,
mcReproducible: false,
badEffectiveRunCoverage: 0.7222222,
badEffectiveRunCoverage: 0.9288889,
explicitlyNotBadEffectiveRunCoverage: 0,
},
},
Expand Down
34 changes: 34 additions & 0 deletions test/api/runs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { RunDetectorQualities } = require('../../lib/domain/enums/RunDetectorQual
const { RunCalibrationStatus } = require('../../lib/domain/enums/RunCalibrationStatus.js');
const { updateRun } = require('../../lib/server/services/run/updateRun.js');
const { RunDefinition } = require('../../lib/domain/enums/RunDefinition.js');
const { qcFlagService } = require('../../lib/server/services/qualityControlFlag/QcFlagService.js');

module.exports = () => {
before(resetDatabaseContent);
Expand Down Expand Up @@ -606,6 +607,39 @@ module.exports = () => {
expect(runs.every(({ aliceDipoleCurrent, aliceDipolePolarity }) =>
Math.round(aliceDipoleCurrent * (aliceDipolePolarity === 'NEGATIVE' ? -1 : 1) / 1000) === 0)).to.be.true;
});

it('should successfully handle query including QC flags', async () => {
{ // Data Passes
const response = await request(server).get(`/api/runs?filter[dataPassIds][]=1&include[effectiveQcFlags]=true`)
expect(response.status).to.equal(200);
const { data: runs } = response.body;

expect(runs).to.have.lengthOf(3);
expect(runs.find(({ runNumber }) => runNumber === 107).qcFlags['1'].map(({ id }) => id)).to.have.all.members([202, 201]);
expect(runs.find(({ runNumber }) => runNumber === 107).qcFlags['2'].map(({ id }) => id)).to.have.all.members([203]);
expect(runs.find(({ runNumber }) => runNumber === 106).qcFlags['1'].map(({ id }) => id)).to.have.all.members([3, 2, 1]);
}

{ // Simulation Passes
const response = await request(server).get(`/api/runs?filter[simulationPassIds][]=1&include[effectiveQcFlags]=true`)
expect(response.status).to.equal(200);
const { data: runs } = response.body;
expect(runs).to.have.lengthOf(3);

console.log('TOBEC', await qcFlagService.getAllPerSimulationPassAndRunAndDetector({ simulationPassId: 1, runNumber: 106, detectorId: 1}));

expect(runs.find(({ runNumber }) => runNumber === 106).qcFlags['1'].map(({ id }) => id)).to.have.all.members([6, 5]);
}

{ // Synchronous flags
const response = await request(server).get(`/api/runs?filter[lhcPeriodIds][]=1&include[effectiveQcFlags]=true`)
expect(response.status).to.equal(200);
const { data: runs } = response.body;
expect(runs).to.have.lengthOf(4);
expect(runs.find(({ runNumber }) => runNumber === 56).qcFlags['7'].map(({ id }) => id)).to.have.all.members([101, 100]);
expect(runs.find(({ runNumber }) => runNumber === 56).qcFlags['4'].map(({ id }) => id)).to.have.all.members([102]);
}
});
});

describe('GET /api/runs/reasonTypes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,9 @@ module.exports = () => {
expect(await qcFlagSummaryService.getSummary({ simulationPassId: 1 })).to.be.eql({
106: {
1: {
missingVerificationsCount: 1,
missingVerificationsCount: 2,
mcReproducible: false,
badEffectiveRunCoverage: 0.7222222,
badEffectiveRunCoverage: 0.9288889,
explicitlyNotBadEffectiveRunCoverage: 0,
},
},
Expand Down
57 changes: 56 additions & 1 deletion test/lib/usecases/run/GetAllRunsUseCase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const chai = require('chai');
const { RunQualities } = require('../../../../lib/domain/enums/RunQualities.js');
const { RunCalibrationStatus } = require('../../../../lib/domain/enums/RunCalibrationStatus.js');
const { RunDefinition } = require('../../../../lib/domain/enums/RunDefinition.js');
const assert = require('assert');
const { BadParameterError } = require('../../../../lib/server/errors/BadParameterError.js');

const { expect } = chai;

Expand Down Expand Up @@ -795,7 +797,7 @@ module.exports = () => {
expect(runs).to.have.lengthOf(0);
}
});

it('should successfully filter by detectors notBadFraction', async () => {
const dataPassIds = [1];
{
Expand Down Expand Up @@ -849,4 +851,57 @@ module.exports = () => {
expect(runs.map(({ runNumber }) => runNumber)).to.have.all.members([107]);
}
});

it('should successfully handle query including QC flags', async () => {
{
await assert.rejects(() => new GetAllRunsUseCase().execute({
query: {
include: { effectiveQcFlags: true },
},
}), new BadParameterError('If including QC flags, one and exactly one'
+ 'of `dataPassId`, `simulationPassId` or `lhcPeriodId` is required'));
}

{ // Data Passes
const { runs } = await new GetAllRunsUseCase().execute({
query: {
filter: {
dataPassIds: [1],
},
include: { effectiveQcFlags: true },
},
});
expect(runs).to.have.lengthOf(3);
expect(runs.find(({ runNumber }) => runNumber === 107).qcFlags['1'].map(({ id }) => id)).to.have.all.members([202, 201]);
expect(runs.find(({ runNumber }) => runNumber === 107).qcFlags['2'].map(({ id }) => id)).to.have.all.members([203]);
expect(runs.find(({ runNumber }) => runNumber === 106).qcFlags['1'].map(({ id }) => id)).to.have.all.members([3, 2, 1]);
}

{ // Simulation Passes
const { runs } = await new GetAllRunsUseCase().execute({
query: {
filter: {
simulationPassIds: [1],
},
include: { effectiveQcFlags: true },
},
});
expect(runs).to.have.lengthOf(3);
expect(runs.find(({ runNumber }) => runNumber === 106).qcFlags['1'].map(({ id }) => id)).to.have.all.members([6, 5]);
}

{ // Synchronous flags
const { runs } = await new GetAllRunsUseCase().execute({
query: {
filter: {
lhcPeriodIds: [1],
},
include: { effectiveQcFlags: true },
},
});
expect(runs).to.have.lengthOf(4);
expect(runs.find(({ runNumber }) => runNumber === 56).qcFlags['7'].map(({ id }) => id)).to.have.all.members([101, 100]);
expect(runs.find(({ runNumber }) => runNumber === 56).qcFlags['4'].map(({ id }) => id)).to.have.all.members([102]);
}
});
};