diff --git a/src/AppConstants.js b/src/AppConstants.js index 09a80b5f..d88403c3 100644 --- a/src/AppConstants.js +++ b/src/AppConstants.js @@ -434,10 +434,10 @@ export const WORKLOADS_TABLE_CELL_OBJECTS = 3; export const WORKLOADS_TABLE_CELL_LAST_SEEN = 4; export const WORKLOADS_TABLE_FILTER_CATEGORIES = { - highest_severity: { + general_severity: { type: 'checkbox', - title: 'Highest severity', - urlParam: 'highest_severity', + title: 'General severity', + urlParam: 'general_severity', values: [ { label: 'Critical', text: 'Critical', value: 'critical' }, { label: 'Important', text: 'Important', value: 'important' }, diff --git a/src/Components/Common/Tables.js b/src/Components/Common/Tables.js index abac318d..eaf58dfb 100644 --- a/src/Components/Common/Tables.js +++ b/src/Components/Common/Tables.js @@ -7,6 +7,10 @@ import { FILTER_CATEGORIES, RULE_CATEGORIES, } from '../../AppConstants'; +import { + hasAnyValueGreaterThanZero, + remappingSeverity, +} from '../../Utilities/Workloads'; export const passFilters = (rule, filters) => Object.entries(filters).every(([filterKey, filterValue]) => { @@ -275,3 +279,32 @@ export const addFilterParam = (currentFilters, updateFilters, param, values) => ...{ [param]: values }, }) : removeFilterParam(currentFilters, updateFilters, param); + +export const passFilterWorkloads = (workloads, filters) => { + const generalSeverityRemapped = remappingSeverity( + workloads.metadata.hits_by_severity, + 'general' + ); + return Object.entries(filters).every(([filterKey, filterValue]) => { + switch (filterKey) { + case 'cluster_name': + return workloads.cluster.display_name + .toLowerCase() + .includes(filterValue.toLowerCase()); + case 'namespace_name': + return workloads.namespace.name + .toLowerCase() + .includes(filterValue.toLowerCase()); + case 'general_severity': + return ( + filterValue.length === 0 || + hasAnyValueGreaterThanZero( + generalSeverityRemapped, + filters.general_severity + ) + ); + default: + return true; + } + }); +}; diff --git a/src/Components/HighestSeverityBadge/HighestSeverityBadge.js b/src/Components/HighestSeverityBadge/HighestSeverityBadge.js index f6a80a06..cea0f252 100644 --- a/src/Components/HighestSeverityBadge/HighestSeverityBadge.js +++ b/src/Components/HighestSeverityBadge/HighestSeverityBadge.js @@ -3,21 +3,9 @@ import { Tooltip } from '@patternfly/react-core'; import { TooltipPosition } from '@patternfly/react-core/dist/js/components/Tooltip'; import InsightsLabel from '@redhat-cloud-services/frontend-components/InsightsLabel'; import PropTypes from 'prop-types'; +import { severityTypeToText } from '../../Utilities/Workloads'; export const HighestSeverityBadge = ({ highestSeverity, severities }) => { - const severityTypeToText = (value) => { - value = parseInt(value); - if (value === 1) { - return 'Low'; - } else if (value === 2) { - return 'Moderate'; - } else if (value === 3) { - return 'Important'; - } else { - return 'Critical'; - } - }; - const severitiesToDisplay = Object.keys(severities) .map((severityType) => { return severities[severityType] > 0 ? ( diff --git a/src/Components/ShieldSet.js b/src/Components/ShieldSet.js index 6f60f122..7af2859a 100644 --- a/src/Components/ShieldSet.js +++ b/src/Components/ShieldSet.js @@ -8,7 +8,8 @@ import { SEVERITY_OPTIONS, remappingSeverity } from '../Utilities/Workloads'; const ShieldSet = (hits_by_severity) => { const DISABLED_COLOR = 'var(--pf-global--disabled-color--200)'; const severitiesRemapped = remappingSeverity( - hits_by_severity.hits_by_severity + hits_by_severity.hits_by_severity, + 'label' ); return (
diff --git a/src/Components/WorkloadsListTable/WorkloadsListTable.js b/src/Components/WorkloadsListTable/WorkloadsListTable.js index 40015344..c5821f63 100644 --- a/src/Components/WorkloadsListTable/WorkloadsListTable.js +++ b/src/Components/WorkloadsListTable/WorkloadsListTable.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import PrimaryToolbar from '@redhat-cloud-services/frontend-components/PrimaryToolbar'; import { @@ -24,35 +24,67 @@ import { updateWorkloadsListFilters, } from '../../Services/Filters'; import isEqual from 'lodash/isEqual'; -import { buildFilterChips } from '../Common/Tables'; +import { + addFilterParam, + buildFilterChips, + passFilterWorkloads, + removeFilterParam as _removeFilterParam, +} from '../Common/Tables'; import { ErrorState, NoMatchingClusters } from '../MessageState/EmptyStates'; import Loading from '../Loading/Loading'; import mockdata from '../../../cypress/fixtures/api/insights-results-aggregator/v2/workloads.json'; import ShieldSet from '../ShieldSet'; +import { noFiltersApplied } from '../../Utilities/Workloads'; const WorkloadsListTable = ({ - query: { isError, isUninitialized, isFetching, isSuccess, data }, + query: { isError, isUninitialized, isFetching, isSuccess, data, refetch }, }) => { const dispatch = useDispatch(); const filters = useSelector(({ filters }) => filters.workloadsListState); //const workloads = data?.workloads || []; + //to check all types of filters use the mockdata json const workloads = mockdata; - const [rows, setRows] = React.useState([]); + const [rows, setRows] = useState([]); + const [filteredRows, setFilteredRows] = useState([]); + const [rowsFiltered, setRowsFiltered] = useState(false); + const [filtersApplied, setFiltersApplied] = useState(false); const updateFilters = (payload) => dispatch(updateWorkloadsListFilters(payload)); + const removeFilterParam = (param) => + _removeFilterParam(filters, updateFilters, param); - const loadingState = isUninitialized || isFetching; + const loadingState = isUninitialized || isFetching || !rowsFiltered; const errorState = isError; const noMatch = rows.length === 0; const successState = isSuccess; useEffect(() => { - setRows(buildRows(workloads)); - }, [data]); + setRows(buildFilteredRows(workloads)); + //should be refactored to smth like setDisplayedRows(buildDisplayedRows(filteredRows)); + //when we add pagination + setRowsFiltered(true); + setFiltersApplied(noFiltersApplied(filters).length > 0 ? true : false); + }, [data, filteredRows]); + + useEffect(() => { + setFilteredRows(buildFilteredRows(workloads)); + }, [ + filters.namespace_name, + filters.cluster_name, + filters.general_severity, + filters.highest_severity, + filters.sortDirection, + filters.sortIndex, + ]); - const buildRows = (items) => { - return items.map((item, index) => { + const buildFilteredRows = (items) => { + setRowsFiltered(false); + const filtered = items.filter((workloadData) => { + return passFilterWorkloads(workloadData, filters); + }); + + return filtered.map((item, index) => { return { entity: item, cells: [ @@ -67,10 +99,6 @@ const WorkloadsListTable = ({ item.metadata.recommendations, - {/* */} , item.metadata.objects, @@ -107,29 +135,29 @@ const WorkloadsListTable = ({ }, }, { - label: 'Highest severity', + label: 'Severity', type: conditionalFilterType.checkbox, - id: WORKLOADS_TABLE_FILTER_CATEGORIES.highest_severity.urlParam, - value: `checkbox-${WORKLOADS_TABLE_FILTER_CATEGORIES.highest_severity.urlParam}`, + id: WORKLOADS_TABLE_FILTER_CATEGORIES.general_severity.urlParam, + value: `checkbox-${WORKLOADS_TABLE_FILTER_CATEGORIES.general_severity.urlParam}`, filterValues: { - key: `${WORKLOADS_TABLE_FILTER_CATEGORIES.highest_severity.urlParam}-filter`, + key: `${WORKLOADS_TABLE_FILTER_CATEGORIES.general_severity.urlParam}-filter`, onChange: (_event, value) => - updateFilters({ ...filters, offset: 0, highest_severity: value }), - value: filters.highest_severity, - items: WORKLOADS_TABLE_FILTER_CATEGORIES.highest_severity.values, - placeholder: 'Filter by highest severity', + addFilterParam(filters, updateFilters, 'general_severity', value), + value: filters.general_severity, + items: WORKLOADS_TABLE_FILTER_CATEGORIES.general_severity.values, + placeholder: 'Filter by severity', }, }, ]; const activeFiltersConfig = { - showDeleteButton: true, + showDeleteButton: filtersApplied ? true : false, deleteTitle: 'Reset filters', filters: buildFilterChips(filters, WORKLOADS_TABLE_FILTER_CATEGORIES), onDelete: (_event, itemsToRemove, isAll) => { if (isAll) { if (isEqual(filters, WORKLOADS_TABLE_INITIAL_STATE)) { - console.log('here should be a refetch!'); + refetch(); } else { resetFilters(filters, WORKLOADS_TABLE_INITIAL_STATE, updateFilters); } @@ -144,9 +172,7 @@ const WorkloadsListTable = ({ }; newFilter[item.urlParam].length > 0 ? updateFilters({ ...filters, ...newFilter }) - : console.log( - 'here we should remove the filter parameter from a URL!' - ); + : removeFilterParam(item.urlParam); }); } }, @@ -165,7 +191,9 @@ const WorkloadsListTable = ({ ouiaId: 'pager', }} filterConfig={{ items: filterConfigItems }} - activeFiltersConfig={activeFiltersConfig} + activeFiltersConfig={ + isError ? { showDeleteButton: false } : activeFiltersConfig + } /> { +export const remappingSeverity = (obj, mode) => { const mapping = { 1: 'low', 2: 'moderate', @@ -42,10 +44,45 @@ export const remappingSeverity = (obj) => { }; let updatedObj = {}; - for (const key in obj) { - if (key in mapping) { - updatedObj[mapping[key]] = obj[key]; + if (mode === 'general' || mode === 'label') { + for (const key in obj) { + if (key in mapping) { + updatedObj[mapping[key]] = obj[key]; + } } + } else { + updatedObj = mapping[obj]; } + return updatedObj; }; + +export const hasAnyValueGreaterThanZero = (obj, stringsToCheck) => { + for (const key of stringsToCheck) { + if (obj[key] > 0) { + return true; // Return true if any matching string has a value greater than 0 + } + } +}; + +export const severityTypeToText = (value) => { + value = parseInt(value); + if (value === 1) { + return 'Low'; + } else if (value === 2) { + return 'Moderate'; + } else if (value === 3) { + return 'Important'; + } else { + return 'Critical'; + } +}; + +export const noFiltersApplied = (params) => { + const cleanedUpParams = _.cloneDeep(params); + delete cleanedUpParams.sortIndex; + delete cleanedUpParams.sortDirection; + delete cleanedUpParams.offset; + delete cleanedUpParams.limit; + return Object.values(cleanedUpParams).filter((value) => !isEmpty(value)); +};