Merge pull request #2073 from github/koesie10/remove-remote-queries-react
Remove remote queries React components
This commit is contained in:
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import TextButtonComponent from "../../view/remote-queries/TextButton";
|
||||
import TextButtonComponent from "../../view/common/TextButton";
|
||||
|
||||
export default {
|
||||
title: "Text Button",
|
||||
@@ -1,27 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import DownloadButtonComponent from "../../view/remote-queries/DownloadButton";
|
||||
|
||||
export default {
|
||||
title: "Download Button",
|
||||
component: DownloadButtonComponent,
|
||||
argTypes: {
|
||||
onClick: {
|
||||
action: "clicked",
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof DownloadButtonComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof DownloadButtonComponent> = (args) => (
|
||||
<DownloadButtonComponent {...args} />
|
||||
);
|
||||
|
||||
export const DownloadButton = Template.bind({});
|
||||
DownloadButton.args = {
|
||||
text: "Download",
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import DownloadSpinnerComponent from "../../view/remote-queries/DownloadSpinner";
|
||||
|
||||
export default {
|
||||
title: "Download Spinner",
|
||||
component: DownloadSpinnerComponent,
|
||||
} as ComponentMeta<typeof DownloadSpinnerComponent>;
|
||||
|
||||
export const DownloadSpinner = <DownloadSpinnerComponent />;
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import LastUpdatedComponent from "../../view/remote-queries/LastUpdated";
|
||||
|
||||
export default {
|
||||
title: "MRVA/Last Updated",
|
||||
component: LastUpdatedComponent,
|
||||
} as ComponentMeta<typeof LastUpdatedComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof LastUpdatedComponent> = (args) => (
|
||||
<LastUpdatedComponent {...args} />
|
||||
);
|
||||
|
||||
export const LastUpdated = Template.bind({});
|
||||
|
||||
LastUpdated.args = {
|
||||
lastUpdated: -3_600_000, // 1 hour ago
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RemoteQueries } from "../../view/remote-queries/RemoteQueries";
|
||||
|
||||
import * as remoteQueryResult from "./data/remoteQueryResultMessage.json";
|
||||
import * as analysesResults from "./data/analysesResultsMessage.json";
|
||||
|
||||
export default {
|
||||
title: "MRVA/Remote Queries",
|
||||
component: RemoteQueries,
|
||||
} as ComponentMeta<typeof RemoteQueries>;
|
||||
|
||||
const Template: ComponentStory<typeof RemoteQueries> = () => {
|
||||
useEffect(() => {
|
||||
window.postMessage(remoteQueryResult);
|
||||
window.postMessage(analysesResults);
|
||||
});
|
||||
|
||||
return <RemoteQueries />;
|
||||
};
|
||||
|
||||
export const Top10JavaScript = Template.bind({});
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import RepositoriesSearchComponent from "../../view/remote-queries/RepositoriesSearch";
|
||||
|
||||
export default {
|
||||
title: "MRVA/Repositories Search",
|
||||
component: RepositoriesSearchComponent,
|
||||
argTypes: {
|
||||
filterValue: {
|
||||
control: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSearchComponent>;
|
||||
|
||||
export const RepositoriesSearch = () => {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
return (
|
||||
<RepositoriesSearchComponent
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,11 @@ import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
|
||||
import AnalysisAlertResult from "../../view/remote-queries/AnalysisAlertResult";
|
||||
import AnalysisAlertResult from "../../view/variant-analysis/AnalysisAlertResult";
|
||||
import type { AnalysisAlert } from "../../remote-queries/shared/analysis-result";
|
||||
|
||||
export default {
|
||||
title: "Analysis Alert Result",
|
||||
title: "Variant Analysis/Analysis Alert Result",
|
||||
component: AnalysisAlertResult,
|
||||
} as ComponentMeta<typeof AnalysisAlertResult>;
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from "../../remote-queries/shared/analysis-result";
|
||||
import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository";
|
||||
|
||||
import * as analysesResults from "../remote-queries/data/analysesResultsMessage.json";
|
||||
import * as rawResults from "../remote-queries/data/rawResults.json";
|
||||
import * as analysesResults from "../data/analysesResultsMessage.json";
|
||||
import * as rawResults from "../data/rawResults.json";
|
||||
import { RepoRow, RepoRowProps } from "../../view/variant-analysis/RepoRow";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { createMockVariantAnalysis } from "../../../test/factories/remote-querie
|
||||
import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository";
|
||||
import { createMockScannedRepo } from "../../../test/factories/remote-queries/shared/scanned-repositories";
|
||||
|
||||
import * as analysesResults from "../remote-queries/data/analysesResultsMessage.json";
|
||||
import * as analysesResults from "../data/analysesResultsMessage.json";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Analyzed Repos",
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@primer/octicons-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const Container = styled.div`
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.span`
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
color: var(--vscode-editor-foreground);
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0.1em;
|
||||
`;
|
||||
|
||||
const CollapsibleItem = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Container onClick={() => setExpanded(!isExpanded)}>
|
||||
<Button>
|
||||
{isExpanded ? (
|
||||
<ChevronDownIcon size={16} />
|
||||
) : (
|
||||
<ChevronRightIcon size={16} />
|
||||
)}
|
||||
</Button>
|
||||
<TitleContainer>{title}</TitleContainer>
|
||||
</Container>
|
||||
{isExpanded && children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapsibleItem;
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { DownloadIcon } from "@primer/octicons-react";
|
||||
|
||||
const ButtonLink = styled.a`
|
||||
display: inline-block;
|
||||
font-size: x-small;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
|
||||
svg {
|
||||
fill: var(--vscode-textLink-foreground);
|
||||
}
|
||||
`;
|
||||
|
||||
const DownloadButton = ({
|
||||
text,
|
||||
onClick,
|
||||
}: {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}) => (
|
||||
<ButtonLink onClick={onClick}>
|
||||
<DownloadIcon size={16} />
|
||||
{text}
|
||||
</ButtonLink>
|
||||
);
|
||||
|
||||
export default DownloadButton;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const SpinnerContainer = styled.span`
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const DownloadSpinner = () => (
|
||||
<SpinnerContainer>
|
||||
<VSCodeProgressRing style={{ height: "0.8em", width: "0.8em" }} />
|
||||
</SpinnerContainer>
|
||||
);
|
||||
|
||||
export default DownloadSpinner;
|
||||
@@ -1,53 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import styled from "styled-components";
|
||||
import { XCircleIcon } from "@primer/octicons-react";
|
||||
|
||||
const Container = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
background-color: var(--vscode-editor-background);
|
||||
z-index: 5000;
|
||||
padding-top: 1em;
|
||||
`;
|
||||
|
||||
const CloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
background-color: var(--vscode-editor-background);
|
||||
border: none;
|
||||
`;
|
||||
|
||||
const FullScreenModal = ({
|
||||
setOpen,
|
||||
containerElementId,
|
||||
children,
|
||||
}: {
|
||||
setOpen: (open: boolean) => void;
|
||||
containerElementId: string;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const containerElement = document.getElementById(containerElementId);
|
||||
if (!containerElement) {
|
||||
throw Error(`Could not find container element. Id: ${containerElementId}`);
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<Container>
|
||||
<CloseButton onClick={() => setOpen(false)}>
|
||||
<XCircleIcon size={24} />
|
||||
</CloseButton>
|
||||
{children}
|
||||
</Container>
|
||||
</>,
|
||||
containerElement,
|
||||
);
|
||||
};
|
||||
|
||||
export default FullScreenModal;
|
||||
@@ -1,35 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { RepoPushIcon } from "@primer/octicons-react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { humanizeRelativeTime } from "../../pure/time";
|
||||
|
||||
const IconContainer = styled.span`
|
||||
flex-grow: 0;
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
`;
|
||||
|
||||
const Duration = styled.span`
|
||||
text-align: left;
|
||||
width: 8em;
|
||||
margin-left: 0.5em;
|
||||
`;
|
||||
|
||||
type Props = { lastUpdated?: number };
|
||||
|
||||
const LastUpdated = ({ lastUpdated }: Props) =>
|
||||
// lastUpdated will be undefined for older results that were
|
||||
// created before the lastUpdated field was added.
|
||||
Number.isFinite(lastUpdated) ? (
|
||||
<>
|
||||
<IconContainer>
|
||||
<RepoPushIcon size={16} />
|
||||
</IconContainer>
|
||||
<Duration>{humanizeRelativeTime(lastUpdated)}</Duration>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
export default LastUpdated;
|
||||
@@ -1,551 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Flash, ThemeProvider } from "@primer/react";
|
||||
import { ToRemoteQueriesMessage } from "../../pure/interface-types";
|
||||
import {
|
||||
AnalysisSummary,
|
||||
RemoteQueryResult,
|
||||
} from "../../remote-queries/shared/remote-query-result";
|
||||
import { MAX_RAW_RESULTS } from "../../remote-queries/shared/result-limits";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react";
|
||||
import {
|
||||
HorizontalSpace,
|
||||
SectionTitle,
|
||||
VerticalSpace,
|
||||
ViewTitle,
|
||||
} from "../common";
|
||||
import DownloadButton from "./DownloadButton";
|
||||
import {
|
||||
AnalysisResults,
|
||||
getAnalysisResultCount,
|
||||
} from "../../remote-queries/shared/analysis-result";
|
||||
import DownloadSpinner from "./DownloadSpinner";
|
||||
import CollapsibleItem from "./CollapsibleItem";
|
||||
import {
|
||||
AlertIcon,
|
||||
CodeSquareIcon,
|
||||
FileCodeIcon,
|
||||
RepoIcon,
|
||||
TerminalIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import AnalysisAlertResult from "./AnalysisAlertResult";
|
||||
import RawResultsTable from "./RawResultsTable";
|
||||
import RepositoriesSearch from "./RepositoriesSearch";
|
||||
import StarCount from "../common/StarCount";
|
||||
import SortRepoFilter, { Sort, sorter } from "./SortRepoFilter";
|
||||
import LastUpdated from "./LastUpdated";
|
||||
import RepoListCopyButton from "./RepoListCopyButton";
|
||||
|
||||
import "./baseStyles.css";
|
||||
import "./remoteQueries.css";
|
||||
|
||||
const numOfReposInContractedMode = 10;
|
||||
|
||||
const emptyQueryResult: RemoteQueryResult = {
|
||||
queryId: "",
|
||||
queryTitle: "",
|
||||
queryFileName: "",
|
||||
queryFilePath: "",
|
||||
queryText: "",
|
||||
language: "",
|
||||
workflowRunUrl: "",
|
||||
totalRepositoryCount: 0,
|
||||
affectedRepositoryCount: 0,
|
||||
totalResultCount: 0,
|
||||
executionTimestamp: "",
|
||||
executionDuration: "",
|
||||
analysisSummaries: [],
|
||||
analysisFailures: [],
|
||||
};
|
||||
|
||||
const downloadAnalysisResults = (analysisSummary: AnalysisSummary) => {
|
||||
vscode.postMessage({
|
||||
t: "remoteQueryDownloadAnalysisResults",
|
||||
analysisSummary,
|
||||
});
|
||||
};
|
||||
|
||||
const downloadAllAnalysesResults = (query: RemoteQueryResult) => {
|
||||
vscode.postMessage({
|
||||
t: "remoteQueryDownloadAllAnalysesResults",
|
||||
analysisSummaries: query.analysisSummaries,
|
||||
});
|
||||
};
|
||||
|
||||
const openQueryFile = (queryResult: RemoteQueryResult) => {
|
||||
vscode.postMessage({
|
||||
t: "openFile",
|
||||
filePath: queryResult.queryFilePath,
|
||||
});
|
||||
};
|
||||
|
||||
const openQueryTextVirtualFile = (queryResult: RemoteQueryResult) => {
|
||||
vscode.postMessage({
|
||||
t: "openVirtualFile",
|
||||
queryText: queryResult.queryText,
|
||||
});
|
||||
};
|
||||
|
||||
function createResultsDescription(queryResult: RemoteQueryResult) {
|
||||
const reposCount = `${queryResult.totalRepositoryCount} ${
|
||||
queryResult.totalRepositoryCount === 1 ? "repository" : "repositories"
|
||||
}`;
|
||||
return `${queryResult.totalResultCount} results from running against ${reposCount} (${queryResult.executionDuration}), ${queryResult.executionTimestamp}`;
|
||||
}
|
||||
|
||||
const sumAnalysesResults = (analysesResults: AnalysisResults[]) =>
|
||||
analysesResults.reduce((acc, curr) => acc + getAnalysisResultCount(curr), 0);
|
||||
|
||||
const QueryInfo = (queryResult: RemoteQueryResult) => (
|
||||
<>
|
||||
<VerticalSpace size={1} />
|
||||
{createResultsDescription(queryResult)}
|
||||
<VerticalSpace size={1} />
|
||||
<span>
|
||||
<a
|
||||
className="vscode-codeql__query-info-link"
|
||||
href="#"
|
||||
onClick={() => openQueryFile(queryResult)}
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
<FileCodeIcon size={16} />{" "}
|
||||
</span>
|
||||
{queryResult.queryFileName}
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<a
|
||||
className="vscode-codeql__query-info-link"
|
||||
href="#"
|
||||
onClick={() => openQueryTextVirtualFile(queryResult)}
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
<CodeSquareIcon size={16} />{" "}
|
||||
</span>
|
||||
Query
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<a
|
||||
className="vscode-codeql__query-info-link"
|
||||
href={queryResult.workflowRunUrl}
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
<TerminalIcon size={16} />{" "}
|
||||
</span>
|
||||
Logs
|
||||
</a>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
const Failures = (queryResult: RemoteQueryResult) => {
|
||||
if (queryResult.analysisFailures.length === 0) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<VerticalSpace size={3} />
|
||||
<Flash variant="danger">
|
||||
{queryResult.analysisFailures.map((f, i) => (
|
||||
<div key={i}>
|
||||
<p className="vscode-codeql__analysis-failure">
|
||||
<AlertIcon size={16} />
|
||||
<b>{f.nwo}: </b>
|
||||
{f.error}
|
||||
</p>
|
||||
{i === queryResult.analysisFailures.length - 1 ? (
|
||||
<></>
|
||||
) : (
|
||||
<VerticalSpace size={1} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Flash>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SummaryTitleWithResults = ({
|
||||
queryResult,
|
||||
analysesResults,
|
||||
sort,
|
||||
setSort,
|
||||
}: {
|
||||
queryResult: RemoteQueryResult;
|
||||
analysesResults: AnalysisResults[];
|
||||
sort: Sort;
|
||||
setSort: (sort: Sort) => void;
|
||||
}) => {
|
||||
const showDownloadButton =
|
||||
queryResult.totalResultCount !== sumAnalysesResults(analysesResults);
|
||||
|
||||
return (
|
||||
<div className="vscode-codeql__query-summary-container">
|
||||
<SectionTitle>
|
||||
Repositories with results ({queryResult.affectedRepositoryCount}):
|
||||
</SectionTitle>
|
||||
{showDownloadButton && (
|
||||
<DownloadButton
|
||||
text="Download all"
|
||||
onClick={() => downloadAllAnalysesResults(queryResult)}
|
||||
/>
|
||||
)}
|
||||
<div style={{ flexGrow: 2, textAlign: "right" }}>
|
||||
<RepoListCopyButton queryResult={queryResult} />
|
||||
<HorizontalSpace size={1} />
|
||||
<SortRepoFilter sort={sort} setSort={setSort} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SummaryTitleNoResults = () => (
|
||||
<div className="vscode-codeql__query-summary-container">
|
||||
<SectionTitle>No results found</SectionTitle>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SummaryItemDownload = ({
|
||||
analysisSummary,
|
||||
analysisResults,
|
||||
}: {
|
||||
analysisSummary: AnalysisSummary;
|
||||
analysisResults: AnalysisResults | undefined;
|
||||
}) => {
|
||||
if (!analysisResults || analysisResults.status === "Failed") {
|
||||
return (
|
||||
<DownloadButton
|
||||
text={analysisSummary.fileSize}
|
||||
onClick={() => downloadAnalysisResults(analysisSummary)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (analysisResults.status === "InProgress") {
|
||||
return (
|
||||
<>
|
||||
<HorizontalSpace size={2} />
|
||||
<DownloadSpinner />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const SummaryItem = ({
|
||||
analysisSummary,
|
||||
analysisResults,
|
||||
}: {
|
||||
analysisSummary: AnalysisSummary;
|
||||
analysisResults: AnalysisResults | undefined;
|
||||
}) => (
|
||||
<>
|
||||
<span className="vscode-codeql__analysis-item">
|
||||
<RepoIcon size={16} />
|
||||
</span>
|
||||
<span className="vscode-codeql__analysis-item">{analysisSummary.nwo}</span>
|
||||
<HorizontalSpace size={1} />
|
||||
<span className="vscode-codeql__analysis-item">
|
||||
<VSCodeBadge>{analysisSummary.resultCount.toString()}</VSCodeBadge>
|
||||
</span>
|
||||
<span className="vscode-codeql__analysis-item">
|
||||
<SummaryItemDownload
|
||||
analysisSummary={analysisSummary}
|
||||
analysisResults={analysisResults}
|
||||
/>
|
||||
</span>
|
||||
<StarCount starCount={analysisSummary.starCount} />
|
||||
<LastUpdated lastUpdated={analysisSummary.lastUpdated} />
|
||||
</>
|
||||
);
|
||||
|
||||
const Summary = ({
|
||||
queryResult,
|
||||
analysesResults,
|
||||
sort,
|
||||
setSort,
|
||||
}: {
|
||||
queryResult: RemoteQueryResult;
|
||||
analysesResults: AnalysisResults[];
|
||||
sort: Sort;
|
||||
setSort: (sort: Sort) => void;
|
||||
}) => {
|
||||
const [repoListExpanded, setRepoListExpanded] = useState(false);
|
||||
const numOfReposToShow = repoListExpanded
|
||||
? queryResult.analysisSummaries.length
|
||||
: numOfReposInContractedMode;
|
||||
|
||||
return (
|
||||
<>
|
||||
{queryResult.affectedRepositoryCount === 0 ? (
|
||||
<SummaryTitleNoResults />
|
||||
) : (
|
||||
<SummaryTitleWithResults
|
||||
queryResult={queryResult}
|
||||
analysesResults={analysesResults}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ul className="vscode-codeql__flat-list">
|
||||
{queryResult.analysisSummaries
|
||||
.slice(0, numOfReposToShow)
|
||||
.sort(sorter(sort))
|
||||
.map((summary, i) => (
|
||||
<li
|
||||
key={summary.nwo}
|
||||
className="vscode-codeql__analysis-summaries-list-item"
|
||||
>
|
||||
<SummaryItem
|
||||
analysisSummary={summary}
|
||||
analysisResults={analysesResults.find(
|
||||
(a) => a.nwo === summary.nwo,
|
||||
)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{queryResult.analysisSummaries.length > numOfReposInContractedMode && (
|
||||
<button
|
||||
className="vscode-codeql__expand-button"
|
||||
onClick={() => setRepoListExpanded(!repoListExpanded)}
|
||||
>
|
||||
{repoListExpanded ? <span>View less</span> : <span>View all</span>}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AnalysesResultsTitle = ({
|
||||
totalAnalysesResults,
|
||||
totalResults,
|
||||
}: {
|
||||
totalAnalysesResults: number;
|
||||
totalResults: number;
|
||||
}) => {
|
||||
if (totalAnalysesResults === totalResults) {
|
||||
return <SectionTitle>{totalAnalysesResults} results</SectionTitle>;
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionTitle>
|
||||
{totalAnalysesResults}/{totalResults} results
|
||||
</SectionTitle>
|
||||
);
|
||||
};
|
||||
|
||||
const exportResults = (queryResult: RemoteQueryResult) => {
|
||||
vscode.postMessage({
|
||||
t: "remoteQueryExportResults",
|
||||
queryId: queryResult.queryId,
|
||||
});
|
||||
};
|
||||
|
||||
const AnalysesResultsDescription = ({
|
||||
queryResult,
|
||||
analysesResults,
|
||||
}: {
|
||||
queryResult: RemoteQueryResult;
|
||||
analysesResults: AnalysisResults[];
|
||||
}) => {
|
||||
const showDownloadsMessage = queryResult.analysisSummaries.some(
|
||||
(s) =>
|
||||
!analysesResults.some((a) => a.nwo === s.nwo && a.status === "Completed"),
|
||||
);
|
||||
const downloadsMessage = (
|
||||
<>
|
||||
<VerticalSpace size={1} />
|
||||
Some results haven't been downloaded automatically because of their
|
||||
size or because enough were downloaded already. Download them manually
|
||||
from the list above if you want to see them here.
|
||||
</>
|
||||
);
|
||||
|
||||
const showMaxResultsMessage = analysesResults.some(
|
||||
(a) => a.rawResults?.capped,
|
||||
);
|
||||
const maxRawResultsMessage = (
|
||||
<>
|
||||
<VerticalSpace size={1} />
|
||||
Some repositories have more than {MAX_RAW_RESULTS} results. We will only
|
||||
show you up to
|
||||
{MAX_RAW_RESULTS} results for each repository.
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showDownloadsMessage && downloadsMessage}
|
||||
{showMaxResultsMessage && maxRawResultsMessage}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const RepoAnalysisResults = (analysisResults: AnalysisResults) => {
|
||||
const numOfResults = getAnalysisResultCount(analysisResults);
|
||||
const title = (
|
||||
<>
|
||||
{analysisResults.nwo}
|
||||
<HorizontalSpace size={1} />
|
||||
<VSCodeBadge>{numOfResults.toString()}</VSCodeBadge>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<CollapsibleItem title={title}>
|
||||
<ul className="vscode-codeql__flat-list">
|
||||
{analysisResults.interpretedResults.map((r, i) => (
|
||||
<li key={i}>
|
||||
<AnalysisAlertResult alert={r} />
|
||||
<VerticalSpace size={2} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{analysisResults.rawResults && (
|
||||
<RawResultsTable
|
||||
schema={analysisResults.rawResults.schema}
|
||||
results={analysisResults.rawResults.resultSet}
|
||||
fileLinkPrefix={analysisResults.rawResults.fileLinkPrefix}
|
||||
sourceLocationPrefix={analysisResults.rawResults.sourceLocationPrefix}
|
||||
/>
|
||||
)}
|
||||
</CollapsibleItem>
|
||||
);
|
||||
};
|
||||
|
||||
const AnalysesResults = ({
|
||||
queryResult,
|
||||
analysesResults,
|
||||
totalResults,
|
||||
sort,
|
||||
}: {
|
||||
queryResult: RemoteQueryResult;
|
||||
analysesResults: AnalysisResults[];
|
||||
totalResults: number;
|
||||
sort: Sort;
|
||||
}) => {
|
||||
const totalAnalysesResults = sumAnalysesResults(analysesResults);
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
if (totalResults === 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<VerticalSpace size={2} />
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<AnalysesResultsTitle
|
||||
totalAnalysesResults={totalAnalysesResults}
|
||||
totalResults={totalResults}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<VSCodeButton onClick={() => exportResults(queryResult)}>
|
||||
Export all
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
<AnalysesResultsDescription
|
||||
queryResult={queryResult}
|
||||
analysesResults={analysesResults}
|
||||
/>
|
||||
|
||||
<VerticalSpace size={2} />
|
||||
<RepositoriesSearch
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
/>
|
||||
|
||||
<ul className="vscode-codeql__flat-list">
|
||||
{analysesResults
|
||||
.filter(
|
||||
(a) =>
|
||||
a.interpretedResults.length ||
|
||||
a.rawResults?.resultSet?.rows?.length,
|
||||
)
|
||||
.filter((a) =>
|
||||
a.nwo.toLowerCase().includes(filterValue.toLowerCase()),
|
||||
)
|
||||
.sort(sorter(sort))
|
||||
.map((r) => (
|
||||
<li
|
||||
key={r.nwo}
|
||||
className="vscode-codeql__analyses-results-list-item"
|
||||
>
|
||||
<RepoAnalysisResults {...r} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function RemoteQueries(): JSX.Element {
|
||||
const [queryResult, setQueryResult] =
|
||||
useState<RemoteQueryResult>(emptyQueryResult);
|
||||
const [analysesResults, setAnalysesResults] = useState<AnalysisResults[]>([]);
|
||||
const [sort, setSort] = useState<Sort>("name");
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToRemoteQueriesMessage = evt.data;
|
||||
if (msg.t === "setRemoteQueryResult") {
|
||||
setQueryResult(msg.queryResult);
|
||||
} else if (msg.t === "setAnalysesResults") {
|
||||
setAnalysesResults(msg.analysesResults);
|
||||
}
|
||||
} else {
|
||||
// sanitize origin
|
||||
const origin = evt.origin.replace(/\n|\r/g, "");
|
||||
console.error(`Invalid event origin ${origin}`);
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", listener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!queryResult) {
|
||||
return <div>Waiting for results to load.</div>;
|
||||
}
|
||||
|
||||
try {
|
||||
return (
|
||||
<div className="vscode-codeql__remote-queries">
|
||||
<ThemeProvider colorMode="auto">
|
||||
<ViewTitle>{queryResult.queryTitle}</ViewTitle>
|
||||
<QueryInfo {...queryResult} />
|
||||
<Failures {...queryResult} />
|
||||
<Summary
|
||||
queryResult={queryResult}
|
||||
analysesResults={analysesResults}
|
||||
sort={sort}
|
||||
setSort={setSort}
|
||||
/>
|
||||
<AnalysesResults
|
||||
queryResult={queryResult}
|
||||
analysesResults={analysesResults}
|
||||
totalResults={queryResult.totalResultCount}
|
||||
sort={sort}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return <div>There was an error displaying the view.</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { RemoteQueryResult } from "../../remote-queries/shared/remote-query-result";
|
||||
import { CopyIcon } from "@primer/octicons-react";
|
||||
import { IconButton } from "@primer/react";
|
||||
|
||||
const copyRepositoryList = (queryResult: RemoteQueryResult) => {
|
||||
vscode.postMessage({
|
||||
t: "copyRepoList",
|
||||
queryId: queryResult.queryId,
|
||||
});
|
||||
};
|
||||
|
||||
const RepoListCopyButton = ({
|
||||
queryResult,
|
||||
}: {
|
||||
queryResult: RemoteQueryResult;
|
||||
}) => (
|
||||
<IconButton
|
||||
aria-label="Copy repository list"
|
||||
icon={CopyIcon}
|
||||
variant="invisible"
|
||||
size="small"
|
||||
sx={{ "text-align": "right" }}
|
||||
onClick={() => copyRepositoryList(queryResult)}
|
||||
/>
|
||||
);
|
||||
|
||||
export default RepoListCopyButton;
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react";
|
||||
|
||||
interface RepositoriesSearchProps {
|
||||
filterValue: string;
|
||||
setFilterValue: (value: string) => void;
|
||||
}
|
||||
|
||||
const RepositoriesSearch = ({
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
}: RepositoriesSearchProps) => {
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Filter by repository owner/name"
|
||||
ariaLabel="Repository search"
|
||||
name="repository-search"
|
||||
value={filterValue}
|
||||
onInput={(e: InputEvent) =>
|
||||
setFilterValue((e.target as HTMLInputElement).value)
|
||||
}
|
||||
>
|
||||
<span slot="start" className="codicon codicon-search"></span>
|
||||
</VSCodeTextField>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RepositoriesSearch;
|
||||
@@ -1,94 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { FilterIcon } from "@primer/octicons-react";
|
||||
import { ActionList, ActionMenu, IconButton } from "@primer/react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const SortWrapper = styled.span`
|
||||
flex-grow: 2;
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
`;
|
||||
|
||||
export type Sort = "name" | "stars" | "results" | "lastUpdated";
|
||||
type Props = {
|
||||
sort: Sort;
|
||||
setSort: (sort: Sort) => void;
|
||||
};
|
||||
|
||||
type Sortable = {
|
||||
nwo: string;
|
||||
starCount?: number;
|
||||
resultCount?: number;
|
||||
lastUpdated?: number;
|
||||
};
|
||||
|
||||
const sortBy = [
|
||||
{ name: "Sort by Name", sort: "name" },
|
||||
{ name: "Sort by Results", sort: "results" },
|
||||
{ name: "Sort by Stars", sort: "stars" },
|
||||
{ name: "Sort by Last Updated", sort: "lastUpdated" },
|
||||
];
|
||||
|
||||
export function sorter(
|
||||
sort: Sort,
|
||||
): (left: Sortable, right: Sortable) => number {
|
||||
// stars and results are highest to lowest
|
||||
// name is alphabetical
|
||||
return (left: Sortable, right: Sortable) => {
|
||||
if (sort === "stars") {
|
||||
const stars = (right.starCount || 0) - (left.starCount || 0);
|
||||
if (stars !== 0) {
|
||||
return stars;
|
||||
}
|
||||
}
|
||||
if (sort === "lastUpdated") {
|
||||
const lastUpdated = (right.lastUpdated || 0) - (left.lastUpdated || 0);
|
||||
if (lastUpdated !== 0) {
|
||||
return lastUpdated;
|
||||
}
|
||||
}
|
||||
if (sort === "results") {
|
||||
const results = (right.resultCount || 0) - (left.resultCount || 0);
|
||||
if (results !== 0) {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back on name compare if results, stars, or lastUpdated are equal
|
||||
return left.nwo.localeCompare(right.nwo, undefined, {
|
||||
sensitivity: "base",
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const SortRepoFilter = ({ sort, setSort }: Props) => {
|
||||
return (
|
||||
<SortWrapper>
|
||||
<ActionMenu>
|
||||
<ActionMenu.Anchor>
|
||||
<IconButton
|
||||
icon={FilterIcon}
|
||||
variant="invisible"
|
||||
aria-label="Sort results"
|
||||
/>
|
||||
</ActionMenu.Anchor>
|
||||
|
||||
<ActionMenu.Overlay width="small" anchorSide="outside-bottom">
|
||||
<ActionList selectionVariant="single">
|
||||
{sortBy.map((type, index) => (
|
||||
<ActionList.Item
|
||||
key={index}
|
||||
selected={type.sort === sort}
|
||||
onSelect={() => setSort(type.sort as Sort)}
|
||||
>
|
||||
{type.name}
|
||||
</ActionList.Item>
|
||||
))}
|
||||
</ActionList>
|
||||
</ActionMenu.Overlay>
|
||||
</ActionMenu>
|
||||
</SortWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SortRepoFilter;
|
||||
@@ -1,4 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
|
||||
sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { WebviewDefinition } from "../webview-definition";
|
||||
import { RemoteQueries } from "./RemoteQueries";
|
||||
|
||||
const definition: WebviewDefinition = {
|
||||
component: <RemoteQueries />,
|
||||
};
|
||||
|
||||
export default definition;
|
||||
@@ -1,53 +0,0 @@
|
||||
.vscode-codeql__remote-queries {
|
||||
max-width: 55em;
|
||||
}
|
||||
|
||||
.vscode-codeql__query-info-link {
|
||||
text-decoration: none;
|
||||
padding-right: 1em;
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.vscode-codeql__query-info-link:hover {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.vscode-codeql__query-summary-container {
|
||||
padding-top: 1.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vscode-codeql__analysis-summaries-list-item {
|
||||
margin-top: 0.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vscode-codeql__analyses-results-list-item {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.vscode-codeql__analysis-item {
|
||||
padding-right: 0.1em;
|
||||
}
|
||||
|
||||
.vscode-codeql__expand-button {
|
||||
background: none;
|
||||
color: var(--vscode-textLink-foreground);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding-top: 1em;
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
.vscode-codeql__analysis-failure {
|
||||
margin: 0;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.vscode-codeql__flat-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0.5em 0 0 0;
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
AnalysisAlert,
|
||||
AnalysisRawResults,
|
||||
} from "../../remote-queries/shared/analysis-result";
|
||||
import AnalysisAlertResult from "../remote-queries/AnalysisAlertResult";
|
||||
import RawResultsTable from "../remote-queries/RawResultsTable";
|
||||
import AnalysisAlertResult from "./AnalysisAlertResult";
|
||||
import RawResultsTable from "./RawResultsTable";
|
||||
import {
|
||||
VariantAnalysisRepoStatus,
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ResultSetSchema,
|
||||
} from "../../pure/bqrs-cli-types";
|
||||
import { tryGetRemoteLocation } from "../../pure/bqrs-utils";
|
||||
import TextButton from "./TextButton";
|
||||
import TextButton from "../common/TextButton";
|
||||
import { convertNonPrintableChars } from "../../text-utils";
|
||||
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";
|
||||
|
||||
Reference in New Issue
Block a user