diff --git a/lib/public/components/Filters/RunsFilter/BeamModeFilterModel.js b/lib/public/components/Filters/RunsFilter/BeamModeFilterModel.js new file mode 100644 index 0000000000..0704fc684d --- /dev/null +++ b/lib/public/components/Filters/RunsFilter/BeamModeFilterModel.js @@ -0,0 +1,61 @@ +/** + * @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 { ObservableBasedSelectionDropdownModel } from '../../detector/ObservableBasedSelectionDropdownModel.js'; +import { FilterModel } from '../common/FilterModel.js'; + +/** + * Beam mode filter model + */ +export class BeamModeFilterModel extends FilterModel { + /** + * Constructor + * + * @param {ObservableData>} beamModes$ observable remote data of objects representing beam modes + */ + constructor(beamModes$) { + super(); + this._selectionDropdownModel = new ObservableBasedSelectionDropdownModel(beamModes$, ({ name }) => ({ value: name })); + this._addSubmodel(this._selectionDropdownModel); + } + + /** + * @inheritDoc + */ + reset() { + this._selectionDropdownModel.reset(); + } + + /** + * @inheritDoc + */ + get isEmpty() { + return this._selectionDropdownModel.isEmpty; + } + + /** + * Return the underlying dropdown model + * + * @return {ObservableDropDownModel} the underlying dropdown model + */ + get selectionDropdownModel() { + return this._selectionDropdownModel; + } + + /** + * @inheritDoc + */ + get normalized() { + return this._selectionDropdownModel.selected; + } +} diff --git a/lib/public/components/runTypes/RunTypesFilterModel.js b/lib/public/components/runTypes/RunTypesFilterModel.js index 15a94c88a0..60a923cbc6 100644 --- a/lib/public/components/runTypes/RunTypesFilterModel.js +++ b/lib/public/components/runTypes/RunTypesFilterModel.js @@ -26,29 +26,29 @@ export class RunTypesFilterModel extends FilterModel { */ constructor(runTypes$) { super(); - this._selectionDropDownModel = new ObservableBasedSelectionDropdownModel(runTypes$, runTypeToOption); - this._addSubmodel(this._selectionDropDownModel); + this._selectionDropdownModel = new ObservableBasedSelectionDropdownModel(runTypes$, runTypeToOption); + this._addSubmodel(this._selectionDropdownModel); } /** * @inheritDoc */ reset() { - this._selectionDropDownModel.reset(); + this._selectionDropdownModel.reset(); } /** * @inheritDoc */ get isEmpty() { - return this._selectionDropDownModel.isEmpty; + return this._selectionDropdownModel.isEmpty; } /** * @inheritDoc */ get normalized() { - return this._selectionDropDownModel.selected; + return this._selectionDropdownModel.selected; } /** @@ -57,6 +57,6 @@ export class RunTypesFilterModel extends FilterModel { * @return {SelectionDropdownModel} the selection dropdown model */ get selectionDropdownModel() { - return this._selectionDropDownModel; + return this._selectionDropdownModel; } } diff --git a/lib/public/services/beamModes/beamModesProvider.js b/lib/public/services/beamModes/beamModesProvider.js new file mode 100644 index 0000000000..dde74e56d2 --- /dev/null +++ b/lib/public/services/beamModes/beamModesProvider.js @@ -0,0 +1,30 @@ +/** + * @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 { getRemoteData } from '../../utilities/fetch/getRemoteData.js'; +import { RemoteDataProvider } from '../RemoteDataProvider.js'; + +/** + * Service class to fetch beamModes from the backend + */ +export class BeamModesProvider extends RemoteDataProvider { + /** + * @inheritDoc + */ + async getRemoteData() { + const { data } = await getRemoteData('/api/runs/beamModes'); + return data; + } +} + +export const beamModesProvider = new BeamModesProvider(); diff --git a/lib/public/utilities/formatting/formatRunType.js b/lib/public/utilities/formatting/formatNamedValue.js similarity index 67% rename from lib/public/utilities/formatting/formatRunType.js rename to lib/public/utilities/formatting/formatNamedValue.js index b478dd5e74..e728e5b2fa 100644 --- a/lib/public/utilities/formatting/formatRunType.js +++ b/lib/public/utilities/formatting/formatNamedValue.js @@ -13,14 +13,15 @@ */ /** - * Fetches the run type and formats on the given state of the fetch - * @param {string|undefined|object} runType The run type can be an id, name or null + * Fetches the value and formats on the given state of the fetch + * @param {string|undefined|object} value The value can be an id, name or null * * @returns {string} The name or id or a '-' if null */ -export function formatRunType(runType) { - if (runType) { - return runType.name ? runType.name : runType; +export function formatNamedValue(value) { + if (['string', 'number', 'boolean'].includes(typeof value)) { + return value; } - return '-'; + + return value?.name ?? '-'; } diff --git a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js index 2278a39466..eefe0f006f 100644 --- a/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runsActiveColumns.js @@ -20,7 +20,7 @@ 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'; -import { formatRunType } from '../../../utilities/formatting/formatRunType.js'; +import { formatNamedValue } from '../../../utilities/formatting/formatNamedValue.js'; import { runDefinitionFilter } from '../../../components/Filters/RunsFilter/runDefinitionFilter.js'; import { profiles } from '../../../components/common/table/profiles.js'; import { formatDuration } from '../../../utilities/formatting/formatDuration.mjs'; @@ -150,6 +150,21 @@ export const runsActiveColumns = { filter: (runModel) => tagFilter(runModel.filteringModel.get('tags')), balloon: (tags) => tags && tags.length > 0, }, + beamModes: { + name: 'Beam Mode', + visible: false, + classes: 'cell-l f6 w-wrapped', + format: formatNamedValue, + + /** + * Beam Modes filter component + * + * @param {RunsOverviewModel} runsOverviewModel the runs overview model + * @return {Component} the beam modes filter component + */ + filter: (runsOverviewModel) => + selectionDropdown(runsOverviewModel.filteringModel.get('beamModes').selectionDropdownModel, { selectorPrefix: 'beam-mode' }), + }, fillNumber: { name: 'Fill No.', visible: true, @@ -397,7 +412,7 @@ export const runsActiveColumns = { name: 'Run Type', visible: false, classes: 'cell-l f6 w-wrapped', - format: formatRunType, + format: formatNamedValue, /** * Run types filter component diff --git a/lib/public/views/Runs/Details/runDetailsComponent.js b/lib/public/views/Runs/Details/runDetailsComponent.js index 14524bf8de..ebb7ae4fe7 100644 --- a/lib/public/views/Runs/Details/runDetailsComponent.js +++ b/lib/public/views/Runs/Details/runDetailsComponent.js @@ -33,7 +33,7 @@ import { displayRunDuration } from '../format/displayRunDuration.js'; import { formatDuration } from '../../../utilities/formatting/formatDuration.mjs'; import { formatRunQuality } from '../format/formatRunQuality.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; -import { formatRunType } from '../../../utilities/formatting/formatRunType.js'; +import { formatNamedValue } from '../../../utilities/formatting/formatNamedValue.js'; import { formatBoolean } from '../../../utilities/formatting/formatBoolean.js'; import { formatFileSize } from '../../../utilities/formatting/formatFileSize.js'; import { RunDefinition } from '../../../domain/enums/RunDefinition.js'; @@ -383,7 +383,7 @@ export const runDetailsComponent = (runDetailsModel, router) => runDetailsModel. ]), h('.flex-column.items-center.flex-grow', [ h('strong', 'Run Type'), - formatRunType(run.runType), + formatNamedValue(run.runType), ]), ]), ]), diff --git a/lib/public/views/Runs/Overview/RunsOverviewModel.js b/lib/public/views/Runs/Overview/RunsOverviewModel.js index 8b1a7ca32c..0249c66085 100644 --- a/lib/public/views/Runs/Overview/RunsOverviewModel.js +++ b/lib/public/views/Runs/Overview/RunsOverviewModel.js @@ -34,6 +34,8 @@ import { RUN_QUALITIES } from '../../../domain/enums/RunQualities.js'; import { SelectionFilterModel } from '../../../components/Filters/common/filters/SelectionFilterModel.js'; 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'; /** * Model representing handlers for runs page @@ -67,6 +69,7 @@ export class RunsOverviewModel extends OverviewPageModel { 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(), diff --git a/test/lib/public/utilities/formatting/formatNamedValue.test.js b/test/lib/public/utilities/formatting/formatNamedValue.test.js new file mode 100644 index 0000000000..cf9f29865e --- /dev/null +++ b/test/lib/public/utilities/formatting/formatNamedValue.test.js @@ -0,0 +1,49 @@ +/** + * @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'); + +let formatNamedValue; + +module.exports = () => { + before(async () => { + ({ formatNamedValue } = await import('../../../../../lib/public/utilities/formatting/formatNamedValue.js')); + }); + + it('should return value.name when value is an object with a name property', () => { + expect(formatNamedValue({ name: 'John Doe' })).to.equal('John Doe'); + expect(formatNamedValue({ name: 'Test', id: 123 })).to.equal('Test'); + }); + + it('should return value when value is a string, number or a boolean', () => { + expect(formatNamedValue('Plain String')).to.equal('Plain String'); + expect(formatNamedValue(42)).to.equal(42); + expect(formatNamedValue(true)).to.equal(true); + }); + + it('should return "-" when value is null or undefined', () => { + expect(formatNamedValue(null)).to.equal('-'); + expect(formatNamedValue(undefined)).to.equal('-'); + }); + + it('should return "-" when value.name is null or undefined', () => { + expect(formatNamedValue({name: undefined})).to.equal('-'); + expect(formatNamedValue({name: null})).to.equal('-'); + }); + + it('should correctly handle falsy but defined values', () => { + expect(formatNamedValue('')).to.equal(''); + expect(formatNamedValue(0)).to.equal(0); + expect(formatNamedValue(false)).to.equal(false); + }); +}; diff --git a/test/lib/public/utilities/formatting/index.js b/test/lib/public/utilities/formatting/index.js index c60cb34481..345cd74620 100644 --- a/test/lib/public/utilities/formatting/index.js +++ b/test/lib/public/utilities/formatting/index.js @@ -12,7 +12,9 @@ */ const formatRunDurationTest = require('./formatRunDuration.test.js'); +const formatNamedValuesTest = require('./formatNamedValue.test.js') module.exports = () => { describe('formatRunDurationTest', formatRunDurationTest); + describe('formatNamedValuesTest', formatNamedValuesTest); }; diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index 5ceac005b2..807b821ffc 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -727,6 +727,14 @@ module.exports = () => { await waitForTableLength(page, 5); }); + it('should successfully filter on beam mode', async () => { + await pressElement(page, '.beamModes-filter .dropdown-trigger'); + await pressElement(page, '#beam-mode-dropdown-option-NO\\ BEAM', true); + await waitForTableLength(page, 1); + await pressElement(page, '#beam-mode-dropdown-option-STABLE\\ BEAMS', true); + await waitForTableLength(page, 6); + }); + it('should successfully filter on nDetectors', async () => { await expectInputValue(page, '#nDetectors-operator', '=');