From 618daa19a6a4468557a5daf57b58d179a98a605d Mon Sep 17 00:00:00 2001 From: Jasper Houweling Date: Mon, 8 Dec 2025 16:59:05 +0100 Subject: [PATCH 1/5] [O2B-1508] Frontend Beams type filter working. --- .../Filters/LhcFillsFilter/beamsTypeFilter.js | 25 +++++++++++++++++++ lib/public/domain/enums/BeamType.js | 19 ++++++++++++++ .../ActiveColumns/lhcFillsActiveColumns.js | 2 ++ .../Overview/LhcFillsOverviewModel.js | 3 +++ 4 files changed, 49 insertions(+) create mode 100644 lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js create mode 100644 lib/public/domain/enums/BeamType.js diff --git a/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js new file mode 100644 index 0000000000..070b37f4ea --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js @@ -0,0 +1,25 @@ +/** + * @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 { checkboxes } from '../common/filters/checkboxFilter.js'; + +/** + * Renders a list of checkboxes that lets the user look for beam types + * + * @param {SelectionFilterModel} selectionFilterModel selectionFilterModel + * @return {Component} the filter + */ +export const beamsTypeFilter = (selectionFilterModel) => checkboxes( + selectionFilterModel._selectionModel, + { selector: 'beams-types' }, +); diff --git a/lib/public/domain/enums/BeamType.js b/lib/public/domain/enums/BeamType.js new file mode 100644 index 0000000000..99fd2bb35c --- /dev/null +++ b/lib/public/domain/enums/BeamType.js @@ -0,0 +1,19 @@ +/** + * @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 BeamType = Object.freeze({ + PROTON_PROTON: 'Proton-Proton', + LEAD_LEAD: 'Lead-Lead', +}); + +export const BEAM_TYPES = Object.values(BeamType); diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 1a0cd8aa9d..897b407d13 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -27,6 +27,7 @@ import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFills import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js'; import { beamDurationFilter } from '../../../components/Filters/LhcFillsFilter/beamDurationFilter.js'; import { runDurationFilter } from '../../../components/Filters/LhcFillsFilter/runDurationFilter.js'; +import { beamsTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamsTypeFilter.js'; /** * List of active columns for a lhc fills table @@ -170,6 +171,7 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (value) => formatBeamType(value), + filter: (lhcFillModel) => beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), }, collidingBunches: { name: 'Colliding bunches', diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 9837d58473..ab90525178 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -17,6 +17,8 @@ import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilte import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; +import { SelectionFilterModel } from '../../../components/Filters/common/filters/SelectionFilterModel.js'; +import { BEAM_TYPES } from '../../../domain/enums/BeamType.js'; const defaultBeamDurationOperator = '='; const defaultRunDurationOperator = '='; @@ -40,6 +42,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { beamDuration: new RawTextFilterModel(), runDuration: new RawTextFilterModel(), hasStableBeams: new StableBeamFilterModel(), + beamsType: new SelectionFilterModel({ availableOptions: BEAM_TYPES.map((type) => ({ value: type })) }), }); this._beamDurationOperator = defaultBeamDurationOperator; From 870124689d0b24b62c98c88c99af4bb76db6f11a Mon Sep 17 00:00:00 2001 From: Jasper Houweling Date: Mon, 8 Dec 2025 17:28:20 +0100 Subject: [PATCH 2/5] [O2B-1508] Basic backend UseCase works --- lib/domain/dtos/filters/LhcFillsFilterDto.js | 1 + lib/public/domain/enums/BeamType.js | 4 ++-- lib/usecases/lhcFill/GetAllLhcFillsUseCase.js | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index 7c5d6853f7..2942844758 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -27,4 +27,5 @@ exports.LhcFillsFilterDto = Joi.object({ 'any.invalid': '{{#message}}', }), runDurationOperator: Joi.string().trim().min(1).max(2), + beamsType: Joi.string(), }); diff --git a/lib/public/domain/enums/BeamType.js b/lib/public/domain/enums/BeamType.js index 99fd2bb35c..c2266fda6f 100644 --- a/lib/public/domain/enums/BeamType.js +++ b/lib/public/domain/enums/BeamType.js @@ -12,8 +12,8 @@ */ export const BeamType = Object.freeze({ - PROTON_PROTON: 'Proton-Proton', - LEAD_LEAD: 'Lead-Lead', + PROTON_PROTON: 'PROTON-PROTON', + LEAD_LEAD: 'LEAD-LEAD', }); export const BEAM_TYPES = Object.values(BeamType); diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index e961548817..155861806c 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -47,7 +47,7 @@ class GetAllLhcFillsUseCase { let associatedStatisticsRequired = false; if (filter) { - const { hasStableBeams, fillNumbers, beamDurationOperator, beamDuration, runDurationOperator, runDuration } = filter; + const { hasStableBeams, fillNumbers, beamDurationOperator, beamDuration, runDurationOperator, runDuration, beamsType } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); @@ -77,6 +77,12 @@ class GetAllLhcFillsUseCase { beamDuration === 0 ? queryBuilder.where('stableBeamsDuration').applyOperator(beamDurationOperator, null) : queryBuilder.where('stableBeamsDuration').applyOperator(beamDurationOperator, beamDuration); } + + // Beams type. + if (beamsType) { + const beamTypes = beamsType.split(','); + queryBuilder.where('beamType').oneOfSubstrings(beamTypes); + } } const { count, rows } = await TransactionHelper.provide(async () => { From e64d28f6d3e13c8abc337dfaa7d67c94bbb451ec Mon Sep 17 00:00:00 2001 From: Jasper Houweling Date: Wed, 10 Dec 2025 08:59:48 +0100 Subject: [PATCH 3/5] [O2B-1508] Load possible beamstypes for the beam_type filter from the backend --- lib/domain/dtos/GetAllBeamsTypesDto.js | 28 +++++++++ lib/domain/dtos/index.js | 2 + lib/{public => }/domain/enums/BeamType.js | 2 +- .../services/beamsTypes/beamsTypesProvider.js | 30 +++++++++ .../ActiveColumns/lhcFillsActiveColumns.js | 3 +- .../Overview/LhcFillsOverviewModel.js | 21 ++++++- .../controllers/beamsTypes.controller.js | 62 +++++++++++++++++++ lib/server/controllers/index.js | 2 + lib/server/routers/beamsTypes.router.js | 20 ++++++ lib/server/routers/index.js | 2 + .../beamsType/GetAllBeamsTypesUseCase.js | 41 ++++++++++++ lib/usecases/beamsType/index.js | 17 +++++ lib/usecases/index.js | 2 + 13 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 lib/domain/dtos/GetAllBeamsTypesDto.js rename lib/{public => }/domain/enums/BeamType.js (95%) create mode 100644 lib/public/services/beamsTypes/beamsTypesProvider.js create mode 100644 lib/server/controllers/beamsTypes.controller.js create mode 100644 lib/server/routers/beamsTypes.router.js create mode 100644 lib/usecases/beamsType/GetAllBeamsTypesUseCase.js create mode 100644 lib/usecases/beamsType/index.js diff --git a/lib/domain/dtos/GetAllBeamsTypesDto.js b/lib/domain/dtos/GetAllBeamsTypesDto.js new file mode 100644 index 0000000000..c4e78bfec5 --- /dev/null +++ b/lib/domain/dtos/GetAllBeamsTypesDto.js @@ -0,0 +1,28 @@ +/** + * @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 Joi = require('joi'); +const PaginationDto = require('./PaginationDto'); + +const QueryDto = Joi.object({ + page: PaginationDto, + token: Joi.string(), +}); + +const GetAllBeamsTypesDto = Joi.object({ + body: Joi.object({}), + params: Joi.object({}), + query: QueryDto, +}); + +module.exports = GetAllBeamsTypesDto; diff --git a/lib/domain/dtos/index.js b/lib/domain/dtos/index.js index 01eb348934..d1bb88492f 100644 --- a/lib/domain/dtos/index.js +++ b/lib/domain/dtos/index.js @@ -18,6 +18,7 @@ const CreateLhcFillDto = require('./CreateLhcFillDto'); const CreateLogDto = require('./CreateLogDto'); const CreateTagDto = require('./CreateTagDto'); const EntityIdDto = require('./EntityIdDto'); +const GetAllBeamsTypesDto = require('./GetAllBeamsTypesDto.js'); const GetAllEnvironmentsDto = require('./GetAllEnvironmentsDto'); const GetAllLhcFillsDto = require('./GetAllLhcFillsDto'); const GetAllLogAttachmentsDto = require('./GetAllLogAttachmentsDto'); @@ -56,6 +57,7 @@ module.exports = { CreateTagDto, EndRunDto, EntityIdDto, + GetAllBeamsTypesDto, GetAllEnvironmentsDto, GetAllLhcFillsDto, GetAllLogAttachmentsDto, diff --git a/lib/public/domain/enums/BeamType.js b/lib/domain/enums/BeamType.js similarity index 95% rename from lib/public/domain/enums/BeamType.js rename to lib/domain/enums/BeamType.js index c2266fda6f..9b7a1285b1 100644 --- a/lib/public/domain/enums/BeamType.js +++ b/lib/domain/enums/BeamType.js @@ -13,7 +13,7 @@ export const BeamType = Object.freeze({ PROTON_PROTON: 'PROTON-PROTON', - LEAD_LEAD: 'LEAD-LEAD', + LEAD_LEAD: 'PB82-PB82', }); export const BEAM_TYPES = Object.values(BeamType); diff --git a/lib/public/services/beamsTypes/beamsTypesProvider.js b/lib/public/services/beamsTypes/beamsTypesProvider.js new file mode 100644 index 0000000000..20a688d0a2 --- /dev/null +++ b/lib/public/services/beamsTypes/beamsTypesProvider.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 beams types from the backend + */ +export class BeamsTypesProvider extends RemoteDataProvider { + /** + * @inheritDoc + */ + async getRemoteData() { + const { data } = await getRemoteData('/api/beamsTypes'); + return data; + } +} + +export const beamsTypesProvider = new BeamsTypesProvider(); diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 897b407d13..1b1ceca149 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -171,7 +171,8 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (value) => formatBeamType(value), - filter: (lhcFillModel) => beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), + filter: (lhcFillModel) => lhcFillModel.getBeamsTypes().length === 0 ? + undefined : beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), }, collidingBunches: { name: 'Colliding bunches', diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index ab90525178..3734968576 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -18,7 +18,7 @@ import { RawTextFilterModel } from '../../../components/Filters/common/filters/R import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; import { SelectionFilterModel } from '../../../components/Filters/common/filters/SelectionFilterModel.js'; -import { BEAM_TYPES } from '../../../domain/enums/BeamType.js'; +import { beamsTypesProvider } from '../../../services/beamsTypes/beamsTypesProvider.js'; const defaultBeamDurationOperator = '='; const defaultRunDurationOperator = '='; @@ -37,12 +37,22 @@ export class LhcFillsOverviewModel extends OverviewPageModel { constructor(stableBeamsOnly = false) { super(); + this._beamsTypes = []; + this._filteringModel = new FilteringModel({ fillNumbers: new RawTextFilterModel(), beamDuration: new RawTextFilterModel(), runDuration: new RawTextFilterModel(), hasStableBeams: new StableBeamFilterModel(), - beamsType: new SelectionFilterModel({ availableOptions: BEAM_TYPES.map((type) => ({ value: type })) }), + }); + + beamsTypesProvider.items$.observe(() => { + beamsTypesProvider.items$.getCurrent().apply({ + Success: (types) => { + this._beamsTypes = types.map((type) => ({ value: type })); + this._filteringModel.put('beamsType', new SelectionFilterModel({ availableOptions: this._beamsTypes })); + }, + }); }); this._beamDurationOperator = defaultBeamDurationOperator; @@ -84,6 +94,13 @@ export class LhcFillsOverviewModel extends OverviewPageModel { return buildUrl('/api/lhcFills', params); } + /** + * Getter + */ + getBeamsTypes() { + return this._beamsTypes; + } + /** * Setter function for runDurationOperator */ diff --git a/lib/server/controllers/beamsTypes.controller.js b/lib/server/controllers/beamsTypes.controller.js new file mode 100644 index 0000000000..e67b21718c --- /dev/null +++ b/lib/server/controllers/beamsTypes.controller.js @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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 { + beamsType: { + GetAllBeamsTypesUseCase, + }, +} = require('../../usecases/index.js'); +const { + dtos: { + GetAllBeamsTypesDto, + }, +} = require('../../domain/index.js'); +const { dtoValidator } = require('../utilities/index.js'); +const { ApiConfig } = require('../../config/index.js'); + +/** + * Get all beams types. + * + * @param {Object} request The *request* object represents the HTTP request and has properties for the request query + * string, parameters, body, HTTP headers, and so on. + * @param {Object} response The *response* object represents the HTTP response that an Express app sends when it gets an + * HTTP request. + * @returns {undefined} + */ +const listBeamsTypes = async (request, response) => { + const value = await dtoValidator(GetAllBeamsTypesDto, request, response); + if (!value) { + return; + } + + const { count, beamsTypes } = await new GetAllBeamsTypesUseCase() + .execute(value); + + const { query: { page: { limit = ApiConfig.pagination.limit } = {} } } = value; + const totalPages = Math.ceil(count / limit); + + response.status(200).json({ + data: beamsTypes, + meta: { + page: { + pageCount: totalPages, + totalCount: count, + }, + }, + }); +}; + +module.exports = { + listBeamsTypes, +}; diff --git a/lib/server/controllers/index.js b/lib/server/controllers/index.js index 48184378b2..904dadaeb1 100644 --- a/lib/server/controllers/index.js +++ b/lib/server/controllers/index.js @@ -12,6 +12,7 @@ */ const AttachmentsController = require('./attachments.controller'); +const BeamsTypeController = require('./beamsTypes.controller.js'); const ConfigurationController = require('./configuration.controller.js'); const DetectorsController = require('./detectors.controller'); const EnvironmentsController = require('./environments.controller'); @@ -26,6 +27,7 @@ const CtpTriggerCountersController = require('./ctpTriggerCounters.controller'); module.exports = { AttachmentsController, + BeamsTypeController, ConfigurationController, DetectorsController, EnvironmentsController, diff --git a/lib/server/routers/beamsTypes.router.js b/lib/server/routers/beamsTypes.router.js new file mode 100644 index 0000000000..a64aa6b4f8 --- /dev/null +++ b/lib/server/routers/beamsTypes.router.js @@ -0,0 +1,20 @@ +/** + * @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 { BeamsTypeController } = require('../controllers'); + +exports.beamsTypesRouter = { + path: '/beamsTypes', + controller: BeamsTypeController.listBeamsTypes, + method: 'get', +}; diff --git a/lib/server/routers/index.js b/lib/server/routers/index.js index cb83465446..a064f5a246 100644 --- a/lib/server/routers/index.js +++ b/lib/server/routers/index.js @@ -15,6 +15,7 @@ const { deepmerge, isPromise } = require('../../utilities'); const attachmentRoute = require('./attachments.router'); const { configurationRouter } = require('./configuration.router.js'); +const { beamsTypesRouter } = require('./beamsTypes.router.js') const detectorsRoute = require('./detectors.router'); const { dplProcessRouter } = require('./dplProcess.router.js'); const environmentRoute = require('./environments.router'); @@ -40,6 +41,7 @@ const { ctpTriggerCountersRouter } = require('./ctpTriggerCounters.router.js'); const routes = [ attachmentRoute, + beamsTypesRouter, configurationRouter, detectorsRoute, dataPassesRouter, diff --git a/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js new file mode 100644 index 0000000000..51a8ddc6cc --- /dev/null +++ b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js @@ -0,0 +1,41 @@ +/** + * @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 { ApiConfig } = require('../../config/index.js'); +const { BEAM_TYPES } = require('../../domain/enums/BeamType.js'); + +/** + * GetAllBeamsTypesUseCase + */ +class GetAllBeamsTypesUseCase { + /** + * Executes this use case. + * + * @param {Object} dto The GetAllBeamsTypes DTO which contains all request data. + * @returns {Promise} Promise object represents the result of this use case. + */ + async execute(dto = {}) { + // DTO data to be used if Database data is desired. + const { query = {} } = dto; + const { page = {} } = query; + const { limit = ApiConfig.pagination.limit, offset = 0 } = page; + + const beamsTypes = BEAM_TYPES; + return { + count: beamsTypes.length, + beamsTypes, + }; + } +} + +module.exports = GetAllBeamsTypesUseCase; diff --git a/lib/usecases/beamsType/index.js b/lib/usecases/beamsType/index.js new file mode 100644 index 0000000000..5a60a1e65f --- /dev/null +++ b/lib/usecases/beamsType/index.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. + */ +const GetAllBeamsTypesUseCase = require('./GetAllBeamsTypesUseCase.js'); + +module.exports = { + GetAllBeamsTypesUseCase, +}; diff --git a/lib/usecases/index.js b/lib/usecases/index.js index 2dfa7807e5..413ffe4791 100644 --- a/lib/usecases/index.js +++ b/lib/usecases/index.js @@ -12,6 +12,7 @@ */ const attachment = require('./attachment'); +const beamsType = require('./beamsType'); const environment = require('./environment'); const flp = require('./flp'); const lhcFill = require('./lhcFill'); @@ -24,6 +25,7 @@ const tag = require('./tag'); module.exports = { attachment, + beamsType, environment, flp, lhcFill, From 19269b8a3625bffd38a20402db97fe17d41a26b9 Mon Sep 17 00:00:00 2001 From: Jasper Houweling Date: Thu, 11 Dec 2025 17:08:44 +0100 Subject: [PATCH 4/5] [O2B-1508] Beams types filter works with data from backend. --- .../repositories/LhcFillRepository.js | 12 ++++ lib/domain/enums/BeamType.js | 19 ------ .../LhcFillsFilter/BeamsTypeFilterModel.js | 59 +++++++++++++++++++ .../Filters/LhcFillsFilter/beamsTypeFilter.js | 11 ++-- .../ActiveColumns/lhcFillsActiveColumns.js | 3 +- .../Overview/LhcFillsOverviewModel.js | 13 +--- .../beamsType/GetAllBeamsTypesUseCase.js | 17 ++---- lib/usecases/lhcFill/GetAllLhcFillsUseCase.js | 2 +- 8 files changed, 86 insertions(+), 50 deletions(-) delete mode 100644 lib/domain/enums/BeamType.js create mode 100644 lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js diff --git a/lib/database/repositories/LhcFillRepository.js b/lib/database/repositories/LhcFillRepository.js index 8ed50d16cb..c74f9ffdd4 100644 --- a/lib/database/repositories/LhcFillRepository.js +++ b/lib/database/repositories/LhcFillRepository.js @@ -34,6 +34,10 @@ const getFillNumbersWithStableBeamsEndedInPeriodQuery = (period) => ` AND stable_beams_start IS NOT NULL `; +const getLhcFillDistinctBeamTypesQuery = () => ` + SELECT DISTINCT beam_type FROM bookkeeping.lhc_fills +`; + /** * Sequelize implementation of the RunRepository. */ @@ -45,6 +49,14 @@ class LhcFillRepository extends Repository { super(LhcFill); } + /** + * Return the list of LHC fills distict beam types. + * @returns {Promise} + */ + async getLhcFillDistinctBeamTypes() { + return await sequelize.query(getLhcFillDistinctBeamTypesQuery(), { type: QueryTypes.SELECT, raw: true }); + } + /** * Return the list of LHC fills numbers for fills with stable beams that ended in the given period * diff --git a/lib/domain/enums/BeamType.js b/lib/domain/enums/BeamType.js deleted file mode 100644 index 9b7a1285b1..0000000000 --- a/lib/domain/enums/BeamType.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @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 BeamType = Object.freeze({ - PROTON_PROTON: 'PROTON-PROTON', - LEAD_LEAD: 'PB82-PB82', -}); - -export const BEAM_TYPES = Object.values(BeamType); diff --git a/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js b/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js new file mode 100644 index 0000000000..389328b220 --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js @@ -0,0 +1,59 @@ +/** + * @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 { beamsTypesProvider } from '../../../services/beamsTypes/beamsTypesProvider.js'; +import { SelectionModel } from '../../common/selection/SelectionModel.js'; + +/** + * Beam type filter model + */ +export class BeamsTypeFilterModel extends SelectionModel { + /** + * Constructor + */ + constructor() { + let beamTypes = []; + super({ availableOptions: beamTypes, + defaultSelection: [], + multiple: true, + allowEmpty: true }); + + beamsTypesProvider.items$.observe(() => { + beamsTypesProvider.items$.getCurrent().apply({ + Success: (types) => { + beamTypes = types.map((type) => ({ value: String(type.beam_type) })); + this.setAvailableOptions(beamTypes); + }, + }); + }); + } + + /** + * Get normalized selected option + */ + get normalized() { + return this.selected.join(','); + } + + /** + * Reset the filter to default values + * + * @return {void} + */ + resetDefaults() { + if (!this.isEmpty) { + this.reset(); + this.notify(); + } + } +} diff --git a/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js index 070b37f4ea..94a5887d42 100644 --- a/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js +++ b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js @@ -16,10 +16,11 @@ import { checkboxes } from '../common/filters/checkboxFilter.js'; /** * Renders a list of checkboxes that lets the user look for beam types * - * @param {SelectionFilterModel} selectionFilterModel selectionFilterModel + * @param {BeamsTypeFilterModel} beamsTypeFilterModel beamsTypeFilterModel * @return {Component} the filter */ -export const beamsTypeFilter = (selectionFilterModel) => checkboxes( - selectionFilterModel._selectionModel, - { selector: 'beams-types' }, -); +export const beamsTypeFilter = (beamsTypeFilterModel) => + checkboxes( + beamsTypeFilterModel, + { selector: 'beams-types' }, + ); diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 1b1ceca149..897b407d13 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -171,8 +171,7 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (value) => formatBeamType(value), - filter: (lhcFillModel) => lhcFillModel.getBeamsTypes().length === 0 ? - undefined : beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), + filter: (lhcFillModel) => beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), }, collidingBunches: { name: 'Colliding bunches', diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 3734968576..5689428c96 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -17,8 +17,7 @@ import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilte import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; -import { SelectionFilterModel } from '../../../components/Filters/common/filters/SelectionFilterModel.js'; -import { beamsTypesProvider } from '../../../services/beamsTypes/beamsTypesProvider.js'; +import { BeamsTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js'; const defaultBeamDurationOperator = '='; const defaultRunDurationOperator = '='; @@ -44,15 +43,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { beamDuration: new RawTextFilterModel(), runDuration: new RawTextFilterModel(), hasStableBeams: new StableBeamFilterModel(), - }); - - beamsTypesProvider.items$.observe(() => { - beamsTypesProvider.items$.getCurrent().apply({ - Success: (types) => { - this._beamsTypes = types.map((type) => ({ value: type })); - this._filteringModel.put('beamsType', new SelectionFilterModel({ availableOptions: this._beamsTypes })); - }, - }); + beamsType: new BeamsTypeFilterModel(), }); this._beamDurationOperator = defaultBeamDurationOperator; diff --git a/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js index 51a8ddc6cc..405e9b5f14 100644 --- a/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js +++ b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js @@ -11,8 +11,7 @@ * or submit itself to any jurisdiction. */ -const { ApiConfig } = require('../../config/index.js'); -const { BEAM_TYPES } = require('../../domain/enums/BeamType.js'); +const LhcFillRepository = require('../../database/repositories/LhcFillRepository.js'); /** * GetAllBeamsTypesUseCase @@ -21,19 +20,13 @@ class GetAllBeamsTypesUseCase { /** * Executes this use case. * - * @param {Object} dto The GetAllBeamsTypes DTO which contains all request data. * @returns {Promise} Promise object represents the result of this use case. */ - async execute(dto = {}) { - // DTO data to be used if Database data is desired. - const { query = {} } = dto; - const { page = {} } = query; - const { limit = ApiConfig.pagination.limit, offset = 0 } = page; - - const beamsTypes = BEAM_TYPES; + async execute() { + const result = await LhcFillRepository.getLhcFillDistinctBeamTypes(); return { - count: beamsTypes.length, - beamsTypes, + count: result.length, + beamsTypes: result, }; } } diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index 155861806c..5a6d2dfa80 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -81,7 +81,7 @@ class GetAllLhcFillsUseCase { // Beams type. if (beamsType) { const beamTypes = beamsType.split(','); - queryBuilder.where('beamType').oneOfSubstrings(beamTypes); + queryBuilder.where('beamType').oneOf(beamTypes); } } From 18ea3868a260f345e1ec0afc613b9c5ffefd78b2 Mon Sep 17 00:00:00 2001 From: Jasper Houweling Date: Tue, 16 Dec 2025 13:33:58 +0100 Subject: [PATCH 5/5] [O2B-1508] Handle beamtype 'null'. Added tests --- lib/database/utilities/QueryBuilder.js | 35 +++++++++++++++- lib/usecases/lhcFill/GetAllLhcFillsUseCase.js | 10 ++++- .../lhcFill/GetAllLhcFillsUseCase.test.js | 40 +++++++++++++++++++ test/public/lhcFills/overview.test.js | 2 + 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/lib/database/utilities/QueryBuilder.js b/lib/database/utilities/QueryBuilder.js index 2f522902e8..ab16d35ccf 100644 --- a/lib/database/utilities/QueryBuilder.js +++ b/lib/database/utilities/QueryBuilder.js @@ -88,7 +88,7 @@ class WhereQueryBuilder { } /** - * Sets an **OR** match filter using the provided values. + * Sets an **IN** match filter using the provided values. * * @param {...any} values The required values. * @returns {QueryBuilder} The current QueryBuilder instance. @@ -104,6 +104,39 @@ class WhereQueryBuilder { return this._op(operation); } + /** + * Sets an **OR** match filter using the provided values. + * Adds an **OR NULL** filter. + * If the spread returns empty array the filter becomes an **IS NULL** filter (**OR** is not valid anymore) + * + * @param {...any} values The required values. + * @returns {QueryBuilder} The current QueryBuilder instance. + */ + oneOfOrNull(...values) { + let operation; + if (this.notFlag) { + operation = values[0]?.length === 0 ? { + [Op.not]: null, + } : { + [Op.or]: { + [Op.notIn]: values, + [Op.not]: null, + }, + }; + } else { + operation = values[0]?.length === 0 ? { + [Op.is]: null, + } : { + [Op.or]: { + [Op.in]: values, + [Op.is]: null, + }, + }; + } + + return this._op(operation); + } + /** * Set a max range limit using the provided value * diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index 5a6d2dfa80..958f07cbe2 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -80,8 +80,14 @@ class GetAllLhcFillsUseCase { // Beams type. if (beamsType) { - const beamTypes = beamsType.split(','); - queryBuilder.where('beamType').oneOf(beamTypes); + let beamTypes = beamsType.split(','); + // Check if 'null' is included in the request + if (beamTypes.find((type) => type.trim() === 'null') !== undefined) { + beamTypes = beamTypes.filter((type) => type.trim() !== 'null'); + queryBuilder.where('beamType').oneOfOrNull(beamTypes); + } else { + queryBuilder.where('beamType').oneOf(beamTypes); + } } } diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index 6c317588ff..c9284001f5 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -220,4 +220,44 @@ module.exports = () => { expect(lhcFill.statistics.runsCoverage).greaterThan(23459) }); }) + + it('should only contain specified beam types, {p-p, PROTON-PROTON, Pb-Pb}', async () => { + const beamTypes = ['p-p', ' PROTON-PROTON', 'Pb-Pb'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(4) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf(beamTypes) + }); + }) + + it('should only contain specified beam types, OR NULL, {p-p, PROTON-PROTON, Pb-Pb, null}', async () => { + let beamTypes = ['p-p', ' PROTON-PROTON', 'Pb-Pb', 'null'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(5) + + const nullIndex = beamTypes.findIndex((value) => value ==='null') + beamTypes[nullIndex] = null; + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf(beamTypes) + }); + }) + + it('should only contain specified beam type, IS NULL, {null}', async () => { + const beamTypes = ['null'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(1) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf([null]) + }); + }) }; diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index c67a1ebb3c..adea7f85fa 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -273,6 +273,7 @@ module.exports = () => { const filterSBDurationPlaceholderExpect = {selector: '.beam-duration-filter', value: 'e.g 16:14:15 (HH:MM:SS)'} const filterRunDurationExpect = {selector: 'div.flex-row:nth-child(4) > div:nth-child(1)', value: 'Total runs duration'} const filterRunDurationPlaceholderExpect = {selector: '.run-duration-filter', value: 'e.g 16:14:15 (HH:MM:SS)'} + const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(5) > div:nth-child(1)', value: 'Beam Type'} await goToPage(page, 'lhc-fill-overview'); @@ -284,6 +285,7 @@ module.exports = () => { await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value); await expectInnerText(page, filterRunDurationExpect.selector, filterRunDurationExpect.value); await expectAttributeValue(page, filterRunDurationPlaceholderExpect.selector, 'placeholder', filterRunDurationPlaceholderExpect.value); + await expectInnerText(page, filterBeamTypeExpect.selector, filterBeamTypeExpect.value); }); it('should successfully un-apply Stable Beam filter menu', async () => {