Skip to content

Commit 1da5d94

Browse files
authored
[OGUI-1840 & OGUI-1841] Refactor the response object of retrieveRunStatus (#3229)
* The method `retrieveRunStatus` is renamed to `retrieveRunInformation` * The method now returns an object with established run information: * `runStatus`: this is the field created right now by QCG and should be kept to ensure front-end compatibility * time at which the run has started - `startTime` * time at which the run has run ended - `endTime` * the run belongs to a partition also known as environment: `environmentId` * the run also is defined by multiple properties. Depending on which oneas are used the run has a definition: `definition` * the run has a quality that decides if it should be stored or not for long time: `runQuality` * run normally runs only during an LHC beam mode: `lhcBeamMode` * A run has multiple detectors taking data, thus we should also get the list of detectors and qualityies: `detectorQualities` * All methods making use of this have been updated accordingly: * Methods in `FilterService` * Methods in `FilterController`
1 parent 436a181 commit 1da5d94

File tree

10 files changed

+198
-70
lines changed

10 files changed

+198
-70
lines changed

QualityControl/lib/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const setup = async (http, ws, eventEmitter) => {
109109
http.get(
110110
'/filter/run-status/:runNumber',
111111
runStatusFilterMiddleware,
112-
filterController.getRunStatusHandler.bind(filterController),
112+
filterController.getRunInformationHandler.bind(filterController),
113113
);
114114
http.get(
115115
'/filter/ongoingRuns',

QualityControl/lib/controllers/FilterController.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@ export class FilterController {
4646
* @param {Request} req - HTTP request
4747
* @param {Response} res - HTTP response to provide run status information
4848
*/
49-
async getRunStatusHandler(req, res) {
49+
async getRunInformationHandler(req, res) {
5050
try {
51-
const runStatus = await this._filterService.getRunStatus(req.params.runNumber);
52-
res.status(200).json({
53-
runStatus,
54-
});
51+
const runInformation = await this._filterService.getRunInformation(req.params.runNumber);
52+
res.status(200).json(runInformation);
5553
} catch (error) {
5654
this._logger.errorMessage('Error getting run status:', error);
5755
updateAndSendExpressResponseFromNativeError(res, error);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
/**
16+
* Quality information for a single detector participating in the run.
17+
* @typedef {object} DetectorQuality
18+
* @property {number} id - Unique detector identifier.
19+
* @property {string} name - The name (abbreviation) of the detector.
20+
* @property {string} quality - Quality flag or classification for the detector.
21+
*/
22+
23+
/**
24+
* Bookkeeping run information.
25+
* @typedef {object} RunInformation
26+
* @property {RunStatus} runStatus - Custom status for front-end consumption:
27+
* - ONGOING: run is currently ongoing
28+
* - ENDED: run has completed (timeO2End is present)
29+
* - NOT_FOUND: run data does not exist
30+
* - UNKNOWN: error occurred or data unavailable
31+
* @property {number} startTime - Time (epoch) at which the run started.
32+
* @property {number|undefined} endTime - Time (epoch) at which the run ended. If `undefined`, the run hasn't ended yet.
33+
* @property {number|undefined} environmentId - Partition/environment the run belongs to.
34+
* @property {string|undefined} definition - The definition of the run.
35+
* @property {string} runQuality - Overall run quality.
36+
* @property {string|undefined} lhcBeamMode - LHC beam mode during which the run was taken, if any.
37+
* @property {DetectorQuality[]} detectorsQualities - Per-detector quality information.
38+
*/
39+
40+
/**
41+
* Wrapped Run Status object
42+
* @typedef {object} WrappedRunStatus
43+
* @property {RunStatus} runStatus - The Run Status
44+
*/
45+
46+
/**
47+
* Wraps a given run status value into a standardized result object.
48+
* Use this helper when you need to return only a `runStatus` field without any
49+
* additional payload. This ensures that all callers receive a consistent
50+
* object shape, matching the structure returned by `retrieveRunInformation`.
51+
* @param {RunStatus} runStatus The run status to wrap. Must be a valid `RunStatus` enum value.
52+
* @returns {WrappedRunStatus} A simple object containing only the provided `runStatus`.
53+
*/
54+
export function wrapRunStatus(runStatus) {
55+
return { runStatus };
56+
}

QualityControl/lib/services/BookkeepingService.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import { RunStatus } from '../../common/library/runStatus.enum.js';
1616
import { httpGetJson } from '../utils/httpRequests.js';
1717
import { LogManager } from '@aliceo2/web-ui';
18+
import { wrapRunStatus } from '../dtos/BookkeepingDto.js';
1819

1920
const GET_BKP_DATABASE_STATUS_PATH = '/api/status/database';
2021
const GET_RUN_TYPES_PATH = '/api/runTypes';
@@ -127,17 +128,14 @@ export class BookkeepingService {
127128
}
128129

129130
/**
130-
* Retrieves the status of a specific run from the Bookkeeping service
131+
* Retrieves the information of a specific run from the Bookkeeping service
131132
* @param {number} runNumber - The run number to check the status for
132-
* @returns {Promise<RunStatus>} - Returns a promise that resolves to the run status:
133-
* - RunStatus.ONGOING if the run is ongoing
134-
* - RunStatus.ENDED if the run has completed (has timeO2End)
135-
* - RunStatus.NOT_FOUND if there was an error or data is not available
133+
* @returns {Promise<RunInformation|WrappedRunStatus>} - Returns a promise that resolves to the run information
136134
*/
137-
async retrieveRunStatus(runNumber) {
135+
async retrieveRunInformation(runNumber) {
138136
if (!this.active) {
139137
this._logger.warnMessage('Could not connect to bookkeeping');
140-
return RunStatus.BOOKKEEPING_UNAVAILABLE;
138+
return wrapRunStatus(RunStatus.BOOKKEEPING_UNAVAILABLE);
141139
}
142140

143141
try {
@@ -150,15 +148,36 @@ export class BookkeepingService {
150148
throw new Error('No data available');
151149
}
152150

153-
return data.timeO2End ? RunStatus.ENDED : RunStatus.ONGOING;
151+
const {
152+
startTime,
153+
endTime,
154+
environmentId,
155+
definition,
156+
runQuality,
157+
lhcBeamMode,
158+
detectorsQualities = [],
159+
timeO2End,
160+
} = data;
161+
const runStatus = timeO2End ? RunStatus.ENDED : RunStatus.ONGOING;
162+
163+
return {
164+
startTime,
165+
endTime,
166+
environmentId,
167+
definition,
168+
runQuality,
169+
lhcBeamMode,
170+
detectorsQualities,
171+
...wrapRunStatus(runStatus),
172+
};
154173
} catch (error) {
155174
const msg = error?.message ?? String(error);
156175
if (msg.includes('404')) {
157176
this._logger.warnMessage(`Run number ${runNumber} not found in bookkeeping`);
158-
return RunStatus.NOT_FOUND;
177+
return wrapRunStatus(RunStatus.NOT_FOUND);
159178
}
160179
this._logger.errorMessage(`Error fetching run status: ${error.message || error}`);
161-
return RunStatus.UNKNOWN;
180+
return wrapRunStatus(RunStatus.UNKNOWN);
162181
}
163182
}
164183

QualityControl/lib/services/FilterService.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import { LogManager } from '@aliceo2/web-ui';
1616
import { RunStatus } from '../../common/library/runStatus.enum.js';
17+
import { wrapRunStatus } from '../dtos/BookkeepingDto.js';
1718

1819
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/filter-service`;
1920

@@ -87,18 +88,17 @@ export class FilterService {
8788
}
8889

8990
/**
90-
* This method is used to retrieve the run status from the bookkeeping service
91-
* @param {number} runNumber - run number to retrieve the status for
92-
* @returns {Promise<string>} - resolves with the run status
91+
* This method is used to retrieve the run information from the bookkeeping service
92+
* @param {number} runNumber - run number to retrieve the information for
93+
* @returns {Promise<object>} - resolves with the run information
9394
*/
94-
async getRunStatus(runNumber) {
95+
async getRunInformation(runNumber) {
9596
try {
96-
const runStatus = await this._bookkeepingService.retrieveRunStatus(runNumber);
97-
return runStatus;
97+
return await this._bookkeepingService.retrieveRunInformation(runNumber);
9898
} catch (error) {
9999
const message = `Error while retrieving run status for run ${runNumber}: ${error.message || error}`;
100100
this._logger.errorMessage(message);
101-
return RunStatus.UNKNOWN;
101+
return wrapRunStatus(RunStatus.UNKNOWN);
102102
}
103103
}
104104
}

QualityControl/lib/services/RunModeService.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class RunModeService {
6363
return { paths: cachedPaths };
6464
}
6565

66-
const runStatus = await this._bookkeepingService.retrieveRunStatus(runNumber);
66+
const { runStatus } = await this._bookkeepingService.retrieveRunInformation(runNumber);
6767
const rawPaths = await this._dataService.getObjectsLatestVersionList({
6868
filters: { RunNumber: runNumber },
6969
});
@@ -88,7 +88,7 @@ export class RunModeService {
8888
async refreshRunsCache() {
8989
for (const [runNumber] of this._ongoingRuns.entries()) {
9090
try {
91-
const runStatus = await this._bookkeepingService.retrieveRunStatus(runNumber);
91+
const { runStatus } = await this._bookkeepingService.retrieveRunInformation(runNumber);
9292
if (runStatus === RunStatus.ONGOING) {
9393
const updatedPaths = await this._dataService.getObjectsLatestVersionList({
9494
filters: { RunNumber: runNumber },

QualityControl/test/lib/controllers/FiltersController.test.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export const filtersControllerTestSuite = async () => {
7373
suite('getRunStatusHandler', async () => {
7474
test('should successfully retrieve run status from FilterService', async () => {
7575
const filterService = sinon.createStubInstance(FilterService);
76-
filterService.getRunStatus.resolves(RunStatus.ONGOING);
76+
filterService.getRunInformation.resolves({
77+
runStatus: RunStatus.ONGOING,
78+
});
7779

7880
const req = {
7981
params: {
@@ -86,9 +88,12 @@ export const filtersControllerTestSuite = async () => {
8688
};
8789

8890
const filterController = new FilterController(filterService);
89-
await filterController.getRunStatusHandler(req, res);
91+
await filterController.getRunInformationHandler(req, res);
9092

91-
ok(filterService.getRunStatus.calledWith(123456), 'FilterService.getRunStatus should be called with run number');
93+
ok(
94+
filterService.getRunInformation.calledWith(123456),
95+
'FilterService.getRunInformation should be called with run number',
96+
);
9297
ok(res.status.calledWith(200), 'Response status should be 200');
9398
ok(res.json.calledWith({
9499
runStatus: RunStatus.ONGOING,
@@ -98,7 +103,7 @@ export const filtersControllerTestSuite = async () => {
98103
test('should handle errors from FilterService and send error response', async () => {
99104
const filterService = sinon.createStubInstance(FilterService);
100105
const testError = new Error('Bookkeeping service unavailable');
101-
filterService.getRunStatus.rejects(testError);
106+
filterService.getRunInformation.rejects(testError);
102107

103108
const req = {
104109
params: {
@@ -111,9 +116,12 @@ export const filtersControllerTestSuite = async () => {
111116
};
112117

113118
const filterController = new FilterController(filterService);
114-
await filterController.getRunStatusHandler(req, res);
119+
await filterController.getRunInformationHandler(req, res);
115120

116-
ok(filterService.getRunStatus.calledWith(123456), 'FilterService.getRunStatus should be called with run number');
121+
ok(
122+
filterService.getRunInformation.calledWith(123456),
123+
'FilterService.getRunStatus should be called with run number',
124+
);
117125
ok(res.status.calledWith(500), 'Response status should be 500 for service errors');
118126
ok(res.json.calledWithMatch({
119127
message: 'Bookkeeping service unavailable',
@@ -124,7 +132,9 @@ export const filtersControllerTestSuite = async () => {
124132

125133
test('should return UNKNOWN status when FilterService returns invalid status', async () => {
126134
const filterService = sinon.createStubInstance(FilterService);
127-
filterService.getRunStatus.resolves('UNKNOWN');
135+
filterService.getRunInformation.resolves({
136+
runStatus: RunStatus.UNKNOWN,
137+
});
128138

129139
const req = {
130140
params: {
@@ -137,9 +147,12 @@ export const filtersControllerTestSuite = async () => {
137147
};
138148

139149
const filterController = new FilterController(filterService);
140-
await filterController.getRunStatusHandler(req, res);
150+
await filterController.getRunInformationHandler(req, res);
141151

142-
ok(filterService.getRunStatus.calledWith(999999), 'FilterService.getRunStatus should be called with run number');
152+
ok(
153+
filterService.getRunInformation.calledWith(999999),
154+
'FilterService.getRunStatus should be called with run number',
155+
);
143156
ok(res.status.calledWith(200), 'Response status should be 200');
144157
ok(res.json.calledWith({
145158
runStatus: RunStatus.UNKNOWN,

QualityControl/test/lib/services/BookkeepingService.test.js

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -256,31 +256,59 @@ export const bookkeepingServiceTestSuite = async () => {
256256
};
257257

258258
nock(VALID_CONFIG.bookkeeping.url).get(runsPathPattern).reply(200, mockResponse);
259-
const result = await bkpService.retrieveRunStatus(123);
260-
strictEqual(result, RunStatus.ENDED);
259+
const { runStatus } = await bkpService.retrieveRunInformation(123);
260+
strictEqual(runStatus, RunStatus.ENDED);
261+
});
262+
263+
test('should return run information when data is present', async () => {
264+
const mockResponse = {
265+
data: {
266+
startTime: 1,
267+
endTime: 2,
268+
definition: null,
269+
runQuality: 'good',
270+
lhcBeamMode: 'PHYSICS',
271+
detectorsQualities: [],
272+
},
273+
};
274+
275+
nock(VALID_CONFIG.bookkeeping.url).get(runsPathPattern).reply(200, mockResponse);
276+
const {
277+
startTime,
278+
endTime,
279+
definition,
280+
runQuality,
281+
lhcBeamMode,
282+
detectorsQualities,
283+
runStatus,
284+
} = await bkpService.retrieveRunInformation(123);
285+
const data = { startTime, endTime, definition, runQuality, lhcBeamMode, detectorsQualities };
286+
287+
deepStrictEqual(data, mockResponse.data);
288+
ok(Object.values(RunStatus).includes(runStatus));
261289
});
262290

263291
test('should return ONGOING status when timeO2End is not present', async () => {
264292
const mockResponse = { data: { timeO2End: undefined } };
265293

266294
nock(VALID_CONFIG.bookkeeping.url).get(runsPathPattern).reply(200, mockResponse);
267295

268-
const result = await bkpService.retrieveRunStatus(456);
269-
strictEqual(result, RunStatus.ONGOING);
296+
const { runStatus } = await bkpService.retrieveRunInformation(456);
297+
strictEqual(runStatus, RunStatus.ONGOING);
270298
});
271299

272300
test('should return UNKNOWN status when no data is returned', async () => {
273301
nock(VALID_CONFIG.bookkeeping.url).get(runsPathPattern).reply(200, {});
274302

275-
const result = await bkpService.retrieveRunStatus(789);
276-
strictEqual(result, RunStatus.UNKNOWN);
303+
const { runStatus } = await bkpService.retrieveRunInformation(789);
304+
strictEqual(runStatus, RunStatus.UNKNOWN);
277305
});
278306

279307
test('should return UNKNOWN status when request fails', async () => {
280308
nock(VALID_CONFIG.bookkeeping.url).get(runsPathPattern).reply(500);
281309

282-
const result = await bkpService.retrieveRunStatus(1010);
283-
strictEqual(result, RunStatus.UNKNOWN);
310+
const { runStatus } = await bkpService.retrieveRunInformation(1010);
311+
strictEqual(runStatus, RunStatus.UNKNOWN);
284312
});
285313

286314
test('should return NOT_FOUND status when request fails', async () => {
@@ -293,15 +321,15 @@ export const bookkeepingServiceTestSuite = async () => {
293321
],
294322
});
295323

296-
const result = await bkpService.retrieveRunStatus(1010);
297-
strictEqual(result, RunStatus.NOT_FOUND);
324+
const { runStatus } = await bkpService.retrieveRunInformation(1010);
325+
strictEqual(runStatus, RunStatus.NOT_FOUND);
298326
});
299327

300328
test('should return BOOKKEEPING_UNAVAILABLE status when service is not active', async () => {
301329
bkpService.active = false;
302330

303-
const result = await bkpService.retrieveRunStatus(123);
304-
strictEqual(result, RunStatus.BOOKKEEPING_UNAVAILABLE);
331+
const { runStatus } = await bkpService.retrieveRunInformation(123);
332+
strictEqual(runStatus, RunStatus.BOOKKEEPING_UNAVAILABLE);
305333
});
306334
});
307335
});

0 commit comments

Comments
 (0)