Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1e8d30e
Fix spelling mistake
graduta Jul 31, 2026
ded1c12
Update beam type dto to split and return array of values
graduta Jul 31, 2026
15fdc9c
Remove unused utility and update tests
graduta Jul 31, 2026
b9334a3
Fix API tests
graduta Jul 31, 2026
67dd8a3
Push also changes related to Runs as are using the BeamTypesDto
graduta Jul 31, 2026
33cf287
FIx GetAllRuns tests
graduta Jul 31, 2026
1758654
Add endpoint for retrieving all unique pdpBeamTypes
graduta Jul 31, 2026
a1273f2
Add runs endpoint option to filter by pdpBeamType
graduta Jul 31, 2026
2d90f63
Fix spelling mistake
graduta Jul 31, 2026
037e703
Update beam type dto to split and return array of values
graduta Jul 31, 2026
7fec84a
Remove unused utility and update tests
graduta Jul 31, 2026
f1c60b4
Fix API tests
graduta Jul 31, 2026
a8e5a47
Push also changes related to Runs as are using the BeamTypesDto
graduta Jul 31, 2026
bde60d6
FIx GetAllRuns tests
graduta Jul 31, 2026
bab5e09
Provide more details to beam type format
graduta Aug 1, 2026
f051789
Fix test for error message
graduta Aug 1, 2026
8b135f0
Add endpoint for retrieving all unique pdpBeamTypes
graduta Jul 31, 2026
82a202d
Add runs endpoint option to filter by pdpBeamType
graduta Jul 31, 2026
97faefa
Merge branch 'feature/O2B-1602-add-pdp-beam-type-filter-back-end' of …
graduta Aug 3, 2026
9c15437
Merge branch 'main' of github.com:AliceO2Group/Bookkeeping into featu…
graduta Aug 3, 2026
d229f01
Reuse beamtype dto for pdpbeamtype as well
graduta Aug 3, 2026
1600a2d
Split the DTOs to provide easier maintainability
graduta Aug 3, 2026
2664829
Fix min limit of beam type
graduta Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 47 additions & 7 deletions lib/domain/dtos/common/BeamTypeDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,25 @@
const Joi = require('joi');
const { CustomJoi } = require('../CustomJoi.js');

/**
* Used by Bookkeeping-LHC Plugin to send data as per LHC
*/
const DASH_SEPARATED_BEAM_TYPE_PATTERN = /^[A-Za-z0-9]{1,6} ?- ?[A-Za-z0-9]{1,6}$/;

/**
* User Input format declared by user at deployment of environment
*/
const COMPACT_BEAM_TYPE_PATTERN = /^[A-Za-z]{2,15}$/;

const BEAM_TYPE_MIN_LENGTH = 3;
const BEAM_TYPE_MAX_LENGTH = 15;
const PDP_BEAM_TYPE_MIN_LENGTH = 2;
const PDP_BEAM_TYPE_MAX_LENGTH = 10;

/**
* @typedef {string[]} BeamTypesDto
* @description An array of beam types, each represented as a string.
* Each beam type must be a string with a minimum length of 3 characters and a maximum length of 15 characters.
* Each beam type must be a string with a minimum length of 2 characters and a maximum length of 15 characters.
* The string must match patterns such as "PROTON - PROTON", "NE10 - NE10", where the two parts are separated by a hyphen and optional spaces.
*
* RUN3 has the following beam types:
Expand All @@ -26,20 +41,45 @@ const { CustomJoi } = require('../CustomJoi.js');
* "O8 - O8"
* "PB82 - PB82"
* "PROTON - O8"
* "PROTON - PROTON"
*
* RUN3 has the following compact beam types:
* "pp"
* "pPb"
* "NeNe"
* "OO"
* "cosmic"
* "technical"
*
* @example
* const beamTypes = ["PROTON - PROTON", "NE10 - NE10"];
*/
exports.BeamTypesDto = CustomJoi.stringArray()
.items(Joi.string()
.trim()
.min(3)
.max(15)
.pattern(/^[A-Za-z0-9]+ ?- ?[A-Za-z0-9]+$/)
.min(BEAM_TYPE_MIN_LENGTH)
.max(BEAM_TYPE_MAX_LENGTH)
.pattern(DASH_SEPARATED_BEAM_TYPE_PATTERN)
.messages({
'string.base': 'Beam type must be a string',
'string.min': 'Beam type must be at least 3 characters long',
'string.max': 'Beam type must be at most 15 characters long',
'string.min': `Beam type must be at least ${BEAM_TYPE_MIN_LENGTH} characters long`,
'string.max': `Beam type must be at most ${BEAM_TYPE_MAX_LENGTH} characters long`,
'string.pattern.base': 'Beam type must look like "PROTON - PROTON", "NE10 - NE10", etc.',
}));

/**
* @typedef {string[]} PdpBeamTypesDto
* @description An array of compact PDP beam types represented as strings.
* Values can include examples like "pp", "pPb", "NeNe", "OO", "cosmic", and "technical".
*/
exports.PdpBeamTypesDto = CustomJoi.stringArray()
.items(Joi.string()
.trim()
.min(PDP_BEAM_TYPE_MIN_LENGTH)
.max(PDP_BEAM_TYPE_MAX_LENGTH)
.pattern(COMPACT_BEAM_TYPE_PATTERN)
.messages({
'string.base': 'PDP beam type must be a string',
'string.min': `PDP beam type must be at least ${PDP_BEAM_TYPE_MIN_LENGTH} characters long`,
'string.max': `PDP beam type must be at most ${PDP_BEAM_TYPE_MAX_LENGTH} characters long`,
'string.pattern.base': 'PDP beam type must look like "pp", "pPb", "NeNe", "OO", "cosmic", "technical", etc.',
}));
3 changes: 2 additions & 1 deletion lib/domain/dtos/filters/RunFilterDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
const Joi = require('joi');
const { CustomJoi } = require('../CustomJoi.js');
const { BeamTypesDto } = require('../common/BeamTypeDto.js');
const { BeamTypesDto, PdpBeamTypesDto } = require('../common/BeamTypeDto.js');
const { FromToFilterDto } = require('./FromToFilterDto.js');
const { RUN_QUALITIES } = require('../../enums/RunQualities.js');
const { IntegerComparisonDto, FloatComparisonDto } = require('./NumericalComparisonDto.js');
Expand Down Expand Up @@ -41,6 +41,7 @@ exports.RunFilterDto = Joi.object({
'Beam modes "{{#value}}" must contain only uppercase letters and single spaces between words.',
})),
beamTypes: BeamTypesDto,
pdpBeamTypes: PdpBeamTypesDto,
runNumbers: Joi.string().trim().custom(validateRange).messages({
[RANGE_INVALID]: '{{#message}}',
'string.base': 'Run numbers must be comma-separated numbers or ranges (e.g. 12,15-18)',
Expand Down
26 changes: 26 additions & 0 deletions lib/server/controllers/runs.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const { ApiConfig } = require('../../config/index.js');
const { DtoFactory } = require('../../domain/dtos/DtoFactory.js');
const { runService } = require('../services/run/RunService.js');
const { getAllBeamModes } = require('../services/beam/getAllBeamModes.js');
const { getAllPdpBeamTypes } = require('../services/beam/getAllPdpBeamTypes.js');
const { updateExpressResponseFromNativeError } = require('../express/updateExpressResponseFromNativeError.js');
const { runToHttpView } = require('./runsToHttpView.js');

Expand Down Expand Up @@ -321,6 +322,30 @@ const listBeamModes = async (_request, response, _next) => {
}
};

/**
* Retrieve a list of unique PDP beam 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.
* @param {NextFunction} _next The *next* object represents the next middleware function which is used to pass control to
* the next middleware function.
* @returns {undefined}
*/
const listPdpBeamTypes = async (_request, response, _next) => {
try {
const pdpBeamTypes = await getAllPdpBeamTypes();
if (pdpBeamTypes?.length > 0) {
response.status(200).json({ data: pdpBeamTypes });
} else {
response.status(204).json({ data: [] });
}
} catch {
response.status(502).json({ errors: ['Unable to retrieve list of PDP beam types'] });
}
};

// eslint-disable-next-line jsdoc/require-param
/**
* Retrieve distinct combination of levels of alice L3 and dipole current rounded to kilo amperes
Expand Down Expand Up @@ -348,6 +373,7 @@ module.exports = {
getFlpsByRunNumberHandler,
listReasonTypes,
listBeamModes,
listPdpBeamTypes,
listRuns,
startRun,
updateRunByRunNumber,
Expand Down
5 changes: 5 additions & 0 deletions lib/server/routers/runs.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ module.exports = {
path: 'beamModes',
controller: RunsController.listBeamModes,
},
{
method: 'get',
path: 'pdpBeamTypes',
controller: RunsController.listPdpBeamTypes,
},
{
method: 'get',
controller: [infoLoggerListenerMiddleware(FilterLogger), RunsController.listRuns],
Expand Down
32 changes: 32 additions & 0 deletions lib/server/services/beam/getAllPdpBeamTypes.js
Original file line number Diff line number Diff line change
@@ -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 { repositories: { RunRepository }, sequelize } = require('../../../database');
const { Op } = require('sequelize');

/**
* Return the a list of unique PDP beam types which is built from the runs data
*
* @returns {Promise<SequelizePdpBeamType[]>} Promise resolving with the list of unique PDP beam types
*/
exports.getAllPdpBeamTypes = async () => {
const pdpBeamTypes = await RunRepository.findAll({
where: {
pdp_beam_type: {
[Op.ne]: null,
},
},
attributes: [[sequelize.fn('DISTINCT', sequelize.col('pdp_beam_type')), 'name']],
});
return pdpBeamTypes;
};
5 changes: 5 additions & 0 deletions lib/usecases/run/GetAllRunsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class GetAllRunsUseCase {
detectorsQcNotBadFraction,
beamModes,
beamTypes,
pdpBeamTypes,
} = filter;

if (runNumbers) {
Expand Down Expand Up @@ -120,6 +121,10 @@ class GetAllRunsUseCase {
filteringQueryBuilder.where('lhcBeamMode').oneOf(...beamModes);
}

if (pdpBeamTypes) {
filteringQueryBuilder.where('pdpBeamType').oneOf(...pdpBeamTypes);
}

if (beamTypes) {
filteringQueryBuilder.include({
association: 'lhcFill',
Expand Down
66 changes: 65 additions & 1 deletion test/api/runs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,52 @@ module.exports = () => {
expect(error.detail).to.equal(`Beam type must look like "PROTON - PROTON", "NE10 - NE10", etc.`);
});

it('should successfully filter runs with pdpBeamType', async () => {
const pdpBeamType = 'pp';
const response = await request(server).get(`/api/runs?filter[pdpBeamTypes]=${pdpBeamType}`);

expect(response.status).to.equal(200);
const { data: runs } = response.body;

expect(runs).to.be.an('array');
expect(runs).to.have.lengthOf(6);
expect(runs.every(({ pdpBeamType: type }) => type === pdpBeamType)).to.be.true;
});

it('should successfully filter runs with multiple pdpBeamTypes', async () => {
const pdpBeamTypes = 'pp,PbPb';
const response = await request(server).get(`/api/runs?filter[pdpBeamTypes]=${pdpBeamTypes}`);

expect(response.status).to.equal(200);
const { data: runs } = response.body;

expect(runs).to.be.an('array');
expect(runs).to.have.lengthOf(10);
expect(runs.every(({ pdpBeamType: type }) => pdpBeamTypes.includes(type))).to.be.true;
});

it('should return 400 if pdpBeamTypes filter has the incorrect format', async () => {
const pdpBeamTypes = 'S'; // Too short
const response = await request(server).get(`/api/runs?filter[pdpBeamTypes]=${pdpBeamTypes}`);

expect(response.status).to.equal(400);

let { errors: [error] } = response.body;

expect(error.title).to.equal('Invalid Attribute');
expect(error.detail).to.equal(`PDP beam type must be at least 2 characters long`);

const pdpBeamTypesLong = 'This is definitely not a pdp beam type'; // Too long
const responseLong = await request(server).get(`/api/runs?filter[pdpBeamTypes]=${pdpBeamTypesLong}`);

expect(responseLong.status).to.equal(400);

({ errors: [error] } = responseLong.body);

expect(error.title).to.equal('Invalid Attribute');
expect(error.detail).to.equal(`PDP beam type must be at most 10 characters long`);
});

it('should return 400 if beamModes filter has the incorrect format', async () => {
const beamModeString = '*THERE\'S NON LETTERS IN HERE';
const response = await request(server).get(`/api/runs?filter[beamModes]=${beamModeString}`);
Expand Down Expand Up @@ -727,7 +773,6 @@ module.exports = () => {
});
});


describe('GET /api/runs/beamModes', () => {
it('should successfully return status 200 and list of beam modes', async () => {
const { body } = await request(server)
Expand All @@ -740,6 +785,25 @@ module.exports = () => {
expect(body.data[0].name).to.equal('STABLE BEAMS');
});
});

describe('GET /api/runs/pdpBeamTypes', () => {
it('should successfully return status 200 and list of pdp beam types', async () => {
const { body } = await request(server)
.get('/api/runs/pdpBeamTypes')
.expect(200);

expect(body.data).to.be.an('array');
expect(body.data).to.have.lengthOf(5);
expect(body.data).to.deep.equal([
{ name: 'pp' },
{ name: 'PbPb' },
{ name: 'technical' },
{ name: 'cosmic' },
{ name: 'OO' },
]);
});
});

describe('GET /api/runs/reasonTypes', () => {
it('should successfully return status 200 and list of reason types', async () => {
const { body } = await request(server)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/

const { expect } = require('chai');
const { getAllPdpBeamTypes } = require('../../../../../lib/server/services/beam/getAllPdpBeamTypes.js');

module.exports = () => {
it('should successfully return the full list of not null PDP beam types from runs table', async () => {
const pdpBeamTypes = await getAllPdpBeamTypes();
expect(pdpBeamTypes.map(({ dataValues: { name } }) => ({ name }))).to.deep.eq([
{ name: 'cosmic' },
{ name: 'technical' },
{ name: 'pp' },
{ name: 'PbPb' },
{ name: 'pO' },
{ name: 'OO' },
{ name: 'NeNe' },
]);
});
};
18 changes: 18 additions & 0 deletions test/lib/server/services/pdpBeamTypes/index.js
Original file line number Diff line number Diff line change
@@ -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.
*/

const getAllPdpBeamTypes = require('./getAllPdpBeamTypes.test.js');

module.exports = () => {
describe('getAllPdpBeamTypes', getAllPdpBeamTypes);
};
Loading