diff --git a/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.js b/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.js new file mode 100644 index 0000000000..4c95138c1f --- /dev/null +++ b/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.js @@ -0,0 +1,29 @@ +/** + * @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 LEGACY_DETECTOR_NAMES_FOR_AFTER_25 = ['CPV', 'PHS']; + +/** + * Remove legacy detectors for newer LHC periods/passes (after LHC25) from the list of detectors. + * + * @param {Detector[]} detectors detectors to filter + * @param {string} [label=''] name of the period or data pass + * @return {Detector[]} filtered detectors + */ +const filterOutLegacyDetectorsForNewerPeriods = (detectors = [], label = '') => { + const shouldFilterOutLegacyDetectors = /LHC(2[5-9]|[3-9]\d)/.test(label ?? ''); + + return detectors.filter(({ name }) => !shouldFilterOutLegacyDetectors || !LEGACY_DETECTOR_NAMES_FOR_AFTER_25.includes(name)); +}; + +export { filterOutLegacyDetectorsForNewerPeriods }; diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index e9ec4f36c5..5b77d1325e 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -45,6 +45,7 @@ import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; import { ToggleFilterModel } from '../../../components/Filters/common/filters/ToggleFilterModel.js'; import { MultiCompositionFilterModel } from '../../../components/Filters/RunsFilter/MultiCompositionFilterModel.js'; +import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; /** * Merge QC summaries @@ -133,6 +134,48 @@ export class RunsWithQcModel extends RunsOverviewModel { super.load(); } + /** + * Build and register a detectors observable with optional filtering logic. + * + * @param {ObservableData|ObservableData[]} [sourceOrSources=rctDetectorsProvider.qc$] detector source(s) + * @param {function} [filterDetectors=(detectors) => detectors] callback used to filter detectors on success + * @return {ObservableData>} detector observable + */ + _setDetectorsObservable(sourceOrSources = rctDetectorsProvider.qc$, filterDetectors = (detectors) => detectors) { + this._detectors$ = this._buildDetectorsObservable(sourceOrSources, filterDetectors); + this._detectors$.bubbleTo(this); + return this._detectors$; + } + + /** + * Create a detectors observable from one or multiple sources. + * + * @param {ObservableData|ObservableData[]} sourceOrSources detector source(s) + * @param {function} filterDetectors callback used to filter detectors on success + * @return {ObservableData>} detector observable + */ + _buildDetectorsObservable(sourceOrSources, filterDetectors) { + const builder = ObservableData.builder(); + + if (Array.isArray(sourceOrSources)) { + builder.sources(sourceOrSources); + } else { + builder.source(sourceOrSources); + } + + return builder + .apply((remoteDataList) => { + const mergedRemoteData = Array.isArray(remoteDataList) + ? mergeRemoteData(remoteDataList) + : remoteDataList; + + return mergedRemoteData.apply({ + Success: (payload) => filterDetectors(payload), + }); + }) + .build(); + } + /** * Register not-bad fraction detectors filtering model and update it when detectors are loaded * Also, trigger an immediate update if detectors are already loaded at the moment of registration @@ -216,12 +259,17 @@ export class RunsWithQcModel extends RunsOverviewModel { } /** - * Return detectors which a model uses + * Return detectors which a model uses. * * @returns {RemoteData} remote data list of detectors */ get detectors() { - throw Error('Method not implemented'); + if (!this._detectors$) { + this._detectors$ = new ObservableData(RemoteData.notAsked()); + this._detectors$.bubbleTo(this); + } + + return this._detectors$.getCurrent(); } /** diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 5bceeeeeb4..f532b13bd2 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -20,10 +20,10 @@ import { jsonPatch } from '../../../utilities/fetch/jsonPatch.js'; import { jsonPut } from '../../../utilities/fetch/jsonPut.js'; import { SkimmingStage } from '../../../domain/enums/SkimmingStage.js'; import { jsonFetch } from '../../../utilities/fetch/jsonFetch.js'; -import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; import { RemoteDataSource } from '../../../utilities/fetch/RemoteDataSource.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { GaqFilterModel } from '../../../components/Filters/RunsFilter/GaqFilterModel.js'; +import { filterOutLegacyDetectorsForNewerPeriods } from '../../../utilities/filterOutLegacyDetectorsForNewerPeriods.js'; const ALL_CPASS_PRODUCTIONS_REGEX = /cpass\d+/; const DETECTOR_NAMES_NOT_IN_CPASSES = ['EVS']; @@ -42,19 +42,18 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo this._dataPass$ = new ObservableData(RemoteData.notAsked()); this._dataPass$.bubbleTo(this); - this._detectors$ = ObservableData - .builder() - .sources([rctDetectorsProvider.qc$, this._dataPass$]) - .apply((remoteDataList) => mergeRemoteData(remoteDataList) - .apply({ Success: ([detectors, dataPass]) => ALL_CPASS_PRODUCTIONS_REGEX.test(dataPass.name) - ? detectors.filter(({ name, type }) => type !== DetectorType.AOT_GLO || DETECTOR_NAMES_NOT_IN_CPASSES.includes(name)) - : detectors, - })) - .build(); - this._filteringModel.put('gaq', new GaqFilterModel(this._mcReproducibleAsNotBad)); - this._detectors$.bubbleTo(this); + this._setDetectorsObservable( + [rctDetectorsProvider.qc$, this._dataPass$], + ([detectors, dataPass]) => { + const filteredDetectors = filterOutLegacyDetectorsForNewerPeriods(detectors, dataPass.name); + + return ALL_CPASS_PRODUCTIONS_REGEX.test(dataPass.name) + ? filteredDetectors.filter(({ name, type }) => type !== DetectorType.AOT_GLO || DETECTOR_NAMES_NOT_IN_CPASSES.includes(name)) + : filteredDetectors; + }, + ); this._markAsSkimmableRequestResult$ = new ObservableData(RemoteData.notAsked()); this._markAsSkimmableRequestResult$.bubbleTo(this); @@ -285,14 +284,6 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo return this._dataPass$.getCurrent(); } - /** - * Get all detectors - * @return {RemoteData} detectors - */ - get detectors() { - return this._detectors$.getCurrent(); - } - /** * GAQ summary getter * @return {RemoteData} GAQ summary diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index c009f2acd8..cd61bc63a9 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -16,6 +16,7 @@ import { jsonFetch } from '../../../utilities/fetch/jsonFetch.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { FixedPdpBeamTypeRunsOverviewModel } from '../Overview/FixedPdpBeamTypeRunsOverviewModel.js'; +import { filterOutLegacyDetectorsForNewerPeriods } from '../../../utilities/filterOutLegacyDetectorsForNewerPeriods.js'; /** * Runs Per LHC Period overview model @@ -33,16 +34,15 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM this._lhcPeriodId = null; this._lhcPeriodStatistics$ = new ObservableData(RemoteData.notAsked()); - this._syncDetectors$ = ObservableData - .builder() - .source(rctDetectorsProvider.qc$) - .apply((remoteDetectors) => - remoteDetectors.apply({ - Success: (detectors) => detectors.filter(({ type }) => [DetectorType.PHYSICAL, DetectorType.MUON_GLO].includes(type)), - })) - .build(); + this._syncDetectors$ = this._setDetectorsObservable( + [rctDetectorsProvider.qc$, this._lhcPeriodStatistics$], + ([detectors, lhcPeriodStatistics]) => { + const filteredDetectors = filterOutLegacyDetectorsForNewerPeriods(detectors, lhcPeriodStatistics.lhcPeriod.name); - this._syncDetectors$.bubbleTo(this); + return filteredDetectors.filter(({ type }) => [DetectorType.PHYSICAL, DetectorType.MUON_GLO].includes(type)); + }, + ); + this._detectors$ = this._syncDetectors$; this._lhcPeriodStatistics$.bubbleTo(this); } diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index 084b57d130..663b5139c9 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -13,8 +13,9 @@ import { buildUrl, RemoteData } from '/js/src/index.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; -import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { FixedPdpBeamTypeRunsOverviewModel } from '../Overview/FixedPdpBeamTypeRunsOverviewModel.js'; +import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; +import { filterOutLegacyDetectorsForNewerPeriods } from '../../../utilities/filterOutLegacyDetectorsForNewerPeriods.js'; /** * Runs Per Simulation Pass overview model @@ -30,10 +31,11 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver this._simulationPass$ = new ObservableData(RemoteData.notAsked()); - this._detectors$ = rctDetectorsProvider.qc$; - - this._detectors$.bubbleTo(this); this._simulationPass$.bubbleTo(this); + this._detectors$ = this._setDetectorsObservable( + [rctDetectorsProvider.qc$, this._simulationPass$], + ([detectors, simulationPass]) => filterOutLegacyDetectorsForNewerPeriods(detectors, simulationPass.name), + ); } /** @@ -106,14 +108,6 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver return this._simulationPass$.getCurrent(); } - /** - * Get all detectors - * @return {RemoteData} detectors - */ - get detectors() { - return this._detectors$.getCurrent(); - } - /** * @inheritdoc */ diff --git a/test/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.test.js b/test/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.test.js new file mode 100644 index 0000000000..015f37363b --- /dev/null +++ b/test/lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.test.js @@ -0,0 +1,26 @@ +/** + * @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'); + +module.exports = () => { + it('should filter legacy detectors for newer periods', async () => { + const { filterOutLegacyDetectorsForNewerPeriods } = await import('../../../../lib/public/utilities/filterOutLegacyDetectorsForNewerPeriods.js'); + const detectors = [{ name: 'CPV' }, { name: 'PHS' }, { name: 'ITS' }]; + + expect(filterOutLegacyDetectorsForNewerPeriods(detectors, 'LHC25ab')).to.deep.equal([{ name: 'ITS' }]); + expect(filterOutLegacyDetectorsForNewerPeriods(detectors, 'LHC26ad_cpass1_residuals')).to.deep.equal([{ name: 'ITS' }]); + expect(filterOutLegacyDetectorsForNewerPeriods(detectors, 'LHC24')).to.deep.equal(detectors); + expect(filterOutLegacyDetectorsForNewerPeriods(detectors, '')).to.deep.equal(detectors); + }); +}; diff --git a/test/lib/public/utilities/index.js b/test/lib/public/utilities/index.js index 5d2771b5e0..b7163daa0f 100644 --- a/test/lib/public/utilities/index.js +++ b/test/lib/public/utilities/index.js @@ -12,7 +12,9 @@ */ const FormattingSuite = require('./formatting/index.js'); +const FilterOutLegacyDetectorsForNewerPeriodsSuite = require('./filterOutLegacyDetectorsForNewerPeriods.test.js'); module.exports = () => { describe('FormattingSuite', FormattingSuite); + describe('FilterOutLegacyDetectorsForNewerPeriods', FilterOutLegacyDetectorsForNewerPeriodsSuite); }; diff --git a/test/lib/utilities/stringUtils.test.js b/test/lib/utilities/stringUtils.test.js index fb06df6d4c..7f385a0fa6 100644 --- a/test/lib/utilities/stringUtils.test.js +++ b/test/lib/utilities/stringUtils.test.js @@ -79,4 +79,5 @@ module.exports = () => { expect(snakeToPascal('_THIS_IS_SNAKE_CASE_')).to.equal('ThisIsSnakeCase'); expect(snakeToPascal('SNAKE')).to.equal('Snake'); }); + }; diff --git a/test/public/runs/overview.test.js b/test/public/runs/overview.test.js index e67cecaee1..16feb209db 100644 --- a/test/public/runs/overview.test.js +++ b/test/public/runs/overview.test.js @@ -219,8 +219,7 @@ module.exports = () => { // Override the amount of runs visible per page manually await navigateToRunsOverview(page); await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.overviewModel.pagination.itemsPerPage = 1; + window.model.runs.overviewModel.pagination.itemsPerPage = 1; }); await waitForTableLength(page, 1); @@ -237,8 +236,9 @@ module.exports = () => { it('notifies if table loading returned an error', async () => { await navigateToRunsOverview(page); - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.runs.overviewModel.pagination.itemsPerPage = 200); + await page.waitForFunction(() => window.model?.runs?.overviewModel?.pagination); + // eslint-disable-next-line no-return-assign + await page.evaluate(() => window.model.runs.overviewModel.pagination.itemsPerPage = 200); await page.waitForSelector('.alert-danger'); // We expect there to be a fitting error message @@ -247,8 +247,7 @@ module.exports = () => { // Revert changes for next test await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.overviewModel.pagination.itemsPerPage = 10; + window.model.runs.overviewModel.pagination.itemsPerPage = 10; }); await waitForTableLength(page, 10); }); @@ -436,8 +435,7 @@ module.exports = () => { }; await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.overviewModel.pagination.itemsPerPage = 20; + window.model.runs.overviewModel.pagination.itemsPerPage = 20; }); await pressElement(page, physicsFilterSelector, true); @@ -473,8 +471,7 @@ module.exports = () => { await pressElement(page, cosmicsFilterSelector, true); await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.overviewModel.pagination.itemsPerPage = 20; + window.model.runs.overviewModel.pagination.itemsPerPage = 20; }); await checkTableSizeAndDefinition( @@ -597,8 +594,7 @@ module.exports = () => { const ltuFilterSelector = `${filterInputSelectorPrefix}LTU`; await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.overviewModel.pagination.itemsPerPage = 10; + window.model.runs.overviewModel.pagination.itemsPerPage = 10; }); await waitForTableLength(page, 10); diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 4d1edbb4d6..6a9ecf34ca 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -306,17 +306,16 @@ module.exports = () => { }); await page.waitForSelector(`${amountSelectorId} input:invalid`); await page.evaluate(() => { - // eslint-disable-next-line no-return-assign, no-undef - model.runs.perDataPassOverviewModel.pagination.reset(); - // eslint-disable-next-line no-return-assign, no-undef - model.runs.perDataPassOverviewModel.notify(); + window.model.runs.perDataPassOverviewModel.pagination.reset(); + window.model.runs.perDataPassOverviewModel.notify(); }); }); it('notifies if table loading returned an error', async () => { await navigateToRunsPerDataPass(page, 1, 3, 4); - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.runs.perDataPassOverviewModel.pagination.itemsPerPage = 200); + await page.waitForFunction(() => window.model?.runs?.perDataPassOverviewModel?.pagination); + // eslint-disable-next-line no-return-assign + await page.evaluate(() => window.model.runs.perDataPassOverviewModel.pagination.itemsPerPage = 200); await page.waitForSelector('.alert-danger'); // We expect there to be a fitting error message @@ -325,10 +324,8 @@ module.exports = () => { // Revert changes for next test await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.perDataPassOverviewModel.pagination.reset(); - // eslint-disable-next-line no-undef - model.runs.perDataPassOverviewModel.notify(); + window.model.runs.perDataPassOverviewModel.pagination.reset(); + window.model.runs.perDataPassOverviewModel.notify(); }); await waitForTableLength(page, 4); }); diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index f52e2b8086..98f6e955ed 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -181,8 +181,9 @@ module.exports = () => { }); it('notifies if table loading returned an error', async () => { - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.runs.perLhcPeriodOverviewModel.pagination.itemsPerPage = 200); + await page.waitForFunction(() => window.model?.runs?.perLhcPeriodOverviewModel?.pagination); + // eslint-disable-next-line no-return-assign + await page.evaluate(() => window.model.runs.perLhcPeriodOverviewModel.pagination.itemsPerPage = 200); await page.waitForSelector('.alert-danger'); // We expect there to be a fitting error message @@ -191,8 +192,7 @@ module.exports = () => { // Revert changes for next test await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.perLhcPeriodOverviewModel.pagination.itemsPerPage = 2; + window.model.runs.perLhcPeriodOverviewModel.pagination.itemsPerPage = 2; }); await waitForTableLength(page, 2); }); @@ -244,8 +244,7 @@ module.exports = () => { fs.unlinkSync(path.resolve(downloadPath, targetFileName)); await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.perLhcPeriodOverviewModel.reset(); + window.model.runs.perLhcPeriodOverviewModel.reset(); }); }); diff --git a/test/public/runs/runsPerSimulationPass.overview.test.js b/test/public/runs/runsPerSimulationPass.overview.test.js index f3c2d47316..d4c4413a5b 100644 --- a/test/public/runs/runsPerSimulationPass.overview.test.js +++ b/test/public/runs/runsPerSimulationPass.overview.test.js @@ -182,22 +182,23 @@ module.exports = () => { await page.waitForSelector(`${amountSelectorId} .dropup-menu`); await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); - await page.waitForSelector(amountSelectorId); + await page.waitForSelector(`${amountSelectorId} input:invalid`); + await fillInput(page, `${amountSelectorId} input[type=number]`, ''); + await page.waitForSelector(`${amountSelectorId} input:not(:invalid)`); }); it('notifies if table loading returned an error', async () => { - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.runs.perSimulationPassOverviewModel.pagination.itemsPerPage = 200); + const amountSelectorId = '#amountSelector'; + await pressElement(page, `${amountSelectorId} button`); + await page.waitForSelector(`${amountSelectorId} .dropup-menu`); + // 200 exceeds the API query limit (100), but is within the UI input range (1–1000) + // This is set via the docker-compose test variable + await fillInput(page, `${amountSelectorId} input[type=number]`, '200', ['input', 'change']); - // We expect there to be a fitting error message const expectedMessage = 'Invalid Attribute: "query.page.limit" must be less than or equal to 100'; await expectInnerText(page, '.alert-danger', expectedMessage); - await page.evaluate(() => { - // eslint-disable-next-line no-undef - model.runs.perSimulationPassOverviewModel.pagination.reset(); - // eslint-disable-next-line no-undef - model.runs.perSimulationPassOverviewModel.pagination.notify(); - }); + + await goToPage(page, 'runs-per-simulation-pass', { queryParameters: { simulationPassId: 2 } }); await waitForTableLength(page, 3); });