Skip to content
1 change: 1 addition & 0 deletions lib/domain/dtos/filters/LhcFillsFilterDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ exports.LhcFillsFilterDto = Joi.object({
fillNumbers: Joi.string().trim().custom(validateRange).messages({
'any.invalid': '{{#message}}',
}),
runDuration: validateTimeDuration,
beamDuration: validateTimeDuration,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE Trg. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-Trg.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

import { comparisonOperatorFilter } from '../common/filters/comparisonOperatorFilter.js';
import { rawTextFilter } from '../common/filters/rawTextFilter.js';

/**
* Component to filter LHC-fills by run duration
*
* @param {TextComparisonFilterModel} runDurationFilterModel runDurationFilterModel
* @returns {Component} the text field
*/
export const runDurationFilter = (runDurationFilterModel) => {
const amountFilter = rawTextFilter(
runDurationFilterModel.operandInputModel,
{ id: 'run-duration-filter-operand', classes: ['w-100', 'run-duration-filter'], placeholder: 'e.g 16:14:15 (HH:MM:SS)' },
);

return comparisonOperatorFilter(amountFilter, runDurationFilterModel.operatorSelectionModel.current, (value) =>
runDurationFilterModel.operatorSelectionModel.select(value), { id: 'run-duration-filter-operator' });
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { frontLink } from '../../../components/common/navigation/frontLink.js';
import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFillsFilter/stableBeamFilter.js';
import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js';
import { beamDurationFilter } from '../../../components/Filters/LhcFillsFilter/beamDurationFilter.js';
import { runDurationFilter } from '../../../components/Filters/LhcFillsFilter/runDurationFilter.js';

/**
* List of active columns for a lhc fills table
Expand Down Expand Up @@ -141,6 +142,7 @@ export const lhcFillsActiveColumns = {
visible: true,
size: 'w-8',
format: (duration) => formatDuration(duration),
filter: (lhcFillModel) => runDurationFilter(lhcFillModel.filteringModel.get('runDuration')),
},
efficiency: {
name: 'Fill Efficiency',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
this._filteringModel = new FilteringModel({
fillNumbers: new RawTextFilterModel(),
beamDuration: new TextComparisonFilterModel(),
runDuration: new TextComparisonFilterModel(),
hasStableBeams: new StableBeamFilterModel(),
});

Expand Down
18 changes: 17 additions & 1 deletion lib/usecases/lhcFill/GetAllLhcFillsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ class GetAllLhcFillsUseCase {

const queryBuilder = new QueryBuilder();

let associatedStatisticsRequired = false;

if (filter) {
const { hasStableBeams, fillNumbers, beamDuration } = filter;
const { hasStableBeams, fillNumbers, beamDuration, runDuration } = filter;
if (hasStableBeams) {
// For now, if a stableBeamsStart is present, then a beam is stable
queryBuilder.where('stableBeamsStart').not().is(null);
Expand All @@ -62,6 +64,15 @@ class GetAllLhcFillsUseCase {
: queryBuilder.where('fillNumber').oneOf(...finalFillnumberList);
}
}

// Run duration filter and corresponding operator.
if (runDuration?.limit !== undefined && runDuration?.operator) {
associatedStatisticsRequired = true;
// 00:00:00 aka 0 value is saved in the DB as null
const runDurationLimit = Number(runDuration.limit) === 0 ? null : runDuration.limit;
queryBuilder.whereAssociation('statistics', 'runsCoverage').applyOperator(runDuration.operator, runDurationLimit);
}

// Beam duration filter, limit and corresponding operator.
if (beamDuration?.limit !== undefined && beamDuration?.operator) {
const beamDurationLimit = Number(beamDuration.limit) === 0 ? null : beamDuration.limit;
Expand All @@ -75,6 +86,11 @@ class GetAllLhcFillsUseCase {
where: { definition: RunDefinition.PHYSICS },
required: false,
});
queryBuilder.include({
association: 'statistics',
required: associatedStatisticsRequired,
});

queryBuilder.orderBy('fillNumber', 'desc');
queryBuilder.limit(limit);
queryBuilder.offset(offset);
Expand Down
62 changes: 61 additions & 1 deletion test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ module.exports = () => {
})

// Beam duration filter tests

it('should only contain specified stable beam durations, < 12:00:00', async () => {
getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '43200', operator: '<'} } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto);
Expand Down Expand Up @@ -160,4 +159,65 @@ module.exports = () => {
expect(lhcFill.stableBeamsDuration).equals(null)
});
})

it('should only contain specified total run duration, > 04:00:00', async () => {
getAllLhcFillsDto.query = { filter: { runDuration: '14400', runDurationOperator: '>' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).greaterThan(14400)
});
})

it('should only contain specified total run duration, >= 05:00:00', async () => {
getAllLhcFillsDto.query = { filter: { runDuration: '18000', runDurationOperator: '>=' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).greaterThan(18000)
});
})

it('should only contain specified total run duration, = 05:00:00', async () => {
getAllLhcFillsDto.query = { filter: { runDuration: '18000', runDurationOperator: '=' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).greaterThan(18000)
});
})

it('should only contain specified total run duration, = 00:00:00', async () => {
// Tests the usecase's ability to replace the request for 0 to a request for null.
getAllLhcFillsDto.query = { filter: { runDuration: 0, runDurationOperator: '=' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(4)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).equals(0)
});
})

it('should only contain specified total run duration, <= 05:00:00', async () => {
getAllLhcFillsDto.query = { filter: { runDuration: '18000', runDurationOperator: '<=' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).greaterThan(18000)
});
})

it('should only contain specified total run duration, < 06:30:59', async () => {
getAllLhcFillsDto.query = { filter: { runDuration: '23459', runDurationOperator: '<' } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.statistics.runsCoverage).greaterThan(23459)
});
})
};
6 changes: 5 additions & 1 deletion test/public/lhcFills/overview.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ module.exports = () => {
const filterSBExpect = { selector: '.stableBeams-filter .w-30', value: 'Stable Beams Only' };
const filterFillNRExpect = {selector: 'div.items-baseline:nth-child(1) > div:nth-child(1)', value: 'Fill #'};
const filterSBDurationExpect = {selector: 'div.items-baseline:nth-child(3) > div:nth-child(1)', value: 'SB Duration'};
const filterSBDurationPlaceholderExpect = {selector: 'input.w-100:nth-child(2)', value: 'e.g 16:14:15 (HH:MM:SS)'};
const filterSBDurationPlaceholderExpect = {selector: '.beam-duration-filter', value: 'e.g 16:14:15 (HH:MM:SS)'}
const filterRunDurationExpect = {selector: 'div.flex-row:nth-child(4) > div:nth-child(1)', value: 'Total runs duration'}
const filterRunDurationPlaceholderExpect = {selector: '.run-duration-filter', value: 'e.g 16:14:15 (HH:MM:SS)'};
const filterSBDurationOperatorExpect = { value: true };


Expand All @@ -283,6 +285,8 @@ module.exports = () => {
await expectInnerText(page, filterFillNRExpect.selector, filterFillNRExpect.value);
await expectInnerText(page, filterSBDurationExpect.selector, filterSBDurationExpect.value);
await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value);
await expectInnerText(page, filterRunDurationExpect.selector, filterRunDurationExpect.value);
await expectAttributeValue(page, filterRunDurationPlaceholderExpect.selector, 'placeholder', filterRunDurationPlaceholderExpect.value);
});

it('should successfully un-apply Stable Beam filter menu', async () => {
Expand Down
Loading