From da47757388ab1119922e32f74d90911ff44aaa8d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:57:18 +0100 Subject: [PATCH 1/6] [O2B-1533] Adjust detector type ordering in DetectorsProvider Move the MUON-GLO, AOT-EVENT, AOT-GLO detectors to appear at the end of the runs-per-data-pass table on request. --- lib/public/services/detectors/detectorsProvider.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/public/services/detectors/detectorsProvider.js b/lib/public/services/detectors/detectorsProvider.js index 3825835d66..19a5a62830 100644 --- a/lib/public/services/detectors/detectorsProvider.js +++ b/lib/public/services/detectors/detectorsProvider.js @@ -76,11 +76,11 @@ export class DetectorsProvider extends RemoteDataProvider { const { data: detectors } = await getRemoteData('/api/detectors'); const typeToOrderingKey = (type) => switchCase(type, { [DetectorType.OTHER]: 0, - [DetectorType.VIRTUAL]: 1, - [DetectorType.PHYSICAL]: 2, - [DetectorType.AOT_GLO]: 3, - [DetectorType.AOT_EVENT]: 4, - [DetectorType.MUON_GLO]: 5, + [DetectorType.AOT_GLO]: 1, + [DetectorType.AOT_EVENT]: 2, + [DetectorType.MUON_GLO]: 3, + [DetectorType.VIRTUAL]: 4, + [DetectorType.PHYSICAL]: 5, [DetectorType.QC_ONLY]: 6, }); From 335147f43c40bfbf2aadc4ebc87eb00172e7b102 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:17:07 +0100 Subject: [PATCH 2/6] [O2V-1533] Add DetectorOrders ENUM and ordering support Accept a detectorOrder ENUM in the DetectorsProvider constructor and sort by it. --- lib/public/domain/enums/DetectorOrders.js | 17 +++++++++ .../services/detectors/detectorsProvider.js | 37 ++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 lib/public/domain/enums/DetectorOrders.js diff --git a/lib/public/domain/enums/DetectorOrders.js b/lib/public/domain/enums/DetectorOrders.js new file mode 100644 index 0000000000..1ddff9473d --- /dev/null +++ b/lib/public/domain/enums/DetectorOrders.js @@ -0,0 +1,17 @@ +/** + * @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. + */ + +export const DetectorOrders = Object.freeze({ + DEFAULT: 'DEFAULT', + RCT: 'RCT', +}); diff --git a/lib/public/services/detectors/detectorsProvider.js b/lib/public/services/detectors/detectorsProvider.js index 19a5a62830..164bcff8d5 100644 --- a/lib/public/services/detectors/detectorsProvider.js +++ b/lib/public/services/detectors/detectorsProvider.js @@ -15,6 +15,7 @@ import { switchCase } from '/js/src/index.js'; import { getRemoteData } from '../../utilities/fetch/getRemoteData.js'; import { ObservableData } from '../../utilities/ObservableData.js'; import { DetectorType, DATA_TAKING_DETECTOR_TYPES, QC_DETECTORS } from '../../domain/enums/DetectorTypes.js'; +import { DetectorOrders } from '../../domain/enums/DetectorOrders.js'; import { NonPhysicalDetector } from '../../domain/enums/detectorsNames.mjs'; @@ -38,15 +39,39 @@ const getPhysicalDetectorsFromAllDetectors = (allDetectors) => allDetectors.filt const getQcDetectorsFromAllDetectors = (allDetectors) => allDetectors .filter(({ type, name }) => QC_DETECTORS.includes(type) && !DETECTORS_EXCLUDED_FROM_QC.includes(name)); +const ORDERING_KEYS = { + [DetectorOrders.DEFAULT]: { + [DetectorType.OTHER]: 0, + [DetectorType.VIRTUAL]: 1, + [DetectorType.PHYSICAL]: 2, + [DetectorType.AOT_GLO]: 3, + [DetectorType.AOT_EVENT]: 4, + [DetectorType.MUON_GLO]: 5, + [DetectorType.QC_ONLY]: 6, + }, + [DetectorOrders.RCT]: { + [DetectorType.OTHER]: 0, + [DetectorType.AOT_GLO]: 1, + [DetectorType.AOT_EVENT]: 2, + [DetectorType.MUON_GLO]: 3, + [DetectorType.VIRTUAL]: 4, + [DetectorType.PHYSICAL]: 5, + [DetectorType.QC_ONLY]: 6, + }, +}; + /** * Service class to fetch detectors from the backend */ export class DetectorsProvider extends RemoteDataProvider { /** * Constructor + * + * @param {DetectorOrders} detectorOrder the order to base sorting on, default is DetectorOrders.DEFAULT */ - constructor() { + constructor(detectorOrder = DetectorOrders.DEFAULT) { super(); + this._detectorOrder = detectorOrder; this._physical$ = ObservableData.builder() .source(this._items$) .apply((remoteDetectors) => remoteDetectors.apply({ @@ -74,15 +99,7 @@ export class DetectorsProvider extends RemoteDataProvider { */ async getRemoteData() { const { data: detectors } = await getRemoteData('/api/detectors'); - const typeToOrderingKey = (type) => switchCase(type, { - [DetectorType.OTHER]: 0, - [DetectorType.AOT_GLO]: 1, - [DetectorType.AOT_EVENT]: 2, - [DetectorType.MUON_GLO]: 3, - [DetectorType.VIRTUAL]: 4, - [DetectorType.PHYSICAL]: 5, - [DetectorType.QC_ONLY]: 6, - }); + const typeToOrderingKey = (type) => switchCase(type, ORDERING_KEYS[this._detectorOrder]); const orderingKey = (detector1, detector2) => { const specialPair = ['ZDC', 'TST']; From b4acdbdcc19ff89d3fcf2ad91d4fd5bce246a8ff Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:35:52 +0100 Subject: [PATCH 3/6] [O2B-1533] Add rctDetectorsProvider and adjust models Export a new rctDetectorsProvider (DetectorsProvider configured with DetectorOrders.RCT) and update RCT tables. --- lib/public/services/detectors/detectorsProvider.js | 1 + .../Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js | 4 ++-- .../Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js | 6 +++--- .../RunsPerSimulationPassOverviewModel.js | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/public/services/detectors/detectorsProvider.js b/lib/public/services/detectors/detectorsProvider.js index 164bcff8d5..689f9acccb 100644 --- a/lib/public/services/detectors/detectorsProvider.js +++ b/lib/public/services/detectors/detectorsProvider.js @@ -178,3 +178,4 @@ export class DetectorsProvider extends RemoteDataProvider { } export const detectorsProvider = new DetectorsProvider(); +export const rctDetectorsProvider = new DetectorsProvider(DetectorOrders.RCT); diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index 0aca49d627..4e602e4ef1 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -14,7 +14,7 @@ import { buildUrl, RemoteData } from '/js/src/index.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; +import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { FixedPdpBeamTypeRunsOverviewModel } from '../Overview/FixedPdpBeamTypeRunsOverviewModel.js'; import { jsonPatch } from '../../../utilities/fetch/jsonPatch.js'; import { jsonPut } from '../../../utilities/fetch/jsonPut.js'; @@ -43,7 +43,7 @@ export class RunsPerDataPassOverviewModel extends FixedPdpBeamTypeRunsOverviewMo this._detectors$ = ObservableData .builder() - .sources([detectorsProvider.qc$, this._dataPass$]) + .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)) diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index b361522b8b..a2f5ebd2e5 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -12,7 +12,7 @@ */ import { buildUrl, RemoteData } from '/js/src/index.js'; import { TabbedPanelModel } from '../../../components/TabbedPanel/TabbedPanelModel.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; +import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { jsonFetch } from '../../../utilities/fetch/jsonFetch.js'; import { DetectorType } from '../../../domain/enums/DetectorTypes.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; @@ -38,11 +38,11 @@ export class RunsPerLhcPeriodOverviewModel extends FixedPdpBeamTypeRunsOverviewM this._lhcPeriodId = null; this._lhcPeriodStatistics$ = new ObservableData(RemoteData.notAsked()); - this._onlineDetectors$ = detectorsProvider.physical$; + this._onlineDetectors$ = rctDetectorsProvider.physical$; this._syncDetectors$ = ObservableData .builder() - .source(detectorsProvider.qc$) + .source(rctDetectorsProvider.qc$) .apply((remoteDetectors) => remoteDetectors.apply({ Success: (detectors) => detectors.filter(({ type }) => [DetectorType.PHYSICAL, DetectorType.MUON_GLO].includes(type)), diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index 9b8b982d4b..3eb0a38303 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -13,7 +13,7 @@ import { buildUrl, RemoteData } from '/js/src/index.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; +import { rctDetectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { FixedPdpBeamTypeRunsOverviewModel } from '../Overview/FixedPdpBeamTypeRunsOverviewModel.js'; /** @@ -29,7 +29,7 @@ export class RunsPerSimulationPassOverviewModel extends FixedPdpBeamTypeRunsOver this._simulationPass$ = new ObservableData(RemoteData.notAsked()); - this._detectors$ = detectorsProvider.qc$; + this._detectors$ = rctDetectorsProvider.qc$; this.registerObervablesQcSummaryDependesOn([this._detectors$]); this.registerDetectorsNotBadFractionFilterModels(this._detectors$); From c6c3a82ffa31b8cf8ab869ef2d04d49087ceab28 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:41:52 +0100 Subject: [PATCH 4/6] [O2B-1533] Forgot to commit adjusted tests Add a test to verify detector columns are rendered in RCT order. Update the CSV export expectations to reflect the new column ordering (CPV/VTX swapped). --- .../runs/runsPerDataPass.overview.test.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index f45d004e55..df8646e50a 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -152,6 +152,18 @@ module.exports = () => { .to.be.equal('Missing 3 verifications'); }); + it('should display detector columns in RCT order (AOT/MUON after physical)', async () => { + await navigateToRunsPerDataPass(page, 1, 3, 4); + const headers = await page.$$eval( + 'table thead th', + (ths) => ths.map((th) => th.id).filter(Boolean), + ); + + // see DetectorOrders.RCT in detectorsProvider.js + expect(headers.indexOf('VTX')).to.be.greaterThan(headers.indexOf('EMC')); + expect(headers.indexOf('MUD')).to.be.greaterThan(headers.indexOf('EMC')); + }); + it('should ignore QC flags created by services in QC summaries of AOT and MUON ', async () => { await navigateToRunsPerDataPass(page, 2, 1, 3); // apass await expectInnerText(page, '#row106-VTX-text', '100'); @@ -394,10 +406,10 @@ module.exports = () => { const exportContent = fs.readFileSync(path.resolve(downloadPath, targetFileName)).toString(); expect(exportContent.trim()).to.be.eql([ - 'runNumber;VTX;CPV', + 'runNumber;CPV;VTX', '108;"";""', - '107;"";"Good (from: 1565290800000 to: 1565359260000) | Limited Acceptance MC Reproducible (from: 1565269140000 to: 1565290800000)"', - '106;"Good (from: 1565269200000 to: 1565304200000) | Good (from: 1565324200000 to: 1565359200000)";"Limited Acceptance MC Reproducible (from: 1565304200000 to: 1565324200000) | Limited acceptance (from: 1565329200000 to: 1565334200000) | Bad (from: 1565339200000 to: 1565344200000)"', + '107;"Good (from: 1565290800000 to: 1565359260000) | Limited Acceptance MC Reproducible (from: 1565269140000 to: 1565290800000)";""', + '106;"Limited Acceptance MC Reproducible (from: 1565304200000 to: 1565324200000) | Limited acceptance (from: 1565329200000 to: 1565334200000) | Bad (from: 1565339200000 to: 1565344200000)";"Good (from: 1565269200000 to: 1565304200000) | Good (from: 1565324200000 to: 1565359200000)"', ].join('\r\n')); fs.unlinkSync(path.resolve(downloadPath, targetFileName)); }); From 4e0fa66400f84ce20f2c155a58dd8bcd745d253d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:24:24 +0100 Subject: [PATCH 5/6] [O2B-1533] Add RCT detector order tests Add tests to verify detector columns are rendered in RCT order on runs-per-lhc-period and runs-per-simulation-pass pages. --- test/public/runs/runsPerDataPass.overview.test.js | 5 ++--- test/public/runs/runsPerLhcPeriod.overview.test.js | 11 +++++++++++ .../runs/runsPerSimulationPass.overview.test.js | 11 +++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index df8646e50a..9d210e25ed 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -153,15 +153,14 @@ module.exports = () => { }); it('should display detector columns in RCT order (AOT/MUON after physical)', async () => { - await navigateToRunsPerDataPass(page, 1, 3, 4); const headers = await page.$$eval( 'table thead th', (ths) => ths.map((th) => th.id).filter(Boolean), ); // see DetectorOrders.RCT in detectorsProvider.js - expect(headers.indexOf('VTX')).to.be.greaterThan(headers.indexOf('EMC')); - expect(headers.indexOf('MUD')).to.be.greaterThan(headers.indexOf('EMC')); + expect(headers.indexOf('VTX')).to.be.greaterThan(headers.indexOf('ZDC')); + expect(headers.indexOf('MUD')).to.be.greaterThan(headers.indexOf('ZDC')); }); it('should ignore QC flags created by services in QC summaries of AOT and MUON ', async () => { diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index f38dc635a9..023ab4921e 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -130,6 +130,17 @@ module.exports = () => { await expectInnerText(page, '#row56-FT0', '83'); }); + it('should display detector columns in RCT order (AOT/MUON after physical) for synchronous flags', async () => { + // Note test starts already on synchronous flags tab + const headers = await page.$$eval( + 'table thead th', + (ths) => ths.map((th) => th.id).filter(Boolean), + ); + + // see DetectorOrders.RCT in detectorsProvider.js + expect(headers.indexOf('MUD')).to.be.greaterThan(headers.indexOf('ZDC')); + }); + it('should successfully sort by runNumber in ascending and descending manners', async () => { await testTableSortingByColumn(page, 'runNumber'); }); diff --git a/test/public/runs/runsPerSimulationPass.overview.test.js b/test/public/runs/runsPerSimulationPass.overview.test.js index b7b1c725fd..afa3dab09c 100644 --- a/test/public/runs/runsPerSimulationPass.overview.test.js +++ b/test/public/runs/runsPerSimulationPass.overview.test.js @@ -137,6 +137,17 @@ module.exports = () => { await qcFlagService.delete(tmpQcFlag.id); }); + it('should display detector columns in RCT order (AOT/MUON after physical)', async () => { + const headers = await page.$$eval( + 'table thead th', + (ths) => ths.map((th) => th.id).filter(Boolean), + ); + + // see DetectorOrders.RCT in detectorsProvider.js + expect(headers.indexOf('VTX')).to.be.greaterThan(headers.indexOf('ZDC')); + expect(headers.indexOf('MUD')).to.be.greaterThan(headers.indexOf('ZDC')); + }); + it('should successfully sort by runNumber in ascending and descending manners', async () => { await testTableSortingByColumn(page, 'runNumber'); }); From f099d33ed00a7fd98d89ed44753e3f51706c8ba7 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:38:12 +0100 Subject: [PATCH 6/6] [O2B-1533] Move detector ordering into DetectorOrders Centralise detector ordering maps by moving DEFAULT and RCT switchCase inputs into the ENUM. This consolidates ordering configuration and avoids duplicate definitions in the provider. --- lib/public/domain/enums/DetectorOrders.js | 22 ++++++++++++++++-- .../services/detectors/detectorsProvider.js | 23 +------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/lib/public/domain/enums/DetectorOrders.js b/lib/public/domain/enums/DetectorOrders.js index 1ddff9473d..e4b95b318b 100644 --- a/lib/public/domain/enums/DetectorOrders.js +++ b/lib/public/domain/enums/DetectorOrders.js @@ -11,7 +11,25 @@ * or submit itself to any jurisdiction. */ +import { DetectorType } from './DetectorTypes.js'; + export const DetectorOrders = Object.freeze({ - DEFAULT: 'DEFAULT', - RCT: 'RCT', + DEFAULT: { + [DetectorType.OTHER]: 0, + [DetectorType.VIRTUAL]: 1, + [DetectorType.PHYSICAL]: 2, + [DetectorType.AOT_GLO]: 3, + [DetectorType.AOT_EVENT]: 4, + [DetectorType.MUON_GLO]: 5, + [DetectorType.QC_ONLY]: 6, + }, + RCT: { + [DetectorType.OTHER]: 0, + [DetectorType.AOT_GLO]: 1, + [DetectorType.AOT_EVENT]: 2, + [DetectorType.MUON_GLO]: 3, + [DetectorType.VIRTUAL]: 4, + [DetectorType.PHYSICAL]: 5, + [DetectorType.QC_ONLY]: 6, + }, }); diff --git a/lib/public/services/detectors/detectorsProvider.js b/lib/public/services/detectors/detectorsProvider.js index 689f9acccb..9c02ea7957 100644 --- a/lib/public/services/detectors/detectorsProvider.js +++ b/lib/public/services/detectors/detectorsProvider.js @@ -39,27 +39,6 @@ const getPhysicalDetectorsFromAllDetectors = (allDetectors) => allDetectors.filt const getQcDetectorsFromAllDetectors = (allDetectors) => allDetectors .filter(({ type, name }) => QC_DETECTORS.includes(type) && !DETECTORS_EXCLUDED_FROM_QC.includes(name)); -const ORDERING_KEYS = { - [DetectorOrders.DEFAULT]: { - [DetectorType.OTHER]: 0, - [DetectorType.VIRTUAL]: 1, - [DetectorType.PHYSICAL]: 2, - [DetectorType.AOT_GLO]: 3, - [DetectorType.AOT_EVENT]: 4, - [DetectorType.MUON_GLO]: 5, - [DetectorType.QC_ONLY]: 6, - }, - [DetectorOrders.RCT]: { - [DetectorType.OTHER]: 0, - [DetectorType.AOT_GLO]: 1, - [DetectorType.AOT_EVENT]: 2, - [DetectorType.MUON_GLO]: 3, - [DetectorType.VIRTUAL]: 4, - [DetectorType.PHYSICAL]: 5, - [DetectorType.QC_ONLY]: 6, - }, -}; - /** * Service class to fetch detectors from the backend */ @@ -99,7 +78,7 @@ export class DetectorsProvider extends RemoteDataProvider { */ async getRemoteData() { const { data: detectors } = await getRemoteData('/api/detectors'); - const typeToOrderingKey = (type) => switchCase(type, ORDERING_KEYS[this._detectorOrder]); + const typeToOrderingKey = (type) => switchCase(type, this._detectorOrder); const orderingKey = (detector1, detector2) => { const specialPair = ['ZDC', 'TST'];