⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 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
36bdd8d
[O2B-1506] Frontend run duration filter code
Houwie7000 Dec 2, 2025
b7fe810
[O2B-1506] Run duration filter tests + backend
Houwie7000 Dec 4, 2025
0751b57
[O2B-1506] Fixed GehAllLhcFillsUseCase run duration tests
Houwie7000 Dec 8, 2025
6f77e0c
[O2B-1506] Fixed 00:00:00 filter, added test to cover condition
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
7ce2103
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 5, 2026
f113473
[O2B-1505] Remove old code
Houwie7000 Jan 5, 2026
16246a0
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 5, 2026
d75c03e
[O2-1506] Process feedback from upstream
Houwie7000 Jan 5, 2026
bd1b836
[O2B-1505] Processed feedback, timeValidator JOI updated, debounce re…
Houwie7000 Jan 20, 2026
9e3bc30
Merge branch 'main' into feature/O2B-1505/lhcfills-beam-duration-filter
Houwie7000 Jan 20, 2026
3d2f665
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 20, 2026
a04fac2
[O2B-1506] Improvements from upstream integrated
Houwie7000 Jan 20, 2026
ff540af
[O2B-1505] Fixed validateTime error handling, removed unused code, TO…
Houwie7000 Jan 20, 2026
9e4e68c
[O2B-1505] processed feedback, empty id, code deduplication, bad doc
Houwie7000 Jan 21, 2026
0af9bdf
[O2B-1505] API + UI tests added
Houwie7000 Jan 21, 2026
45acea2
[O2B-1505] disable 00:00:00 conversion to null for DB
Houwie7000 Jan 21, 2026
fd057c2
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 21, 2026
4a4ed6d
[O2B-1505] typo removed
Houwie7000 Jan 21, 2026
ce6fb68
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 21, 2026
eca4ffa
[1506] WIP testing, usecase fixing
Houwie7000 Jan 21, 2026
133aa01
[O2B-1505] Removed outdated 00:00:00 usecase test
Houwie7000 Jan 21, 2026
191fd52
[O2B-1505] amountFilter -> durationFilter
Houwie7000 Jan 21, 2026
754f669
Merge branch 'feature/O2B-1505/lhcfills-beam-duration-filter' into fe…
Houwie7000 Jan 21, 2026
15e6a39
[1506] Changed the handling of >= 00:00:00 for the runDuration on lhc…
Houwie7000 Jan 21, 2026
78a2d12
Merge branch 'main' into feature/O2B-1506/lhcfills-run-duration-filter
Houwie7000 Jan 22, 2026
1bdf8d0
[O2B-1506] fix outdated usecase test
Houwie7000 Jan 22, 2026
637ad11
[O2B-1506] Consolidated beam and run duration filter elements
Houwie7000 Jan 22, 2026
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
32 changes: 25 additions & 7 deletions lib/database/utilities/QueryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
/**
* Sets an exact match filter using the provided value.
*
* @param {*} value The required value.

Check warning on line 36 in lib/database/utilities/QueryBuilder.js

View workflow job for this annotation

GitHub Actions / linter

Prefer a more specific type to `*`
* @returns {QueryBuilder} The current QueryBuilder instance.
*/
is(value) {
Expand All @@ -54,7 +54,7 @@
* or
* * if value is null
*
* @param {*} value The required value.

Check warning on line 57 in lib/database/utilities/QueryBuilder.js

View workflow job for this annotation

GitHub Actions / linter

Prefer a more specific type to `*`
* @returns {QueryBuilder} The current QueryBuilder instance.
*/
isOrNull(value) {
Expand All @@ -72,7 +72,7 @@
/**
* Sets an **AND** match filter using the provided values.
*
* @param {...any} values The required values.

Check warning on line 75 in lib/database/utilities/QueryBuilder.js

View workflow job for this annotation

GitHub Actions / linter

Prefer a more specific type to `any`
* @returns {QueryBuilder} The current QueryBuilder instance.
*/
allOf(...values) {
Expand Down Expand Up @@ -360,18 +360,36 @@

/**
* Sets the operation.
* If an WhereAssociation is already set it will convert to an OR condition
*
* @param {Object} operation The Sequelize operation to use as where filter.
* @returns {QueryBuilder} The current QueryBuilder instance.
*/
_op(operation) {
this.queryBuilder.include({
association: this.association,
required: true,
where: {
[this.column]: operation,
},
});
// Check if this include association already exists
Copy link
Collaborator

@isaachilly isaachilly Jan 22, 2026

Choose a reason for hiding this comment

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

I am unsure at the moment whether this is the best solution to the be able to join 2 applyOperators. Maybe that is not solution at all and could modify apply operator that if object is passed i.e raw sequalize operator logic then you can pass in all in one go to the method instead of twice and this extra changes to be able to do twice.

const existingInclude = this.queryBuilder.options.include?.find((include) => include.association === this.association);

if (existingInclude && existingInclude.where) {
/*
* Replace existing where operation in the include with OR operation.
* This basically encapsulates the existing where operation in a OR operation together with the new operation.
*/
existingInclude.where = {
[Op.or]: [
{ [this.column]: existingInclude.where[this.column] },
{ [this.column]: operation },
],
};
} else {
// Create new include
this.queryBuilder.include({
association: this.association,
required: true,
where: {
[this.column]: operation,
},
});
}

return this.queryBuilder;
}
Expand Down
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
Expand Up @@ -15,17 +15,18 @@ import { comparisonOperatorFilter } from '../common/filters/comparisonOperatorFi
import { rawTextFilter } from '../common/filters/rawTextFilter.js';

/**
* Component to filter LHC-fills by beam duration
* Component to filter LHC-fills by duration
*
* @param {TextComparisonFilterModel} beamDurationFilterModel beamDurationFilterModel
* @param {TextComparisonFilterModel} durationFilterModel durationFilterModel
* @param {string} id id used for the operand and operator elements, becomes: `${id}-operator` OR `${id}-operand`.
* @returns {Component} the text field
*/
export const beamDurationFilter = (beamDurationFilterModel) => {
const durationFilter = rawTextFilter(
beamDurationFilterModel.operandInputModel,
{ id: 'beam-duration-filter-operand', classes: ['w-100', 'beam-duration-filter'], placeholder: 'e.g 16:14:15 (HH:MM:SS)' },
export const durationFilter = (durationFilterModel, id) => {
const amountFilter = rawTextFilter(
durationFilterModel.operandInputModel,
{ id: `${id}-operand`, classes: ['w-100'], placeholder: 'e.g 16:14:15 (HH:MM:SS)' },
);

return comparisonOperatorFilter(durationFilter, beamDurationFilterModel.operatorSelectionModel.current, (value) =>
beamDurationFilterModel.operatorSelectionModel.select(value), { id: 'beam-duration-filter-operator' });
return comparisonOperatorFilter(amountFilter, durationFilterModel.operatorSelectionModel.current, (value) =>
durationFilterModel.operatorSelectionModel.select(value), { id: `${id}-operator` });
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +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';
import { durationFilter } from '../../../components/Filters/LhcFillsFilter/durationFilter.js';

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

return '-';
},
filter: (lhcFillModel) => beamDurationFilter(lhcFillModel.filteringModel.get('beamDuration')),
filter: (lhcFillModel) => durationFilter(lhcFillModel.filteringModel.get('beamDuration'), 'beam-duration-filter'),
profiles: {
lhcFill: true,
environment: true,
Expand Down Expand Up @@ -141,6 +141,7 @@ export const lhcFillsActiveColumns = {
visible: true,
size: 'w-8',
format: (duration) => formatDuration(duration),
filter: (lhcFillModel) => durationFilter(lhcFillModel.filteringModel.get('runDuration'), 'run-duration-filter'),
},
efficiency: {
name: 'Fill Efficiency',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ 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) {
Expand All @@ -37,6 +36,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
this._filteringModel = new FilteringModel({
fillNumbers: new RawTextFilterModel(),
beamDuration: new TextComparisonFilterModel(),
runDuration: new TextComparisonFilterModel(),
hasStableBeams: new StableBeamFilterModel(),
});

Expand Down
25 changes: 24 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,22 @@ 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 (bookkeeping.fill_statistics.runs_coverage)
if ((runDuration.operator === '>=' || runDuration.operator === '<=') && Number(runDuration.limit) === 0) {
// Include 00:00:00 = 0 = null AND everything above 00:00:00 which is more or less than 0.
queryBuilder.whereAssociation('statistics', 'runsCoverage').applyOperator(runDuration.operator, 0);
queryBuilder.whereAssociation('statistics', 'runsCoverage').applyOperator('=', null);
} else if (Number(runDuration.limit) === 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Operators > and < are not handled here I think properly when limit is 0. They will get caught in the 1st else if and be compared with null which won't work with > and <. Need another condition statement if > or < and limit is 0 then apply operator on 0. And add the extra test cases.

queryBuilder.whereAssociation('statistics', 'runsCoverage').applyOperator(runDuration.operator, null);
} else {
queryBuilder.whereAssociation('statistics', 'runsCoverage').applyOperator(runDuration.operator, runDuration.limit);
}
}

// Beam duration filter, limit and corresponding operator.
if (beamDuration?.limit !== undefined && beamDuration?.operator) {
queryBuilder.where('stableBeamsDuration').applyOperator(beamDuration.operator, beamDuration.limit);
Expand All @@ -74,6 +92,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
218 changes: 218 additions & 0 deletions test/api/lhcFills.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,224 @@ module.exports = () => {
done();
});
});

it('should return 200 and an LHCFill array for runs duration filter, = 05:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=05:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(1);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});

it('should return 200 and an LHCFill array for runs duration filter, = 5:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=5:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(1);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});

it('should return 200 for runs duration filter, = 00:9:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=00:9:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(0);

done();
});
});

it('should return 200 for runs duration filter, = 00:00:9', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=00:00:9')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(0);

done();
});
});

it('should return 200 for runs duration filter, = 999999:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=999999:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(0);

done();
});
});


it('should return 200 for runs duration filter, = 999999:0:0', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=999999:0:0')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(0);

done();
});
});



it('should return 400 for wrong runs duration filter, = 44:60:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=44:60:00')
.expect(400)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.errors[0].title).to.equal('Invalid Attribute');

done();
});
});

it('should return 400 for wrong runs duration filter, = 44:00:60', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=44:00:60')
.expect(400)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.errors[0].title).to.equal('Invalid Attribute');

done();
});
});

it('should return 400 for wrong runs duration filter, = -44:30:15', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]==&filter[runDuration][limit]=-44:30:15')
.expect(400)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.errors[0].title).to.equal('Invalid Attribute');

done();
});
});


it('should return 200 and an LHCFill array for runs duration filter, < 6:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]=<&filter[runDuration][limit]=6:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(1);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});

it('should return 200 and an LHCFill array for runs duration filter, <= 5:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]=<=&filter[runDuration][limit]=5:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(1);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});

it('should return 200 and an LHCFill array for runs duration filter, >= 00:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]=>=&filter[runDuration][limit]=00:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(5);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});

it('should return 200 and an LHCFill array for runs duration filter, > 03:00:00', (done) => {
request(server)
.get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[runDuration][operator]=>&filter[runDuration][limit]=03:00:00')
.expect(200)
.end((err, res) => {
if (err) {
done(err);
return;
}

expect(res.body.data).to.have.lengthOf(1);
expect(res.body.data[0].fillNumber).to.equal(6);

done();
});
});
});
describe('POST /api/lhcFills', () => {
it('should return 201 if valid data is provided', async () => {
Expand Down
Loading
Loading