Skip to content

Commit 3b97032

Browse files
authored
[OGUI-1842] Add panel with run information in QCG under filters (#3232)
* Refactor the response object of `retrieveRunStatus` and rename it to `retrieveRunInformation` * Add panel with run information in QCG under filters * Rewrite updateRunInformation so that it doesn't rely on runNumber to be set. Clear runInformation on clearFilters() * Add and use BookkeepingDto.js * Add typedef for RunInformation * Rename `detectorQualities` to `detectorsQualities` and add default value `[]` * Add a test to validate whether the run information endpoint returns the correct data on success * Add `statusBadgeSuccess`, `statusBadgeFail` and `statusBadge` helper functions * Display the run detector qualities in badges on a new line beneath the run information
1 parent 1da5d94 commit 3b97032

File tree

9 files changed

+334
-15
lines changed

9 files changed

+334
-15
lines changed

QualityControl/public/Model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export default class Model extends Observable {
169169
this.object.objects = {}; // Remove any in-memory loaded objects
170170
this._clearAllIntervals();
171171
await this.filterModel.filterService.initFilterService();
172-
this.filterModel.setFilterFromURL();
172+
await this.filterModel.setFilterFromURL();
173173
this.filterModel.setFilterToURL();
174174

175175
this.services.layout.getLayoutsByUserId(this.session.personid, RequestFields.LAYOUT_CARD);

QualityControl/public/app.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@
156156
cursor: pointer;
157157
}
158158

159+
.b1 { border-style: solid; border-width: 1px; }
160+
161+
.b-danger { border-color: var(--color-danger); }
162+
.b-success { border-color: var(--color-success); }
163+
159164
.header-layout {
160165
&.edit {
161166
display: flex;
@@ -187,3 +192,11 @@
187192
.whitespace-nowrap {
188193
white-space: nowrap;
189194
}
195+
196+
/* This hacky workaround is required due to `justify-content: center;` being unusable thanks to a horizontal scrolling bug */
197+
#header-detector-qualities {
198+
&::before, &::after {
199+
content: '';
200+
flex: 1;
201+
}
202+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
4+
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
5+
* All rights not expressly granted are reserved.
6+
*
7+
* This software is distributed under the terms of the GNU General Public
8+
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
9+
*
10+
* In applying this license CERN does not waive the privileges and immunities
11+
* granted to it by virtue of its status as an Intergovernmental Organization
12+
* or submit itself to any jurisdiction.
13+
*/
14+
15+
import { h, iconCheck, iconX } from '/js/src/index.js';
16+
17+
/**
18+
* A green success badge with the tick icon
19+
* @param {string} text - Text to display in the badge
20+
* @returns {vnode} The badge virtual node
21+
*/
22+
export const statusBadgeSuccess = (text) =>
23+
h('.badge.success.b-success.b1', h('.flex-row.g1', [text, iconCheck()]));
24+
25+
/**
26+
* A red failure badge with the X icon
27+
* @param {string} text - Text to display in the badge
28+
* @returns {vnode} The badge virtual node
29+
*/
30+
export const statusBadgeFail = (text) =>
31+
h('.badge.danger.b-danger.b1', h('.flex-row.g1', [text, iconX()]));
32+
33+
/**
34+
* A status badge with dynamic color and icon depending on success or failure.
35+
* @param {string} text - Text to display inside the badge
36+
* @param {boolean} success - Whether the badge represents success (`true`) or failure (`false`)
37+
* @returns {vnode} The badge virtual node
38+
*/
39+
export const statusBadge = (text, success) =>
40+
success ? statusBadgeSuccess(text) : statusBadgeFail(text);

QualityControl/public/common/filters/filterViews.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import { filterInput, dynamicSelector, ongoingRunsSelector } from './filter.js';
1616
import { FilterType } from './filterTypes.js';
1717
import { filtersConfig, runModeFilterConfig } from './filtersConfig.js';
1818
import { runModeCheckbox } from './runMode/runModeCheckbox.js';
19-
import { lastUpdatePanel, runStatusPanel } from './runMode/runStatusPanel.js';
19+
import {
20+
cleanRunInformationPanel,
21+
detectorsQualitiesPanel,
22+
lastUpdatePanel,
23+
runStatusPanel,
24+
} from './runMode/runStatusPanel.js';
2025
import { h, iconChevronBottom, iconChevronTop } from '/js/src/index.js';
2126

2227
/**
@@ -75,6 +80,7 @@ export function filtersPanel(filterModel, viewModel) {
7580
isVisible,
7681
lastRefresh,
7782
ONGOING_RUN_INTERVAL_MS: refreshRate,
83+
runInformation,
7884
} = filterModel;
7985
const { fetchOngoingRuns } = filterService;
8086
const onInputCallback = setFilterValue.bind(filterModel);
@@ -88,6 +94,7 @@ export function filtersPanel(filterModel, viewModel) {
8894
const filtersList = isRunModeActivated
8995
? runModeFilterConfig(filterService)
9096
: filtersConfig(filterService);
97+
const { detectorsQualities, ...cleanRunInformation } = runInformation;
9198

9299
return h(
93100
'.w-100.flex-column.p2.g2.justify-center#filterElement',
@@ -101,15 +108,11 @@ export function filtersPanel(filterModel, viewModel) {
101108
isRunModeActivated && runStatusPanel(runStatus),
102109
]),
103110
lastUpdatePanel(runStatus, lastRefresh, refreshRate),
111+
cleanRunInformationPanel(cleanRunInformation),
112+
detectorsQualitiesPanel(detectorsQualities),
104113
],
105114
);
106-
};
107-
108-
/**
109-
* Determines if runs mode is allowed based on current page and context
110-
* @param {object} viewModel - Model that manages the state of the page
111-
* @returns {boolean} - whether runs mode is allowed
112-
*/
115+
}
113116

114117
/**
115118
* Button which will allow the user to update filter parameters after the input

QualityControl/public/common/filters/model/FilterModel.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@ import { Observable } from '/js/src/index.js';
1616
import { buildQueryParametersString } from '../../buildQueryParametersString.js';
1717
import FilterService from '../../../services/Filter.service.js';
1818
import { RunStatus } from '../../../library/runStatus.enum.js';
19+
import { prettyFormatDate } from '../../utils.js';
20+
1921
const CCDB_QUERY_PARAMS = ['PeriodName', 'PassName', 'RunNumber', 'RunType'];
2022

23+
const RUN_INFORMATION_MAP = {
24+
startTime: prettyFormatDate,
25+
endTime: prettyFormatDate,
26+
};
27+
2128
/**
2229
* Model namespace that manages the filter state in the application.
2330
*/
@@ -39,6 +46,7 @@ export default class FilterModel extends Observable {
3946
this._runStatus = null;
4047
this._isRunModeActivated = false;
4148
this._lastRefresh = null;
49+
this._runInformation = {};
4250

4351
this.ONGOING_RUN_INTERVAL_MS = 15000;
4452
}
@@ -47,7 +55,7 @@ export default class FilterModel extends Observable {
4755
* Look for parameters used for filtering in URL and apply them in the layout if it exists
4856
* @returns {undefined}
4957
*/
50-
setFilterFromURL() {
58+
async setFilterFromURL() {
5159
const parameters = this.model.router.params;
5260
CCDB_QUERY_PARAMS.forEach((filterKey) => {
5361
if (parameters[filterKey]) {
@@ -64,6 +72,7 @@ export default class FilterModel extends Observable {
6472
Other: () => null,
6573
});
6674

75+
await this.updateRunInformation();
6776
this.notify();
6877
}
6978

@@ -114,12 +123,15 @@ export default class FilterModel extends Observable {
114123
*/
115124
async triggerFilter(baseViewModel) {
116125
this.setFilterToURL();
126+
await this.updateRunInformation();
127+
117128
if (this.isRunModeActivated) {
118129
this.runNumber = this._filterMap['RunNumber'];
119-
this.runStatus = await this.filterService.getRunStatus(this.runNumber);
130+
this.runStatus = this.runInformation.runStatus ?? RunStatus.UNKNOWN;
120131
this.notify();
121132
this._manageRunsModeInterval(baseViewModel, true);
122133
}
134+
123135
baseViewModel.triggerFilter();
124136
this._lastRefresh = Date.now();
125137
this.notify();
@@ -150,6 +162,7 @@ export default class FilterModel extends Observable {
150162
*/
151163
clearFilters() {
152164
this._filterMap = {};
165+
this._runInformation = {};
153166
this.setFilterToURL(true);
154167
this.notify();
155168
}
@@ -285,6 +298,19 @@ export default class FilterModel extends Observable {
285298
}
286299
}
287300

301+
/**
302+
* Updates the `runInformation` property by fetching data from the `filterService`.
303+
* If `this.runNumber` is defined, it asynchronously retrieves the run information
304+
* via `filterService.getRunInformation(runNumber)` and sets it to `runInformation`,
305+
* automatically applying any filtering and transformations defined in the setter.
306+
* If `this.runNumber` is not defined, `runInformation` is reset to an empty object.
307+
* @returns {undefined}
308+
*/
309+
async updateRunInformation() {
310+
const runNumber = this._filterMap['RunNumber'];
311+
this.runInformation = runNumber ? await this.filterService.getRunInformation(runNumber) : {};
312+
}
313+
288314
/**
289315
* Gets the current run number.
290316
* @returns {number} The run number.
@@ -301,6 +327,34 @@ export default class FilterModel extends Observable {
301327
this._runNumber = value;
302328
}
303329

330+
/**
331+
* Gets the current run information.
332+
* @returns {object} The run information.
333+
*/
334+
get runInformation() {
335+
return this._runInformation;
336+
}
337+
338+
/**
339+
* Sets the run information after filtering and transforming values.
340+
* - Filters out properties that are `null` or `undefined`.
341+
* - Applies a transformation function from `RUN_INFORMATION_MAP` for any matching keys.
342+
* @param {RunInformation} value - The new run information object to set.
343+
* Keys corresponding to functions in `RUN_INFORMATION_MAP` will be transformed accordingly.
344+
* @returns {undefined}
345+
*/
346+
set runInformation(value) {
347+
const runInfo = value && typeof value === 'object' ? value : {};
348+
const transformed = Object.entries(runInfo)
349+
// Filters out properties that are `null` or `undefined`.
350+
.filter(([_, v]) => v !== null && v !== undefined)
351+
// Applies a transformation function from `RUN_INFORMATION_MAP` for any matching keys.
352+
.map(([key, value]) =>
353+
[key, typeof RUN_INFORMATION_MAP[key] === 'function' ? RUN_INFORMATION_MAP[key](value) : value]);
354+
355+
this._runInformation = Object.fromEntries(transformed);
356+
}
357+
304358
/**
305359
* Gets the current run status.
306360
* @returns {RemoteData} The run status.

QualityControl/public/common/filters/runMode/runStatusPanel.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import { RunStatus } from '../../../../../library/runStatus.enum.js';
1515
import { h } from '/js/src/index.js';
16+
import { camelToTitleCase } from '../../utils.js';
17+
import { statusBadge } from '../../badge.js';
1618

1719
/**
1820
* Creates and returns a run status panel element displaying the current run number,
@@ -53,3 +55,48 @@ export const lastUpdatePanel = (runStatus, lastRefresh, refreshRate = 15000) =>
5355
),
5456
]);
5557
};
58+
59+
/**
60+
* Renders the run information panel
61+
* @param {object} cleanRunInformation - The `RunInformation` without `detectorsQualities`
62+
* @returns {vnode} - virtual node element
63+
*/
64+
export const cleanRunInformationPanel = (cleanRunInformation) =>
65+
cleanRunInformation && Object.keys(cleanRunInformation).length > 0 && h(
66+
'.flex-row.g4.items-center.f7.gray-darker.text-center.ph4',
67+
{
68+
id: 'header-run-information',
69+
style: 'overflow-x: auto; margin: 0 auto;',
70+
},
71+
Object.entries(cleanRunInformation).map(([key, value]) =>
72+
h('.flex-row.g1', {
73+
key: `${key}-${value}`,
74+
style: 'flex: 0 0 auto;',
75+
}, [
76+
h('strong', `${camelToTitleCase(key)}:`),
77+
h('span', `${value}`),
78+
])),
79+
);
80+
81+
/**
82+
* Renders the detector qualities panel
83+
* @param {DetectorQuality[]} detectorsQualities - The detector qualities of the run
84+
* @returns {vnode} - virtual node element
85+
*/
86+
export const detectorsQualitiesPanel = (detectorsQualities) =>
87+
Array.isArray(detectorsQualities) && detectorsQualities.length > 0 && h(
88+
'.flex-row.g3.items-center.f7.gray-darker.text-center.ph3',
89+
{
90+
id: 'header-detector-qualities',
91+
style: 'overflow-x: auto;',
92+
},
93+
detectorsQualities.map(({ id, name, quality }) =>
94+
h(
95+
'.flex-row.g1',
96+
{
97+
key: `${id}-${name}-${quality}`,
98+
style: 'flex: 0 0 auto;',
99+
},
100+
statusBadge(name, quality === 'good'),
101+
)),
102+
);

QualityControl/public/services/Filter.service.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,22 @@ export default class FilterService {
5050
/**
5151
* Method to get run status for a specific run number
5252
* @param {number} runNumber - The run number to get status for
53-
* @returns {RemoteData} - result within a RemoteData object
53+
* @returns {object} - result as an object containing run information
5454
*/
55-
async getRunStatus(runNumber) {
55+
async getRunInformation(runNumber) {
5656
const parsedRunNumber = parseInt(runNumber, 10);
5757
const { result, ok } = await this.loader.get(`/api/filter/run-status/${parsedRunNumber}`);
58-
return ok ? result?.runStatus : RunStatus.UNKNOWN;
58+
return ok ? result : {};
59+
}
60+
61+
/**
62+
* Method to get run status for a specific run number
63+
* @param {number} runNumber - The run number to get status for
64+
* @returns {RunStatus} - result as a run status
65+
*/
66+
async getRunStatus(runNumber) {
67+
const { runStatus } = await this.getRunInformation(runNumber);
68+
return runStatus ?? RunStatus.UNKNOWN;
5969
}
6070

6171
/**

0 commit comments

Comments
 (0)