From fe19db74bf81f297687facb76954a98fbaee5b71 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 15:29:10 +0200 Subject: [PATCH 01/25] chore: rename fetchLogs to load --- lib/public/views/Home/Overview/HomePageModel.js | 2 +- lib/public/views/Logs/LogsModel.js | 2 +- lib/public/views/Logs/Overview/LogsOverviewModel.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/public/views/Home/Overview/HomePageModel.js b/lib/public/views/Home/Overview/HomePageModel.js index d291fc9b47..e40fe38952 100644 --- a/lib/public/views/Home/Overview/HomePageModel.js +++ b/lib/public/views/Home/Overview/HomePageModel.js @@ -42,7 +42,7 @@ export class HomePageModel extends Observable { */ loadOverview() { this._runsOverviewModel.load(); - this._logsOverviewModel.fetchLogs(true); + this._logsOverviewModel.load(true); this._lhcFillsOverviewModel.load(); } diff --git a/lib/public/views/Logs/LogsModel.js b/lib/public/views/Logs/LogsModel.js index 7822fa4d79..1c894620a4 100644 --- a/lib/public/views/Logs/LogsModel.js +++ b/lib/public/views/Logs/LogsModel.js @@ -56,7 +56,7 @@ export class LogsModel extends Observable { loadOverview() { if (!this._overviewModel.pagination.isInfiniteScrollEnabled) { this._overviewModel.setFilterFromURL(false); - this._overviewModel.fetchLogs(); + this._overviewModel.load(); } } diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index f6fcb01638..cbf1c9a518 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -93,7 +93,7 @@ export class LogsOverviewModel extends Observable { * Retrieve every relevant log from the API * @returns {Promise} Injects the data object with the response data */ - async fetchLogs() { + async load() { const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; const sortOn = this._overviewSortModel.appliedOn; const sortDirection = this._overviewSortModel.appliedDirection; @@ -203,6 +203,6 @@ export class LogsOverviewModel extends Observable { */ _applyFilters(now = false) { this._pagination.silentlySetCurrentPage(1); - now ? this.fetchLogs() : this._debouncedFetchAllLogs(); + now ? this.load() : this._debouncedFetchAllLogs(); } } From 94b04f76559388d3956f48e3b123bea38d270df2 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 15:33:09 +0200 Subject: [PATCH 02/25] chore: rename overviewSortModel to sortModel --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 12 ++++++------ lib/public/views/Logs/Overview/index.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index cbf1c9a518..570a183d93 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -71,8 +71,8 @@ export class LogsOverviewModel extends Observable { this._filteringModel.visualChange$.bubbleTo(this); // Sub-models - this._overviewSortModel.observe(() => this._applyFilters(true)); - this._overviewSortModel.visualChange$.bubbleTo(this); + this._sortModel.observe(() => this._applyFilters(true)); + this._sortModel.visualChange$.bubbleTo(this); this._pagination.observe(() => this.fetchLogs()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); @@ -95,8 +95,8 @@ export class LogsOverviewModel extends Observable { */ async load() { const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; - const sortOn = this._overviewSortModel.appliedOn; - const sortDirection = this._overviewSortModel.appliedDirection; + const sortOn = this._sortModel.appliedOn; + const sortDirection = this._sortModel.appliedDirection; if (!keepExisting) { this._logs = RemoteData.loading(); @@ -172,8 +172,8 @@ export class LogsOverviewModel extends Observable { * * @return {SortModel} the sort model */ - get overviewSortModel() { - return this._overviewSortModel; + get sortModel() { + return this._sortModel; } /** diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index babeb820e5..f0b224f428 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -31,7 +31,7 @@ const PAGE_USED_HEIGHT = 215; * @return {Component} Returns a vnode with the table containing the logs */ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { - const { pagination, filteringModel, logs, overviewSortModel } = logsOverviewModel; + const { pagination, filteringModel, logs, sortModel } = logsOverviewModel; pagination.provideDefaultItemsPerPage(estimateDisplayableRowsCount(TABLEROW_HEIGHT, PAGE_USED_HEIGHT)); @@ -45,7 +45,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { ]), warningComponent(logsOverviewModel), h('.w-100.flex-column', [ - table(logs, logsActiveColumns, null, null, { sort: overviewSortModel }), + table(logs, logsActiveColumns, null, null, { sort: sortModel }), paginationComponent(pagination), ]), ]); From 6efeb006c9b9cdf5db3eba2c1354f197a38277a7 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 15:43:20 +0200 Subject: [PATCH 03/25] load patch --- 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 570a183d93..0295846ae6 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -58,7 +58,7 @@ export class LogsOverviewModel extends Observable { this._overviewSortModel = new SortModel(); this._pagination = new PaginationModel(); const updateDebounceTime = () => { - this._debouncedFetchAllLogs = debounce(this.fetchLogs.bind(this), model.inputDebounceTime); + this._debouncedFetchAllLogs = debounce(this.load.bind(this), model.inputDebounceTime); }; updateDebounceTime(); @@ -74,10 +74,10 @@ export class LogsOverviewModel extends Observable { this._sortModel.observe(() => this._applyFilters(true)); this._sortModel.visualChange$.bubbleTo(this); - this._pagination.observe(() => this.fetchLogs()); + this._pagination.observe(() => this.load()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); - this._logs = RemoteData.NotAsked(); + this._item$ = RemoteData.NotAsked(); } /** From 41f2678f6890611127e57ba4591c9d2657e40218 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 15:47:05 +0200 Subject: [PATCH 04/25] chore: rename logs to items$ --- .../views/Logs/Overview/LogsOverviewModel.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 0295846ae6..e53cfa3084 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -58,7 +58,7 @@ export class LogsOverviewModel extends Observable { this._overviewSortModel = new SortModel(); this._pagination = new PaginationModel(); const updateDebounceTime = () => { - this._debouncedFetchAllLogs = debounce(this.load.bind(this), model.inputDebounceTime); + this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); }; updateDebounceTime(); @@ -99,7 +99,7 @@ export class LogsOverviewModel extends Observable { const sortDirection = this._sortModel.appliedDirection; if (!keepExisting) { - this._logs = RemoteData.loading(); + this._item$ = RemoteData.loading(); this.notify(); } @@ -116,11 +116,11 @@ export class LogsOverviewModel extends Observable { try { const { items, totalCount } = await getRemoteDataSlice(endpoint); - const concatenateWith = keepExisting ? this._logs.payload ?? [] : []; - this._logs = RemoteData.success([...concatenateWith, ...items]); + const concatenateWith = keepExisting ? this._item$.payload ?? [] : []; + this._item$ = RemoteData.success([...concatenateWith, ...items]); this._pagination.itemsCount = totalCount; } catch (errors) { - this._logs = RemoteData.failure(errors); + this._item$ = RemoteData.failure(errors); } this.notify(); @@ -130,8 +130,8 @@ export class LogsOverviewModel extends Observable { * Return current logs * @return {RemoteData<*[]>} current data */ - get logs() { - return this._logs; + get items() { + return this._item$; } /** @@ -203,6 +203,6 @@ export class LogsOverviewModel extends Observable { */ _applyFilters(now = false) { this._pagination.silentlySetCurrentPage(1); - now ? this.load() : this._debouncedFetchAllLogs(); + now ? this.load() : this._debouncedLoad(); } } From eb4ed78d309b572dd2788d16daf540361368601b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 16:34:54 +0200 Subject: [PATCH 05/25] feat: make logsOverveiwModel extend overviewModel --- .../views/Logs/Overview/LogsOverviewModel.js | 89 ++----------------- lib/public/views/Logs/Overview/index.js | 4 +- 2 files changed, 8 insertions(+), 85 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index e53cfa3084..de0f597c97 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -11,24 +11,22 @@ * or submit itself to any jurisdiction. */ -import { buildUrl, Observable, RemoteData } from '/js/src/index.js'; +import { buildUrl } 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 { 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'; import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; +import { OverviewPageModel } from '../../../models/OverviewModel.js'; /** * Model representing handlers for log entries page * * @implements {OverviewModel} */ -export class LogsOverviewModel extends Observable { +export class LogsOverviewModel extends OverviewPageModel { /** * The constructor of the Overview model object * @@ -38,8 +36,6 @@ export class LogsOverviewModel extends Observable { */ constructor(model, excludeAnonymous = false, pageIdentifier) { super(); - this._warnings = new Map(); - this._filteringModel = new FilteringModel( model.router, { @@ -55,8 +51,6 @@ export class LogsOverviewModel extends Observable { this._warnings, ); - this._overviewSortModel = new SortModel(); - this._pagination = new PaginationModel(); const updateDebounceTime = () => { this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); }; @@ -72,12 +66,6 @@ export class LogsOverviewModel extends Observable { // Sub-models this._sortModel.observe(() => this._applyFilters(true)); - this._sortModel.visualChange$.bubbleTo(this); - - this._pagination.observe(() => this.load()); - this._pagination.itemsPerPageSelector$.observe(() => this.notify()); - - this._item$ = RemoteData.NotAsked(); } /** @@ -90,48 +78,10 @@ export class LogsOverviewModel extends Observable { } /** - * Retrieve every relevant log from the API - * @returns {Promise} Injects the data object with the response data - */ - async load() { - const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; - const sortOn = this._sortModel.appliedOn; - const sortDirection = this._sortModel.appliedDirection; - - if (!keepExisting) { - this._item$ = RemoteData.loading(); - this.notify(); - } - - const params = { - ...sortOn && sortDirection && { - [`sort[${sortOn}]`]: sortDirection, - }, - filter: this._filteringModel.normalized, - 'page[offset]': this._pagination.firstItemOffset, - 'page[limit]': this._pagination.itemsPerPage, - }; - - const endpoint = buildUrl('/api/logs', params); - - try { - const { items, totalCount } = await getRemoteDataSlice(endpoint); - const concatenateWith = keepExisting ? this._item$.payload ?? [] : []; - this._item$ = RemoteData.success([...concatenateWith, ...items]); - this._pagination.itemsCount = totalCount; - } catch (errors) { - this._item$ = RemoteData.failure(errors); - } - - this.notify(); - } - - /** - * Return current logs - * @return {RemoteData<*[]>} current data + * @inheritdoc */ - get items() { - return this._item$; + getRootEndpoint() { + return buildUrl('/api/logs', { filter: this.filteringModel.normalized }); } /** @@ -167,33 +117,6 @@ export class LogsOverviewModel extends Observable { return this._filteringModel; } - /** - * Returns the model handling the overview page table sort - * - * @return {SortModel} the sort model - */ - get sortModel() { - return this._sortModel; - } - - /** - * Returns the pagination model - * - * @return {PaginationModel} the pagination model - */ - get pagination() { - return this._pagination; - } - - /** - * Returns the warnings object - * - * @return {object} the warning model - */ - get warnings() { - return this._warnings; - } - /** * Apply the current filtering and update the remote data list * diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index f0b224f428..39e9658585 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -31,7 +31,7 @@ const PAGE_USED_HEIGHT = 215; * @return {Component} Returns a vnode with the table containing the logs */ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { - const { pagination, filteringModel, logs, sortModel } = logsOverviewModel; + const { pagination, filteringModel, item, sortModel } = logsOverviewModel; pagination.provideDefaultItemsPerPage(estimateDisplayableRowsCount(TABLEROW_HEIGHT, PAGE_USED_HEIGHT)); @@ -45,7 +45,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { ]), warningComponent(logsOverviewModel), h('.w-100.flex-column', [ - table(logs, logsActiveColumns, null, null, { sort: sortModel }), + table(item, logsActiveColumns, null, null, { sort: sortModel }), paginationComponent(pagination), ]), ]); From d20f11528eb955200d9a040e6cee2831be28e84b Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Thu, 18 Jun 2026 16:57:48 +0200 Subject: [PATCH 06/25] chore: fix spelling mistake --- lib/public/views/Home/Overview/HomePage.js | 2 +- lib/public/views/Logs/Overview/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/public/views/Home/Overview/HomePage.js b/lib/public/views/Home/Overview/HomePage.js index 92705e6973..3768f6cf6d 100644 --- a/lib/public/views/Home/Overview/HomePage.js +++ b/lib/public/views/Home/Overview/HomePage.js @@ -46,7 +46,7 @@ export const HomePage = ({ home: { logsOverviewModel, runsOverviewModel, lhcFill h('.flex-row.g2', [ h('.flex-column', [ h('h3', 'Log Entries'), - h('.f6#logs-panel', table(logsOverviewModel.logs, logsActiveColumns, null, { profile: 'home' })), + h('.f6#logs-panel', table(logsOverviewModel.items, logsActiveColumns, null, { profile: 'home' })), ]), h('.flex-column', [ h('h3', 'LHC Fills'), diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index 39e9658585..bf72f81c3a 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -31,7 +31,7 @@ const PAGE_USED_HEIGHT = 215; * @return {Component} Returns a vnode with the table containing the logs */ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { - const { pagination, filteringModel, item, sortModel } = logsOverviewModel; + const { pagination, filteringModel, items, sortModel } = logsOverviewModel; pagination.provideDefaultItemsPerPage(estimateDisplayableRowsCount(TABLEROW_HEIGHT, PAGE_USED_HEIGHT)); @@ -45,7 +45,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { ]), warningComponent(logsOverviewModel), h('.w-100.flex-column', [ - table(item, logsActiveColumns, null, null, { sort: sortModel }), + table(items, logsActiveColumns, null, null, { sort: sortModel }), paginationComponent(pagination), ]), ]); From aa2af05f5b439f24e922c32b70546234fecf5272 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:39:21 +0200 Subject: [PATCH 07/25] feat: create FilterableOverviewPageModel --- .../models/FilterableOverviewPageModel.js | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 lib/public/models/FilterableOverviewPageModel.js diff --git a/lib/public/models/FilterableOverviewPageModel.js b/lib/public/models/FilterableOverviewPageModel.js new file mode 100644 index 0000000000..bfbf4ee2c8 --- /dev/null +++ b/lib/public/models/FilterableOverviewPageModel.js @@ -0,0 +1,128 @@ +/** + * @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 { buildUrl } from '/js/src/index.js'; +import { OverviewPageModel } from './OverviewModel.js'; +import { FilteringModel } from '../components/Filters/common/FilteringModel.js'; + +/** + * Base model for a filterable overview page + * + * @template T the type of data displayed in the overview page + */ +export class FilterableOverviewPageModel extends OverviewPageModel { + /** + * Constructor + */ + constructor(router, pageIdentifier, filters) { + super(); + this._filteringModel = new FilteringModel(router, filters, this._warnings); + + this._filteringModel.pageIdentifier = pageIdentifier; + this._filteringModel.visualChange$.bubbleTo(this); + this._filteringModel.observe(() => this._applyFilters()); + this._sortModel.observe(() => this._applyFilters()); + this._debouncedLoad = (_time) => {}; // Abstract, does nothing on purpose + this._fetchInstantly = true; + } + + /** + * Builds a url string from filters and a base string + * + * @param {string} base the base string from which the endpoint will be built + * @return {string} + */ + buildRootEndpoint(base) { + return buildUrl(base, { filter: this.getFilterParams() }); + } + + /** + * Sets the fetchInstantly boolean + * @param {boolean} bool the value to set + * @return {void} + */ + set fetchInstantly(bool) { + this._fetchInstantly = bool; + } + + /** + * Returns all filtering, sorting and pagination settings to their default values + * @param {boolean} [fetch = true] whether to refetch all data after filters have been reset + * @return {void} + */ + reset(fetch = true) { + super.reset(); + this.resetFiltering(fetch); + } + + /** + * 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, clearUrl = false) { + this._filteringModel.reset(false, clearUrl); + + if (fetch) { + this._applyFilters(true); + } + } + + /** + * Checks if any filter value has been modified from their default (empty) + * @return {Boolean} If any filter is active + */ + isAnyFilterActive() { + return this._filteringModel.isAnyFilterActive(); + } + + /** + * Apply the current filtering and update the remote data list + * + * @param {boolean} now if true, filtering will be applied now without debouncing + * + * @return {void} + */ + _applyFilters() { + this._pagination.silentlySetCurrentPage(1); + this._fetchInstantly ? this.load() : this._debouncedLoad(); + } + + /** + * Set underlying FilteringModel's filters from the query parameters in the URL + * + * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters + */ + setFilterFromURL(notify) { + this._filteringModel.setFilterFromURL(notify); + } + + /** + * Return the filtering model + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + + /** + * Return filter params of base model + * + * @return {object} filter + */ + getFilterParams() { + return this._filteringModel.normalized; + } +} From 5936b8e5a84a45d1c4e9fa4c362f601cb72a48ef Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:40:01 +0200 Subject: [PATCH 08/25] feat: migrate datapassesOverviewModel --- .../DataPasses/DataPassesOverviewModel.js | 56 ++----------------- 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/lib/public/views/DataPasses/DataPassesOverviewModel.js b/lib/public/views/DataPasses/DataPassesOverviewModel.js index 2834e1db1d..4d07c34e51 100644 --- a/lib/public/views/DataPasses/DataPassesOverviewModel.js +++ b/lib/public/views/DataPasses/DataPassesOverviewModel.js @@ -11,77 +11,29 @@ * or submit itself to any jurisdiction. */ import { SelectionModel } from '../../components/common/selection/SelectionModel.js'; -import { FilteringModel } from '../../components/Filters/common/FilteringModel.js'; import { TextTokensFilterModel } from '../../components/Filters/common/filters/TextTokensFilterModel.js'; import { NON_PHYSICS_PRODUCTIONS_NAMES_WORDS } from '../../domain/enums/NonPhysicsProductionsNamesWords.js'; -import { OverviewPageModel } from '../../models/OverviewModel.js'; +import { FilterableOverviewPageModel } from '../../models/FilterableOverviewPageModel.js'; /** * Data Passes overview model */ -export class DataPassesOverviewModel extends OverviewPageModel { +export class DataPassesOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - this._filteringModel = new FilteringModel( + super( router, + pageIdentifier, { names: new TextTokensFilterModel(), permittedNonPhysicsNames: new SelectionModel({ availableOptions: NON_PHYSICS_PRODUCTIONS_NAMES_WORDS.map((word) => ({ label: word.toUpperCase(), value: word })), }), }, - this._warnings, ); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.visualChange$.bubbleTo(this); - this._filteringModel.observe(() => { - this._pagination.currentPage = 1; - this.load(); - }); - } - - /** - * Return filter params of base model - * - * @return {object} filter - */ - getFilterParams() { - return this._filteringModel.normalized; - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Reset this model to its default - * - * @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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - super.reset(); - } - - /** - * Return the filtering model - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; } } From 37ec014cd1b73e9bb50bb7b49231871a8415a210 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:40:14 +0200 Subject: [PATCH 09/25] feat: migrate environmentsOverviewModel --- .../Overview/EnvironmentOverviewModel.js | 80 ++----------------- 1 file changed, 5 insertions(+), 75 deletions(-) diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index 5d6eb72cb3..92453fefce 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -11,30 +11,27 @@ * or submit itself to any jurisdiction. */ -import { buildUrl } from '/js/src/index.js'; -import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; -import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { debounce } from '../../../utilities/debounce.js'; import { coloredEnvironmentStatusComponent } from '../ColoredEnvironmentStatusComponent.js'; import { StatusAcronym } from '../../../domain/enums/statusAcronym.mjs'; import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * Environment overview page model */ -export class EnvironmentOverviewModel extends OverviewPageModel { +export class EnvironmentOverviewModel extends FilterableOverviewPageModel { /** * Constructor * @param {Model} model global model * @param {string} pageIdentifier string that indicates what page this model represents */ constructor(model, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel( + super( model.router, + pageIdentifier, { created: new TimeRangeInputModel(), runNumbers: new RawTextFilterModel(), @@ -48,13 +45,8 @@ export class EnvironmentOverviewModel extends OverviewPageModel { }), ids: new RawTextFilterModel(), }, - this._warnings, ); - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.observe(() => this._applyFilters(true)); - this._filteringModel.visualChange$?.bubbleTo(this); - const updateDebounceTime = () => { this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); }; @@ -67,16 +59,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel { * @inheritDoc */ getRootEndpoint() { - return buildUrl('/api/environments', { filter: this.filteringModel.normalized }); - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); + return this.buildRootEndpoint('/api/environments'); } /** @@ -87,57 +70,4 @@ export class EnvironmentOverviewModel extends OverviewPageModel { get environments() { return this.items; } - - /** - * Returns all filtering, sorting and pagination settings to their default values - * @param {boolean} [fetch = true] whether to refetch all data after filters have been reset - * @return {void} - */ - reset(fetch = true) { - super.reset(); - this.resetFiltering(fetch); - } - - /** - * 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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - - if (fetch) { - this._applyFilters(true); - } - } - - /** - * Checks if any filter value has been modified from their default (empty) - * @return {Boolean} If any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } - - /** - * Return the filtering model - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - - /** - * Apply the current filtering and update the remote data list - * - * @param {boolean} now if true, filtering will be applied now without debouncing - * - * @return {void} - */ - _applyFilters(now = false) { - this._pagination.currentPage = 1; - now ? this.load() : this._debouncedLoad(true); - } } From 6a2806a03d812481e5fb625c392893000ee13aea Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:40:28 +0200 Subject: [PATCH 10/25] feat: migrate LhcFillsOverviewModel --- .../Overview/LhcFillsOverviewModel.js | 78 ++----------------- 1 file changed, 5 insertions(+), 73 deletions(-) diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 567182d3c5..3e73c2fa0f 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -11,22 +11,20 @@ * or submit itself to any jurisdiction. */ -import { buildUrl } from '/js/src/index.js'; -import { FilteringModel } from '../../../components/Filters/common/FilteringModel.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'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * Model for the LHC fills overview page * * @implements {OverviewModel} */ -export class LhcFillsOverviewModel extends OverviewPageModel { +export class LhcFillsOverviewModel extends FilterableOverviewPageModel { /** * Constructor * @@ -35,10 +33,9 @@ export class LhcFillsOverviewModel extends OverviewPageModel { * @param {string} pageIdentifier string that indicates what page this model represents */ constructor(router, stableBeamsOnly = false, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel( + super( router, + pageIdentifier, { fillNumbers: new RawTextFilterModel(), beamDuration: new TextComparisonFilterModel(), @@ -49,12 +46,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { beamTypes: new BeamTypeFilterModel(), schemeName: new RawTextFilterModel(), }, - this._warnings, ); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.observe(() => this._applyFilters()); - this._filteringModel.visualChange$.bubbleTo(this); } /** @@ -71,66 +63,6 @@ export class LhcFillsOverviewModel extends OverviewPageModel { * @inheritDoc */ getRootEndpoint() { - return buildUrl('/api/lhcFills', { filter: this.filteringModel.normalized }); - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Returns all filtering, sorting and pagination settings to their default values - * @param {boolean} [fetch = true] whether to refetch all data after filters have been reset - * @return {void} - */ - reset(fetch = true) { - super.reset(); - this.resetFiltering(fetch); - } - - /** - * 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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - - if (fetch) { - this._applyFilters(); - } - } - - /** - * Checks if any filter value has been modified from their default (empty) - * @return {Boolean} If any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } - - /** - * Return the filtering model - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - - /** - * Apply the current filtering and update the remote data list - * - * @return {void} - */ - _applyFilters() { - this._pagination.currentPage = 1; - this.load(); + return this.buildRootEndpoint('/api/lhcFills'); } } From 855e4a05cf5370e71ff9522b705758491d5165c9 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:40:38 +0200 Subject: [PATCH 11/25] feat: migrate LhcPeriodsOverviewModel --- .../Overview/LhcPeriodsOverviewModel.js | 52 ++----------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index f517fe02e7..c65cf8d93f 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -11,33 +11,29 @@ * 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'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * LHC Periods overview model * * @implements {OverviewModel} */ -export class LhcPeriodsOverviewModel extends OverviewPageModel { +export class LhcPeriodsOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel( + super( router, + pageIdentifier, { names: new TextTokensFilterModel(), years: new TextTokensFilterModel(), pdpBeamTypes: new TextTokensFilterModel(), }, - this._warnings, ); this._filteringModel.pageIdentifier = pageIdentifier; @@ -52,25 +48,7 @@ export class LhcPeriodsOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - return buildUrl('/api/lhcPeriodsStatistics', { filter: this._filteringModel.normalized }); - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Return the model managing all filters - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; + return this.buildRootEndpoint('/api/lhcPeriodsStatistics'); } /** @@ -88,24 +66,4 @@ 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} [clearUrl=false] if true filters will be removed from the url - * @return {void} - */ - reset(_fetch = true, clearUrl = false) { - super.reset(); - this._filteringModel.reset(false, clearUrl); - } - - /** - * States whether any filter is active - * @return {boolean} true if any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } } From 81c0e06bd4ff0aa8d9f1d67db576200054a74620 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:40:48 +0200 Subject: [PATCH 12/25] feat: migrate LogsOverviewModel --- .../views/Logs/Overview/LogsOverviewModel.js | 75 ++----------------- 1 file changed, 5 insertions(+), 70 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index de0f597c97..11b48937c4 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -11,22 +11,20 @@ * or submit itself to any jurisdiction. */ -import { buildUrl } from '/js/src/index.js'; import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; import { debounce } from '../../../utilities/debounce.js'; import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author/AuthorFilterModel.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'; import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; -import { OverviewPageModel } from '../../../models/OverviewModel.js'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * Model representing handlers for log entries page * * @implements {OverviewModel} */ -export class LogsOverviewModel extends OverviewPageModel { +export class LogsOverviewModel extends FilterableOverviewPageModel { /** * The constructor of the Overview model object * @@ -35,9 +33,9 @@ export class LogsOverviewModel extends OverviewPageModel { * @param {string} pageIdentifier string that indicates what page this model represents */ constructor(model, excludeAnonymous = false, pageIdentifier) { - super(); - this._filteringModel = new FilteringModel( + super( model.router, + pageIdentifier, { author: new AuthorFilterModel(), title: new RawTextFilterModel(), @@ -48,7 +46,6 @@ export class LogsOverviewModel extends OverviewPageModel { fillNumbers: new RawTextFilterModel(), created: new TimeRangeInputModel(), }, - this._warnings, ); const updateDebounceTime = () => { @@ -57,75 +54,13 @@ export class LogsOverviewModel extends OverviewPageModel { updateDebounceTime(); model.appConfiguration$.observe(() => updateDebounceTime()); - - // Filters - this.filteringModel.pageIdentifier = pageIdentifier; excludeAnonymous && this._filteringModel.get('author').update('!Anonymous'); - this._filteringModel.observe(() => this._applyFilters()); - this._filteringModel.visualChange$.bubbleTo(this); - - // Sub-models - this._sortModel.observe(() => this._applyFilters(true)); - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); } /** * @inheritdoc */ getRootEndpoint() { - return buildUrl('/api/logs', { filter: this.filteringModel.normalized }); - } - - /** - * Reset all filtering, sorting and pagination settings to their default values - * - * @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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - this._pagination.reset(); - - if (fetch) { - this._applyFilters(true); - } - } - - /** - * Checks if any filter value has been modified from their default (empty) - * @returns {boolean} If any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } - - /** - * Return the model managing all filters - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - - /** - * Apply the current filtering and update the remote data list - * - * @param {boolean} now if true, filtering will be applied now without debouncing - * - * @return {void} - */ - _applyFilters(now = false) { - this._pagination.silentlySetCurrentPage(1); - now ? this.load() : this._debouncedLoad(); + return this.buildRootEndpoint('/api/logs'); } } From 4e55f37f1a38039b2df9237710418a6f77a18baa Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:41:13 +0200 Subject: [PATCH 13/25] feat: migrate QCFlagTypesOverviewModel --- .../Overview/QcFlagTypesOverviewModel.js | 61 ++----------------- 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js index a87baedaa5..8f12861e8f 100644 --- a/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js +++ b/lib/public/views/QcFlagTypes/Overview/QcFlagTypesOverviewModel.js @@ -12,85 +12,34 @@ */ import { TextTokensFilterModel } from '../../../components/Filters/common/filters/TextTokensFilterModel.js'; -import { OverviewPageModel } from '../../../models/OverviewModel.js'; -import { buildUrl } from '/js/src/index.js'; -import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { RadioButtonFilterModel } from '../../../components/Filters/common/RadioButtonFilterModel.js'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * QcFlagTypesOverviewModel */ -export class QcFlagTypesOverviewModel extends OverviewPageModel { +export class QcFlagTypesOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel( + super( router, + pageIdentifier, { names: new TextTokensFilterModel(), methods: new TextTokensFilterModel(), bad: new RadioButtonFilterModel([{ label: 'Any' }, { label: 'Bad', value: true }, { label: 'Not Bad', value: false }]), }, - this._warnings, ); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); - - this._filteringModel.visualChange$.bubbleTo(this); } /** * @inheritdoc */ getRootEndpoint() { - return buildUrl('/api/qcFlagTypes', { filter: this._filteringModel.normalized }); - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Return the model managing all filters - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - - /** - * States whether any filter is active - * - * @return {boolean} true if any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } - - /** - * Reset this model to its default - * - * @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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - super.reset(); + return this.buildRootEndpoint('/api/qcFlagTypes'); } } From 7f5f85773ef3c5e38d9c0c9e03f84f53954032c8 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:41:25 +0200 Subject: [PATCH 14/25] feat: migrate RunsOverviewModel --- .../views/Runs/Overview/RunsOverviewModel.js | 81 ++----------------- 1 file changed, 5 insertions(+), 76 deletions(-) diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 1b15fff0dc..1a42d6f920 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -11,18 +11,16 @@ * or submit itself to any jurisdiction. */ -import { buildUrl } from '/js/src/index.js'; import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; import { debounce } from '../../../utilities/debounce.js'; import { DetectorsFilterModel } from '../../../components/Filters/RunsFilter/DetectorsFilterModel.js'; import { RunTypesFilterModel } from '../../../components/runTypes/RunTypesFilterModel.js'; import { EorReasonFilterModel } from '../../../components/Filters/RunsFilter/EorReasonFilterModel.js'; -import { OverviewPageModel } from '../../../models/OverviewModel.js'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; import { CombinationOperator } from '../../../components/Filters/common/CombinationOperatorChoiceModel.js'; import { NumericalComparisonFilterModel } from '../../../components/Filters/common/filters/NumericalComparisonFilterModel.js'; import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { MagnetsFilteringModel } from '../../../components/Filters/RunsFilter/MagnetsFilteringModel.js'; -import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { tagsProvider } from '../../../services/tag/tagsProvider.js'; import { eorReasonTypeProvider } from '../../../services/eorReason/eorReasonTypeProvider.js'; import { runTypesProvider } from '../../../services/runTypes/runTypesProvider.js'; @@ -44,17 +42,16 @@ import { TRIGGER_VALUES } from '../../../domain/enums/TriggerValue.js'; * * @implements {OverviewModel} */ -export class RunsOverviewModel extends OverviewPageModel { +export class RunsOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel( + super( model.router, + pageIdentifier, { runNumbers: new RawTextFilterModel(), detectors: new DetectorsFilterModel(detectorsProvider.dataTaking$), @@ -100,13 +97,8 @@ export class RunsOverviewModel extends OverviewPageModel { 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._warnings, ); - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.observe(() => this._applyFilters(true)); - this._filteringModel.visualChange$.bubbleTo(this); - const updateDebounceTime = () => { this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); }; @@ -134,69 +126,6 @@ export class RunsOverviewModel extends OverviewPageModel { * @inheritdoc */ getRootEndpoint() { - return buildUrl('/api/runs', { filter: this.filteringModel.normalized }); - } - - /** - * Returns all filtering, sorting and pagination settings to their default values - * @param {boolean} [fetch = true] whether to refetch all data after filters have been reset - * @return {void} - */ - reset(fetch = true) { - super.reset(); - this._exportModel?.reset(); - this.resetFiltering(fetch); - } - - /** - * 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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - - if (fetch) { - this._applyFilters(true); - } - } - - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Checks if any filter value has been modified from their default (empty) - * @return {Boolean} If any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } - - /** - * Return the filtering model - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - - /** - * Apply the current filtering and update the remote data list - * - * @param {boolean} now if true, filtering will be applied now without debouncing - * - * @return {void} - */ - _applyFilters(now = false) { - this._pagination.currentPage = 1; - now ? this.load() : this._debouncedLoad(true); + return this.buildRootEndpoint('/api/runs'); } } From 218af8f0aa11e07ea45edf1203bf603a31ea868e Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:42:02 +0200 Subject: [PATCH 15/25] feat: migrate AnchoredSimulationPassesOverviewModel --- .../AnchoredSimulationPassesOverviewModel.js | 62 +++---------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js index 810789c17f..3054251391 100644 --- a/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js +++ b/lib/public/views/SimulationPasses/AnchoredOverview/AnchoredSimulationPassesOverviewModel.js @@ -10,47 +10,26 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { buildUrl, RemoteData } from '/js/src/index.js'; +import { RemoteData } from '/js/src/index.js'; import { TextTokensFilterModel } from '../../../components/Filters/common/filters/TextTokensFilterModel.js'; -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'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * Simulation Passes Per Data Pass overview model */ -export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { +export class AnchoredSimulationPassesOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }, this._warnings); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); - - this._filteringModel.visualChange$.bubbleTo(this); - + super(router, pageIdentifier, { names: new TextTokensFilterModel() }); this._dataPass = new ObservableData(RemoteData.notAsked()); } - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - /** * Fetch data pass info which simulation passes are fetched * @return {Promise} promise @@ -76,30 +55,15 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { /** * @inheritdoc */ - getRootEndpoint() { - const filter = { ...this._filteringModel.normalized, dataPassIds: [this._dataPassId] }; - return buildUrl('/api/simulationPasses', { filter }); + getFilterParams() { + return { ...super.getFilterParams(), dataPassIds: [this._dataPassId] }; } /** - * Reset this model to its default - * - * @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, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - super.reset(); - } - - /** - * Return the model managing all filters - * - * @return {FilteringModel} the filtering model + * @inheritdoc */ - get filteringModel() { - return this._filteringModel; + getRootEndpoint() { + return this.buildRootEndpoint('/api/simulationPasses'); } /** @@ -116,12 +80,4 @@ export class AnchoredSimulationPassesOverviewModel extends OverviewPageModel { get dataPass() { return this._dataPass.getCurrent(); } - - /** - * States whether any filter is active - * @return {boolean} true if any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } } From 9dcd8fab4a732dac57dcf38876dfdbdfe9a388d0 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 13:42:17 +0200 Subject: [PATCH 16/25] feat: migrate SimulationPassesPerLhcPeriodOverviewModel --- ...mulationPassesPerLhcPeriodOverviewModel.js | 60 +++---------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js index ffc2eb5e02..0980a8c961 100644 --- a/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js +++ b/lib/public/views/SimulationPasses/PerLhcPeriodOverview/SimulationPassesPerLhcPeriodOverviewModel.js @@ -11,32 +11,22 @@ * or submit itself to any jurisdiction. */ import { TextTokensFilterModel } from '../../../components/Filters/common/filters/TextTokensFilterModel.js'; -import { OverviewPageModel } from '../../../models/OverviewModel.js'; -import { buildUrl, RemoteData } from '/js/src/index.js'; +import { 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'; +import { FilterableOverviewPageModel } from '../../../models/FilterableOverviewPageModel.js'; /** * Simulation Passes Per LHC Period overview model */ -export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel { +export class SimulationPassesPerLhcPeriodOverviewModel extends FilterableOverviewPageModel { /** * 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, pageIdentifier) { - super(); - - this._filteringModel = new FilteringModel(router, { names: new TextTokensFilterModel() }, this._warnings); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.visualChange$.bubbleTo(this); - this._filteringModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); + super(router, pageIdentifier, { names: new TextTokensFilterModel() }); this._lhcPeriod = new ObservableData(RemoteData.notAsked()); this._lhcPeriod.bubbleTo(this); @@ -58,24 +48,6 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel } } - /** - * Set underlying FilteringModel's filters from the query parameters in the URL - * - * @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters - */ - setFilterFromURL(notify) { - this._filteringModel.setFilterFromURL(notify); - } - - /** - * Return the model managing all filters - * - * @return {FilteringModel} the filtering model - */ - get filteringModel() { - return this._filteringModel; - } - /** * @inheritdoc */ @@ -87,21 +59,15 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel /** * @inheritdoc */ - getRootEndpoint() { - const filter = { ...this._filteringModel.normalized, lhcPeriodIds: [this._lhcPeriodId] }; - return buildUrl('/api/simulationPasses', { filter }); + getFilterParams() { + return { ...super.getFilterParams(), lhcPeriodIds: [this._lhcPeriodId] }; } /** - * Reset this model to its default - * - * @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} + * @inheritdoc */ - reset(_fetch = true, clearUrl = false) { - this._filteringModel.reset(false, clearUrl); - super.reset(); + getRootEndpoint() { + return this.buildRootEndpoint('/api/simulationPasses'); } /** @@ -118,12 +84,4 @@ export class SimulationPassesPerLhcPeriodOverviewModel extends OverviewPageModel get lhcPeriod() { return this._lhcPeriod.getCurrent(); } - - /** - * States whether any filter is active - * @return {boolean} true if any filter is active - */ - isAnyFilterActive() { - return this._filteringModel.isAnyFilterActive(); - } } From db4490f8e548129f8f7c11eda3ea10f5c286677c Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 14:49:03 +0200 Subject: [PATCH 17/25] patch-up reset of runs --- lib/public/views/Runs/Overview/RunsOverviewModel.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 1a42d6f920..4805afa1e2 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -114,6 +114,15 @@ export class RunsOverviewModel extends FilterableOverviewPageModel { updateDebounceTime(); } + /** + * @inheritdoc + */ + reset(fetch = true) { + super.reset(); + this._exportModel?.reset(); + this.resetFiltering(fetch); + } + /** * Get export model * @return {DataExportModel} export model From 8a7dfa04b1d1dd6f0a3d12dc456884681812ddd9 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 15:36:29 +0200 Subject: [PATCH 18/25] chore: add jsdoc arguments for FilterablePageOverviewModel constructor --- lib/public/models/FilterableOverviewPageModel.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/public/models/FilterableOverviewPageModel.js b/lib/public/models/FilterableOverviewPageModel.js index bfbf4ee2c8..3fa47d4b7a 100644 --- a/lib/public/models/FilterableOverviewPageModel.js +++ b/lib/public/models/FilterableOverviewPageModel.js @@ -23,6 +23,9 @@ import { FilteringModel } from '../components/Filters/common/FilteringModel.js'; export class FilterableOverviewPageModel 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 + * @param {Object} filters the filters with their label and model */ constructor(router, pageIdentifier, filters) { super(); From 130d00bbe5bb8f29d4ff95d302139c5657914f4f Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 15:37:14 +0200 Subject: [PATCH 19/25] chore: remove needless resetFilters from runsOverviewModel --- lib/public/views/Runs/Overview/RunsOverviewModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 4805afa1e2..799476e07b 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -118,9 +118,8 @@ export class RunsOverviewModel extends FilterableOverviewPageModel { * @inheritdoc */ reset(fetch = true) { - super.reset(); this._exportModel?.reset(); - this.resetFiltering(fetch); + super.reset(fetch); } /** From eb060a9ba65d68e1088408b6d072e1cef73e4cb5 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 15:41:52 +0200 Subject: [PATCH 20/25] chore: remove useless commands from LhcPeriodsOverviewModel --- .../views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js index c65cf8d93f..88bf797877 100644 --- a/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js +++ b/lib/public/views/lhcPeriods/Overview/LhcPeriodsOverviewModel.js @@ -35,13 +35,6 @@ export class LhcPeriodsOverviewModel extends FilterableOverviewPageModel { pdpBeamTypes: new TextTokensFilterModel(), }, ); - - this._filteringModel.pageIdentifier = pageIdentifier; - this._filteringModel.visualChange$.bubbleTo(this); - this._filteringModel.observe(() => { - this._pagination.silentlySetCurrentPage(1); - this.load(); - }); } /** From 26a337e725d6208e99bbe81e935f6b615511de2f Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 16:39:55 +0200 Subject: [PATCH 21/25] remove debounce functionallity from logsOverviewModel --- lib/public/views/Logs/Overview/LogsOverviewModel.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index 11b48937c4..f8244d42a8 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -12,7 +12,6 @@ */ import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; -import { debounce } from '../../../utilities/debounce.js'; import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author/AuthorFilterModel.js'; import { tagsProvider } from '../../../services/tag/tagsProvider.js'; import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; @@ -48,12 +47,6 @@ export class LogsOverviewModel extends FilterableOverviewPageModel { }, ); - const updateDebounceTime = () => { - this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); - }; - - updateDebounceTime(); - model.appConfiguration$.observe(() => updateDebounceTime()); excludeAnonymous && this._filteringModel.get('author').update('!Anonymous'); } From 1b4d532b34e54b2388487cd81923b5bab6e51c88 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 16:40:39 +0200 Subject: [PATCH 22/25] prevent multiple fetches from repeated observations --- lib/public/models/FilterableOverviewPageModel.js | 1 + lib/public/models/OverviewModel.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/public/models/FilterableOverviewPageModel.js b/lib/public/models/FilterableOverviewPageModel.js index 3fa47d4b7a..b3d954e05d 100644 --- a/lib/public/models/FilterableOverviewPageModel.js +++ b/lib/public/models/FilterableOverviewPageModel.js @@ -34,6 +34,7 @@ export class FilterableOverviewPageModel extends OverviewPageModel { this._filteringModel.pageIdentifier = pageIdentifier; this._filteringModel.visualChange$.bubbleTo(this); this._filteringModel.observe(() => this._applyFilters()); + this._sortModel.unobserve(this._sortModelCallback); this._sortModel.observe(() => this._applyFilters()); this._debouncedLoad = (_time) => {}; // Abstract, does nothing on purpose this._fetchInstantly = true; diff --git a/lib/public/models/OverviewModel.js b/lib/public/models/OverviewModel.js index d278c4d0ed..73334c204b 100644 --- a/lib/public/models/OverviewModel.js +++ b/lib/public/models/OverviewModel.js @@ -40,10 +40,13 @@ export class OverviewPageModel extends Observable { super(); this._warnings = new Map(); this._sortModel = new SortModel(); - this._sortModel.observe(() => { + + this._sortModelCallback = () => { this._pagination.silentlySetCurrentPage(1); this.load(); - }); + }; + + this._sortModel.observe(this._sortModelCallback); this._sortModel.visualChange$.bubbleTo(this); // Single page data handling From 6317575fa700d73e3497db378e524613a0e9ad18 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 16:45:30 +0200 Subject: [PATCH 23/25] remove debounce functionallity from EnvironmentsOverviewModel --- .../Environments/Overview/EnvironmentOverviewModel.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index 92453fefce..fee7bf09ca 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -46,13 +46,6 @@ export class EnvironmentOverviewModel extends FilterableOverviewPageModel { ids: new RawTextFilterModel(), }, ); - - const updateDebounceTime = () => { - this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); - }; - - model.appConfiguration$.observe(() => updateDebounceTime()); - updateDebounceTime(); } /** From 8059d2c4235facea15b85a50945e2f5a904d4f48 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 16:49:07 +0200 Subject: [PATCH 24/25] remove debounce functionallity from EnvironmentsOverviewModel --- lib/public/views/Runs/Overview/RunsOverviewModel.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 799476e07b..807a7e4d5a 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -99,19 +99,12 @@ export class RunsOverviewModel extends FilterableOverviewPageModel { }, ); - const updateDebounceTime = () => { - this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); - }; - this._exportModel = new DataExportModel(this._allItems$, dataExportConfiguration, () => this.loadAll()); this._exportModel.bubbleTo(this); this._item$.observe(() => { this._exportModel.setDisabled(!this.hasAnyData()); this._exportModel.setTotalExistingItemsCount(this._pagination.itemsCount); }); - - model.appConfiguration$.observe(() => updateDebounceTime()); - updateDebounceTime(); } /** From 33c084b35fb544182f2b4b3414adf461022bbbb0 Mon Sep 17 00:00:00 2001 From: NarrowsProjects Date: Fri, 19 Jun 2026 16:52:38 +0200 Subject: [PATCH 25/25] fix eslint issues --- .../views/Environments/Overview/EnvironmentOverviewModel.js | 1 - lib/public/views/Runs/Overview/RunsOverviewModel.js | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index fee7bf09ca..9621e4df33 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -13,7 +13,6 @@ import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; -import { debounce } from '../../../utilities/debounce.js'; import { coloredEnvironmentStatusComponent } from '../ColoredEnvironmentStatusComponent.js'; import { StatusAcronym } from '../../../domain/enums/statusAcronym.mjs'; import { SelectionModel } from '../../../components/common/selection/SelectionModel.js'; diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 807a7e4d5a..c6912513b6 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -12,7 +12,6 @@ */ import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; -import { debounce } from '../../../utilities/debounce.js'; import { DetectorsFilterModel } from '../../../components/Filters/RunsFilter/DetectorsFilterModel.js'; import { RunTypesFilterModel } from '../../../components/runTypes/RunTypesFilterModel.js'; import { EorReasonFilterModel } from '../../../components/Filters/RunsFilter/EorReasonFilterModel.js';