Skip to content

Commit

Permalink
feat(KFLUXUI-125): allow sorting pipeline runs by status and type
Browse files Browse the repository at this point in the history
  • Loading branch information
marcin-michal committed Nov 5, 2024
1 parent 44f9653 commit a9ad41f
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 10 deletions.
76 changes: 70 additions & 6 deletions src/components/PipelineRunListView/PipelineRunsListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -48,6 +50,8 @@ const PipelineRunsListView: React.FC<React.PropsWithChildren<PipelineRunsListVie
const [name, setName] = React.useState('');
const [statusFilterExpanded, setStatusFilterExpanded] = React.useState<boolean>(false);
const [statusFiltersParam, setStatusFiltersParam] = useSearchParam('status', '');
const [typeFilterExpanded, setTypeFilterExpanded] = React.useState<boolean>(false);
const [typeFiltersParam, setTypeFiltersParam] = useSearchParam('type', '');
const requestQueue = React.useRef<Function[]>([]);
const [onLoadName, setOnLoadName] = React.useState(nameFilter);
React.useEffect(() => {
Expand Down Expand Up @@ -104,20 +108,44 @@ const PipelineRunsListView: React.FC<React.PropsWithChildren<PipelineRunsListVie
}, {});
}, [pipelineRuns]);

const typeFilters = React.useMemo(
() => (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;

Check warning on line 125 in src/components/PipelineRunListView/PipelineRunsListView.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/PipelineRunListView/PipelineRunsListView.tsx#L123-L125

Added lines #L123 - L125 were not covered by tests
}
}
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);
Expand All @@ -138,6 +166,7 @@ const PipelineRunsListView: React.FC<React.PropsWithChildren<PipelineRunsListVie
setNameFilter('');
setName('');
setStatusFilters([]);
setTypeFilters([]);
};
const onNameInput = debounce((n: string) => {
n.length === 0 && onLoadName.length && setOnLoadName('');
Expand Down Expand Up @@ -198,6 +227,41 @@ const PipelineRunsListView: React.FC<React.PropsWithChildren<PipelineRunsListVie
]}
</Select>
</ToolbarItem>
<ToolbarItem>
<Select
placeholderText="Type"
toggleIcon={<FilterIcon />}
toggleAriaLabel="Type filter menu"
variant={SelectVariant.checkbox}
isOpen={typeFilterExpanded}
onToggle={(ev, expanded) => setTypeFilterExpanded(expanded)}
onSelect={(event, selection) => {
const checked = (event.target as HTMLInputElement).checked;
setTypeFilters(

Check warning on line 240 in src/components/PipelineRunListView/PipelineRunsListView.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/PipelineRunListView/PipelineRunsListView.tsx#L237-L240

Added lines #L237 - L240 were not covered by tests
checked
? [...typeFilters, String(selection)]
: typeFilters.filter((value) => value !== selection),

Check warning on line 243 in src/components/PipelineRunListView/PipelineRunsListView.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/PipelineRunListView/PipelineRunsListView.tsx#L242-L243

Added lines #L242 - L243 were not covered by tests
);
}}
selections={typeFilters}
isGrouped
>
{[
<SelectGroup label="Type" key="type">
{Object.keys(typeFilterObj).map((filter) => (

Check warning on line 251 in src/components/PipelineRunListView/PipelineRunsListView.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/PipelineRunListView/PipelineRunsListView.tsx#L251

Added line #L251 was not covered by tests
<SelectOption
key={filter}
value={filter}
isChecked={typeFilters.includes(filter)}
itemCount={typeFilterObj[filter] ?? 0}
>
{filter}
</SelectOption>
))}
</SelectGroup>,
]}
</Select>
</ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down
74 changes: 71 additions & 3 deletions src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -48,9 +50,21 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
const [name, setName] = React.useState('');
const [typeFilterExpanded, setTypeFilterExpanded] = React.useState<boolean>(false);
const [typeFiltersParam, setTypeFiltersParam] = useSearchParam('type', '');
const [statusFilterExpanded, setStatusFilterExpanded] = React.useState<boolean>(false);
const [statusFiltersParam, setStatusFiltersParam] = useSearchParam('status', '');
const requestQueue = React.useRef<Function[]>([]);
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],
Expand All @@ -59,6 +73,20 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
const setTypeFilters = (filters: string[]) => 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)) {
Expand All @@ -76,6 +104,7 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
onLoadName.length && setOnLoadName('');
setNameFilter('');
setName('');
setStatusFilters([]);
setTypeFilters([]);
};
const onNameInput = debounce((n: string) => {
Expand All @@ -96,11 +125,12 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
plr.metadata.labels?.[PipelineRunLabel.COMPONENT]?.indexOf(
nameFilter.trim().toLowerCase(),
) >= 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);
Expand Down Expand Up @@ -132,6 +162,8 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
return <PipelineRunEmptyState applicationName={applicationName} />;
}

const EmptyMsg = () => <FilteredEmptyState onClearFilters={onClearFilters} />;

return (
<>
<Title
Expand All @@ -155,6 +187,41 @@ const SnapshotPipelineRunsList: React.FC<React.PropsWithChildren<SnapshotPipelin
value={nameFilter}
/>
</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(

Check warning on line 200 in src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx#L197-L200

Added lines #L197 - L200 were not covered by tests
checked
? [...statusFilters, String(selection)]
: statusFilters.filter((value) => value !== selection),

Check warning on line 203 in src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/SnapshotDetails/tabs/SnapshotPipelineRunsList.tsx#L202-L203

Added lines #L202 - L203 were not covered by tests
);
}}
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"
Expand All @@ -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>
Expand All @@ -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,
Expand Down

0 comments on commit a9ad41f

Please sign in to comment.