Skip to content

Commit c0ba9c5

Browse files
authored
Merge pull request #1705 from github/koesie10/filter-repositories-by-name
Add repository filter by full name
2 parents e7a0c7e + 18111ff commit c0ba9c5

12 files changed

+221
-10
lines changed

extensions/ql-vscode/src/stories/remote-queries/RepositoriesSearch.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ComponentMeta } from '@storybook/react';
55
import RepositoriesSearchComponent from '../../view/remote-queries/RepositoriesSearch';
66

77
export default {
8-
title: 'Repositories Search',
8+
title: 'MRVA/Repositories Search',
99
component: RepositoriesSearchComponent,
1010
argTypes: {
1111
filterValue: {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { useState } from 'react';
2+
3+
import { ComponentMeta } from '@storybook/react';
4+
5+
import { RepositoriesSearch as RepositoriesSearchComponent } from '../../view/variant-analysis/RepositoriesSearch';
6+
7+
export default {
8+
title: 'Variant Analysis/Repositories Search',
9+
component: RepositoriesSearchComponent,
10+
argTypes: {
11+
value: {
12+
control: {
13+
disable: true,
14+
},
15+
},
16+
}
17+
} as ComponentMeta<typeof RepositoriesSearchComponent>;
18+
19+
export const RepositoriesSearch = () => {
20+
const [value, setValue] = useState('');
21+
22+
return (
23+
<RepositoriesSearchComponent value={value} onChange={setValue} />
24+
);
25+
};

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react';
22

33
import { ComponentMeta, ComponentStory } from '@storybook/react';
44

5+
import { faker } from '@faker-js/faker';
6+
57
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
68
import { VariantAnalysisAnalyzedRepos } from '../../view/variant-analysis/VariantAnalysisAnalyzedRepos';
79
import {
@@ -11,6 +13,7 @@ import {
1113
import { AnalysisAlert } from '../../remote-queries/shared/analysis-result';
1214
import { createMockVariantAnalysis } from '../../vscode-tests/factories/remote-queries/shared/variant-analysis';
1315
import { createMockRepositoryWithMetadata } from '../../vscode-tests/factories/remote-queries/shared/repository';
16+
import { createMockScannedRepo } from '../../vscode-tests/factories/remote-queries/shared/scanned-repositories';
1417

1518
import analysesResults from '../remote-queries/data/analysesResultsMessage.json';
1619

@@ -114,5 +117,40 @@ Example.args = {
114117
interpretedResults: interpretedResultsForRepo('expressjs/express'),
115118
}
116119
]
117-
}
118-
;
120+
};
121+
122+
faker.seed(42);
123+
const uniqueStore = {};
124+
125+
const manyScannedRepos = Array.from({ length: 1000 }, (_, i) => {
126+
const mockedScannedRepo = createMockScannedRepo();
127+
128+
return {
129+
...mockedScannedRepo,
130+
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
131+
resultCount: faker.datatype.number({ min: 0, max: 1000 }),
132+
repository: {
133+
...mockedScannedRepo.repository,
134+
// We need to ensure the ID is unique for React keys
135+
id: faker.helpers.unique(faker.datatype.number, [], {
136+
store: uniqueStore,
137+
}),
138+
fullName: `octodemo/${faker.helpers.unique(faker.random.word, [], {
139+
store: uniqueStore,
140+
})}`,
141+
}
142+
};
143+
});
144+
145+
export const PerformanceExample = Template.bind({});
146+
PerformanceExample.args = {
147+
variantAnalysis: {
148+
...createMockVariantAnalysis(VariantAnalysisStatus.Succeeded, manyScannedRepos),
149+
id: 1,
150+
},
151+
repositoryResults: manyScannedRepos.map(repoTask => ({
152+
variantAnalysisId: 1,
153+
repositoryId: repoTask.repository.id,
154+
interpretedResults: interpretedResultsForRepo('facebook/create-react-app'),
155+
}))
156+
};

extensions/ql-vscode/src/view/common/icon/Codicon.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type Props = {
66
name: string;
77
label: string;
88
className?: string;
9+
slot?: string;
910
};
1011

1112
const CodiconIcon = styled.span`
@@ -15,5 +16,6 @@ const CodiconIcon = styled.span`
1516
export const Codicon = ({
1617
name,
1718
label,
18-
className
19-
}: Props) => <CodiconIcon role="img" aria-label={label} className={classNames('codicon', `codicon-${name}`, className)} />;
19+
className,
20+
slot,
21+
}: Props) => <CodiconIcon role="img" aria-label={label} className={classNames('codicon', `codicon-${name}`, className)} slot={slot} />;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react';
2+
import { useCallback } from 'react';
3+
import styled from 'styled-components';
4+
import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react';
5+
import { Codicon } from '../common';
6+
7+
const TextField = styled(VSCodeTextField)`
8+
width: 100%;
9+
`;
10+
11+
type Props = {
12+
value: string;
13+
onChange: (value: string) => void;
14+
}
15+
16+
export const RepositoriesSearch = ({ value, onChange }: Props) => {
17+
const handleInput = useCallback((e: InputEvent) => {
18+
const target = e.target as HTMLInputElement;
19+
20+
onChange(target.value);
21+
}, [onChange]);
22+
23+
return (
24+
<TextField
25+
placeholder='Filter by repository owner/name'
26+
value={value}
27+
onInput={handleInput}
28+
>
29+
<Codicon name="search" label="Search..." slot="start" />
30+
</TextField>
31+
);
32+
};

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
2+
import { useMemo } from 'react';
23
import styled from 'styled-components';
34
import { RepoRow } from './RepoRow';
45
import {
56
VariantAnalysis,
67
VariantAnalysisScannedRepositoryResult,
78
VariantAnalysisScannedRepositoryState
89
} from '../../remote-queries/shared/variant-analysis';
9-
import { useMemo } from 'react';
10+
import { matchesSearchValue } from './filterSort';
1011

1112
const Container = styled.div`
1213
display: flex;
@@ -19,12 +20,15 @@ export type VariantAnalysisAnalyzedReposProps = {
1920
variantAnalysis: VariantAnalysis;
2021
repositoryStates?: VariantAnalysisScannedRepositoryState[];
2122
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
23+
24+
searchValue?: string;
2225
}
2326

2427
export const VariantAnalysisAnalyzedRepos = ({
2528
variantAnalysis,
2629
repositoryStates,
2730
repositoryResults,
31+
searchValue,
2832
}: VariantAnalysisAnalyzedReposProps) => {
2933
const repositoryStateById = useMemo(() => {
3034
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
@@ -42,9 +46,19 @@ export const VariantAnalysisAnalyzedRepos = ({
4246
return map;
4347
}, [repositoryResults]);
4448

49+
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]);
58+
4559
return (
4660
<Container>
47-
{variantAnalysis.scannedRepos?.map(repository => {
61+
{repositories?.map(repository => {
4862
const state = repositoryStateById.get(repository.repository.id);
4963
const results = repositoryResultsById.get(repository.repository.id);
5064

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { useState } from 'react';
23
import styled from 'styled-components';
34
import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vscode/webview-ui-toolkit/react';
45
import { formatDecimal } from '../../pure/number';
@@ -10,6 +11,7 @@ import {
1011
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
1112
import { Alert } from '../common';
1213
import { VariantAnalysisSkippedRepositoriesTab } from './VariantAnalysisSkippedRepositoriesTab';
14+
import { RepositoriesSearch } from './RepositoriesSearch';
1315

1416
export type VariantAnalysisOutcomePanelProps = {
1517
variantAnalysis: VariantAnalysis;
@@ -42,6 +44,8 @@ export const VariantAnalysisOutcomePanels = ({
4244
repositoryStates,
4345
repositoryResults,
4446
}: VariantAnalysisOutcomePanelProps) => {
47+
const [searchValue, setSearchValue] = useState('');
48+
4549
const noCodeqlDbRepos = variantAnalysis.skippedRepos?.noCodeqlDbRepos;
4650
const notFoundRepos = variantAnalysis.skippedRepos?.notFoundRepos;
4751
const overLimitRepositoryCount = variantAnalysis.skippedRepos?.overLimitRepos?.repositoryCount ?? 0;
@@ -70,10 +74,12 @@ export const VariantAnalysisOutcomePanels = ({
7074
return (
7175
<>
7276
{warnings}
77+
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
7378
<VariantAnalysisAnalyzedRepos
7479
variantAnalysis={variantAnalysis}
7580
repositoryStates={repositoryStates}
7681
repositoryResults={repositoryResults}
82+
searchValue={searchValue}
7783
/>
7884
</>
7985
);
@@ -82,6 +88,7 @@ export const VariantAnalysisOutcomePanels = ({
8288
return (
8389
<>
8490
{warnings}
91+
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
8592
<VSCodePanels>
8693
<Tab>
8794
Analyzed
@@ -104,21 +111,26 @@ export const VariantAnalysisOutcomePanels = ({
104111
variantAnalysis={variantAnalysis}
105112
repositoryStates={repositoryStates}
106113
repositoryResults={repositoryResults}
114+
searchValue={searchValue}
107115
/>
108116
</VSCodePanelView>
109117
{notFoundRepos?.repositoryCount &&
110118
<VSCodePanelView>
111119
<VariantAnalysisSkippedRepositoriesTab
112120
alertTitle='No access'
113121
alertMessage='The following repositories could not be scanned because you do not have read access.'
114-
skippedRepositoryGroup={notFoundRepos} />
122+
skippedRepositoryGroup={notFoundRepos}
123+
searchValue={searchValue}
124+
/>
115125
</VSCodePanelView>}
116126
{noCodeqlDbRepos?.repositoryCount &&
117127
<VSCodePanelView>
118128
<VariantAnalysisSkippedRepositoriesTab
119129
alertTitle='No database'
120130
alertMessage='The following repositories could not be scanned because they do not have an available CodeQL database.'
121-
skippedRepositoryGroup={noCodeqlDbRepos} />
131+
skippedRepositoryGroup={noCodeqlDbRepos}
132+
searchValue={searchValue}
133+
/>
122134
</VSCodePanelView>}
123135
</VSCodePanels>
124136
</>

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import * as React from 'react';
2+
import { useMemo } from 'react';
23
import styled from 'styled-components';
34
import { VariantAnalysisSkippedRepositoryGroup } from '../../remote-queries/shared/variant-analysis';
45
import { Alert } from '../common';
56
import { RepoRow } from './RepoRow';
7+
import { matchesSearchValue } from './filterSort';
68

79
export type VariantAnalysisSkippedRepositoriesTabProps = {
810
alertTitle: string,
911
alertMessage: string,
1012
skippedRepositoryGroup: VariantAnalysisSkippedRepositoryGroup,
13+
14+
searchValue?: string,
1115
};
1216

1317
function getSkipReasonAlert(
@@ -39,11 +43,22 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
3943
alertTitle,
4044
alertMessage,
4145
skippedRepositoryGroup,
46+
searchValue,
4247
}: VariantAnalysisSkippedRepositoriesTabProps) => {
48+
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]);
57+
4358
return (
4459
<Container>
4560
{getSkipReasonAlert(alertTitle, alertMessage, skippedRepositoryGroup)}
46-
{skippedRepositoryGroup.repositories.map((repo) =>
61+
{repositories.map((repo) =>
4762
<RepoRow key={`repo/${repo.fullName}`} repository={repo} />
4863
)}
4964
</Container>

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,15 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
110110
}));
111111
expect(screen.getByText('This is an empty block.')).toBeInTheDocument();
112112
});
113+
114+
it('uses the search value', () => {
115+
render({
116+
searchValue: 'world-2',
117+
});
118+
119+
expect(screen.queryByText('octodemo/hello-world-1')).not.toBeInTheDocument();
120+
expect(screen.getByText('octodemo/hello-world-2')).toBeInTheDocument();
121+
expect(screen.queryByText('octodemo/hello-world-3')).not.toBeInTheDocument();
122+
expect(screen.queryByText('octodemo/hello-world-4')).not.toBeInTheDocument();
123+
});
113124
});

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,30 @@ describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
9797
expect(screen.getByText('octodemo/hello-galaxy')).toBeInTheDocument();
9898
expect(screen.getByText('octodemo/hello-universe')).toBeInTheDocument();
9999
});
100+
101+
it('uses the search value', async () => {
102+
render({
103+
alertTitle: 'No database',
104+
alertMessage: 'The following repositories could not be scanned because they do not have an available CodeQL database.',
105+
skippedRepositoryGroup: {
106+
repositoryCount: 1,
107+
repositories: [
108+
{
109+
fullName: 'octodemo/hello-world',
110+
},
111+
{
112+
fullName: 'octodemo/hello-galaxy',
113+
},
114+
{
115+
fullName: 'octodemo/hello-universe',
116+
},
117+
],
118+
},
119+
searchValue: 'world',
120+
});
121+
122+
expect(screen.getByText('octodemo/hello-world')).toBeInTheDocument();
123+
expect(screen.queryByText('octodemo/hello-galaxy')).not.toBeInTheDocument();
124+
expect(screen.queryByText('octodemo/hello-universe')).not.toBeInTheDocument();
125+
});
100126
});

0 commit comments

Comments
 (0)