Skip to content

Commit 668f90e

Browse files
committed
Merge branch 'feature/O2B-1503/lhcfills-fill-numbers-filter' into feature/O2B-1505/lhcfills-beam-duration-filter
2 parents 6a47048 + e5bbc05 commit 668f90e

15 files changed

Lines changed: 458 additions & 57 deletions

File tree

lib/domain/enums/StatusAcronyms.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const statusAcronyms = Object.freeze({
1919
RUNNING: 'R',
2020
ERROR: 'E',
2121
DESTROYED: 'X',
22+
DONE: 'X',
2223
});
2324

2425
exports.statusAcronyms = statusAcronyms;

lib/public/components/environments/environmentStatusHistoryComponent.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export const environmentStatusHistoryLegendComponent = () =>
2525
h('h5', 'Status History Legend'),
2626
Object.keys(StatusAcronym).map((status) =>
2727
h('.flex-row.justify-between', [
28-
h('', status),
29-
h('', StatusAcronym[status]),
28+
coloredEnvironmentStatusComponent(status),
29+
h('', coloredEnvironmentStatusComponent(status, StatusAcronym[status])),
3030
])),
3131
]);
3232

lib/public/domain/enums/statusAcronym.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const StatusAcronym = Object.freeze({
1818
CONFIGURED: 'C',
1919
RUNNING: 'R',
2020
ERROR: 'E',
21-
MIXED: 'M',
2221
DESTROYED: 'X',
2322
DONE: 'X',
2423
});

lib/public/views/Environments/ActiveColumns/environmentsActiveColumns.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { environmentStatusHistoryLegendComponent } from '../../../components/env
2424
import { infoTooltip } from '../../../components/common/popover/infoTooltip.js';
2525
import { aliEcsEnvironmentLinkComponent } from '../../../components/common/externalLinks/aliEcsEnvironmentLinkComponent.js';
2626
import { StatusAcronym } from '../../../domain/enums/statusAcronym.mjs';
27+
import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js';
28+
import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js';
2729
import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js';
2830

2931
/**
@@ -73,6 +75,17 @@ export const environmentsActiveColumns = {
7375
size: 'w-10',
7476
format: formatRunsList,
7577
balloon: true,
78+
79+
/**
80+
* Run numbers filter component
81+
*
82+
* @param {EnvironmentOverviewModel} environmentOverviewModel the environment overview model
83+
* @return {Component} the filter component
84+
*/
85+
filter: (environmentOverviewModel) => rawTextFilter(
86+
environmentOverviewModel.filteringModel.get('runNumbers'),
87+
{ classes: ['w-100'], placeholder: 'e.g. 553203, 553221, ...' },
88+
),
7689
},
7790
updatedAt: {
7891
name: 'Last Update',
@@ -83,10 +96,18 @@ export const environmentsActiveColumns = {
8396
},
8497
createdAt: {
8598
name: 'Created At',
86-
visible: false,
99+
visible: true,
87100
sortable: false,
88101
size: 'w-10',
89102
format: (timestamp) => formatTimestamp(timestamp, false),
103+
104+
/**
105+
* CreatedAt filter component
106+
*
107+
* @param {EnvironmentOverviewModel} environmentOverviewModel the environment overview model
108+
* @return {Component} the filter component
109+
*/
110+
filter: (environmentOverviewModel) => timeRangeFilter(environmentOverviewModel.filteringModel.get('created')),
90111
},
91112
status: {
92113
name: 'Current Status',
@@ -95,6 +116,14 @@ export const environmentsActiveColumns = {
95116
size: 'w-10',
96117
noEllipsis: true,
97118
format: (_, environment) => displayEnvironmentStatus(environment),
119+
120+
/**
121+
* Status filter component
122+
*
123+
* @param {EnvironmentOverviewModel} environmentOverviewModel the environment overview model
124+
* @return {Component} the filter component
125+
*/
126+
filter: (environmentOverviewModel) => checkboxes(environmentOverviewModel.filteringModel.get('currentStatus').selectionModel),
98127
},
99128
historyItems: {
100129
name: h('.flex-row.g2.items-center', ['Status History', infoTooltip(environmentStatusHistoryLegendComponent())]),
@@ -107,5 +136,16 @@ export const environmentsActiveColumns = {
107136
coloredEnvironmentStatusComponent(status, StatusAcronym[status]),
108137
]).slice(1),
109138
balloon: true,
139+
140+
/**
141+
* Status history filter component
142+
*
143+
* @param {EnvironmentOverviewModel} environmentOverviewModel the environment overview model
144+
* @return {Component} the filter component
145+
*/
146+
filter: (environmentOverviewModel) => rawTextFilter(
147+
environmentOverviewModel.filteringModel.get('statusHistory'),
148+
{ classes: ['w-100'], placeholder: 'e.g. D-R-X' },
149+
),
110150
},
111151
};

lib/public/views/Environments/Overview/EnvironmentOverviewModel.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
import { buildUrl } from '/js/src/index.js';
1515
import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js';
1616
import { OverviewPageModel } from '../../../models/OverviewModel.js';
17+
import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js';
18+
import { SelectionFilterModel } from '../../../components/Filters/common/filters/SelectionFilterModel.js';
1719
import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js';
1820
import { debounce } from '../../../utilities/debounce.js';
21+
import { coloredEnvironmentStatusComponent } from '../ColoredEnvironmentStatusComponent.js';
22+
import { StatusAcronym } from '../../../domain/enums/statusAcronym.mjs';
1923

2024
/**
2125
* Environment overview page model
@@ -29,6 +33,16 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
2933
super();
3034

3135
this._filteringModel = new FilteringModel({
36+
created: new TimeRangeInputModel(),
37+
runNumbers: new RawTextFilterModel(),
38+
statusHistory: new RawTextFilterModel(),
39+
currentStatus: new SelectionFilterModel({
40+
availableOptions: Object.keys(StatusAcronym).map((status) => ({
41+
value: status,
42+
label: coloredEnvironmentStatusComponent(status),
43+
rawLabel: status,
44+
})),
45+
}),
3246
ids: new RawTextFilterModel(),
3347
});
3448

lib/usecases/environment/GetAllEnvironmentsUseCase.js

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ class GetAllEnvironmentsUseCase {
126126
}
127127

128128
if (statusHistory) {
129-
// Split the string into separate characters
130-
const historyItems = statusHistory.split('');
129+
// Check if status history ends with 'X' and remove it if present to handle the special case later
130+
const containsX = statusHistory.endsWith('X');
131+
const cleanedStatusHistory = containsX ? statusHistory.slice(0, -1) : statusHistory;
132+
const historyItems = cleanedStatusHistory.split('');
131133

132134
// Swap the acronyms with the status (=acronym -> status)
133135
const acronymToStatus = {};
@@ -145,17 +147,38 @@ class GetAllEnvironmentsUseCase {
145147
}
146148
}
147149

148-
// Filter the environments by status history using the subquery
149-
filterQueryBuilder.literalWhere(
150-
`${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} = :statusFilters`,
151-
// Create a string of the status filters separated by a comma
152-
{ statusFilters: statusFilters.join(',') },
153-
);
150+
if (containsX) {
151+
const statusFiltersWithDestroyed = [...statusFilters, 'DESTROYED'].join(',');
152+
const statusFiltersWithDone = [...statusFilters, 'DONE'].join(',');
153+
154+
/*
155+
* Use OR condition to match subsequences ending with either DESTROYED or DONE
156+
* Filter the environments by using LIKE for subsequence matching
157+
*/
158+
filterQueryBuilder.literalWhere(
159+
`(${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDestroyed OR ` +
160+
`${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFiltersWithDone)`,
161+
{
162+
statusFiltersWithDestroyed: `%${statusFiltersWithDestroyed}`,
163+
statusFiltersWithDone: `%${statusFiltersWithDone}`,
164+
},
165+
);
154166

155-
filterQueryBuilder.includeAttribute({
156-
query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY,
157-
alias: 'statusHistory',
158-
});
167+
filterQueryBuilder.includeAttribute({
168+
query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY,
169+
alias: 'statusHistory',
170+
});
171+
} else {
172+
filterQueryBuilder.literalWhere(
173+
`${ENVIRONMENT_STATUS_HISTORY_SUBQUERY} LIKE :statusFilters`,
174+
{ statusFilters: `%${statusFilters.join(',')}%` },
175+
);
176+
177+
filterQueryBuilder.includeAttribute({
178+
query: ENVIRONMENT_STATUS_HISTORY_SUBQUERY,
179+
alias: 'statusHistory',
180+
});
181+
}
159182
}
160183

161184
if (runNumbersExpression) {

lib/usecases/lhcFill/GetAllLhcFillsUseCase.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class GetAllLhcFillsUseCase {
5858

5959
// Check that the final fill numbers list contains at least one valid fill number
6060
if (finalFillnumberList.length > 0) {
61-
queryBuilder.where('fillNumber').oneOf(...finalFillnumberList);
61+
finalFillnumberList.length === 1 ? queryBuilder.where('fillNumber').is(finalFillnumberList[0])
62+
: queryBuilder.where('fillNumber').oneOf(...finalFillnumberList);
6263
}
6364
}
6465
// Beam duration filter, limit and corresponding operator.

lib/utilities/rangeUtils.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export const validateRange = (value, helpers) => {
2626

2727
for (const number of numbers) {
2828
if (number.includes('-')) {
29-
const [start, end] = number.split('-').map((n) => parseInt(n, 10));
29+
// Check if '-' occurs more than once in this part of the range
30+
if (number.lastIndexOf('-') !== number.indexOf('-')) {
31+
return helpers.error('any.invalid', { message: `Invalid range: ${number}` });
32+
}
33+
const [start, end] = number.split('-').map((n) => Number(n));
3034
if (Number.isNaN(start) || Number.isNaN(end) || start > end) {
3135
return helpers.error('any.invalid', { message: `Invalid range: ${number}` });
3236
}
@@ -35,6 +39,11 @@ export const validateRange = (value, helpers) => {
3539
if (rangeSize > MAX_RANGE_SIZE) {
3640
return helpers.error('any.invalid', { message: `Given range exceeds max size of ${MAX_RANGE_SIZE} range: ${number}` });
3741
}
42+
} else {
43+
// Prevent non-numeric input.
44+
if (isNaN(number)) {
45+
return helpers.error('any.invalid', { message: `Invalid number: ${number}` });
46+
}
3847
}
3948
}
4049

@@ -44,15 +53,15 @@ export const validateRange = (value, helpers) => {
4453
/**
4554
* Unpacks a given string containing number ranges.
4655
* E.G. input: 5,7-9 => output: 5,7,8,9
47-
* @param {string} numbersRange numbers that may or may not contain ranges.
56+
* @param {string[]} numbersRanges numbers that may or may not contain ranges.
4857
* @param {string} rangeSplitter string used to indicate and unpack a range.
4958
* @returns {Set<Number>} set containing the unpacked range.
5059
*/
51-
export function unpackNumberRange(numbersRange, rangeSplitter = '-') {
60+
export function unpackNumberRange(numbersRanges, rangeSplitter = '-') {
5261
// Set to prevent duplicate values.
5362
const resultNumbers = new Set();
5463

55-
numbersRange.forEach((number) => {
64+
numbersRanges.forEach((number) => {
5665
if (number.includes(rangeSplitter)) {
5766
const [start, end] = number.split(rangeSplitter).map((n) => parseInt(n, 10));
5867
if (!Number.isNaN(start) && !Number.isNaN(end)) {
@@ -61,7 +70,7 @@ export function unpackNumberRange(numbersRange, rangeSplitter = '-') {
6170
}
6271
}
6372
} else {
64-
if (!Number.isNaN(number)) {
73+
if (!isNaN(number)) {
6574
resultNumbers.add(Number(number));
6675
}
6776
}

0 commit comments

Comments
 (0)