Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
df64691
Add test suite to tests that are to be ran
graduta Aug 4, 2026
71cea32
Improve docs and constant usage on beamTypesDto
graduta Aug 4, 2026
93e0f96
Add test for digit check in pdpBeamType
graduta Aug 4, 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
53 changes: 44 additions & 9 deletions lib/domain/dtos/common/BeamTypeDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@
const Joi = require('joi');
const { CustomJoi } = require('../CustomJoi.js');

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;

/**
* 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 PDP_BEAM_TYPE_PATTERN = new RegExp(`^[A-Za-z]{${PDP_BEAM_TYPE_MIN_LENGTH},${PDP_BEAM_TYPE_MAX_LENGTH}}$`);

/**
* @typedef {string[]} BeamTypesDto
* @description An array of beam types, each represented as a string.
Expand All @@ -26,20 +41,40 @@ const { CustomJoi } = require('../CustomJoi.js');
* "O8 - O8"
* "PB82 - PB82"
* "PROTON - O8"
* "PROTON - PROTON"
*
* @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 PDP beam types represented as strings.
* RUN3 has the following pdp beam types:
* "pp"
* "pPb"
* "NeNe"
* "OO"
* "cosmic"
* "technical"
*/
exports.PdpBeamTypesDto = CustomJoi.stringArray()
.items(Joi.string()
.trim()
.min(PDP_BEAM_TYPE_MIN_LENGTH)
.max(PDP_BEAM_TYPE_MAX_LENGTH)
.pattern(PDP_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
76 changes: 75 additions & 1 deletion test/api/runs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,62 @@ 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 () => {
Comment thread
isaachilly marked this conversation as resolved.
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`);

const pdpBeamTypeWithDigits = 'pp1';
const responseWithDigits = await request(server).get(`/api/runs?filter[pdpBeamTypes]=${pdpBeamTypeWithDigits}`);

expect(responseWithDigits.status).to.equal(400);
({ errors: [error] } = responseWithDigits.body);

expect(error.title).to.equal('Invalid Attribute');
expect(error.detail).to.equal(`PDP beam type must look like "pp", "pPb", "NeNe", "OO", "cosmic", "technical", etc.`);

});

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 +783,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 +795,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
2 changes: 2 additions & 0 deletions test/lib/server/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
const { resetDatabaseContent } = require('../../../utilities/resetDatabaseContent.js');

const BeamModeSuite = require('./beamMode/index.js');
const PdpBeamTypesSuite = require('./pdpBeamTypes/index.js');
const DetectorSuite = require('./detector/index.js');
const DplSuite = require('./dpl/index.js');
const Environment = require('./environment/index.js');
Expand All @@ -40,6 +41,7 @@ module.exports = () => {
after(resetDatabaseContent);

describe('BeamMode', BeamModeSuite);
describe('PdpBeamTypesSuite', PdpBeamTypesSuite);
describe('Detector', DetectorSuite);
describe('DPL', DplSuite);
describe('Environment', Environment);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { 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: 'pp' },
{ name: 'PbPb' },
{ name: 'technical' },
{ name: 'cosmic' },
{ name: 'OO' },
]);
});
};
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 = () => {
Comment thread
isaachilly marked this conversation as resolved.
describe('getAllPdpBeamTypes', getAllPdpBeamTypes);
};
Loading