= (args) => (
+
+);
+
+export const OnlyFullName = Template.bind({});
+OnlyFullName.args = {
+ repository: {
+ fullName: 'octodemo/hello-globe',
+ }
+};
+
+export const Public = Template.bind({});
+Public.args = {
+ repository: {
+ fullName: 'octodemo/hello-globe',
+ private: false,
+ }
+};
+
+export const Private = Template.bind({});
+Private.args = {
+ repository: {
+ fullName: 'octodemo/hello-globe',
+ private: true,
+ }
+};
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx
index e1f4856230a..711adb3260f 100644
--- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx
+++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx
@@ -106,7 +106,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
],
skippedRepos: {
notFoundRepos: {
- repositoryCount: 2,
+ repositoryCount: 9999,
repositories: [
{
fullName: 'octodemo/hello-globe'
@@ -121,19 +121,23 @@ const variantAnalysis: VariantAnalysisDomainModel = {
repositories: [
{
id: 100,
- fullName: 'octodemo/no-db-1'
+ fullName: 'octodemo/no-db-1',
+ private: false,
},
{
id: 101,
- fullName: 'octodemo/no-db-2'
+ fullName: 'octodemo/no-db-2',
+ private: true,
},
{
id: 102,
- fullName: 'octodemo/no-db-3'
+ fullName: 'octodemo/no-db-3',
+ private: true,
},
{
id: 103,
- fullName: 'octodemo/no-db-4'
+ fullName: 'octodemo/no-db-4',
+ private: false,
}
]
},
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNoCodeqlDbRepos.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNoCodeqlDbRepos.tsx
deleted file mode 100644
index b21ee09588b..00000000000
--- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNoCodeqlDbRepos.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as React from 'react';
-
-export const VariantAnalysisNoCodeqlDbRepos = () => {
- return This is the no database found view
;
-};
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNotFoundRepos.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNotFoundRepos.tsx
deleted file mode 100644
index a35d7a09e6a..00000000000
--- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisNotFoundRepos.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import * as React from 'react';
-
-export const VariantAnalysisNotFoundRepos = () => {
- return This is the no access view
;
-};
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx
index 2a780929d9c..2ccd440e74c 100644
--- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx
+++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx
@@ -4,9 +4,8 @@ import { VSCodeBadge, VSCodePanels, VSCodePanelTab, VSCodePanelView } from '@vsc
import { formatDecimal } from '../../pure/number';
import { VariantAnalysis, VariantAnalysisScannedRepositoryResult } from '../../remote-queries/shared/variant-analysis';
import { VariantAnalysisAnalyzedRepos } from './VariantAnalysisAnalyzedRepos';
-import { VariantAnalysisNotFoundRepos } from './VariantAnalysisNotFoundRepos';
-import { VariantAnalysisNoCodeqlDbRepos } from './VariantAnalysisNoCodeqlDbRepos';
import { Alert } from '../common';
+import { VariantAnalysisSkippedRepositoriesTab } from './VariantAnalysisSkippedRepositoriesTab';
export type VariantAnalysisOutcomePanelProps = {
variantAnalysis: VariantAnalysis;
@@ -37,8 +36,8 @@ export const VariantAnalysisOutcomePanels = ({
variantAnalysis,
repositoryResults,
}: VariantAnalysisOutcomePanelProps) => {
- const noCodeqlDbRepositoryCount = variantAnalysis.skippedRepos?.noCodeqlDbRepos?.repositoryCount ?? 0;
- const notFoundRepositoryCount = variantAnalysis.skippedRepos?.notFoundRepos?.repositoryCount ?? 0;
+ const noCodeqlDbRepos = variantAnalysis.skippedRepos?.noCodeqlDbRepos;
+ const notFoundRepos = variantAnalysis.skippedRepos?.notFoundRepos;
const overLimitRepositoryCount = variantAnalysis.skippedRepos?.overLimitRepos?.repositoryCount ?? 0;
const accessMismatchRepositoryCount = variantAnalysis.skippedRepos?.accessMismatchRepos?.repositoryCount ?? 0;
@@ -61,7 +60,7 @@ export const VariantAnalysisOutcomePanels = ({
);
- if (noCodeqlDbRepositoryCount === 0 && notFoundRepositoryCount === 0) {
+ if (!noCodeqlDbRepos?.repositoryCount && !notFoundRepos?.repositoryCount) {
return (
<>
{warnings}
@@ -78,21 +77,33 @@ export const VariantAnalysisOutcomePanels = ({
Analyzed
{formatDecimal(variantAnalysis.scannedRepos?.length ?? 0)}
- {notFoundRepositoryCount > 0 && (
+ {notFoundRepos?.repositoryCount && (
No access
- {formatDecimal(notFoundRepositoryCount)}
+ {formatDecimal(notFoundRepos.repositoryCount)}
)}
- {noCodeqlDbRepositoryCount > 0 && (
+ {noCodeqlDbRepos?.repositoryCount && (
No database
- {formatDecimal(noCodeqlDbRepositoryCount)}
+ {formatDecimal(noCodeqlDbRepos.repositoryCount)}
)}
- {notFoundRepositoryCount > 0 && }
- {noCodeqlDbRepositoryCount > 0 && }
+ {notFoundRepos?.repositoryCount &&
+
+
+ }
+ {noCodeqlDbRepos?.repositoryCount &&
+
+
+ }
>
);
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoriesTab.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoriesTab.tsx
new file mode 100644
index 00000000000..381cdbaef93
--- /dev/null
+++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoriesTab.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import styled from 'styled-components';
+import { VariantAnalysisSkippedRepositoryGroup } from '../../remote-queries/shared/variant-analysis';
+import { Alert } from '../common';
+import { VariantAnalysisSkippedRepositoryRow } from './VariantAnalysisSkippedRepositoryRow';
+
+export type VariantAnalysisSkippedRepositoriesTabProps = {
+ alertTitle: string,
+ alertMessage: string,
+ skippedRepositoryGroup: VariantAnalysisSkippedRepositoryGroup,
+};
+
+function getSkipReasonAlert(
+ title: string,
+ message: string,
+ repos: VariantAnalysisSkippedRepositoryGroup
+) {
+ const repositoriesOmittedText = repos.repositoryCount > repos.repositories.length
+ ? ` (Only the first ${repos.repositories.length > 1 ? `${repos.repositories.length} repositories are` : 'repository is'} shown.)`
+ : '';
+ return (
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ width: 100%;
+`;
+
+export const VariantAnalysisSkippedRepositoriesTab = ({
+ alertTitle,
+ alertMessage,
+ skippedRepositoryGroup,
+}: VariantAnalysisSkippedRepositoriesTabProps) => {
+ return (
+
+ {getSkipReasonAlert(alertTitle, alertMessage, skippedRepositoryGroup)}
+ {skippedRepositoryGroup.repositories.map((repo) =>
+
+ )}
+
+ );
+};
diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoryRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoryRow.tsx
new file mode 100644
index 00000000000..deb8d9d06df
--- /dev/null
+++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisSkippedRepositoryRow.tsx
@@ -0,0 +1,48 @@
+import { VSCodeBadge, VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
+import * as React from 'react';
+import styled from 'styled-components';
+import { Codicon, WarningIcon } from '../common';
+import { VariantAnalysisSkippedRepository as SkippedRepo } from '../../remote-queries/shared/variant-analysis';
+
+export type VariantAnalysisSkippedRepositoryRowProps = {
+ repository: SkippedRepo,
+};
+
+const Row = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+ align-items: center;
+`;
+
+const ChevronIcon = styled(Codicon)`
+ color: var(--vscode-disabledForeground);
+`;
+
+const PrivacyText = styled.span`
+ font-size: small;
+ color: var(--vscode-descriptionForeground);
+`;
+
+function getPrivacyElement(isPrivate: boolean | undefined) {
+ if (isPrivate === undefined) {
+ return undefined;
+ }
+ const text = isPrivate ? 'private' : 'public';
+ return {text};
+}
+
+export const VariantAnalysisSkippedRepositoryRow = ({
+ repository,
+}: VariantAnalysisSkippedRepositoryRowProps) => {
+ return (
+
+
+
+ -
+ {repository.fullName}
+ {getPrivacyElement(repository.private)}
+
+
+ );
+};
diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoriesTab.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoriesTab.spec.tsx
new file mode 100644
index 00000000000..38558db51ef
--- /dev/null
+++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoriesTab.spec.tsx
@@ -0,0 +1,100 @@
+import * as React from 'react';
+import { render as reactRender, screen } from '@testing-library/react';
+import { VariantAnalysisSkippedRepositoriesTab, VariantAnalysisSkippedRepositoriesTabProps } from '../VariantAnalysisSkippedRepositoriesTab';
+
+describe(VariantAnalysisSkippedRepositoriesTab.name, () => {
+ const render = (props: VariantAnalysisSkippedRepositoriesTabProps) =>
+ reactRender();
+
+ it('renders warning title', async () => {
+ render({
+ alertTitle: 'No access',
+ alertMessage: 'The following repositories could not be scanned because you do not have read access.',
+ skippedRepositoryGroup: {
+ repositoryCount: 1,
+ repositories: [],
+ }
+ });
+
+ expect(screen.getByText('Warning: No access')).toBeInTheDocument();
+ });
+
+ it('renders warning message when no repositories are omitted', async () => {
+ render({
+ alertTitle: 'No access',
+ alertMessage: 'The following repositories could not be scanned because you do not have read access.',
+ skippedRepositoryGroup: {
+ repositoryCount: 1,
+ repositories: [
+ {
+ fullName: 'octodemo/hello-world',
+ },
+ ],
+ }
+ });
+
+ expect(screen.getByText('The following repositories could not be scanned because you do not have read access.')).toBeInTheDocument();
+ });
+
+ it('renders warning message when there are repositories omitted and only one shown', async () => {
+ render({
+ alertTitle: 'No access',
+ alertMessage: 'The following repositories could not be scanned because you do not have read access.',
+ skippedRepositoryGroup: {
+ repositoryCount: 44,
+ repositories: [
+ {
+ fullName: 'octodemo/hello-world',
+ },
+ ],
+ }
+ });
+
+ expect(screen.getByText('The following repositories could not be scanned because you do not have read access. (Only the first repository is shown.)')).toBeInTheDocument();
+ });
+
+ it('renders warning message when there are repositories omitted and multiple shown', async () => {
+ render({
+ alertTitle: 'No access',
+ alertMessage: 'The following repositories could not be scanned because you do not have read access.',
+ skippedRepositoryGroup: {
+ repositoryCount: 44,
+ repositories: [
+ {
+ fullName: 'octodemo/hello-world',
+ },
+ {
+ fullName: 'octodemo/hello-galaxy',
+ },
+ ],
+ }
+ });
+
+ expect(screen.getByText('The following repositories could not be scanned because you do not have read access. (Only the first 2 repositories are shown.)')).toBeInTheDocument();
+ });
+
+ it('renders multiple skipped repository rows', 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',
+ },
+ ],
+ }
+ });
+
+ expect(screen.getByText('octodemo/hello-world')).toBeInTheDocument();
+ expect(screen.getByText('octodemo/hello-galaxy')).toBeInTheDocument();
+ expect(screen.getByText('octodemo/hello-universe')).toBeInTheDocument();
+ });
+});
diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoryRow.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoryRow.spec.tsx
new file mode 100644
index 00000000000..4e108fac884
--- /dev/null
+++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisSkippedRepositoryRow.spec.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import { render as reactRender, screen } from '@testing-library/react';
+import { VariantAnalysisSkippedRepositoryRow, VariantAnalysisSkippedRepositoryRowProps } from '../VariantAnalysisSkippedRepositoryRow';
+
+describe(VariantAnalysisSkippedRepositoryRow.name, () => {
+ const render = (props: VariantAnalysisSkippedRepositoryRowProps) =>
+ reactRender();
+
+ it('shows repository name', async () => {
+ render({
+ repository: {
+ fullName: 'octodemo/hello-world',
+ }
+ });
+
+ expect(screen.getByText('octodemo/hello-world')).toBeInTheDocument();
+ });
+
+ it('shows visibility when public', async () => {
+ render({
+ repository: {
+ fullName: 'octodemo/hello-world',
+ private: false,
+ }
+ });
+
+ expect(screen.getByText('public')).toBeInTheDocument();
+ expect(screen.queryByText('private')).not.toBeInTheDocument();
+ });
+
+ it('shows visibility when private', async () => {
+ render({
+ repository: {
+ fullName: 'octodemo/hello-world',
+ private: true,
+ }
+ });
+
+ expect(screen.queryByText('public')).not.toBeInTheDocument();
+ expect(screen.getByText('private')).toBeInTheDocument();
+ });
+
+ it('does not show visibility when unknown', async () => {
+ render({
+ repository: {
+ fullName: 'octodemo/hello-world',
+ }
+ });
+
+ expect(screen.queryByText('public')).not.toBeInTheDocument();
+ expect(screen.queryByText('private')).not.toBeInTheDocument();
+ });
+});