Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
de741a9
Update model and DB with new attribute for userid
graduta Oct 1, 2026
0abebc0
Update usecases to fetch with userId
graduta Oct 1, 2026
789c809
Improve integrity of created tags
graduta Oct 1, 2026
98641f9
Update backend of tags usecase to return user association
graduta Oct 1, 2026
4bd7b9b
API endpoint should return only needed information on backend
graduta Oct 1, 2026
bee7165
Update data model to use user id and not name in eor reasons
graduta Oct 1, 2026
98db103
Update front-end to use new data model
graduta Oct 1, 2026
d3d9ccb
Update backend service and usecase to use new structure for user asso…
graduta Oct 1, 2026
5fc33cf
Merge branch 'main' of github.com:AliceO2Group/Bookkeeping into featu…
graduta Oct 15, 2026
6b17c93
Merge branch 'main' of github.com:AliceO2Group/Bookkeeping into featu…
graduta Oct 15, 2026
834be32
Merge branch 'main' of github.com:AliceO2Group/Bookkeeping into featu…
graduta Oct 15, 2026
074f1be
Add database changes for allowing linking of logs to datapasses
graduta Oct 16, 2026
10b97ba
Add back-end endpoint changes to allow log link of datapass to creation
graduta Oct 16, 2026
34f730f
Add UI changes for log creation
graduta Oct 16, 2026
403915f
Merge branch 'main' into feature/O2B-1626/log-creation-to-link-to-dat…
graduta Oct 16, 2026
cdc61a9
Update tests for this usecase
graduta Oct 16, 2026
e3640f1
Merge branch 'feature/O2B-1626/log-creation-to-link-to-data-passes' o…
graduta Oct 16, 2026
b8f9bdb
Improve number of lookups on creation of log entry with data passes
graduta Oct 16, 2026
3700745
Reset database after suite for test matching
graduta Oct 16, 2026
62108a8
Add link in model of data pass as well
graduta Oct 16, 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
11 changes: 11 additions & 0 deletions lib/database/adapters/DataPassAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class DataPassAdapter {
*/
constructor() {
this.toEntity = this.toEntity.bind(this);
this.toMinifiedEntity = this.toMinifiedEntity.bind(this);
this.dataPassVersionAdapter = null;
}

Expand Down Expand Up @@ -53,6 +54,16 @@ class DataPassAdapter {
isFrozen,
};
}

/**
* Converts the given database object to a minified entity object (only identification fields).
*
* @param {SequelizeDataPass} databaseObject Object to convert.
* @returns {{id: number, name: string}} Converted minified entity object.
*/
toMinifiedEntity({ id, name }) {
return { id, name };
}
}

module.exports = DataPassAdapter;
8 changes: 8 additions & 0 deletions lib/database/adapters/LogAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class LogAdapter {
*/
this.attachmentAdapter = null;

/**
* @type {DataPassAdapter|null}
*/
this.dataPassAdapter = null;

/**
* @typedef {EnvironmentAdapter|null}
*/
Expand Down Expand Up @@ -74,6 +79,7 @@ class LogAdapter {
tags: sequelizeTags,
runs,
lhcFills: sequelizeLhcFills,
dataPasses: sequelizeDataPasses,
attachments: sequelizeAttachments,
environments: sequelizeEnvironments,
} = databaseObject;
Expand All @@ -82,6 +88,7 @@ class LogAdapter {
const tags = (sequelizeTags || []).map(this.tagAdapter.toEntity);
const attachments = (sequelizeAttachments || []).map(this.attachmentAdapter.toEntity);
const lhcFills = (sequelizeLhcFills || []).map(this.lhcFillAdapter.toEntity);
const dataPasses = (sequelizeDataPasses || []).map(this.dataPassAdapter.toMinifiedEntity);
const environments = (sequelizeEnvironments || []).map(this.environmentAdapter.toEntity);

const entityObject = {
Expand All @@ -97,6 +104,7 @@ class LogAdapter {
runs: minifiedRuns,
tags,
lhcFills,
dataPasses,
attachments,
environments,
};
Expand Down
1 change: 1 addition & 0 deletions lib/database/adapters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ lhcFillAdapter.statisticsAdapter = lhcFillStatisticsAdapter;
lhcPeriodStatisticsAdapter.lhcPeriodAdapter = lhcPeriodAdapter;

logAdapter.attachmentAdapter = attachmentAdapter;
logAdapter.dataPassAdapter = dataPassAdapter;
logAdapter.environmentAdapter = environmentAdapter;
logAdapter.lhcFillAdapter = lhcFillAdapter;
logAdapter.runAdapter = runAdapter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* @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.
*/

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.createTable('log_data_passes', {
log_id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
references: { model: 'logs', key: 'id' },
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
data_pass_id: {
type: Sequelize.INTEGER,
primaryKey: true,
allowNull: false,
references: { model: 'data_passes', key: 'id' },
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
created_at: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
},
}),

down: async (queryInterface) => queryInterface.dropTable('log_data_passes'),
};
6 changes: 6 additions & 0 deletions lib/database/models/dataPass.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ module.exports = (sequelize) => {
through: 'data_pass_quality_control_flag',
});
DataPass.hasMany(models.DataPassVersion, { as: 'versions' });
DataPass.belongsToMany(models.Log, {
as: 'logs',
through: 'log_data_passes',
foreignKey: 'data_pass_id',
otherKey: 'log_id',
});
};

return DataPass;
Expand Down
7 changes: 7 additions & 0 deletions lib/database/models/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ module.exports = (sequelize) => {
Log.belongsToMany(models.Environment, { as: 'environments', through: 'log_environments' });
Log.belongsToMany(models.LhcFill, { as: 'lhcFills', through: 'log_lhc_fills', foreignKey: 'log_id' });

Log.belongsToMany(models.DataPass, {
as: 'dataPasses',
through: 'log_data_passes',
foreignKey: 'log_id',
otherKey: 'data_pass_id',
});

Log.hasMany(models.Attachment, { as: 'attachments', foreignKey: 'log_id' });
};

Expand Down
1 change: 1 addition & 0 deletions lib/database/models/typedefs/SequelizeLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@
* @property {SequelizeTag[]|null} tags
* @property {SequelizeAttachment[]|null} attachments
* @property {SequelizeEnvironment[]|null} environments
* @property {SequelizeDataPass[]|null} dataPasses
*/
39 changes: 39 additions & 0 deletions lib/database/repositories/LogDataPassesRepository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @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 { tables: { LogDataPasses } } = require('..');
const Repository = require('./Repository');

/**
* Sequelize implementation of the LogDataPassesRepository.
*/
class LogDataPassesRepository extends Repository {
/**
* Creates a new `LogDataPassesRepository` instance.
*/
constructor() {
super(LogDataPasses);
}

/**
* Bulk insert logDataPasses.
*
* @param {{logId: number, dataPassId: number}[]} entities the links to insert
* @returns {Promise} Promise object represents the recently inserted LogDataPasses.
*/
async bulkInsert(entities) {
return LogDataPasses.bulkCreate(entities, { returning: true });
}
}

module.exports = new LogDataPassesRepository();
2 changes: 2 additions & 0 deletions lib/database/repositories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const LhcPeriodRepository = require('./LhcPeriodRepository');
const LhcPeriodStatisticsRepository = require('./LhcPeriodStatisticsRepository');
const LogEnvironmentsRepository = require('./LogEnvironmentsRepository');
const LogLhcFillsRepository = require('./LogLhcFillsRepository');
const LogDataPassesRepository = require('./LogDataPassesRepository');
const LogRepository = require('./LogRepository');
const LogRunsRepository = require('./LogRunsRepository');
const LogTagsRepository = require('./LogTagsRepository');
Expand Down Expand Up @@ -77,6 +78,7 @@ module.exports = {
LhcPeriodStatisticsRepository,
LogEnvironmentsRepository,
LogLhcFillsRepository,
LogDataPassesRepository,
LogRepository,
LogRunsRepository,
LogTagsRepository,
Expand Down
27 changes: 27 additions & 0 deletions lib/database/seeders/20240112102500-log-data-passes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @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.
*/

/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface) =>
queryInterface.sequelize.transaction((transaction) =>
queryInterface.bulkInsert('log_data_passes', [
{ log_id: 1, data_pass_id: 1 },
{ log_id: 1, data_pass_id: 2 },
{ log_id: 119, data_pass_id: 1 },
], { transaction })),

down: async (queryInterface) =>
queryInterface.sequelize.transaction((transaction) =>
queryInterface.bulkDelete('log_data_passes', null, { transaction })),
};
2 changes: 2 additions & 0 deletions lib/database/tables/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ const LogRuns = require('./logruns');
const LogEnvironments = require('./logenvironments');
const RunTags = require('./runtags');
const LogLhcFills = require('./loglhcfills');
const LogDataPasses = require('./logdatapasses');

module.exports = (sequelize) => {
const models = {
LogTags: LogTags(sequelize),
LogRuns: LogRuns(sequelize),
LogLhcFills: LogLhcFills(sequelize),
LogDataPasses: LogDataPasses(sequelize),
LogEnvironments: LogEnvironments(sequelize),
RunTags: RunTags(sequelize),
};
Expand Down
38 changes: 38 additions & 0 deletions lib/database/tables/logdatapasses.js
Original file line number Diff line number Diff line change
@@ -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.
*/

module.exports = (sequelize) => {
const Sequelize = require('sequelize');

const LogDataPasses = sequelize.define('log_data_passes', {
logId: {
allowNull: false,
primaryKey: true,
type: Sequelize.INTEGER,
},
dataPassId: {
allowNull: false,
primaryKey: true,
type: Sequelize.INTEGER,
},
});

LogDataPasses.removeAttribute('id');

LogDataPasses.associate = (models) => {
LogDataPasses.belongsTo(models.DataPass, { foreignKey: 'data_pass_id', as: 'dataPass' });
LogDataPasses.belongsTo(models.Log, { foreignKey: 'log_id', as: 'log' });
};

return LogDataPasses;
};
3 changes: 3 additions & 0 deletions lib/domain/dtos/CreateLogDto.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const BodyDto = Joi.object({
lhcFills: Joi.array()
.optional()
.items(Joi.number()),
dataPasses: Joi.array()
.optional()
.items(Joi.string()),
parentLogId: EntityIdDto.optional(),
runNumbers: Joi.string().optional(),
});
Expand Down
1 change: 1 addition & 0 deletions lib/domain/entities/Log.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
* @property {SequelizeRun[]|null} runs
* @property {SequelizeTag[]|null} tags
* @property {SequelizeEnvironment[]|null} environments
* @property {{id: number, name: string}[]} dataPasses - minified version of it
* @property {SequelizeAttachment[]|null} attachments
*/
31 changes: 28 additions & 3 deletions lib/public/views/Logs/Create/LogCreationModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const DATA_DISPLAY_DELIMITER = ',';
* @typedef LogCreationRelations
* @property {number[]} [runNumbers] run numbers associated with log being created
* @property {number[]} [lhcFillNumbers] lhc fill numbers associated with log being created
* @property {string[]} [dataPassNames] data pass names associated with log being created
* @property {number[]} [environmentIds] environment ids associated with log being created
*/

Expand All @@ -44,9 +45,10 @@ export class LogCreationModel extends Observable {
this._creationTagsPickerModel.bubbleTo(this);
this._creationTagsPickerModel.visualChange$.bubbleTo(this);

const { runNumbers = [], environmentIds = [], lhcFillNumbers = [] } = relations;
const { runNumbers = [], environmentIds = [], lhcFillNumbers = [], dataPassNames = [] } = relations;
this._runNumbers = runNumbers.join(DATA_DISPLAY_DELIMITER);
this._lhcFills = lhcFillNumbers.join(DATA_DISPLAY_DELIMITER);
this._dataPasses = dataPassNames.join(DATA_DISPLAY_DELIMITER);
this._environments = environmentIds.join(DATA_DISPLAY_DELIMITER);
this._isRunNumbersReadonly = Boolean(this._runNumbers);

Expand Down Expand Up @@ -93,11 +95,13 @@ export class LogCreationModel extends Observable {
this._createdLog = RemoteData.loading();
this.notify();

const { title, text, parentLogId, runNumbers, environments, lhcFills, attachments } = this;
const { title, text, parentLogId, runNumbers, environments, lhcFills, dataPasses, attachments } = this;
const tagsTexts = this._creationTagsPickerModel.selected;
const environmentsIds = environments ? environments.split(',').map((environment) => environment.trim()) : [];
const lhcFillNumbers = lhcFills ? lhcFills.split(',').map((lhcFillNumber) => lhcFillNumber.trim()) : [];

const dataPassNames = dataPasses.split(',').map((name) => name.trim()).filter(Boolean);

const body = {
title,
text,
Expand All @@ -110,11 +114,13 @@ export class LogCreationModel extends Observable {
headers: { Accept: 'application/json' },
};

if (attachments.length > 0 || tagsTexts.length > 0 || environmentsIds.length > 0 || lhcFillNumbers.length > 0) {
const hasMultipartFields = [attachments, tagsTexts, environmentsIds, lhcFillNumbers, dataPassNames].some(({ length }) => length > 0);
if (hasMultipartFields) {
const formData = new FormData();
Object.entries(body).forEach(([key, value]) => formData.append(key, value));
environmentsIds.forEach((environmentId) => formData.append('environments[]', environmentId));
lhcFillNumbers.forEach((fillNumber) => formData.append('lhcFills[]', fillNumber));
dataPassNames.forEach((name) => formData.append('dataPasses[]', name));
tagsTexts.forEach((tagText) => formData.append('tags[]', tagText));
[...attachments].forEach((attachment, index) => formData.append(`attachments.${index}`, attachment));

Expand Down Expand Up @@ -228,6 +234,25 @@ export class LogCreationModel extends Observable {
return `${this._lhcFills}`;
}

/**
* Returns the data passes which the log will be linked to, as a comma separated string of names
*
* @returns {string} the data pass names
*/
get dataPasses() {
return `${this._dataPasses}`;
}

/**
* Set the data passes which the log will be linked to, as a comma separated string of names
*
* @param {string} value the new data pass names list
*/
set dataPasses(value) {
this._dataPasses = value;
this.notify();
}

/**
* Set the environments which the log will be linked to, as a comma separated string
*
Expand Down
Loading
Loading