diff --git a/QualityControl/common/library/typedef/DataPass.js b/QualityControl/common/library/typedef/DataPass.js new file mode 100644 index 000000000..3ddad2eb1 --- /dev/null +++ b/QualityControl/common/library/typedef/DataPass.js @@ -0,0 +1,18 @@ +/** + * @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. + */ + +/** + * @typedef {object} DataPass + * @property {string} name - Human-readable data pass name. + * @property {boolean} isFrozen - Whether the data pass is frozen. + */ diff --git a/QualityControl/common/library/typedef/DetectorSummary.js b/QualityControl/common/library/typedef/DetectorSummary.js new file mode 100644 index 000000000..481c887ea --- /dev/null +++ b/QualityControl/common/library/typedef/DetectorSummary.js @@ -0,0 +1,18 @@ +/** + * @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. + */ + +/** + * @typedef {object} DetectorSummary + * @property {string} name - Human-readable detector name. + * @property {string} type - Detector type identifier. + */ diff --git a/QualityControl/lib/QCModel.js b/QualityControl/lib/QCModel.js index aa5364af6..ba2af7ee2 100644 --- a/QualityControl/lib/QCModel.js +++ b/QualityControl/lib/QCModel.js @@ -171,4 +171,11 @@ function initializeIntervals(intervalsService, qcObjectService, filterService, r runModeService.refreshInterval, ); } + + if (filterService.dataPassesRefreshInterval > 0) { + intervalsService.register( + filterService.getDataPasses.bind(runModeService), + filterService.dataPassesRefreshInterval, + ); + } } diff --git a/QualityControl/lib/controllers/FilterController.js b/QualityControl/lib/controllers/FilterController.js index 0fcd51908..118b4c5d7 100644 --- a/QualityControl/lib/controllers/FilterController.js +++ b/QualityControl/lib/controllers/FilterController.js @@ -63,12 +63,13 @@ export class FilterController { */ async getFilterConfigurationHandler(req, res) { try { - let runTypes = []; - if (this._filterService) { - runTypes = await this._filterService.runTypes; - } + const runTypes = this._filterService?.runTypes ?? []; + const detectors = this._filterService?.detectors ?? []; + const dataPasses = this._filterService?.dataPasses ?? []; res.status(200).json({ runTypes, + detectors, + dataPasses, }); } catch (error) { res.status(503).json({ error: error.message || error }); diff --git a/QualityControl/lib/dtos/ObjectGetDto.js b/QualityControl/lib/dtos/ObjectGetDto.js index 1aee13e5f..43c93b88f 100644 --- a/QualityControl/lib/dtos/ObjectGetDto.js +++ b/QualityControl/lib/dtos/ObjectGetDto.js @@ -14,6 +14,7 @@ import Joi from 'joi'; import { RunNumberDto } from './filters/RunNumberDto.js'; +import { QcDetectorNameDto } from './filters/QcDetectorNameDto.js'; const periodNamePattern = /^LHC\d{1,2}[a-z0-9]+$/i; @@ -25,6 +26,7 @@ const periodNamePattern = /^LHC\d{1,2}[a-z0-9]+$/i; function createFiltersSchema(runTypes) { return Joi.object({ RunNumber: RunNumberDto.optional(), + QcDetectorName: QcDetectorNameDto.optional(), RunType: runTypes.length > 0 ? Joi.string().valid(...runTypes).optional() : Joi.string().optional(), diff --git a/QualityControl/lib/dtos/filters/QcDetectorNameDto.js b/QualityControl/lib/dtos/filters/QcDetectorNameDto.js new file mode 100644 index 000000000..a067d8e54 --- /dev/null +++ b/QualityControl/lib/dtos/filters/QcDetectorNameDto.js @@ -0,0 +1,22 @@ +/** + * @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. + */ + +import Joi from 'joi'; + +export const QcDetectorNameDto = Joi.string() + .min(1) + .messages({ + 'number.base': 'Detector name must be a string', + 'number.min': 'Detector name must not be an empty string', + }); diff --git a/QualityControl/lib/services/BookkeepingService.js b/QualityControl/lib/services/BookkeepingService.js index f5d9f6353..1435a279e 100644 --- a/QualityControl/lib/services/BookkeepingService.js +++ b/QualityControl/lib/services/BookkeepingService.js @@ -20,6 +20,8 @@ import { wrapRunStatus } from '../dtos/BookkeepingDto.js'; const GET_BKP_DATABASE_STATUS_PATH = '/api/status/database'; const GET_RUN_TYPES_PATH = '/api/runTypes'; const GET_RUN_PATH = '/api/runs'; +const GET_DETECTORS_PATH = '/api/detectors'; +const GET_DATA_PASSES_PATH = '/api/dataPasses'; const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/bkp-service`; @@ -127,6 +129,23 @@ export class BookkeepingService { return data; } + /** + * Retrieve the list of data passes from the bookkeeping service. + * @returns {Promise} Resolves with an array of data passes. + */ + async retrieveDataPasses() { + const { data } = await httpGetJson( + this._hostname, + this._port, + this._createPath(GET_DATA_PASSES_PATH), + { + protocol: this._protocol, + rejectUnauthorized: false, + }, + ); + return Array.isArray(data) ? data : []; + } + /** * Retrieves the information of a specific run from the Bookkeeping service * @param {number} runNumber - The run number to check the status for @@ -181,6 +200,23 @@ export class BookkeepingService { } } + /** + * Retrieves the information about the detectors from the Bookkeeping service. + * @returns {Promise} Array of detector summaries. + */ + async retrieveDetectorSummaries() { + const { data } = await httpGetJson( + this._hostname, + this._port, + this._createPath(GET_DETECTORS_PATH), + { + protocol: this._protocol, + rejectUnauthorized: false, + }, + ); + return Array.isArray(data) ? data : []; + } + /** * Helper method to construct a URL path with the required authentication token. * Appends the service's token as a query parameter to the provided path. diff --git a/QualityControl/lib/services/FilterService.js b/QualityControl/lib/services/FilterService.js index d6da52c57..26f55f866 100644 --- a/QualityControl/lib/services/FilterService.js +++ b/QualityControl/lib/services/FilterService.js @@ -31,10 +31,13 @@ export class FilterService { this._logger = LogManager.getLogger(LOG_FACILITY); this._bookkeepingService = bookkeepingService; this._runTypes = []; - this.initFilters(); + this._detectors = Object.freeze([]); + this._dataPasses = Object.freeze([]); this._runTypesRefreshInterval = config?.bookkeeping?.runTypesRefreshInterval ?? - (config?.bookkeeping ? 24 * 60 * 60 * 1000 : -1); + (config?.bookkeeping ? 24 * 60 * 60 * 1000 : -1);// default interval is 1 day + this._dataPassesRefreshInterval = config?.bookkeeping?.dataPassesRefreshInterval ?? + (config?.bookkeeping ? 60 * 60 * 1000 : -1);// default interval is 1 hour this.initFilters().catch((error) => { this._logger.errorMessage(`FilterService initialization failed: ${error.message || error}`); @@ -48,6 +51,8 @@ export class FilterService { async initFilters() { await this._bookkeepingService.connect(); await this.getRunTypes(); + await this._initializeDetectors(); + await this.getDataPasses(); } /** @@ -71,6 +76,56 @@ export class FilterService { } } + /** + * This method is used to retrieve the list of data passes from the bookkeeping service. + * @returns {Promise} Resolves when the list of data passes is available. + */ + async getDataPasses() { + try { + if (!this._bookkeepingService.active) { + return; + } + + const rawDataPasses = await this._bookkeepingService.retrieveDataPasses(); + this._dataPasses = Object.freeze(rawDataPasses.map(({ name, isFrozen }) => Object.freeze({ name, isFrozen }))); + } catch (error) { + this._logger.errorMessage(`Error while retrieving data passes: ${error.message || error}`); + } + } + + /** + * Returns a list of data passes. + * @returns {Readonly} An immutable array of data passes. + */ + get dataPasses() { + return this._dataPasses; + } + + /** + * This method is used to retrieve the list of detectors from the bookkeeping service + * @returns {Promise} Resolves when the list of detectors is available + */ + async _initializeDetectors() { + try { + if (!this._bookkeepingService.active) { + return; + } + + const detectorSummaries = await this._bookkeepingService.retrieveDetectorSummaries(); + this._detectors = Object.freeze(detectorSummaries.map(({ name, type }) => Object.freeze({ name, type }))); + } catch (error) { + this._logger.errorMessage(`Failed to retrieve detectors: ${error?.message || error}`); + } + } + + /** + * Returns a list of detector summaries. + * @returns {Readonly} An immutable array of detector summaries. + */ + get detectors() { + return this._detectors; + } + /** * Returns the interval in milliseconds for how often the list of run types should be refreshed. * @returns {number} Interval in milliseconds for refreshing the list of run types. @@ -79,6 +134,14 @@ export class FilterService { return this._runTypesRefreshInterval; } + /** + * Returns the interval in milliseconds for how often the list of data passes should be refreshed. + * @returns {number} Interval in milliseconds for refreshing the list of data passes. + */ + get dataPassesRefreshInterval() { + return this._dataPassesRefreshInterval; + } + /** * This method is used to initialize the filter service * @returns {string[]} - resolves when the filter service is initialized diff --git a/QualityControl/public/common/filters/filter.js b/QualityControl/public/common/filters/filter.js index 6d658d9e2..a063dc108 100644 --- a/QualityControl/public/common/filters/filter.js +++ b/QualityControl/public/common/filters/filter.js @@ -13,7 +13,7 @@ */ import { FilterType } from './filterTypes.js'; -import { h, RemoteData } from '/js/src/index.js'; +import { h, RemoteData, DropdownComponent } from '/js/src/index.js'; /** * Builds a filter element. If options to show, selector filter element; otherwise, input element. @@ -61,6 +61,149 @@ export const dynamicSelector = (config) => { }); }; +/** + * Represents options grouped for HTML . + * Keys are group labels (for the label), + * values are arrays of option values (for