Skip to content

Commit e0487a6

Browse files
authored
[O2B-1104] environment history occurrences API (#1327)
* [O2B-1104] add router, controller, service and repository for environment history histogram api * [O2B-1104] changed the naming for some of the api variables * [O2B-1104] Altered query so that it excludes history without DESTROYED or ERROR at the end and changed filter * [O2B-1104] Added StatisticsService tests for the history occurences endpoint * [O2B-1104] Redefined the way to check if ERROR or DESTROYED are in a given environement * [O2B-1104] Implemented changes requested in the PR
1 parent 33d3e7f commit e0487a6

5 files changed

Lines changed: 130 additions & 2 deletions

File tree

lib/database/repositories/EnvironmentHistoryItemRepository.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,32 @@
1111
* or submit itself to any jurisdiction.
1212
*/
1313

14-
const { models: { EnvironmentHistoryItem } } = require('../');
14+
const { models: { EnvironmentHistoryItem }, sequelize } = require('../');
1515
const Repository = require('./Repository');
16+
const { timestampToMysql } = require('../../server/utilities/timestampToMysql.js');
17+
18+
/**
19+
* Returns the SQL query for fetching the environment history combinations and
20+
* their occurrences within a specified time period.
21+
*
22+
* @param {Period} period the period on which the occurrences must be counted
23+
* @return {string} the raw SQL query
24+
*/
25+
const getHistoryDistributionQuery = ({ from, to }) => `
26+
SELECT statusHistory, COUNT(*) AS count
27+
FROM (
28+
SELECT e.id, GROUP_CONCAT(ehi.status) AS statusHistory
29+
FROM environments e
30+
INNER JOIN environments_history_items AS ehi
31+
ON e.id = ehi.environment_id
32+
WHERE e.updated_at >= '${timestampToMysql(from)}'
33+
AND e.created_at < '${timestampToMysql(to)}'
34+
GROUP BY e.id
35+
HAVING FIND_IN_SET('DESTROYED', statusHistory) > 0 OR FIND_IN_SET('ERROR', statusHistory) > 0
36+
) AS statusHistoryByEnvironmentId
37+
GROUP BY statusHistory
38+
ORDER BY count DESC;
39+
`;
1640

1741
/**
1842
* Sequelize implementation of the EnvironmentHistoryItemRepository.
@@ -34,6 +58,17 @@ class EnvironmentHistoryItemRepository extends Repository {
3458
async bulkInsert(historyItems) {
3559
return EnvironmentHistoryItem.bulkCreate(historyItems, { returning: false });
3660
}
61+
62+
/**
63+
* Returns the occurrences of each environment status history combination for a given period.
64+
*
65+
* @param {Period} period the period on which occurrences must be counted
66+
* @return {Promise<{statusHistory: string, count: number}[]>} the resulting histogram
67+
*/
68+
async getHistoryDistribution(period) {
69+
const [rows] = await sequelize.query(getHistoryDistributionQuery(period), { raw: true });
70+
return rows;
71+
}
3772
}
3873

3974
module.exports = new EnvironmentHistoryItemRepository();

lib/server/controllers/statistics.controller.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,35 @@ const getEorReasonsOccurrencesHandler = async (request, response) => {
154154
}
155155
};
156156

157+
// eslint-disable-next-line valid-jsdoc
158+
/**
159+
* Route to get the environment status history combinations and their occurrences
160+
*/
161+
const getHistoryOccurrencesInEnvironmentsHandler = async (request, response) => {
162+
const requestDto = await dtoValidator(DtoFactory.queryOnly({
163+
from: Joi.date().required(),
164+
to: Joi.date().required(),
165+
}), request, response);
166+
167+
if (requestDto) {
168+
try {
169+
const data = await statisticsService.getHistoryOccurrencesInEnvironments({
170+
from: requestDto.query.from,
171+
to: requestDto.query.to,
172+
});
173+
response.status(200).json({ data });
174+
} catch (error) {
175+
updateExpressResponseFromNativeError(response, error);
176+
}
177+
}
178+
};
179+
157180
exports.StatisticsController = {
158181
getLhcFillStatisticsHandler,
159182
getWeeklyRunDataSizeHandler,
160183
getTimeBetweenRunsDistributionHandler,
161184
getDetectorEfficiencyPerFillHandler,
162185
getTagsOccurrencesInLogsHandler,
163186
getEorReasonsOccurrencesHandler,
187+
getHistoryOccurrencesInEnvironmentsHandler,
164188
};

lib/server/routers/statistics.router.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ exports.statisticsRouter = {
4848
path: '/runs/eorReasonsOccurrences',
4949
controller: StatisticsController.getEorReasonsOccurrencesHandler,
5050
},
51+
{
52+
method: 'get',
53+
path: '/environments/historyOccurrences',
54+
controller: StatisticsController.getHistoryOccurrencesInEnvironmentsHandler,
55+
},
5156
],
5257
};

lib/server/services/statistics/StatisticsService.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
* granted to it by virtue of its status as an Intergovernmental Organization
1111
* or submit itself to any jurisdiction.
1212
*/
13-
const { LhcFillStatisticsRepository, StableBeamRunsRepository, LogRepository } = require('../../../database/repositories/index.js');
13+
const {
14+
LhcFillStatisticsRepository,
15+
StableBeamRunsRepository,
16+
LogRepository,
17+
EnvironmentHistoryItemRepository,
18+
} = require('../../../database/repositories/index.js');
1419
const { lhcFillStatisticsAdapter } = require('../../../database/adapters/index.js');
1520
const { Op } = require('sequelize');
1621

@@ -138,6 +143,16 @@ class StatisticsService {
138143
async getEorReasonsOccurrences(period) {
139144
return StableBeamRunsRepository.getEorDistribution(period);
140145
}
146+
147+
/**
148+
* Return the list of environment status history combinations and their occurrences
149+
*
150+
* @param {Period} period the period of statistics to query
151+
* @return {Promise<{statusHistory: string, count: number}[]>} the resulting histogram
152+
*/
153+
async getHistoryOccurrencesInEnvironments(period) {
154+
return EnvironmentHistoryItemRepository.getHistoryDistribution(period);
155+
}
141156
}
142157

143158
exports.StatisticsService = StatisticsService;

test/lib/server/services/statistics/StatisticsService.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,53 @@ module.exports = () => {
323323
expect(eorReasonsOccurrences).to.lengthOf(0);
324324
}
325325
});
326+
327+
it('should successfully extract history occurrences for environments in a given period', async () => {
328+
const historyOccurrences = await statisticsService.getHistoryOccurrencesInEnvironments({
329+
from: new Date('2019/08/09 15:00:00').getTime(),
330+
to: new Date('2019/08/09 17:00:00').getTime(),
331+
});
332+
expect(historyOccurrences).to.lengthOf(3);
333+
expect(historyOccurrences).to.eql([
334+
{ statusHistory: 'STANDBY,ERROR', count: 1 },
335+
{ statusHistory: 'STANDBY,DEPLOYED,ERROR,DESTROYED', count: 1 },
336+
{ statusHistory: 'CONFIGURED,RUNNING,STOPPED,DESTROYED', count: 1 },
337+
]);
338+
});
339+
340+
it('should successfully filter out environment history before a date (included) for history occurences', async () => {
341+
{
342+
const historyOccurrences = await statisticsService.getHistoryOccurrencesInEnvironments({
343+
from: new Date('2019/08/09 14:30:00').getTime(),
344+
to: new Date('2019/08/09 14:40:00').getTime(),
345+
});
346+
expect(historyOccurrences).to.lengthOf(1);
347+
expect(historyOccurrences).to.eql([{ statusHistory: 'DESTROYED', count: 1 }]);
348+
}
349+
{
350+
const historyOccurrences = await statisticsService.getHistoryOccurrencesInEnvironments({
351+
from: new Date('2019/08/09 14:30:01').getTime(),
352+
to: new Date('2019/08/09 14:40:00').getTime(),
353+
});
354+
expect(historyOccurrences).to.lengthOf(0);
355+
}
356+
});
357+
358+
it('should successfully filter out environment history after a date (excluded) for history occurences', async () => {
359+
{
360+
const historyOccurrences = await statisticsService.getHistoryOccurrencesInEnvironments({
361+
from: new Date('2019/08/09 14:15:00').getTime(),
362+
to: new Date('2019/08/09 14:30:01').getTime(),
363+
});
364+
expect(historyOccurrences).to.lengthOf(1);
365+
expect(historyOccurrences).to.eql([{ statusHistory: 'DESTROYED', count: 1 }]);
366+
}
367+
{
368+
const historyOccurrences = await statisticsService.getHistoryOccurrencesInEnvironments({
369+
from: new Date('2019/08/09 14:15:00').getTime(),
370+
to: new Date('2019/08/09 14:30:00').getTime(),
371+
});
372+
expect(historyOccurrences).to.lengthOf(0);
373+
}
374+
});
326375
};

0 commit comments

Comments
 (0)