diff --git a/lib/server/api-docs/API.md b/lib/server/api-docs/API.md index 70a34d614f..80b5632eb0 100644 --- a/lib/server/api-docs/API.md +++ b/lib/server/api-docs/API.md @@ -436,3 +436,58 @@ Responses: ```bash curl -g "$BOOKKEEPING_URL/api/simulationPasses/1?token=$TOKEN" ``` + +## LHC periods statistics + +LHC periods with statistics on their physics runs. + +### `GET /lhcPeriodsStatistics` - List LHC periods with their statistics + +Operation id: `listLhcPeriodsStatistics` + +Returns a page of LHC periods with statistics computed on their **physics runs**: number of runs, average center-of-mass energy, distinct +energies, PDP beam types and number of data and simulation passes. Default order is by id, descending. + +List filters are repeated query parameters: `filter[ids][]=1&filter[ids][]=2`. A single value without `[]` is rejected. Unknown query +parameters are rejected. + +| Parameter | In | Type | Required | Notes | +| --- | --- | --- | --- | --- | +| `filter[ids][]` | query | integer | no | Keep the LHC periods with one of these ids. Repeat the parameter for several values. | +| `filter[names][]` | query | string | no | Keep the LHC periods with one of these exact names. Repeat the parameter for several values. | +| `filter[years][]` | query | integer | no | Keep the LHC periods of these years. The year is read from the period name (`LHC23f` is 2023). Repeat the parameter for several values. | +| `filter[pdpBeamTypes][]` | query | string | no | Keep the LHC periods having runs of one of these PDP beam types. Repeat the parameter for several values. | +| `sort[id]` | query | string | no | Sort by id (the default is `desc`). | +| `sort[name]` | query | string | no | Sort by name. | +| `sort[avgCenterOfMassEnergy]` | query | string | no | Sort by average center-of-mass energy. | +| `sort[year]` | query | string | no | Sort by year. | +| `sort[pdpBeamTypes]` | query | string | no | Sort by PDP beam types. | +| `page[limit]` | query | integer | no | Maximum number of items to return. LLMs and agents must use 100 or less. | +| `page[offset]` | query | integer | no | Number of items to skip. | + +Responses: + +- `200`: A page of LHC periods with statistics. +- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes//`. + +```bash +curl -g "$BOOKKEEPING_URL/api/lhcPeriodsStatistics?page[limit]=100&token=$TOKEN" +``` + +### `GET /lhcPeriodsStatistics/{lhcPeriodId}` - Get the statistics of an LHC period + +Operation id: `getLhcPeriodStatistics` + +| Parameter | In | Type | Required | Notes | +| --- | --- | --- | --- | --- | +| `lhcPeriodId` | path | integer | yes | LHC period identifier. | + +Responses: + +- `200`: The LHC period with its statistics. +- `400`: The request failed validation. HTTP status is 400 although each error's `status` field is `"422"`. `source.pointer` has the form `/data/attributes//`. +- `404`: No LHC period has this id. + +```bash +curl -g "$BOOKKEEPING_URL/api/lhcPeriodsStatistics/1?token=$TOKEN" +``` diff --git a/lib/server/api-docs/index.html b/lib/server/api-docs/index.html index cb869dc1c8..39c3c7d91c 100644 --- a/lib/server/api-docs/index.html +++ b/lib/server/api-docs/index.html @@ -759,6 +759,143 @@

Simulation passes

curl -g "$BOOKKEEPING_URL/api/simulationPasses/1?token=$TOKEN"
 
+

LHC periods statistics

+

LHC periods with statistics on their physics runs.

+
+

GET /lhcPeriodsStatistics - List LHC periods with their statistics

+ +

Operation id: listLhcPeriodsStatistics

+

Returns a page of LHC periods with statistics computed on their physics runs: number of runs, average center-of-mass energy, distinct +energies, PDP beam types and number of data and simulation passes. Default order is by id, descending.

+

List filters are repeated query parameters: filter[ids][]=1&filter[ids][]=2. A single value without [] is rejected. Unknown query +parameters are rejected.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterInTypeRequiredNotes
filter[ids][]queryintegernoKeep the LHC periods with one of these ids. Repeat the parameter for several values.
filter[names][]querystringnoKeep the LHC periods with one of these exact names. Repeat the parameter for several values.
filter[years][]queryintegernoKeep the LHC periods of these years. The year is read from the period name (LHC23f is 2023). Repeat the parameter for several values.
filter[pdpBeamTypes][]querystringnoKeep the LHC periods having runs of one of these PDP beam types. Repeat the parameter for several values.
sort[id]querystringnoSort by id (the default is desc).
sort[name]querystringnoSort by name.
sort[avgCenterOfMassEnergy]querystringnoSort by average center-of-mass energy.
sort[year]querystringnoSort by year.
sort[pdpBeamTypes]querystringnoSort by PDP beam types.
page[limit]queryintegernoMaximum number of items to return. LLMs and agents must use 100 or less.
page[offset]queryintegernoNumber of items to skip.
+

Responses:

+
    +
  • 200: A page of LHC periods with statistics.
  • +
  • 400: The request failed validation. HTTP status is 400 although each error's status field is "422". source.pointer has the form /data/attributes/<query|params|body>/<field>.
  • +
+
curl -g "$BOOKKEEPING_URL/api/lhcPeriodsStatistics?page[limit]=100&token=$TOKEN"
+
+
+
+

GET /lhcPeriodsStatistics/{lhcPeriodId} - Get the statistics of an LHC period

+ +

Operation id: getLhcPeriodStatistics

+ + + + + + + + + + + + + + + + + +
ParameterInTypeRequiredNotes
lhcPeriodIdpathintegeryesLHC period identifier.
+

Responses:

+
    +
  • 200: The LHC period with its statistics.
  • +
  • 400: The request failed validation. HTTP status is 400 although each error's status field is "422". source.pointer has the form /data/attributes/<query|params|body>/<field>.
  • +
  • 404: No LHC period has this id.
  • +
+
curl -g "$BOOKKEEPING_URL/api/lhcPeriodsStatistics/1?token=$TOKEN"
+
+
Generated from openapi.yaml by npm run docs:api · ALICE Bookkeeping · internal use only
diff --git a/lib/server/api-docs/openapi.yaml b/lib/server/api-docs/openapi.yaml index 4a7fc919b2..86cbd3d200 100644 --- a/lib/server/api-docs/openapi.yaml +++ b/lib/server/api-docs/openapi.yaml @@ -51,6 +51,8 @@ tags: description: Data used to fill end-of-shift reports. - name: Simulation passes description: Monte Carlo simulation productions. + - name: LHC periods statistics + description: LHC periods with statistics on their physics runs. paths: /tags: @@ -522,6 +524,88 @@ paths: errors: - { status: 404, title: Not found, detail: Simulation pass with this id (99999) could not be found } + /lhcPeriodsStatistics: + get: + tags: [LHC periods statistics] + operationId: listLhcPeriodsStatistics + summary: List LHC periods with their statistics + description: | + Returns a page of LHC periods with statistics computed on their **physics runs**: number of runs, average center-of-mass energy, distinct + energies, PDP beam types and number of data and simulation passes. Default order is by id, descending. + + List filters are repeated query parameters: `filter[ids][]=1&filter[ids][]=2`. A single value without `[]` is rejected. Unknown query + parameters are rejected. + parameters: + - name: filter[ids][] + in: query + description: Keep the LHC periods with one of these ids. Repeat the parameter for several values. + schema: { type: integer } + - name: filter[names][] + in: query + description: Keep the LHC periods with one of these exact names. Repeat the parameter for several values. + schema: { type: string, examples: [LHC22b] } + - name: filter[years][] + in: query + description: Keep the LHC periods of these years. The year is read from the period name (`LHC23f` is 2023). Repeat the parameter for several values. + schema: { type: integer, examples: [2023] } + - name: filter[pdpBeamTypes][] + in: query + description: Keep the LHC periods having runs of one of these PDP beam types. Repeat the parameter for several values. + schema: { type: string, examples: [pp] } + - name: sort[id] + in: query + description: Sort by id (the default is `desc`). + schema: { $ref: '#/components/schemas/SortOrder' } + - name: sort[name] + in: query + description: Sort by name. + schema: { $ref: '#/components/schemas/SortOrder' } + - name: sort[avgCenterOfMassEnergy] + in: query + description: Sort by average center-of-mass energy. + schema: { $ref: '#/components/schemas/SortOrder' } + - name: sort[year] + in: query + description: Sort by year. + schema: { $ref: '#/components/schemas/SortOrder' } + - name: sort[pdpBeamTypes] + in: query + description: Sort by PDP beam types. + schema: { $ref: '#/components/schemas/SortOrder' } + - $ref: '#/components/parameters/PageLimit' + - $ref: '#/components/parameters/PageOffset' + responses: + '200': + description: A page of LHC periods with statistics. + content: + application/json: + schema: { $ref: '#/components/schemas/LhcPeriodStatisticsListResponse' } + '400': { $ref: '#/components/responses/ValidationError' } + + /lhcPeriodsStatistics/{lhcPeriodId}: + get: + tags: [LHC periods statistics] + operationId: getLhcPeriodStatistics + summary: Get the statistics of an LHC period + parameters: + - name: lhcPeriodId + in: path + required: true + description: LHC period identifier. + schema: { type: integer, minimum: 1 } + responses: + '200': + description: The LHC period with its statistics. + content: + application/json: + schema: { $ref: '#/components/schemas/LhcPeriodStatisticsResponse' } + '400': { $ref: '#/components/responses/ValidationError' } + '404': + description: No LHC period has this id. + content: + application/json: + schema: { $ref: '#/components/schemas/ErrorResponse' } + components: securitySchemes: jwtQuery: @@ -819,3 +903,36 @@ components: type: array items: { $ref: '#/components/schemas/SimulationPass' } meta: { $ref: '#/components/schemas/PageMeta' } + LhcPeriodStatistics: + type: object + properties: + id: { type: integer } + lhcPeriod: + type: [object, 'null'] + properties: + id: { type: integer } + name: { type: string, examples: [LHC22b] } + avgCenterOfMassEnergy: { type: [number, 'null'], description: Average over the physics runs. } + distinctEnergies: + type: array + description: Distinct center-of-mass energies of the physics runs. + items: { type: number } + pdpBeamTypes: + type: array + items: { type: string, examples: [pp] } + runsCount: { type: integer, description: Number of physics runs. } + dataPassesCount: { type: integer } + simulationPassesCount: { type: integer } + LhcPeriodStatisticsResponse: + type: object + required: [data] + properties: + data: { $ref: '#/components/schemas/LhcPeriodStatistics' } + LhcPeriodStatisticsListResponse: + type: object + required: [data, meta] + properties: + data: + type: array + items: { $ref: '#/components/schemas/LhcPeriodStatistics' } + meta: { $ref: '#/components/schemas/PageMeta' } diff --git a/lib/server/controllers/lhcPeriodStatistics.controller.js b/lib/server/controllers/lhcPeriodStatistics.controller.js index 8784e3871e..a9abc2ecc6 100644 --- a/lib/server/controllers/lhcPeriodStatistics.controller.js +++ b/lib/server/controllers/lhcPeriodStatistics.controller.js @@ -11,8 +11,6 @@ * or submit itself to any jurisdiction. */ -/* eslint-disable jsdoc/require-param */ - const Joi = require('joi'); const { ApiConfig } = require('../../config/index.js'); const { DtoFactory } = require('../../domain/dtos/DtoFactory'); @@ -21,9 +19,16 @@ const { dtoValidator } = require('../utilities/dtoValidator.js'); const { countedItemsToHttpView } = require('../utilities/countedItemsToHttpView.js'); const { updateExpressResponseFromNativeError } = require('../express/updateExpressResponseFromNativeError'); const PaginationDto = require('../../domain/dtos/PaginationDto.js'); +const EntityIdDto = require('../../domain/dtos/EntityIdDto.js'); /** * List All LHC Periods with statistics + * + * @param {Object} req The *request* object represents the HTTP request and has properties for the request query + * string, parameters, body, HTTP headers, and so on. + * @param {Object} res The *response* object represents the HTTP response that an Express app sends when it gets an + * HTTP request. + * @returns {Promise} resolves once the response has been sent */ const listLhcPeriodStatisticsHandler = async (req, res) => { const validatedDTO = await dtoValidator( @@ -58,11 +63,17 @@ const listLhcPeriodStatisticsHandler = async (req, res) => { /** * Get one LHC Period with given id with statistics + * + * @param {Object} req The *request* object represents the HTTP request and has properties for the request query + * string, parameters, body, HTTP headers, and so on. + * @param {Object} res The *response* object represents the HTTP response that an Express app sends when it gets an + * HTTP request. + * @returns {Promise} resolves once the response has been sent */ const getLhcPeriodStatisticsByIdHandler = async (req, res) => { const validatedDTO = await dtoValidator( DtoFactory.paramsOnly({ - lhcPeriodId: Joi.number().required(), + lhcPeriodId: EntityIdDto.required(), }), req, res, diff --git a/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.js b/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.js index a64c5566ee..faaf01e7a6 100644 --- a/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.js +++ b/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.js @@ -12,14 +12,15 @@ */ const { Op } = require('sequelize'); -const { repositories: { LhcPeriodStatisticsRepository } } = require('../../../database'); const { dataSource } = require('../../../database/DataSource.js'); const { lhcPeriodStatisticsAdapter } = require('../../../database/adapters'); const { RunQualities } = require('../../../domain/enums/RunQualities.js'); -const { BadParameterError } = require('../../errors/BadParameterError'); -const { NotFoundError } = require('../../errors/NotFoundError'); const { RunDefinition } = require('../../../domain/enums/RunDefinition.js'); const { NonPhysicsProductionsNamesWords } = require('../../../domain/enums/NonPhysicsProductionsNamesWords.js'); +const { getAllLhcPeriodStatistics } = require('./getAllLhcPeriodStatistics.js'); +const { getLhcPeriodStatistics } = require('./getLhcPeriodStatistics.js'); +const { getLhcPeriodStatisticsIdsByPdpBeamTypes } = require('./getLhcPeriodStatisticsIdsByPdpBeamTypes.js'); +const { getLhcPeriodStatisticsOrFail } = require('./getLhcPeriodStatisticsOrFail.js'); const sortExpressionMap = { name: (sequelize) => sequelize.col('`lhcPeriod`.`name`'), @@ -27,49 +28,31 @@ const sortExpressionMap = { pdpBeamTypes: (sequelize) => sequelize.literal('pdpBeamTypes'), }; -/** - * @typedef LhcPeriodIdentifier object to uniquely identify a lhc period - * @property {string} [name] the lhc period name - * @property {number} [id] the id of lhc period - */ - /** * LhcPeriodStatisticsService */ class LhcPeriodStatisticsService { /** - * Find an LHC Period Statistics model by its name or id including - * @param {LhcPeriodIdentifier} identifier the criteria to find run type - * @return {Promise} the run type found or null + * Find the statistics of an LHC period by the id or the name of the period + * + * @param {LhcPeriodIdentifier} identifier the criteria to find the LHC period statistics + * @return {Promise} the LHC period statistics found or null */ - async getByIdentifier({ id, name }) { - const queryBuilder = this.prepareQueryBuilder(); - - if (id !== null & id !== undefined) { - queryBuilder.where('id').is(id); - } else if (name) { - queryBuilder.whereAssociation('lhcPeriod', 'name').is(name); - } else { - throw new BadParameterError('Can not find without LHC Period id or name'); - } - - const data = await LhcPeriodStatisticsRepository.findOne(queryBuilder); - return data ? lhcPeriodStatisticsAdapter.toEntity(data) : null; + async getByIdentifier(identifier) { + const lhcPeriodStatistics = await getLhcPeriodStatistics(identifier, (queryBuilder) => this.prepareQueryBuilder(queryBuilder)); + return lhcPeriodStatistics ? lhcPeriodStatisticsAdapter.toEntity(lhcPeriodStatistics) : null; } /** - * Find an LHC Period Statistics model by its name or id - * @param {LhcPeriodIdentifier} identifier the criteria to find lhc period + * Find the statistics of an LHC period by the id or the name of the period + * + * @param {LhcPeriodIdentifier} identifier the criteria to find the LHC period statistics * @throws {NotFoundError} in case there is no LHC Period with given identifier - * @return {Promise} the lhc period statistics found + * @return {Promise} the LHC period statistics found */ - async getOneOrFail({ id, name }) { - const lhcPeriodStatistics = await this.getByIdentifier({ id, name }); - if (!lhcPeriodStatistics) { - const criteriaExpression = id !== undefined && id !== null ? `id (${id})` : `name (${name})`; - throw new NotFoundError(`LHC Period with this ${criteriaExpression} could not be found`); - } - return lhcPeriodStatistics; + async getOneOrFail(identifier) { + const lhcPeriodStatistics = await getLhcPeriodStatisticsOrFail(identifier, (queryBuilder) => this.prepareQueryBuilder(queryBuilder)); + return lhcPeriodStatisticsAdapter.toEntity(lhcPeriodStatistics); } /** @@ -90,23 +73,26 @@ class LhcPeriodStatisticsService { offset, sort, } = {}) { - const queryBuilder = this.prepareQueryBuilder(); - if (sort) { - for (const property in sort) { - const expression = sortExpressionMap[property]; - queryBuilder.orderBy(expression ?? property, sort[property]); + const { ids, names, years, pdpBeamTypes } = filter ?? {}; + const pdpBeamTypesStatisticsIds = pdpBeamTypes ? await getLhcPeriodStatisticsIdsByPdpBeamTypes(pdpBeamTypes) : null; + + const { count, rows } = await getAllLhcPeriodStatistics((queryBuilder) => { + this.prepareQueryBuilder(queryBuilder); + + if (sort) { + for (const property in sort) { + const expression = sortExpressionMap[property]; + queryBuilder.orderBy(expression ?? property, sort[property]); + } } - } - if (limit) { - queryBuilder.limit(limit); - } - if (offset) { - queryBuilder.offset(offset); - } + if (limit) { + queryBuilder.limit(limit); + } + if (offset) { + queryBuilder.offset(offset); + } - if (filter) { - const { ids, names, years, pdpBeamTypes } = filter; if (ids) { queryBuilder.where('id').oneOf(...ids); } @@ -119,28 +105,10 @@ class LhcPeriodStatisticsService { { years }, ); } - if (pdpBeamTypes) { - const targetIds = (await LhcPeriodStatisticsRepository.findAll({ - raw: true, - include: { - association: 'lhcPeriod', - required: true, - include: { - association: 'runs', - required: true, - where: { - pdpBeamType: { [Op.in]: pdpBeamTypes }, - definition: RunDefinition.PHYSICS, - runQuality: RunQualities.GOOD, - }, - }, - }, - })).map(({ id }) => id); - queryBuilder.where('id').oneOf(...targetIds); + if (pdpBeamTypesStatisticsIds) { + queryBuilder.where('id').oneOf(...pdpBeamTypesStatisticsIds); } - } - - const { count, rows } = await LhcPeriodStatisticsRepository.findAndCountAll(queryBuilder); + }); return { count: count.length, // When using grouping sequelize returns from finAndCountAll counts per each group @@ -149,11 +117,13 @@ class LhcPeriodStatisticsService { } /** - * Prepare common fetch data query builder + * Prepare common fetch data query builder (create a new one if none is provided) + * + * @param {QueryBuilder|null} [queryBuilder=null] if specified, an existing query builder that will be reused * @return {QueryBuilder} query builder with common includes */ - prepareQueryBuilder() { - return dataSource.createQueryBuilder() + prepareQueryBuilder(queryBuilder = null) { + return (queryBuilder ?? dataSource.createQueryBuilder()) .set('subQuery', false) .includeAttribute({ query: (sequelize) => sequelize.fn('COUNT', sequelize.fn('distinct', sequelize.col('lhcPeriod->runs.run_number'))), diff --git a/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.js b/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.js new file mode 100644 index 0000000000..efcc2cd8e6 --- /dev/null +++ b/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.js @@ -0,0 +1,33 @@ +/** + * @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 { dataSource } = require('../../../database/DataSource.js'); +const { LhcPeriodStatisticsRepository } = require('../../../database/repositories/index.js'); + +/** + * Find and return the statistics of the LHC periods (with the number of matching rows) + * + * @param {(function(QueryBuilder):void)|null} [qbConfiguration=null] function called with the statistics find query builder as parameter + * to add specific configuration to the query + * @return {Promise<{count: number|object[], rows: SequelizeLhcPeriodStatistics[]}>} resolves with the statistics found and their count. When + * the query is grouped (as the one prepared by the LHC period statistics service), the count is the list of the counts of each group + */ +exports.getAllLhcPeriodStatistics = (qbConfiguration = null) => { + const queryBuilder = dataSource.createQueryBuilder(); + + if (qbConfiguration) { + qbConfiguration(queryBuilder); + } + + return LhcPeriodStatisticsRepository.findAndCountAll(queryBuilder); +}; diff --git a/lib/server/services/lhcPeriod/getLhcPeriod.js b/lib/server/services/lhcPeriod/getLhcPeriod.js index a97ab73178..6b96e5538c 100644 --- a/lib/server/services/lhcPeriod/getLhcPeriod.js +++ b/lib/server/services/lhcPeriod/getLhcPeriod.js @@ -18,8 +18,9 @@ const LhcPeriodRepository = require('../../../database/repositories/LhcPeriodRep * Find and return a LHC period model by its id * * @param {number} lhcPeriodId the id of the LHC period to find - * @param {Function|null} qbConfiguration function called with the log find query builder as parameter to add specific configuration to the query - * @return {Promise} the LHC period found or null + * @param {Function|null} qbConfiguration function called with the LHC period find query builder as parameter to add specific configuration to + * the query + * @return {Promise} the LHC period found or null */ exports.getLhcPeriod = (lhcPeriodId, qbConfiguration = null) => { const queryBuilder = new QueryBuilder(); diff --git a/lib/server/services/lhcPeriod/getLhcPeriodStatistics.js b/lib/server/services/lhcPeriod/getLhcPeriodStatistics.js new file mode 100644 index 0000000000..b7834f7e77 --- /dev/null +++ b/lib/server/services/lhcPeriod/getLhcPeriodStatistics.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 { dataSource } = require('../../../database/DataSource.js'); +const { LhcPeriodStatisticsRepository } = require('../../../database/repositories/index.js'); +const { BadParameterError } = require('../../errors/BadParameterError.js'); + +/** + * @typedef LhcPeriodIdentifier object to uniquely identify a lhc period + * @property {string} [name] the lhc period name + * @property {number} [id] the id of lhc period + */ + +/** + * Find and return the statistics of an LHC period by the id or the name of the period + * + * @param {LhcPeriodIdentifier} identifier the identifier of the LHC period for which statistics must be found (the name is ignored if the id is + * present) + * @param {(function(QueryBuilder):void)|null} [qbConfiguration=null] function called with the statistics find query builder as parameter + * to add specific configuration to the query + * @return {Promise} resolves with the statistics found or null + */ +exports.getLhcPeriodStatistics = async ({ id, name }, qbConfiguration = null) => { + const queryBuilder = dataSource.createQueryBuilder(); + + if (id !== undefined && id !== null) { + queryBuilder.where('id').is(id); + } else if (name) { + queryBuilder.whereAssociation('lhcPeriod', 'name').is(name); + } else { + throw new BadParameterError('Can not find without LHC Period id or name'); + } + + if (qbConfiguration) { + qbConfiguration(queryBuilder); + } + + return await LhcPeriodStatisticsRepository.findOne(queryBuilder) ?? null; +}; diff --git a/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.js b/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.js new file mode 100644 index 0000000000..b5357f68e6 --- /dev/null +++ b/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.js @@ -0,0 +1,44 @@ +/** + * @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 { Op } = require('sequelize'); +const { LhcPeriodStatisticsRepository } = require('../../../database/repositories/index.js'); +const { RunDefinition } = require('../../../domain/enums/RunDefinition.js'); +const { RunQualities } = require('../../../domain/enums/RunQualities.js'); + +/** + * Return the ids of the statistics of the LHC periods having at least one good physics run of one of the given PDP beam types + * + * @param {string[]} pdpBeamTypes the PDP beam types + * @return {Promise} resolves with the ids of the statistics + */ +exports.getLhcPeriodStatisticsIdsByPdpBeamTypes = async (pdpBeamTypes) => { + const lhcPeriodsStatistics = await LhcPeriodStatisticsRepository.findAll({ + raw: true, + include: { + association: 'lhcPeriod', + required: true, + include: { + association: 'runs', + required: true, + where: { + pdpBeamType: { [Op.in]: pdpBeamTypes }, + definition: RunDefinition.PHYSICS, + runQuality: RunQualities.GOOD, + }, + }, + }, + }); + + return [...new Set(lhcPeriodsStatistics.map(({ id }) => id))]; +}; diff --git a/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.js b/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.js new file mode 100644 index 0000000000..8bb5997014 --- /dev/null +++ b/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.js @@ -0,0 +1,35 @@ +/** + * @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 { getLhcPeriodStatistics } = require('./getLhcPeriodStatistics.js'); +const { NotFoundError } = require('../../errors/NotFoundError.js'); + +/** + * Find the statistics of an LHC period by the id or the name of the period, and reject with a NotFoundError if there is none + * + * @param {LhcPeriodIdentifier} identifier the identifier of the LHC period for which statistics must be found (the name is ignored if the id is + * present) + * @param {(function(QueryBuilder):void)|null} [qbConfiguration=null] function called with the statistics find query builder as parameter + * to add specific configuration to the query + * @return {Promise} resolves with the statistics found or rejects with a NotFoundError + */ +exports.getLhcPeriodStatisticsOrFail = async (identifier, qbConfiguration = null) => { + const lhcPeriodStatistics = await getLhcPeriodStatistics(identifier, qbConfiguration); + if (lhcPeriodStatistics === null) { + const { id, name } = identifier; + const criteriaExpression = id !== undefined && id !== null ? `id (${id})` : `name (${name})`; + throw new NotFoundError(`LHC Period with this ${criteriaExpression} could not be found`); + } + + return lhcPeriodStatistics; +}; diff --git a/lib/server/services/lhcPeriod/getOrCreateLhcPeriod.js b/lib/server/services/lhcPeriod/getOrCreateLhcPeriod.js index 28007940b1..80562be910 100644 --- a/lib/server/services/lhcPeriod/getOrCreateLhcPeriod.js +++ b/lib/server/services/lhcPeriod/getOrCreateLhcPeriod.js @@ -16,7 +16,7 @@ const { LhcPeriodRepository } = require('../../../database/repositories'); /** * Find or create a lhc period model by its name or id * - * @param {LhcPeriodIdentifier} identifier the criteria to find run type - * @return {Promise} the run type found or null + * @param {LhcPeriodIdentifier} identifier the criteria to find the LHC period + * @return {Promise} the LHC period found or created */ exports.getOrCreateLhcPeriod = async (identifier) => await LhcPeriodRepository.findOneOrCreate(identifier); diff --git a/lib/server/services/period/getOrCreateLhcPeriod.js b/lib/server/services/period/getOrCreateLhcPeriod.js deleted file mode 100644 index 0a8dec8f79..0000000000 --- a/lib/server/services/period/getOrCreateLhcPeriod.js +++ /dev/null @@ -1,28 +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. - */ - -const { LhcPeriodRepository } = require('../../../database/repositories'); - -/** - * @typedef LhcPeriodIdentifier object to uniquely identify a lhc period - * @property {string} [name] the lhc period name - * @property {number} [id] the id of lhc period - */ - -/** - * Find or create a lhc period model by its name or id - * - * @param {LhcPeriodIdentifier} identifier the criteria to find run type - * @return {Promise} the run type found or null - */ -exports.getOrCreateLhcPeriod = async (identifier) => await LhcPeriodRepository.findOneOrCreate(identifier); diff --git a/test/api/lhcPeriodsStatistics.test.js b/test/api/lhcPeriodsStatistics.test.js index 8b3a78c2d4..209241490e 100644 --- a/test/api/lhcPeriodsStatistics.test.js +++ b/test/api/lhcPeriodsStatistics.test.js @@ -364,6 +364,21 @@ module.exports = () => { }); }); + it('should return 400 if the LHC period id is not a positive integer', async () => { + for (const [lhcPeriodId, message] of [ + ['abc', '"params.lhcPeriodId" must be a number'], + ['-1', '"params.lhcPeriodId" must be a positive number'], + ['0', '"params.lhcPeriodId" must be a positive number'], + ['0.5', '"params.lhcPeriodId" must be an integer'], + ]) { + const response = await request(server).get(`/api/lhcPeriodsStatistics/${lhcPeriodId}`); + + expect(response.status, `id ${lhcPeriodId}`).to.equal(400); + const error = response.body.errors.find(({ source: { pointer } }) => pointer === '/data/attributes/params/lhcPeriodId'); + expect(error.detail, `id ${lhcPeriodId}`).to.equal(message); + } + }); + it('should return 404 if lhc period could not be found', (done) => { request(server) .get('/api/lhcPeriodsStatistics/9999') diff --git a/test/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.test.js b/test/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.test.js new file mode 100644 index 0000000000..30582d7e44 --- /dev/null +++ b/test/lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.test.js @@ -0,0 +1,32 @@ +/** + * @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'); +const { getAllLhcPeriodStatistics } = require('../../../../../lib/server/services/lhcPeriod/getAllLhcPeriodStatistics.js'); + +module.exports = () => { + it('should successfully return the statistics of all the LHC periods with their count', async () => { + const { count, rows } = await getAllLhcPeriodStatistics(); + + expect(rows.map(({ id }) => id)).to.have.members([1, 2, 3]); + expect(count).to.equal(3); + }); + + it('should apply the given query builder configuration', async () => { + const { rows } = await getAllLhcPeriodStatistics((queryBuilder) => { + queryBuilder.where('id').is(2); + }); + + expect(rows.map(({ id }) => id)).to.eql([2]); + }); +}; diff --git a/test/lib/server/services/lhcPeriod/getLhcPeriodStatistics.test.js b/test/lib/server/services/lhcPeriod/getLhcPeriodStatistics.test.js new file mode 100644 index 0000000000..634c9394de --- /dev/null +++ b/test/lib/server/services/lhcPeriod/getLhcPeriodStatistics.test.js @@ -0,0 +1,42 @@ +/** + * @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'); +const assert = require('assert'); +const { getLhcPeriodStatistics } = require('../../../../../lib/server/services/lhcPeriod/getLhcPeriodStatistics.js'); +const { BadParameterError } = require('../../../../../lib/server/errors/BadParameterError.js'); + +module.exports = () => { + it('should successfully find the statistics of an LHC period by id', async () => { + expect((await getLhcPeriodStatistics({ id: 1 })).id).to.equal(1); + }); + + it('should successfully find the statistics of an LHC period by name', async () => { + expect((await getLhcPeriodStatistics({ name: 'LHC22b' })).id).to.equal(2); + }); + + it('should return null when no LHC period matches', async () => { + expect(await getLhcPeriodStatistics({ id: 99999 })).to.be.null; + expect(await getLhcPeriodStatistics({ name: 'LHC00z' })).to.be.null; + }); + + it('should apply the given query builder configuration', async () => { + const lhcPeriodStatistics = await getLhcPeriodStatistics({ id: 1 }, (queryBuilder) => queryBuilder.set('attributes', ['id'])); + + expect(Object.keys(lhcPeriodStatistics.get())).to.eql(['id']); + }); + + it('should reject when neither the id nor the name is given', async () => { + await assert.rejects(() => getLhcPeriodStatistics({}), new BadParameterError('Can not find without LHC Period id or name')); + }); +}; diff --git a/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.test.js b/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.test.js new file mode 100644 index 0000000000..d2ac73f5da --- /dev/null +++ b/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.test.js @@ -0,0 +1,32 @@ +/** + * @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'); +const { + getLhcPeriodStatisticsIdsByPdpBeamTypes, +} = require('../../../../../lib/server/services/lhcPeriod/getLhcPeriodStatisticsIdsByPdpBeamTypes.js'); + +module.exports = () => { + it('should successfully return the ids of the statistics of the LHC periods having runs of the given PDP beam types', async () => { + expect(await getLhcPeriodStatisticsIdsByPdpBeamTypes(['pp'])).to.eql([2]); + expect(await getLhcPeriodStatisticsIdsByPdpBeamTypes(['PbPb'])).to.eql([1]); + }); + + it('should return each id once when several PDP beam types match the same LHC period', async () => { + expect(await getLhcPeriodStatisticsIdsByPdpBeamTypes(['pp', 'PbPb', 'pp'])).to.have.members([1, 2]); + }); + + it('should return an empty list when no LHC period has runs of the given PDP beam types', async () => { + expect(await getLhcPeriodStatisticsIdsByPdpBeamTypes(['unknown'])).to.eql([]); + }); +}; diff --git a/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.test.js b/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.test.js new file mode 100644 index 0000000000..359903058c --- /dev/null +++ b/test/lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.test.js @@ -0,0 +1,38 @@ +/** + * @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'); +const assert = require('assert'); +const { getLhcPeriodStatisticsOrFail } = require('../../../../../lib/server/services/lhcPeriod/getLhcPeriodStatisticsOrFail.js'); +const { NotFoundError } = require('../../../../../lib/server/errors/NotFoundError.js'); + +module.exports = () => { + it('should successfully return the statistics of an LHC period', async () => { + expect((await getLhcPeriodStatisticsOrFail({ id: 1 })).id).to.equal(1); + expect((await getLhcPeriodStatisticsOrFail({ name: 'LHC22b' })).id).to.equal(2); + }); + + it('should fail when no LHC period has the given id', async () => { + await assert.rejects( + () => getLhcPeriodStatisticsOrFail({ id: 99999 }), + new NotFoundError('LHC Period with this id (99999) could not be found'), + ); + }); + + it('should fail when no LHC period has the given name', async () => { + await assert.rejects( + () => getLhcPeriodStatisticsOrFail({ name: 'LHC00z' }), + new NotFoundError('LHC Period with this name (LHC00z) could not be found'), + ); + }); +}; diff --git a/test/lib/server/services/lhcPeriod/index.js b/test/lib/server/services/lhcPeriod/index.js index 510fb124e3..b709bc09ee 100644 --- a/test/lib/server/services/lhcPeriod/index.js +++ b/test/lib/server/services/lhcPeriod/index.js @@ -13,8 +13,16 @@ const getOrCreateLhcPeriodTest = require('./getOrCreateLhcPeriod.test'); const LhcPeriodStatisticsService = require('./LhcPeriodStatisticsService.test'); +const getLhcPeriodStatisticsTest = require('./getLhcPeriodStatistics.test.js'); +const getLhcPeriodStatisticsOrFailTest = require('./getLhcPeriodStatisticsOrFail.test.js'); +const getAllLhcPeriodStatisticsTest = require('./getAllLhcPeriodStatistics.test.js'); +const getLhcPeriodStatisticsIdsByPdpBeamTypesTest = require('./getLhcPeriodStatisticsIdsByPdpBeamTypes.test.js'); module.exports = () => { describe('getOrCreateLhcPeriodTest', getOrCreateLhcPeriodTest); describe('LhcPeriodStatisticsService', LhcPeriodStatisticsService); + describe('getLhcPeriodStatistics', getLhcPeriodStatisticsTest); + describe('getLhcPeriodStatisticsOrFail', getLhcPeriodStatisticsOrFailTest); + describe('getAllLhcPeriodStatistics', getAllLhcPeriodStatisticsTest); + describe('getLhcPeriodStatisticsIdsByPdpBeamTypes', getLhcPeriodStatisticsIdsByPdpBeamTypesTest); };