Skip to content

Commit

Permalink
[OGUI-1432] Add run controller for allowing retrieval of calibration …
Browse files Browse the repository at this point in the history
…runs (#2169)

* Fixes the runAdpter for summary information
* Adds enum for run definitions
* Small fixes in `RunService`
* Adds tests to `RunService` and `RunController`
* Adds controller for runs
  • Loading branch information
graduta authored Oct 23, 2023
1 parent eb5d603 commit 1173ab5
Show file tree
Hide file tree
Showing 17 changed files with 302 additions and 94 deletions.
14 changes: 10 additions & 4 deletions Control/lib/adapters/RunSummaryAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class RunSummaryAdapter {
/**
* RunSummaryAdapter
*/
constructor() {}
constructor() { }

/**
* Converts the given object to an entity object.
Expand All @@ -36,16 +36,22 @@ class RunSummaryAdapter {
runType,
startTime,
endTime,
detectors = [],
} = run;
return {

let {detectors = []} = run;
if (typeof detectors === 'string') {
detectors = detectors.split(',');
}
detectors.sort();

return {
runNumber,
environmentId,
definition,
calibrationStatus,
runType: runType?.name,
startTime,
detectors: detectors.sort(),
detectors,
endTime,
};
}
Expand Down
23 changes: 16 additions & 7 deletions Control/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@ const log = new (require('@aliceo2/web-ui').Log)(`${process.env.npm_config_log_l
const config = require('./config/configProvider.js');

// controllers
const {ConsulController} = require('./controllers/Consul.controller.js');
const {EnvironmentController} = require('./controllers/Environment.controller.js');
const {RunController} = require('./controllers/Run.controller.js');
const {StatusController} = require('./controllers/Status.controller.js');
const {WebSocketService} = require('./services/WebSocket.service.js');
const {ConsulController} = require('./controllers/Consul.controller.js');
const {WorkflowTemplateController} = require('./controllers/WorkflowTemplate.controller.js');

// local services
const {BookkeepingService} = require('./services/Bookkeeping.service.js');
const {EnvironmentService} = require('./services/Environment.service.js');
const {Intervals} = require('./services/Intervals.service.js');
const Lock = require('./services/Lock.js');
const {RunService} = require('./services/Run.service.js');
const {StatusService} = require('./services/Status.service.js');
const {Intervals} = require('./services/Intervals.service.js');
const {WorkflowTemplateService} = require('./services/WorkflowTemplate.service.js');

// web-ui services
const {NotificationService, ConsulService} = require('@aliceo2/web-ui');
Expand All @@ -34,13 +41,8 @@ const ControlService = require('./control-core/ControlService.js');
const ApricotService = require('./control-core/ApricotService.js');
const AliecsRequestHandler = require('./control-core/RequestHandler.js');
const EnvCache = require('./control-core/EnvCache.js');
const {EnvironmentService} = require('./services/Environment.service.js');
const {WorkflowTemplateService} = require('./services/WorkflowTemplate.service.js');

const {EnvironmentController} = require('./controllers/Environment.controller.js');

const path = require('path');
const {WorkflowTemplateController} = require('./controllers/WorkflowTemplate.controller.js');
const O2_CONTROL_PROTO_PATH = path.join(__dirname, './../protobuf/o2control.proto');
const O2_APRICOT_PROTO_PATH = path.join(__dirname, './../protobuf/o2apricot.proto');

Expand Down Expand Up @@ -86,6 +88,11 @@ module.exports.setup = (http, ws) => {
const envCache = new EnvCache(ctrlService);
envCache.setWs(ws);

const bkpService = new BookkeepingService(config.bookkeeping ?? {});
const runService = new RunService(bkpService, apricotService);
runService.init();
const runController = new RunController(runService);

const notificationService = new NotificationService(config.kafka);
if (notificationService.isConfigured()) {
notificationService.proxyWebNotificationToWs(ws);
Expand Down Expand Up @@ -115,6 +122,8 @@ module.exports.setup = (http, ws) => {
http.get('/workflow/template/mappings', workflowController.getWorkflowMapping.bind(workflowController))
http.get('/workflow/configuration', workflowController.getWorkflowConfiguration.bind(workflowController));

http.get('/runs/calibration', runController.getCalibrationRunsHandler.bind(runController))

http.get('/environment/:id/:source?', coreMiddleware, envCtrl.getEnvironment.bind(envCtrl), {public: true});
http.get('/core/environments', coreMiddleware, (req, res) => envCache.get(req, res), {public: true});
http.post('/core/environments/configuration/save', (req, res) => apricotService.saveCoreEnvConfig(req, res));
Expand Down
1 change: 0 additions & 1 deletion Control/lib/common/kvStore/runtime.enum.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const RUNTIME_KEY = Object.freeze({
FLP_VERSION: 'flp_suite_version',
PDP_VERSION: 'pdp_o2pdpsuite_version',
CALIBRATION_MAPPING: 'calibration-mappings',

});

module.exports = {RUNTIME_COMPONENT, RUNTIME_KEY};
22 changes: 22 additions & 0 deletions Control/lib/common/runDefinition.enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* 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.
*/

/**
* Run Definitions as per Bookkeeping's implementation: https://github.com/AliceO2Group/Bookkeeping/blob/main/docs/RUN_DEFINITIONS.md
*/
const RUN_DEFINITIONS = Object.freeze({
CALIBRATION: 'CALIBRATION'
});

exports.RUN_DEFINITIONS = RUN_DEFINITIONS;
2 changes: 0 additions & 2 deletions Control/lib/control-core/RequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ class RequestHandler {
variables.odc_n_epns = odc_n_epns;
}
req.body.vars = variables;
console.log('-------------------------');
console.log(req.body.vars);
} catch (error) {
console.error(error);
}
Expand Down
51 changes: 51 additions & 0 deletions Control/lib/controllers/Run.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* 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 {Log} = require('@aliceo2/web-ui');
const {updateExpressResponseFromNativeError} = require('./../errors/updateExpressResponseFromNativeError.js');

/**
* Controller for dealing with all API requests on retrieving information on runs
*/
class RunController {
/**
* Constructor for initializing controller of runs
* @param {RunService} runService - service to use to build information on runs
*/
constructor(runService) {
this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/run-ctrl`);

/**
* @type {RunService}
*/
this._runService = runService;
}

/**
* API - GET endpoint for retrieving calibration runs
* @param {Request} req - HTTP Request object
* @param {Response} res - HTTP Response object
* @returns {void}
*/
async getCalibrationRunsHandler(_, res) {
try {
const response = await this._runService.retrieveCalibrationRunsGroupedByDetector();
res.status(200).json(response);
} catch (error) {
this._logger.debug(error);
updateExpressResponseFromNativeError(res, error);
}
}
}

module.exports = {RunController};
5 changes: 1 addition & 4 deletions Control/lib/errors/updateExpressResponseFromNativeError.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const {TimeoutError} = require('./TimeoutError.js');
* @returns {void}
*/
const updateExpressResponseFromNativeError = (response, error) => {
let status = 502;
let status = 500;
const {message, constructor} = error;
switch (constructor) {
case InvalidInputError:
Expand All @@ -36,10 +36,7 @@ const updateExpressResponseFromNativeError = (response, error) => {
case TimeoutError:
status = 408;
break;
default:
status = 502;
}

response.status(status).json({message});
};

Expand Down
4 changes: 2 additions & 2 deletions Control/lib/services/Bookkeeping.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BookkeepingService {
*/
constructor({url = '', token = ''}) {
this._url = url;
const {protocol, hostname, port} = new URL(this._url);
const {protocol, hostname, port} = url ? new URL(this._url) : {};
this._hostname = hostname;
this._port = port;
this._protocol = protocol;
Expand Down Expand Up @@ -57,7 +57,7 @@ class BookkeepingService {
} catch (error) {
this._logger.debug(error);
}
return {};
return null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ const {Log} = require('@aliceo2/web-ui');
const {grpcErrorToNativeError} = require('./../errors/grpcErrorToNativeError.js');

const {RUNTIME_COMPONENT: {COG}, RUNTIME_KEY: {CALIBRATION_MAPPING}} = require('./../common/kvStore/runtime.enum.js');
const {LOG_LEVEL} = require('./../common/logLevel.enum.js');
const {RUN_DEFINITIONS} = require('./../common/runDefinition.enum.js')
const {LOG_LEVEL} = require('../common/logLevel.enum.js');

/**
* @class
* CalibrationRunService class to be used for retrieving and building information needed for the CalibrationRun page:
* RunService class to be used for retrieving and building information on runs(active/previous) from Bookkeeping:
* * store in-memory information with regards to runTypes(name-id mapping), calibration per detector mappings, etc.
* * displaying latest calibration runs as per mapping defined in KV Store
* * allowing user to deploy calibration runs and follow their progress via streams
*/
class CalibrationRunService {
class RunService {
/**
* @constructor
* Constructor for configuring the service to retrieve data via passed services
Expand All @@ -51,9 +52,9 @@ class CalibrationRunService {
/**
* @type {Object<String, Array<String>>}
*/
this._calibrationPerDetectorMap = [];
this._calibrationPerDetectorMap = {};

this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/calibration-service`);
this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/run-service`);
}

/**
Expand All @@ -62,21 +63,52 @@ class CalibrationRunService {
*/
async init() {
this._runTypes = await this._bkpService.getRunTypes();
this._calibrationPerDetectorMap = await this._retrieveCalibrationForDetector();
this._calibrationRunsPerDetector = await this.retrieveCalibrationRunsGroupedByDetector();
}

/**
* Based on already loaded calibration configuration mapping from KV store, retrieve runs with those characteristics from Bookkeeping
* @return {Promise<Object<String, Array<RunSummary>.Error>} - list of calibration runs grouped by detector
*/
async retrieveCalibrationRunsGroupedByDetector() {
const calibrationRunsPerDetector = {};
for (const detector in this._calibrationPerDetectorMap) {
const runTypesPerDetector = this._calibrationPerDetectorMap[detector] ?? [];
calibrationRunsPerDetector[detector] = [];
for (const runType of runTypesPerDetector) {
const runTypeId = this._runTypes[runType];
const runInfo = await this._bkpService.getRun(RUN_DEFINITIONS.CALIBRATION, runTypeId, detector);
if (runInfo) {
calibrationRunsPerDetector[detector].push(runInfo);
}
}
}
return calibrationRunsPerDetector;
}

/*
* Private Loaders
*/

/**
* Load calibration mapping for each detector as per the KV store
* @return {Promise<Object.Error>} - map of calibration configuration
*
* @example
* { "XYZ": ["CALIB1", "CALIB2"], "ABC": ["XCALIB"] }
*/
async _retrieveCalibrationForDetector() {
try {
/**
* @type {Object<String, Array<String>>}
* @example
* { "XYZ": ["CALIB1", "CALIB2"], "ABC": ["XCALIB"] }
*/
this._calibrationPerDetectorMap = await this._apricotService.getRuntimeEntryByComponent(COG, CALIBRATION_MAPPING);
const calibrationMappings = await this._apricotService.getRuntimeEntryByComponent(COG, CALIBRATION_MAPPING);
return JSON.parse(calibrationMappings);
} catch (error) {
const err = grpcErrorToNativeError(error);
this._logger.errorMessage(`Unable to load calibration mapping due to: ${err}`,
{level: LOG_LEVEL.OPERATIONS, system: 'GUI', facility: 'calibration-service'}
)
this._calibrationPerDetectorMap = {};
}
return {};
}

/**
Expand All @@ -100,4 +132,4 @@ class CalibrationRunService {
}
}

module.exports = {CalibrationRunService};
module.exports = {RunService};
1 change: 0 additions & 1 deletion Control/public/environment/Environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ export default class Environment extends Observable {
this.itemNew = RemoteData.loading();
this.notify();

console.log(itemForm)
const {result, ok} = await this.model.loader.post(`/api/core/request`, itemForm);
this.itemNew = !ok ? RemoteData.failure(result.message) : RemoteData.notAsked();
this.model.router.go(`?page=environments`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('EnvironmentController test suite', () => {

it('should respond with error if service for retrieving information failed', async () => {
await envCtrl.getEnvironment({params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}}, res);
assert.ok(res.status.calledWith(502));
assert.ok(res.status.calledWith(500));
assert.ok(res.json.calledWith({message: `Data service failed`}));
});

Expand Down
53 changes: 53 additions & 0 deletions Control/test/lib/controllers/mocha-run-controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* 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.
*/
/* eslint-disable max-len */

const assert = require('assert');
const sinon = require('sinon');

const {RunController} = require('../../../lib/controllers/Run.controller.js');

describe(`'RunController' test suite`, () => {
const res = {
status: sinon.stub().returnsThis(),
json: sinon.stub()
}

describe(`'getCalibrationRunsHandler' test suite`, async () => {
it('should successfully return calibrations runs grouped by detector', async () => {
const runs = {
TPC: [
{runNumber: 1},
{runNumber: 2},
]
};
const runController = new RunController({
retrieveCalibrationRunsGroupedByDetector: sinon.stub().resolves(runs)
});
await runController.getCalibrationRunsHandler({}, res);
assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith(runs));
});

it('should return 500 response as there was a problem internally', async () => {
const runController = new RunController({
retrieveCalibrationRunsGroupedByDetector: sinon.stub().rejects(new Error('Something went wrong'))
});
await runController.getCalibrationRunsHandler({}, res);
assert.ok(res.status.calledWith(500));
assert.ok(res.json.calledWith({message: 'Something went wrong'}));
});
});

});
Loading

0 comments on commit 1173ab5

Please sign in to comment.