diff --git a/src/components/PipelineRunListView/PipelineRunsListView.tsx b/src/components/PipelineRunListView/PipelineRunsListView.tsx index c54c63df7..83babf2e7 100644 --- a/src/components/PipelineRunListView/PipelineRunsListView.tsx +++ b/src/components/PipelineRunListView/PipelineRunsListView.tsx @@ -14,7 +14,7 @@ import { } from '@patternfly/react-core/deprecated'; import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import { debounce } from 'lodash-es'; -import { PipelineRunLabel } from '../../consts/pipelinerun'; +import { PipelineRunLabel, PipelineRunType } from '../../consts/pipelinerun'; import { useComponents } from '../../hooks/useComponents'; import { usePipelineRuns } from '../../hooks/usePipelineRuns'; import { usePLRVulnerabilities } from '../../hooks/useScanResults'; @@ -31,6 +31,8 @@ import PipelineRunEmptyState from '../PipelineRunDetailsView/PipelineRunEmptySta import { PipelineRunListHeaderWithVulnerabilities } from './PipelineRunListHeader'; import { PipelineRunListRowWithVulnerabilities } from './PipelineRunListRow'; +const pipelineRunTypes = [PipelineRunType.BUILD as string, PipelineRunType.TEST as string]; + type PipelineRunsListViewProps = { applicationName: string; componentName?: string; @@ -48,6 +50,8 @@ const PipelineRunsListView: React.FC(false); const [statusFiltersParam, setStatusFiltersParam] = useSearchParam('status', ''); + const [typeFilterExpanded, setTypeFilterExpanded] = React.useState(false); + const [typeFiltersParam, setTypeFiltersParam] = useSearchParam('type', ''); const requestQueue = React.useRef([]); const [onLoadName, setOnLoadName] = React.useState(nameFilter); React.useEffect(() => { @@ -104,20 +108,44 @@ const PipelineRunsListView: React.FC (typeFiltersParam ? typeFiltersParam.split(',') : []), + [typeFiltersParam], + ); + + const setTypeFilters = (filters: string[]) => setTypeFiltersParam(filters.join(',')); + + const typeFilterObj = React.useMemo(() => { + return pipelineRuns.reduce((acc, plr) => { + const runType = plr?.metadata.labels[PipelineRunLabel.COMMIT_TYPE_LABEL]; + if (pipelineRunTypes.includes(runType)) { + if (acc[runType] !== undefined) { + acc[runType] = acc[runType] + 1; + } else { + acc[runType] = 1; + } + } + return acc; + }, {}); + }, [pipelineRuns]); + const filteredPLRs = React.useMemo( () => pipelineRuns - .filter( - (plr) => + .filter((plr) => { + const runType = plr?.metadata.labels[PipelineRunLabel.COMMIT_TYPE_LABEL]; + return ( (!nameFilter || plr.metadata.name.indexOf(nameFilter) >= 0 || plr.metadata.labels?.[PipelineRunLabel.COMPONENT]?.indexOf( nameFilter.trim().toLowerCase(), ) >= 0) && - (!statusFilters.length || statusFilters.includes(pipelineRunStatus(plr))), - ) + (!statusFilters.length || statusFilters.includes(pipelineRunStatus(plr))) && + (!typeFilters.length || typeFilters.includes(runType)) + ); + }) .filter((plr) => !customFilter || customFilter(plr)), - [customFilter, nameFilter, pipelineRuns, statusFilters], + [customFilter, nameFilter, pipelineRuns, statusFilters, typeFilters], ); const vulnerabilities = usePLRVulnerabilities(name ? filteredPLRs : pipelineRuns); @@ -138,6 +166,7 @@ const PipelineRunsListView: React.FC { n.length === 0 && onLoadName.length && setOnLoadName(''); @@ -198,6 +227,41 @@ const PipelineRunsListView: React.FC + + + diff --git a/src/components/PipelineRunListView/__tests__/PipelineRunListView.spec.tsx b/src/components/PipelineRunListView/__tests__/PipelineRunListView.spec.tsx index 30205654a..9ffc39c0b 100644 --- a/src/components/PipelineRunListView/__tests__/PipelineRunListView.spec.tsx +++ b/src/components/PipelineRunListView/__tests__/PipelineRunListView.spec.tsx @@ -223,7 +223,7 @@ describe('Pipeline run List', () => { screen.queryByText('Started'); screen.queryByText('Duration'); screen.queryAllByText('Status'); - screen.queryByText('Type'); + screen.queryAllByText('Type'); screen.queryByText('Component'); }); diff --git a/src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx b/src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx index 3b5a64733..760daf2bb 100644 --- a/src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx +++ b/src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx @@ -24,6 +24,8 @@ import { useSearchParam } from '../../../hooks/useSearchParam'; import { Table } from '../../../shared'; import FilteredEmptyState from '../../../shared/components/empty-state/FilteredEmptyState'; import { PipelineRunKind } from '../../../types'; +import { statuses } from '../../../utils/commits-utils'; +import { pipelineRunStatus } from '../../../utils/pipeline-utils'; import PipelineRunEmptyState from '../../PipelineRunDetailsView/PipelineRunEmptyState'; import { PipelineRunListHeaderWithVulnerabilities } from '../../PipelineRunListView/PipelineRunListHeader'; import { PipelineRunListRowWithVulnerabilities } from '../../PipelineRunListView/PipelineRunListRow'; @@ -48,9 +50,21 @@ const SnapshotPipelineRunsList: React.FC(false); const [typeFiltersParam, setTypeFiltersParam] = useSearchParam('type', ''); + const [statusFilterExpanded, setStatusFilterExpanded] = React.useState(false); + const [statusFiltersParam, setStatusFiltersParam] = useSearchParam('status', ''); const requestQueue = React.useRef([]); const [onLoadName, setOnLoadName] = React.useState(nameFilter); + const statusFilters = React.useMemo( + () => (statusFiltersParam ? statusFiltersParam.split(',') : []), + [statusFiltersParam], + ); + + const setStatusFilters = React.useCallback( + (filters: string[]) => setStatusFiltersParam(filters.join(',')), + [setStatusFiltersParam], + ); + const typeFilters = React.useMemo( () => (typeFiltersParam ? typeFiltersParam.split(',') : []), [typeFiltersParam], @@ -59,6 +73,20 @@ const SnapshotPipelineRunsList: React.FC setTypeFiltersParam(filters.join(',')); const statusFilterObj = React.useMemo(() => { + return snapshotPipelineRuns.reduce((acc, plr) => { + const status = pipelineRunStatus(plr); + if (statuses.includes(status)) { + if (acc[status] !== undefined) { + acc[status] = acc[status] + 1; + } else { + acc[status] = 1; + } + } + return acc; + }, {}); + }, [snapshotPipelineRuns]); + + const typeFilterObj = React.useMemo(() => { return snapshotPipelineRuns.reduce((acc, plr) => { const runType = plr?.metadata.labels[PipelineRunLabel.COMMIT_TYPE_LABEL]; if (pipelineRunTypes.includes(runType)) { @@ -76,6 +104,7 @@ const SnapshotPipelineRunsList: React.FC { @@ -96,11 +125,12 @@ const SnapshotPipelineRunsList: React.FC= 0) && + (!statusFilters.length || statusFilters.includes(pipelineRunStatus(plr))) && (!typeFilters.length || typeFilters.includes(runType)) ); }) .filter((plr) => !customFilter || customFilter(plr)), - [customFilter, nameFilter, snapshotPipelineRuns, typeFilters], + [customFilter, nameFilter, snapshotPipelineRuns, typeFilters, statusFilters], ); const vulnerabilities = usePLRVulnerabilities(name ? filteredPLRs : snapshotPipelineRuns); @@ -132,6 +162,8 @@ const SnapshotPipelineRunsList: React.FC; } + const EmptyMsg = () => ; + return ( <> </ToolbarItem> + <ToolbarItem> + <Select + placeholderText="Status" + toggleIcon={<FilterIcon />} + toggleAriaLabel="Status filter menu" + variant={SelectVariant.checkbox} + isOpen={statusFilterExpanded} + onToggle={(ev, expanded) => setStatusFilterExpanded(expanded)} + onSelect={(event, selection) => { + const checked = (event.target as HTMLInputElement).checked; + setStatusFilters( + checked + ? [...statusFilters, String(selection)] + : statusFilters.filter((value) => value !== selection), + ); + }} + selections={statusFilters} + isGrouped + > + {[ + <SelectGroup label="Status" key="status"> + {Object.keys(statusFilterObj).map((filter) => ( + <SelectOption + key={filter} + value={filter} + isChecked={statusFilters.includes(filter)} + itemCount={statusFilterObj[filter] ?? 0} + > + {filter} + </SelectOption> + ))} + </SelectGroup>, + ]} + </Select> + </ToolbarItem> <ToolbarItem> <Select placeholderText="Type" @@ -177,12 +244,12 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin > {[ <SelectGroup label="Type" key="type"> - {Object.keys(statusFilterObj).map((filter) => ( + {Object.keys(typeFilterObj).map((filter) => ( <SelectOption key={filter} value={filter} isChecked={typeFilters.includes(filter)} - itemCount={statusFilterObj[filter] ?? 0} + itemCount={typeFilterObj[filter] ?? 0} > {capitalize(filter)} </SelectOption> @@ -202,6 +269,7 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin customData={vulnerabilities} Header={PipelineRunListHeaderWithVulnerabilities} Row={PipelineRunListRowWithVulnerabilities} + EmptyMsg={EmptyMsg} loaded getRowProps={(obj: PipelineRunKind) => ({ id: obj.metadata.name,