From ff38a6e56d1c609e33e53ef9f9651270ba218a0a Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Oct 2026 11:03:06 +0200 Subject: [PATCH 1/2] [O2B-1630] Fix issue where in embeded profiles, some logs properties were not returned --- lib/database/repositories/LogRepository.js | 19 +-------- lib/server/services/log/LogService.js | 26 ++++++++++-- lib/usecases/run/GetLogsByRunUseCase.js | 16 ++------ test/api/environments.test.js | 8 ++++ test/api/lhcFills.test.js | 8 ++++ test/api/runs.test.js | 6 +++ test/api/tags.test.js | 15 +++++++ .../server/services/log/LogService.test.js | 40 +++++++++++++++++++ 8 files changed, 105 insertions(+), 33 deletions(-) diff --git a/lib/database/repositories/LogRepository.js b/lib/database/repositories/LogRepository.js index 58e7cfd6d4..3a16664856 100644 --- a/lib/database/repositories/LogRepository.js +++ b/lib/database/repositories/LogRepository.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const { models: { Log }, sequelize, utilities: { QueryBuilder } } = require('../'); +const { models: { Log }, sequelize } = require('../'); const Repository = require('./Repository'); const { timestampToMysql } = require('../../server/utilities/timestampToMysql.js'); @@ -93,23 +93,6 @@ class LogRepository extends Repository { return rows; } - /** - * Returns all entities. - * - * @param {Object} runId The QueryBuilder to use. - * @param {QueryBuilder} queryBuilder The QueryBuilder to use. - * @returns {Promise} Promise object representing the full mock data - */ - async findAllByRunId(runId, queryBuilder = new QueryBuilder()) { - queryBuilder - .setModel(Log) - .include('user') - .include('tags') - .whereAssociation('runs', 'id').is(runId); - - return this.findAll(queryBuilder); - } - /** * Returns the list of tags and their occurrences in the logs created in the given period * diff --git a/lib/server/services/log/LogService.js b/lib/server/services/log/LogService.js index 4a1049f954..16e0a52e0b 100644 --- a/lib/server/services/log/LogService.js +++ b/lib/server/services/log/LogService.js @@ -14,7 +14,7 @@ const { createLog } = require('./createLog.js'); const { getLog } = require('./getLog.js'); const { logAdapter, attachmentAdapter, tagAdapter } = require('../../../database/adapters/index.js'); -const { repositories: { LogRepository, LogLhcFillsRepository } } = require('../../../database'); +const { repositories: { LogRepository, LogLhcFillsRepository, LogRunsRepository } } = require('../../../database'); const { dataSource } = require('../../../database/DataSource.js'); const { LogEnvironmentsRepository } = require('../../../database/repositories/index.js'); const { getLogsByTagsInPeriod } = require('./getLogsByTagsInPeriod.js'); @@ -119,7 +119,26 @@ class LogService { } /** - * Returns all the logs related to a given LHC fill + * Returns all the logs related to a given run, with all their relations (and not only the given run) + * + * @param {number} runId the id of the run for which logs must be returned + * @return {Promise} the resulting logs + */ + async getAllByRun(runId) { + const logRunQueryBuilder = dataSource.createQueryBuilder() + .include({ + association: 'run', + where: { id: runId }, + }); + + const logRuns = await LogRunsRepository.findAll(logRunQueryBuilder); + + const logIds = logRuns.map((logRun) => logRun.logId); + return this._getAllByIds(logIds); + } + + /** + * Returns all the logs related to a given environment * * @param {string} environmentId the id of the environment for which logs must be returned * @return {Promise} the resulting logs @@ -189,7 +208,8 @@ class LogService { .include({ association: 'dataPasses', attributes: ['id', 'name'] }) .include({ association: 'environments', attributes: ['id'] }) .include({ association: 'user', attributes: ['name'] }) - .include({ association: 'tags', attributes: ['text'] }); + .include({ association: 'tags', attributes: ['text'] }) + .include('attachments'); } /** diff --git a/lib/usecases/run/GetLogsByRunUseCase.js b/lib/usecases/run/GetLogsByRunUseCase.js index 2f77190d86..66e7105e83 100644 --- a/lib/usecases/run/GetLogsByRunUseCase.js +++ b/lib/usecases/run/GetLogsByRunUseCase.js @@ -13,7 +13,6 @@ const { repositories: { - LogRepository, RunRepository, }, utilities: { @@ -21,7 +20,7 @@ const { TransactionHelper, }, } = require('../../database'); -const { logAdapter } = require('../../database/adapters/index.js'); +const { logService } = require('../../server/services/log/LogService.js'); /** * GetLogsByRunUseCase @@ -31,7 +30,7 @@ class GetLogsByRunUseCase { * Executes this use case. * * @param {Object} dto The GetRunDto containing all data. - * @returns {Promise} Promise object represents the result of this use case. + * @returns {Promise} Promise object represents the logs of the run, or null if the run does not exist. */ async execute(dto) { const queryBuilder = new QueryBuilder(); @@ -39,17 +38,10 @@ class GetLogsByRunUseCase { queryBuilder.where('runNumber').is(runNumber); - const logs = await TransactionHelper.provide(async () => { + return TransactionHelper.provide(async () => { const run = await RunRepository.findOne(queryBuilder); - return run ? LogRepository.findAllByRunId(run.id) : null; + return run ? logService.getAllByRun(run.id) : null; }); - - if (logs) { - const logsWithReplies = await LogRepository.addChildrenCountByLog(logs); - return logsWithReplies.map(logAdapter.toEntity); - } else { - return null; - } } } diff --git a/test/api/environments.test.js b/test/api/environments.test.js index 009cf0df17..4c573c3e1b 100644 --- a/test/api/environments.test.js +++ b/test/api/environments.test.js @@ -342,6 +342,14 @@ module.exports = () => { const response = await request(server).get('/api/environments/8E4aZTjY/logs'); expect(response.status).to.equal(200); expect(response.body.data).to.lengthOf(3); + + // Log 1 is linked to all kind of entities, which must be returned for logs displayed in embedded tables + const log = response.body.data.find(({ id }) => id === 1); + expect(log.runs).to.deep.equal([{ id: 1, runNumber: 1 }]); + expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); + expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); + expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + expect(log.attachments).to.be.an('array'); }); }); }; diff --git a/test/api/lhcFills.test.js b/test/api/lhcFills.test.js index ff3fd3427f..adb661ae45 100644 --- a/test/api/lhcFills.test.js +++ b/test/api/lhcFills.test.js @@ -957,6 +957,14 @@ module.exports = () => { const response = await request(server).get('/api/lhcFills/6/logs'); expect(response.status).to.equal(200); expect(response.body.data).to.lengthOf(2); + + // Log 1 is linked to all kind of entities, which must be returned for logs displayed in embedded tables + const log = response.body.data.find(({ id }) => id === 1); + expect(log.runs).to.deep.equal([{ id: 1, runNumber: 1 }]); + expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); + expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); + expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + expect(log.attachments).to.be.an('array'); }); }); diff --git a/test/api/runs.test.js b/test/api/runs.test.js index 5172a98bf9..bc6b1d5de9 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -1066,6 +1066,12 @@ module.exports = () => { runNumber: 1, }, ]); + // Relations used by the embedded logs table of the run details page must be returned too + const log = res.body.data[0]; + expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); + expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); + expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + expect(log.attachments).to.be.an('array'); done(); }); }); diff --git a/test/api/tags.test.js b/test/api/tags.test.js index 5924ff8f0d..7cd52b2837 100644 --- a/test/api/tags.test.js +++ b/test/api/tags.test.js @@ -473,6 +473,21 @@ module.exports = () => { }); }); }); + describe('GET /api/tags/:tagId/logs relations', () => { + it('should return the relations displayed in the embedded logs table', async () => { + const response = await request(server).get('/api/tags/3/logs'); + expect(response.status).to.equal(200); + + // Log 1 is linked to all kind of entities, which must be returned for logs displayed in embedded tables + const log = response.body.data.find(({ id }) => id === 1); + expect(log.runs).to.deep.equal([{ id: 1, runNumber: 1 }]); + expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); + expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); + expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + expect(log.attachments).to.be.an('array'); + }); + }); + describe('GET /api/tags/name', () => { it('should return 200 if a valid query is given', (done) => { request(server) diff --git a/test/lib/server/services/log/LogService.test.js b/test/lib/server/services/log/LogService.test.js index 76dd93eaf8..f658d188de 100644 --- a/test/lib/server/services/log/LogService.test.js +++ b/test/lib/server/services/log/LogService.test.js @@ -103,4 +103,44 @@ module.exports = () => { expect(childLogs.length).to.equal(expectedAmountOfChildren); }); + + it('Should successfully return all the logs of a run, with all their relations', async () => { + const logs = await logService.getAllByRun(1); + + expect(logs.map(({ id }) => id)).to.include(1); + const log = logs.find(({ id }) => id === 1); + expect(log.runs).to.deep.equal([{ id: 1, runNumber: 1 }]); + expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); + expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); + expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + expect(log.tags).to.not.be.empty; + expect(log.author).to.not.be.null; + }); + + it('Should successfully return the attachments of logs linked to a run, an LHC fill or an environment', async () => { + const { id: logId } = await logService.create( + logMock, + [1], + [], + ['8E4aZTjY'], + [6], + [{ + size: 10, + path: '/tmp/attachment.txt', + filename: 'attachment.txt', + originalname: 'attachment.txt', + mimetype: 'text/plain', + encoding: '7bit', + }], + ); + + for (const logs of [ + await logService.getAllByRun(1), + await logService.getAllByLhcFill(6), + await logService.getAllByEnvironment('8E4aZTjY'), + ]) { + const log = logs.find(({ id }) => id === logId); + expect(log.attachments.map(({ originalName }) => originalName)).to.deep.equal(['attachment.txt']); + } + }); }; From 4982fc94425a09161bb6267cd70fb87cbf9ace3d Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Oct 2026 11:14:33 +0200 Subject: [PATCH 2/2] Fix introduced issue in which tag properties were not returned anymore --- lib/server/services/log/LogService.js | 2 +- test/api/runs.test.js | 3 +++ test/lib/server/services/log/LogService.test.js | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/server/services/log/LogService.js b/lib/server/services/log/LogService.js index 16e0a52e0b..9391cf81b5 100644 --- a/lib/server/services/log/LogService.js +++ b/lib/server/services/log/LogService.js @@ -208,7 +208,7 @@ class LogService { .include({ association: 'dataPasses', attributes: ['id', 'name'] }) .include({ association: 'environments', attributes: ['id'] }) .include({ association: 'user', attributes: ['name'] }) - .include({ association: 'tags', attributes: ['text'] }) + .include('tags') .include('attachments'); } diff --git a/test/api/runs.test.js b/test/api/runs.test.js index bc6b1d5de9..67747b9b99 100644 --- a/test/api/runs.test.js +++ b/test/api/runs.test.js @@ -1072,6 +1072,9 @@ module.exports = () => { expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); expect(log.attachments).to.be.an('array'); + // Tags must be complete to be displayed with their color and archived state + expect(log.tags).to.not.be.empty; + expect(log.tags.every(({ archived }) => typeof archived === 'boolean')).to.be.true; done(); }); }); diff --git a/test/lib/server/services/log/LogService.test.js b/test/lib/server/services/log/LogService.test.js index f658d188de..038238c265 100644 --- a/test/lib/server/services/log/LogService.test.js +++ b/test/lib/server/services/log/LogService.test.js @@ -113,8 +113,10 @@ module.exports = () => { expect(log.lhcFills.map(({ fillNumber }) => fillNumber)).to.have.members([5, 6]); expect(log.environments.map(({ id }) => id)).to.have.members(['8E4aZTjY', 'eZF99lH6']); expect(log.dataPasses.map(({ name }) => name)).to.have.members(['LHC22b_apass1', 'LHC22b_skimming']); + // Complete tags (with their color and archived state) are required to display the logs, the author is only exposed by name expect(log.tags).to.not.be.empty; - expect(log.author).to.not.be.null; + expect(log.tags.every(({ archived, updatedAt }) => typeof archived === 'boolean' && typeof updatedAt === 'number')).to.be.true; + expect(log.author.name).to.be.a('string'); }); it('Should successfully return the attachments of logs linked to a run, an LHC fill or an environment', async () => {