From 25244f483bc04886e69c0f8f2ff6252fb190de0e Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Mon, 9 Mar 2026 12:37:04 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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);