Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
057c235
[O2B-1502] Filter model setup boilerplate code.
Houwie7000 Nov 26, 2025
a7a9eda
[O2B-1502] Add filter button on LHC-fills overview page.
Houwie7000 Nov 26, 2025
2648f1c
[O2B-1502] Added stable beams only to filter
Houwie7000 Nov 26, 2025
094e6c5
[O2B-1502] Filtering with Stable Beams Only works, radioButton elemen…
Houwie7000 Nov 26, 2025
da7ff37
[O2B-1502] Doc fixes
Houwie7000 Nov 26, 2025
ee72512
[O2B-1502] Increase timeout of detailsForSimulationPass test. Local m…
Houwie7000 Nov 26, 2025
96a04c0
[O2B-1502] Potential fix for test failure.
Houwie7000 Nov 28, 2025
6fa4034
Revert "[O2B-1502] Potential fix for test failure."
Houwie7000 Dec 1, 2025
17ea048
Merge branch 'main' into feature/O2B-1502/filtering-panel-lhc-fills-f…
graduta Dec 5, 2025
695622b
[O2B-1502] Processed feedback
Houwie7000 Dec 8, 2025
87bee89
[O2B-1502] Git failed to detect rename. Ran: git mv RadioButton.js ra…
Houwie7000 Dec 8, 2025
5a18d9e
[O2B-1502] Added test import
Houwie7000 Dec 8, 2025
4c530ef
Revert "[O2B-1502] Processed feedback"
Houwie7000 Dec 10, 2025
51b50d9
[O2B-1502] Cherry pick previous feedback changes
Houwie7000 Dec 10, 2025
c0c8559
[O2B-1502] Integrated stable beam only filter into filtermodel.
Houwie7000 Dec 10, 2025
9934e56
[O2B-1502] fixed stable beam default value
Houwie7000 Dec 10, 2025
f247a6f
[O2B-1502] Fixed logic and type
Houwie7000 Dec 11, 2025
9b67281
[O2B-1502] Don't set any defaults in the filter as it will conflict w…
Houwie7000 Dec 11, 2025
ea0880f
[O2B-1502] Code cleanup
Houwie7000 Dec 11, 2025
46d4ae8
[O2B-1502] minor changes, processed feedback
Houwie7000 Dec 15, 2025
91e350c
[O2B-1502] Removed duplicate function due to override
Houwie7000 Dec 15, 2025
e95c847
[O2B-1503] Added front end fill number filter
Houwie7000 Nov 27, 2025
5077fec
[O2B-1503] fillNumbers work, todo ranges
Houwie7000 Nov 28, 2025
9130c87
[O2B-1503] ranges accepted by fill numbers filter
Houwie7000 Nov 28, 2025
0d0986e
[O2B-1503] Added/fixed test lhc-fill overview
Houwie7000 Nov 28, 2025
804cd4c
[O2B-1503] doc change
Houwie7000 Nov 28, 2025
2f5932a
[O2B-1503] JSDoc enhancements. Extracted duplicate functions to utils…
Houwie7000 Dec 15, 2025
1e3f503
[O2B-1503] placeholder text changed
Houwie7000 Dec 15, 2025
de7d95b
[O2B-1505] Added beam duration filter to frontend
Houwie7000 Nov 28, 2025
cafcba1
[O2B-1505] added simple UI test
Houwie7000 Nov 28, 2025
4c28a84
[O2B-1505] Filter+DTO work
Houwie7000 Dec 1, 2025
a34340e
[O2B-1505] Beam duration filter works, TODO testing
Houwie7000 Dec 1, 2025
9921652
[O2B-1505] tests added/improv
Houwie7000 Dec 2, 2025
1c2fffb
[O2B-1505] Fixed tests
Houwie7000 Dec 2, 2025
d40bd5c
[O2B-1505] Cleanup, remove logs, docs
Houwie7000 Dec 2, 2025
d3c1ead
[O2B-1505] Fixed test
Houwie7000 Dec 8, 2025
fe5f6c7
[O2B-1505] Doc fixes
Houwie7000 Dec 8, 2025
7628bca
[O2B-1505] remove getStableBeamsOnly
Houwie7000 Dec 8, 2025
a533088
[O2B-1505] Fixed 00:00:00 bug, added test
Houwie7000 Dec 16, 2025
fd055ca
Merge branch 'main' into feature/O2B-1503/lhcfills-fill-numbers-filter
Houwie7000 Dec 17, 2025
6a47048
[O2B-1505] Processed feedback
Houwie7000 Dec 17, 2025
8e82b34
[O2B-1503] Processed feedback, added tests
Houwie7000 Dec 18, 2025
e5bbc05
[O2B-1503] Added test for splitStringToStringsTrimmed()
Houwie7000 Dec 18, 2025
668f90e
Merge branch 'feature/O2B-1503/lhcfills-fill-numbers-filter' into fea…
Houwie7000 Dec 18, 2025
a047600
Merge branch 'main' into feature/O2B-1505/lhcfills-beam-duration-filter
Houwie7000 Dec 18, 2025
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
7 changes: 7 additions & 0 deletions lib/domain/dtos/filters/LhcFillsFilterDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@
*/
const Joi = require('joi');
const { validateRange } = require('../../../utilities/rangeUtils');
const { validateTime } = require('../../../utilities/validateTime');

exports.LhcFillsFilterDto = Joi.object({
hasStableBeams: Joi.boolean(),
fillNumbers: Joi.string().trim().custom(validateRange).messages({
'any.invalid': '{{#message}}',
}),
beamDuration: {
limit: Joi.string().trim().min(8).max(8).custom(validateTime).messages({
'any.invalid': '{{#message}}',
}),
operator: Joi.string().trim().min(1).max(2),
},
});
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 beam duration
*
* @param {TextComparisonFilterModel} beamDurationFilterModel beamDurationFilterModel
* @returns {Component} the text field
*/
export const beamDurationFilter = (beamDurationFilterModel) => {
const amountFilter = rawTextFilter(
beamDurationFilterModel.operandInputModel,
{ classes: ['w-100', 'beam-duration-filter'], placeholder: 'e.g 16:14:15 (HH:MM:SS)' },
);

return comparisonOperatorFilter(amountFilter, beamDurationFilterModel.operatorSelectionModel.value, (value) =>
beamDurationFilterModel.operatorSelectionModel.select(value));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. 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-o2.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 { ComparisonSelectionModel } from './ComparisonSelectionModel.js';
import { FilterModel } from '../FilterModel.js';
import { RawTextFilterModel } from './RawTextFilterModel.js';

/**
* TextComparisonFilterModel
*/
export class TextComparisonFilterModel extends FilterModel {
/**
* Constructor
*/
constructor() {
super();

this._operatorSelectionModel = new ComparisonSelectionModel();
this._operatorSelectionModel.visualChange$.bubbleTo(this._visualChange$);

this._operandInputModel = new RawTextFilterModel();
this._operandInputModel.visualChange$.bubbleTo(this._visualChange$);
this._operandInputModel.bubbleTo(this);

this._operatorSelectionModel.observe(() => this._operandInputModel.value ? this.notify() : this._visualChange$.notify());
}

/**
* Return raw text filter model
*
* @return {RawTextFilterModel} operand input model
*/
get operandInputModel() {
return this._operandInputModel;
}

/**
* Get operator selection model
*
* @return {ComparisonSelectionModel} selection model
*/
get operatorSelectionModel() {
return this._operatorSelectionModel;
}

/**
* @inheritDoc
*/
reset() {
this._operandInputModel.reset();
this._operatorSelectionModel.reset();
}

/**
* @inheritDoc
*/
get normalized() {
return {
operator: this._operatorSelectionModel.current,
limit: this._operandInputModel.value,
};
}

/**
* @inheritDoc
*/
get isEmpty() {
return !this._operandInputModel.value;
}
}
2 changes: 1 addition & 1 deletion lib/public/views/Home/Overview/HomePageModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class HomePageModel extends Observable {
this._logsOverviewModel = new LogsOverviewModel(model, true);
this._logsOverviewModel.bubbleTo(this);

this._lhcFillsOverviewModel = new LhcFillsOverviewModel(true);
this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model, true);
this._lhcFillsOverviewModel.bubbleTo(this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { formatBeamType } from '../../../utilities/formatting/formatBeamType.js'
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';

/**
* List of active columns for a lhc fills table
Expand Down Expand Up @@ -108,6 +109,7 @@ export const lhcFillsActiveColumns = {

return '-';
},
filter: (lhcFillModel) => beamDurationFilter(lhcFillModel.filteringModel.get('beamDuration')),
profiles: {
lhcFill: true,
environment: true,
Expand Down
2 changes: 1 addition & 1 deletion lib/public/views/LhcFills/LhcFills.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class LhcFills extends Observable {
this.model = model;

// Sub-models
this._overviewModel = new LhcFillsOverviewModel(true);
this._overviewModel = new LhcFillsOverviewModel(model, true);
this._overviewModel.bubbleTo(this);

this._detailsModel = new LhcFillDetailsModel();
Expand Down
27 changes: 23 additions & 4 deletions lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilte
import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js';
import { OverviewPageModel } from '../../../models/OverviewModel.js';
import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js';
import { debounce } from '../../../utilities/debounce.js';
import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js';

const defaultBeamDurationOperator = '=';

/**
* Model for the LHC fills overview page
Expand All @@ -27,16 +31,20 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
/**
* Constructor
*
* @param {model} model global model
* @param {boolean} [stableBeamsOnly=false] if true, overview will load stable beam only
*/
constructor(stableBeamsOnly = false) {
constructor(model, stableBeamsOnly = false) {
super();

this._filteringModel = new FilteringModel({
fillNumbers: new RawTextFilterModel(),
beamDuration: new TextComparisonFilterModel(),
hasStableBeams: new StableBeamFilterModel(),
});

this._beamDurationOperator = defaultBeamDurationOperator;

this._filteringModel.observe(() => this._applyFilters(true));
this._filteringModel.visualChange$.bubbleTo(this);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding debounce here is good practice.


Expand All @@ -45,6 +53,12 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
if (stableBeamsOnly) {
this._filteringModel.get('hasStableBeams').setStableBeamsOnly(true);
}

const updateDebounceTime = () => {
this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime);
};
model.appConfiguration$.observe(() => updateDebounceTime());
updateDebounceTime();
}

/**
Expand All @@ -61,7 +75,10 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
* @inheritDoc
*/
getRootEndpoint() {
return buildUrl('/api/lhcFills', { filter: this.filteringModel.normalized });
const params = {
filter: this.filteringModel.normalized,
};
return buildUrl('/api/lhcFills', params);
}

/**
Expand All @@ -81,6 +98,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
*/
resetFiltering(fetch = true) {
this._filteringModel.reset();
this._beamDurationOperator = defaultBeamDurationOperator;

if (fetch) {
this._applyFilters(true);
Expand All @@ -106,11 +124,12 @@ export class LhcFillsOverviewModel extends OverviewPageModel {

/**
* Apply the current filtering and update the remote data list
* @param {boolean} now if true, filtering will be applied now without debouncing
*
* @return {void}
*/
_applyFilters() {
_applyFilters(now = false) {
this._pagination.currentPage = 1;
this.load();
now ? this.load() : this._debouncedLoad(true);
}
}
7 changes: 6 additions & 1 deletion lib/usecases/lhcFill/GetAllLhcFillsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class GetAllLhcFillsUseCase {
const queryBuilder = new QueryBuilder();

if (filter) {
const { hasStableBeams, fillNumbers } = filter;
const { hasStableBeams, fillNumbers, beamDuration } = 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 +62,11 @@ class GetAllLhcFillsUseCase {
: queryBuilder.where('fillNumber').oneOf(...finalFillnumberList);
}
}
// Beam duration filter, limit and corresponding operator.
if (beamDuration?.limit !== undefined && beamDuration?.operator) {
const beamDurationLimit = Number(beamDuration.limit) === 0 ? null : beamDuration.limit;
queryBuilder.where('stableBeamsDuration').applyOperator(beamDuration.operator, beamDurationLimit);
}
}

const { count, rows } = await TransactionHelper.provide(async () => {
Expand Down
26 changes: 26 additions & 0 deletions lib/utilities/validateTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Joi from 'joi';

/**
* Validates digital time in string format
*
* @param {*} incomingValue The time to validate
* @param {*} helpers The helpers object
* @returns {number|import("joi").ValidationError} The value if validation passes, as seconds (Number)
*/
export const validateTime = (incomingValue, helpers) => {
// Checks for valid time format.
const { error, value } = Joi.string().pattern(/^\d{2}:[0-5]\d:[0-5]\d$/).validate(incomingValue);

if (error !== undefined) {
return helpers.error('any.invalid', { message: `Validation error: ${error?.message ?? 'failed to validate time'}` });
}

// Extract time to seconds...
const [hoursStr, minutesStr, secondsStr] = value.split(':');

const hours = Number(hoursStr);
const minutes = Number(minutesStr);
const seconds = Number(secondsStr);

return hours * 3600 + minutes * 60 + seconds;
};
60 changes: 60 additions & 0 deletions test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,64 @@ module.exports = () => {
expect(lhcFill.fillNumber).oneOf([6,3])
});
})

// 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);
expect(lhcFills).to.be.an('array').and.lengthOf(3)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.stableBeamsDuration).lessThan(43200)
});
});

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)
expect(lhcFills).to.be.an('array').and.lengthOf(4)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.stableBeamsDuration).lessThanOrEqual(43200)
});
})

it('should only contain specified stable beam durations, = 00:01:40', async () => {
getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '='} } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)
expect(lhcFills).to.be.an('array').and.lengthOf(3)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.stableBeamsDuration).equal(100)
});
});

it('should only contain specified stable beam durations, >= 00:01:40', async () => {
getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '>='} } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(4)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.stableBeamsDuration).greaterThanOrEqual(100)
});
})

it('should only contain specified stable beam durations, > 00:01:40', async () => {
getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '>'} } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

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

it('should only contain specified stable beam durations, = 00:00:00', async () => {
// Tests the usecase's ability to replace the request for 0 to a request for null.
getAllLhcFillsDto.query = { filter: { hasStableBeams: true, beamDuration: {limit: '0', operator: '='} } };
const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto)

expect(lhcFills).to.be.an('array').and.lengthOf(1)
lhcFills.forEach((lhcFill) => {
expect(lhcFill.stableBeamsDuration).equals(null)
});
})
};
7 changes: 7 additions & 0 deletions test/public/lhcFills/overview.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {
waitForTableLength,
expectLink,
openFilteringPanel,
expectAttributeValue,
} = require('../defaults.js');
const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js');

Expand Down Expand Up @@ -268,11 +269,17 @@ module.exports = () => {
it('should successfully display filter elements', async () => {
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)'}


await goToPage(page, 'lhc-fill-overview');
// Open the filtering panel
await openFilteringPanel(page);
await expectInnerText(page, filterSBExpect.selector, filterSBExpect.value);
await expectInnerText(page, filterFillNRExpect.selector, filterFillNRExpect.value);
await expectInnerText(page, filterSBDurationExpect.selector, filterSBDurationExpect.value);
await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value);
});

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