Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OGUI-1543] Extract Query Service #2573

Merged
merged 4 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 17 additions & 81 deletions InfoLogger/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,36 @@
*/

const { LogManager, WebSocketMessage, InfoLoggerReceiver, MySQL } = require('@aliceo2/web-ui');
const SQLDataSource = require('./SQLDataSource.js');
const { QueryService } = require('./services/QueryService.js');
const ProfileService = require('./ProfileService.js');
const JsonFileConnector = require('./JSONFileConnector.js');
const StatusService = require('./StatusService.js');

const projPackage = require('../package.json');

const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/api`);
const projPackage = require('./../package.json');
const config = require('./configProvider.js');

let querySource = null;
let liveSource = null;

const jsonDb = new JsonFileConnector(config.dbFile || `${__dirname}/../db.json`);

const profileService = new ProfileService(jsonDb);
let sqlService = null;
let queryService = null;

module.exports.attachTo = async (http, ws) => {
const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/api`);

const { QueryController } = await import('./controller/QueryController.mjs');
const queryController = new QueryController();

if (config.mysql) {
sqlService = new MySQL(config.mysql);
queryService = new QueryService(sqlService, config.mysql);

Check warning on line 35 in InfoLogger/lib/api.js

View check run for this annotation

Codecov / codecov/patch

InfoLogger/lib/api.js#L34-L35

Added lines #L34 - L35 were not covered by tests
}
const queryController = new QueryController(queryService);

const statusService = new StatusService(config, projPackage, ws);
statusService.setQuerySource(queryService);

const jsonDb = new JsonFileConnector(config.dbFile || `${__dirname}/../db.json`);
const profileService = new ProfileService(jsonDb);

http.post('/query', query);
http.post('/query', queryController.getLogs.bind(queryController));
http.get('/query/stats', queryController.getQueryStats.bind(queryController), { public: true });

http.get('/status/gui', statusService.getILGStatus.bind(statusService), { public: true });
Expand All @@ -46,18 +52,6 @@
http.get('/getProfile', (req, res) => profileService.getProfile(req, res));
http.post('/saveUserProfile', (req, res) => profileService.saveUserProfile(req, res));

if (config.mysql) {
logger.info('[API] Detected InfoLogger database configuration');
setupMySQLConnectors();
setInterval(() => {
if (!querySource) {
setupMySQLConnectors();
}
}, config.mysql.retryMs || 5000);
} else {
logger.warn('[API] InfoLogger database config not found, Query mode not available');
}

if (config.infoLoggerServer) {
logger.info('[API] InfoLogger server config found');
liveSource = new InfoLoggerReceiver();
Expand Down Expand Up @@ -87,62 +81,4 @@
ws.unfilteredBroadcast(new WebSocketMessage().setCommand('il-server-close'));
});
}

/**
* Method to attempt creating a connection to the InfoLogger SQL DB
*/
function setupMySQLConnectors() {
const connector = new MySQL(config.mysql);
connector.testConnection().then(() => {
querySource = new SQLDataSource(connector, config.mysql);
querySource.isConnectionUpAndRunning()
.then(() => {
ws.unfilteredBroadcast(new WebSocketMessage().setCommand('il-sql-server-status').setPayload({ ok: true }));
statusService.setQuerySource(querySource);
queryController.queryService = querySource;
}).catch((error) => {
logger.error(`[API] Unable to instantiate data source due to ${error}`);
ws.unfilteredBroadcast(new WebSocketMessage().setCommand('il-sql-server-status')
.setPayload({ ok: false, message: 'Query service is unavailable' }));
querySource = null;
statusService.setQuerySource(querySource);
});
}).catch((error) => {
logger.error(`[API] Unable to connect to mysql due to ${error}`);
querySource = null;
ws.unfilteredBroadcast(new WebSocketMessage().setCommand('il-sql-server-status')
.setPayload({ ok: false, message: 'Query service is unavailable' }));
statusService.setQuerySource(querySource);
});
}

/**
* Method to perform a query on the SQL Data Source
* @param {Request} req - HTTP Request object
* @param {Response} res - HTTP Response object
* @returns {void}
*/
function query(req, res) {
if (querySource) {
querySource.queryFromFilters(req.body.criterias, req.body.options)
.then((result) => res.json(result))
.catch((error) => {
setupMySQLConnectors();
handleError(res, error);
});
} else {
handleError(res, '[API] MySQL Data Source is currently not available');
}
}

/**
* Catch all HTTP errors
* @param {Response} res - HTTP Response object
* @param {Error} error - Error object to handle and send
* @param {number} status - HTTP status code to send
*/
function handleError(res, error, status = 500) {
logger.trace(error);
res.status(status).json({ message: error.message });
}
};
43 changes: 25 additions & 18 deletions InfoLogger/lib/controller/QueryController.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* or submit itself to any jurisdiction.
*/

import { LogManager } from '@aliceo2/web-ui';
import { LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui';

/**
* Gateway for all calls that are to query InfoLogger database
Expand All @@ -30,15 +30,38 @@ export class QueryController {
this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/query-ctrl`);
}

/**
* Given InfoLogger parameters, use the query service to retrieve logs requested
* @param {Request} req - HTTP request object with "query" information on object
* @param {Response} res - HTTP response object to provide information on request
* @returns {void}
*/
async getLogs(req, res) {
if (this._queryService) {
try {
const { body: { criterias, options } } = req;
const logs = await this._queryService.queryFromFilters(criterias, options);
res.status(200).json(logs);
} catch (error) {
updateAndSendExpressResponseFromNativeError(res, error);
}
} else {
res.status(503).json({ message: 'Query Service was not configured' });
}
}

/**
* API endpoint for retrieving total number of logs grouped by severity for a given runNumber
* (Used within FLP)
* @param {Request} req - HTTP request object with "query" information on object
* @param {Response} res - HTTP response object to provide information on request
* @returns {void}
*/
async getQueryStats(req, res) {
const { runNumber } = req.query;
if (!runNumber || isNaN(runNumber)) {
if (this._queryService) {
res.status(503).json({ message: 'Query Service was not configured' });
} else if (!runNumber || isNaN(runNumber)) {
res.status(400).json({ error: 'Invalid runNumber provided' });
} else {
try {
Expand All @@ -50,20 +73,4 @@ export class QueryController {
}
}
}

/**
* Setter for updating the queryService
* @param {SQLDataSource} queryService - service to be used to query information on the logs
*/
set queryService(queryService) {
this._queryService = queryService;
}

/**
* Getter for the queryService instance currently being used
* @returns {SQLDataSource} - instance of queryService
*/
get queryService() {
return this._queryService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
const logger = require('@aliceo2/web-ui').LogManager
.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/sql`);

module.exports = class SQLDataSource {
class QueryService {
/**
* Instantiate SQL data source and connect to database
* MySQL options: https://github.com/mysqljs/mysql#connection-options
* Limit option
* @param {object} connection - mysql connection
* Query service that is to be used to map the InfoLogger parameters to SQL query and retrieve data
* @param {MySql} connection - mysql connection
* @param {object} configMySql - mysql config
*/
constructor(connection, configMySql) {
Expand All @@ -30,19 +28,12 @@ module.exports = class SQLDataSource {

/**
* Method to check if mysql driver connection is up
* @returns {Promise} with the results
* @returns {Promise} - resolves/rejects
*/
async isConnectionUpAndRunning() {
return await this.connection
.query('select timestamp from messages LIMIT 1000;')
.then(() => {
const url = `${this.configMySql.host}:${this.configMySql.port}/${this.configMySql.database}`;
logger.info(`Connected to infoLogger database ${url}`);
})
.catch((error) => {
logger.error(error);
throw error;
});
await this.connection.query('select timestamp from messages LIMIT 1000;');
const url = `${this.configMySql.host}:${this.configMySql.port}/${this.configMySql.database}`;
logger.infoMessage(`Connected to infoLogger database ${url}`);
}

/**
Expand Down Expand Up @@ -271,3 +262,5 @@ module.exports = class SQLDataSource {
.then((data) => data);
}
};

module.exports.QueryService = QueryService;
Loading