Skip to content

Commit 61bd4c8

Browse files
committed
[O2B-1492] Add status history filter to environments overview
Introduces a status history filter to the environments overview page. Includes special handling for status histories ending with 'X'. Adds tests to verify correct filtering behaviour. Additionally colourises the status history legend to make easier to compare.
1 parent bb74d46 commit 61bd4c8

5 files changed

Lines changed: 81 additions & 14 deletions

File tree

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/views/Environments/ActiveColumns/environmentsActiveColumns.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ 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 { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js';
2728

2829
/**
2930
* List of active columns for a generic Environments component
@@ -95,5 +96,16 @@ export const environmentsActiveColumns = {
9596
coloredEnvironmentStatusComponent(status, StatusAcronym[status]),
9697
]).slice(1),
9798
balloon: true,
99+
100+
/**
101+
* Status history filter component
102+
*
103+
* @param {EnvironmentOverviewModel} environmentOverviewModel the environment overview model
104+
* @return {Component} the filter component
105+
*/
106+
filter: (environmentOverviewModel) => rawTextFilter(
107+
environmentOverviewModel.filteringModel.get('statusHistory'),
108+
{ classes: ['w-100'], placeholder: 'e.g. D-R-X' },
109+
),
98110
},
99111
};

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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 { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js';
1718
import { debounce } from '../../../utilities/debounce.js';
1819

1920
/**
@@ -28,6 +29,7 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
2829
super();
2930

3031
this._filteringModel = new FilteringModel({
32+
statusHistory: new RawTextFilterModel(),
3133
});
3234

3335
this._filteringModel.observe(() => this._applyFilters(true));

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) {

test/public/envs/overview.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ const {
2626
getPopoverSelector,
2727
goToPage,
2828
openFilteringPanel,
29+
fillInput,
30+
expectAttributeValue,
31+
resetFilters,
2932
} = require('../defaults.js');
3033
const dateAndTime = require('date-and-time');
3134
const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js');
@@ -290,4 +293,31 @@ module.exports = () => {
290293
await openFilteringPanel(page);
291294
await page.waitForSelector(filterPanelSelector, { visible: true });
292295
});
296+
297+
it('should successfully filter environments by their status history', async () => {
298+
/**
299+
* This is the sequence to test filtering the environments on their status history.
300+
*
301+
* @param {string} selector the filter input selector
302+
* @param {string} inputValue the value to type in the filter input
303+
* @param {string[]} expectedIds the list of expected environment IDs after filtering
304+
* @return {void}
305+
*/
306+
const filterOnStatusHistory = async (selector, inputValue, expectedIds) => {
307+
await fillInput(page, selector, inputValue, ['change']);
308+
await waitForTableLength(page, expectedIds.length);
309+
expect(await page.$$eval('tbody tr', (rows) => rows.map((row) => row.id))).to.eql(expectedIds.map(id => `row${id}`));
310+
};
311+
312+
await expectAttributeValue(page, '.historyItems-filter input', 'placeholder', 'e.g. D-R-X');
313+
314+
await filterOnStatusHistory('.historyItems-filter input', 'C-R-D-X', ['TDI59So3d']);
315+
await resetFilters(page);
316+
317+
await filterOnStatusHistory('.historyItems-filter input', 'S-E', ['EIDO13i3D', '8E4aZTjY']);
318+
await resetFilters(page);
319+
320+
await filterOnStatusHistory('.historyItems-filter input', 'D-E', ['KGIS12DS']);
321+
await resetFilters(page);
322+
});
293323
};

0 commit comments

Comments
 (0)