Skip to content

Commit ca4e92d

Browse files
[OGUI-1722] Runs mode backend: adapt for frontend support (#3022)
This PR updates the backend to support the frontend in utilizing the `Runs Mode` functionality. Changes include: - Added a new endpoint to fetch the status of runs by providing the run number. - Implemented middlewares for validation in controllers when a run number is provided. - Updated tests to cover the new endpoint and validation logic.
1 parent b2fe1e0 commit ca4e92d

17 files changed

Lines changed: 709 additions & 51 deletions

QualityControl/common/library/runStatus.enum.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export const RunStatus = Object.freeze({
1616
ENDED: 'ENDED',
1717
ONGOING: 'ONGOING',
1818
NOT_FOUND: 'NOT_FOUND',
19+
UNKNOWN: 'UNKNOWN',
1920
});

QualityControl/lib/api.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { layoutOwnerMiddleware } from './middleware/layouts/layoutOwner.middlewa
1919
import { layoutIdMiddleware } from './middleware/layouts/layoutId.middleware.js';
2020
import { layoutServiceMiddleware } from './middleware/layouts/layoutService.middleware.js';
2121
import { statusComponentMiddleware } from './middleware/status/statusComponent.middleware.js';
22+
import { runStatusFilterMiddleware } from './middleware/filters/runStatusFilter.middleware.js';
23+
import { runModeMiddleware } from './middleware/filters/runMode.middleware.js';
2224

2325
/**
2426
* Adds paths and binds websocket to instance of HttpServer passed
@@ -55,7 +57,12 @@ export const setup = (http, ws) => {
5557
http.get('/object/:id', objectGetByIdValidation, objectController.getObjectById.bind(objectController));
5658
http.get('/object', objectGetContentsValidation, objectController.getObjectContent.bind(objectController));
5759

58-
http.get('/objects', objectsGetValidation, objectController.getObjects.bind(objectController), { public: true });
60+
http.get(
61+
'/objects',
62+
objectsGetValidation,
63+
runModeMiddleware,
64+
objectController.getObjects.bind(objectController),
65+
);
5966

6067
http.get('/layouts', layoutController.getLayoutsHandler.bind(layoutController));
6168
http.get('/layout/:id', layoutController.getLayoutHandler.bind(layoutController));
@@ -94,4 +101,9 @@ export const setup = (http, ws) => {
94101
http.get('/checkUser', userController.addUserHandler.bind(userController));
95102

96103
http.get('/filter/configuration', filterController.getFilterConfigurationHandler.bind(filterController));
104+
http.get(
105+
'/filter/run-status/:runNumber',
106+
runStatusFilterMiddleware,
107+
filterController.getRunStatusHandler.bind(filterController),
108+
);
97109
};

QualityControl/lib/controllers/FilterController.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
* or submit itself to any jurisdiction.
1313
*/
1414

15+
import {
16+
LogManager,
17+
updateAndSendExpressResponseFromNativeError,
18+
}
19+
from '@aliceo2/web-ui';
20+
1521
/**
1622
* Gateaway class to be used to retrieve data with regard to filters
1723
*/
@@ -25,6 +31,24 @@ export class FilterController {
2531
* @type {FilterService}
2632
*/
2733
this._filterService = filterService;
34+
this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/filter-ctrl`);
35+
}
36+
37+
/**
38+
* HTTP GET endpoint for retrieving the status of a run from Bookkeeping
39+
* @param {Request} req - HTTP request
40+
* @param {Response} res - HTTP response to provide run status information
41+
*/
42+
async getRunStatusHandler(req, res) {
43+
try {
44+
const runStatus = await this._filterService.getRunStatus(req.params.runNumber);
45+
res.status(200).json({
46+
runStatus: runStatus,
47+
});
48+
} catch (error) {
49+
this._logger.errorMessage('Error getting run status:', error);
50+
updateAndSendExpressResponseFromNativeError(res, error);
51+
}
2852
}
2953

3054
/**

QualityControl/lib/controllers/ObjectController.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* or submit itself to any jurisdiction.
1313
*/
1414
'use strict';
15-
import { InvalidInputError, LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';
15+
import { LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';
1616

1717
/**
1818
* Gateway for all QC Objects requests
@@ -48,19 +48,10 @@ export class ObjectController {
4848
try {
4949
const { prefix, fields, filters = {}, inRunMode = false } = req.query;
5050

51-
const { RunNumber: runNumber } = filters;
52-
const parsedRunNumber = parseInt(runNumber, 10);
53-
54-
if (inRunMode && (!runNumber || isNaN(parsedRunNumber))) {
55-
return updateAndSendExpressResponseFromNativeError(
56-
res,
57-
new InvalidInputError(!runNumber
58-
? 'RunNumber is required when in run mode'
59-
: 'RunNumber must be a number'),
60-
);
61-
} else if (inRunMode && runNumber && !isNaN(parsedRunNumber)) {
62-
const { paths, runStatus } = await this._runModeService.retrievePathsAndSetRunStatus(parsedRunNumber, prefix);
63-
return res.status(200).json({ paths, runStatus });
51+
if (inRunMode) {
52+
const runNumber = filters?.RunNumber;
53+
const { paths } = await this._runModeService.retrievePathsAndSetRunStatus(runNumber);
54+
return res.status(200).json({ paths });
6455
}
6556

6657
const objectsData = await this._objService.retrieveLatestVersionOfObjects({
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 Joi from 'joi';
16+
17+
export const RunNumberDto = Joi.number()
18+
.required()
19+
.integer()
20+
.min(0)
21+
.max(999999)
22+
.messages({
23+
'any.required': 'Run number is required',
24+
'number.base': 'Run number must be a number',
25+
'number.integer': 'Run number must be an integer',
26+
'number.min': 'Run number must be positive',
27+
'number.max': 'Run number must not exceed 999999',
28+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 { InvalidInputError, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';
16+
import { RunNumberDto } from '../../dtos/filters/RunNumberDto.js';
17+
18+
/**
19+
* Middleware function to validate the run number if in run mode.
20+
* @param {object} req - The request object.
21+
* @param {object} res - The response object.
22+
* @param {Function} next - The next middleware function in the stack.
23+
* @returns {Promise<void>}
24+
*/
25+
export const runModeMiddleware = async (req, res, next) => {
26+
const { inRunMode = false, filters = {} } = req.query;
27+
if (!inRunMode) {
28+
next();
29+
return;
30+
}
31+
try {
32+
const validatedRunNumber = await RunNumberDto.validateAsync(filters?.RunNumber);
33+
req.query.filters = { ...filters, RunNumber: validatedRunNumber };
34+
next();
35+
} catch (error) {
36+
updateAndSendExpressResponseFromNativeError(
37+
res,
38+
error.isJoi
39+
? new InvalidInputError(error.details[0].message)
40+
: error,
41+
);
42+
}
43+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 { InvalidInputError, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';
16+
import { RunNumberDto } from '../../dtos/filters/RunNumberDto.js';
17+
18+
/**
19+
* Middleware function to validate the run number and attach it to the request object.
20+
f
21+
* @param {object} req - The request object.
22+
* @param {object} res - The response object.
23+
* @param {Function} next - The next middleware function in the stack.
24+
* @returns {Promise<void>}
25+
*/
26+
export const runStatusFilterMiddleware = async (req, res, next) => {
27+
try {
28+
const validatedRunNumber = await RunNumberDto.validateAsync(req.params.runNumber);
29+
req.params.runNumber = validatedRunNumber;
30+
next();
31+
} catch (error) {
32+
updateAndSendExpressResponseFromNativeError(
33+
res,
34+
error.isJoi
35+
? new InvalidInputError(error.details[0].message)
36+
: error,
37+
);
38+
}
39+
};

QualityControl/lib/services/FilterService.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
*/
1414

1515
import { LogManager } from '@aliceo2/web-ui';
16-
const logger = LogManager.getLogger('filter/service');
16+
import { RunStatus } from '../../common/library/runStatus.enum.js';
17+
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/filter-svc`;
1718

1819
/**
1920
* High level service that composes, processes and maps data from the bookkeeping service
@@ -25,14 +26,15 @@ export class FilterService {
2526
* @param {object} config - Config object file that defines the refresh intervals for checking run status and runtypes
2627
*/
2728
constructor(bookkeepingService, config) {
29+
this._logger = LogManager.getLogger(LOG_FACILITY);
2830
this._bookkeepingService = bookkeepingService;
2931
this._runTypes = [];
3032

3133
this._runTypesRefreshInterval = config?.bookkeeping?.runTypesRefreshInterval ??
3234
(config?.bookkeeping ? 24 * 60 * 60 * 1000 : -1);
3335

3436
this.initFilters().catch((error) => {
35-
logger.errorMessage(`FilterService initialization failed: ${error.message || error}`);
37+
this._logger.errorMessage(`FilterService initialization failed: ${error.message || error}`);
3638
});
3739
}
3840

@@ -61,7 +63,7 @@ export class FilterService {
6163
}
6264
this._runTypes.sort();
6365
} catch (error) {
64-
logger.errorMessage(`Error while retrieving run types: ${error.message || error}`);
66+
this._logger.errorMessage(`Error while retrieving run types: ${error.message || error}`);
6567
this._runTypes = [];
6668
}
6769
}
@@ -81,4 +83,25 @@ export class FilterService {
8183
get runTypes() {
8284
return [...this._runTypes];
8385
}
86+
87+
/**
88+
* This method is used to retrieve the run status from the bookkeeping service
89+
* @param {number} runNumber - run number to retrieve the status for
90+
* @returns {Promise<string>} - resolves with the run status
91+
*/
92+
async getRunStatus(runNumber) {
93+
try {
94+
const runStatus = await this._bookkeepingService.retrieveRunStatus(runNumber);
95+
96+
if (!runStatus || !Object.values(RunStatus).includes(runStatus)) {
97+
this._logger.warnMessage(`Invalid run status received for run ${runNumber}: ${runStatus}`);
98+
return RunStatus.UNKNOWN;
99+
}
100+
return runStatus;
101+
} catch (error) {
102+
const message = `Error while retrieving run status for run ${runNumber}: ${error.message || error}`;
103+
this._logger.errorMessage(message);
104+
return RunStatus.UNKNOWN;
105+
}
106+
}
84107
}

QualityControl/lib/services/RunModeService.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class RunModeService {
5454
async retrievePathsAndSetRunStatus(runNumber) {
5555
if (this._ongoingRuns.has(runNumber)) {
5656
const cachedPaths = parseObjects(this._ongoingRuns.get(runNumber), QCObjectDto);
57-
return { paths: cachedPaths, runStatus: RunStatus.ONGOING };
57+
return { paths: cachedPaths };
5858
}
5959

6060
const runStatus = await this._bookkeepingService.retrieveRunStatus(runNumber);
@@ -70,7 +70,6 @@ export class RunModeService {
7070

7171
return {
7272
paths: parsedPaths,
73-
runStatus,
7473
};
7574
}
7675

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 { suite, test } from 'node:test';
16+
import { URL_ADDRESS, OWNER_TEST_TOKEN } from '../config.js';
17+
import request from 'supertest';
18+
19+
export const apiGetRunStatusTests = () => {
20+
suite('GET /filter/run-status/:runNumber', () => {
21+
test('should return a 403 error if no authentication token is provided', async () => {
22+
await request(`${URL_ADDRESS}/api/filter/run-status/123456`)
23+
.get('')
24+
.expect(403);
25+
});
26+
27+
test('should return a 404 error if run number is not provided', async () => {
28+
await request(`${URL_ADDRESS}/api/filter/run-status/`)
29+
.get(`?token=${OWNER_TEST_TOKEN}`)
30+
.expect(404, {
31+
error: '404 - Page not found',
32+
message: 'The requested URL was not found on this server.',
33+
});
34+
});
35+
36+
test('should return a 400 error for invalid run number (negative)', async () => {
37+
await request(`${URL_ADDRESS}/api/filter/run-status/-1`)
38+
.get(`?token=${OWNER_TEST_TOKEN}`)
39+
.expect(400, {
40+
message: 'Run number must be positive',
41+
status: 400,
42+
title: 'Invalid Input',
43+
});
44+
});
45+
46+
test('should return a 400 error for invalid run number (too large)', async () => {
47+
await request(`${URL_ADDRESS}/api/filter/run-status/1000000`)
48+
.get(`?token=${OWNER_TEST_TOKEN}`)
49+
.expect(400, {
50+
message: 'Run number must not exceed 999999',
51+
status: 400,
52+
title: 'Invalid Input',
53+
});
54+
});
55+
56+
test('should return a 400 error for invalid run number (not a number)', async () => {
57+
await request(`${URL_ADDRESS}/api/filter/run-status/invalid`)
58+
.get(`?token=${OWNER_TEST_TOKEN}`)
59+
.expect(400, {
60+
message: 'Run number must be a number',
61+
status: 400,
62+
title: 'Invalid Input',
63+
});
64+
});
65+
66+
test('should successfully get run status for valid run number', async () => {
67+
await request(`${URL_ADDRESS}/api/filter/run-status/123456`)
68+
.get(`?token=${OWNER_TEST_TOKEN}`)
69+
.expect(200);
70+
});
71+
});
72+
};

0 commit comments

Comments
 (0)