Skip to content

Commit 91236e5

Browse files
committed
feat(RHINENG-20424): improved ux and incident id filtering
1 parent cc4f55f commit 91236e5

File tree

7 files changed

+107
-64
lines changed

7 files changed

+107
-64
lines changed

web/src/actions/observe.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export enum ActionType {
3232
ShowGraphs = 'showGraphs',
3333
SetIncidents = 'setIncidents',
3434
SetIncidentsActiveFilters = 'setIncidentsActiveFilters',
35-
SetChooseIncident = 'setChooseIncident',
3635
SetAlertsData = 'setAlertsData',
3736
SetAlertsTableData = 'setAlertsTableData',
3837
SetAlertsAreLoading = 'setAlertsAreLoading',
@@ -145,8 +144,6 @@ export const setIncidents = (incidents) => action(ActionType.SetIncidents, incid
145144
export const setIncidentsActiveFilters = (incidentsActiveFilters) =>
146145
action(ActionType.SetIncidentsActiveFilters, incidentsActiveFilters);
147146

148-
export const setChooseIncident = (groupId) => action(ActionType.SetChooseIncident, groupId);
149-
150147
export const setAlertsData = (alertsData) => action(ActionType.SetAlertsData, alertsData);
151148

152149
export const setAlertsTableData = (alertsTableData) =>
@@ -196,7 +193,6 @@ type Actions = {
196193
showGraphs: typeof showGraphs;
197194
setIncidents: typeof setIncidents;
198195
setIncidentsActiveFilters: typeof setIncidentsActiveFilters;
199-
setChooseIncident: typeof setChooseIncident;
200196
setAlertsData: typeof setAlertsData;
201197
setAlertsTableData: typeof setAlertsTableData;
202198
setAlertsAreLoading: typeof setAlertsAreLoading;

web/src/components/Incidents/IncidentsChart/IncidentsChart.tsx

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1+
import { useEffect, useMemo, useRef, useState } from 'react';
22

33
import {
44
Chart,
@@ -24,7 +24,7 @@ import {
2424
t_global_color_status_warning_default,
2525
} from '@patternfly/react-tokens';
2626
import { useDispatch, useSelector } from 'react-redux';
27-
import { setAlertsAreLoading, setChooseIncident } from '../../../actions/observe';
27+
import { setAlertsAreLoading, setIncidentsActiveFilters } from '../../../actions/observe';
2828
import { MonitoringState } from '../../../reducers/observe';
2929
import '../incidents-styles.css';
3030
import { IncidentsTooltip } from '../IncidentsTooltip';
@@ -51,10 +51,19 @@ const IncidentsChart = ({
5151
const [chartHeight, setChartHeight] = useState<number>();
5252
const dateValues = useMemo(() => generateDateArray(chartDays), [chartDays]);
5353

54+
const incidentsActiveFilters = useSelector((state: MonitoringState) =>
55+
state.plugins.mcp.getIn(['incidentsData', 'incidentsActiveFilters']),
56+
);
57+
const selectedGroupId = incidentsActiveFilters.groupId?.[0] ?? null;
58+
5459
const chartData = useMemo(() => {
5560
if (!Array.isArray(incidentsData) || incidentsData.length === 0) return [];
56-
return incidentsData.map((incident) => createIncidentsChartBars(incident, dateValues));
57-
}, [incidentsData, dateValues]);
61+
const filteredIncidents = selectedGroupId
62+
? incidentsData.filter((incident) => incident.group_id === selectedGroupId)
63+
: incidentsData;
64+
65+
return filteredIncidents.map((incident) => createIncidentsChartBars(incident, dateValues));
66+
}, [incidentsData, dateValues, selectedGroupId]);
5867

5968
useEffect(() => {
6069
setIsLoading(false);
@@ -79,41 +88,31 @@ const IncidentsChart = ({
7988
return () => observer();
8089
}, []);
8190

82-
const selectedId = useSelector((state: MonitoringState) =>
83-
state.plugins.mcp.getIn(['incidentsData', 'groupId']),
84-
);
85-
const incidentsActiveFilters = useSelector((state: MonitoringState) =>
86-
state.plugins.mcp.getIn(['incidentsData', 'incidentsActiveFilters']),
87-
);
88-
89-
const isHidden = useCallback(
90-
(group_id) => selectedId !== '' && selectedId !== group_id,
91-
[selectedId],
92-
);
9391
const clickHandler = (data, datum) => {
94-
if (datum.datum.group_id === selectedId) {
92+
if (datum.datum.group_id === selectedGroupId) {
9593
dispatch(
96-
setChooseIncident({
97-
groupId: '',
94+
setIncidentsActiveFilters({
95+
incidentsActiveFilters: {
96+
...incidentsActiveFilters,
97+
groupId: [],
98+
},
9899
}),
99100
);
100101
updateBrowserUrl(incidentsActiveFilters, '');
101102
dispatch(setAlertsAreLoading({ alertsAreLoading: true }));
102103
} else {
103104
dispatch(
104-
setChooseIncident({
105-
groupId: datum.datum.group_id,
105+
setIncidentsActiveFilters({
106+
incidentsActiveFilters: {
107+
...incidentsActiveFilters,
108+
groupId: [datum.datum.group_id],
109+
},
106110
}),
107111
);
108112
updateBrowserUrl(incidentsActiveFilters, datum.datum.group_id);
109113
}
110114
};
111115

112-
const getOpacity = useCallback(
113-
(datum) => (datum.fillOpacity = isHidden(datum.group_id) ? '0.3' : '1'),
114-
[isHidden],
115-
);
116-
117116
return (
118117
<Card className="incidents-chart-card" style={{ overflow: 'visible' }}>
119118
<div ref={containerRef} style={{ position: 'relative' }}>
@@ -140,7 +139,7 @@ const IncidentsChart = ({
140139
}
141140
return `Severity: ${datum.name}
142141
Component: ${datum.componentList?.join(', ')}
143-
Incident ID:
142+
Incident ID:
144143
${datum.group_id}
145144
Start: ${formatDate(new Date(datum.y0), true)}
146145
End: ${datum.firing ? '---' : formatDate(new Date(datum.y), true)}`;
@@ -209,7 +208,7 @@ const IncidentsChart = ({
209208
data: {
210209
fill: ({ datum }) => datum.fill,
211210
stroke: ({ datum }) => datum.fill,
212-
fillOpacity: ({ datum }) => (datum.nodata ? 0 : getOpacity(datum)),
211+
fillOpacity: ({ datum }) => (datum.nodata ? 0 : 1),
213212
cursor: 'pointer',
214213
},
215214
}}

web/src/components/Incidents/IncidentsPage.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
setAlertsAreLoading,
4545
setAlertsData,
4646
setAlertsTableData,
47-
setChooseIncident,
4847
setFilteredIncidentsData,
4948
setIncidentPageFilterType,
5049
setIncidents,
@@ -157,13 +156,6 @@ const IncidentsPage = () => {
157156
},
158157
}),
159158
);
160-
if (urlParams?.groupId?.length > 0) {
161-
dispatch(
162-
setChooseIncident({
163-
groupId: urlParams?.groupId?.at(0),
164-
}),
165-
);
166-
}
167159
} else {
168160
// If no URL parameters exist, set the URL based on incidentsInitialState
169161
updateBrowserUrl(incidentsInitialState);
@@ -187,7 +179,11 @@ const IncidentsPage = () => {
187179
filteredIncidentsData: filterIncident(incidentsActiveFilters, incidents),
188180
}),
189181
);
190-
}, [incidentsActiveFilters.state, incidentsActiveFilters.severity]);
182+
}, [
183+
incidentsActiveFilters.state,
184+
incidentsActiveFilters.severity,
185+
incidentsActiveFilters.groupId,
186+
]);
191187

192188
const now = Date.now();
193189
const safeFetch = useSafeFetch();
@@ -334,17 +330,18 @@ const IncidentsPage = () => {
334330
<Spinner aria-label="incidents-chart-spinner" />
335331
</Bullseye>
336332
) : (
337-
<PageSection hasBodyWrapper={false}>
333+
<PageSection hasBodyWrapper={false} className="incidents-page-main-section">
338334
<Toolbar
339335
id="toolbar-with-filter"
340336
collapseListedFiltersBreakpoint="xl"
341337
clearAllFilters={() =>
342338
dispatch(
343339
setIncidentsActiveFilters({
344340
incidentsActiveFilters: {
341+
...incidentsActiveFilters,
345342
severity: [],
346-
days: ['7 days'],
347343
state: [],
344+
groupId: [],
348345
},
349346
}),
350347
)

web/src/components/Incidents/ToolbarItemFilter.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import {
88
MenuToggle,
99
Badge,
1010
} from '@patternfly/react-core';
11-
import FilterIcon from '@patternfly/react-icons/dist/js/icons/filter-icon';
12-
import { getFilterKey } from './utils'; // Assuming this utility function exists
11+
import { getFilterKey } from './utils';
1312

1413
interface IncidentFilterToolbarItemProps {
1514
categoryName: string;
@@ -97,9 +96,9 @@ const IncidentFilterToolbarItem: React.FC<IncidentFilterToolbarItemProps> = ({
9796
ref={toggleRef}
9897
onClick={onIncidentFilterToggle}
9998
isExpanded={incidentFilterIsExpanded}
100-
icon={<FilterIcon />}
10199
badge={
102-
Object.entries(incidentsActiveFilters[getFilterKey(categoryName)]).length > 0 ? (
100+
Object.entries(incidentsActiveFilters?.[getFilterKey(categoryName)] || {}).length >
101+
0 ? (
103102
<Badge isRead>
104103
{Object.entries(incidentsActiveFilters[getFilterKey(categoryName)]).length}
105104
</Badge>

web/src/components/Incidents/incidents-styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@
2525
margin-top: -1px;
2626
width: 0;
2727
}
28+
29+
.incidents-page-main-section.pf-v6-c-page__main-section {
30+
gap: 0;
31+
}

web/src/components/Incidents/utils.ts

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@ import {
1717
Timestamps,
1818
} from './model';
1919

20-
export const isIncidentFilter = (filter: unknown): filter is IncidentFilters => {
21-
return (
22-
typeof filter === 'string' &&
23-
['Critical', 'Warning', 'Firing', 'Informative', 'Resolved'].includes(filter)
24-
);
25-
};
26-
2720
function consolidateAndMergeIntervals(data: Incident, dateArray: SpanDates) {
2821
const severityRank = { 2: 2, 1: 1, 0: 0 };
2922
const filteredValues = filterAndSortValues(data, severityRank);
@@ -331,7 +324,7 @@ export function filterIncident(filters: IncidentFiltersCombined, incidents: Arra
331324
};
332325

333326
return incidents.filter((incident) => {
334-
if (!filters.severity.length && !filters.state.length) {
327+
if (!filters?.severity?.length && !filters?.state?.length && !filters?.groupId?.length) {
335328
return true;
336329
}
337330

@@ -345,8 +338,11 @@ export function filterIncident(filters: IncidentFiltersCombined, incidents: Arra
345338
? filters.state.some((filter) => incident[conditions[filter]] === true)
346339
: true; // True if no state filters
347340

341+
const isIncidentIdMatch =
342+
filters.groupId?.length > 0 ? filters.groupId.includes(incident.group_id) : true;
343+
348344
// Combine conditions with AND behavior between categories
349-
return isSeverityMatch && isStateMatch;
345+
return isSeverityMatch && isStateMatch && isIncidentIdMatch;
350346
});
351347
}
352348

@@ -488,6 +484,20 @@ export const changeDaysFilter = (
488484
);
489485
};
490486

487+
/**
488+
* A wrapper function that handles a user's selection on an incident filter.
489+
*
490+
* This function acts as the public entry point for filter selection,
491+
* passing the event details and filter state to the internal `onSelect`
492+
* helper function to perform the state update.
493+
*
494+
* @param {Event} event - The DOM event from the checkbox or filter selection.
495+
* @param {string} selection - The value of the filter being selected or deselected.
496+
* @param {Function} dispatch - The Redux dispatch function to trigger state changes.
497+
* @param {object} incidentsActiveFilters - The current state of active filters.
498+
* @param {string} filterCategoryType - The category of the filter (e.g., 'Incident ID', 'severity').
499+
* @returns {void}
500+
*/
491501
export const onIncidentFiltersSelect = (
492502
event,
493503
selection: IncidentFilters,
@@ -498,6 +508,20 @@ export const onIncidentFiltersSelect = (
498508
onSelect(event, selection, dispatch, incidentsActiveFilters, filterCategoryType);
499509
};
500510

511+
/**
512+
* An internal helper function that manages the logic for selecting or deselecting a filter.
513+
*
514+
* It updates the Redux state based on the filter type. For 'groupId', it replaces the
515+
* existing selection (single-select behavior). For all other filters, it adds or
516+
* removes the selection from the array (multi-select behavior).
517+
*
518+
* @param {Event} event - The DOM event from the checkbox or filter selection.
519+
* @param {string} selection - The value of the filter being selected or deselected.
520+
* @param {Function} dispatch - The Redux dispatch function to trigger state changes.
521+
* @param {object} incidentsActiveFilters - The current state of active filters.
522+
* @param {string} filterCategoryType - The category of the filter.
523+
* @returns {void}
524+
*/
501525
const onSelect = (event, selection, dispatch, incidentsActiveFilters, filterCategoryType) => {
502526
const checked = event.target.checked;
503527
let effectiveFilterType = filterCategoryType;
@@ -510,12 +534,20 @@ const onSelect = (event, selection, dispatch, incidentsActiveFilters, filterCate
510534
const targetArray = incidentsActiveFilters[effectiveFilterType] || [];
511535
const newFilters = { ...incidentsActiveFilters };
512536

513-
if (checked) {
514-
if (!targetArray.includes(selection)) {
515-
newFilters[effectiveFilterType] = [...targetArray, selection];
537+
if (effectiveFilterType === 'groupId') {
538+
if (checked) {
539+
newFilters[effectiveFilterType] = [selection];
540+
} else {
541+
newFilters[effectiveFilterType] = [];
516542
}
517543
} else {
518-
newFilters[effectiveFilterType] = targetArray.filter((value) => value !== selection);
544+
if (checked) {
545+
if (!targetArray.includes(selection)) {
546+
newFilters[effectiveFilterType] = [...targetArray, selection];
547+
}
548+
} else {
549+
newFilters[effectiveFilterType] = targetArray.filter((value) => value !== selection);
550+
}
519551
}
520552

521553
dispatch(
@@ -542,6 +574,16 @@ export const parseUrlParams = (search) => {
542574
return result;
543575
};
544576

577+
/**
578+
* Generates an array of unique incident ID options for a filter.
579+
*
580+
* This function iterates through a list of incident objects,
581+
* extracts their unique `group_id`s, and returns them in a format
582+
* suitable for a dropdown or filter component.
583+
*
584+
* @param {Array<Incident>} incidents - An array of incident objects.
585+
* @returns {{value: string}[]} An array of objects, where each object has a `value` key with a unique incident ID.
586+
*/
545587
export const getIncidentIdOptions = (incidents: Array<Incident>) => {
546588
const uniqueIds = new Set<string>();
547589
incidents.forEach((incident) => {
@@ -554,6 +596,17 @@ export const getIncidentIdOptions = (incidents: Array<Incident>) => {
554596
}));
555597
};
556598

599+
/**
600+
* Maps a human-readable filter category name to its corresponding data key.
601+
*
602+
* This function is used to convert a display name (e.g., "Incident ID")
603+
* into the internal key used to access filter data (e.g., "groupId").
604+
* It handles a specific case for "Incident ID" and converts all other
605+
* category names to lowercase.
606+
*
607+
* @param {string} categoryName - The human-readable name of the filter category.
608+
* @returns {string} The corresponding data key for the filter.
609+
*/
557610
export const getFilterKey = (categoryName: string): string => {
558611
if (categoryName === 'Incident ID') {
559612
return 'groupId';

web/src/reducers/observe.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ export default (state: ObserveState, action: ObserveAction): ObserveState => {
103103
groupId: [],
104104
},
105105
incidentPageFilterType: 'Severity',
106-
groupId: '',
107106
}),
108107
});
109108
}
@@ -327,10 +326,6 @@ export default (state: ObserveState, action: ObserveAction): ObserveState => {
327326
);
328327
}
329328

330-
case ActionType.SetChooseIncident: {
331-
return state.setIn(['incidentsData', 'groupId'], action.payload.groupId);
332-
}
333-
334329
case ActionType.SetAlertsData: {
335330
return state.setIn(['incidentsData', 'alertsData'], action.payload.alertsData);
336331
}

0 commit comments

Comments
 (0)