Merge branch 'main' into starcke/general-setup

This commit is contained in:
Anders Starcke Henriksen
2023-08-24 13:45:00 +02:00
committed by GitHub
36 changed files with 366 additions and 215 deletions

View File

@@ -2,6 +2,7 @@
## [UNRELEASED]
- When running variant analyses, don't download artifacts for repositories with no results. [#2736](https://github.com/github/vscode-codeql/pull/2736)
- Group the extension settings, so that they're easier to find in the Settings UI. [#2706](https://github.com/github/vscode-codeql/pull/2706)
## 1.8.10 - 15 August 2023

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124815Z&X-Amz-Expires=3600&X-Amz-Signature=0f42201f27ad08ac1fa9caccf5005d9dac7bdcae33e88e33d62e2fc337194164&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124659Z&X-Amz-Expires=3600&X-Amz-Signature=b791ede467cd1783d31d7ee148763d0d4f9eb7abad7506ef9c25017c94aaa1df&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,
@@ -122,9 +122,7 @@
"stargazers_count": 192,
"updated_at": "2022-03-17T08:34:30Z"
},
"analysis_status": "succeeded",
"artifact_size_in_bytes": 156706,
"result_count": 8
"analysis_status": "in_progress"
},
{
"repository": {
@@ -148,9 +146,7 @@
"stargazers_count": 19033,
"updated_at": "2022-11-02T10:25:24Z"
},
"analysis_status": "succeeded",
"artifact_size_in_bytes": 710,
"result_count": 0
"analysis_status": "in_progress"
},
{
"repository": {

View File

@@ -1,11 +0,0 @@
{
"request": {
"kind": "getVariantAnalysisRepoResult",
"repositoryId": 257485422
},
"response": {
"status": 200,
"body": "file:15-getVariantAnalysisRepoResult.body.zip",
"contentType": "application/zip"
}
}

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124659Z&X-Amz-Expires=3600&X-Amz-Signature=b791ede467cd1783d31d7ee148763d0d4f9eb7abad7506ef9c25017c94aaa1df&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124705Z&X-Amz-Expires=3600&X-Amz-Signature=dae9aa87c393b62ad6f84fd280c731fcd8e4551917920bf978eac5dd6102caec&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124705Z&X-Amz-Expires=3600&X-Amz-Signature=dae9aa87c393b62ad6f84fd280c731fcd8e4551917920bf978eac5dd6102caec&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124726Z&X-Amz-Expires=3600&X-Amz-Signature=6698289315bd8d8378a17784ba0be9955e6e9161497137c4ccb29b1d2a3f3587&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,
@@ -122,7 +122,9 @@
"stargazers_count": 192,
"updated_at": "2022-03-17T08:34:30Z"
},
"analysis_status": "in_progress"
"analysis_status": "succeeded",
"artifact_size_in_bytes": 156706,
"result_count": 8
},
{
"repository": {

View File

@@ -5,7 +5,7 @@
},
"response": {
"status": 200,
"body": "file:20-getVariantAnalysisRepoResult.body.zip",
"body": "file:19-getVariantAnalysisRepoResult.body.zip",
"contentType": "application/zip"
}
}

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124726Z&X-Amz-Expires=3600&X-Amz-Signature=6698289315bd8d8378a17784ba0be9955e6e9161497137c4ccb29b1d2a3f3587&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124732Z&X-Amz-Expires=3600&X-Amz-Signature=ad791eb1a754f473a50454616e9bd04663dfed60a5c84f2c61f27ced47ee25d0&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124732Z&X-Amz-Expires=3600&X-Amz-Signature=ad791eb1a754f473a50454616e9bd04663dfed60a5c84f2c61f27ced47ee25d0&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124759Z&X-Amz-Expires=3600&X-Amz-Signature=5b0be34c426152c37e3d4c761bf576a07c9d4bb6222f32c438e02605600f0394&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124759Z&X-Amz-Expires=3600&X-Amz-Signature=5b0be34c426152c37e3d4c761bf576a07c9d4bb6222f32c438e02605600f0394&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124805Z&X-Amz-Expires=3600&X-Amz-Signature=73b43f47ee53938b91fa7d23c33e750c466fff777ea94ceb052771ee8dc25b6c&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,

View File

@@ -94,7 +94,7 @@
"site_admin": true
},
"query_language": "javascript",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124805Z&X-Amz-Expires=3600&X-Amz-Signature=73b43f47ee53938b91fa7d23c33e750c466fff777ea94ceb052771ee8dc25b6c&X-Amz-SignedHeaders=host",
"query_pack_url": "https://objects-origin.githubusercontent.com/codeql-query-console/variant_analyses/146/query_pack?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=queryconsoleprod%2F20221026%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221026T124815Z&X-Amz-Expires=3600&X-Amz-Signature=0f42201f27ad08ac1fa9caccf5005d9dac7bdcae33e88e33d62e2fc337194164&X-Amz-SignedHeaders=host",
"created_at": "2022-10-26T12:45:12Z",
"updated_at": "2022-10-26T12:45:15Z",
"actions_workflow_run_id": 3329095282,
@@ -148,7 +148,9 @@
"stargazers_count": 19033,
"updated_at": "2022-11-02T10:25:24Z"
},
"analysis_status": "in_progress"
"analysis_status": "succeeded",
"artifact_size_in_bytes": 710,
"result_count": 0
},
{
"repository": {

View File

@@ -1,11 +0,0 @@
{
"request": {
"kind": "getVariantAnalysisRepoResult",
"repositoryId": 236095576
},
"response": {
"status": 200,
"body": "file:26-getVariantAnalysisRepoResult.body.zip",
"contentType": "application/zip"
}
}

View File

@@ -11,13 +11,13 @@ import { join } from "path";
import { DisposableObject, DisposeHandler } from "../disposable-object";
import { tmpDir } from "../../tmp-dir";
import { getHtmlForWebview, WebviewMessage, WebviewView } from "./webview-html";
import { getHtmlForWebview, WebviewMessage, WebviewKind } from "./webview-html";
export type WebviewPanelConfig = {
viewId: string;
title: string;
viewColumn: ViewColumn;
view: WebviewView;
view: WebviewKind;
preserveFocus?: boolean;
iconPath?: Uri | { dark: Uri; light: Uri };
additionalOptions?: WebviewPanelOptions & WebviewOptions;

View File

@@ -2,7 +2,7 @@ import { ExtensionContext, Uri, Webview } from "vscode";
import { randomBytes } from "crypto";
import { EOL } from "os";
export type WebviewView =
export type WebviewKind =
| "results"
| "compare"
| "variant-analysis"
@@ -20,7 +20,7 @@ export interface WebviewMessage {
export function getHtmlForWebview(
ctx: ExtensionContext,
webview: Webview,
view: WebviewView,
view: WebviewKind,
{
allowInlineStyles,
allowWasmEval,

View File

@@ -55,6 +55,7 @@ function mapVariantAnalysisDtoToDto(
filePath: variantAnalysis.query.filePath,
language: mapQueryLanguageToDto(variantAnalysis.query.language),
text: variantAnalysis.query.text,
kind: variantAnalysis.query.kind,
},
databases: {
repositories: variantAnalysis.databases.repositories,

View File

@@ -55,6 +55,7 @@ function mapVariantAnalysisToDomainModel(
filePath: variantAnalysis.query.filePath,
language: mapQueryLanguageToDomainModel(variantAnalysis.query.language),
text: variantAnalysis.query.text,
kind: variantAnalysis.query.kind,
},
databases: {
repositories: variantAnalysis.databases.repositories,

View File

@@ -23,6 +23,7 @@ export interface VariantAnalysisDto {
filePath: string;
language: QueryLanguageDto;
text: string;
kind?: string;
};
databases: {
repositories?: string[];

View File

@@ -10,6 +10,7 @@ export interface VariantAnalysis {
filePath: string;
language: QueryLanguage;
text: string;
kind?: string;
};
databases: {
repositories?: string[];
@@ -138,6 +139,7 @@ export interface VariantAnalysisSubmission {
filePath: string;
language: QueryLanguage;
text: string;
kind?: string;
// Base64 encoded query pack.
pack: string;
@@ -231,7 +233,11 @@ export function isRepoScanSuccessful(
export function repoHasDownloadableArtifact(
repo: VariantAnalysisScannedRepository,
): boolean {
return repo.analysisStatus === VariantAnalysisRepoStatus.Succeeded;
return (
repo.analysisStatus === VariantAnalysisRepoStatus.Succeeded &&
repo.resultCount !== undefined &&
repo.resultCount > 0
);
}
/**

View File

@@ -245,6 +245,7 @@ export class VariantAnalysisManager
pack: base64Pack,
language: variantAnalysisLanguage,
text: queryText,
kind: queryMetadata?.kind,
},
databases: {
repositories: repoSelection.repositories,

View File

@@ -32,6 +32,7 @@ export function processVariantAnalysis(
filePath: submission.query.filePath,
language: submission.query.language,
text: submission.query.text,
kind: submission.query.kind,
},
databases: submission.databases,
executionStartTime: submission.startTime,

View File

@@ -199,34 +199,35 @@ export class VariantAnalysisResultsManager extends DisposableObject {
);
const sarifPath = join(resultsDirectory, "results.sarif");
const bqrsPath = join(resultsDirectory, "results.bqrs");
let interpretedResults: AnalysisAlert[] | undefined;
let rawResults: AnalysisRawResults | undefined;
if (await pathExists(sarifPath)) {
const interpretedResults = await this.readSarifResults(
interpretedResults = await this.readSarifResults(
sarifPath,
fileLinkPrefix,
);
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
interpretedResults,
};
}
if (await pathExists(bqrsPath)) {
const rawResults = await this.readBqrsResults(
rawResults = await this.readBqrsResults(
bqrsPath,
fileLinkPrefix,
repoTask.sourceLocationPrefix,
);
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
rawResults,
};
}
throw new Error("Missing results file");
if (!interpretedResults && !rawResults) {
throw new Error("Missing results file");
}
return {
variantAnalysisId,
repositoryId: repoTask.repository.id,
interpretedResults,
rawResults,
};
}
public async isVariantAnalysisRepoDownloaded(

View File

@@ -20,37 +20,36 @@ import { AlertTableHeader } from "./AlertTableHeader";
import { AlertTableNoResults } from "./AlertTableNoResults";
import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage";
import { AlertTableResultRow } from "./AlertTableResultRow";
import { useCallback, useEffect, useRef, useState } from "react";
type AlertTableProps = ResultTableProps & {
resultSet: InterpretedResultSet<SarifInterpretationData>;
};
interface AlertTableState {
expanded: Set<string>;
selectedItem: undefined | Keys.ResultKey;
}
export class AlertTable extends React.Component<
AlertTableProps,
AlertTableState
> {
private scroller = new ScrollIntoViewHelper();
export function AlertTable(props: AlertTableProps) {
const { databaseUri, resultSet } = props;
constructor(props: AlertTableProps) {
super(props);
this.state = { expanded: new Set<string>(), selectedItem: undefined };
this.handleNavigationEvent = this.handleNavigationEvent.bind(this);
const scroller = useRef<ScrollIntoViewHelper | undefined>(undefined);
if (scroller.current === undefined) {
scroller.current = new ScrollIntoViewHelper();
}
useEffect(() => scroller.current?.update());
const [expanded, setExpanded] = useState<Set<string>>(new Set<string>());
const [selectedItem, setSelectedItem] = useState<Keys.ResultKey | undefined>(
undefined,
);
/**
* Given a list of `keys`, toggle the first, and if we 'open' the
* first item, open all the rest as well. This mimics vscode's file
* explorer tree view behavior.
*/
toggle(e: React.MouseEvent, keys: Keys.ResultKey[]) {
const toggle = useCallback((e: React.MouseEvent, keys: Keys.ResultKey[]) => {
const keyStrings = keys.map(Keys.keyToString);
this.setState((previousState) => {
const expanded = new Set(previousState.expanded);
if (previousState.expanded.has(keyStrings[0])) {
setExpanded((previousExpanded) => {
const expanded = new Set(previousExpanded);
if (previousExpanded.has(keyStrings[0])) {
expanded.delete(keyStrings[0]);
} else {
for (const str of keyStrings) {
@@ -60,127 +59,16 @@ export class AlertTable extends React.Component<
if (expanded) {
sendTelemetry("local-results-alert-table-path-expanded");
}
return { expanded };
return expanded;
});
e.stopPropagation();
e.preventDefault();
}
}, []);
render(): JSX.Element {
const { databaseUri, resultSet } = this.props;
const { numTruncatedResults, sourceLocationPrefix } =
resultSet.interpretation;
const updateSelectionCallback = (
resultKey: Keys.PathNode | Keys.Result | undefined,
) => {
this.setState((previousState) => ({
...previousState,
selectedItem: resultKey,
}));
sendTelemetry("local-results-alert-table-path-selected");
};
if (!resultSet.interpretation.data.runs?.[0]?.results?.length) {
return <AlertTableNoResults {...this.props} />;
}
return (
<table className={className}>
<AlertTableHeader sortState={resultSet.interpretation.data.sortState} />
<tbody>
{resultSet.interpretation.data.runs[0].results.map(
(result, resultIndex) => (
<AlertTableResultRow
key={resultIndex}
result={result}
resultIndex={resultIndex}
expanded={this.state.expanded}
selectedItem={this.state.selectedItem}
databaseUri={databaseUri}
sourceLocationPrefix={sourceLocationPrefix}
updateSelectionCallback={updateSelectionCallback}
toggleExpanded={this.toggle.bind(this)}
scroller={this.scroller}
/>
),
)}
<AlertTableTruncatedMessage
numTruncatedResults={numTruncatedResults}
/>
</tbody>
</table>
);
}
private handleNavigationEvent(event: NavigateMsg) {
this.setState((prevState) => {
const key = this.getNewSelection(prevState.selectedItem, event.direction);
const data = this.props.resultSet.interpretation.data;
// Check if the selected node actually exists (bounds check) and get its location if relevant
let jumpLocation: Sarif.Location | undefined;
if (key.pathNodeIndex !== undefined) {
jumpLocation = Keys.getPathNode(data, key);
if (jumpLocation === undefined) {
return prevState; // Result does not exist
}
} else if (key.pathIndex !== undefined) {
if (Keys.getPath(data, key) === undefined) {
return prevState; // Path does not exist
}
jumpLocation = undefined; // When selecting a 'path', don't jump anywhere.
} else {
jumpLocation = Keys.getResult(data, key)?.locations?.[0];
if (jumpLocation === undefined) {
return prevState; // Path step does not exist.
}
}
if (jumpLocation !== undefined) {
const parsedLocation = parseSarifLocation(
jumpLocation,
this.props.resultSet.interpretation.sourceLocationPrefix,
);
if (!isNoLocation(parsedLocation)) {
jumpToLocation(parsedLocation, this.props.databaseUri);
}
}
const expanded = new Set(prevState.expanded);
if (event.direction === NavigationDirection.right) {
// When stepping right, expand to ensure the selected node is visible
expanded.add(Keys.keyToString({ resultIndex: key.resultIndex }));
if (key.pathIndex !== undefined) {
expanded.add(
Keys.keyToString({
resultIndex: key.resultIndex,
pathIndex: key.pathIndex,
}),
);
}
} else if (event.direction === NavigationDirection.left) {
// When stepping left, collapse immediately
expanded.delete(Keys.keyToString(key));
} else {
// When stepping up or down, collapse the previous node
if (prevState.selectedItem !== undefined) {
expanded.delete(Keys.keyToString(prevState.selectedItem));
}
}
this.scroller.scrollIntoViewOnNextUpdate();
return {
...prevState,
expanded,
selectedItem: key,
};
});
}
private getNewSelection(
const getNewSelection = (
key: Keys.ResultKey | undefined,
direction: NavigationDirection,
): Keys.ResultKey {
): Keys.ResultKey => {
if (key === undefined) {
return { resultIndex: 0 };
}
@@ -218,18 +106,113 @@ export class AlertTable extends React.Component<
return key;
}
}
};
const handleNavigationEvent = useCallback(
(event: NavigateMsg) => {
const key = getNewSelection(selectedItem, event.direction);
const data = resultSet.interpretation.data;
// Check if the selected node actually exists (bounds check) and get its location if relevant
let jumpLocation: Sarif.Location | undefined;
if (key.pathNodeIndex !== undefined) {
jumpLocation = Keys.getPathNode(data, key);
if (jumpLocation === undefined) {
return; // Result does not exist
}
} else if (key.pathIndex !== undefined) {
if (Keys.getPath(data, key) === undefined) {
return; // Path does not exist
}
jumpLocation = undefined; // When selecting a 'path', don't jump anywhere.
} else {
jumpLocation = Keys.getResult(data, key)?.locations?.[0];
if (jumpLocation === undefined) {
return; // Path step does not exist.
}
}
if (jumpLocation !== undefined) {
const parsedLocation = parseSarifLocation(
jumpLocation,
resultSet.interpretation.sourceLocationPrefix,
);
if (!isNoLocation(parsedLocation)) {
jumpToLocation(parsedLocation, databaseUri);
}
}
const newExpanded = new Set(expanded);
if (event.direction === NavigationDirection.right) {
// When stepping right, expand to ensure the selected node is visible
newExpanded.add(Keys.keyToString({ resultIndex: key.resultIndex }));
if (key.pathIndex !== undefined) {
newExpanded.add(
Keys.keyToString({
resultIndex: key.resultIndex,
pathIndex: key.pathIndex,
}),
);
}
} else if (event.direction === NavigationDirection.left) {
// When stepping left, collapse immediately
newExpanded.delete(Keys.keyToString(key));
} else {
// When stepping up or down, collapse the previous node
if (selectedItem !== undefined) {
newExpanded.delete(Keys.keyToString(selectedItem));
}
}
scroller.current?.scrollIntoViewOnNextUpdate();
setExpanded(newExpanded);
setSelectedItem(key);
},
[databaseUri, expanded, resultSet, selectedItem],
);
useEffect(() => {
onNavigation.addListener(handleNavigationEvent);
return () => {
onNavigation.removeListener(handleNavigationEvent);
};
}, [handleNavigationEvent]);
const { numTruncatedResults, sourceLocationPrefix } =
resultSet.interpretation;
const updateSelectionCallback = useCallback(
(resultKey: Keys.PathNode | Keys.Result | undefined) => {
setSelectedItem(resultKey);
sendTelemetry("local-results-alert-table-path-selected");
},
[],
);
if (!resultSet.interpretation.data.runs?.[0]?.results?.length) {
return <AlertTableNoResults {...props} />;
}
componentDidUpdate() {
this.scroller.update();
}
componentDidMount() {
this.scroller.update();
onNavigation.addListener(this.handleNavigationEvent);
}
componentWillUnmount() {
onNavigation.removeListener(this.handleNavigationEvent);
}
return (
<table className={className}>
<AlertTableHeader sortState={resultSet.interpretation.data.sortState} />
<tbody>
{resultSet.interpretation.data.runs[0].results.map(
(result, resultIndex) => (
<AlertTableResultRow
key={resultIndex}
result={result}
resultIndex={resultIndex}
expanded={expanded}
selectedItem={selectedItem}
databaseUri={databaseUri}
sourceLocationPrefix={sourceLocationPrefix}
updateSelectionCallback={updateSelectionCallback}
toggleExpanded={toggle}
scroller={scroller.current}
/>
),
)}
<AlertTableTruncatedMessage numTruncatedResults={numTruncatedResults} />
</tbody>
</table>
);
}

View File

@@ -17,7 +17,7 @@ interface Props {
updateSelectionCallback: (
resultKey: Keys.PathNode | Keys.Result | undefined,
) => void;
scroller: ScrollIntoViewHelper;
scroller?: ScrollIntoViewHelper;
}
export function AlertTablePathNodeRow(props: Props) {
@@ -51,7 +51,7 @@ export function AlertTablePathNodeRow(props: Props) {
const zebraIndex = resultIndex + stepIndex;
return (
<tr
ref={scroller.ref(isSelected)}
ref={scroller?.ref(isSelected)}
className={isSelected ? "vscode-codeql__selected-path-node" : undefined}
>
<td className="vscode-codeql__icon-cell">

View File

@@ -19,7 +19,7 @@ interface Props {
resultKey: Keys.PathNode | Keys.Result | undefined,
) => void;
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
scroller: ScrollIntoViewHelper;
scroller?: ScrollIntoViewHelper;
}
export function AlertTablePathRow(props: Props) {
@@ -50,7 +50,7 @@ export function AlertTablePathRow(props: Props) {
return (
<>
<tr
ref={scroller.ref(isPathSpecificallySelected)}
ref={scroller?.ref(isPathSpecificallySelected)}
{...selectableZebraStripe(isPathSpecificallySelected, resultIndex)}
>
<td className="vscode-codeql__icon-cell">

View File

@@ -21,7 +21,7 @@ interface Props {
resultKey: Keys.PathNode | Keys.Result | undefined,
) => void;
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
scroller: ScrollIntoViewHelper;
scroller?: ScrollIntoViewHelper;
}
export function AlertTableResultRow(props: Props) {
@@ -81,7 +81,7 @@ export function AlertTableResultRow(props: Props) {
return (
<>
<tr
ref={scroller.ref(resultRowIsSelected)}
ref={scroller?.ref(resultRowIsSelected)}
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
>
{result.codeFlows === undefined ? (

View File

@@ -0,0 +1,57 @@
import {
Usage,
ExternalApiUsage,
CallClassification,
} from "../../../src/data-extensions-editor/external-api-usage";
import { ModeledMethodType } from "../../../src/data-extensions-editor/modeled-method";
import { ResolvableLocationValue } from "../../../src/common/bqrs-cli-types";
export function createExternalApiUsage({
library = "test",
supported = true,
supportedType = "none" as ModeledMethodType,
usages = [],
signature = "test",
packageName = "test",
typeName = "test",
methodName = "test",
methodParameters = "test",
}: {
library?: string;
supported?: boolean;
supportedType?: ModeledMethodType;
usages?: Usage[];
signature?: string;
packageName?: string;
typeName?: string;
methodName?: string;
methodParameters?: string;
} = {}): ExternalApiUsage {
return {
library,
supported,
supportedType,
usages,
signature,
packageName,
typeName,
methodName,
methodParameters,
};
}
export function createUsage({
classification = CallClassification.Unknown,
label = "test",
url = {} as ResolvableLocationValue,
}: {
classification?: CallClassification;
label?: string;
url?: ResolvableLocationValue;
} = {}): Usage {
return {
classification,
label,
url,
};
}

View File

@@ -12,6 +12,7 @@ export function createMockSubmission(): VariantAnalysisSubmission {
filePath: "query-file-path",
language: QueryLanguage.Javascript,
text: "query-text",
kind: "table",
pack: "base64-encoded-string",
},
databases: {

View File

@@ -51,6 +51,7 @@ describe(processVariantAnalysis.name, () => {
language: QueryLanguage.Javascript,
name: "query-name",
text: mockSubmission.query.text,
kind: "table",
},
databases: {
repositories: ["1", "2", "3"],

View File

@@ -2,7 +2,7 @@ import {
readQueryResults,
runQuery,
setUpPack,
} from "../../../../src/data-extensions-editor/external-api-usage-query";
} from "../../../../src/data-extensions-editor/external-api-usage-queries";
import { createMockLogger } from "../../../__mocks__/loggerMock";
import { DatabaseKind } from "../../../../src/databases/local-databases";
import { dirSync, file } from "tmp-promise";

View File

@@ -2,20 +2,28 @@ import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
import { ExternalApiUsage } from "../../../../../src/data-extensions-editor/external-api-usage";
import { ModelDetailsDataProvider } from "../../../../../src/data-extensions-editor/model-details/model-details-data-provider";
import { DatabaseItem } from "../../../../../src/databases/local-databases";
import {
createExternalApiUsage,
createUsage,
} from "../../../../factories/data-extension/external-api-factories";
import { mockedObject } from "../../../utils/mocking.helpers";
describe("ModelDetailsDataProvider", () => {
const mockCliServer = mockedObject<CodeQLCliServer>({});
let dataProvider: ModelDetailsDataProvider;
beforeEach(() => {
dataProvider = new ModelDetailsDataProvider(mockCliServer);
});
describe("setState", () => {
const hideModeledApis: boolean = false;
const hideModeledApis = false;
const externalApiUsages: ExternalApiUsage[] = [];
const dbItem = mockedObject<DatabaseItem>({
getSourceLocationPrefix: () => "test",
});
it("should not emit onDidChangeTreeData event when state has not changed", async () => {
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
const onDidChangeTreeDataListener = jest.fn();
@@ -29,7 +37,6 @@ describe("ModelDetailsDataProvider", () => {
it("should emit onDidChangeTreeData event when externalApiUsages has changed", async () => {
const externalApiUsages2: ExternalApiUsage[] = [];
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
const onDidChangeTreeDataListener = jest.fn();
@@ -45,7 +52,6 @@ describe("ModelDetailsDataProvider", () => {
getSourceLocationPrefix: () => "test",
});
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
const onDidChangeTreeDataListener = jest.fn();
@@ -57,7 +63,6 @@ describe("ModelDetailsDataProvider", () => {
});
it("should emit onDidChangeTreeData event when hideModeledApis has changed", async () => {
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
const onDidChangeTreeDataListener = jest.fn();
@@ -74,7 +79,6 @@ describe("ModelDetailsDataProvider", () => {
});
const externalApiUsages2: ExternalApiUsage[] = [];
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
const onDidChangeTreeDataListener = jest.fn();
@@ -89,4 +93,45 @@ describe("ModelDetailsDataProvider", () => {
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
});
});
describe("getChildren", () => {
const supportedExternalApiUsage = createExternalApiUsage({
supported: true,
});
const unsupportedExternalApiUsage = createExternalApiUsage({
supported: false,
});
const externalApiUsages: ExternalApiUsage[] = [
supportedExternalApiUsage,
unsupportedExternalApiUsage,
];
const dbItem = mockedObject<DatabaseItem>({
getSourceLocationPrefix: () => "test",
});
const usage = createUsage({});
it("should return [] if item is a usage", async () => {
expect(dataProvider.getChildren(usage)).toEqual([]);
});
it("should return usages if item is external api usage", async () => {
const externalApiUsage = createExternalApiUsage({ usages: [usage] });
expect(dataProvider.getChildren(externalApiUsage)).toEqual([usage]);
});
it("should show all externalApiUsages if hideModeledApis is false and looking at the root", async () => {
const hideModeledApis = false;
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
expect(dataProvider.getChildren().length).toEqual(2);
});
it("should filter externalApiUsages if hideModeledApis is true and looking at the root", async () => {
const hideModeledApis = true;
await dataProvider.setState(externalApiUsages, dbItem, hideModeledApis);
expect(dataProvider.getChildren().length).toEqual(1);
});
});
});

View File

@@ -0,0 +1,73 @@
import { window, TreeView } from "vscode";
import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
import { ExternalApiUsage } from "../../../../../src/data-extensions-editor/external-api-usage";
import { ModelDetailsPanel } from "../../../../../src/data-extensions-editor/model-details/model-details-panel";
import { DatabaseItem } from "../../../../../src/databases/local-databases";
import { mockedObject } from "../../../utils/mocking.helpers";
import {
createExternalApiUsage,
createUsage,
} from "../../../../factories/data-extension/external-api-factories";
describe("ModelDetailsPanel", () => {
const mockCliServer = mockedObject<CodeQLCliServer>({});
const dbItem = mockedObject<DatabaseItem>({
getSourceLocationPrefix: () => "test",
});
describe("setState", () => {
const hideModeledApis = false;
const externalApiUsages: ExternalApiUsage[] = [createExternalApiUsage()];
it("should update the tree view with the correct batch number", async () => {
const mockTreeView = {
badge: undefined,
} as TreeView<unknown>;
jest.spyOn(window, "createTreeView").mockReturnValue(mockTreeView);
const panel = new ModelDetailsPanel(mockCliServer);
await panel.setState(externalApiUsages, dbItem, hideModeledApis);
expect(mockTreeView.badge?.value).toBe(1);
});
});
describe("revealItem", () => {
let mockTreeView: TreeView<unknown>;
const hideModeledApis: boolean = false;
const usage = createUsage();
beforeEach(() => {
mockTreeView = {
reveal: jest.fn(),
} as unknown as TreeView<unknown>;
jest.spyOn(window, "createTreeView").mockReturnValue(mockTreeView);
});
it("should reveal the correct item in the tree view", async () => {
const externalApiUsages = [
createExternalApiUsage({
usages: [usage],
}),
];
const panel = new ModelDetailsPanel(mockCliServer);
await panel.setState(externalApiUsages, dbItem, hideModeledApis);
await panel.revealItem(usage);
expect(mockTreeView.reveal).toHaveBeenCalledWith(usage);
});
it("should do nothing if usage cannot be found", async () => {
const externalApiUsages = [createExternalApiUsage({})];
const panel = new ModelDetailsPanel(mockCliServer);
await panel.setState(externalApiUsages, dbItem, hideModeledApis);
await panel.revealItem(usage);
expect(mockTreeView.reveal).not.toHaveBeenCalled();
});
});
});