Skip to content

Commit bb254ed

Browse files
authored
[O2B-1601] Add feature to allow users to filter runs by beam type (#2200)
* Extract BeamTypeDto in reusable component * Add filtering of GetAllRuns by beam type * Add front-end widget in run-overview to filter by beam type
1 parent 1758c68 commit bb254ed

8 files changed

Lines changed: 108 additions & 8 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
const Joi = require('joi');
15+
const { validateBeamTypes, BEAM_TYPE_INVALID } = require('../../../utilities/beamTypeUtils');
16+
17+
exports.BeamTypesDto = Joi.string()
18+
.trim()
19+
.custom(validateBeamTypes)
20+
.messages({
21+
[BEAM_TYPE_INVALID]: '{{#message}}',
22+
'string.base': 'Beam type must be a string',
23+
});

lib/domain/dtos/filters/LhcFillsFilterDto.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
const Joi = require('joi');
1414
const { validateRange, RANGE_INVALID } = require('../../../utilities/rangeUtils');
15-
const { validateBeamTypes, BEAM_TYPE_INVALID } = require('../../../utilities/beamTypeUtils');
15+
const { BeamTypesDto } = require('../common/BeamTypeDto.js');
1616
const { validateTimeDuration } = require('../../../utilities/validateTime');
1717
const { FromToFilterDto } = require('./FromToFilterDto.js');
1818

@@ -27,11 +27,5 @@ exports.LhcFillsFilterDto = Joi.object({
2727
stableBeamsStart: FromToFilterDto,
2828
stableBeamsEnd: FromToFilterDto,
2929
schemeName: Joi.string().trim().max(64),
30-
beamTypes: Joi.string()
31-
.trim()
32-
.custom(validateBeamTypes)
33-
.messages({
34-
[BEAM_TYPE_INVALID]: '{{#message}}',
35-
'string.base': 'Beam type must be a string',
36-
}),
30+
beamTypes: BeamTypesDto,
3731
});

lib/domain/dtos/filters/RunFilterDto.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
const Joi = require('joi');
1414
const { CustomJoi } = require('../CustomJoi.js');
15+
const { BeamTypesDto } = require('../common/BeamTypeDto.js');
1516
const { FromToFilterDto } = require('./FromToFilterDto.js');
1617
const { RUN_QUALITIES } = require('../../enums/RunQualities.js');
1718
const { IntegerComparisonDto, FloatComparisonDto } = require('./NumericalComparisonDto.js');
@@ -39,6 +40,7 @@ exports.RunFilterDto = Joi.object({
3940
'string.pattern.base':
4041
'Beam modes "{{#value}}" must contain only uppercase letters and single spaces between words.',
4142
})),
43+
beamTypes: BeamTypesDto,
4244
runNumbers: Joi.string().trim().custom(validateRange).messages({
4345
[RANGE_INVALID]: '{{#message}}',
4446
'string.base': 'Run numbers must be comma-separated numbers or ranges (e.g. 12,15-18)',

lib/public/views/Runs/ActiveColumns/runsActiveColumns.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { numericalComparisonFilter } from '../../../components/Filters/common/fi
4444
import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js';
4545
import radioButtonFilter from '../../../components/Filters/common/filters/radioButtonFilter.js';
4646
import { textInputFilter } from '../../../components/Filters/common/filters/textInputFilter.js';
47+
import { beamTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamTypeFilter.js';
4748

4849
/**
4950
* List of active columns for a generic runs table
@@ -588,6 +589,11 @@ export const runsActiveColumns = {
588589
name: 'PDP Beam Type',
589590
visible: false,
590591
},
592+
beamType: {
593+
name: 'Beam Type',
594+
visible: false,
595+
filter: (runsOverviewModel) => beamTypeFilter(runsOverviewModel.filteringModel.get('beamTypes')),
596+
},
591597
readoutCfgUri: {
592598
name: 'Readout Config URI',
593599
visible: false,

lib/public/views/Runs/Overview/RunsOverviewModel.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { beamModesProvider } from '../../../services/beamModes/beamModesProvider
3535
import { RadioButtonFilterModel } from '../../../components/Filters/common/RadioButtonFilterModel.js';
3636
import { SelectionModel } from '../../../components/common/selection/SelectionModel.js';
3737
import { TRIGGER_VALUES } from '../../../domain/enums/TriggerValue.js';
38+
import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js';
3839

3940
/**
4041
* Model representing handlers for runs page
@@ -95,6 +96,7 @@ export class RunsOverviewModel extends FilterableOverviewPageModel {
9596
dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]),
9697
epn: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]),
9798
triggerValues: new SelectionModel({ availableOptions: TRIGGER_VALUES.map((value) => ({ label: value, value })) }),
99+
beamTypes: new BeamTypeFilterModel(),
98100
},
99101
);
100102

lib/usecases/run/GetAllRunsUseCase.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class GetAllRunsUseCase {
8484
gaq,
8585
detectorsQcNotBadFraction,
8686
beamModes,
87+
beamTypes,
8788
} = filter;
8889

8990
if (runNumbers) {
@@ -119,6 +120,15 @@ class GetAllRunsUseCase {
119120
filteringQueryBuilder.where('lhcBeamMode').oneOf(...beamModes);
120121
}
121122

123+
if (beamTypes) {
124+
const beamTypesList = splitStringToStringsTrimmed(beamTypes, SEARCH_ITEMS_SEPARATOR);
125+
filteringQueryBuilder.include({
126+
association: 'lhcFill',
127+
where: { beamType: { [Op.in]: beamTypesList } },
128+
required: true,
129+
});
130+
}
131+
122132
if (eorReason) {
123133
const eorReasonTypeWhere = {};
124134
if (eorReason.category) {

test/api/runs.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,42 @@ module.exports = () => {
160160
expect(runs).to.lengthOf(6);
161161
});
162162

163+
it('should successfully filter with single beamType', async () => {
164+
const beamType = 'p-p';
165+
const response = await request(server).get(`/api/runs?filter[beamTypes]=${beamType}`);
166+
167+
expect(response.status).to.equal(200);
168+
const { data: runs } = response.body;
169+
170+
expect(runs).to.be.an('array');
171+
expect(runs).to.have.lengthOf.greaterThan(0);
172+
expect(runs.every(({ lhcFill }) => lhcFill?.beamType === beamType)).to.be.true;
173+
});
174+
175+
it('should successfully filter with multiple beamTypes', async () => {
176+
const beamTypes = ['p-p', 'Pb-Pb'];
177+
const response = await request(server).get(`/api/runs?filter[beamTypes]=${beamTypes.join(',')}`);
178+
179+
expect(response.status).to.equal(200);
180+
const { data: runs } = response.body;
181+
182+
expect(runs).to.be.an('array');
183+
expect(runs).to.have.lengthOf.greaterThan(0);
184+
expect(runs.every(({ lhcFill }) => beamTypes.includes(lhcFill?.beamType))).to.be.true;
185+
});
186+
187+
it('should return 400 if beamTypes filter has the incorrect format', async () => {
188+
const beamTypeString = 'DOES NOT EXIST';
189+
const response = await request(server).get(`/api/runs?filter[beamTypes]=${beamTypeString}`);
190+
191+
expect(response.status).to.equal(400);
192+
193+
const { errors: [error] } = response.body;
194+
195+
expect(error.title).to.equal('Invalid Attribute');
196+
expect(error.detail).to.equal(`Invalid beam type format: ${beamTypeString}`);
197+
});
198+
163199
it('should return 400 if beamModes filter has the incorrect format', async () => {
164200
const beamModeString = '*THERE\'S NON LETTERS IN HERE';
165201
const response = await request(server).get(`/api/runs?filter[beamModes]=${beamModeString}`);

test/lib/usecases/run/GetAllRunsUseCase.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,33 @@ module.exports = () => {
209209
}
210210
});
211211

212+
it('should successfully filter on beamTypes', async () => {
213+
const singleBeamType = 'p-p';
214+
const multipleBeamTypes = 'p-p,Pb-Pb';
215+
const nonExistentBeamType = 'DOES-NOT-EXIST';
216+
217+
getAllRunsDto.query = { filter: { beamTypes: singleBeamType }, page: { limit: 200 } };
218+
{
219+
const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto);
220+
expect(runs).to.have.lengthOf.greaterThan(0);
221+
expect(runs.every(({ lhcFill }) => lhcFill?.beamType === singleBeamType)).to.be.true;
222+
}
223+
224+
getAllRunsDto.query = { filter: { beamTypes: multipleBeamTypes }, page: { limit: 200 } };
225+
{
226+
const acceptedBeamTypes = multipleBeamTypes.split(',');
227+
const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto);
228+
expect(runs).to.have.lengthOf.greaterThan(0);
229+
expect(runs.every(({ lhcFill }) => acceptedBeamTypes.includes(lhcFill?.beamType))).to.be.true;
230+
}
231+
232+
getAllRunsDto.query = { filter: { beamTypes: nonExistentBeamType } };
233+
{
234+
const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto);
235+
expect(runs).to.have.lengthOf(0);
236+
}
237+
});
238+
212239
it('should successfully filter on run definition', async () => {
213240
const PHYSICS_COUNT = 7;
214241
const COSMICS_COUNT = 2;

0 commit comments

Comments
 (0)