Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions extensions/ql-vscode/src/remote-queries/export-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import * as path from "path";
import * as fs from "fs-extra";

import {
window,
commands,
Uri,
ExtensionContext,
workspace,
Uri,
ViewColumn,
window,
workspace,
} from "vscode";
import { Credentials } from "../authentication";
import { UserCancellationException } from "../commandRunner";
Expand All @@ -29,6 +29,7 @@ import { assertNever } from "../pure/helpers-pure";
import {
VariantAnalysis,
VariantAnalysisScannedRepository,
VariantAnalysisScannedRepositoryDownloadStatus,
VariantAnalysisScannedRepositoryResult,
} from "./shared/variant-analysis";
import {
Expand Down Expand Up @@ -156,6 +157,10 @@ export async function exportVariantAnalysisResults(
);
}

const repoStates = await variantAnalysisManager.getRepoStates(
variantAnalysisId,
);

void extLogger.log(
`Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`,
);
Expand All @@ -181,6 +186,18 @@ export async function exportVariantAnalysisResults(
}

for (const repo of repositories) {
const repoState = repoStates.find(
(r) => r.repositoryId === repo.repository.id,
);

// Do not export if it has not yet completed or the download has not yet succeeded.
if (
repoState?.downloadStatus !==
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
) {
continue;
}

if (repo.resultCount == 0) {
yield [
repo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const variantAnalysis: VariantAnalysisDomainModel = {
private: false,
},
analysisStatus: VariantAnalysisRepoStatus.Succeeded,
resultCount: 100,
},
{
repository: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,24 @@ InProgress.args = {
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
};

export const InProgressWithResults = Template.bind({});
InProgressWithResults.args = {
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
showResultActions: true,
};

export const InProgressWithoutDownloadedRepos = Template.bind({});
InProgressWithoutDownloadedRepos.args = {
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
showResultActions: true,
exportResultsDisabled: true,
};

export const Succeeded = Template.bind({});
Succeeded.args = {
...InProgress.args,
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
showResultActions: true,
};

export const Failed = Template.bind({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ export function VariantAnalysis({
<>
<VariantAnalysisHeader
variantAnalysis={variantAnalysis}
repositoryStates={repoStates}
filterSortState={filterSortState}
selectedRepositoryIds={selectedRepositoryIds}
onOpenQueryFileClick={openQueryFile}
onViewQueryTextClick={openQueryText}
onStopQueryClick={stopQuery}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import styled from "styled-components";
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
import { VariantAnalysisStatus } from "../../remote-queries/shared/variant-analysis";

type Props = {
export type VariantAnalysisActionsProps = {
variantAnalysisStatus: VariantAnalysisStatus;

onStopQueryClick: () => void;
stopQueryDisabled?: boolean;

showResultActions?: boolean;
onCopyRepositoryListClick: () => void;
onExportResultsClick: () => void;
copyRepositoryListDisabled?: boolean;
exportResultsDisabled?: boolean;
};

const Container = styled.div`
Expand All @@ -26,12 +29,33 @@ const Button = styled(VSCodeButton)`
export const VariantAnalysisActions = ({
variantAnalysisStatus,
onStopQueryClick,
stopQueryDisabled,
showResultActions,
onCopyRepositoryListClick,
onExportResultsClick,
stopQueryDisabled,
}: Props) => {
copyRepositoryListDisabled,
exportResultsDisabled,
}: VariantAnalysisActionsProps) => {
return (
<Container>
{showResultActions && (
<>
<Button
appearance="secondary"
onClick={onCopyRepositoryListClick}
disabled={copyRepositoryListDisabled}
>
Copy repository list
</Button>
<Button
appearance="primary"
onClick={onExportResultsClick}
disabled={exportResultsDisabled}
>
Export results
</Button>
</>
)}
{variantAnalysisStatus === VariantAnalysisStatus.InProgress && (
<Button
appearance="secondary"
Expand All @@ -41,16 +65,6 @@ export const VariantAnalysisActions = ({
Stop query
</Button>
)}
{variantAnalysisStatus === VariantAnalysisStatus.Succeeded && (
<>
<Button appearance="secondary" onClick={onCopyRepositoryListClick}>
Copy repository list
</Button>
<Button appearance="primary" onClick={onExportResultsClick}>
Export results
</Button>
</>
)}
</Container>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ import {
getTotalResultCount,
hasRepoScanCompleted,
VariantAnalysis,
VariantAnalysisScannedRepositoryDownloadStatus,
VariantAnalysisScannedRepositoryState,
} from "../../remote-queries/shared/variant-analysis";
import { QueryDetails } from "./QueryDetails";
import { VariantAnalysisActions } from "./VariantAnalysisActions";
import { VariantAnalysisStats } from "./VariantAnalysisStats";
import { parseDate } from "../../pure/date";
import { basename } from "../common/path";
import {
defaultFilterSortState,
filterAndSortRepositoriesWithResults,
RepositoriesFilterSortState,
} from "../../pure/variant-analysis-filter-sort";

export type VariantAnalysisHeaderProps = {
variantAnalysis: VariantAnalysis;
repositoryStates?: VariantAnalysisScannedRepositoryState[];
filterSortState?: RepositoriesFilterSortState;
selectedRepositoryIds?: number[];

onOpenQueryFileClick: () => void;
onViewQueryTextClick: () => void;
Expand All @@ -40,6 +50,9 @@ const Row = styled.div`

export const VariantAnalysisHeader = ({
variantAnalysis,
repositoryStates,
filterSortState,
selectedRepositoryIds,
onOpenQueryFileClick,
onViewQueryTextClick,
onStopQueryClick,
Expand All @@ -62,6 +75,36 @@ export const VariantAnalysisHeader = ({
const hasSkippedRepos = useMemo(() => {
return getSkippedRepoCount(variantAnalysis.skippedRepos) > 0;
}, [variantAnalysis.skippedRepos]);
const filteredRepositories = useMemo(() => {
return filterAndSortRepositoriesWithResults(variantAnalysis.scannedRepos, {
...defaultFilterSortState,
...filterSortState,
repositoryIds: selectedRepositoryIds,
});
}, [filterSortState, selectedRepositoryIds, variantAnalysis.scannedRepos]);
const hasDownloadedRepos = useMemo(() => {
const repositoryStatesById = new Map<
number,
VariantAnalysisScannedRepositoryState
>();
if (repositoryStates) {
for (const repositoryState of repositoryStates) {
repositoryStatesById.set(repositoryState.repositoryId, repositoryState);
}
}

return filteredRepositories?.some((repo) => {
return (
repositoryStatesById.get(repo.repository.id)?.downloadStatus ===
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
);
});
}, [repositoryStates, filteredRepositories]);
const hasReposWithResults = useMemo(() => {
return filteredRepositories?.some(
(repo) => repo.resultCount && repo.resultCount > 0,
);
}, [filteredRepositories]);

return (
<Container>
Expand All @@ -74,10 +117,13 @@ export const VariantAnalysisHeader = ({
/>
<VariantAnalysisActions
variantAnalysisStatus={variantAnalysis.status}
showResultActions={(resultCount ?? 0) > 0}
onStopQueryClick={onStopQueryClick}
onCopyRepositoryListClick={onCopyRepositoryListClick}
onExportResultsClick={onExportResultsClick}
stopQueryDisabled={!variantAnalysis.actionsWorkflowRunId}
exportResultsDisabled={!hasDownloadedRepos}
copyRepositoryListDisabled={!hasReposWithResults}
/>
</Row>
<VariantAnalysisStats
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import * as React from "react";
import { render as reactRender, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { VariantAnalysisStatus } from "../../../remote-queries/shared/variant-analysis";
import { VariantAnalysisActions } from "../VariantAnalysisActions";
import {
VariantAnalysisActions,
VariantAnalysisActionsProps,
} from "../VariantAnalysisActions";

describe(VariantAnalysisActions.name, () => {
const onStopQueryClick = jest.fn();
Expand All @@ -15,51 +18,78 @@ describe(VariantAnalysisActions.name, () => {
onExportResultsClick.mockReset();
});

const render = (variantAnalysisStatus: VariantAnalysisStatus) =>
const render = (
props: Pick<VariantAnalysisActionsProps, "variantAnalysisStatus"> &
Partial<VariantAnalysisActionsProps>,
) =>
reactRender(
<VariantAnalysisActions
variantAnalysisStatus={variantAnalysisStatus}
onStopQueryClick={onStopQueryClick}
onCopyRepositoryListClick={onCopyRepositoryListClick}
onExportResultsClick={onExportResultsClick}
{...props}
/>,
);

it("renders 1 button when in progress", async () => {
const { container } = render(VariantAnalysisStatus.InProgress);
const { container } = render({
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
});

expect(container.querySelectorAll("vscode-button").length).toEqual(1);
});

it("renders the stop query button when in progress", async () => {
render(VariantAnalysisStatus.InProgress);
render({
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
});

await userEvent.click(screen.getByText("Stop query"));
expect(onStopQueryClick).toHaveBeenCalledTimes(1);
});

it("renders 3 buttons when in progress with results", async () => {
const { container } = render({
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
showResultActions: true,
});

expect(container.querySelectorAll("vscode-button").length).toEqual(3);
});

it("renders 2 buttons when succeeded", async () => {
const { container } = render(VariantAnalysisStatus.Succeeded);
const { container } = render({
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
showResultActions: true,
});

expect(container.querySelectorAll("vscode-button").length).toEqual(2);
});

it("renders the copy repository list button when succeeded", async () => {
render(VariantAnalysisStatus.Succeeded);
render({
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
showResultActions: true,
});

await userEvent.click(screen.getByText("Copy repository list"));
expect(onCopyRepositoryListClick).toHaveBeenCalledTimes(1);
});

it("renders the export results button when succeeded", async () => {
render(VariantAnalysisStatus.Succeeded);
render({
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
showResultActions: true,
});

await userEvent.click(screen.getByText("Export results"));
expect(onExportResultsClick).toHaveBeenCalledTimes(1);
});

it("does not render any buttons when failed", () => {
const { container } = render(VariantAnalysisStatus.Failed);
const { container } = render({
variantAnalysisStatus: VariantAnalysisStatus.Failed,
});

expect(container.querySelectorAll("vscode-button").length).toEqual(0);
});
Expand Down