Skip to content

Commit 5ff7b8a

Browse files
committed
Add sorting to variant analysis repositories
This adds sorting to the variant analysis repositories on the outcome panels. The sort state is shared between all panels, so unlike the design this doesn't disable the sort when you are on e.g. the no access panel.
1 parent 18111ff commit 5ff7b8a

12 files changed

+638
-41
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useState } from 'react';
2+
3+
import { ComponentMeta } from '@storybook/react';
4+
5+
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from '../../view/variant-analysis/RepositoriesSearchSortRow';
6+
import { defaultFilterSortState } from '../../view/variant-analysis/filterSort';
7+
8+
export default {
9+
title: 'Variant Analysis/Repositories Search and Sort Row',
10+
component: RepositoriesSearchSortRowComponent,
11+
argTypes: {
12+
value: {
13+
control: {
14+
disable: true,
15+
},
16+
},
17+
}
18+
} as ComponentMeta<typeof RepositoriesSearchSortRowComponent>;
19+
20+
export const RepositoriesSearchSortRow = () => {
21+
const [value, setValue] = useState(defaultFilterSortState);
22+
23+
return (
24+
<RepositoriesSearchSortRowComponent value={value} onChange={setValue} />
25+
);
26+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useState } from 'react';
2+
3+
import { ComponentMeta } from '@storybook/react';
4+
5+
import { RepositoriesSort as RepositoriesSortComponent } from '../../view/variant-analysis/RepositoriesSort';
6+
import { SortKey } from '../../view/variant-analysis/filterSort';
7+
8+
export default {
9+
title: 'Variant Analysis/Repositories Sort',
10+
component: RepositoriesSortComponent,
11+
argTypes: {
12+
value: {
13+
control: {
14+
disable: true,
15+
},
16+
},
17+
}
18+
} as ComponentMeta<typeof RepositoriesSortComponent>;
19+
20+
export const RepositoriesSort = () => {
21+
const [value, setValue] = useState(SortKey.Name);
22+
23+
return (
24+
<RepositoriesSortComponent value={value} onChange={setValue} />
25+
);
26+
};

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ const TextField = styled(VSCodeTextField)`
1111
type Props = {
1212
value: string;
1313
onChange: (value: string) => void;
14+
15+
className?: string;
1416
}
1517

16-
export const RepositoriesSearch = ({ value, onChange }: Props) => {
18+
export const RepositoriesSearch = ({ value, onChange, className }: Props) => {
1719
const handleInput = useCallback((e: InputEvent) => {
1820
const target = e.target as HTMLInputElement;
1921

@@ -25,6 +27,7 @@ export const RepositoriesSearch = ({ value, onChange }: Props) => {
2527
placeholder='Filter by repository owner/name'
2628
value={value}
2729
onInput={handleInput}
30+
className={className}
2831
>
2932
<Codicon name="search" label="Search..." slot="start" />
3033
</TextField>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from 'react';
2+
import { Dispatch, SetStateAction, useCallback } from 'react';
3+
import styled from 'styled-components';
4+
import { RepositoriesFilterSortState, SortKey } from './filterSort';
5+
import { RepositoriesSearch } from './RepositoriesSearch';
6+
import { RepositoriesSort } from './RepositoriesSort';
7+
8+
type Props = {
9+
value: RepositoriesFilterSortState;
10+
onChange: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
11+
}
12+
13+
const Container = styled.div`
14+
display: flex;
15+
gap: 1em;
16+
17+
width: 100%;
18+
`;
19+
20+
const RepositoriesSearchColumn = styled(RepositoriesSearch)`
21+
flex: 3;
22+
`;
23+
24+
const RepositoriesSortColumn = styled(RepositoriesSort)`
25+
flex: 1;
26+
`;
27+
28+
export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
29+
const handleSearchValueChange = useCallback((searchValue: string) => {
30+
onChange(oldValue => ({
31+
...oldValue,
32+
searchValue,
33+
}));
34+
}, [onChange]);
35+
36+
const handleSortKeyChange = useCallback((sortKey: SortKey) => {
37+
onChange(oldValue => ({
38+
...oldValue,
39+
sortKey,
40+
}));
41+
}, [onChange]);
42+
43+
return (
44+
<Container>
45+
<RepositoriesSearchColumn value={value.searchValue} onChange={handleSearchValueChange} />
46+
<RepositoriesSortColumn value={value.sortKey} onChange={handleSortKeyChange} />
47+
</Container>
48+
);
49+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 { SortKey } from './filterSort';
6+
import { Codicon } from '../common';
7+
8+
const Dropdown = styled(VSCodeDropdown)`
9+
width: 100%;
10+
`;
11+
12+
type Props = {
13+
value: SortKey;
14+
onChange: (value: SortKey) => void;
15+
16+
className?: string;
17+
}
18+
19+
export const RepositoriesSort = ({ value, onChange, className }: Props) => {
20+
const handleInput = useCallback((e: InputEvent) => {
21+
const target = e.target as HTMLSelectElement;
22+
23+
onChange(target.value as SortKey);
24+
}, [onChange]);
25+
26+
return (
27+
<Dropdown
28+
value={value}
29+
onInput={handleInput}
30+
className={className}
31+
>
32+
<Codicon name="sort-precedence" label="Sort..." slot="indicator" />
33+
<VSCodeOption value={SortKey.Name}>Name</VSCodeOption>
34+
<VSCodeOption value={SortKey.ResultsCount}>Results</VSCodeOption>
35+
<VSCodeOption value={SortKey.Stars}>Stars</VSCodeOption>
36+
<VSCodeOption value={SortKey.LastUpdated}>Last commit</VSCodeOption>
37+
</Dropdown>
38+
);
39+
};

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
VariantAnalysisScannedRepositoryResult,
88
VariantAnalysisScannedRepositoryState
99
} from '../../remote-queries/shared/variant-analysis';
10-
import { matchesSearchValue } from './filterSort';
10+
import { compareWithResults, matchesFilter, RepositoriesFilterSortState } from './filterSort';
1111

1212
const Container = styled.div`
1313
display: flex;
@@ -21,14 +21,14 @@ export type VariantAnalysisAnalyzedReposProps = {
2121
repositoryStates?: VariantAnalysisScannedRepositoryState[];
2222
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
2323

24-
searchValue?: string;
24+
filterSortState?: RepositoriesFilterSortState;
2525
}
2626

2727
export const VariantAnalysisAnalyzedRepos = ({
2828
variantAnalysis,
2929
repositoryStates,
3030
repositoryResults,
31-
searchValue,
31+
filterSortState,
3232
}: VariantAnalysisAnalyzedReposProps) => {
3333
const repositoryStateById = useMemo(() => {
3434
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
@@ -47,14 +47,10 @@ export const VariantAnalysisAnalyzedRepos = ({
4747
}, [repositoryResults]);
4848

4949
const repositories = useMemo(() => {
50-
if (searchValue) {
51-
return variantAnalysis.scannedRepos?.filter((repoTask) => {
52-
return matchesSearchValue(repoTask.repository, searchValue);
53-
});
54-
}
55-
56-
return variantAnalysis.scannedRepos;
57-
}, [searchValue, variantAnalysis.scannedRepos]);
50+
return variantAnalysis.scannedRepos?.filter((repoTask) => {
51+
return matchesFilter(repoTask.repository, filterSortState);
52+
})?.sort(compareWithResults(filterSortState));
53+
}, [filterSortState, variantAnalysis.scannedRepos]);
5854

5955
return (
6056
<Container>

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
1212
import { Alert } from '../common';
1313
import { VariantAnalysisSkippedRepositoriesTab } from './VariantAnalysisSkippedRepositoriesTab';
14-
import { RepositoriesSearch } from './RepositoriesSearch';
14+
import { defaultFilterSortState, RepositoriesFilterSortState } from './filterSort';
15+
import { RepositoriesSearchSortRow } from './RepositoriesSearchSortRow';
1516

1617
export type VariantAnalysisOutcomePanelProps = {
1718
variantAnalysis: VariantAnalysis;
@@ -44,7 +45,7 @@ export const VariantAnalysisOutcomePanels = ({
4445
repositoryStates,
4546
repositoryResults,
4647
}: VariantAnalysisOutcomePanelProps) => {
47-
const [searchValue, setSearchValue] = useState('');
48+
const [filterSortState, setFilterSortState] = useState<RepositoriesFilterSortState>(defaultFilterSortState);
4849

4950
const noCodeqlDbRepos = variantAnalysis.skippedRepos?.noCodeqlDbRepos;
5051
const notFoundRepos = variantAnalysis.skippedRepos?.notFoundRepos;
@@ -74,12 +75,12 @@ export const VariantAnalysisOutcomePanels = ({
7475
return (
7576
<>
7677
{warnings}
77-
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
78+
<RepositoriesSearchSortRow value={filterSortState} onChange={setFilterSortState} />
7879
<VariantAnalysisAnalyzedRepos
7980
variantAnalysis={variantAnalysis}
8081
repositoryStates={repositoryStates}
8182
repositoryResults={repositoryResults}
82-
searchValue={searchValue}
83+
filterSortState={filterSortState}
8384
/>
8485
</>
8586
);
@@ -88,7 +89,7 @@ export const VariantAnalysisOutcomePanels = ({
8889
return (
8990
<>
9091
{warnings}
91-
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
92+
<RepositoriesSearchSortRow value={filterSortState} onChange={setFilterSortState} />
9293
<VSCodePanels>
9394
<Tab>
9495
Analyzed
@@ -111,7 +112,7 @@ export const VariantAnalysisOutcomePanels = ({
111112
variantAnalysis={variantAnalysis}
112113
repositoryStates={repositoryStates}
113114
repositoryResults={repositoryResults}
114-
searchValue={searchValue}
115+
filterSortState={filterSortState}
115116
/>
116117
</VSCodePanelView>
117118
{notFoundRepos?.repositoryCount &&
@@ -120,7 +121,7 @@ export const VariantAnalysisOutcomePanels = ({
120121
alertTitle='No access'
121122
alertMessage='The following repositories could not be scanned because you do not have read access.'
122123
skippedRepositoryGroup={notFoundRepos}
123-
searchValue={searchValue}
124+
filterSortState={filterSortState}
124125
/>
125126
</VSCodePanelView>}
126127
{noCodeqlDbRepos?.repositoryCount &&
@@ -129,7 +130,7 @@ export const VariantAnalysisOutcomePanels = ({
129130
alertTitle='No database'
130131
alertMessage='The following repositories could not be scanned because they do not have an available CodeQL database.'
131132
skippedRepositoryGroup={noCodeqlDbRepos}
132-
searchValue={searchValue}
133+
filterSortState={filterSortState}
133134
/>
134135
</VSCodePanelView>}
135136
</VSCodePanels>

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import styled from 'styled-components';
44
import { VariantAnalysisSkippedRepositoryGroup } from '../../remote-queries/shared/variant-analysis';
55
import { Alert } from '../common';
66
import { RepoRow } from './RepoRow';
7-
import { matchesSearchValue } from './filterSort';
7+
import { compareRepository, matchesFilter, RepositoriesFilterSortState } from './filterSort';
88

99
export type VariantAnalysisSkippedRepositoriesTabProps = {
1010
alertTitle: string,
1111
alertMessage: string,
1212
skippedRepositoryGroup: VariantAnalysisSkippedRepositoryGroup,
1313

14-
searchValue?: string,
14+
filterSortState?: RepositoriesFilterSortState,
1515
};
1616

1717
function getSkipReasonAlert(
@@ -43,17 +43,13 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
4343
alertTitle,
4444
alertMessage,
4545
skippedRepositoryGroup,
46-
searchValue,
46+
filterSortState,
4747
}: VariantAnalysisSkippedRepositoriesTabProps) => {
4848
const repositories = useMemo(() => {
49-
if (searchValue) {
50-
return skippedRepositoryGroup.repositories?.filter((repo) => {
51-
return matchesSearchValue(repo, searchValue);
52-
});
53-
}
54-
55-
return skippedRepositoryGroup.repositories;
56-
}, [searchValue, skippedRepositoryGroup.repositories]);
49+
return skippedRepositoryGroup.repositories?.filter((repo) => {
50+
return matchesFilter(repo, filterSortState);
51+
})?.sort(compareRepository(filterSortState));
52+
}, [filterSortState, skippedRepositoryGroup.repositories]);
5753

5854
return (
5955
<Container>

0 commit comments

Comments
 (0)