Skip to content

Commit 4dd2e15

Browse files
authored
Merge branch 'main' into improv/O2B-1533/Move-AOT-MUON-columns
2 parents f099d33 + dd61ecd commit 4dd2e15

20 files changed

Lines changed: 771 additions & 231 deletions
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
'use strict';
15+
16+
/** @type {import('sequelize-cli').Migration} */
17+
module.exports = {
18+
up: async (queryInterface) => queryInterface.sequelize.transaction(async (transaction) => {
19+
await queryInterface.addIndex('quality_control_flags', {
20+
name: 'quality_control_flags_run_detector_idx',
21+
fields: ['run_number', 'detector_id'],
22+
}, { transaction });
23+
}),
24+
25+
down: async (queryInterface) => queryInterface.sequelize.transaction(async (transaction) => {
26+
await queryInterface.removeIndex('quality_control_flags', 'quality_control_flags_run_detector_idx', { transaction });
27+
}),
28+
};

lib/domain/dtos/filters/LhcFillsFilterDto.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const Joi = require('joi');
1414
const { validateRange, RANGE_INVALID } = require('../../../utilities/rangeUtils');
1515
const { validateBeamTypes, BEAM_TYPE_INVALID } = require('../../../utilities/beamTypeUtils');
1616
const { validateTimeDuration } = require('../../../utilities/validateTime');
17+
const { FromToFilterDto } = require('./FromToFilterDto.js');
1718

1819
exports.LhcFillsFilterDto = Joi.object({
1920
hasStableBeams: Joi.boolean(),
@@ -23,6 +24,8 @@ exports.LhcFillsFilterDto = Joi.object({
2324
}),
2425
runDuration: validateTimeDuration,
2526
beamDuration: validateTimeDuration,
27+
stableBeamsStart: FromToFilterDto,
28+
stableBeamsEnd: FromToFilterDto,
2629
schemeName: Joi.string().trim().max(64),
2730
beamTypes: Joi.string()
2831
.trim()

lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fil
2828
import { durationFilter } from '../../../components/Filters/LhcFillsFilter/durationFilter.js';
2929
import { beamTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamTypeFilter.js';
3030
import { schemeNameFilter } from '../../../components/Filters/LhcFillsFilter/schemeNameFilter.js';
31+
import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js';
3132

3233
/**
3334
* List of active columns for a lhc fills table
@@ -65,6 +66,14 @@ export const lhcFillsActiveColumns = {
6566
visible: true,
6667
size: 'w-8',
6768
format: (timestamp) => formatTimestamp(timestamp, false),
69+
70+
/**
71+
* Stable Beam start filter component
72+
*
73+
* @param {RunsOverviewModel} lhcFillsOverviewModel the lhcFills overview model
74+
* @return {Component} the filter component
75+
*/
76+
filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsStart').timeRangeInputModel),
6877
profiles: {
6978
lhcFill: true,
7079
environment: true,
@@ -80,6 +89,14 @@ export const lhcFillsActiveColumns = {
8089
visible: true,
8190
size: 'w-8',
8291
format: (timestamp) => formatTimestamp(timestamp, false),
92+
93+
/**
94+
* Stable Beam end filter component
95+
*
96+
* @param {LhcFillsOverviewModel} lhcFillsOverviewModel the lhcFills overview model
97+
* @return {Component} the filter component
98+
*/
99+
filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsEnd').timeRangeInputModel),
83100
profiles: {
84101
lhcFill: true,
85102
environment: true,

lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { OverviewPageModel } from '../../../models/OverviewModel.js';
1919
import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js';
2020
import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js';
2121
import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js';
22+
import { TimeRangeFilterModel } from '../../../components/Filters/RunsFilter/TimeRangeFilter.js';
2223

2324
/**
2425
* Model for the LHC fills overview page
@@ -39,6 +40,8 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
3940
beamDuration: new TextComparisonFilterModel(),
4041
runDuration: new TextComparisonFilterModel(),
4142
hasStableBeams: new StableBeamFilterModel(),
43+
stableBeamsStart: new TimeRangeFilterModel(),
44+
stableBeamsEnd: new TimeRangeFilterModel(),
4245
beamTypes: new BeamTypeFilterModel(),
4346
schemeName: new RawTextFilterModel(),
4447
});

lib/public/views/Runs/Details/RunPatch.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RunQualities } from '../../../domain/enums/RunQualities.js';
99
* @property {string} category
1010
* @property {string} title
1111
* @property {string} description
12+
* @property {string|null} [lastEditedName]
1213
*/
1314

1415
/**
@@ -75,7 +76,8 @@ export class RunPatch extends Observable {
7576
}
7677

7778
if (this._eorReasons.length !== this._run.eorReasons.length || this._eorReasons.some(({ id }) => id === undefined)) {
78-
ret.eorReasons = this._eorReasons;
79+
// Strip lastEditedName — the server's EorReasonDto only accepts id, reasonTypeId, and description
80+
ret.eorReasons = this._eorReasons.map(({ id, reasonTypeId, description }) => ({ id, reasonTypeId, description }));
7981
}
8082

8183
if (this._hasRunQualityChange()) {
@@ -126,7 +128,12 @@ export class RunPatch extends Observable {
126128
} = this._run || {};
127129

128130
this._runQuality = runQuality;
129-
this._eorReasons = eorReasons.map(({ id, description, reasonTypeId }) => ({ id, description, reasonTypeId }));
131+
this._eorReasons = eorReasons.map(({ id, description, reasonTypeId, lastEditedName }) => ({
132+
id,
133+
description,
134+
reasonTypeId,
135+
lastEditedName,
136+
}));
130137
this._tags = tags.map(({ text }) => text);
131138

132139
this.formData = {

lib/public/views/Runs/Details/runDetailsComponent.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { RunDefinition } from '../../../domain/enums/RunDefinition.js';
4040
import { formatFloat } from '../../../utilities/formatting/formatFloat.js';
4141
import { formatEditableNumber } from '../format/formatEditableNumber.js';
4242
import { editRunEorReasons } from '../format/editRunEorReasons.js';
43-
import { formatEorReason } from '../format/formatEorReason.mjs';
43+
import { formatRunEorReason } from '../format/formatRunEorReason.js';
4444
import { selectionDropdown } from '../../../components/common/selection/dropdown/selectionDropdown.js';
4545
import { formatRunCalibrationStatus } from '../format/formatRunCalibrationStatus.js';
4646
import { BeamModes } from '../../../domain/enums/BeamModes.js';
@@ -533,7 +533,10 @@ export const runDetailsComponent = (runDetailsModel, router) => runDetailsModel.
533533
h('#eor-reasons.flex-row', [
534534
runDetailsModel.isEditModeEnabled
535535
? editRunEorReasons(runDetailsModel)
536-
: h('.flex-column.g2', run.eorReasons.map((eorReason) => h('.eor-reason', formatEorReason(eorReason)))),
536+
: h(
537+
'.flex-column.g2.w-100',
538+
run.eorReasons.map((eorReason) => h('.eor-reason', formatRunEorReason(eorReason))),
539+
),
537540
]),
538541
]),
539542
]),

lib/public/views/Runs/format/editRunEorReasons.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,23 @@ export const editRunEorReasons = (runDetailsModel) => {
9494
*/
9595
runDetailsModel.runPatch.eorReasons.length > 0
9696
? runDetailsModel.runPatch.eorReasons.map((eorReason) => {
97-
const { reasonTypeId, description } = eorReason;
97+
const { reasonTypeId, description, lastEditedName } = eorReason;
9898
const { category = '-', title } = eorReasonTypes.find((eorReasonType) => eorReasonType.id === reasonTypeId) || {};
9999
const titleString = title ? ` - ${title}` : '';
100100
const descriptionString = description ? ` - ${description}` : '';
101101
return h(
102-
'.flex-row.items-center',
102+
'.flex-row.justify-between',
103103
{
104104
key: `${category} ${titleString} ${descriptionString}`,
105105
},
106106
[
107-
h('label.remove-eor-reason.danger.ph1.actionable-icon', {
108-
onclick: () => runDetailsModel.runPatch.removeEorReason(eorReason),
109-
}, iconTrash()),
110-
h('.w-wrapped', `${category} ${titleString} ${descriptionString}`),
107+
h('.flex-row.items-center', [
108+
h('label.remove-eor-reason.danger.ph1.actionable-icon', {
109+
onclick: () => runDetailsModel.runPatch.removeEorReason(eorReason),
110+
}, iconTrash()),
111+
h('.w-wrapped', `${category} ${titleString} ${descriptionString}`),
112+
]),
113+
h('.w-wrapped', lastEditedName || null),
111114
],
112115
);
113116
})
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license
3+
* Copyright CERN and copyright holders of ALICE O2. This software is
4+
* distributed under the terms of the GNU General Public License v3 (GPL
5+
* Version 3), copied verbatim in the file "COPYING".
6+
*
7+
* See http://alice-o2.web.cern.ch/license for full licensing information.
8+
*
9+
* In applying this license CERN does not waive the privileges and immunities
10+
* granted to it by virtue of its status as an Intergovernmental Organization
11+
* or submit itself to any jurisdiction.
12+
*/
13+
14+
import { h } from '/js/src/index.js';
15+
import { tooltip } from '../../../../components/common/popover/tooltip.js';
16+
import { formatEorReason } from './formatEorReason.mjs';
17+
18+
/**
19+
* Display the given EoR reason as a vnode component with lastEditedName tooltip
20+
*
21+
* @param {Partial<{
22+
* category: string,
23+
* title: string,
24+
* description: string,
25+
* lastEditedName: string,
26+
* }>} eorReason the EoR reason to display
27+
* @return {VNode} the vnode component
28+
*/
29+
export const formatRunEorReason = (eorReason) => {
30+
const { lastEditedName } = eorReason;
31+
const reasonText = formatEorReason(eorReason);
32+
return h('.w-100.flex-row.justify-between', [
33+
h('', reasonText),
34+
lastEditedName ? tooltip(h('.w-wrapped', lastEditedName), 'Last edited by') : null,
35+
]);
36+
};

lib/usecases/environment/GetAllEnvironmentsUseCase.js

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const { dataSource } = require('../../database/DataSource.js');
2323
const { statusAcronyms } = require('../../domain/enums/StatusAcronyms.js');
2424
const { unpackNumberRange } = require('../../utilities/rangeUtils.js');
2525
const { splitStringToStringsTrimmed } = require('../../utilities/stringUtils.js');
26+
const { setTimeRangeQuery } = require('../../utilities/setTimeRangeQuery.js');
2627

2728
/**
2829
* Subquery to select the latest history item for each environment.
@@ -69,18 +70,11 @@ class GetAllEnvironmentsUseCase {
6970
const { filter, page = {} } = query;
7071
const { limit = ApiConfig.pagination.limit, offset = 0 } = page;
7172

72-
/**
73-
* Prepare a query builder with ordering, limit and offset
74-
*
75-
* @return {QueryBuilder} the created query builder
76-
*/
77-
const prepareQueryBuilder = () => dataSource.createQueryBuilder()
73+
const queryBuilder = dataSource.createQueryBuilder()
7874
.orderBy('updatedAt', 'desc')
7975
.limit(limit)
8076
.offset(offset);
8177

82-
const fetchQueryBuilder = prepareQueryBuilder();
83-
8478
if (filter) {
8579
const {
8680
ids: idsExpression,
@@ -90,38 +84,34 @@ class GetAllEnvironmentsUseCase {
9084
created,
9185
} = filter;
9286

93-
const filterQueryBuilder = prepareQueryBuilder();
94-
9587
if (created) {
96-
const from = created.from !== undefined ? created.from : 0;
97-
const to = created.to !== undefined ? created.to : Date.now();
98-
filterQueryBuilder.where('createdAt').between(from, to);
88+
setTimeRangeQuery(created, 'createdAt', queryBuilder);
9989
}
10090

10191
if (idsExpression) {
10292
const filters = idsExpression.split(',').map((id) => id.trim());
10393

10494
// Filter should be like with only one filter
10595
if (filters.length === 1) {
106-
filterQueryBuilder.where('id').substring(filters[0]);
96+
queryBuilder.where('id').substring(filters[0]);
10797
}
10898

10999
// Filters should be exact with more than one filter
110100
if (filters.length > 1) {
111-
filterQueryBuilder.andWhere({ id: { [Op.in]: filters } });
101+
queryBuilder.andWhere({ id: { [Op.in]: filters } });
112102
}
113103
}
114104

115105
if (currentStatusExpression) {
116106
const filters = currentStatusExpression.split(',').map((status) => status.trim());
117107

118108
// Filter the environments by current status using the subquery
119-
filterQueryBuilder.literalWhere(
109+
queryBuilder.literalWhere(
120110
`${ENVIRONMENT_LATEST_HISTORY_ITEM_SUBQUERY} IN (:filters)`,
121111
{ filters },
122112
);
123113

124-
filterQueryBuilder.includeAttribute({
114+
queryBuilder.includeAttribute({
125115
query: ENVIRONMENT_LATEST_HISTORY_ITEM_SUBQUERY,
126116
alias: 'currentStatus',
127117
});
@@ -157,7 +147,7 @@ class GetAllEnvironmentsUseCase {
157147
* Use OR condition to match subsequences ending with either DESTROYED or DONE
158148
* Filter the environments by using LIKE for subsequence matching
159149
*/
160-
filterQueryBuilder.literalWhere(
150+
queryBuilder.literalWhere(
161151
`(${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDestroyed OR ` +
162152
`${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDone)`,
163153
{
@@ -166,17 +156,17 @@ class GetAllEnvironmentsUseCase {
166156
},
167157
);
168158

169-
filterQueryBuilder.includeAttribute({
159+
queryBuilder.includeAttribute({
170160
query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY,
171161
alias: 'statusHistory',
172162
});
173163
} else {
174-
filterQueryBuilder.literalWhere(
164+
queryBuilder.literalWhere(
175165
`${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFilters`,
176166
{ statusFilters: `%${statusFilters.join(',')}%` },
177167
);
178168

179-
filterQueryBuilder.includeAttribute({
169+
queryBuilder.includeAttribute({
180170
query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY,
181171
alias: 'statusHistory',
182172
});
@@ -190,30 +180,20 @@ class GetAllEnvironmentsUseCase {
190180

191181
// Check that the final run numbers list contains at least one valid run number
192182
if (finalRunNumberList.length > 0) {
193-
filterQueryBuilder.include({
183+
queryBuilder.include({
194184
association: 'runs',
195185
where: {
196186
// Filter should be like with only one filter and exact with more than one filter
197187
runNumber: { [finalRunNumberList.length === 1 ? Op.substring : Op.in]: finalRunNumberList },
198188
},
199189
});
200190
}
201-
};
202-
203-
const filteredEnvironmentsIds = (await EnvironmentRepository.findAll(filterQueryBuilder)).map(({ id }) => id);
204-
// If no environments match the filter, return an empty result
205-
if (filteredEnvironmentsIds.length === 0) {
206-
return {
207-
count: 0,
208-
environments: [],
209-
};
210191
}
211-
fetchQueryBuilder.where('id').oneOf(filteredEnvironmentsIds);
212192
}
213193

214-
fetchQueryBuilder.include({ association: 'runs' });
215-
fetchQueryBuilder.include({ association: 'historyItems' });
216-
const { count, rows } = await EnvironmentRepository.findAndCountAll(fetchQueryBuilder);
194+
queryBuilder.include({ association: 'runs' });
195+
queryBuilder.include({ association: 'historyItems' });
196+
const { count, rows } = await EnvironmentRepository.findAndCountAll(queryBuilder);
217197
return {
218198
count,
219199
environments: rows.map((environment) => environmentAdapter.toEntity(environment)),

lib/usecases/lhcFill/GetAllLhcFillsUseCase.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const { ApiConfig } = require('../../config/index.js');
2424
const { RunDefinition } = require('../../domain/enums/RunDefinition.js');
2525
const { unpackNumberRange } = require('../../utilities/rangeUtils.js');
2626
const { splitStringToStringsTrimmed } = require('../../utilities/stringUtils.js');
27+
const { setTimeRangeQuery } = require('../../utilities/setTimeRangeQuery.js');
2728

2829
/**
2930
* GetAllLhcFillsUseCase
@@ -47,12 +48,20 @@ class GetAllLhcFillsUseCase {
4748
let associatedStatisticsRequired = false;
4849

4950
if (filter) {
50-
const { hasStableBeams, fillNumbers, schemeName, beamDuration, runDuration, beamTypes } = filter;
51+
const { hasStableBeams, fillNumbers, schemeName, beamDuration, stableBeamsStart, stableBeamsEnd, runDuration, beamTypes } = filter;
5152
if (hasStableBeams) {
5253
// For now, if a stableBeamsStart is present, then a beam is stable
5354
queryBuilder.where('stableBeamsStart').not().is(null);
5455
}
5556

57+
if (stableBeamsStart) {
58+
setTimeRangeQuery(stableBeamsStart, 'stableBeamsStart', queryBuilder);
59+
}
60+
61+
if (stableBeamsEnd) {
62+
setTimeRangeQuery(stableBeamsEnd, 'stableBeamsEnd', queryBuilder);
63+
}
64+
5665
if (fillNumbers) {
5766
const fillNumberCriteria = splitStringToStringsTrimmed(fillNumbers, SEARCH_ITEMS_SEPARATOR);
5867

0 commit comments

Comments
 (0)