Add repository filter by full name
This adds a new textbox to the outcome panels that allows filtering by the repository full name (e.g. `github/vscode-codeql`). The filtering uses the same logic as the existing remote queries filter, i.e. by converting the input and the repository full name to lower case and checking the the latter includes the former.
This commit is contained in:
@@ -5,7 +5,7 @@ import { ComponentMeta } from '@storybook/react';
|
|||||||
import RepositoriesSearchComponent from '../../view/remote-queries/RepositoriesSearch';
|
import RepositoriesSearchComponent from '../../view/remote-queries/RepositoriesSearch';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Repositories Search',
|
title: 'MRVA/Repositories Search',
|
||||||
component: RepositoriesSearchComponent,
|
component: RepositoriesSearchComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
filterValue: {
|
filterValue: {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { ComponentMeta } from '@storybook/react';
|
||||||
|
|
||||||
|
import { RepositoriesSearch as RepositoriesSearchComponent } from '../../view/variant-analysis/RepositoriesSearch';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Variant Analysis/Repositories Search',
|
||||||
|
component: RepositoriesSearchComponent,
|
||||||
|
argTypes: {
|
||||||
|
value: {
|
||||||
|
control: {
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} as ComponentMeta<typeof RepositoriesSearchComponent>;
|
||||||
|
|
||||||
|
export const RepositoriesSearch = () => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RepositoriesSearchComponent value={value} onChange={setValue} />
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||||
|
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
|
import { VariantAnalysisContainer } from '../../view/variant-analysis/VariantAnalysisContainer';
|
||||||
import { VariantAnalysisAnalyzedRepos } from '../../view/variant-analysis/VariantAnalysisAnalyzedRepos';
|
import { VariantAnalysisAnalyzedRepos } from '../../view/variant-analysis/VariantAnalysisAnalyzedRepos';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +13,7 @@ import {
|
|||||||
import { AnalysisAlert } from '../../remote-queries/shared/analysis-result';
|
import { AnalysisAlert } from '../../remote-queries/shared/analysis-result';
|
||||||
import { createMockVariantAnalysis } from '../../vscode-tests/factories/remote-queries/shared/variant-analysis';
|
import { createMockVariantAnalysis } from '../../vscode-tests/factories/remote-queries/shared/variant-analysis';
|
||||||
import { createMockRepositoryWithMetadata } from '../../vscode-tests/factories/remote-queries/shared/repository';
|
import { createMockRepositoryWithMetadata } from '../../vscode-tests/factories/remote-queries/shared/repository';
|
||||||
|
import { createMockScannedRepo } from '../../vscode-tests/factories/remote-queries/shared/scanned-repositories';
|
||||||
|
|
||||||
import analysesResults from '../remote-queries/data/analysesResultsMessage.json';
|
import analysesResults from '../remote-queries/data/analysesResultsMessage.json';
|
||||||
|
|
||||||
@@ -111,5 +114,40 @@ Example.args = {
|
|||||||
interpretedResults: interpretedResultsForRepo('expressjs/express'),
|
interpretedResults: interpretedResultsForRepo('expressjs/express'),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
;
|
|
||||||
|
faker.seed(42);
|
||||||
|
const uniqueStore = {};
|
||||||
|
|
||||||
|
const manyScannedRepos = Array.from({ length: 1000 }, (_, i) => {
|
||||||
|
const mockedScannedRepo = createMockScannedRepo();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mockedScannedRepo,
|
||||||
|
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
|
||||||
|
resultCount: faker.datatype.number({ min: 0, max: 1000 }),
|
||||||
|
repository: {
|
||||||
|
...mockedScannedRepo.repository,
|
||||||
|
// We need to ensure the ID is unique for React keys
|
||||||
|
id: faker.helpers.unique(faker.datatype.number, [], {
|
||||||
|
store: uniqueStore,
|
||||||
|
}),
|
||||||
|
fullName: `octodemo/${faker.helpers.unique(faker.random.word, [], {
|
||||||
|
store: uniqueStore,
|
||||||
|
})}`,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PerformanceExample = Template.bind({});
|
||||||
|
PerformanceExample.args = {
|
||||||
|
variantAnalysis: {
|
||||||
|
...createMockVariantAnalysis(VariantAnalysisStatus.Succeeded, manyScannedRepos),
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
repositoryResults: manyScannedRepos.map(repoTask => ({
|
||||||
|
variantAnalysisId: 1,
|
||||||
|
repositoryId: repoTask.repository.id,
|
||||||
|
interpretedResults: interpretedResultsForRepo('facebook/create-react-app'),
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type Props = {
|
|||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
slot?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CodiconIcon = styled.span`
|
const CodiconIcon = styled.span`
|
||||||
@@ -15,5 +16,6 @@ const CodiconIcon = styled.span`
|
|||||||
export const Codicon = ({
|
export const Codicon = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
className
|
className,
|
||||||
}: Props) => <CodiconIcon role="img" aria-label={label} className={classNames('codicon', `codicon-${name}`, className)} />;
|
slot,
|
||||||
|
}: Props) => <CodiconIcon role="img" aria-label={label} className={classNames('codicon', `codicon-${name}`, className)} slot={slot} />;
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react';
|
||||||
|
import { Codicon } from '../common';
|
||||||
|
|
||||||
|
const TextField = styled(VSCodeTextField)`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RepositoriesSearch = ({ value, onChange }: Props) => {
|
||||||
|
const handleInput = useCallback((e: InputEvent) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
|
onChange(target.value);
|
||||||
|
}, [onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
placeholder='Filter by repository owner/name'
|
||||||
|
value={value}
|
||||||
|
onInput={handleInput}
|
||||||
|
>
|
||||||
|
<Codicon name="search" label="Search..." slot="start" />
|
||||||
|
</TextField>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { RepoRow } from './RepoRow';
|
import { RepoRow } from './RepoRow';
|
||||||
import {
|
import {
|
||||||
@@ -6,7 +7,7 @@ import {
|
|||||||
VariantAnalysisScannedRepositoryResult,
|
VariantAnalysisScannedRepositoryResult,
|
||||||
VariantAnalysisScannedRepositoryState
|
VariantAnalysisScannedRepositoryState
|
||||||
} from '../../remote-queries/shared/variant-analysis';
|
} from '../../remote-queries/shared/variant-analysis';
|
||||||
import { useMemo } from 'react';
|
import { matchesSearchValue } from './filterSort';
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -19,12 +20,15 @@ export type VariantAnalysisAnalyzedReposProps = {
|
|||||||
variantAnalysis: VariantAnalysis;
|
variantAnalysis: VariantAnalysis;
|
||||||
repositoryStates?: VariantAnalysisScannedRepositoryState[];
|
repositoryStates?: VariantAnalysisScannedRepositoryState[];
|
||||||
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
|
repositoryResults?: VariantAnalysisScannedRepositoryResult[];
|
||||||
|
|
||||||
|
searchValue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VariantAnalysisAnalyzedRepos = ({
|
export const VariantAnalysisAnalyzedRepos = ({
|
||||||
variantAnalysis,
|
variantAnalysis,
|
||||||
repositoryStates,
|
repositoryStates,
|
||||||
repositoryResults,
|
repositoryResults,
|
||||||
|
searchValue,
|
||||||
}: VariantAnalysisAnalyzedReposProps) => {
|
}: VariantAnalysisAnalyzedReposProps) => {
|
||||||
const repositoryStateById = useMemo(() => {
|
const repositoryStateById = useMemo(() => {
|
||||||
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
|
const map = new Map<number, VariantAnalysisScannedRepositoryState>();
|
||||||
@@ -42,9 +46,19 @@ export const VariantAnalysisAnalyzedRepos = ({
|
|||||||
return map;
|
return map;
|
||||||
}, [repositoryResults]);
|
}, [repositoryResults]);
|
||||||
|
|
||||||
|
const repositories = useMemo(() => {
|
||||||
|
if (searchValue) {
|
||||||
|
return variantAnalysis.scannedRepos?.filter((repoTask) => {
|
||||||
|
return matchesSearchValue(repoTask.repository, searchValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantAnalysis.scannedRepos;
|
||||||
|
}, [searchValue, variantAnalysis.scannedRepos]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{variantAnalysis.scannedRepos?.map(repository => {
|
{repositories?.map(repository => {
|
||||||
const state = repositoryStateById.get(repository.repository.id);
|
const state = repositoryStateById.get(repository.repository.id);
|
||||||
const results = repositoryResultsById.get(repository.repository.id);
|
const results = repositoryResultsById.get(repository.repository.id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vscode/webview-ui-toolkit/react';
|
import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vscode/webview-ui-toolkit/react';
|
||||||
import { formatDecimal } from '../../pure/number';
|
import { formatDecimal } from '../../pure/number';
|
||||||
@@ -10,6 +11,7 @@ import {
|
|||||||
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
|
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
|
||||||
import { Alert } from '../common';
|
import { Alert } from '../common';
|
||||||
import { VariantAnalysisSkippedRepositoriesTab } from './VariantAnalysisSkippedRepositoriesTab';
|
import { VariantAnalysisSkippedRepositoriesTab } from './VariantAnalysisSkippedRepositoriesTab';
|
||||||
|
import { RepositoriesSearch } from './RepositoriesSearch';
|
||||||
|
|
||||||
export type VariantAnalysisOutcomePanelProps = {
|
export type VariantAnalysisOutcomePanelProps = {
|
||||||
variantAnalysis: VariantAnalysis;
|
variantAnalysis: VariantAnalysis;
|
||||||
@@ -42,6 +44,8 @@ export const VariantAnalysisOutcomePanels = ({
|
|||||||
repositoryStates,
|
repositoryStates,
|
||||||
repositoryResults,
|
repositoryResults,
|
||||||
}: VariantAnalysisOutcomePanelProps) => {
|
}: VariantAnalysisOutcomePanelProps) => {
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
const noCodeqlDbRepos = variantAnalysis.skippedRepos?.noCodeqlDbRepos;
|
const noCodeqlDbRepos = variantAnalysis.skippedRepos?.noCodeqlDbRepos;
|
||||||
const notFoundRepos = variantAnalysis.skippedRepos?.notFoundRepos;
|
const notFoundRepos = variantAnalysis.skippedRepos?.notFoundRepos;
|
||||||
const overLimitRepositoryCount = variantAnalysis.skippedRepos?.overLimitRepos?.repositoryCount ?? 0;
|
const overLimitRepositoryCount = variantAnalysis.skippedRepos?.overLimitRepos?.repositoryCount ?? 0;
|
||||||
@@ -70,10 +74,12 @@ export const VariantAnalysisOutcomePanels = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{warnings}
|
{warnings}
|
||||||
|
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
|
||||||
<VariantAnalysisAnalyzedRepos
|
<VariantAnalysisAnalyzedRepos
|
||||||
variantAnalysis={variantAnalysis}
|
variantAnalysis={variantAnalysis}
|
||||||
repositoryStates={repositoryStates}
|
repositoryStates={repositoryStates}
|
||||||
repositoryResults={repositoryResults}
|
repositoryResults={repositoryResults}
|
||||||
|
searchValue={searchValue}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -82,6 +88,7 @@ export const VariantAnalysisOutcomePanels = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{warnings}
|
{warnings}
|
||||||
|
<RepositoriesSearch value={searchValue} onChange={setSearchValue} />
|
||||||
<VSCodePanels>
|
<VSCodePanels>
|
||||||
<Tab>
|
<Tab>
|
||||||
Analyzed
|
Analyzed
|
||||||
@@ -104,6 +111,7 @@ export const VariantAnalysisOutcomePanels = ({
|
|||||||
variantAnalysis={variantAnalysis}
|
variantAnalysis={variantAnalysis}
|
||||||
repositoryStates={repositoryStates}
|
repositoryStates={repositoryStates}
|
||||||
repositoryResults={repositoryResults}
|
repositoryResults={repositoryResults}
|
||||||
|
searchValue={searchValue}
|
||||||
/>
|
/>
|
||||||
</VSCodePanelView>
|
</VSCodePanelView>
|
||||||
{notFoundRepos?.repositoryCount &&
|
{notFoundRepos?.repositoryCount &&
|
||||||
@@ -111,14 +119,18 @@ export const VariantAnalysisOutcomePanels = ({
|
|||||||
<VariantAnalysisSkippedRepositoriesTab
|
<VariantAnalysisSkippedRepositoriesTab
|
||||||
alertTitle='No access'
|
alertTitle='No access'
|
||||||
alertMessage='The following repositories could not be scanned because you do not have read access.'
|
alertMessage='The following repositories could not be scanned because you do not have read access.'
|
||||||
skippedRepositoryGroup={notFoundRepos} />
|
skippedRepositoryGroup={notFoundRepos}
|
||||||
|
searchValue={searchValue}
|
||||||
|
/>
|
||||||
</VSCodePanelView>}
|
</VSCodePanelView>}
|
||||||
{noCodeqlDbRepos?.repositoryCount &&
|
{noCodeqlDbRepos?.repositoryCount &&
|
||||||
<VSCodePanelView>
|
<VSCodePanelView>
|
||||||
<VariantAnalysisSkippedRepositoriesTab
|
<VariantAnalysisSkippedRepositoriesTab
|
||||||
alertTitle='No database'
|
alertTitle='No database'
|
||||||
alertMessage='The following repositories could not be scanned because they do not have an available CodeQL database.'
|
alertMessage='The following repositories could not be scanned because they do not have an available CodeQL database.'
|
||||||
skippedRepositoryGroup={noCodeqlDbRepos} />
|
skippedRepositoryGroup={noCodeqlDbRepos}
|
||||||
|
searchValue={searchValue}
|
||||||
|
/>
|
||||||
</VSCodePanelView>}
|
</VSCodePanelView>}
|
||||||
</VSCodePanels>
|
</VSCodePanels>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { VariantAnalysisSkippedRepositoryGroup } from '../../remote-queries/shared/variant-analysis';
|
import { VariantAnalysisSkippedRepositoryGroup } from '../../remote-queries/shared/variant-analysis';
|
||||||
import { Alert } from '../common';
|
import { Alert } from '../common';
|
||||||
import { RepoRow } from './RepoRow';
|
import { RepoRow } from './RepoRow';
|
||||||
|
import { matchesSearchValue } from './filterSort';
|
||||||
|
|
||||||
export type VariantAnalysisSkippedRepositoriesTabProps = {
|
export type VariantAnalysisSkippedRepositoriesTabProps = {
|
||||||
alertTitle: string,
|
alertTitle: string,
|
||||||
alertMessage: string,
|
alertMessage: string,
|
||||||
skippedRepositoryGroup: VariantAnalysisSkippedRepositoryGroup,
|
skippedRepositoryGroup: VariantAnalysisSkippedRepositoryGroup,
|
||||||
|
|
||||||
|
searchValue?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getSkipReasonAlert(
|
function getSkipReasonAlert(
|
||||||
@@ -39,11 +43,22 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
|
|||||||
alertTitle,
|
alertTitle,
|
||||||
alertMessage,
|
alertMessage,
|
||||||
skippedRepositoryGroup,
|
skippedRepositoryGroup,
|
||||||
|
searchValue,
|
||||||
}: VariantAnalysisSkippedRepositoriesTabProps) => {
|
}: VariantAnalysisSkippedRepositoriesTabProps) => {
|
||||||
|
const repositories = useMemo(() => {
|
||||||
|
if (searchValue) {
|
||||||
|
return skippedRepositoryGroup.repositories?.filter((repo) => {
|
||||||
|
return matchesSearchValue(repo, searchValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return skippedRepositoryGroup.repositories;
|
||||||
|
}, [searchValue, skippedRepositoryGroup.repositories]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{getSkipReasonAlert(alertTitle, alertMessage, skippedRepositoryGroup)}
|
{getSkipReasonAlert(alertTitle, alertMessage, skippedRepositoryGroup)}
|
||||||
{skippedRepositoryGroup.repositories.map((repo) =>
|
{repositories.map((repo) =>
|
||||||
<RepoRow key={`repo/${repo.fullName}`} repository={repo} />
|
<RepoRow key={`repo/${repo.fullName}`} repository={repo} />
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -107,4 +107,15 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
|
|||||||
}));
|
}));
|
||||||
expect(screen.getByText('This is an empty block.')).toBeInTheDocument();
|
expect(screen.getByText('This is an empty block.')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the search value', () => {
|
||||||
|
render({
|
||||||
|
searchValue: 'world-2',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByText('octodemo/hello-world-1')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText('octodemo/hello-world-2')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('octodemo/hello-world-3')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('octodemo/hello-world-4')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,4 +97,30 @@ describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
|
|||||||
expect(screen.getByText('octodemo/hello-galaxy')).toBeInTheDocument();
|
expect(screen.getByText('octodemo/hello-galaxy')).toBeInTheDocument();
|
||||||
expect(screen.getByText('octodemo/hello-universe')).toBeInTheDocument();
|
expect(screen.getByText('octodemo/hello-universe')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses the search value', async () => {
|
||||||
|
render({
|
||||||
|
alertTitle: 'No database',
|
||||||
|
alertMessage: 'The following repositories could not be scanned because they do not have an available CodeQL database.',
|
||||||
|
skippedRepositoryGroup: {
|
||||||
|
repositoryCount: 1,
|
||||||
|
repositories: [
|
||||||
|
{
|
||||||
|
fullName: 'octodemo/hello-world',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: 'octodemo/hello-galaxy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullName: 'octodemo/hello-universe',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
searchValue: 'world',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('octodemo/hello-world')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('octodemo/hello-galaxy')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('octodemo/hello-universe')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { matchesSearchValue } from '../filterSort';
|
||||||
|
|
||||||
|
describe(matchesSearchValue.name, () => {
|
||||||
|
const repository = {
|
||||||
|
fullName: 'github/codeql'
|
||||||
|
};
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{ searchValue: undefined, matches: true },
|
||||||
|
{ searchValue: '', matches: true },
|
||||||
|
{ searchValue: 'github/codeql', matches: true },
|
||||||
|
{ searchValue: 'github', matches: true },
|
||||||
|
{ searchValue: 'git', matches: true },
|
||||||
|
{ searchValue: 'codeql', matches: true },
|
||||||
|
{ searchValue: 'code', matches: true },
|
||||||
|
{ searchValue: 'ql', matches: true },
|
||||||
|
{ searchValue: '/', matches: true },
|
||||||
|
{ searchValue: 'gothub/codeql', matches: false },
|
||||||
|
{ searchValue: 'hello', matches: false },
|
||||||
|
{ searchValue: 'cod*ql', matches: false },
|
||||||
|
{ searchValue: 'cod?ql', matches: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(testCases)('returns $matches if searching for $searchValue', ({ searchValue, matches }) => {
|
||||||
|
expect(matchesSearchValue(repository, searchValue)).toBe(matches);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Repository } from '../../remote-queries/shared/repository';
|
||||||
|
|
||||||
|
export function matchesSearchValue(repo: Pick<Repository, 'fullName'>, searchValue: string | undefined): boolean {
|
||||||
|
if (!searchValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.fullName.toLowerCase().includes(searchValue.toLowerCase());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user