From e981b7a0ccdf6a60c2ac907b1111627c3db894f9 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Wed, 11 Feb 2026 15:56:09 +0100 Subject: [PATCH 1/8] chore: add runfilterDTO beamMode patern --- lib/domain/dtos/filters/RunFilterDto.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index 2dc9ce98ad..54240b1935 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -32,6 +32,9 @@ const EorReasonFilterDto = Joi.object({ }); exports.RunFilterDto = Joi.object({ + beamModes: Joi.string().trim().pattern(/^[A-Z]+(?: [A-Z]+)*$/).messages({ + 'string.pattern.base': 'Beam modes must contain only uppercase letters and single spaces between words.' + }), 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)', From 43c811d50478a688ed0d237bf512cca8cb27358b Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Wed, 11 Feb 2026 16:02:35 +0100 Subject: [PATCH 2/8] chore: add beamModes to getAllRunsUsecase --- lib/usecases/run/GetAllRunsUseCase.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index d25762ad00..6724258098 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -82,6 +82,7 @@ class GetAllRunsUseCase { inelasticInteractionRateAtEnd, gaq, detectorsQc, + beamModes } = filter; if (runNumbers) { @@ -113,6 +114,10 @@ class GetAllRunsUseCase { filteringQueryBuilder.where('definition').oneOf(...definitions); } + if (beamModes) { + filteringQueryBuilder.where('lhcBeamMode').oneOf(...definitions); + } + if (eorReason) { const eorReasonTypeWhere = {}; if (eorReason.category) { From f1d366baa628204bef9a283a95f0f54b2bba357e Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 09:10:36 +0100 Subject: [PATCH 3/8] update dto --- lib/domain/dtos/filters/RunFilterDto.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index 54240b1935..7439c07f12 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -32,9 +32,15 @@ const EorReasonFilterDto = Joi.object({ }); exports.RunFilterDto = Joi.object({ - beamModes: Joi.string().trim().pattern(/^[A-Z]+(?: [A-Z]+)*$/).messages({ - 'string.pattern.base': 'Beam modes must contain only uppercase letters and single spaces between words.' - }), + beamModes: CustomJoi.stringArray().items( + Joi.string() + .trim() + .pattern(/^[A-Z]+(?: [A-Z]+)*$/) + .messages({ + 'string.pattern.base': + 'Beam modes "{{#value}}" must contain only uppercase letters and single spaces between words.', + }) + ), 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)', From 01706582d925e045b2b9c1af6454732bc5fac776 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 09:11:09 +0100 Subject: [PATCH 4/8] use beammodes in getallRunsUseCase instead of definitions --- lib/usecases/run/GetAllRunsUseCase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index 6724258098..ee964cb21d 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -115,7 +115,7 @@ class GetAllRunsUseCase { } if (beamModes) { - filteringQueryBuilder.where('lhcBeamMode').oneOf(...definitions); + filteringQueryBuilder.where('lhcBeamMode').oneOf(...beamModes); } if (eorReason) { From 6fd6bfdccb277dbe8c43ad1a016aa6b5022652a1 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 09:45:37 +0100 Subject: [PATCH 5/8] add runs api test --- test/api/runs.test.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/api/runs.test.js b/test/api/runs.test.js index 4322040bb3..083771bf02 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -142,6 +142,37 @@ module.exports = () => { expect(runs).to.lengthOf(20); }); + it('should successfully filter with single beamMode', async () => { + const response = await request(server).get('/api/runs?filter[beamModes]=STABLE BEAMS'); + + expect(response.status).to.equal(200); + const { data: runs } = response.body; + + expect(runs).to.lengthOf(5); + }); + + it('should successfully filter with multiple beamModes', async () => { + const response = await request(server).get('/api/runs?filter[beamModes]=STABLE BEAMS,NO BEAM'); + + expect(response.status).to.equal(200); + const { data: runs } = response.body; + + expect(runs).to.lengthOf(6); + }); + + 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}`); + + + expect(response.status).to.equal(400); + + const { errors: [error] } = response.body; + + expect(error.title).to.equal('Invalid Attribute'); + expect(error.detail).to.equal(`Beam modes "${beamModeString}" must contain only uppercase letters and single spaces between words.`); + }); + it('should successfully filter on multiple specified run numbers', async () => { const response = await request(server).get('/api/runs?filter[runNumbers]=17,18'); From 7364379394fdf3be3ea1cf0dad00315bd143e85e Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 10:27:29 +0100 Subject: [PATCH 6/8] add usecase tests --- .../usecases/run/GetAllRunsUseCase.test.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/lib/usecases/run/GetAllRunsUseCase.test.js b/test/lib/usecases/run/GetAllRunsUseCase.test.js index 55e5551485..5b080d056c 100644 --- a/test/lib/usecases/run/GetAllRunsUseCase.test.js +++ b/test/lib/usecases/run/GetAllRunsUseCase.test.js @@ -183,6 +183,32 @@ module.exports = () => { } }); + it('should successfully filter on beamModes', async () => { + const singleBeamMode = ['STABLE BEAMS']; + const multipleBeamMode = ['STABLE BEAMS', 'NO BEAM']; + const nonExistentBeamMode = ['DOES NOT EXIST']; + + getAllRunsDto.query = { filter: { beamModes: singleBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(5); + expect(runs.every(({ lhcBeamMode }) => singleBeamMode.includes(lhcBeamMode))).to.be.true; + } + + getAllRunsDto.query = { filter: { beamModes: multipleBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(6); + expect(runs.every(({ lhcBeamMode }) => multipleBeamMode.includes(lhcBeamMode))).to.be.true; + } + + getAllRunsDto.query = { filter: { beamModes: nonExistentBeamMode } }; + { + const { runs } = await new GetAllRunsUseCase().execute(getAllRunsDto); + expect(runs).to.have.lengthOf(0); + } + }); + it('should successfully filter on run definition', async () => { const PHYSICS_COUNT = 7; const COSMICS_COUNT = 2; From 02128b42c8166b181f7a0d7c523d06fda84cc0cd Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 10:38:53 +0100 Subject: [PATCH 7/8] add trailing comma --- lib/usecases/run/GetAllRunsUseCase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/usecases/run/GetAllRunsUseCase.js b/lib/usecases/run/GetAllRunsUseCase.js index ee964cb21d..df1b5f7f5b 100644 --- a/lib/usecases/run/GetAllRunsUseCase.js +++ b/lib/usecases/run/GetAllRunsUseCase.js @@ -82,7 +82,7 @@ class GetAllRunsUseCase { inelasticInteractionRateAtEnd, gaq, detectorsQc, - beamModes + beamModes, } = filter; if (runNumbers) { From 43e5861fa5e421085c4a4d37d279e60787daf975 Mon Sep 17 00:00:00 2001 From: GuustMetz Date: Thu, 12 Feb 2026 10:44:49 +0100 Subject: [PATCH 8/8] fix lint issues --- lib/domain/dtos/filters/RunFilterDto.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/domain/dtos/filters/RunFilterDto.js b/lib/domain/dtos/filters/RunFilterDto.js index 7439c07f12..93f3d5e238 100644 --- a/lib/domain/dtos/filters/RunFilterDto.js +++ b/lib/domain/dtos/filters/RunFilterDto.js @@ -32,15 +32,13 @@ const EorReasonFilterDto = Joi.object({ }); exports.RunFilterDto = Joi.object({ - beamModes: CustomJoi.stringArray().items( - Joi.string() + beamModes: CustomJoi.stringArray().items(Joi.string() .trim() .pattern(/^[A-Z]+(?: [A-Z]+)*$/) .messages({ 'string.pattern.base': 'Beam modes "{{#value}}" must contain only uppercase letters and single spaces between words.', - }) - ), + })), 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)',