Skip to content

Commit cf78dbb

Browse files
Merge pull request #2343 from github/robertbrignull/results_filter
Add a component for filtering repositories based on their results
2 parents dce94e8 + 2844fed commit cf78dbb

File tree

7 files changed

+284
-41
lines changed

7 files changed

+284
-41
lines changed

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [UNRELEASED]
44

5+
- Added ability to filter repositories for a variant analysis to only those that have results [#2343](https://github.com/github/vscode-codeql/pull/2343)
56
- Add new configuration option to allow downloading databases from http, non-secure servers. [#2332](https://github.com/github/vscode-codeql/pull/2332)
67

78
## 1.8.2 - 12 April 2023

extensions/ql-vscode/src/pure/variant-analysis-filter-sort.ts

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import {
33
RepositoryWithMetadata,
44
} from "../variant-analysis/shared/repository";
55
import { parseDate } from "./date";
6+
import { assertNever } from "./helpers-pure";
7+
8+
export enum FilterKey {
9+
All = "all",
10+
WithResults = "withResults",
11+
}
612

713
export enum SortKey {
814
Name = "name",
@@ -13,6 +19,7 @@ export enum SortKey {
1319

1420
export type RepositoriesFilterSortState = {
1521
searchValue: string;
22+
filterKey: FilterKey;
1623
sortKey: SortKey;
1724
};
1825

@@ -22,20 +29,43 @@ export type RepositoriesFilterSortStateWithIds = RepositoriesFilterSortState & {
2229

2330
export const defaultFilterSortState: RepositoriesFilterSortState = {
2431
searchValue: "",
32+
filterKey: FilterKey.All,
2533
sortKey: SortKey.Name,
2634
};
2735

2836
export function matchesFilter(
29-
repo: Pick<Repository, "fullName">,
37+
item: FilterAndSortableResult,
3038
filterSortState: RepositoriesFilterSortState | undefined,
3139
): boolean {
3240
if (!filterSortState) {
3341
return true;
3442
}
3543

36-
return repo.fullName
37-
.toLowerCase()
38-
.includes(filterSortState.searchValue.toLowerCase());
44+
return (
45+
matchesSearch(item.repository, filterSortState.searchValue) &&
46+
matchesFilterKey(item.resultCount, filterSortState.filterKey)
47+
);
48+
}
49+
50+
function matchesSearch(
51+
repository: SortableRepository,
52+
searchValue: string,
53+
): boolean {
54+
return repository.fullName.toLowerCase().includes(searchValue.toLowerCase());
55+
}
56+
57+
function matchesFilterKey(
58+
resultCount: number | undefined,
59+
filterKey: FilterKey,
60+
): boolean {
61+
switch (filterKey) {
62+
case FilterKey.All:
63+
return true;
64+
case FilterKey.WithResults:
65+
return resultCount !== undefined && resultCount > 0;
66+
default:
67+
assertNever(filterKey);
68+
}
3969
}
4070

4171
type SortableRepository = Pick<Repository, "fullName"> &
@@ -71,17 +101,22 @@ export function compareRepository(
71101
};
72102
}
73103

74-
type SortableResult = {
104+
type FilterAndSortableResult = {
105+
repository: SortableRepository;
106+
resultCount?: number;
107+
};
108+
109+
type FilterAndSortableResultWithIds = {
75110
repository: SortableRepository & Pick<Repository, "id">;
76111
resultCount?: number;
77112
};
78113

79114
export function compareWithResults(
80115
filterSortState: RepositoriesFilterSortState | undefined,
81-
): (left: SortableResult, right: SortableResult) => number {
116+
): (left: FilterAndSortableResult, right: FilterAndSortableResult) => number {
82117
const fallbackSort = compareRepository(filterSortState);
83118

84-
return (left: SortableResult, right: SortableResult) => {
119+
return (left: FilterAndSortableResult, right: FilterAndSortableResult) => {
85120
// Highest to lowest
86121
if (filterSortState?.sortKey === SortKey.ResultsCount) {
87122
const resultCount = (right.resultCount ?? 0) - (left.resultCount ?? 0);
@@ -95,7 +130,7 @@ export function compareWithResults(
95130
}
96131

97132
export function filterAndSortRepositoriesWithResultsByName<
98-
T extends SortableResult,
133+
T extends FilterAndSortableResult,
99134
>(
100135
repositories: T[] | undefined,
101136
filterSortState: RepositoriesFilterSortState | undefined,
@@ -105,18 +140,21 @@ export function filterAndSortRepositoriesWithResultsByName<
105140
}
106141

107142
return repositories
108-
.filter((repo) => matchesFilter(repo.repository, filterSortState))
143+
.filter((repo) => matchesFilter(repo, filterSortState))
109144
.sort(compareWithResults(filterSortState));
110145
}
111146

112-
export function filterAndSortRepositoriesWithResults<T extends SortableResult>(
147+
export function filterAndSortRepositoriesWithResults<
148+
T extends FilterAndSortableResultWithIds,
149+
>(
113150
repositories: T[] | undefined,
114151
filterSortState: RepositoriesFilterSortStateWithIds | undefined,
115152
): T[] | undefined {
116153
if (!repositories) {
117154
return undefined;
118155
}
119156

157+
// If repository IDs are given, then ignore the search value and filter key
120158
if (
121159
filterSortState?.repositoryIds &&
122160
filterSortState.repositoryIds.length > 0
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from "react";
2+
import { useState } from "react";
3+
4+
import { ComponentMeta } from "@storybook/react";
5+
6+
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
7+
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
8+
9+
export default {
10+
title: "Variant Analysis/Repositories Filter",
11+
component: RepositoriesFilterComponent,
12+
argTypes: {
13+
value: {
14+
control: {
15+
disable: true,
16+
},
17+
},
18+
},
19+
} as ComponentMeta<typeof RepositoriesFilterComponent>;
20+
21+
export const RepositoriesFilter = () => {
22+
const [value, setValue] = useState(FilterKey.All);
23+
24+
return <RepositoriesFilterComponent value={value} onChange={setValue} />;
25+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as React from "react";
2+
import { useCallback } from "react";
3+
import styled from "styled-components";
4+
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
5+
import { Codicon } from "../common";
6+
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
7+
8+
const Dropdown = styled(VSCodeDropdown)`
9+
width: 100%;
10+
`;
11+
12+
type Props = {
13+
value: FilterKey;
14+
onChange: (value: FilterKey) => void;
15+
16+
className?: string;
17+
};
18+
19+
export const RepositoriesFilter = ({ value, onChange, className }: Props) => {
20+
const handleInput = useCallback(
21+
(e: InputEvent) => {
22+
const target = e.target as HTMLSelectElement;
23+
24+
onChange(target.value as FilterKey);
25+
},
26+
[onChange],
27+
);
28+
29+
return (
30+
<Dropdown value={value} onInput={handleInput} className={className}>
31+
<Codicon name="list-filter" label="Filter..." slot="indicator" />
32+
<VSCodeOption value={FilterKey.All}>All</VSCodeOption>
33+
<VSCodeOption value={FilterKey.WithResults}>With results</VSCodeOption>
34+
</Dropdown>
35+
);
36+
};

extensions/ql-vscode/src/view/variant-analysis/RepositoriesSearchSortRow.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import * as React from "react";
22
import { Dispatch, SetStateAction, useCallback } from "react";
33
import styled from "styled-components";
44
import {
5+
FilterKey,
56
RepositoriesFilterSortState,
67
SortKey,
78
} from "../../pure/variant-analysis-filter-sort";
89
import { RepositoriesSearch } from "./RepositoriesSearch";
910
import { RepositoriesSort } from "./RepositoriesSort";
11+
import { RepositoriesFilter } from "./RepositoriesFilter";
1012

1113
type Props = {
1214
value: RepositoriesFilterSortState;
@@ -25,6 +27,10 @@ const RepositoriesSearchColumn = styled(RepositoriesSearch)`
2527
flex: 3;
2628
`;
2729

30+
const RepositoriesFilterColumn = styled(RepositoriesFilter)`
31+
flex: 1;
32+
`;
33+
2834
const RepositoriesSortColumn = styled(RepositoriesSort)`
2935
flex: 1;
3036
`;
@@ -40,6 +46,16 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
4046
[onChange],
4147
);
4248

49+
const handleFilterKeyChange = useCallback(
50+
(filterKey: FilterKey) => {
51+
onChange((oldValue) => ({
52+
...oldValue,
53+
filterKey,
54+
}));
55+
},
56+
[onChange],
57+
);
58+
4359
const handleSortKeyChange = useCallback(
4460
(sortKey: SortKey) => {
4561
onChange((oldValue) => ({
@@ -56,6 +72,10 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
5672
value={value.searchValue}
5773
onChange={handleSearchValueChange}
5874
/>
75+
<RepositoriesFilterColumn
76+
value={value.filterKey}
77+
onChange={handleFilterKeyChange}
78+
/>
5979
<RepositoriesSortColumn
6080
value={value.sortKey}
6181
onChange={handleSortKeyChange}

extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoriesTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
5656
}: VariantAnalysisSkippedRepositoriesTabProps) => {
5757
const repositories = useMemo(() => {
5858
return skippedRepositoryGroup.repositories
59-
?.filter((repo) => {
60-
return matchesFilter(repo, filterSortState);
59+
?.filter((repository) => {
60+
return matchesFilter({ repository }, filterSortState);
6161
})
6262
?.sort(compareRepository(filterSortState));
6363
}, [filterSortState, skippedRepositoryGroup.repositories]);

0 commit comments

Comments
 (0)