From 722caf7b6d6c78507e4111fb661c217a6f9b5e44 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 10:23:35 +0100 Subject: [PATCH 001/115] chore: remove redundant model assignment. --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index cce376438b..323fcb2473 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -36,8 +36,6 @@ export class LogsOverviewModel extends Observable { constructor(model, excludeAnonymous = false) { super(); - this.model = model; - // Sub-models this._listingTagsFilterModel = new TagFilterModel(tagsProvider.items$); this._listingTagsFilterModel.observe(() => this._applyFilters()); From f161cae69844ffe9fb27239bb9193d68644705d0 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 10:37:43 +0100 Subject: [PATCH 002/115] chore: move all filters denoted as so to a filteringModel --- .../Filters/LogsFilter/author/authorFilter.js | 13 ++--- .../Logs/ActiveColumns/logsActiveColumns.js | 24 +++++++-- .../views/Logs/Overview/LogsOverviewModel.js | 53 ++++++++++--------- lib/public/views/Logs/Overview/index.js | 2 +- 4 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lib/public/components/Filters/LogsFilter/author/authorFilter.js b/lib/public/components/Filters/LogsFilter/author/authorFilter.js index d5fe5a7a45..778934ba23 100644 --- a/lib/public/components/Filters/LogsFilter/author/authorFilter.js +++ b/lib/public/components/Filters/LogsFilter/author/authorFilter.js @@ -55,11 +55,12 @@ export const excludeAnonymousLogAuthorToggle = (authorFilterModel) => switchInpu /** * Returns a authorFilter component with text input, reset button, and anonymous exclusion button. * - * @param {LogModel} logModel the log model object - * @returns {Component} the author filter component + * @param {LogsOverviewModel} logsOverviewModel the log overview model + * @param {FilteringModel} logsOverviewModel.filteringModel the runs overview model + * @return {Component} the filter component */ -export const authorFilter = ({ authorFilter }) => h('.flex-row.items-center.g3', [ - authorFilterTextInput(authorFilter), - resetAuthorFilterButton(authorFilter), - excludeAnonymousLogAuthorToggle(authorFilter), +export const authorFilter = ({ filteringModel }) => h('.flex-row.items-center.g3', [ + authorFilterTextInput(filteringModel.get('authorFilter')), + resetAuthorFilterButton(filteringModel.get('authorFilter')), + excludeAnonymousLogAuthorToggle(filteringModel.get('authorFilter')), ]); diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index c43b04b917..45d626777a 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -71,8 +71,16 @@ export const logsActiveColumns = { visible: true, sortable: true, size: 'w-30', - filter: ({ titleFilter }) => textFilter( - titleFilter, + + /** + * Title filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => textFilter( + filteringModel.get('titleFilter'), { id: 'titleFilterText', class: 'w-75 mt1', @@ -92,8 +100,16 @@ export const logsActiveColumns = { name: 'Content', visible: false, size: 'w-10', - filter: ({ contentFilter }) => textFilter( - contentFilter, + + /** + * Content filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => textFilter( + filteringModel.get('contentFilter'), { id: 'contentFilterText', class: 'w-75 mt1', diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 323fcb2473..afb61a725a 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -20,6 +20,7 @@ import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author import { PaginationModel } from '../../../components/Pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; import { tagsProvider } from '../../../services/tag/tagsProvider.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; /** * Model representing handlers for log entries page @@ -36,6 +37,12 @@ export class LogsOverviewModel extends Observable { constructor(model, excludeAnonymous = false) { super(); + this._filteringModel = new FilteringModel({ + authorFilter: new AuthorFilterModel(), + titleFilter: new FilterInputModel(), + contentFilter: new FilterInputModel(), + }); + // Sub-models this._listingTagsFilterModel = new TagFilterModel(tagsProvider.items$); this._listingTagsFilterModel.observe(() => this._applyFilters()); @@ -49,16 +56,6 @@ export class LogsOverviewModel extends Observable { this._pagination.observe(() => this.fetchLogs()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); - // Filtering models - this._authorFilter = new AuthorFilterModel(); - this._registerFilter(this._authorFilter); - - this._titleFilter = new FilterInputModel(); - this._registerFilter(this._titleFilter); - - this._contentFilter = new FilterInputModel(); - this._registerFilter(this._contentFilter); - this._logs = RemoteData.NotAsked(); const updateDebounceTime = () => { @@ -67,7 +64,7 @@ export class LogsOverviewModel extends Observable { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); - excludeAnonymous && this._authorFilter.update('!Anonymous'); + excludeAnonymous && this._filteringModel.get('authorFilter').update('!Anonymous'); this.reset(false); } @@ -119,10 +116,7 @@ export class LogsOverviewModel extends Observable { * @return {undefined} */ reset(fetch = true) { - this.titleFilter.reset(); - this.contentFilter.reset(); - this.authorFilter.reset(); - + this._filteringModel.reset(); this.createdFilterFrom = ''; this.createdFilterTo = ''; @@ -153,9 +147,7 @@ export class LogsOverviewModel extends Observable { */ isAnyFilterActive() { return ( - !this._titleFilter.isEmpty - || !this._contentFilter.isEmpty - || !this._authorFilter.isEmpty + !this._filteringModel.isAnyFilterActive() || this.createdFilterFrom !== '' || this.createdFilterTo !== '' || !this.listingTagsFilterModel.isEmpty @@ -289,6 +281,15 @@ export class LogsOverviewModel extends Observable { } } + /** + * Return the model managing all filters + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + /** * Return the model handling the filtering on tags * @@ -375,16 +376,18 @@ export class LogsOverviewModel extends Observable { _getFilterQueryParams() { const sortOn = this._overviewSortModel.appliedOn; const sortDirection = this._overviewSortModel.appliedDirection; - + const titleFilter = this._filteringModel.get('titleFilter'); + const contentFilter = this._filteringModel.get('contentFilter'); + const authorFilter = this._filteringModel.get('authorFilter'); return { - ...!this._titleFilter.isEmpty && { - 'filter[title]': this._titleFilter.value, + ...!titleFilter.isEmpty && { + 'filter[title]': titleFilter.value, }, - ...!this._contentFilter.isEmpty && { - 'filter[content]': this._contentFilter.value, + ...!contentFilter.isEmpty && { + 'filter[content]': contentFilter.value, }, - ...!this._authorFilter.isEmpty && { - 'filter[author]': this._authorFilter.value, + ...!authorFilter.isEmpty && { + 'filter[author]': authorFilter, }, ...this.createdFilterFrom && { 'filter[created][from]': diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index 012f6e7bfe..ed5c7a860c 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -39,7 +39,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { h('#main-action-bar.flex-row.justify-between.header-container.pv2', [ h('.flex-row.g3', [ filtersPanelPopover(logsOverviewModel, logsActiveColumns), - excludeAnonymousLogAuthorToggle(logsOverviewModel.authorFilter), + excludeAnonymousLogAuthorToggle(logsOverviewModel.filteringModel.get('authorFilter')), ]), actionButtons(), ]), From 9faffaf8b3668364237ef61644cfebbc66825237 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 11:34:06 +0100 Subject: [PATCH 003/115] Make filterInputModel extend filterModel --- .../common/filters/FilterInputModel.js | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/lib/public/components/Filters/common/filters/FilterInputModel.js b/lib/public/components/Filters/common/filters/FilterInputModel.js index 8860edf61d..7e3600ec7b 100644 --- a/lib/public/components/Filters/common/filters/FilterInputModel.js +++ b/lib/public/components/Filters/common/filters/FilterInputModel.js @@ -10,48 +10,43 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { Observable } from '/js/src/index.js'; +import { FilterModel } from '../FilterModel'; /** * Model for a generic filter input */ -export class FilterInputModel extends Observable { +export class FilterInputModel extends FilterModel { /** * Constructor + * + * @param {callback} parse function called to parse a value from a raw value */ - constructor() { + constructor(parse) { super(); + this.parse = parse; this._value = null; this._raw = ''; - - this._visualChange$ = new Observable(); } /** * Define the current value of the filter * * @param {string} raw the raw value of the filter + * @override * @return {void} */ update(raw) { - const previousValues = this.value; - - this._value = this.valueFromRaw(raw); - this._raw = raw; + const value = this._parse(raw); - if (this.areValuesEquals(this.value, previousValues)) { - // Only raw value changed - this._visualChange$.notify(); - } else { + if (!this.areValuesEquals(this._value, value)) { + this._value = value; this.notify(); } } /** - * Reset the filter to its default value - * - * @return {void} + * @inheritdoc */ reset() { this._value = null; @@ -86,23 +81,10 @@ export class FilterInputModel extends Observable { } /** - * Returns the observable notified any time there is a visual change which has no impact on the actual filter value - * - * @return {Observable} the observable - */ - get visualChange$() { - return this._visualChange$; - } - - /** - * Returns the processed value from raw input - * - * @param {string} raw the raw input value - * @return {*} the processed value - * @protected + * @inheritdoc */ - valueFromRaw(raw) { - return raw.trim(); + get normalized() { + return this.value; } /** From 247943b153c996a6db1102740e82fb8daab9792a Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 12:49:59 +0100 Subject: [PATCH 004/115] rename inputfilter to ParsedInputFilter --- ...{FilterInputModel.js => ParsedInputFilterModel.js} | 11 ++++++----- .../components/Filters/common/filters/textFilter.js | 2 +- lib/public/views/Logs/Overview/LogsOverviewModel.js | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) rename lib/public/components/Filters/common/filters/{FilterInputModel.js => ParsedInputFilterModel.js} (90%) diff --git a/lib/public/components/Filters/common/filters/FilterInputModel.js b/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js similarity index 90% rename from lib/public/components/Filters/common/filters/FilterInputModel.js rename to lib/public/components/Filters/common/filters/ParsedInputFilterModel.js index 7e3600ec7b..7cdd6dc8ef 100644 --- a/lib/public/components/Filters/common/filters/FilterInputModel.js +++ b/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js @@ -10,12 +10,13 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { FilterModel } from '../FilterModel'; + +import { FilterModel } from '../FilterModel.js'; /** - * Model for a generic filter input + * Model that parses raw intput into a value */ -export class FilterInputModel extends FilterModel { +export class ParsedInputFilterModel extends FilterModel { /** * Constructor * @@ -24,9 +25,8 @@ export class FilterInputModel extends FilterModel { constructor(parse) { super(); - this.parse = parse; + this._parse = parse; this._value = null; - this._raw = ''; } /** @@ -37,6 +37,7 @@ export class FilterInputModel extends FilterModel { * @return {void} */ update(raw) { + this._raw = raw; const value = this._parse(raw); if (!this.areValuesEquals(this._value, value)) { diff --git a/lib/public/components/Filters/common/filters/textFilter.js b/lib/public/components/Filters/common/filters/textFilter.js index 6b288d54ac..529f9e7692 100644 --- a/lib/public/components/Filters/common/filters/textFilter.js +++ b/lib/public/components/Filters/common/filters/textFilter.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; /** * Returns a text filter component * - * @param {FilterInputModel|TextTokensFilterModel} filterInputModel the model of the text filter + * @param {ParsedInputFilterModel|TextTokensFilterModel} filterInputModel the model of the text filter * @param {Object} attributes the additional attributes to pass to the component, such as id and classes * @return {Component} the filter component */ diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index afb61a725a..a9a851ac70 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -15,7 +15,7 @@ import { buildUrl, Observable, RemoteData } from '/js/src/index.js'; import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; import { SortModel } from '../../../components/common/table/SortModel.js'; import { debounce } from '../../../utilities/debounce.js'; -import { FilterInputModel } from '../../../components/Filters/common/filters/FilterInputModel.js'; +import { ParsedInputFilterModel } from '../../../components/Filters/common/filters/ParsedInputFilterModel.js'; import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author/AuthorFilterModel.js'; import { PaginationModel } from '../../../components/Pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; @@ -39,8 +39,8 @@ export class LogsOverviewModel extends Observable { this._filteringModel = new FilteringModel({ authorFilter: new AuthorFilterModel(), - titleFilter: new FilterInputModel(), - contentFilter: new FilterInputModel(), + titleFilter: new ParsedInputFilterModel((raw) => raw.trim()), + contentFilter: new ParsedInputFilterModel((raw) => raw.trim()), }); // Sub-models From 114285d1dbeaa590c7dd3ba4a23d1d7499c4b219 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 12:51:47 +0100 Subject: [PATCH 005/115] implement authorfilter with ParsedInputFilterModel changes --- .../LogsFilter/author/AuthorFilterModel.js | 34 +++++++++---------- .../Filters/LogsFilter/author/authorFilter.js | 2 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js index 1b7a133916..2c2294b2a2 100644 --- a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js +++ b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js @@ -11,19 +11,31 @@ * or submit itself to any jurisdiction. */ -import { FilterInputModel } from '../../common/filters/FilterInputModel.js'; +import { ParsedInputFilterModel } from '../../common/filters/ParsedInputFilterModel.js'; + +/** + * Parse raw author input into a normalized array. + * + * @param {string} raw a raw, comma-seperated string + * @returns {string} + */ +const parseAuthors = (raw) => raw + .split(',') + .map((author) => author.trim()) + .filter(Boolean) + .join(','); /** * Model to handle the state of the Author Filter */ -export class AuthorFilterModel extends FilterInputModel { +export class AuthorFilterModel extends ParsedInputFilterModel { /** * Constructor * * @constructor */ constructor() { - super(); + super(parseAuthors); } /** @@ -49,21 +61,7 @@ export class AuthorFilterModel extends FilterInputModel { this._raw += super.isEmpty ? '!Anonymous' : ', !Anonymous'; } - this._value = this.valueFromRaw(this._raw); - this.notify(); - } - - /** - * Reset the filter to its default value and notify the observers. - * - * @return {void} - */ - clear() { - if (this.isEmpty) { - return; - } - - super.reset(); + this._value = this._parse(this._raw); this.notify(); } } diff --git a/lib/public/components/Filters/LogsFilter/author/authorFilter.js b/lib/public/components/Filters/LogsFilter/author/authorFilter.js index 778934ba23..60efee8106 100644 --- a/lib/public/components/Filters/LogsFilter/author/authorFilter.js +++ b/lib/public/components/Filters/LogsFilter/author/authorFilter.js @@ -36,7 +36,7 @@ const authorFilterTextInput = (authorFilterModel) => h('input.w-40', { */ const resetAuthorFilterButton = (authorFilterModel) => h( '.btn.btn-pill.f7', - { disabled: authorFilterModel.isEmpty, onclick: () => authorFilterModel.clear() }, + { disabled: authorFilterModel.isEmpty, onclick: () => authorFilterModel.reset() }, iconX(), ); From f39f7ae408ee29919f55fd70e1eeb9de61c3ffc8 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 13:18:35 +0100 Subject: [PATCH 006/115] re-implement filter --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index a9a851ac70..e5bc4b3251 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -43,6 +43,9 @@ export class LogsOverviewModel extends Observable { contentFilter: new ParsedInputFilterModel((raw) => raw.trim()), }); + this._filteringModel.observe(() => this._applyFilters()); + this._filteringModel.visualChange$.bubbleTo(this); + // Sub-models this._listingTagsFilterModel = new TagFilterModel(tagsProvider.items$); this._listingTagsFilterModel.observe(() => this._applyFilters()); @@ -387,7 +390,7 @@ export class LogsOverviewModel extends Observable { 'filter[content]': contentFilter.value, }, ...!authorFilter.isEmpty && { - 'filter[author]': authorFilter, + 'filter[author]': authorFilter.value, }, ...this.createdFilterFrom && { 'filter[created][from]': From df1fea32a8bd0e2dd6ddf13c7a5c3f65ac7a18c0 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 13:40:27 +0100 Subject: [PATCH 007/115] add tag-filters to the filtering object --- .../Logs/ActiveColumns/logsActiveColumns.js | 6 ++-- .../views/Logs/Overview/LogsOverviewModel.js | 36 ++++--------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 45d626777a..28e6cf0c7a 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -153,10 +153,12 @@ export const logsActiveColumns = { /** * Tag filter component - * @param {LogsOverviewModel} logsModel the log model + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model * @return {Component} the filter component */ - filter: (logsModel) => tagFilter(logsModel.listingTagsFilterModel), + filter: ({ filteringModel }) => tagFilter(filteringModel.get('listingTagsFilterModel')), balloon: true, profiles: [profiles.none, 'embeded'], }, diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index e5bc4b3251..43991f7ef2 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -41,16 +41,13 @@ export class LogsOverviewModel extends Observable { authorFilter: new AuthorFilterModel(), titleFilter: new ParsedInputFilterModel((raw) => raw.trim()), contentFilter: new ParsedInputFilterModel((raw) => raw.trim()), + listingTagsFilterModel: new TagFilterModel(tagsProvider.items$), }); this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); // Sub-models - this._listingTagsFilterModel = new TagFilterModel(tagsProvider.items$); - this._listingTagsFilterModel.observe(() => this._applyFilters()); - this._listingTagsFilterModel.visualChange$.bubbleTo(this); - this._overviewSortModel = new SortModel(); this._overviewSortModel.observe(() => this._applyFilters(true)); this._overviewSortModel.visualChange$.bubbleTo(this); @@ -123,8 +120,6 @@ export class LogsOverviewModel extends Observable { this.createdFilterFrom = ''; this.createdFilterTo = ''; - this.listingTagsFilterModel.reset(); - this.runFilterOperation = 'AND'; this.runFilterValues = []; this._runFilterRawValue = ''; @@ -153,7 +148,6 @@ export class LogsOverviewModel extends Observable { !this._filteringModel.isAnyFilterActive() || this.createdFilterFrom !== '' || this.createdFilterTo !== '' - || !this.listingTagsFilterModel.isEmpty || this.runFilterValues.length !== 0 || this.environmentFilterValues.length !== 0 || this.lhcFillFilterValues.length !== 0 @@ -293,15 +287,6 @@ export class LogsOverviewModel extends Observable { return this._filteringModel; } - /** - * Return the model handling the filtering on tags - * - * @return {TagFilterModel} the filtering model - */ - get listingTagsFilterModel() { - return this._listingTagsFilterModel; - } - /** * Returns the model handling the overview page table sort * @@ -358,17 +343,6 @@ export class LogsOverviewModel extends Observable { now ? this.fetchLogs() : this._debouncedFetchAllLogs(); } - /** - * Register a new filter model - * @param {FilterInputModel} filter the filter to register - * @return {void} - * @private - */ - _registerFilter(filter) { - filter.visualChange$.bubbleTo(this); - filter.observe(() => this._applyFilters()); - } - /** * Returns the list of URL params corresponding to the currently applied filter * @@ -382,6 +356,8 @@ export class LogsOverviewModel extends Observable { const titleFilter = this._filteringModel.get('titleFilter'); const contentFilter = this._filteringModel.get('contentFilter'); const authorFilter = this._filteringModel.get('authorFilter'); + const listingTagsFilterModel = this._filteringModel.get('listingTagsFilterModel'); + return { ...!titleFilter.isEmpty && { 'filter[title]': titleFilter.value, @@ -400,9 +376,9 @@ export class LogsOverviewModel extends Observable { 'filter[created][to]': new Date(`${this.createdFilterTo.replace(/\//g, '-')}T23:59:59.999`).getTime(), }, - ...!this.listingTagsFilterModel.isEmpty && { - 'filter[tags][values]': this.listingTagsFilterModel.selected.join(), - 'filter[tags][operation]': this.listingTagsFilterModel.combinationOperator, + ...!listingTagsFilterModel.isEmpty && { + 'filter[tags][values]': listingTagsFilterModel.selected.join(), + 'filter[tags][operation]': listingTagsFilterModel.combinationOperator, }, ...this.runFilterValues.length > 0 && { 'filter[run][values]': this.runFilterValues.join(), From 96ca85f87d0aa2715423bf4bca7d9a710a3a74c0 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 14:02:35 +0100 Subject: [PATCH 008/115] replace titleFilter and contentFilter with RawTextFilterModels --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 43991f7ef2..c9398ffb68 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -15,12 +15,12 @@ import { buildUrl, Observable, RemoteData } from '/js/src/index.js'; import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; import { SortModel } from '../../../components/common/table/SortModel.js'; import { debounce } from '../../../utilities/debounce.js'; -import { ParsedInputFilterModel } from '../../../components/Filters/common/filters/ParsedInputFilterModel.js'; import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author/AuthorFilterModel.js'; import { PaginationModel } from '../../../components/Pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; import { tagsProvider } from '../../../services/tag/tagsProvider.js'; import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; +import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; /** * Model representing handlers for log entries page @@ -39,8 +39,8 @@ export class LogsOverviewModel extends Observable { this._filteringModel = new FilteringModel({ authorFilter: new AuthorFilterModel(), - titleFilter: new ParsedInputFilterModel((raw) => raw.trim()), - contentFilter: new ParsedInputFilterModel((raw) => raw.trim()), + titleFilter: new RawTextFilterModel(), + contentFilter: new RawTextFilterModel(), listingTagsFilterModel: new TagFilterModel(tagsProvider.items$), }); From 93b3291d0703d04fbee905ab22001d1798d6663f Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 14:42:20 +0100 Subject: [PATCH 009/115] make Authorfilter an implementation of RawTextFilterModel --- .../LogsFilter/author/AuthorFilterModel.js | 30 +++++++++---------- .../Filters/LogsFilter/author/authorFilter.js | 20 ++++--------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js index 2c2294b2a2..506f52e4bb 100644 --- a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js +++ b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js @@ -11,31 +11,20 @@ * or submit itself to any jurisdiction. */ -import { ParsedInputFilterModel } from '../../common/filters/ParsedInputFilterModel.js'; - -/** - * Parse raw author input into a normalized array. - * - * @param {string} raw a raw, comma-seperated string - * @returns {string} - */ -const parseAuthors = (raw) => raw - .split(',') - .map((author) => author.trim()) - .filter(Boolean) - .join(','); +import { RawTextFilterModel } from '../../common/filters/RawTextFilterModel.js'; /** * Model to handle the state of the Author Filter */ -export class AuthorFilterModel extends ParsedInputFilterModel { +export class AuthorFilterModel extends RawTextFilterModel { /** * Constructor * * @constructor */ constructor() { - super(parseAuthors); + super(); + this._raw = ''; } /** @@ -61,7 +50,16 @@ export class AuthorFilterModel extends ParsedInputFilterModel { this._raw += super.isEmpty ? '!Anonymous' : ', !Anonymous'; } - this._value = this._parse(this._raw); + this._value = this._raw.trim(); this.notify(); } + + /** + * @inheritdoc + * @override + */ + reset() { + super.reset(); + this._raw = ''; + } } diff --git a/lib/public/components/Filters/LogsFilter/author/authorFilter.js b/lib/public/components/Filters/LogsFilter/author/authorFilter.js index 60efee8106..7cfc2b7d7e 100644 --- a/lib/public/components/Filters/LogsFilter/author/authorFilter.js +++ b/lib/public/components/Filters/LogsFilter/author/authorFilter.js @@ -14,19 +14,7 @@ import { h } from '/js/src/index.js'; import { iconX } from '/js/src/icons.js'; import { switchInput } from '../../../common/form/switchInput.js'; - -/** - * Returns a text input field that can be used to filter logs by author - * - * @param {AuthorFilterModel} authorFilterModel The author filter model object - * @returns {Component} A text box that allows the user to enter an author substring to match against all logs - */ -const authorFilterTextInput = (authorFilterModel) => h('input.w-40', { - type: 'text', - id: 'authorFilterText', - value: authorFilterModel.raw, - oninput: (e) => authorFilterModel.update(e.target.value), -}); +import { rawTextFilter } from '../../common/filters/rawTextFilter.js'; /** * Returns a button that can be used to reset the author filter. @@ -60,7 +48,11 @@ export const excludeAnonymousLogAuthorToggle = (authorFilterModel) => switchInpu * @return {Component} the filter component */ export const authorFilter = ({ filteringModel }) => h('.flex-row.items-center.g3', [ - authorFilterTextInput(filteringModel.get('authorFilter')), + rawTextFilter(filteringModel.get('authorFilter'), { + classes: ['w-40'], + id: 'authorFilterText', + value: filteringModel.get('authorFilter').raw, + }), resetAuthorFilterButton(filteringModel.get('authorFilter')), excludeAnonymousLogAuthorToggle(filteringModel.get('authorFilter')), ]); From 5a5e830615fd01dbc178f88f2048dd29747bb602 Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 15:31:19 +0100 Subject: [PATCH 010/115] add runs filter to the filteringmodel --- .../Logs/ActiveColumns/logsActiveColumns.js | 23 +++++++++++++++---- .../views/Logs/Overview/LogsOverviewModel.js | 6 +++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 28e6cf0c7a..26cf700590 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -16,7 +16,6 @@ import { iconCommentSquare, iconPaperclip } from '/js/src/icons.js'; import { authorFilter } from '../../../components/Filters/LogsFilter/author/authorFilter.js'; import createdFilter from '../../../components/Filters/LogsFilter/created.js'; -import runsFilter from '../../../components/Filters/LogsFilter/runs.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { frontLinks } from '../../../components/common/navigation/frontLinks.js'; @@ -28,6 +27,7 @@ import { environmentFilter } from '../../../components/Filters/LogsFilter/enviro import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; import { lhcFillsFilter } from '../../../components/Filters/LogsFilter/lhcFill.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; +import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; /** * A method to display a small and simple number/icon collection as a column @@ -108,11 +108,11 @@ export const logsActiveColumns = { * @param {FilteringModel} logOverviewModel.filteringModel filtering model * @return {Component} the filter component */ - filter: ({ filteringModel }) => textFilter( + filter: ({ filteringModel }) => rawTextFilter( filteringModel.get('contentFilter'), { id: 'contentFilterText', - class: 'w-75 mt1', + classes: ['w-75', 'mt1'], }, ), }, @@ -168,7 +168,22 @@ export const logsActiveColumns = { sortable: true, size: 'w-15', format: formatRunsList, - filter: runsFilter, + + /** + * Runs filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('run'), + { + id: 'runsFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. 553203, 553221, ...', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index c9398ffb68..fd6143d390 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -42,6 +42,7 @@ export class LogsOverviewModel extends Observable { titleFilter: new RawTextFilterModel(), contentFilter: new RawTextFilterModel(), listingTagsFilterModel: new TagFilterModel(tagsProvider.items$), + run: new RawTextFilterModel(), }); this._filteringModel.observe(() => this._applyFilters()); @@ -357,6 +358,7 @@ export class LogsOverviewModel extends Observable { const contentFilter = this._filteringModel.get('contentFilter'); const authorFilter = this._filteringModel.get('authorFilter'); const listingTagsFilterModel = this._filteringModel.get('listingTagsFilterModel'); + const run = this._filteringModel.get('run'); return { ...!titleFilter.isEmpty && { @@ -380,8 +382,8 @@ export class LogsOverviewModel extends Observable { 'filter[tags][values]': listingTagsFilterModel.selected.join(), 'filter[tags][operation]': listingTagsFilterModel.combinationOperator, }, - ...this.runFilterValues.length > 0 && { - 'filter[run][values]': this.runFilterValues.join(), + ...!run.isEmpty && { + 'filter[run][values]': run.normalized, 'filter[run][operation]': this.runFilterOperation.toLowerCase(), }, ...this.environmentFilterValues.length > 0 && { From b5e7148d07257a41fdff38097e9329740b3b9a5e Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 15:53:49 +0100 Subject: [PATCH 011/115] add environments filter to the filteringmodel --- .../Logs/ActiveColumns/logsActiveColumns.js | 18 ++++++++- .../views/Logs/Overview/LogsOverviewModel.js | 37 ++----------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 26cf700590..eab0bcd278 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -23,7 +23,6 @@ import { tagFilter } from '../../../components/Filters/common/filters/tagFilter. import { formatRunsList } from '../../Runs/format/formatRunsList.js'; import { profiles } from '../../../components/common/table/profiles.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; -import { environmentFilter } from '../../../components/Filters/LogsFilter/environments.js'; import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; import { lhcFillsFilter } from '../../../components/Filters/LogsFilter/lhcFill.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; @@ -200,7 +199,22 @@ export const logsActiveColumns = { parameters: { environmentId: id }, }), ), - filter: environmentFilter, + + /** + * Environment filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('environments'), + { + id: 'runsFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. Dxi029djX, TDI59So3d...', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index fd6143d390..13504a6e31 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -43,6 +43,7 @@ export class LogsOverviewModel extends Observable { contentFilter: new RawTextFilterModel(), listingTagsFilterModel: new TagFilterModel(tagsProvider.items$), run: new RawTextFilterModel(), + environments: new RawTextFilterModel(), }); this._filteringModel.observe(() => this._applyFilters()); @@ -122,13 +123,7 @@ export class LogsOverviewModel extends Observable { this.createdFilterTo = ''; this.runFilterOperation = 'AND'; - this.runFilterValues = []; - this._runFilterRawValue = ''; - this.environmentFilterOperation = 'AND'; - this.environmentFilterValues = []; - this._environmentFilterRawValue = ''; - this.lhcFillFilterOperation = 'AND'; this.lhcFillFilterValues = []; this._lhcFillFilterRawValue = ''; @@ -149,8 +144,6 @@ export class LogsOverviewModel extends Observable { !this._filteringModel.isAnyFilterActive() || this.createdFilterFrom !== '' || this.createdFilterTo !== '' - || this.runFilterValues.length !== 0 - || this.environmentFilterValues.length !== 0 || this.lhcFillFilterValues.length !== 0 ); } @@ -163,29 +156,6 @@ export class LogsOverviewModel extends Observable { return this._runFilterRawValue; } - /** - * Add a run to the filter - * @param {string} rawRuns The runs to be added to the filter criteria - * @returns {undefined} - */ - setRunsFilter(rawRuns) { - this._runFilterRawValue = rawRuns; - const runs = []; - const valuesRegex = /([0-9]+),?/g; - - let match = valuesRegex.exec(rawRuns); - while (match) { - runs.push(parseInt(match[1], 10)); - match = valuesRegex.exec(rawRuns); - } - - // Allow empty runs only if raw runs is an empty string - if (runs.length > 0 || rawRuns.length === 0) { - this.runFilterValues = runs; - this._applyFilters(); - } - } - /** * Returns the raw current environment filter * @returns {string} the raw current environment filter @@ -359,6 +329,7 @@ export class LogsOverviewModel extends Observable { const authorFilter = this._filteringModel.get('authorFilter'); const listingTagsFilterModel = this._filteringModel.get('listingTagsFilterModel'); const run = this._filteringModel.get('run'); + const environments = this._filteringModel.get('environments'); return { ...!titleFilter.isEmpty && { @@ -386,8 +357,8 @@ export class LogsOverviewModel extends Observable { 'filter[run][values]': run.normalized, 'filter[run][operation]': this.runFilterOperation.toLowerCase(), }, - ...this.environmentFilterValues.length > 0 && { - 'filter[environments][values]': this.environmentFilterValues, + ...!environments.isEmpty && { + 'filter[environments][values]': environments.normalized, 'filter[environments][operation]': this.environmentFilterOperation.toLowerCase(), }, ...this.lhcFillFilterValues.length > 0 && { From 3507ba068b99427803507a5d9d16a27ab876200e Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 16:12:12 +0100 Subject: [PATCH 012/115] add lhcFills filter to the filteringmodel --- .../Logs/ActiveColumns/logsActiveColumns.js | 22 ++++++++-- .../views/Logs/Overview/LogsOverviewModel.js | 40 +++++-------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index eab0bcd278..cce5a551fd 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -24,7 +24,6 @@ import { formatRunsList } from '../../Runs/format/formatRunsList.js'; import { profiles } from '../../../components/common/table/profiles.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; -import { lhcFillsFilter } from '../../../components/Filters/LogsFilter/lhcFill.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; @@ -157,7 +156,7 @@ export const logsActiveColumns = { * @param {FilteringModel} logOverviewModel.filteringModel filtering model * @return {Component} the filter component */ - filter: ({ filteringModel }) => tagFilter(filteringModel.get('listingTagsFilterModel')), + filter: ({ filteringModel }) => tagFilter(filteringModel.get('tags')), balloon: true, profiles: [profiles.none, 'embeded'], }, @@ -210,7 +209,7 @@ export const logsActiveColumns = { filter: ({ filteringModel }) => rawTextFilter( filteringModel.get('environments'), { - id: 'runsFilterText', + id: 'environmentFilterText', classes: ['w-75', 'mt1'], placeholder: 'e.g. Dxi029djX, TDI59So3d...', }, @@ -224,7 +223,22 @@ export const logsActiveColumns = { sortable: false, size: 'w-10', format: formatLhcFillsList, - filter: lhcFillsFilter, + + /** + * LhcFills filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('lhcFills'), + { + id: 'lhcFillsFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. 11392, 11383, 7625', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 13504a6e31..1e9715d3d9 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -41,9 +41,10 @@ export class LogsOverviewModel extends Observable { authorFilter: new AuthorFilterModel(), titleFilter: new RawTextFilterModel(), contentFilter: new RawTextFilterModel(), - listingTagsFilterModel: new TagFilterModel(tagsProvider.items$), + tags: new TagFilterModel(tagsProvider.items$), run: new RawTextFilterModel(), environments: new RawTextFilterModel(), + lhcFills: new RawTextFilterModel(), }); this._filteringModel.observe(() => this._applyFilters()); @@ -125,8 +126,6 @@ export class LogsOverviewModel extends Observable { this.runFilterOperation = 'AND'; this.environmentFilterOperation = 'AND'; this.lhcFillFilterOperation = 'AND'; - this.lhcFillFilterValues = []; - this._lhcFillFilterRawValue = ''; this._pagination.reset(); @@ -144,7 +143,6 @@ export class LogsOverviewModel extends Observable { !this._filteringModel.isAnyFilterActive() || this.createdFilterFrom !== '' || this.createdFilterTo !== '' - || this.lhcFillFilterValues.length !== 0 ); } @@ -198,27 +196,6 @@ export class LogsOverviewModel extends Observable { return this._lhcFillFilterRawValue; } - /** - * Add a lhcFill to the filter - * @param {string} rawLhcFills The LHC fills to be added to the filter criteria - * @returns {void} - */ - setLhcFillsFilter(rawLhcFills) { - this._lhcFillFilterRawValue = rawLhcFills; - - // Split the lhc fills string by comma or whitespace, remove falsy values like empty strings, and convert to int - const lhcFills = rawLhcFills - .split(/[ ,]+/) - .filter(Boolean) - .map((fillNumberStr) => parseInt(fillNumberStr.trim(), 10)); - - // Allow empty lhcFills only if raw lhcFills is an empty string - if (lhcFills.length > 0 || rawLhcFills.length === 0) { - this.lhcFillFilterValues = lhcFills; - this._applyFilters(); - } - } - /** * Returns the current minimum creation datetime * @returns {Integer} The current minimum creation datetime @@ -327,9 +304,10 @@ export class LogsOverviewModel extends Observable { const titleFilter = this._filteringModel.get('titleFilter'); const contentFilter = this._filteringModel.get('contentFilter'); const authorFilter = this._filteringModel.get('authorFilter'); - const listingTagsFilterModel = this._filteringModel.get('listingTagsFilterModel'); + const tags = this._filteringModel.get('tags'); const run = this._filteringModel.get('run'); const environments = this._filteringModel.get('environments'); + const lhcFills = this._filteringModel.get('lhcFills'); return { ...!titleFilter.isEmpty && { @@ -349,9 +327,9 @@ export class LogsOverviewModel extends Observable { 'filter[created][to]': new Date(`${this.createdFilterTo.replace(/\//g, '-')}T23:59:59.999`).getTime(), }, - ...!listingTagsFilterModel.isEmpty && { - 'filter[tags][values]': listingTagsFilterModel.selected.join(), - 'filter[tags][operation]': listingTagsFilterModel.combinationOperator, + ...!tags.isEmpty && { + 'filter[tags][values]': tags.selected.join(), + 'filter[tags][operation]': tags.combinationOperator, }, ...!run.isEmpty && { 'filter[run][values]': run.normalized, @@ -361,8 +339,8 @@ export class LogsOverviewModel extends Observable { 'filter[environments][values]': environments.normalized, 'filter[environments][operation]': this.environmentFilterOperation.toLowerCase(), }, - ...this.lhcFillFilterValues.length > 0 && { - 'filter[lhcFills][values]': this.lhcFillFilterValues.join(), + ...!lhcFills.isEmpty && { + 'filter[lhcFills][values]': lhcFills.normalized, 'filter[lhcFills][operation]': this.lhcFillFilterOperation.toLowerCase(), }, ...sortOn && sortDirection && { From 9c63409629ff400d84bb76a9537540e9c164e95f Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 16:31:33 +0100 Subject: [PATCH 013/115] add created filter to the filteringmodel --- .../Logs/ActiveColumns/logsActiveColumns.js | 12 +- .../views/Logs/Overview/LogsOverviewModel.js | 133 ++---------------- 2 files changed, 20 insertions(+), 125 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index cce5a551fd..2f56db5f00 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -15,7 +15,6 @@ import { h } from '/js/src/index.js'; import { iconCommentSquare, iconPaperclip } from '/js/src/icons.js'; import { authorFilter } from '../../../components/Filters/LogsFilter/author/authorFilter.js'; -import createdFilter from '../../../components/Filters/LogsFilter/created.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { frontLinks } from '../../../components/common/navigation/frontLinks.js'; @@ -26,6 +25,7 @@ import { textFilter } from '../../../components/Filters/common/filters/textFilte import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; +import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js'; /** * A method to display a small and simple number/icon collection as a column @@ -129,7 +129,15 @@ export const logsActiveColumns = { sortable: true, size: 'w-10', format: (timestamp) => formatTimestamp(timestamp, false), - filter: createdFilter, + + /** + * Created filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => timeRangeFilter(filteringModel.get('created')), profiles: { embeded: { format: (timestamp) => formatTimestamp(timestamp), diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 1e9715d3d9..ffee7549c1 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -21,6 +21,7 @@ import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice. import { tagsProvider } from '../../../services/tag/tagsProvider.js'; import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; +import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; /** * Model representing handlers for log entries page @@ -45,6 +46,7 @@ export class LogsOverviewModel extends Observable { run: new RawTextFilterModel(), environments: new RawTextFilterModel(), lhcFills: new RawTextFilterModel(), + created: new TimeRangeInputModel(), }); this._filteringModel.observe(() => this._applyFilters()); @@ -120,8 +122,6 @@ export class LogsOverviewModel extends Observable { */ reset(fetch = true) { this._filteringModel.reset(); - this.createdFilterFrom = ''; - this.createdFilterTo = ''; this.runFilterOperation = 'AND'; this.environmentFilterOperation = 'AND'; @@ -139,91 +139,7 @@ export class LogsOverviewModel extends Observable { * @returns {boolean} If any filter is active */ isAnyFilterActive() { - return ( - !this._filteringModel.isAnyFilterActive() - || this.createdFilterFrom !== '' - || this.createdFilterTo !== '' - ); - } - - /** - * Returns the current title substring filter - * @returns {string} The current title substring filter - */ - getRunsFilterRaw() { - return this._runFilterRawValue; - } - - /** - * Returns the raw current environment filter - * @returns {string} the raw current environment filter - */ - getEnvFilterRaw() { - return this._environmentFilterRawValue; - } - - /** - * Returns the current environment filter - * @returns {string[]} The current environment filter - */ - getEnvFilter() { - return this.environmentFilterValues; - } - - /** - * Sets the environment filter - * @param {string} rawEnvironments The environments to apply to the filter - * @returns {undefined} - */ - setEnvFilter(rawEnvironments) { - this._environmentFilterRawValue = rawEnvironments; - const envs = rawEnvironments - .split(/[ ,]+/) - .filter(Boolean) - .map((id) => id.trim()); - - if (envs.length > 0 || rawEnvironments.length === 0) { - this.environmentFilterValues = envs; - this._applyFilters(); - } - } - - /** - * Returns the current title substring filter - * @returns {string} The current title substring filter - */ - getLhcFillsFilterRaw() { - return this._lhcFillFilterRawValue; - } - - /** - * Returns the current minimum creation datetime - * @returns {Integer} The current minimum creation datetime - */ - getCreatedFilterFrom() { - return this.createdFilterFrom; - } - - /** - * Returns the current maximum creation datetime - * @returns {Integer} The current maximum creation datetime - */ - getCreatedFilterTo() { - return this.createdFilterTo; - } - - /** - * Set a datetime for the creation datetime filter - * @param {string} key The filter value to apply the datetime to - * @param {Object} date The datetime to be applied to the creation datetime filter - * @param {boolean} valid Whether the inserted date passes validity check - * @returns {undefined} - */ - setCreatedFilter(key, date, valid) { - if (valid) { - this[`createdFilter${key}`] = date; - this._applyFilters(); - } + return !this._filteringModel.isAnyFilterActive(); } /** @@ -244,32 +160,6 @@ export class LogsOverviewModel extends Observable { return this._overviewSortModel; } - /** - * Returns the filter model for author filter - * - * @return {FilterInputModel} the filter model - */ - get authorFilter() { - return this._authorFilter; - } - - /** - * Returns the filter model for title filter - * - * @return {FilterInputModel} the filter model - */ - get titleFilter() { - return this._titleFilter; - } - - /** - * Returns the model for body filter - * @return {FilterInputModel} the filter model - */ - get contentFilter() { - return this._contentFilter; - } - /** * Returns the pagination model * @@ -308,24 +198,21 @@ export class LogsOverviewModel extends Observable { const run = this._filteringModel.get('run'); const environments = this._filteringModel.get('environments'); const lhcFills = this._filteringModel.get('lhcFills'); + const created = this._filteringModel.get('created'); return { ...!titleFilter.isEmpty && { - 'filter[title]': titleFilter.value, + 'filter[title]': titleFilter.normalized, }, ...!contentFilter.isEmpty && { - 'filter[content]': contentFilter.value, + 'filter[content]': contentFilter.normalized, }, ...!authorFilter.isEmpty && { - 'filter[author]': authorFilter.value, - }, - ...this.createdFilterFrom && { - 'filter[created][from]': - new Date(`${this.createdFilterFrom.replace(/\//g, '-')}T00:00:00.000`).getTime(), + 'filter[author]': authorFilter.normalized, }, - ...this.createdFilterTo && { - 'filter[created][to]': - new Date(`${this.createdFilterTo.replace(/\//g, '-')}T23:59:59.999`).getTime(), + ...!created.isEmpty && { + 'filter[created][from]': created.normalized.from, + 'filter[created][to]': created.normalized.to, }, ...!tags.isEmpty && { 'filter[tags][values]': tags.selected.join(), From 5500b3e443404d669b43bd61b5104e170e973bae Mon Sep 17 00:00:00 2001 From: Guust Date: Fri, 20 Feb 2026 17:23:26 +0100 Subject: [PATCH 014/115] move the sort 'filter' to fetchlogs, since it doesn't actually filter anything --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index ffee7549c1..fb14e9f770 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -80,6 +80,8 @@ export class LogsOverviewModel extends Observable { */ async fetchLogs() { const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; + const sortOn = this._overviewSortModel.appliedOn; + const sortDirection = this._overviewSortModel.appliedDirection; if (!keepExisting) { this._logs = RemoteData.loading(); @@ -87,6 +89,9 @@ export class LogsOverviewModel extends Observable { } const params = { + ...sortOn && sortDirection && { + [`sort[${sortOn}]`]: sortDirection, + }, ...this._getFilterQueryParams(), 'page[offset]': this._pagination.firstItemOffset, 'page[limit]': this._pagination.itemsPerPage, @@ -189,8 +194,6 @@ export class LogsOverviewModel extends Observable { * @private */ _getFilterQueryParams() { - const sortOn = this._overviewSortModel.appliedOn; - const sortDirection = this._overviewSortModel.appliedDirection; const titleFilter = this._filteringModel.get('titleFilter'); const contentFilter = this._filteringModel.get('contentFilter'); const authorFilter = this._filteringModel.get('authorFilter'); @@ -230,9 +233,6 @@ export class LogsOverviewModel extends Observable { 'filter[lhcFills][values]': lhcFills.normalized, 'filter[lhcFills][operation]': this.lhcFillFilterOperation.toLowerCase(), }, - ...sortOn && sortDirection && { - [`sort[${sortOn}]`]: sortDirection, - }, }; } } From a09ae8b4922e4f43c44bfa458a3edf902e8dbd41 Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 16:56:12 +0100 Subject: [PATCH 015/115] change filter to rawTextFilter for title --- lib/public/views/Logs/ActiveColumns/logsActiveColumns.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 2f56db5f00..21402ca680 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -21,7 +21,6 @@ import { frontLinks } from '../../../components/common/navigation/frontLinks.js' import { tagFilter } from '../../../components/Filters/common/filters/tagFilter.js'; import { formatRunsList } from '../../Runs/format/formatRunsList.js'; import { profiles } from '../../../components/common/table/profiles.js'; -import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; @@ -77,7 +76,7 @@ export const logsActiveColumns = { * @param {FilteringModel} logOverviewModel.filteringModel filtering model * @return {Component} the filter component */ - filter: ({ filteringModel }) => textFilter( + filter: ({ filteringModel }) => rawTextFilter( filteringModel.get('titleFilter'), { id: 'titleFilterText', From 512999b8c7e21b379d805d5d6edb0039ab28f93b Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 16:58:51 +0100 Subject: [PATCH 016/115] fix test by changing event type to 'change' --- test/public/logs/overview.test.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 39119d7ef1..14ec5e1700 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -91,16 +91,13 @@ module.exports = () => { it('can filter by log title', async () => { await waitForTableLength(page, 10); - await pressElement(page, '#openFilterToggle'); - await page.waitForSelector('#titleFilterText'); - - await fillInput(page, '#titleFilterText', 'first'); + await openFilteringPanel(page) + await fillInput(page, '#titleFilterText', 'first', ['change']); await waitForTableLength(page, 1); - await fillInput(page, '#titleFilterText', 'bogusbogusbogus'); + await fillInput(page, '#titleFilterText', 'bogusbogusbogus', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('should successfully provide an input to filter on log content', async () => { From 0f15812b68ac49429ac953ec16a84ecd53b6c709 Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 17:08:30 +0100 Subject: [PATCH 017/115] fix content and author tests --- test/public/logs/overview.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 14ec5e1700..91dfd04806 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -103,29 +103,29 @@ module.exports = () => { it('should successfully provide an input to filter on log content', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#contentFilterText', 'particle'); + await fillInput(page, '#contentFilterText', 'particle', ['change']); await waitForTableLength(page, 2); - await fillInput(page, '#titleFilterText', 'this-content-do-not-exists-anywhere'); + await fillInput(page, '#titleFilterText', 'this-content-do-not-exists-anywhere', ['change']); await waitForEmptyTable(page); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by log author', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#authorFilterText', 'Jane'); + await fillInput(page, '#authorFilterText', 'Jane', ['change']); await waitForEmptyTable(page); - await pressElement(page, '#reset-filters'); + await resetFilters(page); await waitForTableLength(page, 10); - await fillInput(page, '#authorFilterText', 'John'); + await fillInput(page, '#authorFilterText', 'John', ['change']); await waitForTableLength(page, 5); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('should successfully provide an easy-to-access button to filter in/out anonymous logs', async () => { From 7b641ca3768baf1a3cea85c4b1f81ee9998b3cac Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 17:10:40 +0100 Subject: [PATCH 018/115] import openFilteringPanel and resetFilters --- test/public/logs/overview.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 91dfd04806..20c95db194 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -34,6 +34,8 @@ const { waitForEmptyTable, waitForTableTotalRowsCountToEqual, waitForTableFirstRowIndexToEqual, + openFilteringPanel, + resetFilters, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); From 5ab82185a6e414d5788ca402f4ef56b37df6d592 Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 17:50:37 +0100 Subject: [PATCH 019/115] fix createdAt filter test --- test/public/logs/overview.test.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 20c95db194..6d438ff053 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -153,17 +153,25 @@ module.exports = () => { }); it('can filter by creation date', async () => { - await pressElement(page, '#openFilterToggle'); + await openFilteringPanel(page); + + const popoverTrigger = '.createdAt-filter .popover-trigger'; + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); await waitForTableTotalRowsCountToEqual(page, 119); - // Insert a minimum date into the filter + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + const limit = '2020-02-02'; - await fillInput(page, '#createdFilterFrom', limit); - await fillInput(page, '#createdFilterTo', limit); - await waitForTableLength(page, 1); + + await fillInput(page, fromDateSelector, limit, ['change']); + await fillInput(page, toDateSelector, limit, ['change']); + await fillInput(page, fromTimeSelector, '11:00', ['change']); + await fillInput(page, toTimeSelector, '12:00', ['change']); - await pressElement(page, '#reset-filters'); + await waitForTableLength(page, 1); + await openFilteringPanel(page); + await resetFilters(page); }); it('can filter by tags', async () => { @@ -190,7 +198,7 @@ module.exports = () => { await pressElement(page, '#tag-filter-combination-operator-radio-button-or', true); await waitForTableLength(page, 3); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by environments', async () => { From 4c96d73705977f970ff49812e4962eccc2050ade Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 18:07:39 +0100 Subject: [PATCH 020/115] change event types to change --- test/public/logs/overview.test.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 6d438ff053..357584425c 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -36,6 +36,7 @@ const { waitForTableFirstRowIndexToEqual, openFilteringPanel, resetFilters, + getPeriodInputsSelectors, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -204,16 +205,14 @@ module.exports = () => { it('can filter by environments', async () => { await waitForTableLength(page, 10); - await fillInput(page, '.environments-filter input', '8E4aZTjY'); + await fillInput(page, '.environments-filter input', '8E4aZTjY', ['change']); await waitForTableLength(page, 3); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); await waitForTableLength(page, 10); - await fillInput(page, '.environments-filter input', 'abcdefgh'); + await fillInput(page, '.environments-filter input', 'abcdefgh', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can search for tag in the dropdown', async () => { @@ -241,31 +240,29 @@ module.exports = () => { await waitForTableLength(page, 10); // Insert some text into the filter - await fillInput(page, '#runsFilterText', '1, 2'); + await fillInput(page, '#runsFilterText', '1, 2', ['change']); await waitForTableLength(page, 2); + await resetFilters(page); - await pressElement(page, '#reset-filters'); await waitForTableLength(page, 10); - await fillInput(page, '#runsFilterText', '1234567890'); + await fillInput(page, '#runsFilterText', '1234567890', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by lhc fill number', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#lhcFillsFilter', '1, 6'); + await fillInput(page, '#lhcFillsFilterText', '1, 6', ['change']); await waitForTableLength(page, 1); + await resetFilters(page); - await pressElement(page, '#reset-filters'); await waitForTableLength(page, 10); - await fillInput(page, '#lhcFillsFilter', '1234567890'); + await fillInput(page, '#lhcFillsFilterText', '1234567890', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can sort by columns in ascending and descending manners', async () => { From 18ce4dcc0e8d26e5cd4986bf46fb955c2967e87f Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 19:06:32 +0100 Subject: [PATCH 021/115] fix isAnyFilterActive --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index fb14e9f770..23946cec78 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -144,7 +144,7 @@ export class LogsOverviewModel extends Observable { * @returns {boolean} If any filter is active */ isAnyFilterActive() { - return !this._filteringModel.isAnyFilterActive(); + return this._filteringModel.isAnyFilterActive(); } /** From 685dd38f4650aba9b4386b259c943f35f58cd7c8 Mon Sep 17 00:00:00 2001 From: Guust Date: Sat, 21 Feb 2026 19:49:24 +0100 Subject: [PATCH 022/115] remove _raw as from authorfilterModel, as it serves no purpose --- .../LogsFilter/author/AuthorFilterModel.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js index 506f52e4bb..6e76b7b3e1 100644 --- a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js +++ b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js @@ -24,7 +24,6 @@ export class AuthorFilterModel extends RawTextFilterModel { */ constructor() { super(); - this._raw = ''; } /** @@ -33,7 +32,7 @@ export class AuthorFilterModel extends RawTextFilterModel { * @return {boolean} true if '!Anonymous' is included in the raw filter string, false otherwise. */ isAnonymousExcluded() { - return this._raw.includes('!Anonymous'); + return this._value.includes('!Anonymous'); } /** @@ -43,23 +42,13 @@ export class AuthorFilterModel extends RawTextFilterModel { */ toggleAnonymousFilter() { if (this.isAnonymousExcluded()) { - this._raw = this._raw.split(',') + this._value = this._value.split(',') .filter((author) => author.trim() !== '!Anonymous') .join(','); } else { - this._raw += super.isEmpty ? '!Anonymous' : ', !Anonymous'; + this._value += super.isEmpty ? '!Anonymous' : ', !Anonymous'; } - this._value = this._raw.trim(); this.notify(); } - - /** - * @inheritdoc - * @override - */ - reset() { - super.reset(); - this._raw = ''; - } } From 4e132fdc91fe1a62f570b279c67109558317b54a Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 2 Mar 2026 12:08:00 +0100 Subject: [PATCH 023/115] chore: removed ParsedInputFilterModel --- .../common/filters/ParsedInputFilterModel.js | 102 ------------------ .../Filters/common/filters/textFilter.js | 8 +- 2 files changed, 4 insertions(+), 106 deletions(-) delete mode 100644 lib/public/components/Filters/common/filters/ParsedInputFilterModel.js diff --git a/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js b/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js deleted file mode 100644 index 7cdd6dc8ef..0000000000 --- a/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @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 { FilterModel } from '../FilterModel.js'; - -/** - * Model that parses raw intput into a value - */ -export class ParsedInputFilterModel extends FilterModel { - /** - * Constructor - * - * @param {callback} parse function called to parse a value from a raw value - */ - constructor(parse) { - super(); - - this._parse = parse; - this._value = null; - } - - /** - * Define the current value of the filter - * - * @param {string} raw the raw value of the filter - * @override - * @return {void} - */ - update(raw) { - this._raw = raw; - const value = this._parse(raw); - - if (!this.areValuesEquals(this._value, value)) { - this._value = value; - this.notify(); - } - } - - /** - * @inheritdoc - */ - reset() { - this._value = null; - this._raw = ''; - } - - /** - * Returns the raw value of the filter (the user input) - * - * @return {string} the raw value - */ - get raw() { - return this._raw; - } - - /** - * Return the parsed values of the filter - * - * @return {*} the parsed values - */ - get value() { - return this._value; - } - - /** - * States if the filter has been filled - * - * @return {boolean} true if the filter has been filled - */ - get isEmpty() { - return !this.value; - } - - /** - * @inheritdoc - */ - get normalized() { - return this.value; - } - - /** - * Compares two values - * - * @param {*} first the first value - * @param {*} second the second value - * @return {boolean} true if the values are equals - * @protected - */ - areValuesEquals(first, second) { - return first === second; - } -} diff --git a/lib/public/components/Filters/common/filters/textFilter.js b/lib/public/components/Filters/common/filters/textFilter.js index 529f9e7692..d6ae0cdfa4 100644 --- a/lib/public/components/Filters/common/filters/textFilter.js +++ b/lib/public/components/Filters/common/filters/textFilter.js @@ -16,13 +16,13 @@ import { h } from '/js/src/index.js'; /** * Returns a text filter component * - * @param {ParsedInputFilterModel|TextTokensFilterModel} filterInputModel the model of the text filter + * @param {TextTokensFilterModel} textTokensFilterModel the model of the text filter * @param {Object} attributes the additional attributes to pass to the component, such as id and classes * @return {Component} the filter component */ -export const textFilter = (filterInputModel, attributes) => h('input', { +export const textFilter = (textTokensFilterModel, attributes) => h('input', { ...attributes, type: 'text', - value: filterInputModel.raw, - oninput: (e) => filterInputModel.update(e.target.value), + value: textTokensFilterModel.raw, + oninput: (e) => textTokensFilterModel.update(e.target.value), }, ''); From caf9812fef510fc1f2ff72fbde757d4eda2661ba Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 2 Mar 2026 12:10:55 +0100 Subject: [PATCH 024/115] chore: add toLowerCase to filterQueryParam computation --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 23946cec78..d85021f607 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -219,7 +219,7 @@ export class LogsOverviewModel extends Observable { }, ...!tags.isEmpty && { 'filter[tags][values]': tags.selected.join(), - 'filter[tags][operation]': tags.combinationOperator, + 'filter[tags][operation]': tags.combinationOperator.toLowerCase(), }, ...!run.isEmpty && { 'filter[run][values]': run.normalized, From 41c5a72a7c3d06d117c48d563b2ed9f1afa416d8 Mon Sep 17 00:00:00 2001 From: NarrowsProjects <150556207+NarrowsProjects@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:00:33 +0100 Subject: [PATCH 025/115] [O2B-1530] Lhc fills add sb duration filter (#2080) * Filtering by stableBeamsStart and stableBeamsEnd has been added to LHC Fills overview page * lhcFills endpoint & DTO validation modified and testing added for the aforementioned changes --------- Co-authored-by: GuustMetz Co-authored-by: Guust --- lib/domain/dtos/filters/LhcFillsFilterDto.js | 3 + .../ActiveColumns/lhcFillsActiveColumns.js | 17 ++ .../Overview/LhcFillsOverviewModel.js | 3 + lib/usecases/lhcFill/GetAllLhcFillsUseCase.js | 14 +- test/api/lhcFills.test.js | 172 ++++++++++++++++++ .../lhcFill/GetAllLhcFillsUseCase.test.js | 56 ++++++ test/public/lhcFills/overview.test.js | 68 ++++++- 7 files changed, 323 insertions(+), 10 deletions(-) diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index 3338f44517..3fe9578388 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -14,6 +14,7 @@ const Joi = require('joi'); const { validateRange, RANGE_INVALID } = require('../../../utilities/rangeUtils'); const { validateBeamTypes, BEAM_TYPE_INVALID } = require('../../../utilities/beamTypeUtils'); const { validateTimeDuration } = require('../../../utilities/validateTime'); +const { FromToFilterDto } = require('./FromToFilterDto.js'); exports.LhcFillsFilterDto = Joi.object({ hasStableBeams: Joi.boolean(), @@ -23,6 +24,8 @@ exports.LhcFillsFilterDto = Joi.object({ }), runDuration: validateTimeDuration, beamDuration: validateTimeDuration, + stableBeamsStart: FromToFilterDto, + stableBeamsEnd: FromToFilterDto, schemeName: Joi.string().trim().max(64), beamTypes: Joi.string() .trim() diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 9dbd83ae4f..b2657c8cfd 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -28,6 +28,7 @@ import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fil import { durationFilter } from '../../../components/Filters/LhcFillsFilter/durationFilter.js'; import { beamTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamTypeFilter.js'; import { schemeNameFilter } from '../../../components/Filters/LhcFillsFilter/schemeNameFilter.js'; +import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js'; /** * List of active columns for a lhc fills table @@ -65,6 +66,14 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (timestamp) => formatTimestamp(timestamp, false), + + /** + * Stable Beam start filter component + * + * @param {RunsOverviewModel} lhcFillsOverviewModel the lhcFills overview model + * @return {Component} the filter component + */ + filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsStart').timeRangeInputModel), profiles: { lhcFill: true, environment: true, @@ -80,6 +89,14 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (timestamp) => formatTimestamp(timestamp, false), + + /** + * Stable Beam end filter component + * + * @param {LhcFillsOverviewModel} lhcFillsOverviewModel the lhcFills overview model + * @return {Component} the filter component + */ + filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsEnd').timeRangeInputModel), profiles: { lhcFill: true, environment: true, diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 910eacc644..c57ae69c25 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -19,6 +19,7 @@ import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js'; import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js'; +import { TimeRangeFilterModel } from '../../../components/Filters/RunsFilter/TimeRangeFilter.js'; /** * Model for the LHC fills overview page @@ -39,6 +40,8 @@ export class LhcFillsOverviewModel extends OverviewPageModel { beamDuration: new TextComparisonFilterModel(), runDuration: new TextComparisonFilterModel(), hasStableBeams: new StableBeamFilterModel(), + stableBeamsStart: new TimeRangeFilterModel(), + stableBeamsEnd: new TimeRangeFilterModel(), beamTypes: new BeamTypeFilterModel(), schemeName: new RawTextFilterModel(), }); diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index 898ec5d3de..4315cf9e1a 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -47,12 +47,24 @@ class GetAllLhcFillsUseCase { let associatedStatisticsRequired = false; if (filter) { - const { hasStableBeams, fillNumbers, schemeName, beamDuration, runDuration, beamTypes } = filter; + const { hasStableBeams, fillNumbers, schemeName, beamDuration, stableBeamsStart, stableBeamsEnd, runDuration, beamTypes } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); } + if (stableBeamsStart) { + const from = stableBeamsStart.from !== undefined ? stableBeamsStart.from : 0; + const to = stableBeamsStart.to !== undefined ? stableBeamsStart.to : new Date().getTime(); + queryBuilder.where('stableBeamsStart').between(from, to); + } + + if (stableBeamsEnd) { + const from = stableBeamsEnd.from !== undefined ? stableBeamsEnd.from : 0; + const to = stableBeamsEnd.to !== undefined ? stableBeamsEnd.to : new Date().getTime(); + queryBuilder.where('stableBeamsEnd').between(from, to); + } + if (fillNumbers) { const fillNumberCriteria = splitStringToStringsTrimmed(fillNumbers, SEARCH_ITEMS_SEPARATOR); diff --git a/test/api/lhcFills.test.js b/test/api/lhcFills.test.js index f468e1c3de..dd84946b07 100644 --- a/test/api/lhcFills.test.js +++ b/test/api/lhcFills.test.js @@ -503,6 +503,7 @@ module.exports = () => { }); }); + 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') @@ -519,6 +520,177 @@ module.exports = () => { done(); }); }); + + it('should return 400 when stableBeamEnd filter "from" is greater than the current time', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsEnd][from]=2647867600000') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors: [error] } = res.body; + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal('"query.filter.stableBeamsEnd.from" must be less than "now"'); + done() + }); + }); + + it('should return 400 when stableBeamStart filter "from" is greater than the current time', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][from]=2647867600000') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors: [error] } = res.body; + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal('"query.filter.stableBeamsStart.from" must be less than "now"'); + done() + }); + }); + + it('should return 400 when stableBeamEnd filter "from" is greater than "to"', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsEnd][from]=1647867699999&filter[stableBeamsEnd][to]=1647867600000') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors: [error] } = res.body; + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal('"query.filter.stableBeamsEnd.to" must be greater than "ref:from"'); + done() + }); + }); + + it('should return 400 when stableBeamStart filters are strings', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][from]=bogus&filter[stableBeamsStart][to]=bogus') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors } = res.body; + + expect(errors.map(e => e.detail)).to.have.members([ + '"query.filter.stableBeamsStart.from" must be a valid date', + '"query.filter.stableBeamsStart.to" must be a valid date', + ]); + + expect(errors.every(e => e.title === 'Invalid Attribute')).to.be.true; + done() + }); + }); + + it('should return 400 when stableBeamEnd filters are strings', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsEnd][from]=bogus&filter[stableBeamsEnd][to]=bogus') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors } = res.body; + + expect(errors.map(e => e.detail)).to.have.members([ + '"query.filter.stableBeamsEnd.from" must be a valid date', + '"query.filter.stableBeamsEnd.to" must be a valid date', + ]); + + expect(errors.every(e => e.title === 'Invalid Attribute')).to.be.true; + done() + }); + }); + + it('should return 400 when stableBeamStart filter "from" is greater than "to"', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][from]=1647867699999&filter[stableBeamsStart][to]=1647867600000') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + const { errors: [error] } = res.body; + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal('"query.filter.stableBeamsStart.to" must be greater than "ref:from"'); + done() + }); + }); + + it('should return 200 and a LHCFill array for only "from" filters set for stableBeamStart and end', (done) => { + const fromValue = 1647867600000; + + request(server) + .get(`/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][from]=${fromValue}&filter[stableBeamsEnd][from]=${fromValue}`) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(3); + res.body.data.forEach(fill => { + expect(fill.stableBeamsStart).to.be.at.least(fromValue); + expect(fill.stableBeamsEnd).to.be.at.least(fromValue); + }); + + done(); + }); + }); + + it('should return 200 and a LHCFill array for only "to" filters set for stableBeamStart and end', (done) => { + const toValue = 2000000000000; + + request(server) + .get(`/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][to]=${toValue}&filter[stableBeamsEnd][to]=${toValue}`) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(4); + + res.body.data.forEach(fill => { + expect(fill.stableBeamsStart).to.be.at.most(toValue); + expect(fill.stableBeamsEnd).to.be.at.most(toValue); + }); + done(); + }); + }); + + it('should return 200 and a LHCFill array for stableBeamStart and end filter set', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[stableBeamsStart][from]=1647867600000&filter[stableBeamsStart][to]=1647867600001&filter[stableBeamsEnd][from]=1647961200000&filter[stableBeamsEnd][to]=1647961200001') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(3); + done(); + }); + }); it('should return 200 and an LHCFill array for beam types filter, correct', (done) => { request(server) diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index 852c559da3..8fbb5f2781 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -317,4 +317,60 @@ module.exports = () => { expect(lhcFills).to.be.an('array').and.lengthOf(0) }) + + it('should return an array with only \'from\' values given', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + from: 1647867600000, + }, + stableBeamsEnd: { + from: 1647867600000, + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(3); + }); + + it('should return an array with only \'to\' values given', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + to: 2000000000000 + }, + stableBeamsEnd: { + to: 2000000000000 + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(4); + }); + + it('should return an array with fills on certain timestamps', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + from: 1647867600000, + to: 1647867600000, + }, + stableBeamsEnd: { + from: 1647961200000, + to: 1647961200000, + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(3); + }); }; diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index 2d1f72bb28..dca414570f 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -26,6 +26,8 @@ const { openFilteringPanel, expectAttributeValue, fillInput, + getPeriodInputsSelectors, + getPopoverSelector, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -161,16 +163,19 @@ module.exports = () => { }); it('fill dropdown menu should be correct', async() => { - // activate the popover - await pressElement(page, `#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)`) - await page.waitForSelector(`body > div:nth-child(3) > div:nth-child(1)`); - await expectInnerText(page, `#copy-6 > div:nth-child(1)`, 'Copy Fill Number') + const popoverTrigger = '#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)'; - await expectLink(page, 'body > div:nth-child(4) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > a:nth-child(3)', { + await pressElement(page, popoverTrigger); + await expectInnerText(page, '#copy-6 > div:nth-child(1)', 'Copy Fill Number'); + + const popoverSelector = await getPopoverSelector(await page.waitForSelector(popoverTrigger)); + + + await expectLink(page, `${popoverSelector} a:nth-of-type(2)`, { href: `http://localhost:4000/?page=log-create&lhcFillNumbers=6`, innerText: ' Add log to this fill' }) // disable the popover - await pressElement(page, `#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)`) + await pressElement(page, popoverTrigger) }) it('can set how many lhcFills are available per page', async () => { @@ -272,12 +277,14 @@ 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 filterSBStartExpect = {selector: 'div.items-baseline:nth-child(2) > div:nth-child(1)', value: 'SB START'}; + const filterSBEndExpect = {selector: 'div.items-baseline:nth-child(3) > div:nth-child(1)', value: 'SB END'}; + const filterSBDurationExpect = {selector: 'div.items-baseline:nth-child(5) > div:nth-child(1)', value: 'SB Duration'}; const filterSBDurationPlaceholderExpect = {selector: '#beam-duration-filter-operand', 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 filterRunDurationExpect = {selector: 'div.flex-row:nth-child(6) > div:nth-child(1)', value: 'Total runs duration'} const filterRunDurationPlaceholderExpect = {selector: '#run-duration-filter-operand', value: 'e.g 16:14:15 (HH:MM:SS)'}; const filterSBDurationOperatorExpect = { value: true }; - const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(5) > div:nth-child(1)', value: 'Beam Type'} + const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(7) > div:nth-child(1)', value: 'Beam Type'} const filterSchemeNamePlaceholderExpect = {selector: '.fillingSchemeName-filter input', value: 'e.g. Single_12b_8_1024_8_2018'} await goToPage(page, 'lhc-fill-overview'); @@ -287,6 +294,8 @@ module.exports = () => { expect(await page.evaluate(() => document.querySelector('#beam-duration-filter-operator > option:nth-child(3)').selected)).to.equal(filterSBDurationOperatorExpect.value); await expectInnerText(page, filterSBExpect.selector, filterSBExpect.value); await expectInnerText(page, filterFillNRExpect.selector, filterFillNRExpect.value); + await expectInnerText(page, filterSBStartExpect.selector, filterSBStartExpect.value); + await expectInnerText(page, filterSBEndExpect.selector, filterSBEndExpect.value); await expectInnerText(page, filterSBDurationExpect.selector, filterSBDurationExpect.value); await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value); await expectInnerText(page, filterRunDurationExpect.selector, filterRunDurationExpect.value); @@ -354,6 +363,47 @@ module.exports = () => { await waitForTableLength(page, 2); }); + it('should successfully apply stableBeamStart filter', async () => { + const popoverTrigger = '.stableBeamsStart-filter .popover-trigger'; + + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + await page.waitForSelector('.column-stableBeamsStart'); + await openFilteringPanel(page); + + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + + await fillInput(page, fromDateSelector, '2019-08-08', ['change']); + await fillInput(page, toDateSelector, '2019-08-08', ['change']); + await fillInput(page, fromTimeSelector, '10:00', ['change']); + await fillInput(page, toTimeSelector, '12:00', ['change']); + + await openFilteringPanel(page); + await pressElement(page, popoverTrigger); + await waitForTableLength(page, 1); + }); + + it('should successfully apply stableBeamEnd filter', async () => { + const popoverTrigger = '.stableBeamsEnd-filter .popover-trigger'; + + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + await page.waitForSelector('.column-stableBeamsEnd'); + await openFilteringPanel(page); + + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + + await fillInput(page, fromDateSelector, '2022-03-22', ['change']); + await fillInput(page, toDateSelector, '2022-03-22', ['change']); + await fillInput(page, fromTimeSelector, '01:00', ['change']); + await fillInput(page, toTimeSelector, '23:59', ['change']); + await openFilteringPanel(page); + await pressElement(page, popoverTrigger); + await waitForTableLength(page, 3); + }); + it('should successfully apply scheme name filter', async () => { const filterSchemeNameInputField= '.fillingSchemeName-filter input'; await goToPage(page, 'lhc-fill-overview'); From f38f597931600d2ad5b75f4691f42597800754a1 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:13:27 +0100 Subject: [PATCH 026/115] [O2B-1544] Fix pagination for filtered envs and add a test (#2096) * Replaced the two-query pattern with a single queryBuilder in GetAllEnvironmentsUseCase. The previous approach was redundant following Sequelize performance improvements; furthermore, the original implementation's logic was flawed which resulted in the pagination bug. --- .../environment/GetAllEnvironmentsUseCase.js | 47 ++++++------------- .../GetAllEnvironmentsUseCase.test.js | 36 ++++++++++++++ 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/lib/usecases/environment/GetAllEnvironmentsUseCase.js b/lib/usecases/environment/GetAllEnvironmentsUseCase.js index c742c53b62..83366aff4e 100644 --- a/lib/usecases/environment/GetAllEnvironmentsUseCase.js +++ b/lib/usecases/environment/GetAllEnvironmentsUseCase.js @@ -69,18 +69,11 @@ class GetAllEnvironmentsUseCase { const { filter, page = {} } = query; const { limit = ApiConfig.pagination.limit, offset = 0 } = page; - /** - * Prepare a query builder with ordering, limit and offset - * - * @return {QueryBuilder} the created query builder - */ - const prepareQueryBuilder = () => dataSource.createQueryBuilder() + const queryBuilder = dataSource.createQueryBuilder() .orderBy('updatedAt', 'desc') .limit(limit) .offset(offset); - const fetchQueryBuilder = prepareQueryBuilder(); - if (filter) { const { ids: idsExpression, @@ -90,12 +83,10 @@ class GetAllEnvironmentsUseCase { created, } = filter; - const filterQueryBuilder = prepareQueryBuilder(); - if (created) { const from = created.from !== undefined ? created.from : 0; const to = created.to !== undefined ? created.to : Date.now(); - filterQueryBuilder.where('createdAt').between(from, to); + queryBuilder.where('createdAt').between(from, to); } if (idsExpression) { @@ -103,12 +94,12 @@ class GetAllEnvironmentsUseCase { // Filter should be like with only one filter if (filters.length === 1) { - filterQueryBuilder.where('id').substring(filters[0]); + queryBuilder.where('id').substring(filters[0]); } // Filters should be exact with more than one filter if (filters.length > 1) { - filterQueryBuilder.andWhere({ id: { [Op.in]: filters } }); + queryBuilder.andWhere({ id: { [Op.in]: filters } }); } } @@ -116,12 +107,12 @@ class GetAllEnvironmentsUseCase { const filters = currentStatusExpression.split(',').map((status) => status.trim()); // Filter the environments by current status using the subquery - filterQueryBuilder.literalWhere( + queryBuilder.literalWhere( `${ENVIRONMENT_LATEST_HISTORY_ITEM_SUBQUERY} IN (:filters)`, { filters }, ); - filterQueryBuilder.includeAttribute({ + queryBuilder.includeAttribute({ query: ENVIRONMENT_LATEST_HISTORY_ITEM_SUBQUERY, alias: 'currentStatus', }); @@ -157,7 +148,7 @@ class GetAllEnvironmentsUseCase { * Use OR condition to match subsequences ending with either DESTROYED or DONE * Filter the environments by using LIKE for subsequence matching */ - filterQueryBuilder.literalWhere( + queryBuilder.literalWhere( `(${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDestroyed OR ` + `${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDone)`, { @@ -166,17 +157,17 @@ class GetAllEnvironmentsUseCase { }, ); - filterQueryBuilder.includeAttribute({ + queryBuilder.includeAttribute({ query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY, alias: 'statusHistory', }); } else { - filterQueryBuilder.literalWhere( + queryBuilder.literalWhere( `${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFilters`, { statusFilters: `%${statusFilters.join(',')}%` }, ); - filterQueryBuilder.includeAttribute({ + queryBuilder.includeAttribute({ query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY, alias: 'statusHistory', }); @@ -190,7 +181,7 @@ class GetAllEnvironmentsUseCase { // Check that the final run numbers list contains at least one valid run number if (finalRunNumberList.length > 0) { - filterQueryBuilder.include({ + queryBuilder.include({ association: 'runs', where: { // Filter should be like with only one filter and exact with more than one filter @@ -198,22 +189,12 @@ class GetAllEnvironmentsUseCase { }, }); } - }; - - const filteredEnvironmentsIds = (await EnvironmentRepository.findAll(filterQueryBuilder)).map(({ id }) => id); - // If no environments match the filter, return an empty result - if (filteredEnvironmentsIds.length === 0) { - return { - count: 0, - environments: [], - }; } - fetchQueryBuilder.where('id').oneOf(filteredEnvironmentsIds); } - fetchQueryBuilder.include({ association: 'runs' }); - fetchQueryBuilder.include({ association: 'historyItems' }); - const { count, rows } = await EnvironmentRepository.findAndCountAll(fetchQueryBuilder); + queryBuilder.include({ association: 'runs' }); + queryBuilder.include({ association: 'historyItems' }); + const { count, rows } = await EnvironmentRepository.findAndCountAll(queryBuilder); return { count, environments: rows.map((environment) => environmentAdapter.toEntity(environment)), diff --git a/test/lib/usecases/environment/GetAllEnvironmentsUseCase.test.js b/test/lib/usecases/environment/GetAllEnvironmentsUseCase.test.js index 96b4ee1c11..5f1e816571 100644 --- a/test/lib/usecases/environment/GetAllEnvironmentsUseCase.test.js +++ b/test/lib/usecases/environment/GetAllEnvironmentsUseCase.test.js @@ -225,4 +225,40 @@ module.exports = () => { expect(environments).to.be.an('array'); expect(environments.length).to.be.equal(0); // Environments from seeders }); + + it('should return correct total count and all filtered results across pages', async () => { + const totalMatchingFilter = 6; // 'RUNNING, ERROR' matches 6 environments at this point + const limit = 2; + + // First page + getAllEnvsDto.query = { page: { limit, offset: 0 }, filter: { currentStatus: 'RUNNING, ERROR' } }; + const page1 = await new GetAllEnvironmentsUseCase().execute(getAllEnvsDto); + + expect(page1.count).to.be.equal(totalMatchingFilter); + expect(page1.environments).to.be.an('array'); + expect(page1.environments.length).to.be.equal(limit); + + // Second page + getAllEnvsDto.query = { page: { limit, offset: 2 }, filter: { currentStatus: 'RUNNING, ERROR' } }; + const page2 = await new GetAllEnvironmentsUseCase().execute(getAllEnvsDto); + + expect(page2.count).to.be.equal(totalMatchingFilter); + expect(page2.environments).to.be.an('array'); + expect(page2.environments.length).to.be.equal(limit); + + // Third page + getAllEnvsDto.query = { page: { limit, offset: 4 }, filter: { currentStatus: 'RUNNING, ERROR' } }; + const page3 = await new GetAllEnvironmentsUseCase().execute(getAllEnvsDto); + + expect(page3.count).to.be.equal(totalMatchingFilter); + expect(page3.environments).to.be.an('array'); + expect(page3.environments.length).to.be.equal(limit); + + // Collect all environment IDs and verify no duplicates and all present + const allIds = [page1, page2, page3].flatMap(({ environments })=> environments.map(({ id }) => id)); + + expect(allIds.length).to.be.equal(totalMatchingFilter); + expect(new Set(allIds).size).to.be.equal(totalMatchingFilter); + expect(allIds).to.have.members(['SomeId', 'newId', 'CmCvjNbg', 'EIDO13i3D', '8E4aZTjY', 'Dxi029djX']); + }); }; From 3efdf1d668376df87e0e5f6477df4a95337b1bfd Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 13:28:24 +0100 Subject: [PATCH 027/115] remove the combination operator from runs --- lib/domain/dtos/GetAllLogsDto.js | 7 ++----- .../Logs/ActiveColumns/logsActiveColumns.js | 2 +- .../views/Logs/Overview/LogsOverviewModel.js | 9 ++++---- lib/usecases/log/GetAllLogsUseCase.js | 21 +++++-------------- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/lib/domain/dtos/GetAllLogsDto.js b/lib/domain/dtos/GetAllLogsDto.js index 8f6be452d7..3ac480990e 100644 --- a/lib/domain/dtos/GetAllLogsDto.js +++ b/lib/domain/dtos/GetAllLogsDto.js @@ -19,10 +19,7 @@ const { TagsFilterDto } = require('./filters/TagsFilterDto.js'); const { FromToFilterDto } = require('./filters/FromToFilterDto.js'); const { EnvironmentsFilterDto } = require('./filters/EnvironmentsFilterDto'); -const RunFilterDto = Joi.object({ - values: CustomJoi.stringArray().items(EntityIdDto).single().required(), - operation: Joi.string().valid('and', 'or').required(), -}); +const RunFilterDto = CustomJoi.stringArray().items(EntityIdDto).single().required(); const LhcFillFilterDto = Joi.object({ values: CustomJoi.stringArray().items(EntityIdDto).single().required(), @@ -36,7 +33,7 @@ const FilterDto = Joi.object({ created: FromToFilterDto, tags: TagsFilterDto, lhcFills: LhcFillFilterDto, - run: RunFilterDto, + runNumbers: RunFilterDto, origin: Joi.string() .valid('human', 'process'), parentLog: EntityIdDto, diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 21402ca680..1f941cd038 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -182,7 +182,7 @@ export const logsActiveColumns = { * @return {Component} the filter component */ filter: ({ filteringModel }) => rawTextFilter( - filteringModel.get('run'), + filteringModel.get('runNumbers'), { id: 'runsFilterText', classes: ['w-75', 'mt1'], diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index d85021f607..190ff480fe 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -43,7 +43,7 @@ export class LogsOverviewModel extends Observable { titleFilter: new RawTextFilterModel(), contentFilter: new RawTextFilterModel(), tags: new TagFilterModel(tagsProvider.items$), - run: new RawTextFilterModel(), + runNumbers: new RawTextFilterModel(), environments: new RawTextFilterModel(), lhcFills: new RawTextFilterModel(), created: new TimeRangeInputModel(), @@ -198,7 +198,7 @@ export class LogsOverviewModel extends Observable { const contentFilter = this._filteringModel.get('contentFilter'); const authorFilter = this._filteringModel.get('authorFilter'); const tags = this._filteringModel.get('tags'); - const run = this._filteringModel.get('run'); + const runNumbers = this._filteringModel.get('runNumbers'); const environments = this._filteringModel.get('environments'); const lhcFills = this._filteringModel.get('lhcFills'); const created = this._filteringModel.get('created'); @@ -221,9 +221,8 @@ export class LogsOverviewModel extends Observable { 'filter[tags][values]': tags.selected.join(), 'filter[tags][operation]': tags.combinationOperator.toLowerCase(), }, - ...!run.isEmpty && { - 'filter[run][values]': run.normalized, - 'filter[run][operation]': this.runFilterOperation.toLowerCase(), + ...!runNumbers.isEmpty && { + 'filter[runNumbers]': runNumbers.normalized, }, ...!environments.isEmpty && { 'filter[environments][values]': environments.normalized, diff --git a/lib/usecases/log/GetAllLogsUseCase.js b/lib/usecases/log/GetAllLogsUseCase.js index b1f7ea72b5..013eb98953 100644 --- a/lib/usecases/log/GetAllLogsUseCase.js +++ b/lib/usecases/log/GetAllLogsUseCase.js @@ -39,7 +39,7 @@ const { checkForFilterExclusion } = require('../common/checkForFilterExclusion.j * @return {Promise} resolves once the filter has been applied */ const applyFilter = async (dataSource, queryBuilder, filter) => { - const { title, content, author, created, origin, parentLog, rootLog, rootOnly } = filter; + const { title, content, author, created, origin, parentLog, rootLog, rootOnly, runNumbers } = filter; if (title) { queryBuilder.where('title').substring(title); @@ -112,26 +112,15 @@ const applyFilter = async (dataSource, queryBuilder, filter) => { queryBuilder.where('id').oneOf(...logIds); } - if (filter.run?.values?.length > 0) { + if (runNumbers.length > 0) { const runQueryBuilder = dataSource.createQueryBuilder(); runQueryBuilder.include({ association: 'run', - where: { runNumber: { [Op.in]: filter.run.values } }, + where: { runNumber: { [Op.in]: runNumbers } }, }).orderBy('logId', 'asc'); - let logRuns; - switch (filter.run.operation) { - case 'and': - logRuns = await LogRunsRepository - .findAllAndGroup(runQueryBuilder); - logRuns = logRuns - .filter((logRun) => filter.run.values.every((runNumber) => logRun.runNumbers.includes(runNumber))); - break; - case 'or': - logRuns = await LogRunsRepository - .findAll(runQueryBuilder); - break; - } + let logRuns = await LogRunsRepository.findAllAndGroup(runQueryBuilder); + logRuns = logRuns.filter((logRun) => runNumbers.every((runNumber) => logRun.runNumbers.includes(runNumber))); const logIds = logRuns.map((logRun) => logRun.logId); queryBuilder.where('id').oneOf(...logIds); From fc61bea32380ee4074041cce7e461630016b1421 Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 13:57:42 +0100 Subject: [PATCH 028/115] remove the combination operator from envirionments --- lib/domain/dtos/GetAllLogsDto.js | 6 ++--- .../dtos/filters/EnvironmentsFilterDto.js | 20 ----------------- .../Logs/ActiveColumns/logsActiveColumns.js | 2 +- .../views/Logs/Overview/LogsOverviewModel.js | 11 ++++------ lib/usecases/log/GetAllLogsUseCase.js | 22 ++++++------------- 5 files changed, 15 insertions(+), 46 deletions(-) delete mode 100644 lib/domain/dtos/filters/EnvironmentsFilterDto.js diff --git a/lib/domain/dtos/GetAllLogsDto.js b/lib/domain/dtos/GetAllLogsDto.js index 3ac480990e..02949589da 100644 --- a/lib/domain/dtos/GetAllLogsDto.js +++ b/lib/domain/dtos/GetAllLogsDto.js @@ -17,9 +17,9 @@ const PaginationDto = require('./PaginationDto'); const { CustomJoi } = require('./CustomJoi.js'); const { TagsFilterDto } = require('./filters/TagsFilterDto.js'); const { FromToFilterDto } = require('./filters/FromToFilterDto.js'); -const { EnvironmentsFilterDto } = require('./filters/EnvironmentsFilterDto'); -const RunFilterDto = CustomJoi.stringArray().items(EntityIdDto).single().required(); +const RunFilterDto = CustomJoi.stringArray().items(EntityIdDto).single(); +const EnvironmentsFilterDto = CustomJoi.stringArray().items(Joi.string()).single(); const LhcFillFilterDto = Joi.object({ values: CustomJoi.stringArray().items(EntityIdDto).single().required(), @@ -39,7 +39,7 @@ const FilterDto = Joi.object({ parentLog: EntityIdDto, rootLog: EntityIdDto, rootOnly: Joi.boolean(), - environments: EnvironmentsFilterDto, + environmentIds: EnvironmentsFilterDto, }); const SortDto = Joi.object({ diff --git a/lib/domain/dtos/filters/EnvironmentsFilterDto.js b/lib/domain/dtos/filters/EnvironmentsFilterDto.js deleted file mode 100644 index 3baa97a747..0000000000 --- a/lib/domain/dtos/filters/EnvironmentsFilterDto.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @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. - */ - -const Joi = require('joi'); -const { CustomJoi } = require('../CustomJoi.js'); - -exports.EnvironmentsFilterDto = Joi.object({ - values: CustomJoi.stringArray().items(Joi.string()).single().required(), - operation: Joi.string().valid('and', 'or').required(), -}); diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 1f941cd038..3d71b61942 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -214,7 +214,7 @@ export const logsActiveColumns = { * @return {Component} the filter component */ filter: ({ filteringModel }) => rawTextFilter( - filteringModel.get('environments'), + filteringModel.get('environmentIds'), { id: 'environmentFilterText', classes: ['w-75', 'mt1'], diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 190ff480fe..8a6940ff7b 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -44,7 +44,7 @@ export class LogsOverviewModel extends Observable { contentFilter: new RawTextFilterModel(), tags: new TagFilterModel(tagsProvider.items$), runNumbers: new RawTextFilterModel(), - environments: new RawTextFilterModel(), + environmentIds: new RawTextFilterModel(), lhcFills: new RawTextFilterModel(), created: new TimeRangeInputModel(), }); @@ -128,8 +128,6 @@ export class LogsOverviewModel extends Observable { reset(fetch = true) { this._filteringModel.reset(); - this.runFilterOperation = 'AND'; - this.environmentFilterOperation = 'AND'; this.lhcFillFilterOperation = 'AND'; this._pagination.reset(); @@ -199,7 +197,7 @@ export class LogsOverviewModel extends Observable { const authorFilter = this._filteringModel.get('authorFilter'); const tags = this._filteringModel.get('tags'); const runNumbers = this._filteringModel.get('runNumbers'); - const environments = this._filteringModel.get('environments'); + const environmentIds = this._filteringModel.get('environmentIds'); const lhcFills = this._filteringModel.get('lhcFills'); const created = this._filteringModel.get('created'); @@ -224,9 +222,8 @@ export class LogsOverviewModel extends Observable { ...!runNumbers.isEmpty && { 'filter[runNumbers]': runNumbers.normalized, }, - ...!environments.isEmpty && { - 'filter[environments][values]': environments.normalized, - 'filter[environments][operation]': this.environmentFilterOperation.toLowerCase(), + ...!environmentIds.isEmpty && { + 'filter[environmentIds]': environmentIds.normalized, }, ...!lhcFills.isEmpty && { 'filter[lhcFills][values]': lhcFills.normalized, diff --git a/lib/usecases/log/GetAllLogsUseCase.js b/lib/usecases/log/GetAllLogsUseCase.js index 013eb98953..0d1df29790 100644 --- a/lib/usecases/log/GetAllLogsUseCase.js +++ b/lib/usecases/log/GetAllLogsUseCase.js @@ -39,7 +39,7 @@ const { checkForFilterExclusion } = require('../common/checkForFilterExclusion.j * @return {Promise} resolves once the filter has been applied */ const applyFilter = async (dataSource, queryBuilder, filter) => { - const { title, content, author, created, origin, parentLog, rootLog, rootOnly, runNumbers } = filter; + const { title, content, author, created, origin, parentLog, rootLog, rootOnly, runNumbers, environmentIds } = filter; if (title) { queryBuilder.where('title').substring(title); @@ -112,7 +112,7 @@ const applyFilter = async (dataSource, queryBuilder, filter) => { queryBuilder.where('id').oneOf(...logIds); } - if (runNumbers.length > 0) { + if (runNumbers?.length > 0) { const runQueryBuilder = dataSource.createQueryBuilder(); runQueryBuilder.include({ association: 'run', @@ -150,25 +150,17 @@ const applyFilter = async (dataSource, queryBuilder, filter) => { queryBuilder.where('id').oneOf(...logIds); } - if (filter.environments?.values?.length > 0) { - const validEnvironments = await EnvironmentRepository.findAll({ where: { id: { [Op.in]: filter.environments.values } } }); + if (environmentIds?.length > 0) { + const validEnvironments = await EnvironmentRepository.findAll({ where: { id: { [Op.in]: environmentIds} } }); const logEnvironmentQueryBuilder = dataSource.createQueryBuilder() .where('environmentId') .oneOf(...validEnvironments.map(({ id }) => id)) .orderBy('logId', 'asc'); - let logIds; - switch (filter.environments.operation) { - case 'and': - logIds = groupByProperty(await LogEnvironmentsRepository.findAll(logEnvironmentQueryBuilder), 'logId') - .filter(({ values }) => validEnvironments.every((env) => values.some((item) => item.environmentId === env.id))) - .map(({ index }) => index); - break; - case 'or': - logIds = (await LogEnvironmentsRepository.findAll(logEnvironmentQueryBuilder)).map(({ logId }) => logId); - break; - } + const logIds = groupByProperty(await LogEnvironmentsRepository.findAll(logEnvironmentQueryBuilder), 'logId') + .filter(({ values }) => validEnvironments.every((env) => values.some((item) => item.environmentId === env.id))) + .map(({ index }) => index); queryBuilder.where('id').oneOf(...logIds); } From 9c4df68f0d91a354226a94437773a50e36d46d23 Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 14:09:45 +0100 Subject: [PATCH 029/115] remove the combination operator from lhcFills --- lib/domain/dtos/GetAllLogsDto.js | 8 ++--- .../Logs/ActiveColumns/logsActiveColumns.js | 2 +- .../views/Logs/Overview/LogsOverviewModel.js | 9 +++--- lib/usecases/log/GetAllLogsUseCase.js | 32 +++++++++++-------- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/domain/dtos/GetAllLogsDto.js b/lib/domain/dtos/GetAllLogsDto.js index 02949589da..7a0ef08306 100644 --- a/lib/domain/dtos/GetAllLogsDto.js +++ b/lib/domain/dtos/GetAllLogsDto.js @@ -20,11 +20,7 @@ const { FromToFilterDto } = require('./filters/FromToFilterDto.js'); const RunFilterDto = CustomJoi.stringArray().items(EntityIdDto).single(); const EnvironmentsFilterDto = CustomJoi.stringArray().items(Joi.string()).single(); - -const LhcFillFilterDto = Joi.object({ - values: CustomJoi.stringArray().items(EntityIdDto).single().required(), - operation: Joi.string().valid('and', 'or').required(), -}); +const LhcFillFilterDto = CustomJoi.stringArray().items(EntityIdDto).single(); const FilterDto = Joi.object({ title: Joi.string().trim(), @@ -32,7 +28,7 @@ const FilterDto = Joi.object({ author: Joi.string().trim(), created: FromToFilterDto, tags: TagsFilterDto, - lhcFills: LhcFillFilterDto, + fillNumbers: LhcFillFilterDto, runNumbers: RunFilterDto, origin: Joi.string() .valid('human', 'process'), diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 3d71b61942..46486e5f02 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -239,7 +239,7 @@ export const logsActiveColumns = { * @return {Component} the filter component */ filter: ({ filteringModel }) => rawTextFilter( - filteringModel.get('lhcFills'), + filteringModel.get('fillNumbers'), { id: 'lhcFillsFilterText', classes: ['w-75', 'mt1'], diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 8a6940ff7b..e1f11054bd 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -45,7 +45,7 @@ export class LogsOverviewModel extends Observable { tags: new TagFilterModel(tagsProvider.items$), runNumbers: new RawTextFilterModel(), environmentIds: new RawTextFilterModel(), - lhcFills: new RawTextFilterModel(), + fillNumbers: new RawTextFilterModel(), created: new TimeRangeInputModel(), }); @@ -198,7 +198,7 @@ export class LogsOverviewModel extends Observable { const tags = this._filteringModel.get('tags'); const runNumbers = this._filteringModel.get('runNumbers'); const environmentIds = this._filteringModel.get('environmentIds'); - const lhcFills = this._filteringModel.get('lhcFills'); + const fillNumbers = this._filteringModel.get('fillNumbers'); const created = this._filteringModel.get('created'); return { @@ -225,9 +225,8 @@ export class LogsOverviewModel extends Observable { ...!environmentIds.isEmpty && { 'filter[environmentIds]': environmentIds.normalized, }, - ...!lhcFills.isEmpty && { - 'filter[lhcFills][values]': lhcFills.normalized, - 'filter[lhcFills][operation]': this.lhcFillFilterOperation.toLowerCase(), + ...!fillNumbers.isEmpty && { + 'filter[fillNumbers]': fillNumbers.normalized, }, }; } diff --git a/lib/usecases/log/GetAllLogsUseCase.js b/lib/usecases/log/GetAllLogsUseCase.js index 0d1df29790..c1972de498 100644 --- a/lib/usecases/log/GetAllLogsUseCase.js +++ b/lib/usecases/log/GetAllLogsUseCase.js @@ -39,7 +39,19 @@ const { checkForFilterExclusion } = require('../common/checkForFilterExclusion.j * @return {Promise} resolves once the filter has been applied */ const applyFilter = async (dataSource, queryBuilder, filter) => { - const { title, content, author, created, origin, parentLog, rootLog, rootOnly, runNumbers, environmentIds } = filter; + const { + title, + content, + author, + created, + origin, + parentLog, + rootLog, + rootOnly, + runNumbers, + environmentIds, + fillNumbers, + } = filter; if (title) { queryBuilder.where('title').substring(title); @@ -126,24 +138,16 @@ const applyFilter = async (dataSource, queryBuilder, filter) => { queryBuilder.where('id').oneOf(...logIds); } - if (filter.lhcFills?.values?.length > 0) { + if (fillNumbers?.length > 0) { const logLhcFillQueryBuilder = dataSource.createQueryBuilder(); logLhcFillQueryBuilder.include({ association: 'lhcFill', - where: { fill_number: { [Op.in]: filter.lhcFills.values } }, + where: { fill_number: { [Op.in]: fillNumbers } }, }).orderBy('logId', 'asc'); - let logLhcFills; - switch (filter.lhcFills.operation) { - case 'and': - logLhcFills = await LogLhcFillsRepository.findAllAndGroup(logLhcFillQueryBuilder); - logLhcFills = logLhcFills - .filter((logLhcFill) => filter.lhcFills.values.every((fillNumber) => logLhcFill.fillNumbers.includes(fillNumber))); - break; - case 'or': - logLhcFills = await LogLhcFillsRepository.findAll(logLhcFillQueryBuilder); - break; - } + let logLhcFills = await LogLhcFillsRepository.findAllAndGroup(logLhcFillQueryBuilder); + logLhcFills = logLhcFills.filter((logLhcFill) => + fillNumbers.every((fillNumber) => logLhcFill.fillNumbers.includes(fillNumber))); const logIds = logLhcFills.map((logLhcFill) => logLhcFill.logId); From b860320b8ec411a0170d3953265cd410cc135a5b Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 14:40:27 +0100 Subject: [PATCH 030/115] make filter computation much more compact using filteringmodel.normalize --- .../Filters/LogsFilter/author/authorFilter.js | 8 +-- .../Logs/ActiveColumns/logsActiveColumns.js | 4 +- .../views/Logs/Overview/LogsOverviewModel.js | 60 ++----------------- lib/public/views/Logs/Overview/index.js | 2 +- 4 files changed, 12 insertions(+), 62 deletions(-) diff --git a/lib/public/components/Filters/LogsFilter/author/authorFilter.js b/lib/public/components/Filters/LogsFilter/author/authorFilter.js index 7cfc2b7d7e..993ce6f957 100644 --- a/lib/public/components/Filters/LogsFilter/author/authorFilter.js +++ b/lib/public/components/Filters/LogsFilter/author/authorFilter.js @@ -48,11 +48,11 @@ export const excludeAnonymousLogAuthorToggle = (authorFilterModel) => switchInpu * @return {Component} the filter component */ export const authorFilter = ({ filteringModel }) => h('.flex-row.items-center.g3', [ - rawTextFilter(filteringModel.get('authorFilter'), { + rawTextFilter(filteringModel.get('author'), { classes: ['w-40'], id: 'authorFilterText', - value: filteringModel.get('authorFilter').raw, + value: filteringModel.get('author').raw, }), - resetAuthorFilterButton(filteringModel.get('authorFilter')), - excludeAnonymousLogAuthorToggle(filteringModel.get('authorFilter')), + resetAuthorFilterButton(filteringModel.get('author')), + excludeAnonymousLogAuthorToggle(filteringModel.get('author')), ]); diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index 46486e5f02..fd91851980 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -77,7 +77,7 @@ export const logsActiveColumns = { * @return {Component} the filter component */ filter: ({ filteringModel }) => rawTextFilter( - filteringModel.get('titleFilter'), + filteringModel.get('title'), { id: 'titleFilterText', class: 'w-75 mt1', @@ -106,7 +106,7 @@ export const logsActiveColumns = { * @return {Component} the filter component */ filter: ({ filteringModel }) => rawTextFilter( - filteringModel.get('contentFilter'), + filteringModel.get('content'), { id: 'contentFilterText', classes: ['w-75', 'mt1'], diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index e1f11054bd..4cb565e9b9 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -39,9 +39,9 @@ export class LogsOverviewModel extends Observable { super(); this._filteringModel = new FilteringModel({ - authorFilter: new AuthorFilterModel(), - titleFilter: new RawTextFilterModel(), - contentFilter: new RawTextFilterModel(), + author: new AuthorFilterModel(), + title: new RawTextFilterModel(), + content: new RawTextFilterModel(), tags: new TagFilterModel(tagsProvider.items$), runNumbers: new RawTextFilterModel(), environmentIds: new RawTextFilterModel(), @@ -69,7 +69,7 @@ export class LogsOverviewModel extends Observable { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); - excludeAnonymous && this._filteringModel.get('authorFilter').update('!Anonymous'); + excludeAnonymous && this._filteringModel.get('author').update('!Anonymous'); this.reset(false); } @@ -92,7 +92,7 @@ export class LogsOverviewModel extends Observable { ...sortOn && sortDirection && { [`sort[${sortOn}]`]: sortDirection, }, - ...this._getFilterQueryParams(), + filter: this._filteringModel.normalized, 'page[offset]': this._pagination.firstItemOffset, 'page[limit]': this._pagination.itemsPerPage, }; @@ -127,9 +127,6 @@ export class LogsOverviewModel extends Observable { */ reset(fetch = true) { this._filteringModel.reset(); - - this.lhcFillFilterOperation = 'AND'; - this._pagination.reset(); if (fetch) { @@ -183,51 +180,4 @@ export class LogsOverviewModel extends Observable { this._pagination.silentlySetCurrentPage(1); now ? this.fetchLogs() : this._debouncedFetchAllLogs(); } - - /** - * Returns the list of URL params corresponding to the currently applied filter - * - * @return {Object} the URL params - * - * @private - */ - _getFilterQueryParams() { - const titleFilter = this._filteringModel.get('titleFilter'); - const contentFilter = this._filteringModel.get('contentFilter'); - const authorFilter = this._filteringModel.get('authorFilter'); - const tags = this._filteringModel.get('tags'); - const runNumbers = this._filteringModel.get('runNumbers'); - const environmentIds = this._filteringModel.get('environmentIds'); - const fillNumbers = this._filteringModel.get('fillNumbers'); - const created = this._filteringModel.get('created'); - - return { - ...!titleFilter.isEmpty && { - 'filter[title]': titleFilter.normalized, - }, - ...!contentFilter.isEmpty && { - 'filter[content]': contentFilter.normalized, - }, - ...!authorFilter.isEmpty && { - 'filter[author]': authorFilter.normalized, - }, - ...!created.isEmpty && { - 'filter[created][from]': created.normalized.from, - 'filter[created][to]': created.normalized.to, - }, - ...!tags.isEmpty && { - 'filter[tags][values]': tags.selected.join(), - 'filter[tags][operation]': tags.combinationOperator.toLowerCase(), - }, - ...!runNumbers.isEmpty && { - 'filter[runNumbers]': runNumbers.normalized, - }, - ...!environmentIds.isEmpty && { - 'filter[environmentIds]': environmentIds.normalized, - }, - ...!fillNumbers.isEmpty && { - 'filter[fillNumbers]': fillNumbers.normalized, - }, - }; - } } diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index ed5c7a860c..93f58c8c40 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -39,7 +39,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { h('#main-action-bar.flex-row.justify-between.header-container.pv2', [ h('.flex-row.g3', [ filtersPanelPopover(logsOverviewModel, logsActiveColumns), - excludeAnonymousLogAuthorToggle(logsOverviewModel.filteringModel.get('authorFilter')), + excludeAnonymousLogAuthorToggle(logsOverviewModel.filteringModel.get('author')), ]), actionButtons(), ]), From 7a000433572254f753c5379cffdaa2064f632c71 Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 15:05:34 +0100 Subject: [PATCH 031/115] add happy-flow tests for logs api --- test/api/logs.test.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/api/logs.test.js b/test/api/logs.test.js index ada81f070e..02e7dbaeed 100644 --- a/test/api/logs.test.js +++ b/test/api/logs.test.js @@ -233,7 +233,7 @@ module.exports = () => { }); it('should successfully filter by run number', async () => { - const response = await request(server).get('/api/logs?filter[run][values]=1,2&filter[run][operation]=and'); + const response = await request(server).get('/api/logs?filter[runNumbers]=1,2'); expect(response.status).to.equal(200); expect(response.body.data).to.be.an('array'); @@ -244,6 +244,30 @@ module.exports = () => { } }); + it('should successfully filter by lhcFillNumber', async () => { + const response = await request(server).get('/api/logs?filter[fillNumbers]=1,4,6'); + expect(response.status).to.equal(200); + + expect(response.body.data).to.be.an('array'); + expect(response.body.data).to.lengthOf(1); + for (const { lhcFills } of response.body.data) { + const fillNumbers = lhcFills.map(({ fillNumber }) => fillNumber); + expect([1, 4, 6].every((fillNumber) => fillNumbers.includes(fillNumber))).to.be.true; + } + }); + + it('should successfully filter by EnvironmentIds', async () => { + const response = await request(server).get('/api/logs?filter[environmentIds]=Dxi029djX,eZF99lH6'); + expect(response.status).to.equal(200); + + expect(response.body.data).to.be.an('array'); + expect(response.body.data).to.lengthOf(1); + for (const { environments } of response.body.data) { + const environmentIds = environments.map(({ id }) => id); + expect(["Dxi029djX", "eZF99lH6"].every((environmentId) => environmentIds.includes(environmentId))).to.be.true; + } + }); + it('should successfully filter by content', async () => { const response = await request(server).get('/api/logs?filter[content]=particle'); expect(response.status).to.equal(200); From ada3eb3f9372979196d6e3e5370ee3a0d9da4cd5 Mon Sep 17 00:00:00 2001 From: Guust Date: Thu, 5 Mar 2026 15:35:18 +0100 Subject: [PATCH 032/115] fix usecase unit tests --- lib/usecases/log/GetAllLogsUseCase.js | 2 +- .../usecases/log/GetAllLogsUseCase.test.js | 52 ++++--------------- 2 files changed, 10 insertions(+), 44 deletions(-) diff --git a/lib/usecases/log/GetAllLogsUseCase.js b/lib/usecases/log/GetAllLogsUseCase.js index c1972de498..8c69a3c4b8 100644 --- a/lib/usecases/log/GetAllLogsUseCase.js +++ b/lib/usecases/log/GetAllLogsUseCase.js @@ -155,7 +155,7 @@ const applyFilter = async (dataSource, queryBuilder, filter) => { } if (environmentIds?.length > 0) { - const validEnvironments = await EnvironmentRepository.findAll({ where: { id: { [Op.in]: environmentIds} } }); + const validEnvironments = await EnvironmentRepository.findAll({ where: { id: { [Op.in]: environmentIds } } }); const logEnvironmentQueryBuilder = dataSource.createQueryBuilder() .where('environmentId') diff --git a/test/lib/usecases/log/GetAllLogsUseCase.test.js b/test/lib/usecases/log/GetAllLogsUseCase.test.js index 61a402cdb8..d4475d2d60 100644 --- a/test/lib/usecases/log/GetAllLogsUseCase.test.js +++ b/test/lib/usecases/log/GetAllLogsUseCase.test.js @@ -73,7 +73,7 @@ module.exports = () => { it('should successfully filter on run numbers', async () => { const runNumbers = [1, 2]; - getAllLogsDto.query = { filter: { run: { operation: 'and', values: runNumbers } } }; + getAllLogsDto.query = { filter: { runNumbers } }; { const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); @@ -83,17 +83,6 @@ module.exports = () => { expect(runNumbers.every((runNumber) => relatedRunNumbers.includes(runNumber))).to.be.true; } } - - getAllLogsDto.query = { filter: { run: { operation: 'or', values: runNumbers } } }; - - { - const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); - expect(filteredResult).to.lengthOf(6); - for (const log of filteredResult) { - const relatedRunNumbers = log.runs.map(({ runNumber }) => runNumber); - expect(runNumbers.some((runNumber) => relatedRunNumbers.includes(runNumber))).to.be.true; - } - } }); it('should successfully filter on log content', async () => { @@ -117,9 +106,9 @@ module.exports = () => { }); it('should successfully filter on lhc fills', async () => { - const lhcFills = [1, 6]; + const fillNumbers = [1, 6]; - getAllLogsDto.query = { filter: { lhcFills: { operation: 'and', values: lhcFills } } }; + getAllLogsDto.query = { filter: { fillNumbers } }; { const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); expect(filteredResult).to.have.lengthOf(1); @@ -128,47 +117,24 @@ module.exports = () => { // For each returned log, check at least one of the associated fill numbers was in the filter query expect(fillNumbersPerLog.every((logFillNumbers) => - logFillNumbers.includes(lhcFills[0]) && logFillNumbers.includes(lhcFills[1]))).to.be.true; - } - - getAllLogsDto.query = { filter: { lhcFills: { operation: 'or', values: lhcFills } } }; - { - const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); - expect(filteredResult).to.have.lengthOf(3); - - const fillNumbersPerLog = filteredResult.map(({ lhcFills }) => lhcFills.map(({ fillNumber }) => fillNumber)); - - // For each returned log, check at least one of the associated fill numbers was in the filter query - expect(fillNumbersPerLog.every((logFillNumbers) => - logFillNumbers.includes(lhcFills[0]) || logFillNumbers.includes(lhcFills[1]))).to.be.true; + logFillNumbers.includes(fillNumbers[0]) && logFillNumbers.includes(fillNumbers[1]))).to.be.true; } }); it ('should successfully filter on log environment', async () => { - const environments = ['8E4aZTjY', 'eZF99lH6']; - getAllLogsDto.query = { filter: { environments: { operation: 'and', values: environments } } }; + const environmentIds = ['8E4aZTjY', 'eZF99lH6']; + getAllLogsDto.query = { filter: { environmentIds } }; { const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); expect(filteredResult).to.lengthOf(2); for (const log of filteredResult) { - const relatedEnvironments = log.environments.map(({ id }) => id); - expect(environments.every((env) => relatedEnvironments.includes(env))).to.be.true; - } - } - - getAllLogsDto.query = { filter: { environments: { operation: 'or', values: environments } } }; - - { - const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); - expect(filteredResult).to.lengthOf(5); - for (const log of filteredResult) { - const relatedEnvironments = log.environments.map(({ id }) => id); - expect(environments.some((env) => relatedEnvironments.includes(env))).to.be.true; + const relatedenvironmentIds = log.environments.map(({ id }) => id); + expect(environmentIds.every((env) => relatedenvironmentIds.includes(env))).to.be.true; } } - getAllLogsDto.query = { filter: { environments: { operation: 'and', values: ['non-existent-environment'] } } }; + getAllLogsDto.query = { filter: { environmentIds: ['non-existent-environment'] } }; { const { logs: filteredResult } = await new GetAllLogsUseCase().execute(getAllLogsDto); From 25244f483bc04886e69c0f8f2ff6252fb190de0e Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 9 Mar 2026 12:37:04 +0100 Subject: [PATCH 033/115] chore: add filteringmodel to QcFlagTypesOverviewModel --- .../Overview/QcFlagTypesOverviewModel.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 6c80ada996..28c6b81002 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -15,6 +15,7 @@ import { TextTokensFilterModel } from '../../../components/Filters/common/filter import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; import { buildUrl } from '/js/src/index.js'; +import { FilterModel } from '../../../components/Filters/common/FilterModel.js'; /** * QcFlagTypesOverviewModel @@ -26,6 +27,8 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { constructor() { super(); + this._filteringModel = new FilterModel({}); + this._namesFilterModel = new TextTokensFilterModel(); this._registerFilter(this._namesFilterModel); this._methodsFilterModel = new TextTokensFilterModel(); @@ -33,6 +36,9 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { this._isBadFilterModel = new SelectionModel({ availableOptions: [{ label: 'Bad', value: true }, { label: 'Not Bad', value: false }] }); this._registerFilter(this._isBadFilterModel); + + this._filteringModel.observe(() => this._applyFilters()); + this._filteringModel.visualChange$.bubbleTo(this); } /** @@ -53,6 +59,15 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { return buildUrl('/api/qcFlagTypes', params); } + /** + * Return the model managing all filters + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + /** * Get names filter model * From 6970a747fc7a667c237839b59bebc97153bcdb1e Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 9 Mar 2026 14:02:26 +0100 Subject: [PATCH 034/115] feat: move namesFilterModelFilter to fileringmodel --- .../ActiveColumns/qcFlagTypesActiveColumns.js | 4 +-- .../Overview/QcFlagTypesOverviewModel.js | 29 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index 7f4ae8aa69..2251357d59 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -30,8 +30,8 @@ export const qcFlagTypesActiveColumns = { name: { name: 'Name', visible: true, - filter: ({ namesFilterModel }) => textFilter( - namesFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('names'), { class: 'w-75 mt1', placeholder: 'e.g. BadPID, ...' }, ), classes: 'f6', diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 28c6b81002..492b96e567 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -15,7 +15,7 @@ import { TextTokensFilterModel } from '../../../components/Filters/common/filter import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; import { buildUrl } from '/js/src/index.js'; -import { FilterModel } from '../../../components/Filters/common/FilterModel.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; /** * QcFlagTypesOverviewModel @@ -27,17 +27,21 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { constructor() { super(); - this._filteringModel = new FilterModel({}); + this._filteringModel = new FilteringModel({ + names: new TextTokensFilterModel(), + }); - this._namesFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._namesFilterModel); this._methodsFilterModel = new TextTokensFilterModel(); this._registerFilter(this._methodsFilterModel); this._isBadFilterModel = new SelectionModel({ availableOptions: [{ label: 'Bad', value: true }, { label: 'Not Bad', value: false }] }); this._registerFilter(this._isBadFilterModel); - this._filteringModel.observe(() => this._applyFilters()); + this._filteringModel.observe(() => { + this._pagination.silentlySetCurrentPage(1); + this.load(); + }); + this._filteringModel.visualChange$.bubbleTo(this); } @@ -48,7 +52,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { const params = {}; if (this.isAnyFilterActive()) { params.filter = { - names: this._namesFilterModel.normalized, + names: this._filteringModel.get("names").normalized, methods: this._methodsFilterModel.normalized, bad: this._isBadFilterModel.selected.length === 2 ? undefined @@ -68,15 +72,6 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { return this._filteringModel; } - /** - * Get names filter model - * - * @return {TextTokensFilterModel} names filter model - */ - get namesFilterModel() { - return this._namesFilterModel; - } - /** * Get methods filter model * @@ -116,7 +111,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return !this._namesFilterModel.isEmpty || !this._methodsFilterModel.isEmpty || this._isBadFilterModel.selected.length; + return this._filteringModel.isAnyFilterActive() || !this._methodsFilterModel.isEmpty || this._isBadFilterModel.selected.length; } /** @@ -126,7 +121,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { */ reset() { this._methodsFilterModel.reset(); - this._namesFilterModel.reset(); + this._filteringModel.reset(); this._isBadFilterModel.reset(); super.reset(); } From cdaaf0ee42cf6890afcb1c761e2f756cea658be7 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 9 Mar 2026 14:07:30 +0100 Subject: [PATCH 035/115] feat: move methodsFilterModelFilter to filteringModel --- .../ActiveColumns/qcFlagTypesActiveColumns.js | 4 ++-- .../Overview/QcFlagTypesOverviewModel.js | 17 +++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index 2251357d59..c094c06a17 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -43,8 +43,8 @@ export const qcFlagTypesActiveColumns = { name: 'Method', visible: true, sortable: true, - filter: ({ methodsFilterModel }) => textFilter( - methodsFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('methods'), { class: 'w-75 mt1', placeholder: 'e.g. Bad PID, ...' }, ), classes: 'f6', diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 492b96e567..ee4cd096f6 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -29,10 +29,9 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel(), + methods: new TextTokensFilterModel(), }); - this._methodsFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._methodsFilterModel); this._isBadFilterModel = new SelectionModel({ availableOptions: [{ label: 'Bad', value: true }, { label: 'Not Bad', value: false }] }); this._registerFilter(this._isBadFilterModel); @@ -53,7 +52,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { if (this.isAnyFilterActive()) { params.filter = { names: this._filteringModel.get("names").normalized, - methods: this._methodsFilterModel.normalized, + methods: this._filteringModel.get("methods").normalized, bad: this._isBadFilterModel.selected.length === 2 ? undefined : this._isBadFilterModel.selected[0], @@ -72,15 +71,6 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { return this._filteringModel; } - /** - * Get methods filter model - * - * @return {TextTokensFilterModel} methods filter model - */ - get methodsFilterModel() { - return this._methodsFilterModel; - } - /** * Returns filter model for filtering bad and not bad flags * @@ -111,7 +101,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() || !this._methodsFilterModel.isEmpty || this._isBadFilterModel.selected.length; + return this._filteringModel.isAnyFilterActive() || this._isBadFilterModel.selected.length; } /** @@ -120,7 +110,6 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { * @returns {void} */ reset() { - this._methodsFilterModel.reset(); this._filteringModel.reset(); this._isBadFilterModel.reset(); super.reset(); From 27a8bbc645d8e1463bc8daacb841c35828c4f3c2 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 9 Mar 2026 16:56:39 +0100 Subject: [PATCH 036/115] feat: move methodsFilterModelFilter to filteringModel --- .../Filters/QcFlagTypesFilter/bad.js | 36 +++++++++++++ .../common/selection/SelectionModel.js | 12 +++++ .../ActiveColumns/qcFlagTypesActiveColumns.js | 7 +-- .../Overview/QcFlagTypesOverviewModel.js | 50 +++---------------- test/public/qcFlagTypes/overview.test.js | 2 +- 5 files changed, 59 insertions(+), 48 deletions(-) create mode 100644 lib/public/components/Filters/QcFlagTypesFilter/bad.js diff --git a/lib/public/components/Filters/QcFlagTypesFilter/bad.js b/lib/public/components/Filters/QcFlagTypesFilter/bad.js new file mode 100644 index 0000000000..7378d18d0d --- /dev/null +++ b/lib/public/components/Filters/QcFlagTypesFilter/bad.js @@ -0,0 +1,36 @@ +/** + * @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 { radioButton } from '../../common/form/inputs/radioButton.js'; +import { h } from '/js/src/index.js'; + +/** + * Radiobutton filter for the qcFlag 'bad' filter + * @param {SelectionModel} selectionModel the a selectionmodel + * @return {vnode} A number of radiobuttons corresponding with the selection options + */ +const badFilterRadioButtons = (selectionModel) => { + const name = 'badFilterRadio'; + return h( + '.form-group-header.flex-row.w-100', + selectionModel.options.map((option) => { + const { label } = option; + const action = () => selectionModel.select(option); + const isChecked = selectionModel.isSelected(option); + + return radioButton({ label, isChecked, action, name }); + }), + ); +}; + +export default badFilterRadioButtons; diff --git a/lib/public/components/common/selection/SelectionModel.js b/lib/public/components/common/selection/SelectionModel.js index 8b28aa28d1..18bbaf56eb 100644 --- a/lib/public/components/common/selection/SelectionModel.js +++ b/lib/public/components/common/selection/SelectionModel.js @@ -331,4 +331,16 @@ export class SelectionModel extends Observable { get optionsSelectedByDefault() { return this._defaultSelection; } + + /** + * Returns the normalized value of the selection + * + * @return {string|boolean|number} the normalized value + * @abstract + */ + get normalized() { + return (this._allowEmpty || this._multiple) + ? this._selectedOptions.join() + : this.current; + } } diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index c094c06a17..9bed5b35a6 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -14,8 +14,8 @@ import { h } from '/js/src/index.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; -import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js'; import { qcFlagTypeColoredBadge } from '../../../components/qcFlags/qcFlagTypeColoredBadge.js'; +import badFilterRadioButtons from '../../../components/Filters/QcFlagTypesFilter/bad.js'; /** * List of active columns for a QC Flag Types table @@ -54,10 +54,7 @@ export const qcFlagTypesActiveColumns = { name: 'Bad', visible: true, sortable: true, - filter: ({ isBadFilterModel }) => checkboxes( - isBadFilterModel, - { class: 'w-75 mt1', selector: 'qc-flag-type-bad-filter' }, - ), + filter: ({ filteringModel }) => badFilterRadioButtons(filteringModel.get('bad')), classes: 'f6 w-5', format: (bad) => bad ? h('.danger', 'Yes') : h('.success', 'No'), }, diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index ee4cd096f6..cc4ced6716 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -30,12 +30,14 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel(), methods: new TextTokensFilterModel(), + bad: new SelectionModel({ + availableOptions: [{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }], + defaultSelection: [{ label: 'Any' }], + allowEmpty: false, + multiple: false, + }), }); - this._isBadFilterModel = - new SelectionModel({ availableOptions: [{ label: 'Bad', value: true }, { label: 'Not Bad', value: false }] }); - this._registerFilter(this._isBadFilterModel); - this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); @@ -48,18 +50,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - const params = {}; - if (this.isAnyFilterActive()) { - params.filter = { - names: this._filteringModel.get("names").normalized, - methods: this._filteringModel.get("methods").normalized, - bad: this._isBadFilterModel.selected.length === 2 - ? undefined - : this._isBadFilterModel.selected[0], - }; - } - - return buildUrl('/api/qcFlagTypes', params); + return buildUrl('/api/qcFlagTypes', { filter: this._filteringModel.normalized }); } /** @@ -71,37 +62,13 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { return this._filteringModel; } - /** - * Returns filter model for filtering bad and not bad flags - * - * @return {TextTokensFilterModel} filter model for filtering bad and not bad flags - */ - get isBadFilterModel() { - return this._isBadFilterModel; - } - - /** - * Register a new filter model - * - * @param {FilterModel} filterModel the filter model to register - * @return {void} - * @private - */ - _registerFilter(filterModel) { - filterModel.visualChange$.bubbleTo(this); - filterModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); - } - /** * States whether any filter is active * * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() || this._isBadFilterModel.selected.length; + return this._filteringModel.isAnyFilterActive(); } /** @@ -111,7 +78,6 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { */ reset() { this._filteringModel.reset(); - this._isBadFilterModel.reset(); super.reset(); } } diff --git a/test/public/qcFlagTypes/overview.test.js b/test/public/qcFlagTypes/overview.test.js index 0bf4d519cc..77b4fe656b 100644 --- a/test/public/qcFlagTypes/overview.test.js +++ b/test/public/qcFlagTypes/overview.test.js @@ -112,7 +112,7 @@ module.exports = () => { it('should successfully apply QC flag type bad filter', async () => { await waitForTableLength(page, 7); - await pressElement(page, '.bad-filter input[type=checkbox]', true); + await pressElement(page, '#badFilterRadioBad', true); await checkColumnValuesWithRegex(page, 'bad', '^Yes$'); await pressElement(page, '#reset-filters', true); From c24d3020d0d232f343ad2b1475f00ae6575f989c Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 09:24:20 +0100 Subject: [PATCH 037/115] chore: add filteringModel to AnchoredSimulationPassesOverviewModel --- .../AnchoredSimulationPassesOverviewModel.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index ed6b776215..496201b465 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -15,6 +15,7 @@ import { TextTokensFilterModel } from '../../../components/Filters/common/filter import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; /** * Simulation Passes Per Data Pass overview model @@ -25,6 +26,8 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { */ constructor() { super(); + + this._filteringModel = new FilteringModel(); this._namesFilterModel = new TextTokensFilterModel(); this._registerFilter(this._namesFilterModel); @@ -77,6 +80,15 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { super.reset(); } + /** + * Return the model managing all filters + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + /** * Set id of current data pass which simulation passes are fetched * @param {number} dataPassId data pass id From 2f6fcf11ceebac40d163f7b5d2b3bef78414444d Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 09:51:34 +0100 Subject: [PATCH 038/115] chore: add namesFilterModel to the filteringModel instance --- .../simulationPassesActiveColumns.js | 4 +- .../AnchoredSimulationPassesOverviewModel.js | 47 +++++-------------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/lib/public/views/SimulationPasses/ActiveColumns/simulationPassesActiveColumns.js b/lib/public/views/SimulationPasses/ActiveColumns/simulationPassesActiveColumns.js index 05b796bcf8..95f9940c22 100644 --- a/lib/public/views/SimulationPasses/ActiveColumns/simulationPassesActiveColumns.js +++ b/lib/public/views/SimulationPasses/ActiveColumns/simulationPassesActiveColumns.js @@ -31,8 +31,8 @@ export const simulationPassesActiveColumns = { name: 'Name', visible: true, sortable: true, - filter: ({ namesFilterModel }) => textFilter( - namesFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('names'), { class: 'w-75 mt1', placeholder: 'e.g. LHC23k5, ...' }, ), classes: 'w-10 f6', diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index 496201b465..620ba5527b 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -27,9 +27,14 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { constructor() { super(); - this._filteringModel = new FilteringModel(); - this._namesFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._namesFilterModel); + this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel() }); + + this._filteringModel.observe(() => { + this._pagination.silentlySetCurrentPage(1); + this.load(); + }); + + this._filteringModel.visualChange$.bubbleTo(this); this._dataPass = new ObservableData(RemoteData.notAsked()); } @@ -60,14 +65,10 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - const params = { - filter: { - names: this._namesFilterModel.normalized, - dataPassIds: [this._dataPassId], - }, - }; + const filter = this._filteringModel.normalized; + filter.dataPassIds = [this._dataPassId]; - return buildUrl('/api/simulationPasses', params); + return buildUrl('/api/simulationPasses', { filter }); } /** @@ -76,7 +77,7 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { * @returns {void} */ reset() { - this._namesFilterModel.reset(); + this._filteringModel.reset(); super.reset(); } @@ -104,33 +105,11 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { return this._dataPass.getCurrent(); } - /** - * Returns data passes names filter model - * @return {TextTokensFilterModel} data passes names filter model - */ - get namesFilterModel() { - return this._namesFilterModel; - } - - /** - * Register a new filter model - * @param {FilterModel} filterModel the filter model to register - * @return {void} - * @private - */ - _registerFilter(filterModel) { - filterModel.visualChange$.bubbleTo(this); - filterModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); - } - /** * States whether any filter is active * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return !this._namesFilterModel.isEmpty; + return this._filteringModel.isAnyFilterActive(); } } From 790d76bc2b8539fdfeec6de76a912c251634a38f Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 10:50:43 +0100 Subject: [PATCH 039/115] chore: add filteringModel to SimulationPassesPerLhcPeriodOverviewModel --- ...imulationPassesPerLhcPeriodOverviewModel.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 98e5d12059..bcb6694b69 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -15,6 +15,7 @@ import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { buildUrl, RemoteData } from '/js/src/index.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; /** * Simulation Passes Per LHC Period overview model @@ -26,6 +27,14 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel constructor() { super(); + this._filteringModel = new FilteringModel(); + + this._filteringModel.visualChange$.bubbleTo(this); + this._filteringModel.observe(() => { + this._pagination.silentlySetCurrentPage(1); + this.load(); + }); + this._namesFilterModel = new TextTokensFilterModel(); this._registerFilter(this._namesFilterModel); @@ -49,6 +58,15 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel } } + /** + * Return the model managing all filters + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + /** * @inheritdoc */ From 6fe055b6dc145ec67a28855308f8c7619f487410 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 11:02:48 +0100 Subject: [PATCH 040/115] chore: add namesFilterModel to the filteringModel instance of SimulationPassesPerLhcPeriodOverviewModel --- .../AnchoredSimulationPassesOverviewModel.js | 4 +- ...mulationPassesPerLhcPeriodOverviewModel.js | 43 +++---------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index 620ba5527b..d986ece53b 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -65,9 +65,7 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - const filter = this._filteringModel.normalized; - filter.dataPassIds = [this._dataPassId]; - + const filter = { ...this._filteringModel.normalized, dataPassIds: [this._dataPassId] }; return buildUrl('/api/simulationPasses', { filter }); } diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index bcb6694b69..b7ccdac847 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -27,7 +27,9 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel constructor() { super(); - this._filteringModel = new FilteringModel(); + this._filteringModel = new FilteringModel({ + names: new TextTokensFilterModel(), + }); this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { @@ -35,9 +37,6 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel this.load(); }); - this._namesFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._namesFilterModel); - this._lhcPeriod = new ObservableData(RemoteData.notAsked()); this._lhcPeriod.bubbleTo(this); @@ -79,14 +78,8 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel * @inheritdoc */ getRootEndpoint() { - const params = { - filter: { - names: this._namesFilterModel.normalized, - lhcPeriodIds: [this._lhcPeriodId], - }, - }; - - return buildUrl('/api/simulationPasses', params); + const filter = { ...this._filteringModel.normalized, lhcPeriodIds: [this._lhcPeriodId] }; + return buildUrl('/api/simulationPasses', { filter }); } /** @@ -95,7 +88,7 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel * @returns {void} */ reset() { - this._namesFilterModel.reset(); + this._filteringModel.reset(); super.reset(); } @@ -114,33 +107,11 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel return this._lhcPeriod.getCurrent(); } - /** - * Returns simulation passes names filter model - * @return {TextTokensFilterModel} simulation passes names filter model - */ - get namesFilterModel() { - return this._namesFilterModel; - } - - /** - * Register a new filter model - * @param {FilterModel} filterModel the filter model to register - * @return {void} - * @private - */ - _registerFilter(filterModel) { - filterModel.visualChange$.bubbleTo(this); - filterModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); - } - /** * States whether any filter is active * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return !this._namesFilterModel.isEmpty; + return this._filteringModel.isAnyFilterActive(); } } From 0e5d56742aaa4deb90450872ace7fd95cac0539c Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 11:31:46 +0100 Subject: [PATCH 041/115] chore: add filteringModel to overviewmodel class --- .../Overview/LhcPeriodsOverviewModel.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index eb2d5e48cd..37d541f032 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -27,6 +27,14 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { constructor() { super(); + this._filteringModel = new FilteringModel({}); + + this._filteringModel.visualChange$.bubbleTo(this); + this._filteringModel.observe(() => { + this._pagination.silentlySetCurrentPage(1); + this.load(); + }); + this._namesFilterModel = new TextTokensFilterModel(); this._registerFilter(this._namesFilterModel); this._yearsFilterModel = new TextTokensFilterModel(); @@ -50,6 +58,15 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { return buildUrl('/api/lhcPeriodsStatistics', params); } + /** + * Return the model managing all filters + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + /** * Flatten lhc period statistics objects * @param {LhcPeriodStatistics[]} lhcPeriods data From 05461dd5711be01ed849cd4785d4ec3128ba1b6d Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 11:38:35 +0100 Subject: [PATCH 042/115] feat: move namesFilterModel to the filteringModel --- .../ActiveColumns/lhcPeriodsActiveColumns.js | 4 +-- .../Overview/LhcPeriodsOverviewModel.js | 31 ++++++------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js index 6f757e25d0..9fbe5b18db 100644 --- a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js +++ b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js @@ -30,8 +30,8 @@ export const lhcPeriodsActiveColumns = { name: 'Name', visible: true, sortable: true, - filter: ({ namesFilterModel }) => textFilter( - namesFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('names'), { class: 'w-75 mt1', placeholder: 'e.g. LHC22a, lhc23b, ...' }, ), classes: 'w-15', diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index 37d541f032..b80f1742f7 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -11,6 +11,7 @@ * or submit itself to any jurisdiction. */ +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { TextTokensFilterModel } from '../../../components/Filters/common/filters/TextTokensFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { buildUrl } from '/js/src/index.js'; @@ -27,7 +28,9 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { constructor() { super(); - this._filteringModel = new FilteringModel({}); + this._filteringModel = new FilteringModel({ + names: new TextTokensFilterModel(), + }); this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { @@ -35,8 +38,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this.load(); }); - this._namesFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._namesFilterModel); this._yearsFilterModel = new TextTokensFilterModel(); this._registerFilter(this._yearsFilterModel); this._pdpBeamTypesFilterModel = new TextTokensFilterModel(); @@ -47,15 +48,11 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - const params = { - filter: { - names: this._namesFilterModel.normalized, - years: this._yearsFilterModel.normalized, - pdpBeamTypes: this._pdpBeamTypesFilterModel.normalized, - }, - }; + const filter = this._filteringModel.normalized; + filter.years = this._yearsFilterModel.normalized; + filter.pdpBeamTypes = this._pdpBeamTypesFilterModel.normalized; - return buildUrl('/api/lhcPeriodsStatistics', params); + return buildUrl('/api/lhcPeriodsStatistics', { filter }); } /** @@ -83,14 +80,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { }); } - /** - * Returns lhc periods names filter model - * @return {TextTokensFilterModel} lhc periods names filter model - */ - get namesFilterModel() { - return this._namesFilterModel; - } - /** * Returns lhc periods years filter model * @return {TextTokensFilterModel} lhc periods years filter model @@ -114,7 +103,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { */ reset() { super.reset(); - this._namesFilterModel.reset(); + this._filteringModel.reset(); this._yearsFilterModel.reset(); this._pdpBeamTypesFilterModel.reset(); } @@ -138,6 +127,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return !this._namesFilterModel.isEmpty || !this._yearsFilterModel.isEmpty || !this._pdpBeamTypesFilterModel.isEmpty; + return this._filteringModel.isAnyFilterActive() || !this._yearsFilterModel.isEmpty || !this._pdpBeamTypesFilterModel.isEmpty; } } From 102c1b61966e9d7e76561d30fe29346cab8bc43b Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 11:41:24 +0100 Subject: [PATCH 043/115] feat: move yearsFilterModel to the filteringModel --- .../ActiveColumns/lhcPeriodsActiveColumns.js | 4 ++-- .../Overview/LhcPeriodsOverviewModel.js | 15 ++------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js index 9fbe5b18db..299e54b4ee 100644 --- a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js +++ b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js @@ -92,8 +92,8 @@ export const lhcPeriodsActiveColumns = { visible: true, sortable: true, format: (_, lhcPeriod) => formatLhcPeriodYear(lhcPeriod.name), - filter: ({ yearsFilterModel }) => textFilter( - yearsFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('years'), { class: 'w-75 mt1', placeholder: 'e.g. 2022, 2023, ...' }, ), classes: 'w-7', diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index b80f1742f7..9d01425b74 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -30,6 +30,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel(), + years: new TextTokensFilterModel(), }); this._filteringModel.visualChange$.bubbleTo(this); @@ -38,8 +39,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this.load(); }); - this._yearsFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._yearsFilterModel); this._pdpBeamTypesFilterModel = new TextTokensFilterModel(); this._registerFilter(this._pdpBeamTypesFilterModel); } @@ -49,7 +48,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { */ getRootEndpoint() { const filter = this._filteringModel.normalized; - filter.years = this._yearsFilterModel.normalized; filter.pdpBeamTypes = this._pdpBeamTypesFilterModel.normalized; return buildUrl('/api/lhcPeriodsStatistics', { filter }); @@ -80,14 +78,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { }); } - /** - * Returns lhc periods years filter model - * @return {TextTokensFilterModel} lhc periods years filter model - */ - get yearsFilterModel() { - return this._yearsFilterModel; - } - /** * Returns lhc periods beam type filter model * @return {TextTokensFilterModel} lhc periods beam type filter model @@ -104,7 +94,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { reset() { super.reset(); this._filteringModel.reset(); - this._yearsFilterModel.reset(); this._pdpBeamTypesFilterModel.reset(); } @@ -127,6 +116,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() || !this._yearsFilterModel.isEmpty || !this._pdpBeamTypesFilterModel.isEmpty; + return this._filteringModel.isAnyFilterActive() || !this._pdpBeamTypesFilterModel.isEmpty; } } From e2ab763304f0e3c61686c74a19a8c3aa747ce558 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 11:47:00 +0100 Subject: [PATCH 044/115] feat: move pdpBeamTypes to the filteringModel --- .../ActiveColumns/lhcPeriodsActiveColumns.js | 4 +-- .../Overview/LhcPeriodsOverviewModel.js | 34 ++----------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js index 299e54b4ee..6c312fbecf 100644 --- a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js +++ b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js @@ -104,8 +104,8 @@ export const lhcPeriodsActiveColumns = { visible: true, sortable: true, format: (pdpBeamTypes) => pdpBeamTypes.length > 0 ? pdpBeamTypes.join(',') : '-', - filter: ({ pdpBeamTypesFilterModel }) => textFilter( - pdpBeamTypesFilterModel, + filter: ({ filteringModel }) => textFilter( + filteringModel.get('pdpBeamTypes'), { class: 'w-75 mt1', placeholder: 'e.g. pp, PbPb' }, ), classes: 'w-7', diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index 9d01425b74..e6073eb68f 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -31,6 +31,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel(), years: new TextTokensFilterModel(), + pdpBeamTypes: new TextTokensFilterModel(), }); this._filteringModel.visualChange$.bubbleTo(this); @@ -38,19 +39,13 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this._pagination.silentlySetCurrentPage(1); this.load(); }); - - this._pdpBeamTypesFilterModel = new TextTokensFilterModel(); - this._registerFilter(this._pdpBeamTypesFilterModel); } /** * @inheritdoc */ getRootEndpoint() { - const filter = this._filteringModel.normalized; - filter.pdpBeamTypes = this._pdpBeamTypesFilterModel.normalized; - - return buildUrl('/api/lhcPeriodsStatistics', { filter }); + return buildUrl('/api/lhcPeriodsStatistics', { filter: this._filteringModel.normalized }); } /** @@ -78,14 +73,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { }); } - /** - * Returns lhc periods beam type filter model - * @return {TextTokensFilterModel} lhc periods beam type filter model - */ - get pdpBeamTypesFilterModel() { - return this._pdpBeamTypesFilterModel; - } - /** * Reset this model to its default * @@ -94,21 +81,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { reset() { super.reset(); this._filteringModel.reset(); - this._pdpBeamTypesFilterModel.reset(); - } - - /** - * Register a new filter model - * @param {FilterModel} filterModel the filter model to register - * @return {void} - * @private - */ - _registerFilter(filterModel) { - filterModel.visualChange$.bubbleTo(this); - filterModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); } /** @@ -116,6 +88,6 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * @return {boolean} true if any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() || !this._pdpBeamTypesFilterModel.isEmpty; + return this._filteringModel.isAnyFilterActive(); } } From 30d7b39dd6e31cf425f94b8b5fc1234418dbf5cd Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 12:47:07 +0100 Subject: [PATCH 045/115] rename te bad filter to be a generic component --- .../bad.js => common/filters/radioButtonFilter.js} | 14 ++++++++------ .../ActiveColumns/qcFlagTypesActiveColumns.js | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) rename lib/public/components/Filters/{QcFlagTypesFilter/bad.js => common/filters/radioButtonFilter.js} (72%) diff --git a/lib/public/components/Filters/QcFlagTypesFilter/bad.js b/lib/public/components/Filters/common/filters/radioButtonFilter.js similarity index 72% rename from lib/public/components/Filters/QcFlagTypesFilter/bad.js rename to lib/public/components/Filters/common/filters/radioButtonFilter.js index 7378d18d0d..1b1d91d7ed 100644 --- a/lib/public/components/Filters/QcFlagTypesFilter/bad.js +++ b/lib/public/components/Filters/common/filters/radioButtonFilter.js @@ -11,16 +11,18 @@ * or submit itself to any jurisdiction. */ -import { radioButton } from '../../common/form/inputs/radioButton.js'; +import { radioButton } from '../../../common/form/inputs/radioButton.js'; import { h } from '/js/src/index.js'; /** - * Radiobutton filter for the qcFlag 'bad' filter - * @param {SelectionModel} selectionModel the a selectionmodel + * Radiobutton filter component + * + * @param {RadioSelectionModel} selectionModel the a selectionmodel + * @param {string} filterName the name of the filter * @return {vnode} A number of radiobuttons corresponding with the selection options */ -const badFilterRadioButtons = (selectionModel) => { - const name = 'badFilterRadio'; +const radioButtonFilter = (selectionModel, filterName) => { + const name = `${filterName}FilterRadio`; return h( '.form-group-header.flex-row.w-100', selectionModel.options.map((option) => { @@ -33,4 +35,4 @@ const badFilterRadioButtons = (selectionModel) => { ); }; -export default badFilterRadioButtons; +export default radioButtonFilter; diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index 9bed5b35a6..af1c8fe385 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -15,7 +15,7 @@ import { h } from '/js/src/index.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; import { qcFlagTypeColoredBadge } from '../../../components/qcFlags/qcFlagTypeColoredBadge.js'; -import badFilterRadioButtons from '../../../components/Filters/QcFlagTypesFilter/bad.js'; +import radioButtonFilter from '../../../components/Filters/common/filters/radioButtonFilter.js'; /** * List of active columns for a QC Flag Types table @@ -54,7 +54,7 @@ export const qcFlagTypesActiveColumns = { name: 'Bad', visible: true, sortable: true, - filter: ({ filteringModel }) => badFilterRadioButtons(filteringModel.get('bad')), + filter: ({ filteringModel }) => radioButtonFilter(filteringModel.get('bad'), 'bad'), classes: 'f6 w-5', format: (bad) => bad ? h('.danger', 'Yes') : h('.success', 'No'), }, From bdba964531c1e468135e66b6d3df55d0627ddc95 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 13:08:03 +0100 Subject: [PATCH 046/115] feat: replace qcFlagType bad filter with radioButtonFilterModel --- .../Filters/common/RadioButtonFilterModel.js | 34 +++++++++++++++++++ .../Overview/QcFlagTypesOverviewModel.js | 9 ++--- 2 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 lib/public/components/Filters/common/RadioButtonFilterModel.js diff --git a/lib/public/components/Filters/common/RadioButtonFilterModel.js b/lib/public/components/Filters/common/RadioButtonFilterModel.js new file mode 100644 index 0000000000..5e93205bfc --- /dev/null +++ b/lib/public/components/Filters/common/RadioButtonFilterModel.js @@ -0,0 +1,34 @@ +/** + * @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 { SelectionModel } from '../../common/selection/SelectionModel.js'; + +/** + * Model for managing a radiobutton view and state + */ +export class RadioButtonFilterModel extends SelectionModel { + /** + * Constructor + * + * @param {SelectionOption[]} [availableOptions] the list of possible operators + * @param {function} [setDefault] function that selects the default from the list of available options. Selects first entry by default + */ + constructor(availableOptions, setDefault = (options) => [options[0]]) { + super({ + availableOptions, + defaultSelection: setDefault(availableOptions), + multiple: false, + allowEmpty: false, + }); + } +} diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index cc4ced6716..72abce90f5 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -13,9 +13,9 @@ import { TextTokensFilterModel } from '../../../components/Filters/common/filters/TextTokensFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; -import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; import { buildUrl } from '/js/src/index.js'; import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; +import { RadioButtonFilterModel } from '../../../components/Filters/common/RadioButtonFilterModel.js'; /** * QcFlagTypesOverviewModel @@ -30,12 +30,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel(), methods: new TextTokensFilterModel(), - bad: new SelectionModel({ - availableOptions: [{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }], - defaultSelection: [{ label: 'Any' }], - allowEmpty: false, - multiple: false, - }), + bad: new RadioButtonFilterModel([{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }]), }); this._filteringModel.observe(() => { From 4790d309f15f501e365eb984b811bc26652acd22 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 13:20:30 +0100 Subject: [PATCH 047/115] feat: replace ddflp's filter component with the common radiButton componnent --- .../Runs/ActiveColumns/runsActiveColumns.js | 3 +- .../views/Runs/Overview/RunsOverviewModel.js | 37 ++----------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index eefe0f006f..484cb5d8b2 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -48,6 +48,7 @@ import { rawTextFilter } from '../../../components/Filters/common/filters/rawTex import { numericalComparisonFilter } from '../../../components/Filters/common/filters/numericalComparisonFilter.js'; import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js'; import { triggerValueFilter } from '../../../components/Filters/RunsFilter/triggerValueFilter.js'; +import radioButtonFilter from '../../../components/Filters/common/filters/radioButtonFilter.js'; /** * List of active columns for a generic runs table @@ -525,7 +526,7 @@ export const runsActiveColumns = { classes: 'w-2 f6 w-wrapped', format: (boolean) => boolean ? 'On' : 'Off', exportFormat: (boolean) => boolean ? 'On' : 'Off', - filter: ddflpFilter, + filter: ({ filteringModel }) => radioButtonFilter(filteringModel.get('ddflp'), 'ddFlp'), }, dcs: { name: 'DCS', diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 0249c66085..a8855d776c 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -36,6 +36,7 @@ import { DataExportModel } from '../../../models/DataExportModel.js'; import { runsActiveColumns as dataExportConfiguration } from '../ActiveColumns/runsActiveColumns.js'; import { BeamModeFilterModel } from '../../../components/Filters/RunsFilter/BeamModeFilterModel.js'; import { beamModesProvider } from '../../../services/beamModes/beamModesProvider.js'; +import { RadioButtonFilterModel } from '../../../components/Filters/common/RadioButtonFilterModel.js'; /** * Model representing handlers for runs page @@ -90,6 +91,7 @@ export class RunsOverviewModel extends OverviewPageModel { inelasticInteractionRateAtStart: new NumericalComparisonFilterModel(), inelasticInteractionRateAtMid: new NumericalComparisonFilterModel(), inelasticInteractionRateAtEnd: new NumericalComparisonFilterModel(), + ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), }); this._filteringModel.observe(() => this._applyFilters(true)); @@ -147,8 +149,6 @@ export class RunsOverviewModel extends OverviewPageModel { this._triggerValuesFilters = new Set(); - this.ddflpFilter = ''; - this.dcsFilter = ''; this.epnFilter = ''; @@ -165,7 +165,6 @@ export class RunsOverviewModel extends OverviewPageModel { isAnyFilterActive() { return this._filteringModel.isAnyFilterActive() || this._triggerValuesFilters.size !== 0 - || this.ddflpFilter !== '' || this.dcsFilter !== '' || this.epnFilter !== ''; } @@ -197,34 +196,6 @@ export class RunsOverviewModel extends OverviewPageModel { this._applyFilters(); } - /** - * Returns the boolean of ddflp - * @return {Boolean} if ddflp is on - */ - getDdflpFilterOperation() { - return this.ddflpFilter; - } - - /** - * Sets the boolean of the filter if no new inputs were detected for 200 milliseconds - * @param {boolean} operation if the ddflp is on - * @return {undefined} - */ - setDdflpFilterOperation(operation) { - this.ddflpFilter = operation; - this._applyFilters(); - } - - /** - * Unchecks the ddflp checkbox and fetches all the runs. - * @return {undefined} - * - */ - removeDdflp() { - this.ddflpFilter = ''; - this._applyFilters(); - } - /** * Returns the boolean of dcs * @return {Boolean} if dcs is on @@ -291,9 +262,7 @@ export class RunsOverviewModel extends OverviewPageModel { ...this._triggerValuesFilters.size !== 0 && { 'filter[triggerValues]': Array.from(this._triggerValuesFilters).join(), }, - ...(this.ddflpFilter === true || this.ddflpFilter === false) && { - 'filter[ddflp]': this.ddflpFilter, - }, + 'filter[ddflp]': this._filteringModel.get('ddflp').normalized, ...(this.dcsFilter === true || this.dcsFilter === false) && { 'filter[dcs]': this.dcsFilter, }, From 3cf6c1b9e49eabe9a5323cbc003bba0fdbbb5c81 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 13:38:32 +0100 Subject: [PATCH 048/115] feat: replace ddflp's filter component with the common radioButton componnent --- .../Runs/ActiveColumns/runsActiveColumns.js | 2 +- .../views/Runs/Overview/RunsOverviewModel.js | 32 +------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index 484cb5d8b2..a6bc04119a 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -535,7 +535,7 @@ export const runsActiveColumns = { classes: 'w-2 f6 w-wrapped', format: (boolean) => boolean ? 'On' : 'Off', exportFormat: (boolean) => boolean ? 'On' : 'Off', - filter: dcsFilter, + filter: ({ filteringModel }) => radioButtonFilter(filteringModel.get('dcs'), 'dcs'), }, triggerValue: { name: 'TRG', diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index a8855d776c..1f520fce70 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -92,6 +92,7 @@ export class RunsOverviewModel extends OverviewPageModel { inelasticInteractionRateAtMid: new NumericalComparisonFilterModel(), inelasticInteractionRateAtEnd: new NumericalComparisonFilterModel(), ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), }); this._filteringModel.observe(() => this._applyFilters(true)); @@ -196,33 +197,6 @@ export class RunsOverviewModel extends OverviewPageModel { this._applyFilters(); } - /** - * Returns the boolean of dcs - * @return {Boolean} if dcs is on - */ - getDcsFilterOperation() { - return this.dcsFilter; - } - - /** - * Sets the boolean of the filter if no new inputs were detected for 200 milliseconds - * @param {boolean} operation if the dcs is on - * @return {undefined} - */ - setDcsFilterOperation(operation) { - this.dcsFilter = operation; - this._applyFilters(); - } - - /** - * Unchecks the dcs checkbox and fetches all the runs. - * @return {undefined} - */ - removeDcs() { - this.dcsFilter = ''; - this._applyFilters(); - } - /** * Returns the boolean of epn * @return {Boolean} if epn is on @@ -262,10 +236,6 @@ export class RunsOverviewModel extends OverviewPageModel { ...this._triggerValuesFilters.size !== 0 && { 'filter[triggerValues]': Array.from(this._triggerValuesFilters).join(), }, - 'filter[ddflp]': this._filteringModel.get('ddflp').normalized, - ...(this.dcsFilter === true || this.dcsFilter === false) && { - 'filter[dcs]': this.dcsFilter, - }, ...(this.epnFilter === true || this.epnFilter === false) && { 'filter[epn]': this.epnFilter, }, From 17a4ce87b2f241eb1596427b115dcabcda573a0d Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 13:44:02 +0100 Subject: [PATCH 049/115] feat: replace epn's filter component with the common radioButton componnent --- .../Runs/ActiveColumns/runsActiveColumns.js | 5 +-- .../views/Runs/Overview/RunsOverviewModel.js | 40 +------------------ 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index a6bc04119a..aa0eced044 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -14,9 +14,6 @@ import { CopyToClipboardComponent, h } from '/js/src/index.js'; import { runNumbersFilter } from '../../../components/Filters/RunsFilter/runNumbersFilter.js'; import { displayRunEorReasonsOverview } from '../format/displayRunEorReasonOverview.js'; -import ddflpFilter from '../../../components/Filters/RunsFilter/ddflp.js'; -import dcsFilter from '../../../components/Filters/RunsFilter/dcs.js'; -import epnFilter from '../../../components/Filters/RunsFilter/epn.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { displayRunDuration } from '../format/displayRunDuration.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; @@ -552,7 +549,7 @@ export const runsActiveColumns = { classes: 'w-2 f6 w-wrapped', format: (boolean) => boolean ? 'On' : 'Off', exportFormat: (boolean) => boolean ? 'On' : 'Off', - filter: epnFilter, + filter: ({ filteringModel }) => radioButtonFilter(filteringModel.get('epn'), 'epn'), }, epnTopology: { name: 'EPN Topology', diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 1f520fce70..2c37a3a2e4 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -93,6 +93,7 @@ export class RunsOverviewModel extends OverviewPageModel { inelasticInteractionRateAtEnd: new NumericalComparisonFilterModel(), ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + epn: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), }); this._filteringModel.observe(() => this._applyFilters(true)); @@ -150,10 +151,6 @@ export class RunsOverviewModel extends OverviewPageModel { this._triggerValuesFilters = new Set(); - this.dcsFilter = ''; - - this.epnFilter = ''; - if (fetch) { this._applyFilters(true); } @@ -164,10 +161,7 @@ export class RunsOverviewModel extends OverviewPageModel { * @return {Boolean} If any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() - || this._triggerValuesFilters.size !== 0 - || this.dcsFilter !== '' - || this.epnFilter !== ''; + return this._filteringModel.isAnyFilterActive() || this._triggerValuesFilters.size !== 0; } /** @@ -197,33 +191,6 @@ export class RunsOverviewModel extends OverviewPageModel { this._applyFilters(); } - /** - * Returns the boolean of epn - * @return {Boolean} if epn is on - */ - getEpnFilterOperation() { - return this.epnFilter; - } - - /** - * Sets the boolean of the filter if no new inputs were detected for 200 milliseconds - * @param {boolean} operation if the epn is on - * @return {undefined} - */ - setEpnFilterOperation(operation) { - this.epnFilter = operation; - this._applyFilters(); - } - - /** - * Unchecks the epn checkbox and fetches all the runs. - * @return {undefined} - */ - removeEpn() { - this.epnFilter = ''; - this._applyFilters(); - } - /** * Returns the list of URL params corresponding to the currently applied filter * @@ -236,9 +203,6 @@ export class RunsOverviewModel extends OverviewPageModel { ...this._triggerValuesFilters.size !== 0 && { 'filter[triggerValues]': Array.from(this._triggerValuesFilters).join(), }, - ...(this.epnFilter === true || this.epnFilter === false) && { - 'filter[epn]': this.epnFilter, - }, }; } From 1cd5603c8c42b54a365b0ef3d61bce74b0d4ddff Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Tue, 10 Mar 2026 17:31:11 +0100 Subject: [PATCH 050/115] feat: replace triggerValue's filter component with the standard selectionModel + checkboxes component --- .../common/selection/SelectionModel.js | 4 +- .../Runs/ActiveColumns/runsActiveColumns.js | 10 ++++- .../views/Runs/Overview/RunsOverviewModel.js | 42 +++---------------- test/public/runs/overview.test.js | 2 +- 4 files changed, 15 insertions(+), 43 deletions(-) diff --git a/lib/public/components/common/selection/SelectionModel.js b/lib/public/components/common/selection/SelectionModel.js index 18bbaf56eb..c19d6ff893 100644 --- a/lib/public/components/common/selection/SelectionModel.js +++ b/lib/public/components/common/selection/SelectionModel.js @@ -339,8 +339,6 @@ export class SelectionModel extends Observable { * @abstract */ get normalized() { - return (this._allowEmpty || this._multiple) - ? this._selectedOptions.join() - : this.current; + return (this._allowEmpty || this._multiple) ? this.selected.join() : this.current; } } diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index aa0eced044..5bafc19895 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -44,7 +44,6 @@ import { timeRangeFilter } from '../../../components/Filters/common/filters/time import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; import { numericalComparisonFilter } from '../../../components/Filters/common/filters/numericalComparisonFilter.js'; import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js'; -import { triggerValueFilter } from '../../../components/Filters/RunsFilter/triggerValueFilter.js'; import radioButtonFilter from '../../../components/Filters/common/filters/radioButtonFilter.js'; /** @@ -539,7 +538,14 @@ export const runsActiveColumns = { visible: true, profiles: [profiles.none, 'lhcFill', 'environment'], classes: 'w-5 f6 w-wrapped', - filter: triggerValueFilter, + + /** + * TriggerValue filter component + * + * @param {RunsOverviewModel} runsOverviewModel the runs overview model + * @return {Component} the trigger value filter component + */ + filter: ({ filteringModel }) => checkboxes(filteringModel.get('triggerValues')), format: (trgValue) => trgValue ? trgValue : '-', }, epn: { diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 2c37a3a2e4..b5c400e5d2 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -37,6 +37,8 @@ import { runsActiveColumns as dataExportConfiguration } from '../ActiveColumns/r import { BeamModeFilterModel } from '../../../components/Filters/RunsFilter/BeamModeFilterModel.js'; import { beamModesProvider } from '../../../services/beamModes/beamModesProvider.js'; import { RadioButtonFilterModel } from '../../../components/Filters/common/RadioButtonFilterModel.js'; +import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; +import { TRIGGER_VALUES } from '../../../domain/enums/TriggerValue.js'; /** * Model representing handlers for runs page @@ -94,6 +96,7 @@ export class RunsOverviewModel extends OverviewPageModel { ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), epn: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + triggerValues: new SelectionModel({ availableOptions: TRIGGER_VALUES.map((value) => ({ label: value, value })) }), }); this._filteringModel.observe(() => this._applyFilters(true)); @@ -127,7 +130,7 @@ export class RunsOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - return buildUrl('/api/runs', { ...this._getFilterQueryParams(), ...{ filter: this.filteringModel.normalized } }); + return buildUrl('/api/runs', { filter: this.filteringModel.normalized }); } /** @@ -149,8 +152,6 @@ export class RunsOverviewModel extends OverviewPageModel { resetFiltering(fetch = true) { this._filteringModel.reset(); - this._triggerValuesFilters = new Set(); - if (fetch) { this._applyFilters(true); } @@ -161,7 +162,7 @@ export class RunsOverviewModel extends OverviewPageModel { * @return {Boolean} If any filter is active */ isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive() || this._triggerValuesFilters.size !== 0; + return this._filteringModel.isAnyFilterActive(); } /** @@ -173,39 +174,6 @@ export class RunsOverviewModel extends OverviewPageModel { return this._filteringModel; } - /** - * Getter for the trigger values filter Set - * @return {Set} set of trigger filter values - */ - get triggerValuesFilters() { - return this._triggerValuesFilters; - } - - /** - * Setter for trigger values filter, this replaces the current set - * @param {Array} newTriggerValues new Set of values - * @return {undefined} - */ - set triggerValuesFilters(newTriggerValues) { - this._triggerValuesFilters = new Set(newTriggerValues); - this._applyFilters(); - } - - /** - * Returns the list of URL params corresponding to the currently applied filter - * - * @return {Object} the URL params - * - * @private - */ - _getFilterQueryParams() { - return { - ...this._triggerValuesFilters.size !== 0 && { - 'filter[triggerValues]': Array.from(this._triggerValuesFilters).join(), - }, - }; - } - /** * Apply the current filtering and update the remote data list * diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index 807b821ffc..73f82d9ae3 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -600,7 +600,7 @@ module.exports = () => { it('Should successfully filter runs by their trigger value', async () => { await navigateToRunsOverview(page); - const filterInputSelectorPrefix = '#triggerValueCheckbox'; + const filterInputSelectorPrefix = '#checkboxes-checkbox-'; const offFilterSelector = `${filterInputSelectorPrefix}OFF`; const ltuFilterSelector = `${filterInputSelectorPrefix}LTU`; From 4cea58dfa2e94e8395dfd34dfe241a7bea7f3cb9 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Wed, 11 Mar 2026 08:29:46 +0100 Subject: [PATCH 051/115] chore: remove unused components --- .../components/Filters/RunsFilter/dcs.js | 50 ------------------- .../components/Filters/RunsFilter/ddflp.js | 50 ------------------- .../components/Filters/RunsFilter/epn.js | 50 ------------------- .../Filters/RunsFilter/triggerValueFilter.js | 21 -------- .../Filters/common/filters/checkboxFilter.js | 26 ---------- 5 files changed, 197 deletions(-) delete mode 100644 lib/public/components/Filters/RunsFilter/dcs.js delete mode 100644 lib/public/components/Filters/RunsFilter/ddflp.js delete mode 100644 lib/public/components/Filters/RunsFilter/epn.js delete mode 100644 lib/public/components/Filters/RunsFilter/triggerValueFilter.js diff --git a/lib/public/components/Filters/RunsFilter/dcs.js b/lib/public/components/Filters/RunsFilter/dcs.js deleted file mode 100644 index 590eb81b78..0000000000 --- a/lib/public/components/Filters/RunsFilter/dcs.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @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 { radioButton } from '../../common/form/inputs/radioButton.js'; -import { h } from '/js/src/index.js'; - -/** - * Filter panel for DCS toggle; ON/OFF/ANY - * @param {RunsOverviewModel} runModel the run model object - * @return {vnode} Three radio buttons inline - */ -const dcsOperationRadioButtons = (runModel) => { - const state = runModel.getDcsFilterOperation(); - const name = 'dcsFilterRadio'; - const labelAny = 'ANY'; - const labelOff = 'OFF'; - const labelOn = 'ON'; - return h('.form-group-header.flex-row.w-100', [ - radioButton({ - label: labelAny, - isChecked: state === '', - action: () => runModel.removeDcs(), - name, - }), - radioButton({ - label: labelOff, - isChecked: state === false, - action: () => runModel.setDcsFilterOperation(false), - name, - }), - radioButton({ - label: labelOn, - isChecked: state === true, - action: () => runModel.setDcsFilterOperation(true), - name, - }), - ]); -}; - -export default dcsOperationRadioButtons; diff --git a/lib/public/components/Filters/RunsFilter/ddflp.js b/lib/public/components/Filters/RunsFilter/ddflp.js deleted file mode 100644 index 74bf28f4ba..0000000000 --- a/lib/public/components/Filters/RunsFilter/ddflp.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @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 { radioButton } from '../../common/form/inputs/radioButton.js'; -import { h } from '/js/src/index.js'; - -/** - * Filter panel for Data Distribution toggle; ON/OFF/ANY - * @param {RunsOverviewModel} runModel the run model object - * @return {vnode} Three radio buttons inline - */ -const ddflpOperationRadioButtons = (runModel) => { - const state = runModel.getDdflpFilterOperation(); - const name = 'ddFlpFilterRadio'; - const labelAny = 'ANY'; - const labelOff = 'OFF'; - const labelOn = 'ON'; - return h('.form-group-header.flex-row.w-100', [ - radioButton({ - label: labelAny, - isChecked: state === '', - action: () => runModel.removeDdflp(), - name, - }), - radioButton({ - label: labelOff, - isChecked: state === false, - action: () => runModel.setDdflpFilterOperation(false), - name, - }), - radioButton({ - label: labelOn, - isChecked: state === true, - action: () => runModel.setDdflpFilterOperation(true), - name, - }), - ]); -}; - -export default ddflpOperationRadioButtons; diff --git a/lib/public/components/Filters/RunsFilter/epn.js b/lib/public/components/Filters/RunsFilter/epn.js deleted file mode 100644 index 5e639d8afb..0000000000 --- a/lib/public/components/Filters/RunsFilter/epn.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @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 { radioButton } from '../../common/form/inputs/radioButton.js'; -import { h } from '/js/src/index.js'; - -/** - * Filter panel for EPN toggle; ON/OFF/ANY - * @param {RunsOverviewModel} runModel the run model object - * @return {vnode} Three radio buttons inline - */ -const epnOperationRadioButtons = (runModel) => { - const state = runModel.getEpnFilterOperation(); - const name = 'epnFilterRadio'; - const labelAny = 'ANY'; - const labelOff = 'OFF'; - const labelOn = 'ON'; - return h('.form-group-header.flex-row.w-100', [ - radioButton({ - label: labelAny, - isChecked: state === '', - action: () => runModel.removeEpn(), - name, - }), - radioButton({ - label: labelOff, - isChecked: state === false, - action: () => runModel.setEpnFilterOperation(false), - name, - }), - radioButton({ - label: labelOn, - isChecked: state === true, - action: () => runModel.setEpnFilterOperation(true), - name, - }), - ]); -}; - -export default epnOperationRadioButtons; diff --git a/lib/public/components/Filters/RunsFilter/triggerValueFilter.js b/lib/public/components/Filters/RunsFilter/triggerValueFilter.js deleted file mode 100644 index 5addab02fe..0000000000 --- a/lib/public/components/Filters/RunsFilter/triggerValueFilter.js +++ /dev/null @@ -1,21 +0,0 @@ -import { checkboxFilter } from '../common/filters/checkboxFilter.js'; -import { TRIGGER_VALUES } from '../../../domain/enums/TriggerValue.js'; - -/** - * Returns a panel to be used by user to filter runs by trigger value - * @param {RunsOverviewModel} runModel The global model object - * @return {vnode} Multiple checkboxes for a user to select the values to be filtered. - */ -export const triggerValueFilter = (runModel) => checkboxFilter( - 'triggerValue', - TRIGGER_VALUES, - (value) => runModel.triggerValuesFilters.has(value), - (e, value) => { - if (e.target.checked) { - runModel.triggerValuesFilters.add(value); - } else { - runModel.triggerValuesFilters.delete(value); - } - runModel.triggerValuesFilters = Array.from(runModel.triggerValuesFilters); - }, -); diff --git a/lib/public/components/Filters/common/filters/checkboxFilter.js b/lib/public/components/Filters/common/filters/checkboxFilter.js index dcfcb4a95b..2cf550c091 100644 --- a/lib/public/components/Filters/common/filters/checkboxFilter.js +++ b/lib/public/components/Filters/common/filters/checkboxFilter.js @@ -14,32 +14,6 @@ import { h } from '/js/src/index.js'; -/** - * A general component for generating checkboxes. - * - * @param {string} name The general name of the element. - * @param {Array} values the list of options to display - * @param {function} isChecked true if the checkbox is checked, else false - * @param {function} onChange the handler called once the checkbox state changes (change event is passed as first parameter, value as second) - * @param {Object} [additionalProperties] Additional options that can be given to the class. - * @returns {vnode} An object that has one or multiple checkboxes. - * @deprecated use checkboxes - */ -export const checkboxFilter = (name, values, isChecked, onChange, additionalProperties) => - h('.flex-row.flex-wrap', values.map((value) => h('.form-check.flex-grow', [ - h('input.form-check-input', { - id: `${name}Checkbox${value}`, - class: name, - type: 'checkbox', - checked: isChecked(value), - onchange: (e) => onChange(e, value), - ...additionalProperties || {}, - }), - h('label.form-check-label', { - for: `${name}Checkbox${value}`, - }, value.toUpperCase()), - ]))); - /** * Display a filter composed of checkbox listing pre-defined options * @param {SelectionModel} selectionModel filter model From a9a1cd96cc45146a70c4378b95cc1bddbb019004 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Mar 2026 08:50:27 +0100 Subject: [PATCH 052/115] fix: fix Selectionmodel.normalized to use selected rather than selectedOptions --- lib/public/components/common/selection/SelectionModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/public/components/common/selection/SelectionModel.js b/lib/public/components/common/selection/SelectionModel.js index 18bbaf56eb..c19d6ff893 100644 --- a/lib/public/components/common/selection/SelectionModel.js +++ b/lib/public/components/common/selection/SelectionModel.js @@ -339,8 +339,6 @@ export class SelectionModel extends Observable { * @abstract */ get normalized() { - return (this._allowEmpty || this._multiple) - ? this._selectedOptions.join() - : this.current; + return (this._allowEmpty || this._multiple) ? this.selected.join() : this.current; } } From aded3af2bf0a6766689553a82e74cb1cac6e9161 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 2 Apr 2026 16:22:55 +0200 Subject: [PATCH 053/115] rename triggerValue checkboxes to include the triggerValue name --- lib/public/views/Runs/ActiveColumns/runsActiveColumns.js | 2 +- test/public/runs/overview.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index 5bafc19895..e0d5110557 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -545,7 +545,7 @@ export const runsActiveColumns = { * @param {RunsOverviewModel} runsOverviewModel the runs overview model * @return {Component} the trigger value filter component */ - filter: ({ filteringModel }) => checkboxes(filteringModel.get('triggerValues')), + filter: ({ filteringModel }) => checkboxes(filteringModel.get('triggerValues'), { selector: 'triggerValue' }), format: (trgValue) => trgValue ? trgValue : '-', }, epn: { diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index 73f82d9ae3..6adaac43de 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -600,7 +600,7 @@ module.exports = () => { it('Should successfully filter runs by their trigger value', async () => { await navigateToRunsOverview(page); - const filterInputSelectorPrefix = '#checkboxes-checkbox-'; + const filterInputSelectorPrefix = '#triggerValue-checkbox-'; const offFilterSelector = `${filterInputSelectorPrefix}OFF`; const ltuFilterSelector = `${filterInputSelectorPrefix}LTU`; From fa199dea60853aaf4f26c3ff5a776dbfc1981bd9 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 9 Apr 2026 12:39:23 +0200 Subject: [PATCH 054/115] feat: create ToggleFilterModel --- .../common/filters/ToggleFilterModel.js | 55 +++++++++++++++++++ .../Overview/LhcFillsOverviewModel.js | 4 +- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 lib/public/components/Filters/common/filters/ToggleFilterModel.js diff --git a/lib/public/components/Filters/common/filters/ToggleFilterModel.js b/lib/public/components/Filters/common/filters/ToggleFilterModel.js new file mode 100644 index 0000000000..7ce76f202c --- /dev/null +++ b/lib/public/components/Filters/common/filters/ToggleFilterModel.js @@ -0,0 +1,55 @@ +/** + * @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 { SelectionModel } from '../../../common/selection/SelectionModel.js'; + +/** + * Filtering model to handle raw text value + */ +export class ToggleFilterModel extends SelectionModel { + /** + * Constructor + * @param {boolean} toggledByDefault If the filter should be toggled by default + */ + constructor(toggledByDefault) { + super({ availableOptions: [{ value: true }, { value: false }], + defaultSelection: [{ value: toggledByDefault }], + multiple: false, + allowEmpty: false }); + } + + /** + * Returns true if the current value is set to true + * + * @return {boolean} true if filter is stable beams only + */ + isToggled() { + return this.current; + } + + /** + * Toggles the filter state + * + * @return {void} + */ + toggle() { + this.select({ value: !this.current }); + } + + /** + * Overrides SelectionModel.isEmpty to respect the fact that toggle filters cannot be empty. + * @returns {boolean} true if the current value of the filter is false. + */ + get isEmpty() { + return this.current === false; + } +} diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index c57ae69c25..17c2c459ae 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -13,13 +13,13 @@ import { buildUrl } from '/js/src/index.js'; import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; -import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilter/StableBeamFilterModel.js'; import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js'; import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js'; import { TimeRangeFilterModel } from '../../../components/Filters/RunsFilter/TimeRangeFilter.js'; +import { ToggleFilterModel } from '../../../components/Filters/common/filters/toggleFilterModel.js'; /** * Model for the LHC fills overview page @@ -39,7 +39,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { fillNumbers: new RawTextFilterModel(), beamDuration: new TextComparisonFilterModel(), runDuration: new TextComparisonFilterModel(), - hasStableBeams: new StableBeamFilterModel(), + hasStableBeams: new ToggleFilterModel(false), stableBeamsStart: new TimeRangeFilterModel(), stableBeamsEnd: new TimeRangeFilterModel(), beamTypes: new BeamTypeFilterModel(), From 47c01058d2b20a6b750ff368756689875f00dc5a Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 9 Apr 2026 13:18:47 +0200 Subject: [PATCH 055/115] revamp the stableBeamFilter to be more general for toggles --- .../LhcFillsFilter/stableBeamFilter.js | 28 ++++++++----------- .../common/filters/ToggleFilterModel.js | 8 ++++-- .../ActiveColumns/lhcFillsActiveColumns.js | 2 +- .../Overview/LhcFillsOverviewModel.js | 6 +--- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js b/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js index b4429c002c..cb92baa93b 100644 --- a/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js +++ b/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js @@ -16,34 +16,30 @@ import { switchInput } from '../../common/form/switchInput.js'; import { radioButton } from '../../common/form/inputs/radioButton.js'; /** - * Display a toggle switch or radio buttons to filter stable beams only + * Display a toggle switch or radio buttons for toggle filters * - * @param {StableBeamFilterModel} stableBeamFilterModel the stableBeamFilterModel + * @param {ToggleFilterModel} toggleFilterModel a ToggleFilterModel + * @param {name} toggleFilterModel the name used to identify and label the filter * @param {boolean} radioButtonMode define whether or not to return radio buttons or a switch. * @returns {Component} the toggle switch */ -export const toggleStableBeamOnlyFilter = (stableBeamFilterModel, radioButtonMode = false) => { - const name = 'stableBeamsOnlyRadio'; - const labelOff = 'OFF'; - const labelOn = 'ON'; +export const toggleStableBeamOnlyFilter = (toggleFilterModel, name, radioButtonMode = false) => { if (radioButtonMode) { return h('.form-group-header.flex-row.w-100', [ radioButton({ - label: labelOff, - isChecked: !stableBeamFilterModel.isStableBeamsOnly(), - action: () => stableBeamFilterModel.setStableBeamsOnly(false), + label: 'OFF', + isChecked: !toggleFilterModel.isToggled(), + action: () => toggleFilterModel.toggle(), name: name, }), radioButton({ - label: labelOn, - isChecked: stableBeamFilterModel.isStableBeamsOnly(), - action: () => stableBeamFilterModel.setStableBeamsOnly(true), + label: 'ON', + isChecked: toggleFilterModel.isToggled(), + action: () => toggleFilterModel.toggle(), name: name, }), ]); - } else { - return switchInput(stableBeamFilterModel.isStableBeamsOnly(), (newState) => { - stableBeamFilterModel.setStableBeamsOnly(newState); - }, { labelAfter: 'STABLE BEAM ONLY' }); } + + return switchInput(toggleFilterModel.isToggled(), (newState) => toggleFilterModel.toggle(newState), { labelAfter: name }); }; diff --git a/lib/public/components/Filters/common/filters/ToggleFilterModel.js b/lib/public/components/Filters/common/filters/ToggleFilterModel.js index 7ce76f202c..46c16c7f80 100644 --- a/lib/public/components/Filters/common/filters/ToggleFilterModel.js +++ b/lib/public/components/Filters/common/filters/ToggleFilterModel.js @@ -20,11 +20,13 @@ export class ToggleFilterModel extends SelectionModel { * Constructor * @param {boolean} toggledByDefault If the filter should be toggled by default */ - constructor(toggledByDefault) { + constructor(toggledByDefault = false) { super({ availableOptions: [{ value: true }, { value: false }], defaultSelection: [{ value: toggledByDefault }], multiple: false, allowEmpty: false }); + + this._toggledByDefault = toggledByDefault; } /** @@ -47,9 +49,9 @@ export class ToggleFilterModel extends SelectionModel { /** * Overrides SelectionModel.isEmpty to respect the fact that toggle filters cannot be empty. - * @returns {boolean} true if the current value of the filter is false. + * @returns {boolean} true if the current toggle state is equal to the default toggle state. */ get isEmpty() { - return this.current === false; + return this.current === this._toggledByDefault; } } diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index b2657c8cfd..86ea92d2ef 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -111,7 +111,7 @@ export const lhcFillsActiveColumns = { name: 'Stable Beams Only', visible: false, format: (boolean) => boolean ? 'On' : 'Off', - filter: (lhcFillModel) => toggleStableBeamOnlyFilter(lhcFillModel.filteringModel.get('hasStableBeams'), true), + filter: (lhcFillModel) => toggleStableBeamOnlyFilter(lhcFillModel.filteringModel.get('hasStableBeams'), 'stableBeamsOnlyRadio', true), }, stableBeamsDuration: { name: 'SB Duration', diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 17c2c459ae..8cd9d3f4f5 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -39,7 +39,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { fillNumbers: new RawTextFilterModel(), beamDuration: new TextComparisonFilterModel(), runDuration: new TextComparisonFilterModel(), - hasStableBeams: new ToggleFilterModel(false), + hasStableBeams: new ToggleFilterModel(stableBeamsOnly), stableBeamsStart: new TimeRangeFilterModel(), stableBeamsEnd: new TimeRangeFilterModel(), beamTypes: new BeamTypeFilterModel(), @@ -50,10 +50,6 @@ export class LhcFillsOverviewModel extends OverviewPageModel { this._filteringModel.visualChange$.bubbleTo(this); this.reset(false); - - if (stableBeamsOnly) { - this._filteringModel.get('hasStableBeams').setStableBeamsOnly(true); - } } /** From 0c3f7018d6d174abbf0add19340803c841d53f0c Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 9 Apr 2026 13:24:26 +0200 Subject: [PATCH 056/115] rename stableBeamFilter to toggleFilter --- .../LhcFillsFilter/StableBeamFilterModel.js | 76 ------------------- .../filters/toggleFilter.js} | 6 +- .../ActiveColumns/lhcFillsActiveColumns.js | 4 +- lib/public/views/LhcFills/Overview/index.js | 4 +- 4 files changed, 7 insertions(+), 83 deletions(-) delete mode 100644 lib/public/components/Filters/LhcFillsFilter/StableBeamFilterModel.js rename lib/public/components/Filters/{LhcFillsFilter/stableBeamFilter.js => common/filters/toggleFilter.js} (87%) diff --git a/lib/public/components/Filters/LhcFillsFilter/StableBeamFilterModel.js b/lib/public/components/Filters/LhcFillsFilter/StableBeamFilterModel.js deleted file mode 100644 index 1bc3f8aed2..0000000000 --- a/lib/public/components/Filters/LhcFillsFilter/StableBeamFilterModel.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @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 { SelectionModel } from '../../common/selection/SelectionModel.js'; - -/** - * Stable beam filter model - * Holds true or false value - */ -export class StableBeamFilterModel extends SelectionModel { - /** - * Constructor - */ - constructor() { - super({ availableOptions: [{ value: true }, { value: false }], - defaultSelection: [{ value: false }], - multiple: false, - allowEmpty: false }); - } - - /** - * Returns true if the current filter is stable beams only - * - * @return {boolean} true if filter is stable beams only - */ - isStableBeamsOnly() { - return this.current; - } - - /** - * Sets the current filter to stable beams only - * - * @param {boolean} value value to set this stable beams only filter with - * @return {void} - */ - setStableBeamsOnly(value) { - this.select({ value }); - } - - /** - * Get normalized selected option - */ - get normalized() { - return this.current; - } - - /** - * Overrides SelectionModel.isEmpty to respect the fact that stable beam filter cannot be empty. - * @returns {boolean} true if the current value of the filter is false. - */ - get isEmpty() { - return this.current === false; - } - - /** - * Reset the filter to default values - * - * @return {void} - */ - resetDefaults() { - if (!this.isEmpty) { - this.reset(); - this.notify(); - } - } -} diff --git a/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js b/lib/public/components/Filters/common/filters/toggleFilter.js similarity index 87% rename from lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js rename to lib/public/components/Filters/common/filters/toggleFilter.js index cb92baa93b..e5cd4c69dd 100644 --- a/lib/public/components/Filters/LhcFillsFilter/stableBeamFilter.js +++ b/lib/public/components/Filters/common/filters/toggleFilter.js @@ -12,8 +12,8 @@ */ import { h } from '/js/src/index.js'; -import { switchInput } from '../../common/form/switchInput.js'; -import { radioButton } from '../../common/form/inputs/radioButton.js'; +import { switchInput } from '../../../common/form/switchInput.js'; +import { radioButton } from '../../../common/form/inputs/radioButton.js'; /** * Display a toggle switch or radio buttons for toggle filters @@ -23,7 +23,7 @@ import { radioButton } from '../../common/form/inputs/radioButton.js'; * @param {boolean} radioButtonMode define whether or not to return radio buttons or a switch. * @returns {Component} the toggle switch */ -export const toggleStableBeamOnlyFilter = (toggleFilterModel, name, radioButtonMode = false) => { +export const toggleFilter = (toggleFilterModel, name, radioButtonMode = false) => { if (radioButtonMode) { return h('.form-group-header.flex-row.w-100', [ radioButton({ diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 86ea92d2ef..6d2968b1c8 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -23,7 +23,7 @@ import { buttonLinkWithDropdown } from '../../../components/common/selection/inf import { infologgerLinksComponents } from '../../../components/common/externalLinks/infologgerLinksComponents.js'; import { formatBeamType } from '../../../utilities/formatting/formatBeamType.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; -import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFillsFilter/stableBeamFilter.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js'; import { durationFilter } from '../../../components/Filters/LhcFillsFilter/durationFilter.js'; import { beamTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamTypeFilter.js'; @@ -111,7 +111,7 @@ export const lhcFillsActiveColumns = { name: 'Stable Beams Only', visible: false, format: (boolean) => boolean ? 'On' : 'Off', - filter: (lhcFillModel) => toggleStableBeamOnlyFilter(lhcFillModel.filteringModel.get('hasStableBeams'), 'stableBeamsOnlyRadio', true), + filter: (lhcFillModel) => toggleFilter(lhcFillModel.filteringModel.get('hasStableBeams'), 'stableBeamsOnlyRadio', true), }, stableBeamsDuration: { name: 'SB Duration', diff --git a/lib/public/views/LhcFills/Overview/index.js b/lib/public/views/LhcFills/Overview/index.js index e81409f06c..a29abf5145 100644 --- a/lib/public/views/LhcFills/Overview/index.js +++ b/lib/public/views/LhcFills/Overview/index.js @@ -18,7 +18,7 @@ import { lhcFillsActiveColumns } from '../ActiveColumns/lhcFillsActiveColumns.js import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; import { filtersPanelPopover } from '../../../components/Filters/common/filtersPanelPopover.js'; -import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFillsFilter/stableBeamFilter.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; const TABLEROW_HEIGHT = 53.3; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -50,7 +50,7 @@ const showLhcFillsTable = (lhcFillsOverviewModel) => { return [ h('.flex-row.header-container.g2.pv2', [ filtersPanelPopover(lhcFillsOverviewModel, lhcFillsActiveColumns), - toggleStableBeamOnlyFilter(lhcFillsOverviewModel.filteringModel.get('hasStableBeams')), + toggleFilter(lhcFillsOverviewModel.filteringModel.get('hasStableBeams'), 'STABLE BEAM ONLY'), ]), h('.w-100.flex-column', [ table(lhcFillsOverviewModel.items, lhcFillsActiveColumns, null, { tableClasses: '.table-sm' }), From 1333c45b63d3bf4788e9533c11c4145e46ca8b12 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 10:27:12 +0200 Subject: [PATCH 057/115] feat: turn mcReproducibleNotAsBad into a toggleFilter --- .../views/Runs/Overview/RunsWithQcModel.js | 26 +++++++------------ .../RunsPerDataPassOverviewModel.js | 4 +-- .../RunsPerDataPassOverviewPage.js | 7 ++--- .../RunsPerLhcPeriodOverviewPage.js | 7 ++--- .../RunsPerSimulationPassOverviewPage.js | 7 ++--- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index ad09ea4718..5153ea63d4 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -43,6 +43,7 @@ const qcFlagsExportConfigurationFactory = (detectors) => Object.fromEntries(dete import { ObservableData } from '../../../utilities/ObservableData.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; +import { ToggleFilterModel } from '../../../components/Filters/common/filters/ToggleFilterModel.js'; /** * Merge QC summaries @@ -70,7 +71,11 @@ export class RunsWithQcModel extends RunsOverviewModel { constructor(model) { super(model); - this._mcReproducibleAsNotBad = false; + this._mcReproducibleAsNotBad = new ToggleFilterModel(); + + this.mcReproducibleAsNotBad.observe(() => { + this.load(); + }) this._runDetectorsSelectionModel = new RunDetectorsSelectionModel(); this._runDetectorsSelectionModel.bubbleTo(this); @@ -91,27 +96,16 @@ export class RunsWithQcModel extends RunsOverviewModel { getRootEndpoint() { const filter = {}; filter.detectorsQc = { - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), }; return buildUrl(super.getRootEndpoint(), { filter, include: { effectiveQcFlags: true } }); } - /** - * Set mcReproducibleAsNotBad - * - * @param {boolean} mcReproducibleAsNotBad new value - * @return {void} - */ - setMcReproducibleAsNotBad(mcReproducibleAsNotBad) { - this._mcReproducibleAsNotBad = mcReproducibleAsNotBad; - this.load(); - } - /** * Get mcReproducibleAsNotBad * - * @return {boolean} mcReproducibleAsNotBad + * @return {ToggleFilterModel} mcReproducibleAsNotBad */ get mcReproducibleAsNotBad() { return this._mcReproducibleAsNotBad; @@ -218,7 +212,7 @@ export class RunsWithQcModel extends RunsOverviewModel { detectorIds: detectors .filter(({ type }) => type === DetectorType.PHYSICAL) .map(({ id }) => id).join(','), - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), })); const { data: qcSummary2 } = await getRemoteData(buildUrl('/api/qcFlags/summary', { @@ -232,7 +226,7 @@ export class RunsWithQcModel extends RunsOverviewModel { operator: 'none', }, }, - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), })); this._qcSummary$.setCurrent(RemoteData.success(mergeQcSummaries([qcSummary1, qcSummary2]))); } catch (error) { diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 4e602e4ef1..645f380b9f 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -146,7 +146,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo const filter = { dataPassIds: [this._dataPassId] }; if (!gaqNotBadFilter.isEmpty) { filter.gaq = { - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), }; } @@ -363,7 +363,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo }); const url = buildUrl('/api/qcFlags/summary/gaq', { dataPassId: this._dataPassId, - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), runNumber: runNumber, }); await this._gaqSummarySources[runNumber].fetch(url); diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index 8f63fb608b..6f1e4008d8 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -38,7 +38,7 @@ import { iconCaretBottom } from '/js/src/icons.js'; import { BkpRoles } from '../../../domain/enums/BkpRoles.js'; import { getInelasticInteractionRateColumns } from '../ActiveColumns/getInelasticInteractionRateActiveColumns.js'; import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; -import { mcReproducibleAsNotBadToggle } from '../mcReproducibleAsNotBadToggle.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -229,10 +229,7 @@ export const RunsPerDataPassOverviewPage = ({ )), ]), ), - mcReproducibleAsNotBadToggle( - mcReproducibleAsNotBad, - () => perDataPassOverviewModel.setMcReproducibleAsNotBad(!mcReproducibleAsNotBad), - ), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), h('.mlauto', qcSummaryLegendTooltip()), h('#actions-dropdown-button', DropdownComponent( h('button.btn.btn-primary', h('.flex-row.g2', ['Actions', iconCaretBottom()])), diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js index 7526324b35..0256c1962e 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js @@ -27,8 +27,8 @@ import spinner from '../../../components/common/spinner.js'; import { getInelasticInteractionRateColumns } from '../ActiveColumns/getInelasticInteractionRateActiveColumns.js'; import { filtersPanelPopover } from '../../../components/Filters/common/filtersPanelPopover.js'; import { runNumbersFilter } from '../../../components/Filters/RunsFilter/runNumbersFilter.js'; -import { mcReproducibleAsNotBadToggle } from '../mcReproducibleAsNotBadToggle.js'; import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; const TABLEROW_HEIGHT = 62; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -113,10 +113,7 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel filtersPanelPopover(perLhcPeriodOverviewModel, activeColumns, { profile: 'runsPerLhcPeriod' }), h('.pl2#runOverviewFilter', runNumbersFilter(perLhcPeriodOverviewModel.filteringModel.get('runNumbers'))), h('h2', `Good, physics runs of ${lhcPeriodStatistics.lhcPeriod.name}`), - mcReproducibleAsNotBadToggle( - mcReproducibleAsNotBad, - () => perLhcPeriodOverviewModel.setMcReproducibleAsNotBad(!mcReproducibleAsNotBad), - ), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), exportTriggerAndModal(perLhcPeriodOverviewModel.exportModel, modalModel), ]), ...tabbedPanelComponent( diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js index 55d4cdb988..ada3335926 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js @@ -28,7 +28,7 @@ import { getInelasticInteractionRateColumns } from '../ActiveColumns/getInelasti import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; import { filtersPanelPopover } from '../../../components/Filters/common/filtersPanelPopover.js'; import { runNumbersFilter } from '../../../components/Filters/RunsFilter/runNumbersFilter.js'; -import { mcReproducibleAsNotBadToggle } from '../mcReproducibleAsNotBadToggle.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -100,10 +100,7 @@ export const RunsPerSimulationPassOverviewPage = ({ '.flex-row.g1.items-center', breadcrumbs([commonTitle, h('h2#breadcrumb-simulation-pass-name', simulationPass.name)]), ), - mcReproducibleAsNotBadToggle( - mcReproducibleAsNotBad, - () => perSimulationPassOverviewModel.setMcReproducibleAsNotBad(!mcReproducibleAsNotBad), - ), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), h('.mlauto', qcSummaryLegendTooltip()), exportTriggerAndModal(perSimulationPassOverviewModel.exportModel, modalModel, { autoMarginLeft: false }), frontLink( From 1365033d95763a451f790d0d3e8a9b12cb08a275 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 10:48:41 +0200 Subject: [PATCH 058/115] chore: remove mcreprodicebleAsNotBadToggle.js --- .../Overview/LhcFillsOverviewModel.js | 2 +- .../Runs/mcReproducibleAsNotBadToggle.js | 28 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 lib/public/views/Runs/mcReproducibleAsNotBadToggle.js diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 8cd9d3f4f5..8e292f9fbf 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -19,7 +19,7 @@ import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsT import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js'; import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js'; import { TimeRangeFilterModel } from '../../../components/Filters/RunsFilter/TimeRangeFilter.js'; -import { ToggleFilterModel } from '../../../components/Filters/common/filters/toggleFilterModel.js'; +import { ToggleFilterModel } from '../../../components/Filters/common/filters/ToggleFilterModel.js'; /** * Model for the LHC fills overview page diff --git a/lib/public/views/Runs/mcReproducibleAsNotBadToggle.js b/lib/public/views/Runs/mcReproducibleAsNotBadToggle.js deleted file mode 100644 index 636ed0f245..0000000000 --- a/lib/public/views/Runs/mcReproducibleAsNotBadToggle.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @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 { switchInput } from '../../components/common/form/switchInput.js'; -import { h } from '/js/src/index.js'; - -/** - * Display a toggle switch to change interpretation of MC.Reproducible flag type from bad to not-bad - * - * @param {boolean} value current value - * @param {function} onChange to be called when switching - * @returns {Component} the toggle switch - */ -export const mcReproducibleAsNotBadToggle = (value, onChange) => h('#mcReproducibleAsNotBadToggle', switchInput( - value, - onChange, - { labelAfter: h('em', 'MC.R as not-bad') }, -)); From 39d282af3e780f70b996c4ba211afeb51cb2230b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 11:15:50 +0200 Subject: [PATCH 059/115] add optional ID to togglefilter.js --- .../components/Filters/common/filters/toggleFilter.js | 8 ++++---- lib/public/components/common/form/switchInput.js | 4 ++-- .../Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js | 2 +- .../Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js | 2 +- .../RunsPerSimulationPassOverviewPage.js | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/public/components/Filters/common/filters/toggleFilter.js b/lib/public/components/Filters/common/filters/toggleFilter.js index e5cd4c69dd..064ce2f0f9 100644 --- a/lib/public/components/Filters/common/filters/toggleFilter.js +++ b/lib/public/components/Filters/common/filters/toggleFilter.js @@ -23,23 +23,23 @@ import { radioButton } from '../../../common/form/inputs/radioButton.js'; * @param {boolean} radioButtonMode define whether or not to return radio buttons or a switch. * @returns {Component} the toggle switch */ -export const toggleFilter = (toggleFilterModel, name, radioButtonMode = false) => { +export const toggleFilter = (toggleFilterModel, name, id, radioButtonMode = false) => { if (radioButtonMode) { return h('.form-group-header.flex-row.w-100', [ radioButton({ label: 'OFF', isChecked: !toggleFilterModel.isToggled(), action: () => toggleFilterModel.toggle(), - name: name, + name, }), radioButton({ label: 'ON', isChecked: toggleFilterModel.isToggled(), action: () => toggleFilterModel.toggle(), - name: name, + name, }), ]); } - return switchInput(toggleFilterModel.isToggled(), (newState) => toggleFilterModel.toggle(newState), { labelAfter: name }); + return switchInput(toggleFilterModel.isToggled(), () => toggleFilterModel.toggle(), { labelAfter: name, id }); }; diff --git a/lib/public/components/common/form/switchInput.js b/lib/public/components/common/form/switchInput.js index ad7f7f8135..f06cb5154a 100644 --- a/lib/public/components/common/form/switchInput.js +++ b/lib/public/components/common/form/switchInput.js @@ -32,7 +32,7 @@ import { h } from '/js/src/index.js'; * @return {Component} the switch component */ export const switchInput = (value, onChange, options) => { - const { key, labelAfter, labelBefore, color } = options || {}; + const { key, labelAfter, labelBefore, color, id } = options || {}; const attributes = { ...key ? { key } : {} }; return h( @@ -40,7 +40,7 @@ export const switchInput = (value, onChange, options) => { attributes, [ labelBefore, - h('.switch', [ + h('.switch', { id }, [ h('input', { onchange: (e) => onChange(e.target.checked), type: 'checkbox', diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index 6f1e4008d8..ab11a81970 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -229,7 +229,7 @@ export const RunsPerDataPassOverviewPage = ({ )), ]), ), - toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad'), 'mcReproducibleAsNotBadToggle'), h('.mlauto', qcSummaryLegendTooltip()), h('#actions-dropdown-button', DropdownComponent( h('button.btn.btn-primary', h('.flex-row.g2', ['Actions', iconCaretBottom()])), diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js index 0256c1962e..973500b106 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js @@ -113,7 +113,7 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel filtersPanelPopover(perLhcPeriodOverviewModel, activeColumns, { profile: 'runsPerLhcPeriod' }), h('.pl2#runOverviewFilter', runNumbersFilter(perLhcPeriodOverviewModel.filteringModel.get('runNumbers'))), h('h2', `Good, physics runs of ${lhcPeriodStatistics.lhcPeriod.name}`), - toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad'), 'mcReproducibleAsNotBadToggle'), exportTriggerAndModal(perLhcPeriodOverviewModel.exportModel, modalModel), ]), ...tabbedPanelComponent( diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js index ada3335926..5127c8df20 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js @@ -100,7 +100,7 @@ export const RunsPerSimulationPassOverviewPage = ({ '.flex-row.g1.items-center', breadcrumbs([commonTitle, h('h2#breadcrumb-simulation-pass-name', simulationPass.name)]), ), - toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad')), + toggleFilter(mcReproducibleAsNotBad, h('em', 'MC.R as not-bad'), 'mcReproducibleAsNotBadToggle'), h('.mlauto', qcSummaryLegendTooltip()), exportTriggerAndModal(perSimulationPassOverviewModel.exportModel, modalModel, { autoMarginLeft: false }), frontLink( From 306d3d053d836a648663e67e81467800df78737b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 13:25:39 +0200 Subject: [PATCH 060/115] feat: create gaqFilterModel --- .../Filters/RunsFilter/GaqFilterModel.js | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lib/public/components/Filters/RunsFilter/GaqFilterModel.js diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js new file mode 100644 index 0000000000..f10a73564d --- /dev/null +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -0,0 +1,66 @@ +import { FilterModel } from '../common/FilterModel.js'; +import { NumericalComparisonFilterModel } from '../common/filters/NumericalComparisonFilterModel.js'; + +/** + * Time-range filter model + */ +export class GaqFilterModel extends FilterModel { + /** + * Constructor + * @param {ToggleFilterModel} mcReproducibleAsNotBad model that determines if a 'not bad' status was reproduceable for a Monte Carlo. + * This param is required as many other filters models need to make use of the same ToggleFilterModel instance + */ + constructor(mcReproducibleAsNotBad) { + super(); + + this._notBadFraction = new NumericalComparisonFilterModel({ scale: 0.01, integer: false }); + this._addSubmodel(this._notBadFraction); + this._mcReproducibleAsNotBad = mcReproducibleAsNotBad; + this._addSubmodel(this._mcReproducibleAsNotBad); + } + + /** + * @inheritDoc + */ + reset() { + this._notBadFraction.reset(); + } + + /** + * @inheritDoc + */ + get isEmpty() { + return this._notBadFraction.isEmpty; + } + + /** + * @inheritDoc + */ + get normalized() { + const normalized = { notBadFraction: this._notBadFraction.normalized }; + + if (!this.isEmpty) { + normalized.mcReproducibleAsNotBad = this._mcReproducibleAsNotBad.isToggled(); + } + + return normalized; + } + + /** + * Return the underlying notBadFraction model + * + * @return {NumericalComparisonFilterModel} the filter model + */ + get notBadFraction() { + return this._notBadFraction; + } + + /** + * Return the underlying mcReproducibleAsNotBad model + * + * @return {ToggleFilterModel} the filter model + */ + get mcReproducibleAsNotBad() { + return this._mcReproducibleAsNotBad; + } +} From 7d7f6c929bd1b0cb9270ee9122477772366f49c8 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 13:26:15 +0200 Subject: [PATCH 061/115] feat: replace 'gaq[notBadFraction]' with a gaqFilterModel --- .../Filters/RunsFilter/GaqFilterModel.js | 13 +++++++++++++ .../views/Runs/Overview/RunsWithQcModel.js | 2 +- .../RunsPerDataPassOverviewModel.js | 16 +++------------- .../RunsPerDataPassOverviewPage.js | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js index f10a73564d..8d42fd8b90 100644 --- a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -1,3 +1,16 @@ +/** + * @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 { FilterModel } from '../common/FilterModel.js'; import { NumericalComparisonFilterModel } from '../common/filters/NumericalComparisonFilterModel.js'; diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 5153ea63d4..3446359246 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -75,7 +75,7 @@ export class RunsWithQcModel extends RunsOverviewModel { this.mcReproducibleAsNotBad.observe(() => { this.load(); - }) + }); this._runDetectorsSelectionModel = new RunDetectorsSelectionModel(); this._runDetectorsSelectionModel.bubbleTo(this); diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 645f380b9f..9928055705 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -19,11 +19,11 @@ import { FixedPdpBeamTypeRunsOverviewModel } from '../Overview/FixedPdpBeamTypeR import { jsonPatch } from '../../../utilities/fetch/jsonPatch.js'; import { jsonPut } from '../../../utilities/fetch/jsonPut.js'; import { SkimmingStage } from '../../../domain/enums/SkimmingStage.js'; -import { NumericalComparisonFilterModel } from '../../../components/Filters/common/filters/NumericalComparisonFilterModel.js'; import { jsonFetch } from '../../../utilities/fetch/jsonFetch.js'; import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; import { RemoteDataSource } from '../../../utilities/fetch/RemoteDataSource.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; +import { GaqFilterModel } from '../../../components/Filters/RunsFilter/GaqFilterModel.js'; const ALL_CPASS_PRODUCTIONS_REGEX = /cpass\d+/; const DETECTOR_NAMES_NOT_IN_CPASSES = ['EVS']; @@ -68,10 +68,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo this._skimmableRuns$ = new ObservableData(RemoteData.notAsked()); this._skimmableRuns$.bubbleTo(this); - this._filteringModel.put('gaq[notBadFraction]', new NumericalComparisonFilterModel({ - scale: 0.01, - integer: false, - })); + this._filteringModel.put('gaq', new GaqFilterModel(this._mcReproducibleAsNotBad)); this._freezeOrUnfreezeActionState$ = new ObservableData(RemoteData.notAsked()); this._freezeOrUnfreezeActionState$.bubbleTo(this); @@ -142,14 +139,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo * @inheritdoc */ getRootEndpoint() { - const gaqNotBadFilter = this._filteringModel.get('gaq[notBadFraction]'); - const filter = { dataPassIds: [this._dataPassId] }; - if (!gaqNotBadFilter.isEmpty) { - filter.gaq = { - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), - }; - } - + const filter = { ...this._filteringModel.normalized, dataPassIds: [this._dataPassId] }; return buildUrl(super.getRootEndpoint(), { filter }); } diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index ab11a81970..dda853f084 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -191,7 +191,7 @@ export const RunsPerDataPassOverviewPage = ({ }); }, filter: ({ filteringModel }) => numericalComparisonFilter( - filteringModel.get('gaq[notBadFraction]'), + filteringModel.get('gaq').notBadFraction, { step: 0.1, selectorPrefix: 'gaqNotBadFraction' }, ), filterTooltip: 'not-bad fraction expressed as a percentage', From accd756db8db0ea796a4ab23f12eb01c66295ced Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 13:50:10 +0200 Subject: [PATCH 062/115] feat: create MultiCompositionFilterModel --- .../RunsFilter/MultiCompositionFilterModel.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js diff --git a/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js new file mode 100644 index 0000000000..9428614a91 --- /dev/null +++ b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js @@ -0,0 +1,55 @@ +/** + * @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 { FilterModel } from '../common/FilterModel.js'; + +/** + * Time-range filter model + */ +export class MultiCompositionFilterModel extends FilterModel { + /** + * Constructor + */ + constructor() { + super(); + this._detectors = {}; + } + + putFilter(id, filterModel) { + this._detectors[id] = filterModel; + this._addSubmodel(filterModel); + } + + reset() { + Object.values(this._detectors).forEach((detector) => detector.reset()); + } + + get isEmpty() { + Object.values(this._detectors).every((detector) => detector.isEmpty()); + } + + /** + * @inheritDoc + */ + get normalized() { + const normalized = {}; + + for (const [id, detector] of Object.entries(this._detectors)) { + if (!detector.isEmpty) { + normalized[id] = detector.normalized; + } + } + + return normalized; + } +} From d7d617791b8848e1a5b66d96719da43f984e1a25 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 18:07:27 +0200 Subject: [PATCH 063/115] improve MultiCompositionFiltermodel --- .../RunsFilter/MultiCompositionFilterModel.js | 51 ++++++++++++++++--- .../runDetectorsAsyncQcActiveColumns.js | 2 +- .../views/Runs/Overview/RunsWithQcModel.js | 24 ++++----- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js index 9428614a91..96e47e871d 100644 --- a/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js @@ -19,23 +19,60 @@ import { FilterModel } from '../common/FilterModel.js'; export class MultiCompositionFilterModel extends FilterModel { /** * Constructor + * @param {Object} filters the filters that will make up the composite filter */ - constructor() { + constructor(filters = {}) { super(); - this._detectors = {}; + + /** + * @type {Object} + */ + this._filters = {}; + + Object.entries(filters).forEach(([key, filter]) => this.putFilter(key, filter)); } - putFilter(id, filterModel) { - this._detectors[id] = filterModel; + /** + * Return a subfilter by key + * + * @param {string} key the key of the subfilter + * @return {FilterModel} the subfilter + */ + putFilter(key, filterModel) { + if (key in this._filters) { + return; + } + + this._filters[key] = filterModel; this._addSubmodel(filterModel); } + /** + * Add new subfilter + * + * @param {string} key key of the subfilter + * @param {FilterModel} filter the the subfilter + */ + getFilter(key) { + if (!(key in this._filters)) { + throw new Error(`No filter found with key ${key}`); + } + + return this._filters[key]; + } + + /** + * @inheritDoc + */ reset() { - Object.values(this._detectors).forEach((detector) => detector.reset()); + Object.values(this._filters).forEach((filter) => filter.reset()); } + /** + * @inheritDoc + */ get isEmpty() { - Object.values(this._detectors).every((detector) => detector.isEmpty()); + return Object.values(this._filters).every((filter) => filter.isEmpty); } /** @@ -44,7 +81,7 @@ export class MultiCompositionFilterModel extends FilterModel { get normalized() { const normalized = {}; - for (const [id, detector] of Object.entries(this._detectors)) { + for (const [id, detector] of Object.entries(this._filters)) { if (!detector.isEmpty) { normalized[id] = detector.normalized; } diff --git a/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js index f4497010c4..b2069d0886 100644 --- a/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js @@ -161,7 +161,7 @@ export const createRunDetectorsAsyncQcActiveColumns = ( visible: false, profiles: profile, filter: (filteringModel) => { - const filterModel = filteringModel.get(`detectorsQc[_${dplDetectorId}][notBadFraction]`); + const filterModel = filteringModel.get('detectorsQc').getFilter(`_${dplDetectorId}`).getFilter('notBadFraction'); return filterModel ? numericalComparisonFilter(filterModel, { step: 0.1, selectorPrefix: `detectorsQc-for-${dplDetectorId}-notBadFraction` }) : null; diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 3446359246..5fec9ad7e2 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -44,6 +44,7 @@ import { ObservableData } from '../../../utilities/ObservableData.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; import { ToggleFilterModel } from '../../../components/Filters/common/filters/ToggleFilterModel.js'; +import { MultiCompositionFilterModel } from '../../../components/Filters/RunsFilter/MultiCompositionFilterModel.js'; /** * Merge QC summaries @@ -88,18 +89,15 @@ export class RunsWithQcModel extends RunsOverviewModel { verticalScrollEnabled: true, freezeFirstColumn: true, }); + + this._filteringModel.put('detectorsQc', new MultiCompositionFilterModel({ mcReproducibleAsNotBad: this._mcReproducibleAsNotBad })); } /** * @inheritdoc */ getRootEndpoint() { - const filter = {}; - filter.detectorsQc = { - mcReproducibleAsNotBad: this._mcReproducibleAsNotBad.isToggled(), - }; - - return buildUrl(super.getRootEndpoint(), { filter, include: { effectiveQcFlags: true } }); + return buildUrl(super.getRootEndpoint(), { include: { effectiveQcFlags: true } }); } /** @@ -135,13 +133,15 @@ export class RunsWithQcModel extends RunsOverviewModel { * @param {ObservableData>} detectors$ detectors remote data observable */ registerDetectorsNotBadFractionFilterModels(detectors$) { + const detectorsQc = this._filteringModel.get('detectorsQc'); + detectors$.observe((observableData) => observableData.getCurrent().apply({ - Success: (detectors) => detectors.forEach(({ id }) => { - this._filteringModel.put(`detectorsQc[_${id}][notBadFraction]`, new NumericalComparisonFilterModel({ - scale: 0.01, - integer: false, - })); - }), + Success: (detectors) => + detectors.forEach(({ id }) => + detectorsQc.putFilter( + `_${id}`, // This should probably be changed to a flat name in the future + new MultiCompositionFilterModel({ notBadFraction: new NumericalComparisonFilterModel({ scale: 0.01, integer: false }) }), + )), })); } From 6df07ffeff6bad8745ab109635baf52ed293e43b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 15 Apr 2026 18:07:52 +0200 Subject: [PATCH 064/115] change tests to be compatible with the mc filter now being clearable --- test/public/runs/runsPerDataPass.overview.test.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 5eeef9c018..2ff7dfc4ac 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -523,8 +523,6 @@ module.exports = () => { it('should successfully apply gaqNotBadFraction filters', async () => { await navigateToRunsPerDataPass(page, 2, 1, 3); - await pressElement(page, '#openFilterToggle', true); - await page.waitForSelector('#gaqNotBadFraction-operator'); await page.select('#gaqNotBadFraction-operator', '<='); await fillInput(page, '#gaqNotBadFraction-operand', '80', ['change']); @@ -533,7 +531,6 @@ module.exports = () => { await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); await expectColumnValues(page, 'runNumber', []); - await pressElement(page, '#openFilterToggle', true); await pressElement(page, '#reset-filters', true); await expectColumnValues(page, 'runNumber', ['108', '107', '106']); }); @@ -542,12 +539,8 @@ module.exports = () => { await page.waitForSelector('#detectorsQc-for-1-notBadFraction-operator'); await page.select('#detectorsQc-for-1-notBadFraction-operator', '<='); await fillInput(page, '#detectorsQc-for-1-notBadFraction-operand', '90', ['change']); - await expectColumnValues(page, 'runNumber', ['106']); - - await pressElement(page, '#mcReproducibleAsNotBadToggle input', true); await expectColumnValues(page, 'runNumber', ['107', '106']); - await pressElement(page, '#openFilterToggle', true); await pressElement(page, '#reset-filters', true); await expectColumnValues(page, 'runNumber', ['108', '107', '106']); }); From 95773bd571b9588852ce6a341ec31a1c968c5e3d Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 16 Apr 2026 10:32:50 +0200 Subject: [PATCH 065/115] feat: flatten the detectorsQc[id][NotBadFraction] to detectorsQcNotBadFraction[id] --- lib/domain/dtos/filters/RunFilterDto.js | 4 ++-- .../runDetectorsAsyncQcActiveColumns.js | 2 +- .../views/Runs/Overview/RunsWithQcModel.js | 13 ++++------- lib/usecases/run/GetAllRunsUseCase.js | 23 +++++++------------ 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index c66a194778..e27c523ab3 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -99,10 +99,10 @@ exports.RunFilterDto = Joi.object({ mcReproducibleAsNotBad: Joi.boolean().optional(), }), - detectorsQc: Joi.object() + detectorsQcNotBadFraction: Joi.object() .pattern( Joi.string().regex(/^_\d+$/), // Detector id with '_' prefix - Joi.object({ notBadFraction: FloatComparisonDto }), + FloatComparisonDto, ) .keys({ mcReproducibleAsNotBad: Joi.boolean().optional(), diff --git a/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js index b2069d0886..2647f8589a 100644 --- a/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js @@ -161,7 +161,7 @@ export const createRunDetectorsAsyncQcActiveColumns = ( visible: false, profiles: profile, filter: (filteringModel) => { - const filterModel = filteringModel.get('detectorsQc').getFilter(`_${dplDetectorId}`).getFilter('notBadFraction'); + const filterModel = filteringModel.get('detectorsQcNotBadFraction').getFilter(`_${dplDetectorId}`); return filterModel ? numericalComparisonFilter(filterModel, { step: 0.1, selectorPrefix: `detectorsQc-for-${dplDetectorId}-notBadFraction` }) : null; diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 5fec9ad7e2..75051af6e5 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -90,7 +90,8 @@ export class RunsWithQcModel extends RunsOverviewModel { freezeFirstColumn: true, }); - this._filteringModel.put('detectorsQc', new MultiCompositionFilterModel({ mcReproducibleAsNotBad: this._mcReproducibleAsNotBad })); + this._filteringModel + .put('detectorsQcNotBadFraction', new MultiCompositionFilterModel({ mcReproducibleAsNotBad: this._mcReproducibleAsNotBad })); } /** @@ -133,15 +134,11 @@ export class RunsWithQcModel extends RunsOverviewModel { * @param {ObservableData>} detectors$ detectors remote data observable */ registerDetectorsNotBadFractionFilterModels(detectors$) { - const detectorsQc = this._filteringModel.get('detectorsQc'); + const detectorsQcNotBadFraction = this._filteringModel.get('detectorsQcNotBadFraction'); detectors$.observe((observableData) => observableData.getCurrent().apply({ - Success: (detectors) => - detectors.forEach(({ id }) => - detectorsQc.putFilter( - `_${id}`, // This should probably be changed to a flat name in the future - new MultiCompositionFilterModel({ notBadFraction: new NumericalComparisonFilterModel({ scale: 0.01, integer: false }) }), - )), + Success: (detectors) => detectors.forEach(({ id }) => + detectorsQcNotBadFraction.putFilter(`_${id}`, new NumericalComparisonFilterModel({ scale: 0.01, integer: false }))), })); } diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index 819dae8095..20d3153f83 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -82,7 +82,7 @@ class GetAllRunsUseCase { inelasticInteractionRateAtMid, inelasticInteractionRateAtEnd, gaq, - detectorsQc, + detectorsQcNotBadFraction, beamModes, } = filter; @@ -386,28 +386,21 @@ class GetAllRunsUseCase { } } - if (detectorsQc) { + if (detectorsQcNotBadFraction) { const [dataPassId] = dataPassIds ?? []; const [simulationPassId] = simulationPassIds ?? []; const [lhcPeriodId] = lhcPeriodIds ?? []; - const { mcReproducibleAsNotBad } = detectorsQc; - delete detectorsQc.mcReproducibleAsNotBad; + const { mcReproducibleAsNotBad } = detectorsQcNotBadFraction; + delete detectorsQcNotBadFraction.mcReproducibleAsNotBad; - const dplDetectorIds = Object.keys(detectorsQc).map((id) => parseInt(id.slice(1), 10)); + const dplDetectorIds = Object.keys(detectorsQcNotBadFraction).map((id) => parseInt(id.slice(1), 10)); if (dplDetectorIds.length > 0) { - const qcSummary = await qcFlagSummaryService.getSummary( - { - dataPassId, - simulationPassId, - lhcPeriodId, - dplDetectorIds, - }, - { mcReproducibleAsNotBad }, - ); + const scope = { dataPassId, simulationPassId, lhcPeriodId, dplDetectorIds }; + const qcSummary = await qcFlagSummaryService.getSummary(scope, { mcReproducibleAsNotBad }); const runNumbers = Object.entries(qcSummary) .filter(([_, runSummary]) => { - const mask = Object.entries(detectorsQc).map(([prefixedDetectorId, { notBadFraction: { operator, limit } }]) => { + const mask = Object.entries(detectorsQcNotBadFraction).map(([prefixedDetectorId, { operator, limit }]) => { const dplDetectorId = parseInt(prefixedDetectorId.slice(1), 10); if (!(dplDetectorId in runSummary)) { return false; From 2ca9a26c6e95824c3ce82ec890923b1d3bd888ea Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 16 Apr 2026 10:39:11 +0200 Subject: [PATCH 066/115] test: update api tests for the new flat detectorsQcNotBadFraction name --- test/api/runs.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/api/runs.test.js b/test/api/runs.test.js index 083771bf02..36bc52673b 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -454,11 +454,11 @@ module.exports = () => { } }); - it('should successfully filter by detectors notBadFraction', async () => { + it('should successfully filter by detectorsQcNotBadFraction', async () => { const dataPassId = 1; { const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}` - + '&filter[detectorsQc][_1][notBadFraction][operator]=>&filter[detectorsQc][_1][notBadFraction][limit]=0.7'); + + '&filter[detectorsQcNotBadFraction][_1][operator]=>&filter[detectorsQcNotBadFraction][_1][limit]=0.7'); expect(response.status).to.equal(200); const { data: runs } = response.body; @@ -468,7 +468,7 @@ module.exports = () => { } { const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}` - + '&filter[detectorsQc][_1][notBadFraction][operator]=<&filter[detectorsQc][_1][notBadFraction][limit]=0.9&filter[detectorsQc][mcReproducibleAsNotBad]=true'); + + '&filter[detectorsQcNotBadFraction][_1][operator]=<&filter[detectorsQcNotBadFraction][_1][limit]=0.9&filter[detectorsQcNotBadFraction][mcReproducibleAsNotBad]=true'); expect(response.status).to.equal(200); const { data: runs } = response.body; @@ -478,8 +478,8 @@ module.exports = () => { } { const response = await request(server).get(`/api/runs?filter[dataPassIds][]=${dataPassId}` - + '&filter[detectorsQc][_1][notBadFraction][operator]=<&filter[detectorsQc][_1][notBadFraction][limit]=0.7' - + '&filter[detectorsQc][_16][notBadFraction][operator]=>&filter[detectorsQc][_16][notBadFraction][limit]=0.9' + + '&filter[detectorsQcNotBadFraction][_1][operator]=<&filter[detectorsQcNotBadFraction][_1][limit]=0.7' + + '&filter[detectorsQcNotBadFraction][_16][operator]=>&filter[detectorsQcNotBadFraction][_16][limit]=0.9' ); expect(response.status).to.equal(200); From 6f3de22079b4f065241d1818cd430e129850281e Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 09:48:09 +0200 Subject: [PATCH 067/115] chore: turn hasStableBeams back into a radiobutton --- .../views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 6d2968b1c8..4ed77f1766 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -111,7 +111,8 @@ export const lhcFillsActiveColumns = { name: 'Stable Beams Only', visible: false, format: (boolean) => boolean ? 'On' : 'Off', - filter: (lhcFillModel) => toggleFilter(lhcFillModel.filteringModel.get('hasStableBeams'), 'stableBeamsOnlyRadio', true), + filter: (lhcFillModel) => + toggleFilter(lhcFillModel.filteringModel.get('hasStableBeams'), 'stableBeamsOnlyRadio', 'stableBeamsOnlyRadio', true), }, stableBeamsDuration: { name: 'SB Duration', From de862929169fc50428a63da103e3d2efe68c3adb Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 11:26:05 +0200 Subject: [PATCH 068/115] chore: update jsdoc for gaqfilterModel and MultiCompositionFilterModel --- lib/public/components/Filters/RunsFilter/GaqFilterModel.js | 2 +- .../Filters/RunsFilter/MultiCompositionFilterModel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js index 8d42fd8b90..d3b012cde8 100644 --- a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -15,7 +15,7 @@ import { FilterModel } from '../common/FilterModel.js'; import { NumericalComparisonFilterModel } from '../common/filters/NumericalComparisonFilterModel.js'; /** - * Time-range filter model + * FilterModel that filters by the fraction of gaq that was not bad */ export class GaqFilterModel extends FilterModel { /** diff --git a/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js index 96e47e871d..0b278e1d4b 100644 --- a/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/MultiCompositionFilterModel.js @@ -14,7 +14,7 @@ import { FilterModel } from '../common/FilterModel.js'; /** - * Time-range filter model + * FilterModel that allows devs to create custom filters from multiple other filters during instantiation, or using putFilter */ export class MultiCompositionFilterModel extends FilterModel { /** From 83fb6f718d845e1010e2ef89555b1e82e42b4a96 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 12:05:50 +0200 Subject: [PATCH 069/115] chore improve comment explanation of mcReproducibleNotbad --- lib/public/components/Filters/RunsFilter/GaqFilterModel.js | 2 +- lib/public/views/Runs/Overview/RunsWithQcModel.js | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js index d3b012cde8..f715614e6e 100644 --- a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -21,7 +21,7 @@ export class GaqFilterModel extends FilterModel { /** * Constructor * @param {ToggleFilterModel} mcReproducibleAsNotBad model that determines if a 'not bad' status was reproduceable for a Monte Carlo. - * This param is required as many other filters models need to make use of the same ToggleFilterModel instance + * This param is required as multiple other filters models need to make use of the same ToggleFilterModel instance */ constructor(mcReproducibleAsNotBad) { super(); diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 75051af6e5..600825a9d2 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -72,12 +72,9 @@ export class RunsWithQcModel extends RunsOverviewModel { constructor(model) { super(model); + // This filter instance will be added as a sub-filter for a MultiCompositionFilter and a GaqFilter later. this._mcReproducibleAsNotBad = new ToggleFilterModel(); - this.mcReproducibleAsNotBad.observe(() => { - this.load(); - }); - this._runDetectorsSelectionModel = new RunDetectorsSelectionModel(); this._runDetectorsSelectionModel.bubbleTo(this); From aa550f8a9a2000620759d58a094e9da08c9df082 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 12:11:35 +0200 Subject: [PATCH 070/115] chore: update ToggleFilterModel jsdoc --- .../components/Filters/common/filters/ToggleFilterModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/components/Filters/common/filters/ToggleFilterModel.js b/lib/public/components/Filters/common/filters/ToggleFilterModel.js index 46c16c7f80..65c2ec797e 100644 --- a/lib/public/components/Filters/common/filters/ToggleFilterModel.js +++ b/lib/public/components/Filters/common/filters/ToggleFilterModel.js @@ -13,7 +13,7 @@ import { SelectionModel } from '../../../common/selection/SelectionModel.js'; /** - * Filtering model to handle raw text value + * SelectionModel that restricts the selection to a boolean toggle (true/false). */ export class ToggleFilterModel extends SelectionModel { /** From c5820a6b3ade19783fa367320e3c6c991734da63 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 15:55:23 +0200 Subject: [PATCH 071/115] remove unused import --- .../views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index f75e774d9a..91ab82290d 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -37,7 +37,6 @@ import { iconCaretBottom } from '/js/src/icons.js'; import { BkpRoles } from '../../../domain/enums/BkpRoles.js'; import { getInelasticInteractionRateColumns } from '../ActiveColumns/getInelasticInteractionRateActiveColumns.js'; import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; -import { mcReproducibleAsNotBadToggle } from '../mcReproducibleAsNotBadToggle.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; From 9c32b2eb4cd7ace2f807f9e906db44ef6116750b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 15:59:04 +0200 Subject: [PATCH 072/115] fix linting issues --- .../ActiveColumns/qcFlagTypesActiveColumns.js | 1 - .../Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js index d56e3b0ade..6f1ae72b84 100644 --- a/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js +++ b/lib/public/views/QcFlagTypes/ActiveColumns/qcFlagTypesActiveColumns.js @@ -15,7 +15,6 @@ import { h } from '/js/src/index.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; import { qcFlagTypeColoredBadge } from '../../../components/qcFlags/qcFlagTypeColoredBadge.js'; -import badFilterRadioButtons from '../../../components/Filters/QcFlagTypesFilter/bad.js'; import radioButtonFilter from '../../../components/Filters/common/filters/radioButtonFilter.js'; /** diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index 91ab82290d..59f396515c 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -37,6 +37,8 @@ import { iconCaretBottom } from '/js/src/icons.js'; import { BkpRoles } from '../../../domain/enums/BkpRoles.js'; import { getInelasticInteractionRateColumns } from '../ActiveColumns/getInelasticInteractionRateActiveColumns.js'; import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; +import { textInputFilter } from '../../../components/Filters/common/filters/textInputFilter.js'; +import { toggleFilter } from '../../../components/Filters/common/filters/toggleFilter.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -172,13 +174,8 @@ export const RunsPerDataPassOverviewPage = ({ ), }); }, - filter: ({ filteringModel }) => - numericalComparisonFilter( - filteringModel.get('gaq[notBadFraction]'), - { step: 0.1, selectorPrefix: 'gaqNotBadFraction' }, - ), - + numericalComparisonFilter(filteringModel.get('gaq').notBadFraction, { step: 0.1, selectorPrefix: 'gaqNotBadFraction' }), filterTooltip: 'not-bad fraction expressed as a percentage', profiles: ['runsPerDataPass'], }, From 75fe876fadc370ee11b4569978c8e81b25864383 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 17:04:35 +0200 Subject: [PATCH 073/115] feat: update parameters of togglefilterModel to include falseIsEmpty --- .../common/filters/ToggleFilterModel.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/public/components/Filters/common/filters/ToggleFilterModel.js b/lib/public/components/Filters/common/filters/ToggleFilterModel.js index 65c2ec797e..b5a98fd4a4 100644 --- a/lib/public/components/Filters/common/filters/ToggleFilterModel.js +++ b/lib/public/components/Filters/common/filters/ToggleFilterModel.js @@ -19,14 +19,17 @@ export class ToggleFilterModel extends SelectionModel { /** * Constructor * @param {boolean} toggledByDefault If the filter should be toggled by default + * @param {boolean} falseIsEmpty if true, will treat false as empty. */ - constructor(toggledByDefault = false) { - super({ availableOptions: [{ value: true }, { value: false }], + constructor(toggledByDefault = false, falseIsEmpty = false) { + super({ + availableOptions: [{ value: true }, { value: false }], defaultSelection: [{ value: toggledByDefault }], multiple: false, - allowEmpty: false }); + allowEmpty: false, + }); - this._toggledByDefault = toggledByDefault; + this._falseIsEmpty = falseIsEmpty; } /** @@ -48,10 +51,13 @@ export class ToggleFilterModel extends SelectionModel { } /** - * Overrides SelectionModel.isEmpty to respect the fact that toggle filters cannot be empty. - * @returns {boolean} true if the current toggle state is equal to the default toggle state. + * @inheritdoc */ get isEmpty() { - return this.current === this._toggledByDefault; + if (!this._falseIsEmpty) { + return this.current === false; + } + + return false; } } From 6c61766fedce9a06406c96e648dcb160bba99507 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 17 Apr 2026 17:09:34 +0200 Subject: [PATCH 074/115] test: update GetAllRunsUsecase.test.js to use the new flat detectorsQcNotBadFraction name --- test/lib/usecases/run/GetAllRunsUseCase.test.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/lib/usecases/run/GetAllRunsUseCase.test.js b/test/lib/usecases/run/GetAllRunsUseCase.test.js index 5b080d056c..f3f006a80a 100644 --- a/test/lib/usecases/run/GetAllRunsUseCase.test.js +++ b/test/lib/usecases/run/GetAllRunsUseCase.test.js @@ -831,7 +831,7 @@ module.exports = () => { query: { filter: { dataPassIds, - detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.7 } } }, + detectorsQcNotBadFraction: { '_1': { operator: '<', limit: 0.7 } }, }, }, }); @@ -843,7 +843,7 @@ module.exports = () => { query: { filter: { dataPassIds, - detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.8 } } }, + detectorsQcNotBadFraction: { '_1': { operator: '<', limit: 0.8 } }, }, }, }); @@ -855,7 +855,7 @@ module.exports = () => { query: { filter: { dataPassIds, - detectorsQc: { '_1': { notBadFraction: { operator: '<', limit: 0.9 } }, mcReproducibleAsNotBad: true }, + detectorsQcNotBadFraction: { '_1': { operator: '<', limit: 0.9 } }, mcReproducibleAsNotBad: true, }, }, }); @@ -867,9 +867,10 @@ module.exports = () => { query: { filter: { dataPassIds, - detectorsQc: { - '_2': { notBadFraction: { operator: '>', limit: 0.8 } }, - '_1': { notBadFraction: {operator: '<', limit: 0.8 } }, + detectorsQcNotBadFraction: + { + '_2': { operator: '>', limit: 0.8 }, + '_1': { operator: '<', limit: 0.8 }, }, }, }, From a3fe2e222d84c1a6402ad7dc42c19f467c64966e Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Mon, 20 Apr 2026 07:36:06 +0200 Subject: [PATCH 075/115] test: fixup misplacement of mcReproducibleAsNotBad in filters --- test/lib/usecases/run/GetAllRunsUseCase.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/usecases/run/GetAllRunsUseCase.test.js b/test/lib/usecases/run/GetAllRunsUseCase.test.js index f3f006a80a..febeae02aa 100644 --- a/test/lib/usecases/run/GetAllRunsUseCase.test.js +++ b/test/lib/usecases/run/GetAllRunsUseCase.test.js @@ -855,7 +855,7 @@ module.exports = () => { query: { filter: { dataPassIds, - detectorsQcNotBadFraction: { '_1': { operator: '<', limit: 0.9 } }, mcReproducibleAsNotBad: true, + detectorsQcNotBadFraction: { '_1': { operator: '<', limit: 0.9 }, mcReproducibleAsNotBad: true }, }, }, }); From 4e92c1e476ce268341c10de388901632107a2267 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Mon, 20 Apr 2026 11:08:28 +0200 Subject: [PATCH 076/115] feat: change GaqFilterModel.notBadFraction notification functionallity to only notify when notBadFraction is not empty. --- .../components/Filters/RunsFilter/GaqFilterModel.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js index f715614e6e..2ad715260e 100644 --- a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -29,7 +29,15 @@ export class GaqFilterModel extends FilterModel { this._notBadFraction = new NumericalComparisonFilterModel({ scale: 0.01, integer: false }); this._addSubmodel(this._notBadFraction); this._mcReproducibleAsNotBad = mcReproducibleAsNotBad; - this._addSubmodel(this._mcReproducibleAsNotBad); + + // _mcReproducableAsNotBad will only be added to the normalize call notBadFraction is not empty + // So, notifying when it is empty will just send an unneeded request. + this._mcReproducibleAsNotBad.visualChange$.bubbleTo(this._visualChange$); + this._mcReproducibleAsNotBad.observe(() => { + if (!this.notBadFraction.isEmpty) { + this.notify() + } + }); } /** From 4e9559b95a666aaf482d458199862653d5009d3d Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Mon, 20 Apr 2026 12:19:02 +0200 Subject: [PATCH 077/115] chore: fix linter problems --- .../components/Filters/RunsFilter/GaqFilterModel.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js index 2ad715260e..ef2b6be122 100644 --- a/lib/public/components/Filters/RunsFilter/GaqFilterModel.js +++ b/lib/public/components/Filters/RunsFilter/GaqFilterModel.js @@ -30,12 +30,14 @@ export class GaqFilterModel extends FilterModel { this._addSubmodel(this._notBadFraction); this._mcReproducibleAsNotBad = mcReproducibleAsNotBad; - // _mcReproducableAsNotBad will only be added to the normalize call notBadFraction is not empty - // So, notifying when it is empty will just send an unneeded request. + /** + * _mcReproducableAsNotBad will only be added to the normalize call notBadFraction is not empty + * So, notifying when it is empty will just send an unneeded request. + */ this._mcReproducibleAsNotBad.visualChange$.bubbleTo(this._visualChange$); this._mcReproducibleAsNotBad.observe(() => { if (!this.notBadFraction.isEmpty) { - this.notify() + this.notify(); } }); } From ee70acfb19e92a6ed0e7d8576f61d9fd77a7877c Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 10:35:20 +0200 Subject: [PATCH 078/115] chore: force all filters managed by FilteringModel to extend FilterModel or SelectionModel --- lib/public/components/Filters/common/FilteringModel.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index e937786456..ac3224ecc4 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -12,6 +12,8 @@ */ import { expandQueryLikeNestedKey } from '../../../utilities/expandNestedKey.js'; +import { SelectionModel } from '../../common/selection/SelectionModel.js'; +import { FilterModel } from './FilterModel.js'; import { Observable } from '/js/src/index.js'; /** @@ -118,6 +120,10 @@ export class FilteringModel extends Observable { return; } + if (!(filter instanceof FilterModel || filter instanceof SelectionModel)) { + throw new Error('Filter must extend FilterModel or SelectionModel'); + } + this._filters[key] = filter; this._filterModels.push(filter); filter.bubbleTo(this); From 3bc171a9bb5e617aea1183c486d2e0bb382e7edc Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 10:59:28 +0200 Subject: [PATCH 079/115] feat: update filteringModel to set filters to the url when they are updated. --- .../Filters/common/FilteringModel.js | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index ac3224ecc4..c56a9dbd38 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -11,6 +11,7 @@ * or submit itself to any jurisdiction. */ +import { buildUrl } from '@aliceo2/web-ui'; import { expandQueryLikeNestedKey } from '../../../utilities/expandNestedKey.js'; import { SelectionModel } from '../../common/selection/SelectionModel.js'; import { FilterModel } from './FilterModel.js'; @@ -23,17 +24,18 @@ export class FilteringModel extends Observable { /** * Constructor * + * @param {QueryRouter} router router that controls the application's page navigation * @param {Object} filters the filters with their label and model */ - constructor(filters) { + constructor(router, filters) { super(); - this._visualChange$ = new Observable(); + this._router = router; this._filters = filters; this._filterModels = Object.values(filters); for (const model of this._filterModels) { - model.bubbleTo(this); + model.observe(() => this.setFilterToURL()); model.visualChange$?.bubbleTo(this._visualChange$); } } @@ -107,6 +109,22 @@ export class FilteringModel extends Observable { return this._filters[key]; } + /** + * When the user updates the displayed Objects, the filters should be placed in the URL as well + * @returns {undefined} + */ + setFilterToURL() { + const { params } = this._router; + const newParams = { ...params }; + newParams.filter = this.normalized; + + if (this._pageIdentifiers.includes(params.page)) { + this._router.go(buildUrl('?', newParams), false, true); + } + + this.notify(); + } + /** * Add new filter * From 0957b9e123256015af71c47ff9441a0f136d0fdf Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 11:02:44 +0200 Subject: [PATCH 080/115] chore: mover router instantiation to before the pageModel instantiations --- lib/public/Model.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/public/Model.js b/lib/public/Model.js index 6818118c81..05e2dd2f2f 100644 --- a/lib/public/Model.js +++ b/lib/public/Model.js @@ -95,6 +95,12 @@ export default class Model extends Observable { this._appConfiguration$ = new Observable(); this._inputDebounceTime = INPUT_DEBOUNCE_TIME; + // Setup router + this.router = new QueryRouter(); + this.router.observe(this.handleLocationChange.bind(this)); + this.router.bubbleTo(this); + registerFrontLinkListener((e) => this.router.handleLinkEvent(e)); + // Models this.home = new HomePageModel(this); @@ -103,7 +109,7 @@ export default class Model extends Observable { this.lhcPeriods = new LhcPeriodsModel(this); this.lhcPeriods.bubbleTo(this); - this.dataPasses = new DataPassesModel(this); + this.dataPasses = new DataPassesModel(this.router); this.dataPasses.bubbleTo(this); this.qcFlags = new QcFlagsModel(this); @@ -178,12 +184,6 @@ export default class Model extends Observable { this.errorModel = new ErrorModel(); this.errorModel.bubbleTo(this); - // Setup router - this.router = new QueryRouter(); - this.router.observe(this.handleLocationChange.bind(this)); - this.router.bubbleTo(this); - registerFrontLinkListener((e) => this.router.handleLinkEvent(e)); - // Init pages this.handleLocationChange(); this.window.addEventListener('resize', debounce(() => this.notify(), 100)); From 23d02fc072d604fac6618c02df4e3d3d000dd19d Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 11:02:58 +0200 Subject: [PATCH 081/115] chore: pass router to the dataPassOverview models --- lib/public/views/DataPasses/DataPassesModel.js | 9 +++++---- .../DataPasses/DataPassesOverviewModel.js | 18 +++++++++++------- .../DataPassesPerLhcPeriodOverviewModel.js | 5 +++-- ...DataPassesPerSimulationPassOverviewModel.js | 5 +++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/public/views/DataPasses/DataPassesModel.js b/lib/public/views/DataPasses/DataPassesModel.js index 5d987b31d7..5af74e48be 100644 --- a/lib/public/views/DataPasses/DataPassesModel.js +++ b/lib/public/views/DataPasses/DataPassesModel.js @@ -21,14 +21,15 @@ import { DataPassesPerSimulationPassOverviewModel } from './PerSimulationPassOve export class DataPassesModel extends Observable { /** * The constructor of the model + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { - super(); + constructor(router) { + super(router); - this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(); + this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(); + this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router); this._perSimulationPassOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index b85cc052d7..d32b7eef67 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -22,15 +22,19 @@ import { OverviewPageModel } from '../../models/OverviewModel.js'; export class DataPassesOverviewModel extends OverviewPageModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._filteringModel = new FilteringModel({ - names: new TextTokensFilterModel(), - 'include[byName]': new SelectionFilterModel({ - availableOptions: NON_PHYSICS_PRODUCTIONS_NAMES_WORDS.map((word) => ({ label: word.toUpperCase(), value: word })), - }), - }); + this._filteringModel = new FilteringModel( + router, + { + names: new TextTokensFilterModel(), + 'include[byName]': new SelectionFilterModel({ + availableOptions: NON_PHYSICS_PRODUCTIONS_NAMES_WORDS.map((word) => ({ label: word.toUpperCase(), value: word })), + }), + }, + ); this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { diff --git a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js index dc125e1a94..ee7f580ef7 100644 --- a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js @@ -19,9 +19,10 @@ import { buildUrl } from '/js/src/index.js'; export class DataPassesPerLhcPeriodOverviewModel extends DataPassesOverviewModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { - super(); + constructor(router) { + super(router); this._lhcPeriodId = null; } diff --git a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js index 30fd3c616c..12fd208a39 100644 --- a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js +++ b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js @@ -21,9 +21,10 @@ import { DataPassesOverviewModel } from '../DataPassesOverviewModel.js'; export class DataPassesPerSimulationPassOverviewModel extends DataPassesOverviewModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { - super(); + constructor(router) { + super(router); this._simulationPass = new ObservableData(RemoteData.notAsked()); this._simulationPass.bubbleTo(this); } From 51b88275fbc6bd8e70fd5604adf480068fdbb04c Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:26:57 +0200 Subject: [PATCH 082/115] chore: pass router to the EnvironmentOverviewModel models --- .../Overview/EnvironmentOverviewModel.js | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index 8498a02d79..f49de81b3b 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -32,19 +32,22 @@ export class EnvironmentOverviewModel extends OverviewPageModel { constructor(model) { super(); - this._filteringModel = new FilteringModel({ - created: new TimeRangeInputModel(), - runNumbers: new RawTextFilterModel(), - statusHistory: new RawTextFilterModel(), - currentStatus: new SelectionFilterModel({ - availableOptions: Object.keys(StatusAcronym).map((status) => ({ - value: status, - label: coloredEnvironmentStatusComponent(status), - rawLabel: status, - })), - }), - ids: new RawTextFilterModel(), - }); + this._filteringModel = new FilteringModel( + model.router, + { + created: new TimeRangeInputModel(), + runNumbers: new RawTextFilterModel(), + statusHistory: new RawTextFilterModel(), + currentStatus: new SelectionFilterModel({ + availableOptions: Object.keys(StatusAcronym).map((status) => ({ + value: status, + label: coloredEnvironmentStatusComponent(status), + rawLabel: status, + })), + }), + ids: new RawTextFilterModel(), + }, + ); this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$?.bubbleTo(this); From 22efa5ff1adbcdc09e9eebf8264911a69e670ca8 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:30:45 +0200 Subject: [PATCH 083/115] chore: pass router to the EnvironmentOverviewModel models --- .../views/Home/Overview/HomePageModel.js | 2 +- lib/public/views/LhcFills/LhcFills.js | 2 +- .../Overview/LhcFillsOverviewModel.js | 26 +++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/public/views/Home/Overview/HomePageModel.js b/lib/public/views/Home/Overview/HomePageModel.js index 40b6cfac85..1826bc5b61 100644 --- a/lib/public/views/Home/Overview/HomePageModel.js +++ b/lib/public/views/Home/Overview/HomePageModel.js @@ -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.router, true); this._lhcFillsOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/LhcFills/LhcFills.js b/lib/public/views/LhcFills/LhcFills.js index 70b6c5eb3d..a9b4036ab0 100644 --- a/lib/public/views/LhcFills/LhcFills.js +++ b/lib/public/views/LhcFills/LhcFills.js @@ -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.router, true); this._overviewModel.bubbleTo(this); this._detailsModel = new LhcFillDetailsModel(); diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 8e292f9fbf..52c99c52c4 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -30,21 +30,25 @@ export class LhcFillsOverviewModel extends OverviewPageModel { /** * Constructor * + * @param {QueryRouter} router router that controls the application's page navigation * @param {boolean} [stableBeamsOnly=false] if true, overview will load stable beam only */ - constructor(stableBeamsOnly = false) { + constructor(router, stableBeamsOnly = false) { super(); - this._filteringModel = new FilteringModel({ - fillNumbers: new RawTextFilterModel(), - beamDuration: new TextComparisonFilterModel(), - runDuration: new TextComparisonFilterModel(), - hasStableBeams: new ToggleFilterModel(stableBeamsOnly), - stableBeamsStart: new TimeRangeFilterModel(), - stableBeamsEnd: new TimeRangeFilterModel(), - beamTypes: new BeamTypeFilterModel(), - schemeName: new RawTextFilterModel(), - }); + this._filteringModel = new FilteringModel( + router, + { + fillNumbers: new RawTextFilterModel(), + beamDuration: new TextComparisonFilterModel(), + runDuration: new TextComparisonFilterModel(), + hasStableBeams: new ToggleFilterModel(stableBeamsOnly), + stableBeamsStart: new TimeRangeFilterModel(), + stableBeamsEnd: new TimeRangeFilterModel(), + beamTypes: new BeamTypeFilterModel(), + schemeName: new RawTextFilterModel(), + }, + ); this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); From 325fb6ec229ff11723551824b10cadbd0709129f Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:33:14 +0200 Subject: [PATCH 084/115] chore: pass router to the LhcPeriodOverview model --- lib/public/Model.js | 2 +- lib/public/views/lhcPeriods/LhcPeriodsModel.js | 5 +++-- .../Overview/LhcPeriodsOverviewModel.js | 16 ++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/public/Model.js b/lib/public/Model.js index 05e2dd2f2f..3c1eacffa9 100644 --- a/lib/public/Model.js +++ b/lib/public/Model.js @@ -106,7 +106,7 @@ export default class Model extends Observable { this.home = new HomePageModel(this); this.home.bubbleTo(this); - this.lhcPeriods = new LhcPeriodsModel(this); + this.lhcPeriods = new LhcPeriodsModel(this.router); this.lhcPeriods.bubbleTo(this); this.dataPasses = new DataPassesModel(this.router); diff --git a/lib/public/views/lhcPeriods/LhcPeriodsModel.js b/lib/public/views/lhcPeriods/LhcPeriodsModel.js index 4f9d0ed185..b9ed51719e 100644 --- a/lib/public/views/lhcPeriods/LhcPeriodsModel.js +++ b/lib/public/views/lhcPeriods/LhcPeriodsModel.js @@ -20,11 +20,12 @@ import { LhcPeriodsOverviewModel } from './Overview/LhcPeriodsOverviewModel.js'; export class LhcPeriodsModel extends Observable { /** * The constructor of the model + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._overviewModel = new LhcPeriodsOverviewModel(); + this._overviewModel = new LhcPeriodsOverviewModel(router); this._overviewModel.bubbleTo(this); } diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index e6073eb68f..b989b60488 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -24,15 +24,19 @@ import { buildUrl } from '/js/src/index.js'; export class LhcPeriodsOverviewModel extends OverviewPageModel { /** * The constructor of the Overview model object + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._filteringModel = new FilteringModel({ - names: new TextTokensFilterModel(), - years: new TextTokensFilterModel(), - pdpBeamTypes: new TextTokensFilterModel(), - }); + this._filteringModel = new FilteringModel( + router, + { + names: new TextTokensFilterModel(), + years: new TextTokensFilterModel(), + pdpBeamTypes: new TextTokensFilterModel(), + }, + ); this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { From 69521dc44b35c44525ab870aa320c532417c7de5 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:35:07 +0200 Subject: [PATCH 085/115] chore: pass router to the LogsOverview model --- .../views/Logs/Overview/LogsOverviewModel.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 4cb565e9b9..eef752c91e 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -38,16 +38,19 @@ export class LogsOverviewModel extends Observable { constructor(model, excludeAnonymous = false) { super(); - this._filteringModel = new FilteringModel({ - author: new AuthorFilterModel(), - title: new RawTextFilterModel(), - content: new RawTextFilterModel(), - tags: new TagFilterModel(tagsProvider.items$), - runNumbers: new RawTextFilterModel(), - environmentIds: new RawTextFilterModel(), - fillNumbers: new RawTextFilterModel(), - created: new TimeRangeInputModel(), - }); + this._filteringModel = new FilteringModel( + model.router, + { + author: new AuthorFilterModel(), + title: new RawTextFilterModel(), + content: new RawTextFilterModel(), + tags: new TagFilterModel(tagsProvider.items$), + runNumbers: new RawTextFilterModel(), + environmentIds: new RawTextFilterModel(), + fillNumbers: new RawTextFilterModel(), + created: new TimeRangeInputModel(), + }, + ); this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); From fda7ea13f6a19f4f0cdeef0c2bdc2180a63007c4 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:37:40 +0200 Subject: [PATCH 086/115] chore: pass router to the QcFlagTypesOverview model --- .../Overview/QcFlagTypesOverviewModel.js | 16 ++++++++++------ lib/public/views/QcFlagTypes/QcFlagTypesModel.js | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 72abce90f5..a5d7a757ec 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -23,15 +23,19 @@ import { RadioButtonFilterModel } from '../../../components/Filters/common/Radio export class QcFlagTypesOverviewModel extends OverviewPageModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._filteringModel = new FilteringModel({ - names: new TextTokensFilterModel(), - methods: new TextTokensFilterModel(), - bad: new RadioButtonFilterModel([{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }]), - }); + this._filteringModel = new FilteringModel( + router, + { + names: new TextTokensFilterModel(), + methods: new TextTokensFilterModel(), + bad: new RadioButtonFilterModel([{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }]), + }, + ); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); diff --git a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js index 43468d3e34..b0802cfb97 100644 --- a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js +++ b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js @@ -29,7 +29,7 @@ export class QcFlagTypesModel extends Observable { this.model = model; // Overview - this._overviewModel = new QcFlagTypesOverviewModel(); + this._overviewModel = new QcFlagTypesOverviewModel(model.router); this._overviewModel.bubbleTo(this); } From 555cd6df36fa280bbc1eb3da0f1ce5e49974f95d Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:41:36 +0200 Subject: [PATCH 087/115] chore: pass router to the RunsOverview model --- .../views/Runs/Overview/RunsOverviewModel.js | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index b5c400e5d2..ea383bb95c 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -53,51 +53,54 @@ export class RunsOverviewModel extends OverviewPageModel { constructor(model) { super(); - this._filteringModel = new FilteringModel({ - runNumbers: new RawTextFilterModel(), - detectors: new DetectorsFilterModel(detectorsProvider.dataTaking$), - tags: new TagFilterModel( - tagsProvider.items$, - [ - CombinationOperator.AND, - CombinationOperator.OR, - CombinationOperator.NONE_OF, - ], - ), - fillNumbers: new RawTextFilterModel(), - lhcPeriods: new RawTextFilterModel(), - o2start: new TimeRangeFilterModel(), - o2end: new TimeRangeFilterModel(), - definitions: new RunDefinitionFilterModel(), - runDuration: new NumericalComparisonFilterModel({ scale: 60 * 1000 }), - environmentIds: new RawTextFilterModel(), - runTypes: new RunTypesFilterModel(runTypesProvider.items$), - beamModes: new BeamModeFilterModel(beamModesProvider.items$), - runQualities: new SelectionFilterModel({ - availableOptions: RUN_QUALITIES.map((quality) => ({ - label: quality.toUpperCase(), - value: quality, - })), - }), - nDetectors: new NumericalComparisonFilterModel({ integer: true }), - nEpns: new NumericalComparisonFilterModel({ integer: true }), - nFlps: new NumericalComparisonFilterModel({ integer: true }), - ctfFileCount: new NumericalComparisonFilterModel({ integer: true }), - tfFileCount: new NumericalComparisonFilterModel({ integer: true }), - otherFileCount: new NumericalComparisonFilterModel({ integer: true }), - odcTopologyFullName: new RawTextFilterModel(), - eorReason: new EorReasonFilterModel(eorReasonTypeProvider.items$), - magnets: new MagnetsFilteringModel(magnetsCurrentLevelsProvider.items$), - muInelasticInteractionRate: new NumericalComparisonFilterModel(), - inelasticInteractionRateAvg: new NumericalComparisonFilterModel(), - inelasticInteractionRateAtStart: new NumericalComparisonFilterModel(), - inelasticInteractionRateAtMid: new NumericalComparisonFilterModel(), - inelasticInteractionRateAtEnd: new NumericalComparisonFilterModel(), - ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), - dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), - epn: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), - triggerValues: new SelectionModel({ availableOptions: TRIGGER_VALUES.map((value) => ({ label: value, value })) }), - }); + this._filteringModel = new FilteringModel( + model.router, + { + runNumbers: new RawTextFilterModel(), + detectors: new DetectorsFilterModel(detectorsProvider.dataTaking$), + tags: new TagFilterModel( + tagsProvider.items$, + [ + CombinationOperator.AND, + CombinationOperator.OR, + CombinationOperator.NONE_OF, + ], + ), + fillNumbers: new RawTextFilterModel(), + lhcPeriods: new RawTextFilterModel(), + o2start: new TimeRangeFilterModel(), + o2end: new TimeRangeFilterModel(), + definitions: new RunDefinitionFilterModel(), + runDuration: new NumericalComparisonFilterModel({ scale: 60 * 1000 }), + environmentIds: new RawTextFilterModel(), + runTypes: new RunTypesFilterModel(runTypesProvider.items$), + beamModes: new BeamModeFilterModel(beamModesProvider.items$), + runQualities: new SelectionFilterModel({ + availableOptions: RUN_QUALITIES.map((quality) => ({ + label: quality.toUpperCase(), + value: quality, + })), + }), + nDetectors: new NumericalComparisonFilterModel({ integer: true }), + nEpns: new NumericalComparisonFilterModel({ integer: true }), + nFlps: new NumericalComparisonFilterModel({ integer: true }), + ctfFileCount: new NumericalComparisonFilterModel({ integer: true }), + tfFileCount: new NumericalComparisonFilterModel({ integer: true }), + otherFileCount: new NumericalComparisonFilterModel({ integer: true }), + odcTopologyFullName: new RawTextFilterModel(), + eorReason: new EorReasonFilterModel(eorReasonTypeProvider.items$), + magnets: new MagnetsFilteringModel(magnetsCurrentLevelsProvider.items$), + muInelasticInteractionRate: new NumericalComparisonFilterModel(), + inelasticInteractionRateAvg: new NumericalComparisonFilterModel(), + inelasticInteractionRateAtStart: new NumericalComparisonFilterModel(), + inelasticInteractionRateAtMid: new NumericalComparisonFilterModel(), + inelasticInteractionRateAtEnd: new NumericalComparisonFilterModel(), + ddflp: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + dcs: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + epn: new RadioButtonFilterModel([{ label: 'ANY' }, { label: 'ON', value: true }, { label: 'OFF', value: false }]), + triggerValues: new SelectionModel({ availableOptions: TRIGGER_VALUES.map((value) => ({ label: value, value })) }), + }, + ); this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$.bubbleTo(this); From 2da2473353f42bf14289101b059c84ad0eb621bd Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:45:44 +0200 Subject: [PATCH 088/115] chore: pass router to the Simulation pass overview models --- lib/public/Model.js | 2 +- .../AnchoredSimulationPassesOverviewModel.js | 5 +++-- .../SimulationPassesPerLhcPeriodOverviewModel.js | 5 +++-- lib/public/views/SimulationPasses/SimulationPassesModel.js | 7 ++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/public/Model.js b/lib/public/Model.js index 3c1eacffa9..0d0ae222f3 100644 --- a/lib/public/Model.js +++ b/lib/public/Model.js @@ -115,7 +115,7 @@ export default class Model extends Observable { this.qcFlags = new QcFlagsModel(this); this.qcFlags.bubbleTo(this); - this.simulationPasses = new SimulationPassesModel(this); + this.simulationPasses = new SimulationPassesModel(this.router); this.simulationPasses.bubbleTo(this); this.qcFlagTypes = new QcFlagTypesModel(this); diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index d986ece53b..ad01197cef 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -23,11 +23,12 @@ import { FilteringModel } from '../../../components/Filters/common/FilteringMode export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel() }); + this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 0d8af9b309..fbd210982d 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -23,11 +23,12 @@ import { FilteringModel } from '../../../components/Filters/common/FilteringMode export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel { /** * Constructor + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._filteringModel = new FilteringModel({ names: new TextTokensFilterModel() }); + this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); diff --git a/lib/public/views/SimulationPasses/SimulationPassesModel.js b/lib/public/views/SimulationPasses/SimulationPassesModel.js index 8e8d6e7969..22436b4c5c 100644 --- a/lib/public/views/SimulationPasses/SimulationPassesModel.js +++ b/lib/public/views/SimulationPasses/SimulationPassesModel.js @@ -21,14 +21,15 @@ import { AnchoredSimulationPassesOverviewModel } from './AnchoredOverview/Anchor export class SimulationPassesModel extends Observable { /** * The constructor of the model + * @param {QueryRouter} router router that controls the application's page navigation */ - constructor() { + constructor(router) { super(); - this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(); + this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(router); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(); + this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(router); this._anchoredOverviewModel.bubbleTo(this); } From 61bcae061f5e9520ea96fba879558d66a84a90f4 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:53:31 +0200 Subject: [PATCH 089/115] fixup: the import of buildUrl --- lib/public/components/Filters/common/FilteringModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index c56a9dbd38..380aaf92fb 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -11,11 +11,10 @@ * or submit itself to any jurisdiction. */ -import { buildUrl } from '@aliceo2/web-ui'; import { expandQueryLikeNestedKey } from '../../../utilities/expandNestedKey.js'; import { SelectionModel } from '../../common/selection/SelectionModel.js'; import { FilterModel } from './FilterModel.js'; -import { Observable } from '/js/src/index.js'; +import { buildUrl, Observable } from '/js/src/index.js'; /** * Model representing a filtering system, including filter inputs visibility, filters values and so on From 3f06051a7c27cbfd44f38a50768e5619771f3350 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 14:57:40 +0200 Subject: [PATCH 090/115] chore: add pageidentifiers field to FilteringModel --- .../components/Filters/common/FilteringModel.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index 380aaf92fb..0f9a455f1c 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -29,6 +29,7 @@ export class FilteringModel extends Observable { constructor(router, filters) { super(); this._visualChange$ = new Observable(); + this._pageIdentifiers = []; this._router = router; this._filters = filters; @@ -39,6 +40,17 @@ export class FilteringModel extends Observable { } } + /** + * Sets the page identifiers + * + * @param {string[]} identifiers Strings that identify the pages as shown in the router params. + * Used to prevent unneeded reads/writes from/to the url + * @returns {void} + */ + set pageIdentifiers(identifiers) { + this._pageIdentifiers = identifiers; + } + /** * Reset the filters * From fb562c845ace5fc3d0699f78497d54424f49b63c Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 15:15:08 +0200 Subject: [PATCH 091/115] chore: add page identifiers to all of the overview page models --- .../DataPassesPerSimulationPassOverviewModel.js | 1 + .../views/Environments/Overview/EnvironmentOverviewModel.js | 1 + lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js | 1 + lib/public/views/Logs/Overview/LogsOverviewModel.js | 1 + .../views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js | 1 + lib/public/views/Runs/Overview/RunsOverviewModel.js | 1 + .../views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js | 1 + .../views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js | 1 + .../RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js | 1 + .../AnchoredOverview/AnchoredSimulationPassesOverviewModel.js | 1 + .../SimulationPassesPerLhcPeriodOverviewModel.js | 1 + 11 files changed, 11 insertions(+) diff --git a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js index 12fd208a39..43c73d6988 100644 --- a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js +++ b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js @@ -27,6 +27,7 @@ export class DataPassesPerSimulationPassOverviewModel extends DataPassesOverview super(router); this._simulationPass = new ObservableData(RemoteData.notAsked()); this._simulationPass.bubbleTo(this); + this._filteringModel.pageIdentifiers = ['data-passes-per-simulation-pass-overview']; } /** diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index f49de81b3b..f898a1d989 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -59,6 +59,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); + this._filteringModel.pageIdentifiers = ['env-overview']; } /** diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 52c99c52c4..de58e1324d 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -54,6 +54,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { this._filteringModel.visualChange$.bubbleTo(this); this.reset(false); + this._filteringModel.pageIdentifiers = ['lhc-fill-overview']; } /** diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index eef752c91e..dbd1604e7c 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -75,6 +75,7 @@ export class LogsOverviewModel extends Observable { excludeAnonymous && this._filteringModel.get('author').update('!Anonymous'); this.reset(false); + this._filteringModel.pageIdentifiers = ['log-overview']; } /** diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index a5d7a757ec..1f559eaf03 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -43,6 +43,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { }); this._filteringModel.visualChange$.bubbleTo(this); + this._filteringModel.pageIdentifiers = ['qc-flag-types-overview']; } /** diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index ea383bb95c..6945eeb1aa 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -119,6 +119,7 @@ export class RunsOverviewModel extends OverviewPageModel { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); + this._filteringModel.pageIdentifiers = ['run-overview']; } /** diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 597a579d6b..b45b8a25df 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -74,6 +74,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo this._discardAllQcFlagsActionState$.bubbleTo(this); this._item$.observe(() => this._fetchGaqSummaryForCurrentRuns()); + this._filteringModel.pageIdentifiers = ['runs-per-data-pass']; } /** diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index 8c2332fb22..2cd931065b 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -55,6 +55,7 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM this._tabbedPanelModel = new RunsPerLhcPeriodTabbedPanelModel(this._qcSummary$); this._tabbedPanelModel.bubbleTo(this); + this._filteringModel.pageIdentifiers = ['runs-per-lhc-period']; } /** diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index d5fbeb400f..0b78d2fe73 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -33,6 +33,7 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver this._detectors$.bubbleTo(this); this._simulationPass$.bubbleTo(this); + this._filteringModel.pageIdentifiers = ['runs-per-simulation-pass']; } /** diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index ad01197cef..8218bffbb1 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -38,6 +38,7 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { this._filteringModel.visualChange$.bubbleTo(this); this._dataPass = new ObservableData(RemoteData.notAsked()); + this._filteringModel.pageIdentifiers = ['anchored-simulation-passes-overview']; } /** diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index fbd210982d..883e23c4ef 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -39,6 +39,7 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel this._lhcPeriod.bubbleTo(this); this._lhcPeriodId = null; + this._filteringModel.pageIdentifiers = ['simulation-passes-per-lhc-period-overview']; } /** From 1f9af448706657cbc7de7844e666aac5dc0e7037 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 21 Apr 2026 15:42:26 +0200 Subject: [PATCH 092/115] feat: make filteringmodel constructor more efficient --- lib/public/components/Filters/common/FilteringModel.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index ac3224ecc4..828fe55bed 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -30,12 +30,9 @@ export class FilteringModel extends Observable { this._visualChange$ = new Observable(); - this._filters = filters; - this._filterModels = Object.values(filters); - for (const model of this._filterModels) { - model.bubbleTo(this); - model.visualChange$?.bubbleTo(this._visualChange$); - } + this._filters = {}; + this._filterModels = []; + Object.entries(filters).forEach(([key, model]) => this.put(key, model)); } /** From 3a3fa99f298483bfa192d7e4a4f2e9c48ca6950f Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 13:22:50 +0200 Subject: [PATCH 093/115] test: create tests for logsOverviewPage --- test/public/Filters/filtersToUrl.test.js | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 test/public/Filters/filtersToUrl.test.js diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js new file mode 100644 index 0000000000..ec647fd05a --- /dev/null +++ b/test/public/Filters/filtersToUrl.test.js @@ -0,0 +1,77 @@ +/** + * @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. + */ + +const { expect } = require('chai'); +const { + defaultBefore, + defaultAfter, + goToPage, + fillInput, + getPopoverSelector, + getPeriodInputsSelectors, + pressElement, + openFilteringPanel, +} = require('../defaults.js'); + +module.exports = () => { + let page; + let browser; + + before(async () => { + [page, browser] = await defaultBefore(); + }); + + const getQueryParameters = (page) => Object.fromEntries(new URL(page.url()).searchParams.entries()); + + it('Filters from LogsOverview should be set to the URL', async () => { + await goToPage(page, 'log-overview'); + const firstCheckboxId = 'tag-dropdown-option-DPG'; + const popoverTrigger = '.createdAt-filter .popover-trigger'; + + await page.waitForSelector(popoverTrigger); + await openFilteringPanel(page); + + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + + await fillInput(page, '.title-textFilter', 'bogusbogusbogus', ['change']); + await fillInput(page, '#authorFilterText', 'Jane', ['change']); + await fillInput(page, '.content-textFilter', 'particle', ['change']); + await pressElement(page, '.tags-filter .dropdown-trigger'); + await pressElement(page, `#${firstCheckboxId}`, true); + await fillInput(page, '.environments-filter input', '8E4aZTjY', ['change']); + await fillInput(page, '.runNumbers-textFilter', '1,2', ['change']); + await fillInput(page, '.fillNumbers-textFilter', '1, 6', ['change']); + await fillInput(page, fromDateSelector, '2020-02-02', ['change']); + await fillInput(page, toDateSelector, '2020-02-02', ['change']); + await fillInput(page, fromTimeSelector, '11:00', ['change']); + await fillInput(page, toTimeSelector, '12:00', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "log-overview", + "filter[author]": "Jane", + "filter[title]": "bogusbogusbogus", + "filter[content]": "particle", + "filter[tags][values]": "DPG", + "filter[tags][operation]": "and", + "filter[runNumbers]": "1,2", + "filter[environmentIds]": "8E4aZTjY", + "filter[fillNumbers]": "1, 6", + "filter[created][from]": "1580641200000", + "filter[created][to]": "1580644800000" + }); + }); + + after(async () => await defaultAfter(page, browser)); +} From 4b7b1f178fc789132a73e3b6da43e4e3cdd88f7f Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 14:28:43 +0200 Subject: [PATCH 094/115] test: create tests for logsOverviewPage --- test/public/Filters/filtersToUrl.test.js | 33 +++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index ec647fd05a..88c015687a 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -33,7 +33,7 @@ module.exports = () => { const getQueryParameters = (page) => Object.fromEntries(new URL(page.url()).searchParams.entries()); - it('Filters from LogsOverview should be set to the URL', async () => { + it('should set filters from LogsOverview to the URL', async () => { await goToPage(page, 'log-overview'); const firstCheckboxId = 'tag-dropdown-option-DPG'; const popoverTrigger = '.createdAt-filter .popover-trigger'; @@ -73,5 +73,36 @@ module.exports = () => { }); }); + it('should set filters from EnvironmentsOverview to the URL', async () => { + await goToPage(page, 'env-overview'); + const popoverTrigger = '.createdAt-filter .popover-trigger'; + + await page.waitForSelector(popoverTrigger); + await openFilteringPanel(page); + + const createdAtPopoverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const periodInputsSelectors = getPeriodInputsSelectors(createdAtPopoverSelector); + + await fillInput(page, '.runs-filter input', '10', ['change']); + await fillInput(page, '.id-filter input', 'Dxi029djX, TDI59So3d', ['change']); + await pressElement(page, '#checkboxes-checkbox-DESTROYED'); + await fillInput(page, '.historyItems-filter input', 'C-R-D-X', ['change']); + await fillInput(page, periodInputsSelectors.fromDateSelector, '2019-08-09', ['change']); + await fillInput(page, periodInputsSelectors.toDateSelector, '2019-08-10', ['change']); + await fillInput(page, periodInputsSelectors.fromTimeSelector, '00:00', ['change']); + await fillInput(page, periodInputsSelectors.toTimeSelector, '23:59', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "env-overview", + "filter[created][from]": "1565308800000", + "filter[created][to]": "1565481540000", + "filter[runNumbers]": "10", + "filter[statusHistory]": "C-R-D-X", + "filter[currentStatus]": "DESTROYED", + "filter[ids]": "Dxi029djX, TDI59So3d" + }); + }); + after(async () => await defaultAfter(page, browser)); } From fd8d650a7138371909d1a2872a208617579dcba2 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 14:48:31 +0200 Subject: [PATCH 095/115] feat: remove url params upon reset --- lib/public/components/Filters/common/FilteringModel.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index b837bbb3f0..c69a09feff 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -62,6 +62,10 @@ export class FilteringModel extends Observable { if (notify) { this.notify(); } + + const { params } = this._router; + delete params.filter; + this._router.go(buildUrl('?', params), false, true); } /** From 5a54dfe5689a883079e6b1d25ef78cc9e20fe075 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 15:27:30 +0200 Subject: [PATCH 096/115] test: create tests for lhcFillsOverviewPage --- test/public/Filters/filtersToUrl.test.js | 61 ++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 88c015687a..3003e226a4 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -21,6 +21,7 @@ const { getPeriodInputsSelectors, pressElement, openFilteringPanel, + waitForTableLength, } = require('../defaults.js'); module.exports = () => { @@ -104,5 +105,65 @@ module.exports = () => { }); }); + it('should set filters from LhcFillsOverview to the URL', async () => { + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + const filterSBDurationOperator= '#beam-duration-filter-operator'; + const filterSBDurationOperand= '#beam-duration-filter-operand'; + const filterRunDurationOperator= '#run-duration-filter-operator'; + const filterRunDurationOperand= '#run-duration-filter-operand'; + const filterBeamTypeP_Pb = '#beam-types-checkbox-p-Pb'; + const sbEndPopoverTrigger = '.stableBeamsEnd-filter .popover-trigger'; + const sbStartPopoverTrigger = '.stableBeamsStart-filter .popover-trigger'; + const sbStartPopOverSelector = await getPopoverSelector(await page.$(sbStartPopoverTrigger)); + const sbEndPopOverSelector = await getPopoverSelector(await page.$(sbEndPopoverTrigger)); + const filterSchemeNameInputField= '.fillingSchemeName-filter input'; + const { + fromDateSelector: sbStartFromDateSelector, + toDateSelector: sbStartToDateSelector, + fromTimeSelector: sbStartFromTimeSelector, + toTimeSelector: sbStartToTimeSelector + } = getPeriodInputsSelectors(sbStartPopOverSelector); + + const { + fromDateSelector: sbEndFromDateSelector, + toDateSelector: sbEndToDateSelector, + fromTimeSelector: sbEndFromTimeSelector, + toTimeSelector: sbEndToTimeSelector + } = getPeriodInputsSelectors(sbEndPopOverSelector); + + await openFilteringPanel(page); + await page.select(filterSBDurationOperator, '>='); + await fillInput(page, filterSBDurationOperand, '00:01:40', ['change']); + await page.select(filterRunDurationOperator, '<='); + await fillInput(page, filterRunDurationOperand, '00:00:00', ['change']); + await pressElement(page, filterBeamTypeP_Pb); + await fillInput(page, sbStartFromDateSelector, '2019-08-08', ['change']); + await fillInput(page, sbStartToDateSelector, '2019-08-08', ['change']); + await fillInput(page, sbStartFromTimeSelector, '10:00', ['change']); + await fillInput(page, sbStartToTimeSelector, '12:00', ['change']); + await fillInput(page, sbEndFromDateSelector, '2022-03-22', ['change']); + await fillInput(page, sbEndToDateSelector, '2022-03-22', ['change']); + await fillInput(page, sbEndFromTimeSelector, '01:00', ['change']); + await fillInput(page, sbEndToTimeSelector, '23:59', ['change']); + await fillInput(page, filterSchemeNameInputField, 'Single_12b_8_1024_8_2018', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "lhc-fill-overview", + "filter[beamDuration][operator]": ">=", + "filter[beamDuration][limit]": "00:01:40", + "filter[runDuration][operator]": "<=", + "filter[runDuration][limit]": "00:00:00", + "filter[hasStableBeams]": "true", + "filter[stableBeamsEnd][from]": "1647910800000", + "filter[stableBeamsEnd][to]": "1647993540000", + "filter[stableBeamsStart][from]": "1565258400000", + "filter[stableBeamsStart][to]": "1565265600000", + "filter[beamTypes]": "p-Pb", + "filter[schemeName]": "Single_12b_8_1024_8_2018" + }); + }); + after(async () => await defaultAfter(page, browser)); } From aa0fdbaf2da7103f5521deeb566825e71d06a9a1 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 16:41:49 +0200 Subject: [PATCH 097/115] test: create tests for runsOverviewPage --- test/public/Filters/filtersToUrl.test.js | 84 ++++++++++++++++++++++-- test/public/index.js | 2 + 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 3003e226a4..4b90ad0ea2 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -108,9 +108,7 @@ module.exports = () => { it('should set filters from LhcFillsOverview to the URL', async () => { await goToPage(page, 'lhc-fill-overview'); await waitForTableLength(page, 5); - const filterSBDurationOperator= '#beam-duration-filter-operator'; const filterSBDurationOperand= '#beam-duration-filter-operand'; - const filterRunDurationOperator= '#run-duration-filter-operator'; const filterRunDurationOperand= '#run-duration-filter-operand'; const filterBeamTypeP_Pb = '#beam-types-checkbox-p-Pb'; const sbEndPopoverTrigger = '.stableBeamsEnd-filter .popover-trigger'; @@ -133,9 +131,7 @@ module.exports = () => { } = getPeriodInputsSelectors(sbEndPopOverSelector); await openFilteringPanel(page); - await page.select(filterSBDurationOperator, '>='); await fillInput(page, filterSBDurationOperand, '00:01:40', ['change']); - await page.select(filterRunDurationOperator, '<='); await fillInput(page, filterRunDurationOperand, '00:00:00', ['change']); await pressElement(page, filterBeamTypeP_Pb); await fillInput(page, sbStartFromDateSelector, '2019-08-08', ['change']); @@ -151,9 +147,9 @@ module.exports = () => { const queryParameters = getQueryParameters(page); expect(queryParameters).to.deep.equal({ "page": "lhc-fill-overview", - "filter[beamDuration][operator]": ">=", + "filter[beamDuration][operator]": "=", "filter[beamDuration][limit]": "00:01:40", - "filter[runDuration][operator]": "<=", + "filter[runDuration][operator]": "=", "filter[runDuration][limit]": "00:00:00", "filter[hasStableBeams]": "true", "filter[stableBeamsEnd][from]": "1647910800000", @@ -165,5 +161,81 @@ module.exports = () => { }); }); + it('should set filters from runsOverview to the URL', async () => { + await goToPage(page, 'lhc-fill-overview'); + await goToPage(page, 'run-overview'); + const o2StartPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); + const { fromTimeSelector, toTimeSelector, fromDateSelector, toDateSelector } = getPeriodInputsSelectors(o2StartPopoverSelector); + const popoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + + await openFilteringPanel(page); + await pressElement(page, '#detector-filter-dropdown-option-ITS', true); + await pressElement(page, '#tag-dropdown-option-FOOD', true); + await pressElement(page, '#run-definition-checkbox-PHYSICS', true); + await pressElement(page, '.timeO2Start-filter .popover-trigger'); + await fillInput(page, fromTimeSelector, '11:11', ['change']); + await fillInput(page, toTimeSelector, '14:00', ['change']); + await fillInput(page, fromDateSelector, '2021-02-03', ['change']); + await fillInput(page, toDateSelector, '2021-02-03', ['change']); + await fillInput(page, '#duration-operand', '1500', ['change']); + await pressElement(page, `${popoverSelector} .dropdown-option:last-child`, true); + await pressElement(page, '#checkboxes-checkbox-bad'); + await pressElement(page, '#triggerValue-checkbox-OFF'); + await fillInput(page, '#runOverviewFilter .runNumbers-textFilter', '101'); + await fillInput(page, '.fillNumbers-textFilter', '1, 3', ['change']); + await fillInput(page, '.environmentIds-textFilter', 'Dxi029djX, TDI59So3d', ['change']); + await pressElement(page, '#run-types-dropdown-option-2', true); + await pressElement(page, '#beam-mode-dropdown-option-NO\\ BEAM', true); + await fillInput(page, '#nDetectors-operand', '1', ['change']); + await fillInput(page, '#nFlps-operand', '10', ['change']); + await fillInput(page, '#nEpns-operand', '10', ['change']); + await fillInput(page, '#ctfFileCount-operand', '1', ['change']); + await fillInput(page, '#tfFileCount-operand', '1', ['change']); + await fillInput(page, '#otherFileCount-operand', '1', ['change']); + await pressElement(page, '#epnFilterRadioOFF', true); + await page.select('#eorCategories', 'DETECTORS'); + await page.select('#eorTitles', 'CPV'); + await fillInput(page, '#eorDescription', 'some', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "run-overview", + "filter[runNumbers]": "101", + "filter[detectors][operator]": "and", + "filter[detectors][values]": "ITS", + "filter[tags][values]": "FOOD", + "filter[tags][operation]": "and", + "filter[fillNumbers]": "1, 3", + "filter[o2start][from]": "1612350660000", + "filter[o2start][to]": "1612360800000", + "filter[definitions]": "PHYSICS", + "filter[runDuration][operator]": "=", + "filter[runDuration][limit]": "90000000", + "filter[environmentIds]": "Dxi029djX, TDI59So3d", + "filter[runTypes][]": "2", + "filter[beamModes][]": "NO BEAM", + "filter[runQualities]": "bad", + "filter[nDetectors][operator]": "=", + "filter[nDetectors][limit]": "1", + "filter[nEpns][operator]": "=", + "filter[nEpns][limit]": "10", + "filter[nFlps][operator]": "=", + "filter[nFlps][limit]": "10", + "filter[ctfFileCount][operator]": "=", + "filter[ctfFileCount][limit]": "1", + "filter[tfFileCount][operator]": "=", + "filter[tfFileCount][limit]": "1", + "filter[otherFileCount][operator]": "=", + "filter[otherFileCount][limit]": "1", + "filter[eorReason][category]": "DETECTORS", + "filter[eorReason][title]": "CPV", + "filter[eorReason][description]": "some", + "filter[magnets][l3]": "30003", + "filter[magnets][dipole]": "0", + "filter[epn]": "false", + "filter[triggerValues]": "OFF" + }); + }); + after(async () => await defaultAfter(page, browser)); } diff --git a/test/public/index.js b/test/public/index.js index 8ebdc23e68..c5513a26c8 100644 --- a/test/public/index.js +++ b/test/public/index.js @@ -27,9 +27,11 @@ const ComponentsSuite = require('./components'); const SimulationPassesSuite = require('./simulationPasses'); const QcFlagTypesSuite = require('./qcFlagTypes'); const QcFlagsSuite = require('./qcFlags'); +const FilterSuite = require('./Filters'); module.exports = () => { describe('Components', ComponentsSuite); + describe('Filters', FilterSuite) describe('LhcPeriods', LhcPeriodsSuite); describe('LhcFills', LhcFillsSuite); describe('Logs', LogsSuite); From f1218bd64377ee094b3f205983b90769ea385906 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Wed, 22 Apr 2026 16:43:01 +0200 Subject: [PATCH 098/115] fix: add semicolon --- test/public/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/index.js b/test/public/index.js index c5513a26c8..293d9a9e94 100644 --- a/test/public/index.js +++ b/test/public/index.js @@ -31,7 +31,7 @@ const FilterSuite = require('./Filters'); module.exports = () => { describe('Components', ComponentsSuite); - describe('Filters', FilterSuite) + describe('Filters', FilterSuite); describe('LhcPeriods', LhcPeriodsSuite); describe('LhcFills', LhcFillsSuite); describe('Logs', LogsSuite); From f8372f94deb3f2a2c80fbb06a3cf3c95edd42547 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 09:09:25 +0200 Subject: [PATCH 099/115] test: create tests for flagtypes overview and lhcperiod overview --- .../Overview/LhcPeriodsOverviewModel.js | 2 + test/public/Filters/filtersToUrl.test.js | 57 ++++++++++++++++--- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index b989b60488..dbf707abce 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -43,6 +43,8 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { this._pagination.silentlySetCurrentPage(1); this.load(); }); + + this._filteringModel.pageIdentifiers = ['lhc-period-overview']; } /** diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 4b90ad0ea2..e48d9baa8d 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -108,9 +108,6 @@ module.exports = () => { it('should set filters from LhcFillsOverview to the URL', async () => { await goToPage(page, 'lhc-fill-overview'); await waitForTableLength(page, 5); - const filterSBDurationOperand= '#beam-duration-filter-operand'; - const filterRunDurationOperand= '#run-duration-filter-operand'; - const filterBeamTypeP_Pb = '#beam-types-checkbox-p-Pb'; const sbEndPopoverTrigger = '.stableBeamsEnd-filter .popover-trigger'; const sbStartPopoverTrigger = '.stableBeamsStart-filter .popover-trigger'; const sbStartPopOverSelector = await getPopoverSelector(await page.$(sbStartPopoverTrigger)); @@ -131,9 +128,9 @@ module.exports = () => { } = getPeriodInputsSelectors(sbEndPopOverSelector); await openFilteringPanel(page); - await fillInput(page, filterSBDurationOperand, '00:01:40', ['change']); - await fillInput(page, filterRunDurationOperand, '00:00:00', ['change']); - await pressElement(page, filterBeamTypeP_Pb); + await fillInput(page, '#beam-duration-filter-operand', '00:01:40', ['change']); + await fillInput(page, '#run-duration-filter-operand', '00:00:00', ['change']); + await pressElement(page, '#beam-types-checkbox-p-Pb'); await fillInput(page, sbStartFromDateSelector, '2019-08-08', ['change']); await fillInput(page, sbStartToDateSelector, '2019-08-08', ['change']); await fillInput(page, sbStartFromTimeSelector, '10:00', ['change']); @@ -162,7 +159,6 @@ module.exports = () => { }); it('should set filters from runsOverview to the URL', async () => { - await goToPage(page, 'lhc-fill-overview'); await goToPage(page, 'run-overview'); const o2StartPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); const { fromTimeSelector, toTimeSelector, fromDateSelector, toDateSelector } = getPeriodInputsSelectors(o2StartPopoverSelector); @@ -237,5 +233,52 @@ module.exports = () => { }); }); + it('should set filters from lhcPriodOverview to the URL', async () => { + await goToPage(page, 'lhc-period-overview'); + + await fillInput(page, 'div.flex-row.items-baseline:nth-of-type(1) input[type=text]', 'LHC22a'); + await fillInput(page, 'div.flex-row.items-baseline:nth-of-type(2) input[type=text]', '2022'); + await fillInput(page, 'div.flex-row.items-baseline:nth-of-type(3) input[type=text]', 'PbPb'); + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "lhc-period-overview", + "filter[names][]": "LHC22a", + "filter[years][]": "2022", + "filter[pdpBeamTypes][]": "PbPb" + }); + }); + + it('should set filters from qcFlagTypesOverview to the URL', async () => { + await goToPage(page, 'qc-flag-types-overview'); + + await fillInput(page, '.name-filter input[type=text]', 'bad'); + await fillInput(page, '.method-filter input[type=text]', 'bad'); + await pressElement(page, '#badFilterRadioBad', true); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "qc-flag-types-overview", + "filter[names][]": "bad", + "filter[methods][]": "bad", + "filter[bad]": "true" + }); + }); + + it.skip('should set filters from runsPerLhcPeriodOverview to the URL', async () => { + await goToPage(page, 'runs-per-lhc-period'); + + await fillInput(page, '.name-filter input[type=text]', 'bad'); + await fillInput(page, '.method-filter input[type=text]', 'bad'); + await pressElement(page, '#badFilterRadioBad', true); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "qc-flag-types-overview", + "filter[names][]": "bad", + "filter[methods][]": "bad", + "filter[bad]": "true" + }); + }); + after(async () => await defaultAfter(page, browser)); } From c9f979afeac350725e112f84a1fcda51e949fe1a Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 09:44:54 +0200 Subject: [PATCH 100/115] test add runsPerLhcPeriodOverview tests --- test/public/Filters/filtersToUrl.test.js | 93 +++++++++++++++++++----- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index e48d9baa8d..27bb403bb1 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -160,21 +160,39 @@ module.exports = () => { it('should set filters from runsOverview to the URL', async () => { await goToPage(page, 'run-overview'); - const o2StartPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); - const { fromTimeSelector, toTimeSelector, fromDateSelector, toDateSelector } = getPeriodInputsSelectors(o2StartPopoverSelector); - const popoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + const dipolePopoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + const startPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); + const endPopoverSelector = await getPopoverSelector(await page.$('.timeO2End-filter .popover-trigger')); + + const { + fromDateSelector: startFromDateSelector, + toDateSelector: startToDateSelector, + fromTimeSelector: startFromTimeSelector, + toTimeSelector: startToTimeSelector + } = getPeriodInputsSelectors(startPopoverSelector); + + const { + fromDateSelector: endFromDateSelector, + toDateSelector: endToDateSelector, + fromTimeSelector: endFromTimeSelector, + toTimeSelector: endToTimeSelector + } = getPeriodInputsSelectors(endPopoverSelector); await openFilteringPanel(page); await pressElement(page, '#detector-filter-dropdown-option-ITS', true); await pressElement(page, '#tag-dropdown-option-FOOD', true); await pressElement(page, '#run-definition-checkbox-PHYSICS', true); await pressElement(page, '.timeO2Start-filter .popover-trigger'); - await fillInput(page, fromTimeSelector, '11:11', ['change']); - await fillInput(page, toTimeSelector, '14:00', ['change']); - await fillInput(page, fromDateSelector, '2021-02-03', ['change']); - await fillInput(page, toDateSelector, '2021-02-03', ['change']); + await fillInput(page, startFromTimeSelector, '11:11', ['change']); + await fillInput(page, startToTimeSelector, '14:00', ['change']); + await fillInput(page, startFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, startToDateSelector, '2021-02-03', ['change']); + await fillInput(page, endFromTimeSelector, '11:11', ['change']); + await fillInput(page, endToTimeSelector, '14:00', ['change']); + await fillInput(page, endFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, endToDateSelector, '2021-02-03', ['change']); await fillInput(page, '#duration-operand', '1500', ['change']); - await pressElement(page, `${popoverSelector} .dropdown-option:last-child`, true); + await pressElement(page, `${dipolePopoverSelector} .dropdown-option:last-child`, true); await pressElement(page, '#checkboxes-checkbox-bad'); await pressElement(page, '#triggerValue-checkbox-OFF'); await fillInput(page, '#runOverviewFilter .runNumbers-textFilter', '101'); @@ -204,6 +222,8 @@ module.exports = () => { "filter[fillNumbers]": "1, 3", "filter[o2start][from]": "1612350660000", "filter[o2start][to]": "1612360800000", + "filter[o2end][from]": "1612350660000", + "filter[o2end][to]": "1612360800000", "filter[definitions]": "PHYSICS", "filter[runDuration][operator]": "=", "filter[runDuration][limit]": "90000000", @@ -264,19 +284,58 @@ module.exports = () => { }); }); - it.skip('should set filters from runsPerLhcPeriodOverview to the URL', async () => { - await goToPage(page, 'runs-per-lhc-period'); + it('should set filters from runsPerLhcPeriodOverview to the URL', async () => { + await goToPage(page, 'runs-per-lhc-period', { queryParameters: { lhcPeriodId: 2 }}); + const startPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); + const endPopoverSelector = await getPopoverSelector(await page.$('.timeO2End-filter .popover-trigger')); + const dipolePopoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + + const { + fromDateSelector: startFromDateSelector, + toDateSelector: startToDateSelector, + fromTimeSelector: startFromTimeSelector, + toTimeSelector: startToTimeSelector + } = getPeriodInputsSelectors(startPopoverSelector); + + const { + fromDateSelector: endFromDateSelector, + toDateSelector: endToDateSelector, + fromTimeSelector: endFromTimeSelector, + toTimeSelector: endToTimeSelector + } = getPeriodInputsSelectors(endPopoverSelector); + + await fillInput(page, '#inelasticInteractionRateAvg-operand', '100000', ['change']); + await fillInput(page, '#muInelasticInteractionRate-operand', '100000', ['change']); + await fillInput(page, '#runOverviewFilter .runNumbers-textFilter', '101'); + await fillInput(page, '.fillNumbers-textFilter', '1, 3', ['change']); + + await pressElement(page, `${dipolePopoverSelector} .dropdown-option:last-child`, true); + await fillInput(page, startFromTimeSelector, '11:11', ['change']); + await fillInput(page, startToTimeSelector, '14:00', ['change']); + await fillInput(page, startFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, startToDateSelector, '2021-02-03', ['change']); + await fillInput(page, endFromTimeSelector, '11:11', ['change']); + await fillInput(page, endToTimeSelector, '14:00', ['change']); + await fillInput(page, endFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, endToDateSelector, '2021-02-03', ['change']); - await fillInput(page, '.name-filter input[type=text]', 'bad'); - await fillInput(page, '.method-filter input[type=text]', 'bad'); - await pressElement(page, '#badFilterRadioBad', true); const queryParameters = getQueryParameters(page); expect(queryParameters).to.deep.equal({ - "page": "qc-flag-types-overview", - "filter[names][]": "bad", - "filter[methods][]": "bad", - "filter[bad]": "true" + "page": "runs-per-lhc-period", + "lhcPeriodId": "2", + "filter[runNumbers]": "101", + "filter[fillNumbers]": "1, 3", + "filter[o2end][from]": "1612350660000", + "filter[o2end][to]": "1612360800000", + "filter[o2start][from]": "1612350660000", + "filter[o2start][to]": "1612360800000", + "filter[magnets][l3]": "30003", + "filter[magnets][dipole]": "0", + "filter[muInelasticInteractionRate][operator]": "=", + "filter[muInelasticInteractionRate][limit]": "100000", + "filter[inelasticInteractionRateAvg][operator]": "=", + "filter[inelasticInteractionRateAvg][limit]": "100000" }); }); From 3045d5acb36ac8b194a8b02fe51803fd0710cd1e Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 09:52:36 +0200 Subject: [PATCH 101/115] test add DataPassesPerLhcPeriodOverview tests --- .../DataPassesPerLhcPeriodOverviewModel.js | 1 + test/public/Filters/filtersToUrl.test.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js index ee7f580ef7..57c4e122d9 100644 --- a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js @@ -24,6 +24,7 @@ export class DataPassesPerLhcPeriodOverviewModel extends DataPassesOverviewModel constructor(router) { super(router); this._lhcPeriodId = null; + this._filteringModel.pageIdentifiers = ['data-passes-per-lhc-period-overview']; } /** diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 27bb403bb1..b6216849f3 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -339,5 +339,21 @@ module.exports = () => { }); }); + it('should set filters from DataPassesPerLhcPeriodOverview to the URL', async () => { + await goToPage(page, 'data-passes-per-lhc-period-overview', { queryParameters: { lhcPeriodId: 2 }}); + + await fillInput(page, 'div.flex-row.items-baseline:nth-of-type(1) input[type=text]', 'LHC22b_apass1', ['change']); + await pressElement(page, '#checkboxes-checkbox-test', true); + + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "data-passes-per-lhc-period-overview", + "lhcPeriodId": "2", + "filter[names][]": "LHC22b_apass1", + "filter[include][byName]": "test" + }); + }); + after(async () => await defaultAfter(page, browser)); } From c11492b01f65f0928a5648df9a170465f6379fa2 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 09:57:31 +0200 Subject: [PATCH 102/115] test add anchoredSimulationPassOverview tests --- test/public/Filters/filtersToUrl.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index b6216849f3..a612c59602 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -355,5 +355,20 @@ module.exports = () => { }); }); + it('should set filters from AnchoredSimulationPassesOverview to the URL', async () => { + await goToPage(page, 'anchored-simulation-passes-overview', { queryParameters: { dataPassId: 1 }}); + + await fillInput(page, 'name-filter input', 'LHC23k6c', ['input']); + await pressElement(page, '#checkboxes-checkbox-test', true); + + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "anchored-simulation-passes-overview", + "dataPassId": "1", + "filter[names][]": "LHC23k6c" + }); + }); + after(async () => await defaultAfter(page, browser)); } From e2ac21788e5f2cdecc191aa6e772b899769041ec Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 10:00:56 +0200 Subject: [PATCH 103/115] test add DataPassesPerSimulationPassOverview tests --- test/public/Filters/filtersToUrl.test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index a612c59602..55b8fb7727 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -355,10 +355,25 @@ module.exports = () => { }); }); + it('should set filters from DataPassesPerSimulationPassOverview to the URL', async () => { + await goToPage(page, 'data-passes-per-simulation-pass-overview', { queryParameters: { simulationPassId: 1 }}); + + await fillInput(page, 'div.flex-row.items-baseline:nth-of-type(1) input[type=text]', 'LHC22b_apass1', ['change']); + await pressElement(page, '#checkboxes-checkbox-test', true); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "data-passes-per-simulation-pass-overview", + "simulationPassId": "1", + "filter[names][]": "LHC22b_apass1", + "filter[include][byName]": "test" + }); + }); + it('should set filters from AnchoredSimulationPassesOverview to the URL', async () => { await goToPage(page, 'anchored-simulation-passes-overview', { queryParameters: { dataPassId: 1 }}); - await fillInput(page, 'name-filter input', 'LHC23k6c', ['input']); + await fillInput(page, '.name-filter input', 'LHC23k6c', ['input']); await pressElement(page, '#checkboxes-checkbox-test', true); From fda76f8a81da602ba725f84b549c19458c5c5c6b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 11:25:44 +0200 Subject: [PATCH 104/115] add runs per simulationpass and datapass tests --- test/public/Filters/filtersToUrl.test.js | 145 ++++++++++++++++++++++- 1 file changed, 143 insertions(+), 2 deletions(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 55b8fb7727..44d2c2b3ac 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -22,6 +22,7 @@ const { pressElement, openFilteringPanel, waitForTableLength, + takeScreenshot, } = require('../defaults.js'); module.exports = () => { @@ -374,8 +375,6 @@ module.exports = () => { await goToPage(page, 'anchored-simulation-passes-overview', { queryParameters: { dataPassId: 1 }}); await fillInput(page, '.name-filter input', 'LHC23k6c', ['input']); - await pressElement(page, '#checkboxes-checkbox-test', true); - const queryParameters = getQueryParameters(page); expect(queryParameters).to.deep.equal({ @@ -385,5 +384,147 @@ module.exports = () => { }); }); + it('should set filters from RunsPerSimulationPass to the URL', async () => { + await goToPage(page, 'runs-per-simulation-pass', { queryParameters: { simulationPassId: 2 }}); + + const dipolePopoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + const startPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); + const endPopoverSelector = await getPopoverSelector(await page.$('.timeO2End-filter .popover-trigger')); + + const { + fromDateSelector: startFromDateSelector, + toDateSelector: startToDateSelector, + fromTimeSelector: startFromTimeSelector, + toTimeSelector: startToTimeSelector + } = getPeriodInputsSelectors(startPopoverSelector); + + const { + fromDateSelector: endFromDateSelector, + toDateSelector: endToDateSelector, + fromTimeSelector: endFromTimeSelector, + toTimeSelector: endToTimeSelector + } = getPeriodInputsSelectors(endPopoverSelector); + + await openFilteringPanel(page); + await pressElement(page, '.timeO2Start-filter .popover-trigger'); + await fillInput(page, startFromTimeSelector, '11:11', ['change']); + await fillInput(page, startToTimeSelector, '14:00', ['change']); + await fillInput(page, startFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, startToDateSelector, '2021-02-03', ['change']); + await fillInput(page, endFromTimeSelector, '11:11', ['change']); + await fillInput(page, endToTimeSelector, '14:00', ['change']); + await fillInput(page, endFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, endToDateSelector, '2021-02-03', ['change']); + await fillInput(page, '.inelasticInteractionRateAtMid-filter input', '1', ['change']); + await fillInput(page, '.inelasticInteractionRateAtEnd-filter input', '1', ['change']); + await fillInput(page, '.inelasticInteractionRateAtStart-filter input', '1', ['change']); + await pressElement(page, `${dipolePopoverSelector} .dropdown-option:last-child`, true); + await pressElement(page, '#mcReproducibleAsNotBadToggle', true); + + // These two are detectorQCNotBadFraction[_id] filters. There are a dozen more, but they are all identical hence why only these were tested + await fillInput(page, '.QC-SPECIFIC-filter input', '1', ['change']); + await fillInput(page, '.ACO-filter input', '1', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "runs-per-simulation-pass", + "simulationPassId": "2", + "filter[o2end][from]": "1612350660000", + "filter[o2start][from]": "1612350660000", + "filter[o2end][to]": "1612360800000", + "filter[o2start][to]": "1612360800000", + "filter[magnets][l3]": "30003", + "filter[magnets][dipole]": "0", + "filter[inelasticInteractionRateAtStart][operator]": "=", + "filter[inelasticInteractionRateAtStart][limit]": "1", + "filter[inelasticInteractionRateAtMid][operator]": "=", + "filter[inelasticInteractionRateAtMid][limit]": "1", + "filter[inelasticInteractionRateAtEnd][operator]": "=", + "filter[inelasticInteractionRateAtEnd][limit]": "1", + "filter[detectorsQcNotBadFraction][mcReproducibleAsNotBad]": "true", + "filter[detectorsQcNotBadFraction][_20][operator]": "=", + "filter[detectorsQcNotBadFraction][_20][limit]": "0.01", + "filter[detectorsQcNotBadFraction][_17][operator]": "=", + "filter[detectorsQcNotBadFraction][_17][limit]": "0.01" + }); + }); + + it('should set filters from RunsPerSimulationPass to the URL', async () => { + await goToPage(page, 'runs-per-data-pass', { queryParameters: { dataPassId: 1 }}); + + const dipolePopoverSelector = await getPopoverSelector(await page.$('.aliceL3AndDipoleCurrent-filter .popover-trigger')); + const startPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger')); + const endPopoverSelector = await getPopoverSelector(await page.$('.timeO2End-filter .popover-trigger')); + + const { + fromDateSelector: startFromDateSelector, + toDateSelector: startToDateSelector, + fromTimeSelector: startFromTimeSelector, + toTimeSelector: startToTimeSelector + } = getPeriodInputsSelectors(startPopoverSelector); + + const { + fromDateSelector: endFromDateSelector, + toDateSelector: endToDateSelector, + fromTimeSelector: endFromTimeSelector, + toTimeSelector: endToTimeSelector + } = getPeriodInputsSelectors(endPopoverSelector); + + await openFilteringPanel(page); + await pressElement(page, '#detector-filter-dropdown-option-ITS', true); + await pressElement(page, '#tag-dropdown-option-FOOD', true); + await pressElement(page, '.timeO2Start-filter .popover-trigger'); + await fillInput(page, startFromTimeSelector, '11:11', ['change']); + await fillInput(page, startToTimeSelector, '14:00', ['change']); + await fillInput(page, startFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, startToDateSelector, '2021-02-03', ['change']); + await fillInput(page, endFromTimeSelector, '11:11', ['change']); + await fillInput(page, endToTimeSelector, '14:00', ['change']); + await fillInput(page, endFromDateSelector, '2021-02-03', ['change']); + await fillInput(page, endToDateSelector, '2021-02-03', ['change']); + await fillInput(page, '#duration-operand', '1500', ['change']); + await fillInput(page, '.muInelasticInteractionRate-filter input', '1', ['change']); + await fillInput(page, '.inelasticInteractionRateAvg-filter input', '1', ['change']); + await fillInput(page, '.globalAggregatedQuality-filter input', '1', ['change']); + + await pressElement(page, `${dipolePopoverSelector} .dropdown-option:last-child`, true); + await pressElement(page, '#mcReproducibleAsNotBadToggle', true); + + // These two are detectorQCNotBadFraction[_id] filters. There are a dozen more, but they are all identical hence why only these were tested + await fillInput(page, '.QC-SPECIFIC-filter input', '1', ['change']); + await fillInput(page, '.ACO-filter input', '1', ['change']); + + const queryParameters = getQueryParameters(page); + expect(queryParameters).to.deep.equal({ + "page": "runs-per-data-pass", + "dataPassId": "1", + "filter[detectors][operator]": "and", + "filter[detectors][values]": "ITS", + "filter[tags][values]": "FOOD", + "filter[tags][operation]": "and", + "filter[o2end][from]": "1612350660000", + "filter[o2end][to]": "1612360800000", + "filter[o2start][from]": "1612350660000", + "filter[o2start][to]": "1612360800000", + "filter[runDuration][limit]": "90000000", + "filter[runDuration][operator]": "=", + "filter[runDuration][limit]": "90000000", + "filter[magnets][l3]": "30003", + "filter[magnets][dipole]": "0", + "filter[muInelasticInteractionRate][operator]": "=", + "filter[muInelasticInteractionRate][limit]": "1", + "filter[inelasticInteractionRateAvg][operator]": "=", + "filter[inelasticInteractionRateAvg][limit]": "1", + "filter[detectorsQcNotBadFraction][mcReproducibleAsNotBad]": "true", + "filter[detectorsQcNotBadFraction][_20][operator]": "=", + "filter[detectorsQcNotBadFraction][_20][limit]": "0.01", + "filter[detectorsQcNotBadFraction][_17][operator]": "=", + "filter[detectorsQcNotBadFraction][_17][limit]": "0.01", + "filter[gaq][notBadFraction][operator]": "=", + "filter[gaq][notBadFraction][limit]": "0.01", + "filter[gaq][mcReproducibleAsNotBad]": "true" + }); + }); + after(async () => await defaultAfter(page, browser)); } From 88062835a97fea729e9f1dda1ee467efad9ad5d7 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 11:30:25 +0200 Subject: [PATCH 105/115] add filters index file --- test/public/Filters/index.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/public/Filters/index.js diff --git a/test/public/Filters/index.js b/test/public/Filters/index.js new file mode 100644 index 0000000000..e5146bb7fc --- /dev/null +++ b/test/public/Filters/index.js @@ -0,0 +1,18 @@ +/** + * @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. + */ + +const ToUrlSuite = require('./filtersToUrl.test.js'); + +module.exports = () => { + describe('Filters to URL', ToUrlSuite); +}; From 78923f2cd6ee82e8b030c7cfb4da96c0783fa2f2 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 12:07:50 +0200 Subject: [PATCH 106/115] chore: remove router from the observable constructor in DatapassesModel --- lib/public/views/DataPasses/DataPassesModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/views/DataPasses/DataPassesModel.js b/lib/public/views/DataPasses/DataPassesModel.js index 5af74e48be..48793bf3df 100644 --- a/lib/public/views/DataPasses/DataPassesModel.js +++ b/lib/public/views/DataPasses/DataPassesModel.js @@ -24,7 +24,7 @@ export class DataPassesModel extends Observable { * @param {QueryRouter} router router that controls the application's page navigation */ constructor(router) { - super(router); + super(); this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router); this._perLhcPeriodOverviewModel.bubbleTo(this); From edbcbc3b879176d0b42b79747f0c982a677b3d73 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 14:03:48 +0200 Subject: [PATCH 107/115] fix lint issues --- test/public/Filters/filtersToUrl.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index 44d2c2b3ac..dc0dc24356 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -22,7 +22,6 @@ const { pressElement, openFilteringPanel, waitForTableLength, - takeScreenshot, } = require('../defaults.js'); module.exports = () => { @@ -508,7 +507,6 @@ module.exports = () => { "filter[o2start][to]": "1612360800000", "filter[runDuration][limit]": "90000000", "filter[runDuration][operator]": "=", - "filter[runDuration][limit]": "90000000", "filter[magnets][l3]": "30003", "filter[magnets][dipole]": "0", "filter[muInelasticInteractionRate][operator]": "=", From cbd335ddfca8ef9710ec83d22c27c4f92523ee47 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 16:31:33 +0200 Subject: [PATCH 108/115] add clearUrl parameter to filterIngModel --- .../components/Filters/common/FilteringModel.js | 13 ++++++++----- .../views/DataPasses/DataPassesOverviewModel.js | 8 +++++--- .../Overview/EnvironmentOverviewModel.js | 5 +++-- .../LhcFills/Overview/LhcFillsOverviewModel.js | 5 +++-- .../Overview/QcFlagTypesOverviewModel.js | 8 +++++--- lib/public/views/Runs/Overview/RunsOverviewModel.js | 5 +++-- .../RunPerDataPass/RunsPerDataPassOverviewModel.js | 4 ++-- .../AnchoredSimulationPassesOverviewModel.js | 9 +++++---- .../SimulationPassesPerLhcPeriodOverviewModel.js | 9 +++++---- .../lhcPeriods/Overview/LhcPeriodsOverviewModel.js | 8 +++++--- 10 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index c69a09feff..d55253ba44 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -52,9 +52,10 @@ export class FilteringModel extends Observable { * Reset the filters * * @param {boolean} [notify=false] if true the model notifies its observers + * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ - reset(notify = false) { + reset(notify = false, clearUrl = false) { for (const model of this._filterModels) { model.reset(); } @@ -62,10 +63,12 @@ export class FilteringModel extends Observable { if (notify) { this.notify(); } - - const { params } = this._router; - delete params.filter; - this._router.go(buildUrl('?', params), false, true); + + if (clearUrl) { + const { params } = this._router; + delete params.filter; + this._router.go(buildUrl('?', params), false, true); + } } /** diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index d32b7eef67..73d7292b37 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -55,10 +55,12 @@ export class DataPassesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @returns {void} + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset() { - this._filteringModel.reset(); + reset(_fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); super.reset(); } diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index f898a1d989..1b9676eeae 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -91,10 +91,11 @@ export class EnvironmentOverviewModel extends OverviewPageModel { /** * Reset all filtering models * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ - resetFiltering(fetch = true) { - this._filteringModel.reset(); + resetFiltering(fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); if (fetch) { this._applyFilters(true); diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index de58e1324d..7b415190b9 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -90,10 +90,11 @@ export class LhcFillsOverviewModel extends OverviewPageModel { /** * Reset all filtering models * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ - resetFiltering(fetch = true) { - this._filteringModel.reset(); + resetFiltering(fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); if (fetch) { this._applyFilters(); diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 1f559eaf03..38c7d0707f 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -74,10 +74,12 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @returns {void} + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset() { - this._filteringModel.reset(); + reset(_fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); super.reset(); } } diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 6945eeb1aa..5a80a0ca9c 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -151,10 +151,11 @@ export class RunsOverviewModel extends OverviewPageModel { /** * Reset all filtering models * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ - resetFiltering(fetch = true) { - this._filteringModel.reset(); + resetFiltering(fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); if (fetch) { this._applyFilters(true); diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index b45b8a25df..7f29e5458a 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -149,8 +149,8 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo /** * @inheritdoc */ - resetFiltering(fetch = true) { - super.resetFiltering(fetch); + resetFiltering(fetch = true, clearUrl = false) { + super.resetFiltering(fetch, clearUrl); } /** diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index 8218bffbb1..e5447f8a0d 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -74,13 +74,14 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @returns {void} + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset() { - this._filteringModel.reset(); + reset(_fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); super.reset(); } - /** * Return the model managing all filters * diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 883e23c4ef..6c5123056f 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -84,13 +84,14 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel /** * Reset this model to its default * - * @returns {void} + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset() { - this._filteringModel.reset(); + reset(_fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); super.reset(); } - /** * Set id of LHC Period which simulation passes are to be fetched * @param {number} lhcPeriodId id of LHC Period diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index dbf707abce..c35cfb1dff 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -82,11 +82,13 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @returns {void} + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset() { + reset(_fetch = true, clearUrl = false) { super.reset(); - this._filteringModel.reset(); + this._filteringModel.reset(false, clearUrl); } /** From 1ec54b2098bc3cead430f8f40d990395c7731cfd Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 16:48:59 +0200 Subject: [PATCH 109/115] test: upgrade the fillInput function --- test/public/defaults.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/public/defaults.js b/test/public/defaults.js index 0523d8462b..ed6726bece 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -667,14 +667,24 @@ module.exports.checkColumnBalloon = async (page, rowIndex, columnIndex) => { * @return {Promise} resolves once the value has been typed */ module.exports.fillInput = async (page, inputSelector, value, events = ['input']) => { - await page.waitForSelector(inputSelector); - await page.evaluate((inputSelector, value, events) => { + await page.waitForFunction((inputSelector, value, events) => { const element = document.querySelector(inputSelector); + + if (!element) { + return false; + } + element.value = value; + for (const eventKey of events) { element.dispatchEvent(new Event(eventKey, { bubbles: true })); } - }, inputSelector, value, events); + + return true; + }, + {}, + inputSelector, value, events + ); }; /** From 1a6fe588ba6cc8848e8f43d0708f78580480d963 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 23 Apr 2026 16:54:06 +0200 Subject: [PATCH 110/115] chore: fix eslint errors --- lib/public/components/Filters/common/FilteringModel.js | 2 +- lib/public/views/DataPasses/DataPassesOverviewModel.js | 2 +- .../views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js | 2 +- .../AnchoredOverview/AnchoredSimulationPassesOverviewModel.js | 3 ++- .../SimulationPassesPerLhcPeriodOverviewModel.js | 3 ++- .../views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index d55253ba44..8f6f70decc 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -63,7 +63,7 @@ export class FilteringModel extends Observable { if (notify) { this.notify(); } - + if (clearUrl) { const { params } = this._router; delete params.filter; diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index 73d7292b37..a5add50bc8 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -55,7 +55,7 @@ export class DataPassesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} _fetch Whether to refetch all data after filters have been reset * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 38c7d0707f..0b19b00366 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -74,7 +74,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} _fetch Whether to refetch all data after filters have been reset * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index e5447f8a0d..88e434b192 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -74,7 +74,7 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} _fetch Whether to refetch all data after filters have been reset * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ @@ -82,6 +82,7 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { this._filteringModel.reset(false, clearUrl); super.reset(); } + /** * Return the model managing all filters * diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 6c5123056f..2d84730a19 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -84,7 +84,7 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel /** * Reset this model to its default * - * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} _fetch Whether to refetch all data after filters have been reset * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ @@ -92,6 +92,7 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel this._filteringModel.reset(false, clearUrl); super.reset(); } + /** * Set id of LHC Period which simulation passes are to be fetched * @param {number} lhcPeriodId id of LHC Period diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index c35cfb1dff..c729d89931 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -82,7 +82,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { /** * Reset this model to its default * - * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @param {boolean} _fetch Whether to refetch all data after filters have been reset * @param {boolean} [clearUrl=false] if true filters will be removed from the url * @return {void} */ From d45eded63a73241672ebd681d9f9f8161ffc65b7 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 24 Apr 2026 09:47:49 +0200 Subject: [PATCH 111/115] chore: add missing reset filtering params for logs --- .../components/Filters/common/filtersPanelPopover.js | 4 ++-- lib/public/views/Logs/Overview/LogsOverviewModel.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/public/components/Filters/common/filtersPanelPopover.js b/lib/public/components/Filters/common/filtersPanelPopover.js index e0c0a7490c..1d729eb93e 100644 --- a/lib/public/components/Filters/common/filtersPanelPopover.js +++ b/lib/public/components/Filters/common/filtersPanelPopover.js @@ -48,8 +48,8 @@ const filtersToggleContentHeader = (filteringModel) => h('.flex-row.justify-betw 'button#reset-filters.btn.btn-danger', { onclick: () => filteringModel.resetFiltering - ? filteringModel.resetFiltering() - : filteringModel.reset(true), + ? filteringModel.resetFiltering(true, true) + : filteringModel.reset(true, true), disabled: !filteringModel.isAnyFilterActive(), }, 'Reset all filters', diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index dbd1604e7c..698b0b9645 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -126,11 +126,12 @@ export class LogsOverviewModel extends Observable { /** * Reset all filtering, sorting and pagination settings to their default values * - * @param {boolean} fetch Whether to refetch all logs after filters have been reset - * @return {undefined} + * @param {boolean} _fetch Whether to refetch all data after filters have been reset + * @param {boolean} [clearUrl=false] if true filters will be removed from the url + * @return {void} */ - reset(fetch = true) { - this._filteringModel.reset(); + reset(_fetch = true, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); this._pagination.reset(); if (fetch) { From de9af6ffe3c60abb32043c8da8c0b31d2b961d2a Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 24 Apr 2026 16:31:59 +0200 Subject: [PATCH 112/115] chore: move the pageIdentifier definitions to the entity models --- lib/public/views/DataPasses/DataPassesModel.js | 4 ++-- lib/public/views/DataPasses/DataPassesOverviewModel.js | 3 ++- .../DataPassesPerLhcPeriodOverviewModel.js | 5 ++--- .../DataPassesPerSimulationPassOverviewModel.js | 5 ++--- lib/public/views/Environments/EnvironmentModel.js | 2 +- .../Environments/Overview/EnvironmentOverviewModel.js | 4 ++-- lib/public/views/Home/Overview/HomePageModel.js | 6 +++--- lib/public/views/LhcFills/LhcFills.js | 2 +- .../views/LhcFills/Overview/LhcFillsOverviewModel.js | 4 ++-- lib/public/views/Logs/LogsModel.js | 2 +- lib/public/views/Logs/Overview/LogsOverviewModel.js | 4 ++-- .../QcFlagTypes/Overview/QcFlagTypesOverviewModel.js | 4 ++-- lib/public/views/QcFlagTypes/QcFlagTypesModel.js | 2 +- .../Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js | 4 ++-- lib/public/views/Runs/Overview/RunsOverviewModel.js | 4 ++-- lib/public/views/Runs/Overview/RunsWithQcModel.js | 4 ++-- .../Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js | 5 ++--- .../Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js | 5 ++--- lib/public/views/Runs/RunsModel.js | 8 ++++---- .../RunsPerSimulationPassOverviewModel.js | 5 ++--- .../AnchoredSimulationPassesOverviewModel.js | 4 ++-- .../SimulationPassesPerLhcPeriodOverviewModel.js | 5 +++-- .../views/SimulationPasses/SimulationPassesModel.js | 4 ++-- lib/public/views/lhcPeriods/LhcPeriodsModel.js | 2 +- .../views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js | 5 ++--- 25 files changed, 49 insertions(+), 53 deletions(-) diff --git a/lib/public/views/DataPasses/DataPassesModel.js b/lib/public/views/DataPasses/DataPassesModel.js index 48793bf3df..95b0135707 100644 --- a/lib/public/views/DataPasses/DataPassesModel.js +++ b/lib/public/views/DataPasses/DataPassesModel.js @@ -26,10 +26,10 @@ export class DataPassesModel extends Observable { constructor(router) { super(); - this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router); + this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router, ['data-passes-per-lhc-period-overview']); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router); + this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router, ['data-passes-per-simulation-pass-overview']); this._perSimulationPassOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index a5add50bc8..38d4d0edd3 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -24,7 +24,7 @@ export class DataPassesOverviewModel extends OverviewPageModel { * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { + constructor(router, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( router, @@ -36,6 +36,7 @@ export class DataPassesOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.currentPage = 1; diff --git a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js index 57c4e122d9..145f840496 100644 --- a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js @@ -21,10 +21,9 @@ export class DataPassesPerLhcPeriodOverviewModel extends DataPassesOverviewModel * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { - super(router); + constructor(router, pageIdentifiers) { + super(router, pageIdentifiers); this._lhcPeriodId = null; - this._filteringModel.pageIdentifiers = ['data-passes-per-lhc-period-overview']; } /** diff --git a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js index 43c73d6988..52c38e9e13 100644 --- a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js +++ b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js @@ -23,11 +23,10 @@ export class DataPassesPerSimulationPassOverviewModel extends DataPassesOverview * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { - super(router); + constructor(router, pageIdentifiers) { + super(router, pageIdentifiers); this._simulationPass = new ObservableData(RemoteData.notAsked()); this._simulationPass.bubbleTo(this); - this._filteringModel.pageIdentifiers = ['data-passes-per-simulation-pass-overview']; } /** diff --git a/lib/public/views/Environments/EnvironmentModel.js b/lib/public/views/Environments/EnvironmentModel.js index ba4b1e86bf..41b80de18e 100644 --- a/lib/public/views/Environments/EnvironmentModel.js +++ b/lib/public/views/Environments/EnvironmentModel.js @@ -29,7 +29,7 @@ export class EnvironmentModel extends Observable { super(); // Sub-models - this._overviewModel = new EnvironmentOverviewModel(model); + this._overviewModel = new EnvironmentOverviewModel(model, ['env-overview']); this._overviewModel.bubbleTo(this); this._detailsModel = new EnvironmentDetailsModel(); diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index 1b9676eeae..024f1590dd 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -29,7 +29,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel { * Constructor * @param {Model} model global model */ - constructor(model) { + constructor(model, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -49,6 +49,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$?.bubbleTo(this); @@ -59,7 +60,6 @@ export class EnvironmentOverviewModel extends OverviewPageModel { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); - this._filteringModel.pageIdentifiers = ['env-overview']; } /** diff --git a/lib/public/views/Home/Overview/HomePageModel.js b/lib/public/views/Home/Overview/HomePageModel.js index 1826bc5b61..2464042271 100644 --- a/lib/public/views/Home/Overview/HomePageModel.js +++ b/lib/public/views/Home/Overview/HomePageModel.js @@ -26,13 +26,13 @@ export class HomePageModel extends Observable { */ constructor(model) { super(); - this._runsOverviewModel = new RunsOverviewModel(model); + this._runsOverviewModel = new RunsOverviewModel(model, ['home']); this._runsOverviewModel.bubbleTo(this); - this._logsOverviewModel = new LogsOverviewModel(model, true); + this._logsOverviewModel = new LogsOverviewModel(model, true, ['home']); this._logsOverviewModel.bubbleTo(this); - this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model.router, true); + this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model.router, true, ['home']); this._lhcFillsOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/LhcFills/LhcFills.js b/lib/public/views/LhcFills/LhcFills.js index a9b4036ab0..c34c12e06a 100644 --- a/lib/public/views/LhcFills/LhcFills.js +++ b/lib/public/views/LhcFills/LhcFills.js @@ -29,7 +29,7 @@ export default class LhcFills extends Observable { this.model = model; // Sub-models - this._overviewModel = new LhcFillsOverviewModel(model.router, true); + this._overviewModel = new LhcFillsOverviewModel(model.router, true, ['lhc-fill-overview']); this._overviewModel.bubbleTo(this); this._detailsModel = new LhcFillDetailsModel(); diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 7b415190b9..0414410679 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -33,7 +33,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { * @param {QueryRouter} router router that controls the application's page navigation * @param {boolean} [stableBeamsOnly=false] if true, overview will load stable beam only */ - constructor(router, stableBeamsOnly = false) { + constructor(router, stableBeamsOnly = false, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -50,11 +50,11 @@ export class LhcFillsOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); this.reset(false); - this._filteringModel.pageIdentifiers = ['lhc-fill-overview']; } /** diff --git a/lib/public/views/Logs/LogsModel.js b/lib/public/views/Logs/LogsModel.js index b4f9342d42..6c5c463649 100644 --- a/lib/public/views/Logs/LogsModel.js +++ b/lib/public/views/Logs/LogsModel.js @@ -30,7 +30,7 @@ export class LogsModel extends Observable { super(); this.model = model; - this._overviewModel = new LogsOverviewModel(model); + this._overviewModel = new LogsOverviewModel(model, false, 'log-overview'); this._overviewModel.bubbleTo(this); this._treeViewModel = new LogTreeViewModel(); diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 698b0b9645..8655037e61 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -35,7 +35,7 @@ export class LogsOverviewModel extends Observable { * @param {Model} model global model * @param {boolean} excludeAnonymous Whether to exclude anonymous logs */ - constructor(model, excludeAnonymous = false) { + constructor(model, excludeAnonymous = false, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -52,6 +52,7 @@ export class LogsOverviewModel extends Observable { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); @@ -75,7 +76,6 @@ export class LogsOverviewModel extends Observable { excludeAnonymous && this._filteringModel.get('author').update('!Anonymous'); this.reset(false); - this._filteringModel.pageIdentifiers = ['log-overview']; } /** diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 0b19b00366..71c1271b3f 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -25,7 +25,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { + constructor(router, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -37,13 +37,13 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); }); this._filteringModel.visualChange$.bubbleTo(this); - this._filteringModel.pageIdentifiers = ['qc-flag-types-overview']; } /** diff --git a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js index b0802cfb97..75bf03cc06 100644 --- a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js +++ b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js @@ -29,7 +29,7 @@ export class QcFlagTypesModel extends Observable { this.model = model; // Overview - this._overviewModel = new QcFlagTypesOverviewModel(model.router); + this._overviewModel = new QcFlagTypesOverviewModel(model.router, ['qc-flag-types-overview']); this._overviewModel.bubbleTo(this); } diff --git a/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js b/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js index 82eaf9e819..256f2d95b6 100644 --- a/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js @@ -24,8 +24,8 @@ export class FixedPdpBeamTypeRunsOverviewModel extends RunsWithQcModel { * Constructor * @param {Model} model global model */ - constructor(model) { - super(model); + constructor(model, pageIdentifiers) { + super(model, pageIdentifiers); this._pdpBeamTypes = []; } diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 5a80a0ca9c..69cbe415ba 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -50,7 +50,7 @@ export class RunsOverviewModel extends OverviewPageModel { * The constructor of the Overview model object * @param {Model} model global model */ - constructor(model) { + constructor(model, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -102,6 +102,7 @@ export class RunsOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$.bubbleTo(this); @@ -119,7 +120,6 @@ export class RunsOverviewModel extends OverviewPageModel { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); - this._filteringModel.pageIdentifiers = ['run-overview']; } /** diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index f92530b11c..76398e4388 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -69,8 +69,8 @@ export class RunsWithQcModel extends RunsOverviewModel { * Constructor * @param {Model} model global model */ - constructor(model) { - super(model); + constructor(model, pageIdentifiers) { + super(model, pageIdentifiers); this._observablesQcFlagsSummaryDependsOn$ = null; // This filter instance will be added as a sub-filter for a MultiCompositionFilter and a GaqFilter later. diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 7f29e5458a..7ac7206f9e 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -36,8 +36,8 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo * Constructor * @param {Model} model global model */ - constructor(model) { - super(model); + constructor(model, pageIdentifiers) { + super(model, pageIdentifiers); this._dataPass$ = new ObservableData(RemoteData.notAsked()); this._dataPass$.bubbleTo(this); @@ -74,7 +74,6 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo this._discardAllQcFlagsActionState$.bubbleTo(this); this._item$.observe(() => this._fetchGaqSummaryForCurrentRuns()); - this._filteringModel.pageIdentifiers = ['runs-per-data-pass']; } /** diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index 2cd931065b..e63208e70a 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -32,8 +32,8 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM * * @param {Model} model global model */ - constructor(model) { - super(model); + constructor(model, pageIdentifiers) { + super(model, pageIdentifiers); this._lhcPeriodId = null; this._lhcPeriodStatistics$ = new ObservableData(RemoteData.notAsked()); @@ -55,7 +55,6 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM this._tabbedPanelModel = new RunsPerLhcPeriodTabbedPanelModel(this._qcSummary$); this._tabbedPanelModel.bubbleTo(this); - this._filteringModel.pageIdentifiers = ['runs-per-lhc-period']; } /** diff --git a/lib/public/views/Runs/RunsModel.js b/lib/public/views/Runs/RunsModel.js index 934c278d90..199c8eafba 100644 --- a/lib/public/views/Runs/RunsModel.js +++ b/lib/public/views/Runs/RunsModel.js @@ -32,13 +32,13 @@ export class RunsModel extends Observable { super(); this._detailsModel = new RunDetailsModel(); this._detailsModel.bubbleTo(this); - this._overviewModel = new RunsOverviewModel(model); + this._overviewModel = new RunsOverviewModel(model, ['run-overview']); this._overviewModel.bubbleTo(this); - this._perLhcPeriodOverviewModel = new RunsPerLhcPeriodOverviewModel(model); + this._perLhcPeriodOverviewModel = new RunsPerLhcPeriodOverviewModel(model, ['runs-per-lhc-period']); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._perDataPassOverviewModel = new RunsPerDataPassOverviewModel(model); + this._perDataPassOverviewModel = new RunsPerDataPassOverviewModel(model, ['runs-per-data-pass']); this._perDataPassOverviewModel.bubbleTo(this); - this._perSimulationPassOverviewModel = new RunsPerSimulationPassOverviewModel(model); + this._perSimulationPassOverviewModel = new RunsPerSimulationPassOverviewModel(model, ['runs-per-simulation-pass']); this._perSimulationPassOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index 0b78d2fe73..6be80fb1d1 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -24,8 +24,8 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver * Constructor * @param {Model} model global model */ - constructor(model) { - super(model); + constructor(model, pageIdentifiers) { + super(model, pageIdentifiers); this._simulationPass$ = new ObservableData(RemoteData.notAsked()); @@ -33,7 +33,6 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver this._detectors$.bubbleTo(this); this._simulationPass$.bubbleTo(this); - this._filteringModel.pageIdentifiers = ['runs-per-simulation-pass']; } /** diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index 88e434b192..f689d79a65 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -25,11 +25,12 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { + constructor(router, pageIdentifiers) { super(); this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); @@ -38,7 +39,6 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { this._filteringModel.visualChange$.bubbleTo(this); this._dataPass = new ObservableData(RemoteData.notAsked()); - this._filteringModel.pageIdentifiers = ['anchored-simulation-passes-overview']; } /** diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 2d84730a19..1c6fef855e 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -25,10 +25,12 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel * Constructor * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { + constructor(router, pageIdentifiers) { super(); this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); + + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); @@ -39,7 +41,6 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel this._lhcPeriod.bubbleTo(this); this._lhcPeriodId = null; - this._filteringModel.pageIdentifiers = ['simulation-passes-per-lhc-period-overview']; } /** diff --git a/lib/public/views/SimulationPasses/SimulationPassesModel.js b/lib/public/views/SimulationPasses/SimulationPassesModel.js index 22436b4c5c..4dcf8ac55a 100644 --- a/lib/public/views/SimulationPasses/SimulationPassesModel.js +++ b/lib/public/views/SimulationPasses/SimulationPassesModel.js @@ -26,10 +26,10 @@ export class SimulationPassesModel extends Observable { constructor(router) { super(); - this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(router); + this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(router, ['simulation-passes-per-lhc-period-overview']); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(router); + this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(router, ['anchored-simulation-passes-overview']); this._anchoredOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/lhcPeriods/LhcPeriodsModel.js b/lib/public/views/lhcPeriods/LhcPeriodsModel.js index b9ed51719e..c003e9add5 100644 --- a/lib/public/views/lhcPeriods/LhcPeriodsModel.js +++ b/lib/public/views/lhcPeriods/LhcPeriodsModel.js @@ -25,7 +25,7 @@ export class LhcPeriodsModel extends Observable { constructor(router) { super(); - this._overviewModel = new LhcPeriodsOverviewModel(router); + this._overviewModel = new LhcPeriodsOverviewModel(router, ['lhc-period-overview']); this._overviewModel.bubbleTo(this); } diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index c729d89931..98750d226e 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -26,7 +26,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * The constructor of the Overview model object * @param {QueryRouter} router router that controls the application's page navigation */ - constructor(router) { + constructor(router, pageIdentifiers) { super(); this._filteringModel = new FilteringModel( @@ -38,13 +38,12 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { }, ); + this._filteringModel.pageIdentifiers = pageIdentifiers; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); }); - - this._filteringModel.pageIdentifiers = ['lhc-period-overview']; } /** From ae6c2877479b98a808a5ad54409001e5638bd62a Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 24 Apr 2026 16:45:16 +0200 Subject: [PATCH 113/115] chore: rename pageIdentifiers to pageIdentifier --- lib/public/components/Filters/common/FilteringModel.js | 10 +++++----- lib/public/views/DataPasses/DataPassesModel.js | 4 ++-- lib/public/views/DataPasses/DataPassesOverviewModel.js | 5 +++-- .../DataPassesPerLhcPeriodOverviewModel.js | 5 +++-- .../DataPassesPerSimulationPassOverviewModel.js | 5 +++-- lib/public/views/Environments/EnvironmentModel.js | 2 +- .../Environments/Overview/EnvironmentOverviewModel.js | 5 +++-- lib/public/views/Home/Overview/HomePageModel.js | 6 +++--- lib/public/views/LhcFills/LhcFills.js | 2 +- .../views/LhcFills/Overview/LhcFillsOverviewModel.js | 5 +++-- lib/public/views/Logs/Overview/LogsOverviewModel.js | 5 +++-- .../QcFlagTypes/Overview/QcFlagTypesOverviewModel.js | 5 +++-- lib/public/views/QcFlagTypes/QcFlagTypesModel.js | 2 +- .../Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js | 5 +++-- lib/public/views/Runs/Overview/RunsOverviewModel.js | 5 +++-- lib/public/views/Runs/Overview/RunsWithQcModel.js | 5 +++-- .../RunPerDataPass/RunsPerDataPassOverviewModel.js | 5 +++-- .../Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js | 5 +++-- lib/public/views/Runs/RunsModel.js | 8 ++++---- .../RunsPerSimulationPassOverviewModel.js | 5 +++-- .../AnchoredSimulationPassesOverviewModel.js | 5 +++-- .../SimulationPassesPerLhcPeriodOverviewModel.js | 5 +++-- .../views/SimulationPasses/SimulationPassesModel.js | 4 ++-- lib/public/views/lhcPeriods/LhcPeriodsModel.js | 2 +- .../lhcPeriods/Overview/LhcPeriodsOverviewModel.js | 5 +++-- 25 files changed, 68 insertions(+), 52 deletions(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index 8f6f70decc..545d848190 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -29,7 +29,7 @@ export class FilteringModel extends Observable { constructor(router, filters) { super(); this._visualChange$ = new Observable(); - this._pageIdentifiers = []; + this._pageIdentifier = null; this._router = router; this._filters = {}; @@ -40,12 +40,12 @@ export class FilteringModel extends Observable { /** * Sets the page identifiers * - * @param {string[]} identifiers Strings that identify the pages as shown in the router params. + * @param {string} identifier a string identifies a page from the router params. * Used to prevent unneeded reads/writes from/to the url * @returns {void} */ - set pageIdentifiers(identifiers) { - this._pageIdentifiers = identifiers; + set pageIdentifier(identifier) { + this._pageIdentifier = identifier; } /** @@ -133,7 +133,7 @@ export class FilteringModel extends Observable { const newParams = { ...params }; newParams.filter = this.normalized; - if (this._pageIdentifiers.includes(params.page)) { + if (this._pageIdentifier === params.page) { this._router.go(buildUrl('?', newParams), false, true); } diff --git a/lib/public/views/DataPasses/DataPassesModel.js b/lib/public/views/DataPasses/DataPassesModel.js index 95b0135707..78589a56cd 100644 --- a/lib/public/views/DataPasses/DataPassesModel.js +++ b/lib/public/views/DataPasses/DataPassesModel.js @@ -26,10 +26,10 @@ export class DataPassesModel extends Observable { constructor(router) { super(); - this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router, ['data-passes-per-lhc-period-overview']); + this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router, 'data-passes-per-lhc-period-overview'); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router, ['data-passes-per-simulation-pass-overview']); + this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router, 'data-passes-per-simulation-pass-overview'); this._perSimulationPassOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index 38d4d0edd3..d30d5435ed 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -23,8 +23,9 @@ export class DataPassesOverviewModel extends OverviewPageModel { /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { + constructor(router, pageIdentifier) { super(); this._filteringModel = new FilteringModel( router, @@ -36,7 +37,7 @@ export class DataPassesOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.currentPage = 1; diff --git a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js index 145f840496..6da2205751 100644 --- a/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js @@ -20,9 +20,10 @@ export class DataPassesPerLhcPeriodOverviewModel extends DataPassesOverviewModel /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { - super(router, pageIdentifiers); + constructor(router, pageIdentifier) { + super(router, pageIdentifier); this._lhcPeriodId = null; } diff --git a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js index 52c38e9e13..d9b1008552 100644 --- a/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js +++ b/lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js @@ -22,9 +22,10 @@ export class DataPassesPerSimulationPassOverviewModel extends DataPassesOverview /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { - super(router, pageIdentifiers); + constructor(router, pageIdentifier) { + super(router, pageIdentifier); this._simulationPass = new ObservableData(RemoteData.notAsked()); this._simulationPass.bubbleTo(this); } diff --git a/lib/public/views/Environments/EnvironmentModel.js b/lib/public/views/Environments/EnvironmentModel.js index 41b80de18e..53be81b0a0 100644 --- a/lib/public/views/Environments/EnvironmentModel.js +++ b/lib/public/views/Environments/EnvironmentModel.js @@ -29,7 +29,7 @@ export class EnvironmentModel extends Observable { super(); // Sub-models - this._overviewModel = new EnvironmentOverviewModel(model, ['env-overview']); + this._overviewModel = new EnvironmentOverviewModel(model, 'env-overview'); this._overviewModel.bubbleTo(this); this._detailsModel = new EnvironmentDetailsModel(); diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index 024f1590dd..64893e72af 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -28,8 +28,9 @@ export class EnvironmentOverviewModel extends OverviewPageModel { /** * Constructor * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { + constructor(model, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -49,7 +50,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$?.bubbleTo(this); diff --git a/lib/public/views/Home/Overview/HomePageModel.js b/lib/public/views/Home/Overview/HomePageModel.js index 2464042271..d291fc9b47 100644 --- a/lib/public/views/Home/Overview/HomePageModel.js +++ b/lib/public/views/Home/Overview/HomePageModel.js @@ -26,13 +26,13 @@ export class HomePageModel extends Observable { */ constructor(model) { super(); - this._runsOverviewModel = new RunsOverviewModel(model, ['home']); + this._runsOverviewModel = new RunsOverviewModel(model, 'home'); this._runsOverviewModel.bubbleTo(this); - this._logsOverviewModel = new LogsOverviewModel(model, true, ['home']); + this._logsOverviewModel = new LogsOverviewModel(model, true, 'home'); this._logsOverviewModel.bubbleTo(this); - this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model.router, true, ['home']); + this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model.router, true, 'home'); this._lhcFillsOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/LhcFills/LhcFills.js b/lib/public/views/LhcFills/LhcFills.js index c34c12e06a..1fda55ab58 100644 --- a/lib/public/views/LhcFills/LhcFills.js +++ b/lib/public/views/LhcFills/LhcFills.js @@ -29,7 +29,7 @@ export default class LhcFills extends Observable { this.model = model; // Sub-models - this._overviewModel = new LhcFillsOverviewModel(model.router, true, ['lhc-fill-overview']); + this._overviewModel = new LhcFillsOverviewModel(model.router, true, 'lhc-fill-overview'); this._overviewModel.bubbleTo(this); this._detailsModel = new LhcFillDetailsModel(); diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 0414410679..1bf04f6878 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -32,8 +32,9 @@ export class LhcFillsOverviewModel extends OverviewPageModel { * * @param {QueryRouter} router router that controls the application's page navigation * @param {boolean} [stableBeamsOnly=false] if true, overview will load stable beam only + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, stableBeamsOnly = false, pageIdentifiers) { + constructor(router, stableBeamsOnly = false, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -50,7 +51,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 8655037e61..f2d531335b 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -34,8 +34,9 @@ export class LogsOverviewModel extends Observable { * * @param {Model} model global model * @param {boolean} excludeAnonymous Whether to exclude anonymous logs + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, excludeAnonymous = false, pageIdentifiers) { + constructor(model, excludeAnonymous = false, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -52,7 +53,7 @@ export class LogsOverviewModel extends Observable { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index 71c1271b3f..36c3f33f50 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -24,8 +24,9 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { + constructor(router, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -37,7 +38,7 @@ export class QcFlagTypesOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); diff --git a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js index 75bf03cc06..fd5c391ada 100644 --- a/lib/public/views/QcFlagTypes/QcFlagTypesModel.js +++ b/lib/public/views/QcFlagTypes/QcFlagTypesModel.js @@ -29,7 +29,7 @@ export class QcFlagTypesModel extends Observable { this.model = model; // Overview - this._overviewModel = new QcFlagTypesOverviewModel(model.router, ['qc-flag-types-overview']); + this._overviewModel = new QcFlagTypesOverviewModel(model.router, 'qc-flag-types-overview'); this._overviewModel.bubbleTo(this); } diff --git a/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js b/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js index 256f2d95b6..5edce4c14e 100644 --- a/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/FixedPdpBeamTypeRunsOverviewModel.js @@ -23,9 +23,10 @@ export class FixedPdpBeamTypeRunsOverviewModel extends RunsWithQcModel { /** * Constructor * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { - super(model, pageIdentifiers); + constructor(model, pageIdentifier) { + super(model, pageIdentifier); this._pdpBeamTypes = []; } diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 69cbe415ba..c27dbc5e49 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -49,8 +49,9 @@ export class RunsOverviewModel extends OverviewPageModel { /** * The constructor of the Overview model object * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { + constructor(model, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -102,7 +103,7 @@ export class RunsOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => this._applyFilters(true)); this._filteringModel.visualChange$.bubbleTo(this); diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 76398e4388..6a3128b98d 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -68,9 +68,10 @@ export class RunsWithQcModel extends RunsOverviewModel { /** * Constructor * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { - super(model, pageIdentifiers); + constructor(model, pageIdentifier) { + super(model, pageIdentifier); this._observablesQcFlagsSummaryDependsOn$ = null; // This filter instance will be added as a sub-filter for a MultiCompositionFilter and a GaqFilter later. diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 7ac7206f9e..28eed6d61b 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -35,9 +35,10 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo /** * Constructor * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { - super(model, pageIdentifiers); + constructor(model, pageIdentifier) { + super(model, pageIdentifier); this._dataPass$ = new ObservableData(RemoteData.notAsked()); this._dataPass$.bubbleTo(this); diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index e63208e70a..7a63578bef 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -31,9 +31,10 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM * Constructor * * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { - super(model, pageIdentifiers); + constructor(model, pageIdentifier) { + super(model, pageIdentifier); this._lhcPeriodId = null; this._lhcPeriodStatistics$ = new ObservableData(RemoteData.notAsked()); diff --git a/lib/public/views/Runs/RunsModel.js b/lib/public/views/Runs/RunsModel.js index 199c8eafba..fa2106a2cb 100644 --- a/lib/public/views/Runs/RunsModel.js +++ b/lib/public/views/Runs/RunsModel.js @@ -32,13 +32,13 @@ export class RunsModel extends Observable { super(); this._detailsModel = new RunDetailsModel(); this._detailsModel.bubbleTo(this); - this._overviewModel = new RunsOverviewModel(model, ['run-overview']); + this._overviewModel = new RunsOverviewModel(model, 'run-overview'); this._overviewModel.bubbleTo(this); - this._perLhcPeriodOverviewModel = new RunsPerLhcPeriodOverviewModel(model, ['runs-per-lhc-period']); + this._perLhcPeriodOverviewModel = new RunsPerLhcPeriodOverviewModel(model, 'runs-per-lhc-period'); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._perDataPassOverviewModel = new RunsPerDataPassOverviewModel(model, ['runs-per-data-pass']); + this._perDataPassOverviewModel = new RunsPerDataPassOverviewModel(model, 'runs-per-data-pass'); this._perDataPassOverviewModel.bubbleTo(this); - this._perSimulationPassOverviewModel = new RunsPerSimulationPassOverviewModel(model, ['runs-per-simulation-pass']); + this._perSimulationPassOverviewModel = new RunsPerSimulationPassOverviewModel(model, 'runs-per-simulation-pass'); this._perSimulationPassOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index 6be80fb1d1..5d081c33f5 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -23,9 +23,10 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver /** * Constructor * @param {Model} model global model + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(model, pageIdentifiers) { - super(model, pageIdentifiers); + constructor(model, pageIdentifier) { + super(model, pageIdentifier); this._simulationPass$ = new ObservableData(RemoteData.notAsked()); diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index f689d79a65..b74c665344 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -24,13 +24,14 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { + constructor(router, pageIdentifier) { super(); this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); this.load(); diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index 1c6fef855e..51556d1f1c 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -24,13 +24,14 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel /** * Constructor * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { + constructor(router, pageIdentifier) { super(); this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); diff --git a/lib/public/views/SimulationPasses/SimulationPassesModel.js b/lib/public/views/SimulationPasses/SimulationPassesModel.js index 4dcf8ac55a..03ec1818fd 100644 --- a/lib/public/views/SimulationPasses/SimulationPassesModel.js +++ b/lib/public/views/SimulationPasses/SimulationPassesModel.js @@ -26,10 +26,10 @@ export class SimulationPassesModel extends Observable { constructor(router) { super(); - this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(router, ['simulation-passes-per-lhc-period-overview']); + this._perLhcPeriodOverviewModel = new SimulationPassesPerLhcPeriodOverviewModel(router, 'simulation-passes-per-lhc-period-overview'); this._perLhcPeriodOverviewModel.bubbleTo(this); - this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(router, ['anchored-simulation-passes-overview']); + this._anchoredOverviewModel = new AnchoredSimulationPassesOverviewModel(router, 'anchored-simulation-passes-overview'); this._anchoredOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/lhcPeriods/LhcPeriodsModel.js b/lib/public/views/lhcPeriods/LhcPeriodsModel.js index c003e9add5..7400961fec 100644 --- a/lib/public/views/lhcPeriods/LhcPeriodsModel.js +++ b/lib/public/views/lhcPeriods/LhcPeriodsModel.js @@ -25,7 +25,7 @@ export class LhcPeriodsModel extends Observable { constructor(router) { super(); - this._overviewModel = new LhcPeriodsOverviewModel(router, ['lhc-period-overview']); + this._overviewModel = new LhcPeriodsOverviewModel(router, 'lhc-period-overview'); this._overviewModel.bubbleTo(this); } diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index 98750d226e..11df6a7ab7 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -25,8 +25,9 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { /** * The constructor of the Overview model object * @param {QueryRouter} router router that controls the application's page navigation + * @param {string} pageIdentifier string that indicates what page this model represents */ - constructor(router, pageIdentifiers) { + constructor(router, pageIdentifier) { super(); this._filteringModel = new FilteringModel( @@ -38,7 +39,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { }, ); - this._filteringModel.pageIdentifiers = pageIdentifiers; + this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => { this._pagination.silentlySetCurrentPage(1); From 90e1911e44fc3b6f0972f6ae7836aa567cbb6de7 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 5 May 2026 09:39:24 +0200 Subject: [PATCH 114/115] add pageIdentifier setter and router getter back --- .../components/Filters/common/FilteringModel.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/public/components/Filters/common/FilteringModel.js b/lib/public/components/Filters/common/FilteringModel.js index 29e7343caf..545d848190 100644 --- a/lib/public/components/Filters/common/FilteringModel.js +++ b/lib/public/components/Filters/common/FilteringModel.js @@ -14,7 +14,7 @@ import { expandQueryLikeNestedKey } from '../../../utilities/expandNestedKey.js'; import { SelectionModel } from '../../common/selection/SelectionModel.js'; import { FilterModel } from './FilterModel.js'; -import { Observable } from '/js/src/index.js'; +import { buildUrl, Observable } from '/js/src/index.js'; /** * Model representing a filtering system, including filter inputs visibility, filters values and so on @@ -31,11 +31,23 @@ export class FilteringModel extends Observable { this._visualChange$ = new Observable(); this._pageIdentifier = null; + this._router = router; this._filters = {}; this._filterModels = []; Object.entries(filters).forEach(([key, model]) => this.put(key, model)); } + /** + * Sets the page identifiers + * + * @param {string} identifier a string identifies a page from the router params. + * Used to prevent unneeded reads/writes from/to the url + * @returns {void} + */ + set pageIdentifier(identifier) { + this._pageIdentifier = identifier; + } + /** * Reset the filters * From f1bb6370da2caba9eed8b736b104dc8b0597ebc1 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Tue, 5 May 2026 09:50:11 +0200 Subject: [PATCH 115/115] add mcreproducable toggle back to test --- test/public/Filters/filtersToUrl.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/public/Filters/filtersToUrl.test.js b/test/public/Filters/filtersToUrl.test.js index dc0dc24356..ce420cdaf2 100644 --- a/test/public/Filters/filtersToUrl.test.js +++ b/test/public/Filters/filtersToUrl.test.js @@ -335,7 +335,8 @@ module.exports = () => { "filter[muInelasticInteractionRate][operator]": "=", "filter[muInelasticInteractionRate][limit]": "100000", "filter[inelasticInteractionRateAvg][operator]": "=", - "filter[inelasticInteractionRateAvg][limit]": "100000" + "filter[inelasticInteractionRateAvg][limit]": "100000", + "filter[detectorsQcNotBadFraction][mcReproducibleAsNotBad]": "false" }); });