Merge branch 'main' into robertbrignull/useScrollIntoView
This commit is contained in:
4
.github/workflows/cli-test.yml
vendored
4
.github/workflows/cli-test.yml
vendored
@@ -6,9 +6,11 @@ on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- .github/workflows/cli-test.yml
|
||||
- extensions/ql-vscode/src/codeql-cli/**
|
||||
- extensions/ql-vscode/src/language-support/**
|
||||
- extensions/ql-vscode/src/query-server/**
|
||||
- extensions/ql-vscode/supported_cli_versions.json
|
||||
|
||||
jobs:
|
||||
find-nightly:
|
||||
@@ -47,7 +49,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
version: ${{ fromJson(needs.set-matrix.outputs.cli-versions) }}
|
||||
version: ${{ fromJson(needs.set-matrix.outputs.cli-versions) }}
|
||||
fail-fast: false
|
||||
env:
|
||||
CLI_VERSION: ${{ matrix.version }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
**/* @github/codeql-vscode-reviewers
|
||||
**/variant-analysis/ @github/code-scanning-secexp-reviewers
|
||||
**/databases/ @github/code-scanning-secexp-reviewers
|
||||
**/data-extensions-editor/ @github/code-scanning-secexp-reviewers
|
||||
**/model-editor/ @github/code-scanning-secexp-reviewers
|
||||
**/queries-panel/ @github/code-scanning-secexp-reviewers
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- Update how variant analysis results are displayed. For queries with ["path-problem" or "problem" `@kind`](https://codeql.github.com/docs/writing-codeql-queries/metadata-for-codeql-queries/#metadata-properties), you can choose to display the results as rendered alerts or as a table of raw results. For queries with any other `@kind`, the results are displayed as a table. [#2745](https://github.com/github/vscode-codeql/pull/2745) & [#2749](https://github.com/github/vscode-codeql/pull/2749)
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -943,8 +943,8 @@
|
||||
"enablement": "codeql.hasQLSource"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openDataExtensionsEditor",
|
||||
"title": "CodeQL: Open Data Extensions Editor"
|
||||
"command": "codeQL.openModelEditor",
|
||||
"title": "CodeQL: Open CodeQL Model Editor"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.startRecording",
|
||||
@@ -1415,7 +1415,7 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openDataExtensionsEditor",
|
||||
"command": "codeQL.openModelEditor",
|
||||
"when": "config.codeQL.canary && config.codeQL.dataExtensions.editor"
|
||||
},
|
||||
{
|
||||
@@ -1652,7 +1652,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQL.createQuery",
|
||||
"when": "config.codeQL.codespacesTemplate"
|
||||
"when": "config.codeQL.codespacesTemplate || config.codeQL.canary && config.codeQL.queriesPanel"
|
||||
},
|
||||
{
|
||||
"command": "codeQLTests.acceptOutputContextTestItem",
|
||||
@@ -1724,8 +1724,8 @@
|
||||
],
|
||||
"panel": [
|
||||
{
|
||||
"id": "codeql-model-details",
|
||||
"title": "CodeQL Model Details",
|
||||
"id": "codeql-methods-usage",
|
||||
"title": "CodeQL Methods Usage",
|
||||
"icon": "media/logo.svg"
|
||||
}
|
||||
]
|
||||
@@ -1759,17 +1759,25 @@
|
||||
"when": "config.codeQL.canary"
|
||||
}
|
||||
],
|
||||
"codeql-model-details": [
|
||||
"codeql-methods-usage": [
|
||||
{
|
||||
"id": "codeQLModelDetails",
|
||||
"name": "CodeQL Model Details",
|
||||
"when": "config.codeQL.canary && codeql.dataExtensionsEditorOpen"
|
||||
"id": "codeQLMethodsUsage",
|
||||
"name": "CodeQL Methods Usage",
|
||||
"when": "config.codeQL.canary && codeql.modelEditorOpen"
|
||||
}
|
||||
],
|
||||
"explorer": [
|
||||
{
|
||||
"type": "webview",
|
||||
"id": "codeQLMethodModeling",
|
||||
"name": "CodeQL Method Modeling",
|
||||
"when": "config.codeQL.canary && config.codeQL.modelEditor.methodModelingView && codeql.modelEditorOpen"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "codeQLModelDetails",
|
||||
"view": "codeQLMethodsUsage",
|
||||
"contents": "Loading..."
|
||||
},
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
|
||||
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
|
||||
import type { Usage } from "../data-extensions-editor/external-api-usage";
|
||||
import type { Usage } from "../model-editor/external-api-usage";
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command is invoked from a context menu on a TreeView with
|
||||
@@ -302,9 +302,9 @@ export type PackagingCommands = {
|
||||
"codeQL.downloadPacks": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type DataExtensionsEditorCommands = {
|
||||
"codeQL.openDataExtensionsEditor": () => Promise<void>;
|
||||
"codeQLDataExtensionsEditor.jumpToUsageLocation": (
|
||||
export type ModelEditorCommands = {
|
||||
"codeQL.openModelEditor": () => Promise<void>;
|
||||
"codeQLModelEditor.jumpToUsageLocation": (
|
||||
usage: Usage,
|
||||
databaseItem: DatabaseItem,
|
||||
) => Promise<void>;
|
||||
@@ -347,7 +347,7 @@ export type AllExtensionCommands = BaseCommands &
|
||||
AstCfgCommands &
|
||||
AstViewerCommands &
|
||||
PackagingCommands &
|
||||
DataExtensionsEditorCommands &
|
||||
ModelEditorCommands &
|
||||
EvalLogViewerCommands &
|
||||
SummaryLanguageSupportCommands &
|
||||
Partial<TestUICommands> &
|
||||
|
||||
@@ -17,13 +17,10 @@ import {
|
||||
} from "../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { ErrorLike } from "../common/errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import {
|
||||
ExternalApiUsage,
|
||||
Usage,
|
||||
} from "../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../data-extensions-editor/modeled-method";
|
||||
import { DataExtensionEditorViewState } from "../data-extensions-editor/shared/view-state";
|
||||
import { Mode } from "../data-extensions-editor/shared/mode";
|
||||
import { ExternalApiUsage, Usage } from "../model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../model-editor/modeled-method";
|
||||
import { ModelEditorViewState } from "../model-editor/shared/view-state";
|
||||
import { Mode } from "../model-editor/shared/mode";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -494,8 +491,8 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
interface SetExtensionPackStateMessage {
|
||||
t: "setDataExtensionEditorViewState";
|
||||
viewState: DataExtensionEditorViewState;
|
||||
t: "setModelEditorViewState";
|
||||
viewState: ModelEditorViewState;
|
||||
}
|
||||
|
||||
interface SetExternalApiUsagesMessage {
|
||||
@@ -572,14 +569,14 @@ interface HideModeledApisMessage {
|
||||
hideModeledApis: boolean;
|
||||
}
|
||||
|
||||
export type ToDataExtensionsEditorMessage =
|
||||
export type ToModelEditorMessage =
|
||||
| SetExtensionPackStateMessage
|
||||
| SetExternalApiUsagesMessage
|
||||
| LoadModeledMethodsMessage
|
||||
| AddModeledMethodsMessage
|
||||
| SetInProgressMethodsMessage;
|
||||
|
||||
export type FromDataExtensionsEditorMessage =
|
||||
export type FromModelEditorMessage =
|
||||
| ViewLoadedMsg
|
||||
| SwitchModeMessage
|
||||
| RefreshExternalApiUsages
|
||||
@@ -592,3 +589,7 @@ export type FromDataExtensionsEditorMessage =
|
||||
| StopGeneratingExternalApiFromLlmMessage
|
||||
| ModelDependencyMessage
|
||||
| HideModeledApisMessage;
|
||||
|
||||
export type FromMethodModelingMessage =
|
||||
| TelemetryMessage
|
||||
| UnhandledErrorMessage;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,12 +2,13 @@ import { ExtensionContext, Uri, Webview } from "vscode";
|
||||
import { randomBytes } from "crypto";
|
||||
import { EOL } from "os";
|
||||
|
||||
export type WebviewView =
|
||||
export type WebviewKind =
|
||||
| "results"
|
||||
| "compare"
|
||||
| "variant-analysis"
|
||||
| "data-flow-paths"
|
||||
| "data-extensions-editor";
|
||||
| "model-editor"
|
||||
| "method-modeling";
|
||||
|
||||
export interface WebviewMessage {
|
||||
t: string;
|
||||
@@ -20,7 +21,7 @@ export interface WebviewMessage {
|
||||
export function getHtmlForWebview(
|
||||
ctx: ExtensionContext,
|
||||
webview: Webview,
|
||||
view: WebviewView,
|
||||
view: WebviewKind,
|
||||
{
|
||||
allowInlineStyles,
|
||||
allowWasmEval,
|
||||
|
||||
@@ -704,7 +704,6 @@ export function showQueriesPanel(): boolean {
|
||||
|
||||
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
|
||||
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
|
||||
const FRAMEWORK_MODE = new Setting("frameworkMode", DATA_EXTENSIONS);
|
||||
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
|
||||
"disableAutoNameExtensionPack",
|
||||
DATA_EXTENSIONS,
|
||||
@@ -718,10 +717,6 @@ export function showLlmGeneration(): boolean {
|
||||
return !!LLM_GENERATION.getValue<boolean>();
|
||||
}
|
||||
|
||||
export function enableFrameworkMode(): boolean {
|
||||
return !!FRAMEWORK_MODE.getValue<boolean>();
|
||||
}
|
||||
|
||||
export function disableAutoNameExtensionPack(): boolean {
|
||||
return !!DISABLE_AUTO_NAME_EXTENSION_PACK.getValue<boolean>();
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ import { getAstCfgCommands } from "./language-support/ast-viewer/ast-cfg-command
|
||||
import { App } from "./common/app";
|
||||
import { registerCommandWithErrorHandling } from "./common/vscode/commands";
|
||||
import { DebuggerUI } from "./debugger/debugger-ui";
|
||||
import { DataExtensionsEditorModule } from "./data-extensions-editor/data-extensions-editor-module";
|
||||
import { ModelEditorModule } from "./model-editor/model-editor-module";
|
||||
import { TestManager } from "./query-testing/test-manager";
|
||||
import { TestRunner } from "./query-testing/test-runner";
|
||||
import { TestManagerBase } from "./query-testing/test-manager-base";
|
||||
@@ -934,15 +934,14 @@ async function activateWithInstalledDistribution(
|
||||
const debuggerUI = new DebuggerUI(app, localQueries, dbm);
|
||||
ctx.subscriptions.push(debuggerUI);
|
||||
|
||||
const dataExtensionsEditorModule =
|
||||
await DataExtensionsEditorModule.initialize(
|
||||
ctx,
|
||||
app,
|
||||
dbm,
|
||||
cliServer,
|
||||
qs,
|
||||
tmpDir.name,
|
||||
);
|
||||
const modelEditorModule = await ModelEditorModule.initialize(
|
||||
ctx,
|
||||
app,
|
||||
dbm,
|
||||
cliServer,
|
||||
qs,
|
||||
tmpDir.name,
|
||||
);
|
||||
|
||||
void extLogger.log("Initializing QLTest interface.");
|
||||
|
||||
@@ -1015,7 +1014,7 @@ async function activateWithInstalledDistribution(
|
||||
...getPackagingCommands({
|
||||
cliServer,
|
||||
}),
|
||||
...dataExtensionsEditorModule.getCommands(),
|
||||
...modelEditorModule.getCommands(),
|
||||
...evalLogViewer.getCommands(),
|
||||
...summaryLanguageSupport.getCommands(),
|
||||
...testUiCommands,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "./key-type";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { resolveQueries as resolveLocalQueries } from "../../local-queries/query-resolver";
|
||||
import { resolveQueriesByLanguagePack as resolveLocalQueries } from "../../local-queries/query-resolver";
|
||||
import { extLogger } from "../../common/logging/vscode";
|
||||
import { TeeLogger } from "../../common/logging";
|
||||
import { CancellationToken } from "vscode";
|
||||
|
||||
@@ -72,16 +72,7 @@ async function resolveQueriesFromPacks(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the queries with the specified kind and tags in a QLPack.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param qlpacks The list of packs to search.
|
||||
* @param name The name of the query to use in error messages.
|
||||
* @param constraints Constraints on the queries to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
export async function resolveQueries(
|
||||
export async function resolveQueriesByLanguagePack(
|
||||
cli: CodeQLCliServer,
|
||||
qlpacks: QlPacksForLanguage,
|
||||
name: string,
|
||||
@@ -95,6 +86,24 @@ export async function resolveQueries(
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
return resolveQueries(cli, packsToSearch, name, constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the queries with the specified kind and tags in a QLPack.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param packsToSearch The list of packs to search.
|
||||
* @param name The name of the query to use in error messages.
|
||||
* @param constraints Constraints on the queries to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
export async function resolveQueries(
|
||||
cli: CodeQLCliServer,
|
||||
packsToSearch: string[],
|
||||
name: string,
|
||||
constraints: QueryConstraints,
|
||||
): Promise<string[]> {
|
||||
const queries = await resolveQueriesFromPacks(
|
||||
cli,
|
||||
packsToSearch,
|
||||
|
||||
84
extensions/ql-vscode/src/local-queries/run-query.ts
Normal file
84
extensions/ql-vscode/src/local-queries/run-query.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { createLockFileForStandardQuery } from "./standard-queries";
|
||||
import { TeeLogger, showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { basename } from "path";
|
||||
|
||||
type RunQueryOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryPath: string;
|
||||
queryStorageDir: string;
|
||||
additionalPacks: string[];
|
||||
extensionPacks: string[] | undefined;
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
createLockFile: boolean;
|
||||
};
|
||||
|
||||
export async function runQuery({
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
progress,
|
||||
token,
|
||||
createLockFile,
|
||||
}: RunQueryOptions): Promise<CoreCompletedQuery | undefined> {
|
||||
let cleanupLockFile: (() => Promise<void>) | undefined = undefined;
|
||||
if (createLockFile) {
|
||||
// Create a lock file for the query. This is required to resolve dependencies and library path for the query.
|
||||
const { cleanup } = await createLockFileForStandardQuery(
|
||||
cliServer,
|
||||
queryPath,
|
||||
);
|
||||
cleanupLockFile = cleanup;
|
||||
}
|
||||
|
||||
// Create a query run to execute
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
queryPath,
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const completedQuery = await queryRun.evaluate(
|
||||
progress,
|
||||
token,
|
||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
|
||||
await cleanupLockFile?.();
|
||||
|
||||
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Failed to run ${basename(queryPath)} query: ${
|
||||
completedQuery.message ?? "No message"
|
||||
}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
return completedQuery;
|
||||
}
|
||||
@@ -1,19 +1,10 @@
|
||||
import { CodeQLCliServer, SourceInfo } from "../codeql-cli/cli";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import * as Sarif from "sarif";
|
||||
import { qlpackOfDatabase, resolveQueries } from "../local-queries";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { QlPacksForLanguage } from "../databases/qlpack";
|
||||
import { createLockFileForStandardQuery } from "../local-queries/standard-queries";
|
||||
import { CancellationToken, CancellationTokenSource } from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { interpretResultsSarif } from "../query-results";
|
||||
import { join } from "path";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
@@ -21,22 +12,10 @@ import { dir } from "tmp-promise";
|
||||
import { writeFile, outputFile } from "fs-extra";
|
||||
import { dump as dumpYaml } from "js-yaml";
|
||||
import { MethodSignature } from "./external-api-usage";
|
||||
|
||||
type AutoModelQueryOptions = {
|
||||
queryTag: string;
|
||||
mode: Mode;
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
qlpack: QlPacksForLanguage;
|
||||
sourceInfo: SourceInfo | undefined;
|
||||
additionalPacks: string[];
|
||||
extensionPacks: string[];
|
||||
queryStorageDir: string;
|
||||
|
||||
progress: ProgressCallback;
|
||||
token: CancellationToken;
|
||||
};
|
||||
import { runQuery } from "../local-queries/run-query";
|
||||
import { QueryMetadata } from "../common/interface-types";
|
||||
import { CancellationTokenSource } from "vscode";
|
||||
import { resolveQueries } from "../local-queries";
|
||||
|
||||
function modeTag(mode: Mode): string {
|
||||
switch (mode) {
|
||||
@@ -49,108 +28,6 @@ function modeTag(mode: Mode): string {
|
||||
}
|
||||
}
|
||||
|
||||
async function runAutoModelQuery({
|
||||
queryTag,
|
||||
mode,
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
qlpack,
|
||||
sourceInfo,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
progress,
|
||||
token,
|
||||
}: AutoModelQueryOptions): Promise<Sarif.Log | undefined> {
|
||||
// First, resolve the query that we want to run.
|
||||
// All queries are tagged like this:
|
||||
// internal extract automodel <mode> <queryTag>
|
||||
// Example: internal extract automodel framework-mode candidates
|
||||
const queries = await resolveQueries(
|
||||
cliServer,
|
||||
qlpack,
|
||||
`Extract automodel ${queryTag}`,
|
||||
{
|
||||
kind: "problem",
|
||||
"tags contain all": ["automodel", modeTag(mode), ...queryTag.split(" ")],
|
||||
},
|
||||
);
|
||||
if (queries.length > 1) {
|
||||
throw new Error(
|
||||
`Found multiple auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
if (queries.length === 0) {
|
||||
throw new Error(
|
||||
`Did not found any auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
|
||||
const queryPath = queries[0];
|
||||
const { cleanup: cleanupLockFile } = await createLockFileForStandardQuery(
|
||||
cliServer,
|
||||
queryPath,
|
||||
);
|
||||
|
||||
// Get metadata for the query. This is required to interpret the results. We already know the kind is problem
|
||||
// (because of the constraint in resolveQueries), so we don't need any more checks on the metadata.
|
||||
const metadata = await cliServer.resolveMetadata(queryPath);
|
||||
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
queryPath,
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const completedQuery = await queryRun.evaluate(
|
||||
progress,
|
||||
token,
|
||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
|
||||
await cleanupLockFile?.();
|
||||
|
||||
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Auto-model query ${queryTag} failed: ${
|
||||
completedQuery.message ?? "No message"
|
||||
}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const interpretedResultsPath = join(
|
||||
queryStorageDir,
|
||||
`interpreted-results-${queryTag.replaceAll(" ", "-")}-${queryRun.id}.sarif`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- We only need the actual SARIF data, not the extra fields added by SarifInterpretationData
|
||||
const { t, sortState, ...sarif } = await interpretResultsSarif(
|
||||
cliServer,
|
||||
metadata,
|
||||
{
|
||||
resultsPath: completedQuery.outputDir.bqrsPath,
|
||||
interpretedResultsPath,
|
||||
},
|
||||
sourceInfo,
|
||||
["--sarif-add-snippets"],
|
||||
);
|
||||
|
||||
return sarif;
|
||||
}
|
||||
|
||||
type AutoModelQueriesOptions = {
|
||||
mode: Mode;
|
||||
candidateMethods: MethodSignature[];
|
||||
@@ -177,7 +54,46 @@ export async function runAutoModelQueries({
|
||||
progress,
|
||||
cancellationTokenSource,
|
||||
}: AutoModelQueriesOptions): Promise<AutoModelQueriesResult | undefined> {
|
||||
const qlpack = await qlpackOfDatabase(cliServer, databaseItem);
|
||||
// First, resolve the query that we want to run.
|
||||
const queryPath = await resolveAutomodelQuery(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
"candidates",
|
||||
mode,
|
||||
);
|
||||
|
||||
// Generate a pack containing the candidate filters
|
||||
const filterPackDir = await generateCandidateFilterPack(
|
||||
databaseItem.language,
|
||||
candidateMethods,
|
||||
);
|
||||
|
||||
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
// Run the actual query
|
||||
const completedQuery = await runQuery({
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
progress,
|
||||
token: cancellationTokenSource.token,
|
||||
createLockFile: false,
|
||||
});
|
||||
|
||||
if (!completedQuery) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Get metadata for the query. This is required to interpret the results. We already know the kind is problem
|
||||
// (because of the constraint in resolveQueries), so we don't need any more checks on the metadata.
|
||||
const metadata = await cliServer.resolveMetadata(queryPath);
|
||||
|
||||
// CodeQL needs to have access to the database to be able to retrieve the
|
||||
// snippets from it. The source location prefix is used to determine the
|
||||
@@ -194,41 +110,53 @@ export async function runAutoModelQueries({
|
||||
sourceLocationPrefix,
|
||||
};
|
||||
|
||||
// Generate a pack containing the candidate filters
|
||||
const filterPackDir = await generateCandidateFilterPack(
|
||||
databaseItem.language,
|
||||
candidateMethods,
|
||||
);
|
||||
|
||||
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
|
||||
const extensionPacks = Object.keys(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
const candidates = await runAutoModelQuery({
|
||||
mode,
|
||||
queryTag: "candidates",
|
||||
const candidates = await interpretAutomodelResults(
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
qlpack,
|
||||
completedQuery,
|
||||
metadata,
|
||||
sourceInfo,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
queryStorageDir,
|
||||
progress,
|
||||
token: cancellationTokenSource.token,
|
||||
});
|
||||
|
||||
if (!candidates) {
|
||||
return undefined;
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
candidates,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveAutomodelQuery(
|
||||
cliServer: CodeQLCliServer,
|
||||
databaseItem: DatabaseItem,
|
||||
queryTag: string,
|
||||
mode: Mode,
|
||||
): Promise<string> {
|
||||
const packsToSearch = [`codeql/${databaseItem.language}-automodel-queries`];
|
||||
|
||||
// First, resolve the query that we want to run.
|
||||
// All queries are tagged like this:
|
||||
// internal extract automodel <mode> <queryTag>
|
||||
// Example: internal extract automodel framework-mode candidates
|
||||
const queries = await resolveQueries(
|
||||
cliServer,
|
||||
packsToSearch,
|
||||
`Extract automodel ${queryTag}`,
|
||||
{
|
||||
kind: "problem",
|
||||
"tags contain all": ["automodel", modeTag(mode), ...queryTag.split(" ")],
|
||||
},
|
||||
);
|
||||
if (queries.length > 1) {
|
||||
throw new Error(
|
||||
`Found multiple auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
if (queries.length === 0) {
|
||||
throw new Error(
|
||||
`Did not found any auto model queries for ${mode} ${queryTag}. Can't continue`,
|
||||
);
|
||||
}
|
||||
|
||||
return queries[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* generateCandidateFilterPack will create a temporary extension pack.
|
||||
* This pack will contain a filter that will restrict the automodel queries
|
||||
@@ -250,7 +178,7 @@ export async function generateCandidateFilterPack(
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
[`codeql/${language}-queries`]: "*",
|
||||
[`codeql/${language}-automodel-queries`]: "*",
|
||||
},
|
||||
dataExtensions: ["filter.yml"],
|
||||
};
|
||||
@@ -271,7 +199,7 @@ export async function generateCandidateFilterPack(
|
||||
extensions: [
|
||||
{
|
||||
addsTo: {
|
||||
pack: `codeql/${language}-queries`,
|
||||
pack: `codeql/${language}-automodel-queries`,
|
||||
extensible: "automodelCandidateFilter",
|
||||
},
|
||||
data: dataRows,
|
||||
@@ -284,3 +212,28 @@ export async function generateCandidateFilterPack(
|
||||
|
||||
return packDir;
|
||||
}
|
||||
|
||||
async function interpretAutomodelResults(
|
||||
cliServer: CodeQLCliServer,
|
||||
completedQuery: CoreCompletedQuery,
|
||||
metadata: QueryMetadata,
|
||||
sourceInfo: SourceInfo | undefined,
|
||||
): Promise<Sarif.Log> {
|
||||
const interpretedResultsPath = join(
|
||||
completedQuery.outputDir.querySaveDir,
|
||||
"results.sarif",
|
||||
);
|
||||
|
||||
const { ...sarif } = await interpretResultsSarif(
|
||||
cliServer,
|
||||
metadata,
|
||||
{
|
||||
resultsPath: completedQuery.outputDir.bqrsPath,
|
||||
interpretedResultsPath,
|
||||
},
|
||||
sourceInfo,
|
||||
["--sarif-add-snippets"],
|
||||
);
|
||||
|
||||
return sarif;
|
||||
}
|
||||
@@ -1,25 +1,26 @@
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
|
||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { DatabaseItem } from "../databases/local-databases";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { join } from "path";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { Query } from "./queries/query";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { dump } from "js-yaml";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { runQuery } from "../local-queries/run-query";
|
||||
import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
|
||||
type RunQueryOptions = {
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">;
|
||||
queryRunner: Pick<QueryRunner, "createQueryRun" | "logger">;
|
||||
databaseItem: Pick<DatabaseItem, "contents" | "databaseUri" | "language">;
|
||||
cliServer: CodeQLCliServer;
|
||||
queryRunner: QueryRunner;
|
||||
databaseItem: DatabaseItem;
|
||||
queryStorageDir: string;
|
||||
queryDir: string;
|
||||
|
||||
@@ -27,39 +28,37 @@ type RunQueryOptions = {
|
||||
token: CancellationToken;
|
||||
};
|
||||
|
||||
export async function setUpPack(
|
||||
export async function prepareExternalApiQuery(
|
||||
queryDir: string,
|
||||
query: Query,
|
||||
language: QueryLanguage,
|
||||
) {
|
||||
Object.values(Mode).map(async (mode) => {
|
||||
const queryFile = join(
|
||||
queryDir,
|
||||
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
|
||||
): Promise<boolean> {
|
||||
// Resolve the query that we want to run.
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`No external API usage query found for language ${language}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Create the query file.
|
||||
Object.values(Mode).map(async (mode) => {
|
||||
const queryFile = join(queryDir, queryNameFromMode(mode));
|
||||
await writeFile(queryFile, query[`${mode}ModeQuery`], "utf8");
|
||||
});
|
||||
|
||||
// Create any dependencies
|
||||
if (query.dependencies) {
|
||||
for (const [filename, contents] of Object.entries(query.dependencies)) {
|
||||
const dependencyFile = join(queryDir, filename);
|
||||
await writeFile(dependencyFile, contents, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
const syntheticQueryPack = {
|
||||
name: "codeql/external-api-usage",
|
||||
version: "0.0.0",
|
||||
dependencies: {
|
||||
[`codeql/${language}-all`]: "*",
|
||||
},
|
||||
};
|
||||
|
||||
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function runQuery(
|
||||
export async function runExternalApiQueries(
|
||||
mode: Mode,
|
||||
{
|
||||
cliServer,
|
||||
@@ -70,7 +69,7 @@ export async function runQuery(
|
||||
progress,
|
||||
token,
|
||||
}: RunQueryOptions,
|
||||
): Promise<CoreCompletedQuery | undefined> {
|
||||
): Promise<ExternalApiUsage[] | undefined> {
|
||||
// The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will
|
||||
// move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries.
|
||||
// This is intentionally not pretty code, as it will be removed soon.
|
||||
@@ -82,44 +81,49 @@ export async function runQuery(
|
||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||
);
|
||||
|
||||
const queryFile = join(
|
||||
queryDir,
|
||||
`FetchExternalApis${mode.charAt(0).toUpperCase() + mode.slice(1)}Mode.ql`,
|
||||
);
|
||||
const queryPath = join(queryDir, queryNameFromMode(mode));
|
||||
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
queryPath: queryFile,
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
extensionPacks,
|
||||
// Run the actual query
|
||||
const completedQuery = await runQuery({
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const completedQuery = await queryRun.evaluate(
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
progress,
|
||||
token,
|
||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
// We need to create a lock file, because the query is inside our own pack
|
||||
createLockFile: true,
|
||||
});
|
||||
|
||||
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`External API usage query failed: ${
|
||||
completedQuery.message ?? "No message"
|
||||
}`,
|
||||
);
|
||||
if (!completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
return completedQuery;
|
||||
// Read the results and covert to internal representation
|
||||
progress({
|
||||
message: "Decoding results",
|
||||
step: 1100,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer,
|
||||
bqrsPath: completedQuery.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Finalizing results",
|
||||
step: 1450,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
return decodeBqrsToExternalApiUsages(bqrsChunk);
|
||||
}
|
||||
|
||||
type GetResultsOptions = {
|
||||
@@ -145,3 +149,9 @@ export async function readQueryResults({
|
||||
|
||||
return cliServer.bqrsDecode(bqrsPath, resultSet.name);
|
||||
}
|
||||
|
||||
function queryNameFromMode(mode: Mode): string {
|
||||
return `FetchExternalApis${
|
||||
mode.charAt(0).toUpperCase() + mode.slice(1)
|
||||
}Mode.ql`;
|
||||
}
|
||||
@@ -3,19 +3,16 @@ import { DatabaseItem } from "../databases/local-databases";
|
||||
import { basename } from "path";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { showAndLogExceptionWithTelemetry, TeeLogger } from "../common/logging";
|
||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import { extensiblePredicateDefinitions } from "./predicates";
|
||||
import { ProgressCallback } from "../common/vscode/progress";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { QueryResultType } from "../query-server/new-messages";
|
||||
import { file } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { qlpackOfDatabase } from "../local-queries";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { runQuery } from "../local-queries/run-query";
|
||||
import { resolveQueries } from "../local-queries";
|
||||
|
||||
type FlowModelOptions = {
|
||||
cliServer: CodeQLCliServer;
|
||||
@@ -27,44 +24,78 @@ type FlowModelOptions = {
|
||||
onResults: (results: ModeledMethod[]) => void | Promise<void>;
|
||||
};
|
||||
|
||||
async function resolveQueries(
|
||||
export async function runFlowModelQueries({
|
||||
onResults,
|
||||
...options
|
||||
}: FlowModelOptions) {
|
||||
const queries = await resolveFlowQueries(
|
||||
options.cliServer,
|
||||
options.databaseItem,
|
||||
);
|
||||
|
||||
const queriesByBasename: Record<string, string> = {};
|
||||
for (const query of queries) {
|
||||
queriesByBasename[basename(query)] = query;
|
||||
}
|
||||
|
||||
const summaryResults = await runSingleFlowQuery(
|
||||
"summary",
|
||||
queriesByBasename["CaptureSummaryModels.ql"],
|
||||
0,
|
||||
options,
|
||||
);
|
||||
if (summaryResults) {
|
||||
await onResults(summaryResults);
|
||||
}
|
||||
|
||||
const sinkResults = await runSingleFlowQuery(
|
||||
"sink",
|
||||
queriesByBasename["CaptureSinkModels.ql"],
|
||||
1,
|
||||
options,
|
||||
);
|
||||
if (sinkResults) {
|
||||
await onResults(sinkResults);
|
||||
}
|
||||
|
||||
const sourceResults = await runSingleFlowQuery(
|
||||
"source",
|
||||
queriesByBasename["CaptureSourceModels.ql"],
|
||||
2,
|
||||
options,
|
||||
);
|
||||
if (sourceResults) {
|
||||
await onResults(sourceResults);
|
||||
}
|
||||
|
||||
const neutralResults = await runSingleFlowQuery(
|
||||
"neutral",
|
||||
queriesByBasename["CaptureNeutralModels.ql"],
|
||||
3,
|
||||
options,
|
||||
);
|
||||
if (neutralResults) {
|
||||
await onResults(neutralResults);
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveFlowQueries(
|
||||
cliServer: CodeQLCliServer,
|
||||
databaseItem: DatabaseItem,
|
||||
): Promise<string[]> {
|
||||
const qlpacks = await qlpackOfDatabase(cliServer, databaseItem);
|
||||
const packsToSearch = [`codeql/${databaseItem.language}-queries`];
|
||||
|
||||
const packsToSearch: string[] = [];
|
||||
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
const suiteFile = (
|
||||
await file({
|
||||
postfix: ".qls",
|
||||
})
|
||||
).path;
|
||||
const suiteYaml = [];
|
||||
for (const qlpack of packsToSearch) {
|
||||
suiteYaml.push({
|
||||
from: qlpack,
|
||||
queries: ".",
|
||||
include: {
|
||||
"tags contain": "modelgenerator",
|
||||
},
|
||||
});
|
||||
}
|
||||
await writeFile(suiteFile, dump(suiteYaml), "utf8");
|
||||
|
||||
return await cliServer.resolveQueriesInSuite(
|
||||
suiteFile,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
return await resolveQueries(
|
||||
cliServer,
|
||||
packsToSearch,
|
||||
"flow model generator",
|
||||
{
|
||||
"tags contain": ["modelgenerator"],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function getModeledMethodsFromFlow(
|
||||
async function runSingleFlowQuery(
|
||||
type: Exclude<ModeledMethodType, "none">,
|
||||
queryPath: string | undefined,
|
||||
queryStep: number,
|
||||
@@ -77,6 +108,7 @@ async function getModeledMethodsFromFlow(
|
||||
token,
|
||||
}: Omit<FlowModelOptions, "onResults">,
|
||||
): Promise<ModeledMethod[]> {
|
||||
// Check that the right query was found
|
||||
if (queryPath === undefined) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
@@ -86,45 +118,33 @@ async function getModeledMethodsFromFlow(
|
||||
return [];
|
||||
}
|
||||
|
||||
const definition = extensiblePredicateDefinitions[type];
|
||||
|
||||
const queryRun = queryRunner.createQueryRun(
|
||||
databaseItem.databaseUri.fsPath,
|
||||
{
|
||||
queryPath,
|
||||
quickEvalPosition: undefined,
|
||||
quickEvalCountOnly: false,
|
||||
},
|
||||
false,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
undefined,
|
||||
// Run the query
|
||||
const completedQuery = await runQuery({
|
||||
cliServer,
|
||||
queryRunner,
|
||||
databaseItem,
|
||||
queryPath,
|
||||
queryStorageDir,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const queryResult = await queryRun.evaluate(
|
||||
({ step, message }) =>
|
||||
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||
extensionPacks: undefined,
|
||||
progress: ({ step, message }) =>
|
||||
progress({
|
||||
message: `Generating ${type} model: ${message}`,
|
||||
step: queryStep * 1000 + step,
|
||||
maxStep: 4000,
|
||||
}),
|
||||
token,
|
||||
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
|
||||
);
|
||||
if (queryResult.resultType !== QueryResultType.SUCCESS) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`Failed to run ${basename(queryPath)} query: ${
|
||||
queryResult.message ?? "No message"
|
||||
}`,
|
||||
);
|
||||
createLockFile: false,
|
||||
});
|
||||
|
||||
if (!completedQuery) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bqrsPath = queryResult.outputDir.bqrsPath;
|
||||
// Interpret the results
|
||||
const definition = extensiblePredicateDefinitions[type];
|
||||
|
||||
const bqrsPath = completedQuery.outputDir.bqrsPath;
|
||||
|
||||
const bqrsInfo = await cliServer.bqrsInfo(bqrsPath);
|
||||
if (bqrsInfo["result-sets"].length !== 1) {
|
||||
@@ -154,55 +174,3 @@ async function getModeledMethodsFromFlow(
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateFlowModel({
|
||||
onResults,
|
||||
...options
|
||||
}: FlowModelOptions) {
|
||||
const queries = await resolveQueries(options.cliServer, options.databaseItem);
|
||||
|
||||
const queriesByBasename: Record<string, string> = {};
|
||||
for (const query of queries) {
|
||||
queriesByBasename[basename(query)] = query;
|
||||
}
|
||||
|
||||
const summaryResults = await getModeledMethodsFromFlow(
|
||||
"summary",
|
||||
queriesByBasename["CaptureSummaryModels.ql"],
|
||||
0,
|
||||
options,
|
||||
);
|
||||
if (summaryResults) {
|
||||
await onResults(summaryResults);
|
||||
}
|
||||
|
||||
const sinkResults = await getModeledMethodsFromFlow(
|
||||
"sink",
|
||||
queriesByBasename["CaptureSinkModels.ql"],
|
||||
1,
|
||||
options,
|
||||
);
|
||||
if (sinkResults) {
|
||||
await onResults(sinkResults);
|
||||
}
|
||||
|
||||
const sourceResults = await getModeledMethodsFromFlow(
|
||||
"source",
|
||||
queriesByBasename["CaptureSourceModels.ql"],
|
||||
2,
|
||||
options,
|
||||
);
|
||||
if (sourceResults) {
|
||||
await onResults(sourceResults);
|
||||
}
|
||||
|
||||
const neutralResults = await getModeledMethodsFromFlow(
|
||||
"neutral",
|
||||
queriesByBasename["CaptureNeutralModels.ql"],
|
||||
3,
|
||||
options,
|
||||
);
|
||||
if (neutralResults) {
|
||||
await onResults(neutralResults);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ExtensionContext, window } from "vscode";
|
||||
import { DisposableObject } from "../../common/disposable-object";
|
||||
import { MethodModelingViewProvider } from "./method-modeling-view-provider";
|
||||
|
||||
export class MethodModelingPanel extends DisposableObject {
|
||||
constructor(context: ExtensionContext) {
|
||||
super();
|
||||
|
||||
const provider = new MethodModelingViewProvider(context);
|
||||
this.push(
|
||||
window.registerWebviewViewProvider(
|
||||
MethodModelingViewProvider.viewType,
|
||||
provider,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as vscode from "vscode";
|
||||
import { WebviewViewProvider } from "vscode";
|
||||
import { getHtmlForWebview } from "../../common/vscode/webview-html";
|
||||
import { FromMethodModelingMessage } from "../../common/interface-types";
|
||||
import { telemetryListener } from "../../common/vscode/telemetry";
|
||||
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
|
||||
import { extLogger } from "../../common/logging/vscode/loggers";
|
||||
import { redactableError } from "../../common/errors";
|
||||
|
||||
export class MethodModelingViewProvider implements WebviewViewProvider {
|
||||
public static readonly viewType = "codeQLMethodModeling";
|
||||
|
||||
constructor(private readonly context: vscode.ExtensionContext) {}
|
||||
|
||||
/**
|
||||
* This is called when a view first becomes visible. This may happen when the view is
|
||||
* first loaded or when the user hides and then shows a view again.
|
||||
*/
|
||||
public resolveWebviewView(
|
||||
webviewView: vscode.WebviewView,
|
||||
_context: vscode.WebviewViewResolveContext,
|
||||
_token: vscode.CancellationToken,
|
||||
) {
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [this.context.extensionUri],
|
||||
};
|
||||
|
||||
const html = getHtmlForWebview(
|
||||
this.context,
|
||||
webviewView.webview,
|
||||
"method-modeling",
|
||||
{
|
||||
allowInlineStyles: true,
|
||||
allowWasmEval: false,
|
||||
},
|
||||
);
|
||||
|
||||
webviewView.webview.html = html;
|
||||
|
||||
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
|
||||
}
|
||||
|
||||
private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case "telemetry": {
|
||||
telemetryListener?.sendUIInteraction(msg.action);
|
||||
break;
|
||||
}
|
||||
case "unhandledError":
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError(
|
||||
msg.error,
|
||||
)`Unhandled error in method modeling view: ${msg.error.message}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@ import { relative } from "path";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../shared/hide-modeled-apis";
|
||||
|
||||
export class ModelDetailsDataProvider
|
||||
export class MethodsUsageDataProvider
|
||||
extends DisposableObject
|
||||
implements TreeDataProvider<ModelDetailsTreeViewItem>
|
||||
implements TreeDataProvider<MethodsUsageTreeViewItem>
|
||||
{
|
||||
private externalApiUsages: ExternalApiUsage[] = [];
|
||||
private databaseItem: DatabaseItem | undefined = undefined;
|
||||
@@ -63,7 +63,7 @@ export class ModelDetailsDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItem(item: ModelDetailsTreeViewItem): TreeItem {
|
||||
getTreeItem(item: MethodsUsageTreeViewItem): TreeItem {
|
||||
if (isExternalApiUsage(item)) {
|
||||
return {
|
||||
label: `${item.packageName}.${item.typeName}.${item.methodName}${item.methodParameters}`,
|
||||
@@ -79,7 +79,7 @@ export class ModelDetailsDataProvider
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
command: {
|
||||
title: "Show usage",
|
||||
command: "codeQLDataExtensionsEditor.jumpToUsageLocation",
|
||||
command: "codeQLModelEditor.jumpToUsageLocation",
|
||||
arguments: [item, this.databaseItem],
|
||||
},
|
||||
iconPath: new ThemeIcon("error", new ThemeColor("errorForeground")),
|
||||
@@ -96,7 +96,7 @@ export class ModelDetailsDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(item?: ModelDetailsTreeViewItem): ModelDetailsTreeViewItem[] {
|
||||
getChildren(item?: MethodsUsageTreeViewItem): MethodsUsageTreeViewItem[] {
|
||||
if (item === undefined) {
|
||||
if (this.hideModeledApis) {
|
||||
return this.externalApiUsages.filter((api) => !api.supported);
|
||||
@@ -111,8 +111,8 @@ export class ModelDetailsDataProvider
|
||||
}
|
||||
|
||||
getParent(
|
||||
item: ModelDetailsTreeViewItem,
|
||||
): ModelDetailsTreeViewItem | undefined {
|
||||
item: MethodsUsageTreeViewItem,
|
||||
): MethodsUsageTreeViewItem | undefined {
|
||||
if (isExternalApiUsage(item)) {
|
||||
return undefined;
|
||||
} else {
|
||||
@@ -132,10 +132,10 @@ export class ModelDetailsDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelDetailsTreeViewItem = ExternalApiUsage | Usage;
|
||||
export type MethodsUsageTreeViewItem = ExternalApiUsage | Usage;
|
||||
|
||||
function isExternalApiUsage(
|
||||
item: ModelDetailsTreeViewItem,
|
||||
item: MethodsUsageTreeViewItem,
|
||||
): item is ExternalApiUsage {
|
||||
return (item as any).usages !== undefined;
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
import { TreeView, window } from "vscode";
|
||||
import { DisposableObject } from "../../common/disposable-object";
|
||||
import {
|
||||
ModelDetailsDataProvider,
|
||||
ModelDetailsTreeViewItem,
|
||||
} from "./model-details-data-provider";
|
||||
MethodsUsageDataProvider,
|
||||
MethodsUsageTreeViewItem,
|
||||
} from "./methods-usage-data-provider";
|
||||
import { ExternalApiUsage, Usage } from "../external-api-usage";
|
||||
import { DatabaseItem } from "../../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../../codeql-cli/cli";
|
||||
|
||||
export class ModelDetailsPanel extends DisposableObject {
|
||||
private readonly dataProvider: ModelDetailsDataProvider;
|
||||
private readonly treeView: TreeView<ModelDetailsTreeViewItem>;
|
||||
export class MethodsUsagePanel extends DisposableObject {
|
||||
private readonly dataProvider: MethodsUsageDataProvider;
|
||||
private readonly treeView: TreeView<MethodsUsageTreeViewItem>;
|
||||
|
||||
public constructor(cliServer: CodeQLCliServer) {
|
||||
super();
|
||||
|
||||
this.dataProvider = new ModelDetailsDataProvider(cliServer);
|
||||
this.dataProvider = new MethodsUsageDataProvider(cliServer);
|
||||
|
||||
this.treeView = window.createTreeView("codeQLModelDetails", {
|
||||
this.treeView = window.createTreeView("codeQLMethodsUsage", {
|
||||
treeDataProvider: this.dataProvider,
|
||||
});
|
||||
this.push(this.treeView);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
|
||||
import { DataExtensionsEditorCommands } from "../common/commands";
|
||||
import { ModelEditorView } from "./model-editor-view";
|
||||
import { ModelEditorCommands } from "../common/commands";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
@@ -9,31 +9,25 @@ import { join } from "path";
|
||||
import { App } from "../common/app";
|
||||
import { withProgress } from "../common/vscode/progress";
|
||||
import { pickExtensionPack } from "./extension-pack-picker";
|
||||
import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "../common/logging";
|
||||
import { showAndLogErrorMessage } from "../common/logging";
|
||||
import { dir } from "tmp-promise";
|
||||
import { fetchExternalApiQueries } from "./queries";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
|
||||
import { isQueryLanguage } from "../common/query-language";
|
||||
import { setUpPack } from "./external-api-usage-query";
|
||||
import { DisposableObject } from "../common/disposable-object";
|
||||
import { ModelDetailsPanel } from "./model-details/model-details-panel";
|
||||
import { MethodsUsagePanel } from "./methods-usage/methods-usage-panel";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||
import { Usage } from "./external-api-usage";
|
||||
import { setUpPack } from "./model-editor-queries";
|
||||
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
|
||||
|
||||
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
|
||||
|
||||
export class DataExtensionsEditorModule extends DisposableObject {
|
||||
export class ModelEditorModule extends DisposableObject {
|
||||
private readonly queryStorageDir: string;
|
||||
private readonly modelDetailsPanel: ModelDetailsPanel;
|
||||
private readonly methodsUsagePanel: MethodsUsagePanel;
|
||||
|
||||
private mostRecentlyActiveView: DataExtensionsEditorView | undefined =
|
||||
undefined;
|
||||
private mostRecentlyActiveView: ModelEditorView | undefined = undefined;
|
||||
|
||||
private constructor(
|
||||
private readonly ctx: ExtensionContext,
|
||||
@@ -44,24 +38,22 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
baseQueryStorageDir: string,
|
||||
) {
|
||||
super();
|
||||
this.queryStorageDir = join(
|
||||
baseQueryStorageDir,
|
||||
"data-extensions-editor-results",
|
||||
);
|
||||
this.modelDetailsPanel = this.push(new ModelDetailsPanel(cliServer));
|
||||
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
|
||||
this.methodsUsagePanel = this.push(new MethodsUsagePanel(cliServer));
|
||||
this.push(new MethodModelingPanel(ctx));
|
||||
}
|
||||
|
||||
private handleViewBecameActive(view: DataExtensionsEditorView): void {
|
||||
private handleViewBecameActive(view: ModelEditorView): void {
|
||||
this.mostRecentlyActiveView = view;
|
||||
}
|
||||
|
||||
private handleViewWasDisposed(view: DataExtensionsEditorView): void {
|
||||
private handleViewWasDisposed(view: ModelEditorView): void {
|
||||
if (this.mostRecentlyActiveView === view) {
|
||||
this.mostRecentlyActiveView = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private isMostRecentlyActiveView(view: DataExtensionsEditorView): boolean {
|
||||
private isMostRecentlyActiveView(view: ModelEditorView): boolean {
|
||||
return this.mostRecentlyActiveView === view;
|
||||
}
|
||||
|
||||
@@ -72,8 +64,8 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
cliServer: CodeQLCliServer,
|
||||
queryRunner: QueryRunner,
|
||||
queryStorageDir: string,
|
||||
): Promise<DataExtensionsEditorModule> {
|
||||
const dataExtensionsEditorModule = new DataExtensionsEditorModule(
|
||||
): Promise<ModelEditorModule> {
|
||||
const modelEditorModule = new ModelEditorModule(
|
||||
ctx,
|
||||
app,
|
||||
databaseManager,
|
||||
@@ -82,13 +74,13 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
queryStorageDir,
|
||||
);
|
||||
|
||||
await dataExtensionsEditorModule.initialize();
|
||||
return dataExtensionsEditorModule;
|
||||
await modelEditorModule.initialize();
|
||||
return modelEditorModule;
|
||||
}
|
||||
|
||||
public getCommands(): DataExtensionsEditorCommands {
|
||||
public getCommands(): ModelEditorCommands {
|
||||
return {
|
||||
"codeQL.openDataExtensionsEditor": async () => {
|
||||
"codeQL.openModelEditor": async () => {
|
||||
const db = this.databaseManager.currentDatabaseItem;
|
||||
if (!db) {
|
||||
void showAndLogErrorMessage(this.app.logger, "No database selected");
|
||||
@@ -102,7 +94,7 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`The data extensions editor is not supported for ${language} databases.`,
|
||||
`The CodeQL Model Editor is not supported for ${language} databases.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -138,22 +130,14 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = fetchExternalApiQueries[language];
|
||||
if (!query) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
extLogger,
|
||||
telemetryListener,
|
||||
redactableError`No external API usage query found for language ${language}`,
|
||||
);
|
||||
// Create new temporary directory for query files and pack dependencies
|
||||
const queryDir = (await dir({ unsafeCleanup: true })).path;
|
||||
const success = await setUpPack(this.cliServer, queryDir, language);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new temporary directory for query files and pack dependencies
|
||||
const queryDir = (await dir({ unsafeCleanup: true })).path;
|
||||
await setUpPack(queryDir, query, language);
|
||||
await this.cliServer.packInstall(queryDir);
|
||||
|
||||
const view = new DataExtensionsEditorView(
|
||||
const view = new ModelEditorView(
|
||||
this.ctx,
|
||||
this.app,
|
||||
this.databaseManager,
|
||||
@@ -164,8 +148,8 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
db,
|
||||
modelFile,
|
||||
Mode.Application,
|
||||
this.modelDetailsPanel.setState.bind(this.modelDetailsPanel),
|
||||
this.modelDetailsPanel.revealItem.bind(this.modelDetailsPanel),
|
||||
this.methodsUsagePanel.setState.bind(this.methodsUsagePanel),
|
||||
this.methodsUsagePanel.revealItem.bind(this.methodsUsagePanel),
|
||||
this.handleViewBecameActive.bind(this),
|
||||
this.handleViewWasDisposed.bind(this),
|
||||
this.isMostRecentlyActiveView.bind(this),
|
||||
@@ -173,11 +157,11 @@ export class DataExtensionsEditorModule extends DisposableObject {
|
||||
await view.openView();
|
||||
},
|
||||
{
|
||||
title: "Opening Data Extensions Editor",
|
||||
title: "Opening CodeQL Model Editor",
|
||||
},
|
||||
);
|
||||
},
|
||||
"codeQLDataExtensionsEditor.jumpToUsageLocation": async (
|
||||
"codeQLModelEditor.jumpToUsageLocation": async (
|
||||
usage: Usage,
|
||||
databaseItem: DatabaseItem,
|
||||
) => {
|
||||
@@ -0,0 +1,46 @@
|
||||
import { join } from "path";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump } from "js-yaml";
|
||||
import { prepareExternalApiQuery } from "./external-api-usage-queries";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
|
||||
/**
|
||||
* setUpPack sets up a directory to use for the data extension editor queries.
|
||||
* @param queryDir The directory to set up.
|
||||
* @param language The language to use for the queries.
|
||||
* @returns true if the setup was successful, false otherwise.
|
||||
*/
|
||||
export async function setUpPack(
|
||||
cliServer: CodeQLCliServer,
|
||||
queryDir: string,
|
||||
language: QueryLanguage,
|
||||
): Promise<boolean> {
|
||||
// Create the external API query
|
||||
const externalApiQuerySuccess = await prepareExternalApiQuery(
|
||||
queryDir,
|
||||
language,
|
||||
);
|
||||
if (!externalApiQuerySuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up a synthetic pack so that the query can be resolved later.
|
||||
const syntheticQueryPack = {
|
||||
name: "codeql/external-api-usage",
|
||||
version: "0.0.0",
|
||||
dependencies: {
|
||||
[`codeql/${language}-all`]: "*",
|
||||
},
|
||||
};
|
||||
|
||||
const qlpackFile = join(queryDir, "codeql-pack.yml");
|
||||
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
|
||||
await cliServer.packInstall(queryDir);
|
||||
|
||||
// Install the other needed query packs
|
||||
await cliServer.packDownload([`codeql/${language}-queries`]);
|
||||
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
WebviewPanelConfig,
|
||||
} from "../common/vscode/abstract-webview";
|
||||
import {
|
||||
FromDataExtensionsEditorMessage,
|
||||
ToDataExtensionsEditorMessage,
|
||||
FromModelEditorMessage,
|
||||
ToModelEditorMessage,
|
||||
} from "../common/interface-types";
|
||||
import { ProgressCallback, withProgress } from "../common/vscode/progress";
|
||||
import { QueryRunner } from "../query-server";
|
||||
@@ -22,17 +22,16 @@ import {
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
|
||||
import { generateFlowModel } from "./generate-flow-model";
|
||||
import { runFlowModelQueries } from "./flow-model-queries";
|
||||
import { promptImportGithubDatabase } from "../databases/database-fetcher";
|
||||
import { App } from "../common/app";
|
||||
import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||
import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { readQueryResults, runQuery } from "./external-api-usage-query";
|
||||
import { runExternalApiQueries } from "./external-api-usage-queries";
|
||||
import { ExternalApiUsage, Usage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
import { enableFrameworkMode, showLlmGeneration } from "../config";
|
||||
import { showLlmGeneration } from "../config";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
||||
import { join } from "path";
|
||||
@@ -41,9 +40,9 @@ import { getLanguageDisplayName } from "../common/query-language";
|
||||
import { AutoModeler } from "./auto-modeler";
|
||||
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "./shared/hide-modeled-apis";
|
||||
|
||||
export class DataExtensionsEditorView extends AbstractWebview<
|
||||
ToDataExtensionsEditorMessage,
|
||||
FromDataExtensionsEditorMessage
|
||||
export class ModelEditorView extends AbstractWebview<
|
||||
ToModelEditorMessage,
|
||||
FromModelEditorMessage
|
||||
> {
|
||||
private readonly autoModeler: AutoModeler;
|
||||
|
||||
@@ -61,20 +60,16 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
private readonly databaseItem: DatabaseItem,
|
||||
private readonly extensionPack: ExtensionPack,
|
||||
private mode: Mode,
|
||||
private readonly updateModelDetailsPanelState: (
|
||||
private readonly updateMethodsUsagePanelState: (
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
databaseItem: DatabaseItem,
|
||||
hideModeledApis: boolean,
|
||||
) => Promise<void>,
|
||||
private readonly revealItemInDetailsPanel: (usage: Usage) => Promise<void>,
|
||||
private readonly handleViewBecameActive: (
|
||||
view: DataExtensionsEditorView,
|
||||
) => void,
|
||||
private readonly handleViewWasDisposed: (
|
||||
view: DataExtensionsEditorView,
|
||||
) => void,
|
||||
private readonly revealItemInUsagePanel: (usage: Usage) => Promise<void>,
|
||||
private readonly handleViewBecameActive: (view: ModelEditorView) => void,
|
||||
private readonly handleViewWasDisposed: (view: ModelEditorView) => void,
|
||||
private readonly isMostRecentlyActiveView: (
|
||||
view: DataExtensionsEditorView,
|
||||
view: ModelEditorView,
|
||||
) => boolean,
|
||||
) {
|
||||
super(ctx);
|
||||
@@ -107,7 +102,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
panel.onDidChangeViewState(async () => {
|
||||
if (panel.active) {
|
||||
this.handleViewBecameActive(this);
|
||||
await this.updateModelDetailsPanelState(
|
||||
await this.updateMethodsUsagePanelState(
|
||||
this.externalApiUsages,
|
||||
this.databaseItem,
|
||||
this.hideModeledApis,
|
||||
@@ -121,8 +116,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
// so we want to check if there are any others still open.
|
||||
void this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.dataExtensionsEditorOpen",
|
||||
this.isADataExtensionsEditorOpen(),
|
||||
"codeql.modelEditorOpen",
|
||||
this.isAModelEditorOpen(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -130,31 +125,31 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
|
||||
void this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.dataExtensionsEditorOpen",
|
||||
"codeql.modelEditorOpen",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
private isADataExtensionsEditorOpen(): boolean {
|
||||
private isAModelEditorOpen(): boolean {
|
||||
return window.tabGroups.all.some((tabGroup) =>
|
||||
tabGroup.tabs.some((tab) => {
|
||||
const viewType: string | undefined = (tab.input as any)?.viewType;
|
||||
// The viewType has a prefix, such as "mainThreadWebview-", but if the
|
||||
// suffix matches that should be enough to identify the view.
|
||||
return viewType && viewType.endsWith("data-extensions-editor");
|
||||
return viewType && viewType.endsWith("model-editor");
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||
return {
|
||||
viewId: "data-extensions-editor",
|
||||
viewId: "model-editor",
|
||||
title: `Modeling ${getLanguageDisplayName(
|
||||
this.extensionPack.language,
|
||||
)} (${this.extensionPack.name})`,
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: "data-extensions-editor",
|
||||
view: "model-editor",
|
||||
iconPath: {
|
||||
dark: Uri.file(
|
||||
join(this.ctx.extensionPath, "media/dark/symbol-misc.svg"),
|
||||
@@ -170,9 +165,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
protected async onMessage(
|
||||
msg: FromDataExtensionsEditorMessage,
|
||||
): Promise<void> {
|
||||
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case "viewLoaded":
|
||||
await this.onWebViewLoaded();
|
||||
@@ -239,7 +232,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
break;
|
||||
case "hideModeledApis":
|
||||
this.hideModeledApis = msg.hideModeledApis;
|
||||
await this.updateModelDetailsPanelState(
|
||||
await this.updateMethodsUsagePanelState(
|
||||
this.externalApiUsages,
|
||||
this.databaseItem,
|
||||
this.hideModeledApis,
|
||||
@@ -265,10 +258,9 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
this.databaseItem.language === "java" && showLlmGeneration();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setDataExtensionEditorViewState",
|
||||
t: "setModelEditorViewState",
|
||||
viewState: {
|
||||
extensionPack: this.extensionPack,
|
||||
enableFrameworkMode: enableFrameworkMode(),
|
||||
showLlmButton,
|
||||
mode: this.mode,
|
||||
},
|
||||
@@ -276,7 +268,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
}
|
||||
|
||||
protected async handleJumpToUsage(usage: Usage) {
|
||||
await this.revealItemInDetailsPanel(usage);
|
||||
await this.revealItemInUsagePanel(usage);
|
||||
await showResolvableLocation(usage.url, this.databaseItem, this.app.logger);
|
||||
}
|
||||
|
||||
@@ -304,7 +296,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
async (progress) => {
|
||||
try {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
const queryResult = await runQuery(this.mode, {
|
||||
const queryResult = await runExternalApiQueries(this.mode, {
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
databaseItem: this.databaseItem,
|
||||
@@ -316,35 +308,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
if (!queryResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Decoding results",
|
||||
step: 1100,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
const bqrsChunk = await readQueryResults({
|
||||
cliServer: this.cliServer,
|
||||
bqrsPath: queryResult.outputDir.bqrsPath,
|
||||
});
|
||||
if (!bqrsChunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Finalizing results",
|
||||
step: 1450,
|
||||
maxStep: 1500,
|
||||
});
|
||||
|
||||
this.externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk);
|
||||
this.externalApiUsages = queryResult;
|
||||
|
||||
await this.postMessage({
|
||||
t: "setExternalApiUsages",
|
||||
externalApiUsages: this.externalApiUsages,
|
||||
});
|
||||
if (this.isMostRecentlyActiveView(this)) {
|
||||
await this.updateModelDetailsPanelState(
|
||||
await this.updateMethodsUsagePanelState(
|
||||
this.externalApiUsages,
|
||||
this.databaseItem,
|
||||
this.hideModeledApis,
|
||||
@@ -389,7 +360,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
});
|
||||
|
||||
try {
|
||||
await generateFlowModel({
|
||||
await runFlowModelQueries({
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
@@ -456,7 +427,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
const view = new DataExtensionsEditorView(
|
||||
const view = new ModelEditorView(
|
||||
this.ctx,
|
||||
this.app,
|
||||
this.databaseManager,
|
||||
@@ -467,8 +438,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
addedDatabase,
|
||||
modelFile,
|
||||
Mode.Framework,
|
||||
this.updateModelDetailsPanelState,
|
||||
this.revealItemInDetailsPanel,
|
||||
this.updateMethodsUsagePanelState,
|
||||
this.revealItemInUsagePanel,
|
||||
this.handleViewBecameActive,
|
||||
this.handleViewWasDisposed,
|
||||
this.isMostRecentlyActiveView,
|
||||
@@ -147,7 +147,12 @@ class CallableMethod extends Callable {
|
||||
|
||||
/** Holds if this API is a known neutral. */
|
||||
pragma[nomagic]
|
||||
predicate isNeutral() { this = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() }
|
||||
predicate isNeutral() {
|
||||
exists(string namespace, string type, string name, string signature, string kind, string provenance |
|
||||
neutralModel(namespace, type, name, signature, kind, provenance) and
|
||||
this = interpretElement(namespace, type, false, name, signature, "")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ExtensionPack } from "./extension-pack";
|
||||
import { Mode } from "./mode";
|
||||
|
||||
export interface DataExtensionEditorViewState {
|
||||
export interface ModelEditorViewState {
|
||||
extensionPack: ExtensionPack;
|
||||
enableFrameworkMode: boolean;
|
||||
showLlmButton: boolean;
|
||||
mode: Mode;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface VariantAnalysisDto {
|
||||
filePath: string;
|
||||
language: QueryLanguageDto;
|
||||
text: string;
|
||||
kind?: string;
|
||||
};
|
||||
databases: {
|
||||
repositories?: string[];
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodModeling as MethodModelingComponent } from "../../view/method-modeling/MethodModeling";
|
||||
export default {
|
||||
title: "Method Modeling/Method Modeling",
|
||||
component: MethodModelingComponent,
|
||||
} as Meta<typeof MethodModelingComponent>;
|
||||
|
||||
const Template: StoryFn<typeof MethodModelingComponent> = (args) => (
|
||||
<MethodModelingComponent {...args} />
|
||||
);
|
||||
|
||||
export const MethodUnmodeled = Template.bind({});
|
||||
MethodUnmodeled.args = { modelingStatus: "unmodeled" };
|
||||
|
||||
export const MethodModeled = Template.bind({});
|
||||
MethodModeled.args = { modelingStatus: "unsaved" };
|
||||
|
||||
export const MethodSaved = Template.bind({});
|
||||
MethodSaved.args = { modelingStatus: "saved" };
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { ExternalApiUsageName as ExternalApiUsageNameComponent } from "../../view/model-editor/ExternalApiUsageName";
|
||||
import { createExternalApiUsage } from "../../../test/factories/data-extension/external-api-factories";
|
||||
|
||||
export default {
|
||||
title: "CodeQL Model Editor/External API Usage Name",
|
||||
component: ExternalApiUsageNameComponent,
|
||||
} as Meta<typeof ExternalApiUsageNameComponent>;
|
||||
|
||||
const Template: StoryFn<typeof ExternalApiUsageNameComponent> = (args) => (
|
||||
<ExternalApiUsageNameComponent {...args} />
|
||||
);
|
||||
|
||||
export const ExternalApiUsageName = Template.bind({});
|
||||
ExternalApiUsageName.args = createExternalApiUsage();
|
||||
@@ -2,11 +2,11 @@ import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodRow as MethodRowComponent } from "../../view/data-extensions-editor/MethodRow";
|
||||
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
|
||||
import { MethodRow as MethodRowComponent } from "../../view/model-editor/MethodRow";
|
||||
import { CallClassification } from "../../model-editor/external-api-usage";
|
||||
|
||||
export default {
|
||||
title: "Data Extensions Editor/Method Row",
|
||||
title: "CodeQL Model Editor/Method Row",
|
||||
component: MethodRowComponent,
|
||||
} as Meta<typeof MethodRowComponent>;
|
||||
|
||||
@@ -2,21 +2,21 @@ import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { DataExtensionsEditor as DataExtensionsEditorComponent } from "../../view/data-extensions-editor/DataExtensionsEditor";
|
||||
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import { ModelEditor as ModelEditorComponent } from "../../view/model-editor/ModelEditor";
|
||||
import { CallClassification } from "../../model-editor/external-api-usage";
|
||||
|
||||
export default {
|
||||
title: "Data Extensions Editor/Data Extensions Editor",
|
||||
component: DataExtensionsEditorComponent,
|
||||
} as Meta<typeof DataExtensionsEditorComponent>;
|
||||
title: "CodeQL Model Editor/CodeQL Model Editor",
|
||||
component: ModelEditorComponent,
|
||||
} as Meta<typeof ModelEditorComponent>;
|
||||
|
||||
const Template: StoryFn<typeof DataExtensionsEditorComponent> = (args) => (
|
||||
<DataExtensionsEditorComponent {...args} />
|
||||
const Template: StoryFn<typeof ModelEditorComponent> = (args) => (
|
||||
<ModelEditorComponent {...args} />
|
||||
);
|
||||
|
||||
export const DataExtensionsEditor = Template.bind({});
|
||||
DataExtensionsEditor.args = {
|
||||
export const ModelEditor = Template.bind({});
|
||||
ModelEditor.args = {
|
||||
initialViewState: {
|
||||
extensionPack: {
|
||||
path: "/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o",
|
||||
@@ -28,7 +28,6 @@ DataExtensionsEditor.args = {
|
||||
extensionTargets: {},
|
||||
dataExtensions: [],
|
||||
},
|
||||
enableFrameworkMode: true,
|
||||
showLlmButton: true,
|
||||
mode: Mode.Application,
|
||||
},
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesResultFormat as RepositoriesResultFormatComponent } from "../../view/variant-analysis/RepositoriesResultFormat";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Result Format",
|
||||
component: RepositoriesResultFormatComponent,
|
||||
argTypes: {
|
||||
value: {
|
||||
control: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta<typeof RepositoriesResultFormatComponent>;
|
||||
|
||||
export const RepositoriesResultFormat = () => {
|
||||
const [value, setValue] = useState(ResultFormat.Alerts);
|
||||
|
||||
return (
|
||||
<RepositoriesResultFormatComponent value={value} onChange={setValue} />
|
||||
);
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from "../../view/variant-analysis/RepositoriesSearchSortRow";
|
||||
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Search and Sort Row",
|
||||
@@ -19,9 +20,23 @@ export default {
|
||||
} as Meta<typeof RepositoriesSearchSortRowComponent>;
|
||||
|
||||
export const RepositoriesSearchSortRow = () => {
|
||||
const [value, setValue] = useState(defaultFilterSortState);
|
||||
const [filterSortValue, setFilterSortValue] = useState(
|
||||
defaultFilterSortState,
|
||||
);
|
||||
|
||||
const [resultFormatValue, setResultFormatValue] = useState(
|
||||
ResultFormat.Alerts,
|
||||
);
|
||||
|
||||
const variantAnalysisQueryKind = "problem";
|
||||
|
||||
return (
|
||||
<RepositoriesSearchSortRowComponent value={value} onChange={setValue} />
|
||||
<RepositoriesSearchSortRowComponent
|
||||
filterSortValue={filterSortValue}
|
||||
resultFormatValue={resultFormatValue}
|
||||
onFilterSortChange={setFilterSortValue}
|
||||
onResultFormatChange={setResultFormatValue}
|
||||
variantAnalysisQueryKind={variantAnalysisQueryKind}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum ResultFormat {
|
||||
Alerts = "Alerts",
|
||||
RawResults = "Raw results",
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -245,6 +245,7 @@ export class VariantAnalysisManager
|
||||
pack: base64Pack,
|
||||
language: variantAnalysisLanguage,
|
||||
text: queryText,
|
||||
kind: queryMetadata?.kind,
|
||||
},
|
||||
databases: {
|
||||
repositories: repoSelection.repositories,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
import { styled } from "styled-components";
|
||||
import {
|
||||
ModelingStatus,
|
||||
ModelingStatusIndicator,
|
||||
} from "../model-editor/ModelingStatusIndicator";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import { ExternalApiUsageName } from "../model-editor/ExternalApiUsageName";
|
||||
|
||||
const Container = styled.div`
|
||||
background-color: var(--vscode-peekViewResult-background);
|
||||
padding: 0.3rem;
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
padding-bottom: 0.3rem;
|
||||
font-size: 1.2em;
|
||||
`;
|
||||
|
||||
const DependencyContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export type MethodModelingProps = {
|
||||
modelingStatus: ModelingStatus;
|
||||
externalApiUsage: ExternalApiUsage;
|
||||
};
|
||||
|
||||
export const MethodModeling = ({
|
||||
modelingStatus,
|
||||
externalApiUsage,
|
||||
}: MethodModelingProps): JSX.Element => {
|
||||
return (
|
||||
<Container>
|
||||
<Title>API or Method</Title>
|
||||
<DependencyContainer>
|
||||
<ExternalApiUsageName {...externalApiUsage} />
|
||||
<ModelingStatusIndicator status={modelingStatus} />
|
||||
</DependencyContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { MethodModeling } from "./MethodModeling";
|
||||
import { ModelingStatus } from "../model-editor/ModelingStatusIndicator";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
|
||||
export function MethodModelingView(): JSX.Element {
|
||||
useEffect(() => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.origin === window.origin) {
|
||||
// Nothing to do yet.
|
||||
} 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);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const modelingStatus: ModelingStatus = "saved";
|
||||
const externalApiUsage: ExternalApiUsage = {
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
supported: true,
|
||||
supportedType: "summary",
|
||||
usages: [],
|
||||
};
|
||||
return (
|
||||
<MethodModeling
|
||||
modelingStatus={modelingStatus}
|
||||
externalApiUsage={externalApiUsage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { MethodModeling, MethodModelingProps } from "../MethodModeling";
|
||||
import { createExternalApiUsage } from "../../../../test/factories/data-extension/external-api-factories";
|
||||
|
||||
describe(MethodModeling.name, () => {
|
||||
const render = (props: MethodModelingProps) =>
|
||||
reactRender(<MethodModeling {...props} />);
|
||||
|
||||
it("renders method modeling panel", () => {
|
||||
render({
|
||||
modelingStatus: "saved",
|
||||
externalApiUsage: createExternalApiUsage(),
|
||||
});
|
||||
|
||||
expect(screen.getByText("API or Method")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { WebviewDefinition } from "../webview-definition";
|
||||
import { DataExtensionsEditor } from "./DataExtensionsEditor";
|
||||
import { MethodModelingView } from "./MethodModelingView";
|
||||
|
||||
const definition: WebviewDefinition = {
|
||||
component: <DataExtensionsEditor />,
|
||||
component: <MethodModelingView />,
|
||||
};
|
||||
|
||||
export default definition;
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { styled } from "styled-components";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
|
||||
const Name = styled.span`
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
`;
|
||||
|
||||
export const ExternalApiUsageName = (
|
||||
externalApiUsage: ExternalApiUsage,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Name>
|
||||
{externalApiUsage.packageName && <>{externalApiUsage.packageName}.</>}
|
||||
{externalApiUsage.typeName}.{externalApiUsage.methodName}
|
||||
{externalApiUsage.methodParameters}
|
||||
</Name>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo } from "react";
|
||||
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||
import type { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
|
||||
type Props = {
|
||||
@@ -1,20 +1,20 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { styled } from "styled-components";
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { ModeledMethodDataGrid } from "./ModeledMethodDataGrid";
|
||||
import { calculateModeledPercentage } from "../../data-extensions-editor/shared/modeled-percentage";
|
||||
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
|
||||
import { percentFormatter } from "./formatters";
|
||||
import { Codicon } from "../common";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeDivider,
|
||||
VSCodeTag,
|
||||
} from "@vscode/webview-ui-toolkit/react";
|
||||
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
|
||||
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
|
||||
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
|
||||
const LibraryContainer = styled.div`
|
||||
background-color: var(--vscode-peekViewResult-background);
|
||||
@@ -74,7 +74,7 @@ type Props = {
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
inProgressMethods: InProgressMethods;
|
||||
viewState: DataExtensionEditorViewState;
|
||||
viewState: ModelEditorViewState;
|
||||
hideModeledApis: boolean;
|
||||
onChange: (
|
||||
modelName: string,
|
||||
@@ -221,13 +221,12 @@ export const LibraryRow = ({
|
||||
Model from source
|
||||
</VSCodeButton>
|
||||
)}
|
||||
{viewState.enableFrameworkMode &&
|
||||
viewState.mode === Mode.Application && (
|
||||
<VSCodeButton appearance="icon" onClick={handleModelDependency}>
|
||||
<Codicon name="references" label="Model dependency" />
|
||||
Model dependency
|
||||
</VSCodeButton>
|
||||
)}
|
||||
{viewState.mode === Mode.Application && (
|
||||
<VSCodeButton appearance="icon" onClick={handleModelDependency}>
|
||||
<Codicon name="references" label="Model dependency" />
|
||||
Model dependency
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</TitleContainer>
|
||||
{isExpanded && (
|
||||
<>
|
||||
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
||||
import {
|
||||
CallClassification,
|
||||
ExternalApiUsage,
|
||||
} from "../../data-extensions-editor/external-api-usage";
|
||||
} from "../../model-editor/external-api-usage";
|
||||
import { VSCodeTag } from "@vscode/webview-ui-toolkit/react";
|
||||
import { styled } from "styled-components";
|
||||
|
||||
@@ -9,15 +9,15 @@ import { ChangeEvent, useCallback, useMemo } from "react";
|
||||
import { styled } from "styled-components";
|
||||
import { vscode } from "../vscode-api";
|
||||
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import {
|
||||
ModeledMethod,
|
||||
ModeledMethodType,
|
||||
Provenance,
|
||||
} from "../../data-extensions-editor/modeled-method";
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { KindInput } from "./KindInput";
|
||||
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { extensiblePredicateDefinitions } from "../../model-editor/predicates";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import { MethodClassifications } from "./MethodClassifications";
|
||||
import {
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ModelingStatusIndicator,
|
||||
} from "./ModelingStatusIndicator";
|
||||
import { InProgressDropdown } from "./InProgressDropdown";
|
||||
import { ExternalApiUsageName } from "./ExternalApiUsageName";
|
||||
|
||||
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
|
||||
display: flex;
|
||||
@@ -218,7 +219,7 @@ function ModelableMethodRow(props: Props) {
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<ModelingStatusIndicator status={modelingStatus} />
|
||||
<MethodClassifications externalApiUsage={externalApiUsage} />
|
||||
<ExternalApiUsageName {...props} />
|
||||
<ExternalApiUsageName {...props.externalApiUsage} />
|
||||
{mode === Mode.Application && (
|
||||
<UsagesButton onClick={jumpToUsage}>
|
||||
{externalApiUsage.usages.length}
|
||||
@@ -294,7 +295,7 @@ function UnmodelableMethodRow(props: Props) {
|
||||
<VSCodeDataGridRow>
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<ModelingStatusIndicator status="saved" />
|
||||
<ExternalApiUsageName {...props} />
|
||||
<ExternalApiUsageName {...props.externalApiUsage} />
|
||||
{mode === Mode.Application && (
|
||||
<UsagesButton onClick={jumpToUsage}>
|
||||
{externalApiUsage.usages.length}
|
||||
@@ -310,18 +311,6 @@ function UnmodelableMethodRow(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function ExternalApiUsageName(props: { externalApiUsage: ExternalApiUsage }) {
|
||||
return (
|
||||
<span>
|
||||
{props.externalApiUsage.packageName && (
|
||||
<>{props.externalApiUsage.packageName}.</>
|
||||
)}
|
||||
{props.externalApiUsage.typeName}.{props.externalApiUsage.methodName}
|
||||
{props.externalApiUsage.methodParameters}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function sendJumpToUsageMessage(externalApiUsage: ExternalApiUsage) {
|
||||
vscode.postMessage({
|
||||
t: "jumpToUsage",
|
||||
@@ -1,25 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ToDataExtensionsEditorMessage } from "../../common/interface-types";
|
||||
import { ToModelEditorMessage } from "../../common/interface-types";
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeCheckbox,
|
||||
VSCodeTag,
|
||||
} from "@vscode/webview-ui-toolkit/react";
|
||||
import { styled } from "styled-components";
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { calculateModeledPercentage } from "../../data-extensions-editor/shared/modeled-percentage";
|
||||
import { calculateModeledPercentage } from "../../model-editor/shared/modeled-percentage";
|
||||
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
|
||||
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
|
||||
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||
import { ModeledMethodsList } from "./ModeledMethodsList";
|
||||
import { percentFormatter } from "./formatters";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
import { getLanguageDisplayName } from "../../common/query-language";
|
||||
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../../data-extensions-editor/shared/hide-modeled-apis";
|
||||
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../../model-editor/shared/hide-modeled-apis";
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
text-align: center;
|
||||
@@ -28,7 +28,7 @@ const LoadingContainer = styled.div`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const DataExtensionsEditorContainer = styled.div`
|
||||
const ModelEditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
@@ -72,21 +72,21 @@ const ButtonsContainer = styled.div`
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
initialViewState?: DataExtensionEditorViewState;
|
||||
initialViewState?: ModelEditorViewState;
|
||||
initialExternalApiUsages?: ExternalApiUsage[];
|
||||
initialModeledMethods?: Record<string, ModeledMethod>;
|
||||
initialHideModeledApis?: boolean;
|
||||
};
|
||||
|
||||
export function DataExtensionsEditor({
|
||||
export function ModelEditor({
|
||||
initialViewState,
|
||||
initialExternalApiUsages = [],
|
||||
initialModeledMethods = {},
|
||||
initialHideModeledApis = INITIAL_HIDE_MODELED_APIS_VALUE,
|
||||
}: Props): JSX.Element {
|
||||
const [viewState, setViewState] = useState<
|
||||
DataExtensionEditorViewState | undefined
|
||||
>(initialViewState);
|
||||
const [viewState, setViewState] = useState<ModelEditorViewState | undefined>(
|
||||
initialViewState,
|
||||
);
|
||||
|
||||
const [externalApiUsages, setExternalApiUsages] = useState<
|
||||
ExternalApiUsage[]
|
||||
@@ -117,9 +117,9 @@ export function DataExtensionsEditor({
|
||||
useEffect(() => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToDataExtensionsEditorMessage = evt.data;
|
||||
const msg: ToModelEditorMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case "setDataExtensionEditorViewState":
|
||||
case "setModelEditorViewState":
|
||||
setViewState(msg.viewState);
|
||||
break;
|
||||
case "setExternalApiUsages":
|
||||
@@ -297,7 +297,7 @@ export function DataExtensionsEditor({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataExtensionsEditorContainer>
|
||||
<ModelEditorContainer>
|
||||
<HeaderContainer>
|
||||
<HeaderColumn>
|
||||
<HeaderRow>
|
||||
@@ -320,14 +320,12 @@ export function DataExtensionsEditor({
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
Open extension pack
|
||||
</LinkIconButton>
|
||||
{viewState.enableFrameworkMode && (
|
||||
<LinkIconButton onClick={onSwitchModeClick}>
|
||||
<span slot="start" className="codicon codicon-library"></span>
|
||||
{viewState.mode === Mode.Framework
|
||||
? "Model as application"
|
||||
: "Model as dependency"}
|
||||
</LinkIconButton>
|
||||
)}
|
||||
<LinkIconButton onClick={onSwitchModeClick}>
|
||||
<span slot="start" className="codicon codicon-library"></span>
|
||||
{viewState.mode === Mode.Framework
|
||||
? "Model as application"
|
||||
: "Model as dependency"}
|
||||
</LinkIconButton>
|
||||
</HeaderRow>
|
||||
</HeaderColumn>
|
||||
<HeaderSpacer />
|
||||
@@ -349,11 +347,9 @@ export function DataExtensionsEditor({
|
||||
>
|
||||
Save all
|
||||
</VSCodeButton>
|
||||
{viewState.enableFrameworkMode && (
|
||||
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
|
||||
Refresh
|
||||
</VSCodeButton>
|
||||
)}
|
||||
<VSCodeButton appearance="secondary" onClick={onRefreshClick}>
|
||||
Refresh
|
||||
</VSCodeButton>
|
||||
{viewState.mode === Mode.Framework && (
|
||||
<VSCodeButton onClick={onGenerateFromSourceClick}>
|
||||
Generate
|
||||
@@ -375,6 +371,6 @@ export function DataExtensionsEditor({
|
||||
onModelDependencyClick={onModelDependencyClick}
|
||||
/>
|
||||
</EditorContainer>
|
||||
</DataExtensionsEditorContainer>
|
||||
</ModelEditorContainer>
|
||||
);
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
VSCodeDataGridRow,
|
||||
} from "@vscode/webview-ui-toolkit/react";
|
||||
import { MethodRow } from "./MethodRow";
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { useMemo } from "react";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { sortMethods } from "../../data-extensions-editor/shared/sorting";
|
||||
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import { sortMethods } from "../../model-editor/shared/sorting";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
|
||||
type Props = {
|
||||
packageName: string;
|
||||
@@ -1,22 +1,22 @@
|
||||
import * as React from "react";
|
||||
import { useMemo } from "react";
|
||||
import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||
import { ExternalApiUsage } from "../../model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||
import { LibraryRow } from "./LibraryRow";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import {
|
||||
groupMethods,
|
||||
sortGroupNames,
|
||||
} from "../../data-extensions-editor/shared/sorting";
|
||||
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
|
||||
import { InProgressMethods } from "../../data-extensions-editor/shared/in-progress-methods";
|
||||
} from "../../model-editor/shared/sorting";
|
||||
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
|
||||
type Props = {
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
inProgressMethods: InProgressMethods;
|
||||
viewState: DataExtensionEditorViewState;
|
||||
viewState: ModelEditorViewState;
|
||||
hideModeledApis: boolean;
|
||||
onChange: (
|
||||
modelName: string,
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { ExternalApiUsageName } from "../ExternalApiUsageName";
|
||||
import { ExternalApiUsage } from "../../../model-editor/external-api-usage";
|
||||
import { createExternalApiUsage } from "../../../../test/factories/data-extension/external-api-factories";
|
||||
|
||||
describe(ExternalApiUsageName.name, () => {
|
||||
const render = (props: ExternalApiUsage) =>
|
||||
reactRender(<ExternalApiUsageName {...props} />);
|
||||
|
||||
it("renders method name", () => {
|
||||
const apiUsage = createExternalApiUsage();
|
||||
render(apiUsage);
|
||||
|
||||
const name = `${apiUsage.packageName}.${apiUsage.typeName}.${apiUsage.methodName}${apiUsage.methodParameters}`;
|
||||
expect(screen.getByText(name)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
9
extensions/ql-vscode/src/view/model-editor/index.tsx
Normal file
9
extensions/ql-vscode/src/view/model-editor/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { WebviewDefinition } from "../webview-definition";
|
||||
import { ModelEditor } from "./ModelEditor";
|
||||
|
||||
const definition: WebviewDefinition = {
|
||||
component: <ModelEditor />,
|
||||
};
|
||||
|
||||
export default definition;
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
VariantAnalysisScannedRepositoryDownloadStatus,
|
||||
} from "../../variant-analysis/shared/variant-analysis";
|
||||
import { Alert } from "../common";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -37,12 +38,28 @@ const RawResultsContainer = styled.div`
|
||||
margin-top: 0.5em;
|
||||
`;
|
||||
|
||||
function chooseResultFormat(
|
||||
interpretedResults: AnalysisAlert[] | undefined,
|
||||
rawResults: AnalysisRawResults | undefined,
|
||||
resultFormat: ResultFormat,
|
||||
): ResultFormat | undefined {
|
||||
if (interpretedResults && resultFormat === ResultFormat.Alerts) {
|
||||
return ResultFormat.Alerts;
|
||||
} else if (rawResults) {
|
||||
return ResultFormat.RawResults;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export type AnalyzedRepoItemContentProps = {
|
||||
status?: VariantAnalysisRepoStatus;
|
||||
downloadStatus?: VariantAnalysisScannedRepositoryDownloadStatus;
|
||||
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
rawResults?: AnalysisRawResults;
|
||||
|
||||
resultFormat: ResultFormat;
|
||||
};
|
||||
|
||||
export const AnalyzedRepoItemContent = ({
|
||||
@@ -50,7 +67,13 @@ export const AnalyzedRepoItemContent = ({
|
||||
downloadStatus,
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
resultFormat,
|
||||
}: AnalyzedRepoItemContentProps) => {
|
||||
const chosenResultFormat = chooseResultFormat(
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
resultFormat,
|
||||
);
|
||||
return (
|
||||
<ContentContainer>
|
||||
{status === VariantAnalysisRepoStatus.Failed && (
|
||||
@@ -90,7 +113,7 @@ export const AnalyzedRepoItemContent = ({
|
||||
/>
|
||||
</AlertContainer>
|
||||
)}
|
||||
{interpretedResults && (
|
||||
{interpretedResults && chosenResultFormat === ResultFormat.Alerts && (
|
||||
<InterpretedResultsContainer>
|
||||
{interpretedResults.map((r, i) => (
|
||||
<InterpretedResultItem key={i}>
|
||||
@@ -99,7 +122,7 @@ export const AnalyzedRepoItemContent = ({
|
||||
))}
|
||||
</InterpretedResultsContainer>
|
||||
)}
|
||||
{rawResults && (
|
||||
{rawResults && chosenResultFormat === ResultFormat.RawResults && (
|
||||
<RawResultsContainer>
|
||||
<RawResultsTable
|
||||
schema={rawResults.schema}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
|
||||
import StarCount from "../common/StarCount";
|
||||
import { useTelemetryOnChange } from "../common/telemetry";
|
||||
import { DeterminateProgressRing } from "../common/DeterminateProgressRing";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
// This will ensure that these icons have a className which we can use in the TitleContainer
|
||||
const ExpandCollapseCodicon = styled(Codicon)``;
|
||||
@@ -98,6 +99,8 @@ export type RepoRowProps = {
|
||||
interpretedResults?: AnalysisAlert[];
|
||||
rawResults?: AnalysisRawResults;
|
||||
|
||||
resultFormat?: ResultFormat;
|
||||
|
||||
selected?: boolean;
|
||||
onSelectedChange?: (repositoryId: number, selected: boolean) => void;
|
||||
};
|
||||
@@ -168,6 +171,7 @@ export const RepoRow = ({
|
||||
resultCount,
|
||||
interpretedResults,
|
||||
rawResults,
|
||||
resultFormat = ResultFormat.Alerts,
|
||||
selected,
|
||||
onSelectedChange,
|
||||
}: RepoRowProps) => {
|
||||
@@ -304,6 +308,7 @@ export const RepoRow = ({
|
||||
downloadStatus={downloadState?.downloadStatus}
|
||||
interpretedResults={interpretedResults}
|
||||
rawResults={rawResults}
|
||||
resultFormat={resultFormat}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { styled } from "styled-components";
|
||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
||||
import { Codicon } from "../common";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
value: ResultFormat;
|
||||
onChange: (value: ResultFormat) => void;
|
||||
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const RepositoriesResultFormat = ({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
}: Props) => {
|
||||
const handleInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(target.value as ResultFormat);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown value={value} onInput={handleInput} className={className}>
|
||||
<Codicon name="table" label="Result format..." slot="indicator" />
|
||||
<VSCodeOption value={ResultFormat.Alerts}>
|
||||
{ResultFormat.Alerts}
|
||||
</VSCodeOption>
|
||||
<VSCodeOption value={ResultFormat.RawResults}>
|
||||
{ResultFormat.RawResults}
|
||||
</VSCodeOption>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -9,10 +9,15 @@ import {
|
||||
import { RepositoriesSearch } from "./RepositoriesSearch";
|
||||
import { RepositoriesSort } from "./RepositoriesSort";
|
||||
import { RepositoriesFilter } from "./RepositoriesFilter";
|
||||
import { RepositoriesResultFormat } from "./RepositoriesResultFormat";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
type Props = {
|
||||
value: RepositoriesFilterSortState;
|
||||
onChange: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
|
||||
filterSortValue: RepositoriesFilterSortState;
|
||||
resultFormatValue: ResultFormat;
|
||||
onFilterSortChange: Dispatch<SetStateAction<RepositoriesFilterSortState>>;
|
||||
onResultFormatChange: Dispatch<SetStateAction<ResultFormat>>;
|
||||
variantAnalysisQueryKind: string | undefined;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
@@ -35,51 +40,83 @@ const RepositoriesSortColumn = styled(RepositoriesSort)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
|
||||
const RepositoriesResultFormatColumn = styled(RepositoriesResultFormat)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
function showResultFormatColumn(
|
||||
variantAnalysisQueryKind: string | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
variantAnalysisQueryKind === "problem" ||
|
||||
variantAnalysisQueryKind === "path-problem"
|
||||
);
|
||||
}
|
||||
|
||||
export const RepositoriesSearchSortRow = ({
|
||||
filterSortValue,
|
||||
resultFormatValue,
|
||||
onFilterSortChange,
|
||||
onResultFormatChange,
|
||||
variantAnalysisQueryKind,
|
||||
}: Props) => {
|
||||
const handleSearchValueChange = useCallback(
|
||||
(searchValue: string) => {
|
||||
onChange((oldValue) => ({
|
||||
onFilterSortChange((oldValue) => ({
|
||||
...oldValue,
|
||||
searchValue,
|
||||
}));
|
||||
},
|
||||
[onChange],
|
||||
[onFilterSortChange],
|
||||
);
|
||||
|
||||
const handleFilterKeyChange = useCallback(
|
||||
(filterKey: FilterKey) => {
|
||||
onChange((oldValue) => ({
|
||||
onFilterSortChange((oldValue) => ({
|
||||
...oldValue,
|
||||
filterKey,
|
||||
}));
|
||||
},
|
||||
[onChange],
|
||||
[onFilterSortChange],
|
||||
);
|
||||
|
||||
const handleSortKeyChange = useCallback(
|
||||
(sortKey: SortKey) => {
|
||||
onChange((oldValue) => ({
|
||||
onFilterSortChange((oldValue) => ({
|
||||
...oldValue,
|
||||
sortKey,
|
||||
}));
|
||||
},
|
||||
[onChange],
|
||||
[onFilterSortChange],
|
||||
);
|
||||
|
||||
const handleResultFormatChange = useCallback(
|
||||
(resultFormat: ResultFormat) => {
|
||||
onResultFormatChange(resultFormat);
|
||||
},
|
||||
[onResultFormatChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<RepositoriesSearchColumn
|
||||
value={value.searchValue}
|
||||
value={filterSortValue.searchValue}
|
||||
onChange={handleSearchValueChange}
|
||||
/>
|
||||
<RepositoriesFilterColumn
|
||||
value={value.filterKey}
|
||||
value={filterSortValue.filterKey}
|
||||
onChange={handleFilterKeyChange}
|
||||
/>
|
||||
<RepositoriesSortColumn
|
||||
value={value.sortKey}
|
||||
value={filterSortValue.sortKey}
|
||||
onChange={handleSortKeyChange}
|
||||
/>
|
||||
{showResultFormatColumn(variantAnalysisQueryKind) && (
|
||||
<RepositoriesResultFormatColumn
|
||||
value={resultFormatValue}
|
||||
onChange={handleResultFormatChange}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
filterAndSortRepositoriesWithResultsByName,
|
||||
RepositoriesFilterSortState,
|
||||
} from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
@@ -26,6 +27,8 @@ export type VariantAnalysisAnalyzedReposProps = {
|
||||
|
||||
filterSortState?: RepositoriesFilterSortState;
|
||||
|
||||
resultFormat: ResultFormat;
|
||||
|
||||
selectedRepositoryIds?: number[];
|
||||
setSelectedRepositoryIds?: Dispatch<SetStateAction<number[]>>;
|
||||
};
|
||||
@@ -35,6 +38,7 @@ export const VariantAnalysisAnalyzedRepos = ({
|
||||
repositoryStates,
|
||||
repositoryResults,
|
||||
filterSortState,
|
||||
resultFormat,
|
||||
selectedRepositoryIds,
|
||||
setSelectedRepositoryIds,
|
||||
}: VariantAnalysisAnalyzedReposProps) => {
|
||||
@@ -93,6 +97,7 @@ export const VariantAnalysisAnalyzedRepos = ({
|
||||
resultCount={repository.resultCount}
|
||||
interpretedResults={results?.interpretedResults}
|
||||
rawResults={results?.rawResults}
|
||||
resultFormat={resultFormat}
|
||||
selected={selectedRepositoryIds?.includes(repository.repository.id)}
|
||||
onSelectedChange={onSelectedChange}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import { styled } from "styled-components";
|
||||
import {
|
||||
VSCodeBadge,
|
||||
@@ -20,6 +20,7 @@ import { VariantAnalysisSkippedRepositoriesTab } from "./VariantAnalysisSkippedR
|
||||
import { RepositoriesFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { RepositoriesSearchSortRow } from "./RepositoriesSearchSortRow";
|
||||
import { FailureReasonAlert } from "./FailureReasonAlert";
|
||||
import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
export type VariantAnalysisOutcomePanelProps = {
|
||||
variantAnalysis: VariantAnalysis;
|
||||
@@ -70,6 +71,7 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
const accessMismatchRepositoryCount =
|
||||
variantAnalysis.skippedRepos?.accessMismatchRepos?.repositoryCount ?? 0;
|
||||
|
||||
const [resultFormat, setResultFormat] = useState(ResultFormat.Alerts);
|
||||
const warnings = (
|
||||
<WarningsContainer>
|
||||
{variantAnalysis.status === VariantAnalysisStatus.Canceled && (
|
||||
@@ -123,14 +125,18 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
<>
|
||||
{warnings}
|
||||
<RepositoriesSearchSortRow
|
||||
value={filterSortState}
|
||||
onChange={setFilterSortState}
|
||||
filterSortValue={filterSortState}
|
||||
resultFormatValue={resultFormat}
|
||||
onFilterSortChange={setFilterSortState}
|
||||
onResultFormatChange={setResultFormat}
|
||||
variantAnalysisQueryKind={variantAnalysis.query.kind}
|
||||
/>
|
||||
<VariantAnalysisAnalyzedRepos
|
||||
variantAnalysis={variantAnalysis}
|
||||
repositoryStates={repositoryStates}
|
||||
repositoryResults={repositoryResults}
|
||||
filterSortState={filterSortState}
|
||||
resultFormat={resultFormat}
|
||||
selectedRepositoryIds={selectedRepositoryIds}
|
||||
setSelectedRepositoryIds={setSelectedRepositoryIds}
|
||||
/>
|
||||
@@ -142,8 +148,11 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
<>
|
||||
{warnings}
|
||||
<RepositoriesSearchSortRow
|
||||
value={filterSortState}
|
||||
onChange={setFilterSortState}
|
||||
filterSortValue={filterSortState}
|
||||
resultFormatValue={resultFormat}
|
||||
onFilterSortChange={setFilterSortState}
|
||||
onResultFormatChange={setResultFormat}
|
||||
variantAnalysisQueryKind={variantAnalysis.query.kind}
|
||||
/>
|
||||
<VSCodePanels>
|
||||
{scannedReposCount > 0 && (
|
||||
@@ -177,6 +186,7 @@ export const VariantAnalysisOutcomePanels = ({
|
||||
repositoryStates={repositoryStates}
|
||||
repositoryResults={repositoryResults}
|
||||
filterSortState={filterSortState}
|
||||
resultFormat={resultFormat}
|
||||
selectedRepositoryIds={selectedRepositoryIds}
|
||||
setSelectedRepositoryIds={setSelectedRepositoryIds}
|
||||
/>
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
AnalyzedRepoItemContent,
|
||||
AnalyzedRepoItemContentProps,
|
||||
} from "../AnalyzedRepoItemContent";
|
||||
import { ResultFormat } from "../../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
describe(AnalyzedRepoItemContent.name, () => {
|
||||
const render = (props: Partial<AnalyzedRepoItemContentProps> = {}) => {
|
||||
return reactRender(
|
||||
<AnalyzedRepoItemContent
|
||||
status={VariantAnalysisRepoStatus.Succeeded}
|
||||
resultFormat={ResultFormat.Alerts}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { createMockRepositoryWithMetadata } from "../../../../test/factories/var
|
||||
import { createMockScannedRepo } from "../../../../test/factories/variant-analysis/shared/scanned-repositories";
|
||||
import { SortKey } from "../../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { permissiveFilterSortState } from "../../../../test/unit-tests/variant-analysis-filter-sort.test";
|
||||
import { ResultFormat } from "../../../variant-analysis/shared/variant-analysis-result-format";
|
||||
|
||||
describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
const defaultVariantAnalysis = createMockVariantAnalysis({
|
||||
@@ -99,6 +100,7 @@ describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
return reactRender(
|
||||
<VariantAnalysisAnalyzedRepos
|
||||
variantAnalysis={defaultVariantAnalysis}
|
||||
resultFormat={ResultFormat.Alerts}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
FromCompareViewMessage,
|
||||
FromDataExtensionsEditorMessage,
|
||||
FromModelEditorMessage,
|
||||
FromResultsViewMsg,
|
||||
FromVariantAnalysisMessage,
|
||||
VariantAnalysisState,
|
||||
@@ -15,7 +15,7 @@ interface VsCodeApi {
|
||||
| FromResultsViewMsg
|
||||
| FromCompareViewMessage
|
||||
| FromVariantAnalysisMessage
|
||||
| FromDataExtensionsEditorMessage,
|
||||
| FromModelEditorMessage,
|
||||
): void;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.14.2",
|
||||
"v2.14.3",
|
||||
"v2.13.5",
|
||||
"v2.12.7",
|
||||
"v2.11.6",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
Usage,
|
||||
ExternalApiUsage,
|
||||
CallClassification,
|
||||
} from "../../../src/model-editor/external-api-usage";
|
||||
import { ModeledMethodType } from "../../../src/model-editor/modeled-method";
|
||||
import { ResolvableLocationValue } from "../../../src/common/bqrs-cli-types";
|
||||
|
||||
export function createExternalApiUsage({
|
||||
library = "sql2o-1.6.0.jar",
|
||||
supported = true,
|
||||
supportedType = "summary" as ModeledMethodType,
|
||||
usages = [],
|
||||
signature = "org.sql2o.Sql2o#open()",
|
||||
packageName = "org.sql2o",
|
||||
typeName = "Sql2o",
|
||||
methodName = "open",
|
||||
methodParameters = "()",
|
||||
}: {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("commands declared in package.json", () => {
|
||||
command.match(/^codeQLAstViewer\./) ||
|
||||
command.match(/^codeQLEvalLogViewer\./) ||
|
||||
command.match(/^codeQLTests\./) ||
|
||||
command.match(/^codeQLDataExtensionsEditor\./)
|
||||
command.match(/^codeQLModelEditor\./)
|
||||
) {
|
||||
scopedCmds.add(command);
|
||||
expect(title).toBeDefined();
|
||||
|
||||
@@ -2,14 +2,14 @@ import {
|
||||
createAutoModelRequest,
|
||||
encodeSarif,
|
||||
getCandidates,
|
||||
} from "../../../src/data-extensions-editor/auto-model";
|
||||
import { Mode } from "../../../src/data-extensions-editor/shared/mode";
|
||||
import { AutomodelMode } from "../../../src/data-extensions-editor/auto-model-api";
|
||||
import { AutoModelQueriesResult } from "../../../src/data-extensions-editor/auto-model-codeml-queries";
|
||||
} from "../../../src/model-editor/auto-model";
|
||||
import { Mode } from "../../../src/model-editor/shared/mode";
|
||||
import { AutomodelMode } from "../../../src/model-editor/auto-model-api";
|
||||
import { AutoModelQueriesResult } from "../../../src/model-editor/auto-model-codeml-queries";
|
||||
import * as sarif from "sarif";
|
||||
import { gzipDecode } from "../../../src/common/zlib";
|
||||
import { ExternalApiUsage } from "../../../src/data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../../src/data-extensions-editor/modeled-method";
|
||||
import { ExternalApiUsage } from "../../../src/model-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../../../src/model-editor/modeled-method";
|
||||
|
||||
describe("createAutoModelRequest", () => {
|
||||
const createSarifLog = (queryId: string): sarif.Log => {
|
||||
@@ -1,6 +1,6 @@
|
||||
import { decodeBqrsToExternalApiUsages } from "../../../src/data-extensions-editor/bqrs";
|
||||
import { decodeBqrsToExternalApiUsages } from "../../../src/model-editor/bqrs";
|
||||
import { DecodedBqrsChunk } from "../../../src/common/bqrs-cli-types";
|
||||
import { CallClassification } from "../../../src/data-extensions-editor/external-api-usage";
|
||||
import { CallClassification } from "../../../src/model-editor/external-api-usage";
|
||||
|
||||
describe("decodeBqrsToExternalApiUsages", () => {
|
||||
const chunk: DecodedBqrsChunk = {
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
formatPackName,
|
||||
parsePackName,
|
||||
validatePackName,
|
||||
} from "../../../src/data-extensions-editor/extension-pack-name";
|
||||
} from "../../../src/model-editor/extension-pack-name";
|
||||
|
||||
describe("autoNameExtensionPack", () => {
|
||||
const testCases: Array<{
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseLibraryFilename } from "../../../src/data-extensions-editor/library";
|
||||
import { parseLibraryFilename } from "../../../src/model-editor/library";
|
||||
|
||||
describe("parseLibraryFilename", () => {
|
||||
const testCases = [
|
||||
@@ -1,4 +1,4 @@
|
||||
import { calculateModeledPercentage } from "../../../../src/data-extensions-editor/shared/modeled-percentage";
|
||||
import { calculateModeledPercentage } from "../../../../src/model-editor/shared/modeled-percentage";
|
||||
|
||||
describe("calculateModeledPercentage", () => {
|
||||
it("when there are no external API usages", () => {
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
createDataExtensionYamlsForFrameworkMode,
|
||||
createFilenameForLibrary,
|
||||
loadDataExtensionYaml,
|
||||
} from "../../../src/data-extensions-editor/yaml";
|
||||
import { CallClassification } from "../../../src/data-extensions-editor/external-api-usage";
|
||||
} from "../../../src/model-editor/yaml";
|
||||
import { CallClassification } from "../../../src/model-editor/external-api-usage";
|
||||
|
||||
describe("createDataExtensionYaml", () => {
|
||||
it("creates the correct YAML 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"],
|
||||
|
||||
@@ -6,8 +6,8 @@ import { mkdirSync, writeFileSync } from "fs";
|
||||
import {
|
||||
listModelFiles,
|
||||
loadModeledMethods,
|
||||
} from "../../../../src/data-extensions-editor/modeled-method-fs";
|
||||
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
|
||||
} from "../../../../src/model-editor/modeled-method-fs";
|
||||
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
||||
import { join } from "path";
|
||||
import { extLogger } from "../../../../src/common/logging/vscode";
|
||||
import { homedir } from "os";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user