Merge branch 'main' into github-action/bump-cli
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- Added ability to filter repositories for a variant analysis to only those that have results [#2343](https://github.com/github/vscode-codeql/pull/2343)
|
||||
- Add new configuration option to allow downloading databases from http, non-secure servers. [#2332](https://github.com/github/vscode-codeql/pull/2332)
|
||||
- Remove title actions from the query history panel that depended on history items being selected. [#2350](https://github.com/github/vscode-codeql/pull/2350)
|
||||
|
||||
## 1.8.2 - 12 April 2023
|
||||
|
||||
|
||||
@@ -340,6 +340,12 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Allow database to be downloaded via HTTP. Warning: enabling this option will allow downloading from insecure servers."
|
||||
},
|
||||
"codeQL.createQuery.folder": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"patternErrorMessage": "Please enter a valid folder",
|
||||
"markdownDescription": "The name of the folder where we want to create queries and query packs via the \"CodeQL: Create Query\" command. The folder should exist."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -627,11 +633,6 @@
|
||||
"command": "codeQL.checkForUpdatesToCLI",
|
||||
"title": "CodeQL: Check for CLI Updates"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"title": "View Query",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"title": "View Query",
|
||||
@@ -642,11 +643,6 @@
|
||||
"title": "Open Query Results",
|
||||
"icon": "$(preview)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"title": "Delete",
|
||||
@@ -847,21 +843,6 @@
|
||||
"when": "view == codeQLDatabases",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.itemClicked",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
@@ -1304,18 +1285,10 @@
|
||||
"command": "codeQLDatabases.upgradeDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"when": "false"
|
||||
@@ -1446,7 +1419,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQL.createQuery",
|
||||
"when": "config.codeQL.canary"
|
||||
"when": "config.codeQL.codespacesTemplate"
|
||||
},
|
||||
{
|
||||
"command": "codeQLTests.acceptOutputContextTestItem",
|
||||
|
||||
@@ -13,21 +13,6 @@ import type {
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command is invoked from the title bar of a TreeView with
|
||||
// canSelectMany set to true.
|
||||
//
|
||||
// It is possible to get any combination of singleItem and multiSelect
|
||||
// to be undefined. This is because it is possible to click a title bar
|
||||
// option without interacting with any individual items first, or even
|
||||
// when there are no items present at all.
|
||||
// If both singleItem and multiSelect are defined, then singleItem will
|
||||
// be contained within multiSelect.
|
||||
export type TreeViewTitleMultiSelectionCommandFunction<Item> = (
|
||||
singleItem: Item | undefined,
|
||||
multiSelect: Item[] | undefined,
|
||||
) => Promise<void>;
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command is invoked from a context menu on a TreeView with
|
||||
// canSelectMany set to true.
|
||||
@@ -176,9 +161,7 @@ export type QueryHistoryCommands = {
|
||||
"codeQLQueryHistory.sortByCount": () => Promise<void>;
|
||||
|
||||
// Commands in the context menu or in the hover menu
|
||||
"codeQLQueryHistory.openQueryTitleMenu": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openQueryContextMenu": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemTitleMenu": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextMenu": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextInline": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.renameItem": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
@@ -195,7 +178,7 @@ export type QueryHistoryCommands = {
|
||||
"codeQLQueryHistory.viewCsvAlerts": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewSarifAlerts": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewDil": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.itemClicked": TreeViewTitleMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.itemClicked": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openOnGithub": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.copyRepoList": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
|
||||
|
||||
|
||||
@@ -619,3 +619,19 @@ export const ALLOW_HTTP_SETTING = new Setting(
|
||||
export function allowHttp(): boolean {
|
||||
return ALLOW_HTTP_SETTING.getValue<boolean>() || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the folder where we want to create skeleton wizard QL packs.
|
||||
**/
|
||||
const SKELETON_WIZARD_FOLDER = new Setting(
|
||||
"folder",
|
||||
new Setting("createQuery", ROOT_SETTING),
|
||||
);
|
||||
|
||||
export function getSkeletonWizardFolder(): string | undefined {
|
||||
return SKELETON_WIZARD_FOLDER.getValue<string>() || undefined;
|
||||
}
|
||||
|
||||
export async function setSkeletonWizardFolder(folder: string | undefined) {
|
||||
await SKELETON_WIZARD_FOLDER.updateValue(folder, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from "./queryResolver";
|
||||
import { CancellationToken, LocationLink, Uri } from "vscode";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
|
||||
export const SELECT_QUERY_NAME = "#select";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { DatabaseItem } from "../local-databases";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
runContextualQuery,
|
||||
} from "./queryResolver";
|
||||
import { isCanary, NO_CACHE_AST_VIEWER } from "../config";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
|
||||
/**
|
||||
* Runs templated CodeQL queries to find definitions in
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ExtensionContext } from "vscode";
|
||||
import { DataExtensionsEditorView } from "./data-extensions-editor-view";
|
||||
import { DataExtensionsEditorCommands } from "../common/commands";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../cli";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import { join } from "path";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ToDataExtensionsEditorMessage,
|
||||
} from "../pure/interface-types";
|
||||
import { ProgressUpdate } from "../progress";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
@@ -35,7 +35,7 @@ import { readQueryResults, runQuery } from "./external-api-usage-query";
|
||||
import { createDataExtensionYaml, loadDataExtensionYaml } from "./yaml";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPackModelFile } from "./extension-pack-picker";
|
||||
import { ExtensionPackModelFile } from "./shared/extension-pack";
|
||||
|
||||
function getQlSubmoduleFolder(): WorkspaceFolder | undefined {
|
||||
const workspaceFolder = workspace.workspaceFolders?.find(
|
||||
@@ -118,7 +118,7 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
msg.externalApiUsages,
|
||||
msg.modeledMethods,
|
||||
);
|
||||
await this.loadExternalApiUsages();
|
||||
await Promise.all([this.setViewState(), this.loadExternalApiUsages()]);
|
||||
|
||||
break;
|
||||
case "generateExternalApi":
|
||||
@@ -134,16 +134,22 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
super.onWebViewLoaded();
|
||||
|
||||
await Promise.all([
|
||||
this.postMessage({
|
||||
t: "setDataExtensionEditorInitialData",
|
||||
extensionPackName: this.modelFile.extensionPack.name,
|
||||
modelFilename: this.modelFile.filename,
|
||||
}),
|
||||
this.setViewState(),
|
||||
this.loadExternalApiUsages(),
|
||||
this.loadExistingModeledMethods(),
|
||||
]);
|
||||
}
|
||||
|
||||
private async setViewState(): Promise<void> {
|
||||
await this.postMessage({
|
||||
t: "setDataExtensionEditorViewState",
|
||||
viewState: {
|
||||
extensionPackModelFile: this.modelFile,
|
||||
modelFileExists: await pathExists(this.modelFile.filename),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected async jumpToUsage(
|
||||
location: ResolvableLocationValue,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ProgressCallback } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import { ExtensionPack, ExtensionPackModelFile } from "./shared/extension-pack";
|
||||
|
||||
const maxStep = 3;
|
||||
|
||||
@@ -22,22 +23,6 @@ const packNameRegex = new RegExp(
|
||||
);
|
||||
const packNameLength = 128;
|
||||
|
||||
export interface ExtensionPack {
|
||||
path: string;
|
||||
yamlPath: string;
|
||||
|
||||
name: string;
|
||||
version: string;
|
||||
|
||||
extensionTargets: Record<string, string>;
|
||||
dataExtensions: string[];
|
||||
}
|
||||
|
||||
export interface ExtensionPackModelFile {
|
||||
filename: string;
|
||||
extensionPack: ExtensionPack;
|
||||
}
|
||||
|
||||
export async function pickExtensionPackModelFile(
|
||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks" | "resolveExtensions">,
|
||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CoreCompletedQuery, QueryRunner } from "../queryRunner";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { dir } from "tmp-promise";
|
||||
import { writeFile } from "fs-extra";
|
||||
import { dump as dumpYaml } from "js-yaml";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { join } from "path";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { TeeLogger } from "../common";
|
||||
import { extensiblePredicateDefinitions } from "./predicates";
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface ExtensionPack {
|
||||
path: string;
|
||||
yamlPath: string;
|
||||
|
||||
name: string;
|
||||
version: string;
|
||||
|
||||
extensionTargets: Record<string, string>;
|
||||
dataExtensions: string[];
|
||||
}
|
||||
|
||||
export interface ExtensionPackModelFile {
|
||||
filename: string;
|
||||
extensionPack: ExtensionPack;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ExtensionPackModelFile } from "./extension-pack";
|
||||
|
||||
export interface DataExtensionEditorViewState {
|
||||
extensionPackModelFile: ExtensionPackModelFile;
|
||||
modelFileExists: boolean;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { Disposable } from "vscode";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import { BaseLogger, LogOptions, queryServerLogger } from "../common";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../queryRunner";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../query-server";
|
||||
import * as CodeQLProtocol from "./debug-protocol";
|
||||
import { QuickEvalContext } from "../run-queries-shared";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { isCanary } from "../config";
|
||||
import { LocalQueries } from "../local-queries";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { QLDebugConfigurationProvider } from "./debug-configuration";
|
||||
import { QLDebugSession } from "./debug-session";
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ import {
|
||||
} from "vscode";
|
||||
import { DebuggerCommands } from "../common/commands";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { LocalQueries, LocalQueryRun } from "../local-queries";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { CoreQueryResults } from "../queryRunner";
|
||||
import { CoreQueryResults } from "../query-server";
|
||||
import {
|
||||
getQuickEvalContext,
|
||||
QueryOutputDir,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
import { QLResolvedDebugConfiguration } from "./debug-configuration";
|
||||
import * as CodeQLProtocol from "./debug-protocol";
|
||||
import { App } from "../common/app";
|
||||
import { LocalQueryRun, LocalQueries } from "../local-queries";
|
||||
|
||||
/**
|
||||
* Listens to messages passing between VS Code and the debug adapter, so that we can supplement the
|
||||
|
||||
@@ -83,8 +83,10 @@ import {
|
||||
} from "./common";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { CompletedLocalQueryInfo } from "./query-results";
|
||||
import { QueryServerClient as LegacyQueryServerClient } from "./legacy-query-server/queryserver-client";
|
||||
import { QueryServerClient } from "./query-server/queryserver-client";
|
||||
import {
|
||||
LegacyQueryRunner,
|
||||
QueryServerClient as LegacyQueryServerClient,
|
||||
} from "./query-server/legacy";
|
||||
import { QLTestAdapterFactory } from "./test-adapter";
|
||||
import { TestUIService } from "./test-ui";
|
||||
import { CompareView } from "./compare/compare-view";
|
||||
@@ -97,9 +99,6 @@ import { EvalLogViewer } from "./eval-log-viewer";
|
||||
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
||||
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
||||
import { LogScannerService } from "./log-insights/log-scanner-service";
|
||||
import { LegacyQueryRunner } from "./legacy-query-server/legacyRunner";
|
||||
import { NewQueryRunner } from "./query-server/query-runner";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { VariantAnalysisView } from "./variant-analysis/variant-analysis-view";
|
||||
import { VariantAnalysisViewSerializer } from "./variant-analysis/variant-analysis-view-serializer";
|
||||
import { VariantAnalysisManager } from "./variant-analysis/variant-analysis-manager";
|
||||
@@ -127,6 +126,7 @@ import { DataExtensionsEditorModule } from "./data-extensions-editor/data-extens
|
||||
import { TestManager } from "./test-manager";
|
||||
import { TestRunner } from "./test-runner";
|
||||
import { TestManagerBase } from "./test-manager-base";
|
||||
import { NewQueryRunner, QueryRunner, QueryServerClient } from "./query-server";
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -707,9 +707,14 @@ async function activateWithInstalledDistribution(
|
||||
for (const glob of PACK_GLOBS) {
|
||||
const fsWatcher = workspace.createFileSystemWatcher(glob);
|
||||
ctx.subscriptions.push(fsWatcher);
|
||||
fsWatcher.onDidChange(async (_uri) => {
|
||||
|
||||
const clearPackCache = async (_uri: Uri) => {
|
||||
await qs.clearPackCache();
|
||||
});
|
||||
};
|
||||
|
||||
fsWatcher.onDidCreate(clearPackCache);
|
||||
fsWatcher.onDidChange(clearPackCache);
|
||||
fsWatcher.onDidDelete(clearPackCache);
|
||||
}
|
||||
|
||||
void extLogger.log("Initializing database manager.");
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
promptImportInternetDatabase,
|
||||
} from "./databaseFetcher";
|
||||
import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { QueryRunner } from "./query-server";
|
||||
import { isCanary } from "./config";
|
||||
import { App } from "./common/app";
|
||||
import { redactableError } from "./pure/errors";
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { Logger, extLogger } from "./common";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { QueryRunner } from "./query-server";
|
||||
import { pathsEqual } from "./pure/files";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { isCodespacesTemplate } from "./config";
|
||||
|
||||
2
extensions/ql-vscode/src/local-queries/index.ts
Normal file
2
extensions/ql-vscode/src/local-queries/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./local-queries";
|
||||
export * from "./local-query-run";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
|
||||
import { ProgressCallback, ProgressUpdate, withProgress } from "../progress";
|
||||
import {
|
||||
CancellationToken,
|
||||
CancellationTokenSource,
|
||||
@@ -8,76 +8,46 @@ import {
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { BaseLogger, extLogger, Logger, TeeLogger } from "./common";
|
||||
import { isCanary, MAX_QUERIES } from "./config";
|
||||
import { gatherQlFiles } from "./pure/files";
|
||||
import { extLogger, TeeLogger } from "../common";
|
||||
import { isCanary, MAX_QUERIES } from "../config";
|
||||
import { gatherQlFiles } from "../pure/files";
|
||||
import { basename } from "path";
|
||||
import {
|
||||
createTimestampFile,
|
||||
findLanguage,
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogErrorMessage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
showBinaryChoiceDialog,
|
||||
tryGetQueryMetadata,
|
||||
} from "./helpers";
|
||||
import { displayQuickQuery } from "./quick-query";
|
||||
import {
|
||||
CoreCompletedQuery,
|
||||
CoreQueryResults,
|
||||
QueryRunner,
|
||||
} from "./queryRunner";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { DatabaseUI } from "./local-databases-ui";
|
||||
import { ResultsView } from "./interface";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
} from "../helpers";
|
||||
import { displayQuickQuery } from "../quick-query";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../query-server";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
import { DatabaseUI } from "../local-databases-ui";
|
||||
import { ResultsView } from "../interface";
|
||||
import { DatabaseItem, DatabaseManager } from "../local-databases";
|
||||
import {
|
||||
createInitialQueryInfo,
|
||||
EvaluatorLogPaths,
|
||||
generateEvalLogSummaries,
|
||||
getQuickEvalContext,
|
||||
logEndSummary,
|
||||
promptUserToSaveChanges,
|
||||
QueryEvaluationInfo,
|
||||
QueryOutputDir,
|
||||
QueryWithResults,
|
||||
SelectedQuery,
|
||||
validateQueryUri,
|
||||
} from "./run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
|
||||
import { WebviewReveal } from "./interface-utils";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { LocalQueryCommands } from "./common/commands";
|
||||
import { App } from "./common/app";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { QueryResultType } from "./pure/new-messages";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { SkeletonQueryWizard } from "./skeleton-query-wizard";
|
||||
} from "../run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { LocalQueryCommands } from "../common/commands";
|
||||
import { App } from "../common/app";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { SkeletonQueryWizard } from "../skeleton-query-wizard";
|
||||
import { LocalQueryRun } from "./local-query-run";
|
||||
|
||||
interface DatabaseQuickPickItem extends QuickPickItem {
|
||||
databaseItem: DatabaseItem;
|
||||
}
|
||||
|
||||
function formatResultMessage(result: CoreQueryResults): string {
|
||||
switch (result.resultType) {
|
||||
case QueryResultType.CANCELLATION:
|
||||
return `cancelled after ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
case QueryResultType.OOM:
|
||||
return "out of memory";
|
||||
case QueryResultType.SUCCESS:
|
||||
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
|
||||
case QueryResultType.COMPILATION_ERROR:
|
||||
return `compilation failed: ${result.message}`;
|
||||
case QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
return result.message ? `failed: ${result.message}` : "failed";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If either the query file or the quickeval file is dirty, give the user the chance to save them.
|
||||
*/
|
||||
@@ -97,142 +67,6 @@ async function promptToSaveQueryIfNeeded(query: SelectedQuery): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the evaluation of a local query, including its interactions with the UI.
|
||||
*
|
||||
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
|
||||
* the `complete()` function once the query has completed (successfully or otherwise).
|
||||
*
|
||||
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
|
||||
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
|
||||
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
|
||||
* evaluation, and we can only respond to events from the debug adapter.
|
||||
*/
|
||||
export class LocalQueryRun {
|
||||
public constructor(
|
||||
private readonly outputDir: QueryOutputDir,
|
||||
private readonly localQueries: LocalQueries,
|
||||
private readonly queryInfo: LocalQueryInfo,
|
||||
private readonly dbItem: DatabaseItem,
|
||||
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
|
||||
private readonly queryHistoryManager: QueryHistoryManager,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
|
||||
* summaries, updates the query history item for the evaluation with the results and evaluation
|
||||
* time, and displays the results view.
|
||||
*
|
||||
* This function must be called when the evaluation completes, whether the evaluation was
|
||||
* successful or not.
|
||||
* */
|
||||
public async complete(results: CoreQueryResults): Promise<void> {
|
||||
const evalLogPaths = await this.summarizeEvalLog(
|
||||
results.resultType,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
}
|
||||
const queryWithResults = await this.getCompletedQueryInfo(results);
|
||||
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
|
||||
await this.localQueries.showResultsForCompletedQuery(
|
||||
this.queryInfo as CompletedLocalQueryInfo,
|
||||
WebviewReveal.Forced,
|
||||
);
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI in the case where query evaluation throws an exception.
|
||||
*/
|
||||
public async fail(err: Error): Promise<void> {
|
||||
err.message = `Error running query: ${err.message}`;
|
||||
this.queryInfo.failureReason = err.message;
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate summaries of the structured evaluator log.
|
||||
*/
|
||||
private async summarizeEvalLog(
|
||||
resultType: QueryResultType,
|
||||
outputDir: QueryOutputDir,
|
||||
logger: BaseLogger,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const evalLogPaths = await generateEvalLogSummaries(
|
||||
this.cliServer,
|
||||
outputDir,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
if (evalLogPaths.endSummary !== undefined) {
|
||||
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
|
||||
}
|
||||
} else {
|
||||
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
|
||||
if (resultType === QueryResultType.SUCCESS) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
|
||||
);
|
||||
} else {
|
||||
// Don't bother notifying the user if there's no log. For some errors, like compilation
|
||||
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
|
||||
// a log depends on how far execution got before termination.
|
||||
}
|
||||
}
|
||||
|
||||
return evalLogPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
|
||||
* result, in the form expected by the query history UI.
|
||||
*/
|
||||
private async getCompletedQueryInfo(
|
||||
results: CoreQueryResults,
|
||||
): Promise<QueryWithResults> {
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(
|
||||
this.cliServer,
|
||||
this.queryInfo.initialInfo.queryPath,
|
||||
);
|
||||
const query = new QueryEvaluationInfo(
|
||||
this.outputDir.querySaveDir,
|
||||
this.dbItem.databaseUri.fsPath,
|
||||
await this.dbItem.hasMetadataFile(),
|
||||
this.queryInfo.initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (results.resultType !== QueryResultType.SUCCESS) {
|
||||
const message = results.message
|
||||
? redactableError`Failed to run query: ${results.message}`
|
||||
: redactableError`Failed to run query`;
|
||||
void showAndLogExceptionWithTelemetry(message);
|
||||
}
|
||||
const message = formatResultMessage(results);
|
||||
const successful = results.resultType === QueryResultType.SUCCESS;
|
||||
return {
|
||||
query,
|
||||
result: {
|
||||
evaluationTime: results.evaluationTime,
|
||||
queryId: 0,
|
||||
resultType: successful
|
||||
? QueryResultType.SUCCESS
|
||||
: QueryResultType.OTHER_ERROR,
|
||||
runId: 0,
|
||||
message,
|
||||
},
|
||||
message,
|
||||
successful,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalQueries extends DisposableObject {
|
||||
public constructor(
|
||||
private readonly app: App,
|
||||
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal file
177
extensions/ql-vscode/src/local-queries/local-query-run.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { BaseLogger, Logger } from "../common";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
tryGetQueryMetadata,
|
||||
} from "../helpers";
|
||||
import { CoreQueryResults } from "../query-server";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
EvaluatorLogPaths,
|
||||
generateEvalLogSummaries,
|
||||
logEndSummary,
|
||||
QueryEvaluationInfo,
|
||||
QueryOutputDir,
|
||||
QueryWithResults,
|
||||
} from "../run-queries-shared";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { LocalQueries } from "./local-queries";
|
||||
|
||||
function formatResultMessage(result: CoreQueryResults): string {
|
||||
switch (result.resultType) {
|
||||
case QueryResultType.CANCELLATION:
|
||||
return `cancelled after ${Math.round(
|
||||
result.evaluationTime / 1000,
|
||||
)} seconds`;
|
||||
case QueryResultType.OOM:
|
||||
return "out of memory";
|
||||
case QueryResultType.SUCCESS:
|
||||
return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`;
|
||||
case QueryResultType.COMPILATION_ERROR:
|
||||
return `compilation failed: ${result.message}`;
|
||||
case QueryResultType.OTHER_ERROR:
|
||||
default:
|
||||
return result.message ? `failed: ${result.message}` : "failed";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the evaluation of a local query, including its interactions with the UI.
|
||||
*
|
||||
* The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes
|
||||
* the `complete()` function once the query has completed (successfully or otherwise).
|
||||
*
|
||||
* Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having
|
||||
* the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way
|
||||
* because once we move query evaluation into a Debug Adapter, the debugging UI drives the
|
||||
* evaluation, and we can only respond to events from the debug adapter.
|
||||
*/
|
||||
export class LocalQueryRun {
|
||||
public constructor(
|
||||
private readonly outputDir: QueryOutputDir,
|
||||
private readonly localQueries: LocalQueries,
|
||||
private readonly queryInfo: LocalQueryInfo,
|
||||
private readonly dbItem: DatabaseItem,
|
||||
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
|
||||
private readonly queryHistoryManager: QueryHistoryManager,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the results of the query evaluation. This creates the evaluator log
|
||||
* summaries, updates the query history item for the evaluation with the results and evaluation
|
||||
* time, and displays the results view.
|
||||
*
|
||||
* This function must be called when the evaluation completes, whether the evaluation was
|
||||
* successful or not.
|
||||
* */
|
||||
public async complete(results: CoreQueryResults): Promise<void> {
|
||||
const evalLogPaths = await this.summarizeEvalLog(
|
||||
results.resultType,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
}
|
||||
const queryWithResults = await this.getCompletedQueryInfo(results);
|
||||
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
|
||||
await this.localQueries.showResultsForCompletedQuery(
|
||||
this.queryInfo as CompletedLocalQueryInfo,
|
||||
WebviewReveal.Forced,
|
||||
);
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI in the case where query evaluation throws an exception.
|
||||
*/
|
||||
public async fail(err: Error): Promise<void> {
|
||||
err.message = `Error running query: ${err.message}`;
|
||||
this.queryInfo.failureReason = err.message;
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate summaries of the structured evaluator log.
|
||||
*/
|
||||
private async summarizeEvalLog(
|
||||
resultType: QueryResultType,
|
||||
outputDir: QueryOutputDir,
|
||||
logger: BaseLogger,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const evalLogPaths = await generateEvalLogSummaries(
|
||||
this.cliServer,
|
||||
outputDir,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
if (evalLogPaths.endSummary !== undefined) {
|
||||
void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously
|
||||
}
|
||||
} else {
|
||||
// Raw evaluator log was not found. Notify the user, unless we know why it wasn't found.
|
||||
if (resultType === QueryResultType.SUCCESS) {
|
||||
void showAndLogWarningMessage(
|
||||
`Failed to write structured evaluator log to ${outputDir.evalLogPath}.`,
|
||||
);
|
||||
} else {
|
||||
// Don't bother notifying the user if there's no log. For some errors, like compilation
|
||||
// errors, we don't expect a log. For cancellations and OOM errors, whether or not we have
|
||||
// a log depends on how far execution got before termination.
|
||||
}
|
||||
}
|
||||
|
||||
return evalLogPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `QueryWithResults` containing information about the evaluation of the query and its
|
||||
* result, in the form expected by the query history UI.
|
||||
*/
|
||||
private async getCompletedQueryInfo(
|
||||
results: CoreQueryResults,
|
||||
): Promise<QueryWithResults> {
|
||||
// Read the query metadata if possible, to use in the UI.
|
||||
const metadata = await tryGetQueryMetadata(
|
||||
this.cliServer,
|
||||
this.queryInfo.initialInfo.queryPath,
|
||||
);
|
||||
const query = new QueryEvaluationInfo(
|
||||
this.outputDir.querySaveDir,
|
||||
this.dbItem.databaseUri.fsPath,
|
||||
await this.dbItem.hasMetadataFile(),
|
||||
this.queryInfo.initialInfo.quickEvalPosition,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (results.resultType !== QueryResultType.SUCCESS) {
|
||||
const message = results.message
|
||||
? redactableError`Failed to run query: ${results.message}`
|
||||
: redactableError`Failed to run query`;
|
||||
void showAndLogExceptionWithTelemetry(message);
|
||||
}
|
||||
const message = formatResultMessage(results);
|
||||
const successful = results.resultType === QueryResultType.SUCCESS;
|
||||
return {
|
||||
query,
|
||||
result: {
|
||||
evaluationTime: results.evaluationTime,
|
||||
queryId: 0,
|
||||
resultType: successful
|
||||
? QueryResultType.SUCCESS
|
||||
: QueryResultType.OTHER_ERROR,
|
||||
runId: 0,
|
||||
message,
|
||||
},
|
||||
message,
|
||||
successful,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { ErrorLike } from "./errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import { ExternalApiUsage } from "../data-extensions-editor/external-api-usage";
|
||||
import { ModeledMethod } from "../data-extensions-editor/modeled-method";
|
||||
import { DataExtensionEditorViewState } from "../data-extensions-editor/shared/view-state";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -481,10 +482,9 @@ export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
export interface SetDataExtensionEditorInitialDataMessage {
|
||||
t: "setDataExtensionEditorInitialData";
|
||||
extensionPackName: string;
|
||||
modelFilename: string;
|
||||
export interface SetExtensionPackStateMessage {
|
||||
t: "setDataExtensionEditorViewState";
|
||||
viewState: DataExtensionEditorViewState;
|
||||
}
|
||||
|
||||
export interface SetExternalApiUsagesMessage {
|
||||
@@ -536,7 +536,7 @@ export interface GenerateExternalApiMessage {
|
||||
}
|
||||
|
||||
export type ToDataExtensionsEditorMessage =
|
||||
| SetDataExtensionEditorInitialDataMessage
|
||||
| SetExtensionPackStateMessage
|
||||
| SetExternalApiUsagesMessage
|
||||
| ShowProgressMessage
|
||||
| AddModeledMethodsMessage;
|
||||
|
||||
@@ -3,6 +3,12 @@ import {
|
||||
RepositoryWithMetadata,
|
||||
} from "../variant-analysis/shared/repository";
|
||||
import { parseDate } from "./date";
|
||||
import { assertNever } from "./helpers-pure";
|
||||
|
||||
export enum FilterKey {
|
||||
All = "all",
|
||||
WithResults = "withResults",
|
||||
}
|
||||
|
||||
export enum SortKey {
|
||||
Name = "name",
|
||||
@@ -13,6 +19,7 @@ export enum SortKey {
|
||||
|
||||
export type RepositoriesFilterSortState = {
|
||||
searchValue: string;
|
||||
filterKey: FilterKey;
|
||||
sortKey: SortKey;
|
||||
};
|
||||
|
||||
@@ -22,20 +29,43 @@ export type RepositoriesFilterSortStateWithIds = RepositoriesFilterSortState & {
|
||||
|
||||
export const defaultFilterSortState: RepositoriesFilterSortState = {
|
||||
searchValue: "",
|
||||
filterKey: FilterKey.All,
|
||||
sortKey: SortKey.Name,
|
||||
};
|
||||
|
||||
export function matchesFilter(
|
||||
repo: Pick<Repository, "fullName">,
|
||||
item: FilterAndSortableResult,
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
): boolean {
|
||||
if (!filterSortState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return repo.fullName
|
||||
.toLowerCase()
|
||||
.includes(filterSortState.searchValue.toLowerCase());
|
||||
return (
|
||||
matchesSearch(item.repository, filterSortState.searchValue) &&
|
||||
matchesFilterKey(item.resultCount, filterSortState.filterKey)
|
||||
);
|
||||
}
|
||||
|
||||
function matchesSearch(
|
||||
repository: SortableRepository,
|
||||
searchValue: string,
|
||||
): boolean {
|
||||
return repository.fullName.toLowerCase().includes(searchValue.toLowerCase());
|
||||
}
|
||||
|
||||
function matchesFilterKey(
|
||||
resultCount: number | undefined,
|
||||
filterKey: FilterKey,
|
||||
): boolean {
|
||||
switch (filterKey) {
|
||||
case FilterKey.All:
|
||||
return true;
|
||||
case FilterKey.WithResults:
|
||||
return resultCount !== undefined && resultCount > 0;
|
||||
default:
|
||||
assertNever(filterKey);
|
||||
}
|
||||
}
|
||||
|
||||
type SortableRepository = Pick<Repository, "fullName"> &
|
||||
@@ -71,17 +101,22 @@ export function compareRepository(
|
||||
};
|
||||
}
|
||||
|
||||
type SortableResult = {
|
||||
type FilterAndSortableResult = {
|
||||
repository: SortableRepository;
|
||||
resultCount?: number;
|
||||
};
|
||||
|
||||
type FilterAndSortableResultWithIds = {
|
||||
repository: SortableRepository & Pick<Repository, "id">;
|
||||
resultCount?: number;
|
||||
};
|
||||
|
||||
export function compareWithResults(
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
): (left: SortableResult, right: SortableResult) => number {
|
||||
): (left: FilterAndSortableResult, right: FilterAndSortableResult) => number {
|
||||
const fallbackSort = compareRepository(filterSortState);
|
||||
|
||||
return (left: SortableResult, right: SortableResult) => {
|
||||
return (left: FilterAndSortableResult, right: FilterAndSortableResult) => {
|
||||
// Highest to lowest
|
||||
if (filterSortState?.sortKey === SortKey.ResultsCount) {
|
||||
const resultCount = (right.resultCount ?? 0) - (left.resultCount ?? 0);
|
||||
@@ -95,7 +130,7 @@ export function compareWithResults(
|
||||
}
|
||||
|
||||
export function filterAndSortRepositoriesWithResultsByName<
|
||||
T extends SortableResult,
|
||||
T extends FilterAndSortableResult,
|
||||
>(
|
||||
repositories: T[] | undefined,
|
||||
filterSortState: RepositoriesFilterSortState | undefined,
|
||||
@@ -105,11 +140,13 @@ export function filterAndSortRepositoriesWithResultsByName<
|
||||
}
|
||||
|
||||
return repositories
|
||||
.filter((repo) => matchesFilter(repo.repository, filterSortState))
|
||||
.filter((repo) => matchesFilter(repo, filterSortState))
|
||||
.sort(compareWithResults(filterSortState));
|
||||
}
|
||||
|
||||
export function filterAndSortRepositoriesWithResults<T extends SortableResult>(
|
||||
export function filterAndSortRepositoriesWithResults<
|
||||
T extends FilterAndSortableResultWithIds,
|
||||
>(
|
||||
repositories: T[] | undefined,
|
||||
filterSortState: RepositoriesFilterSortStateWithIds | undefined,
|
||||
): T[] | undefined {
|
||||
@@ -117,6 +154,7 @@ export function filterAndSortRepositoriesWithResults<T extends SortableResult>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If repository IDs are given, then ignore the search value and filter key
|
||||
if (
|
||||
filterSortState?.repositoryIds &&
|
||||
filterSortState.repositoryIds.length > 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Uri, window } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { QueryRunner } from "./query-server";
|
||||
import { basename, join } from "path";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableError } from "./pure/errors";
|
||||
|
||||
@@ -49,7 +49,7 @@ import { EvalLogViewer } from "../eval-log-viewer";
|
||||
import EvalLogTreeBuilder from "../eval-log-tree-builder";
|
||||
import { EvalLogData, parseViewerData } from "../pure/log-summary-parser";
|
||||
import { QueryWithResults } from "../run-queries-shared";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { QueryRunner } from "../query-server";
|
||||
import { VariantAnalysisManager } from "../variant-analysis/variant-analysis-manager";
|
||||
import { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
|
||||
import { getTotalResultCount } from "../variant-analysis/shared/variant-analysis";
|
||||
@@ -235,11 +235,8 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
"codeQLQueryHistory.sortByDate": this.handleSortByDate.bind(this),
|
||||
"codeQLQueryHistory.sortByCount": this.handleSortByCount.bind(this),
|
||||
|
||||
"codeQLQueryHistory.openQueryTitleMenu": this.handleOpenQuery.bind(this),
|
||||
"codeQLQueryHistory.openQueryContextMenu":
|
||||
this.handleOpenQuery.bind(this),
|
||||
"codeQLQueryHistory.removeHistoryItemTitleMenu":
|
||||
this.handleRemoveHistoryItem.bind(this),
|
||||
"codeQLQueryHistory.removeHistoryItemContextMenu":
|
||||
this.handleRemoveHistoryItem.bind(this),
|
||||
"codeQLQueryHistory.removeHistoryItemContextInline":
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
QueryEvaluationInfo,
|
||||
QueryWithResults,
|
||||
} from "./run-queries-shared";
|
||||
import { formatLegacyMessage } from "./legacy-query-server/run-queries";
|
||||
import { formatLegacyMessage } from "./query-server/legacy";
|
||||
import { sarifParser } from "./sarif-parser";
|
||||
|
||||
/**
|
||||
|
||||
5
extensions/ql-vscode/src/query-server/index.ts
Normal file
5
extensions/ql-vscode/src/query-server/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./new-query-runner";
|
||||
export * from "./query-runner";
|
||||
export * from "./query-server-client";
|
||||
export * from "./run-queries";
|
||||
export * from "./server-process";
|
||||
4
extensions/ql-vscode/src/query-server/legacy/index.ts
Normal file
4
extensions/ql-vscode/src/query-server/legacy/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./legacy-query-runner";
|
||||
export * from "./query-server-client";
|
||||
export * from "./run-queries";
|
||||
export * from "./upgrades";
|
||||
@@ -1,16 +1,20 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { Logger } from "../common";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { CodeQLCliServer } from "../../cli";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { Logger } from "../../common";
|
||||
import { DatabaseItem } from "../../local-databases";
|
||||
import {
|
||||
Dataset,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
} from "../pure/legacy-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
} from "../../pure/legacy-messages";
|
||||
import {
|
||||
CoreQueryResults,
|
||||
CoreQueryTarget,
|
||||
QueryRunner,
|
||||
} from "../query-runner";
|
||||
import { QueryOutputDir } from "../../run-queries-shared";
|
||||
import { QueryServerClient } from "./query-server-client";
|
||||
import {
|
||||
clearCacheInDatabase,
|
||||
compileAndRunQueryAgainstDatabaseCore,
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ensureFile } from "fs-extra";
|
||||
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { DisposableObject } from "../../pure/disposable-object";
|
||||
import { CancellationToken } from "vscode";
|
||||
import { createMessageConnection, RequestType } from "vscode-jsonrpc/node";
|
||||
import * as cli from "../cli";
|
||||
import { QueryServerConfig } from "../config";
|
||||
import { Logger, ProgressReporter } from "../common";
|
||||
import * as cli from "../../cli";
|
||||
import { QueryServerConfig } from "../../config";
|
||||
import { Logger, ProgressReporter } from "../../common";
|
||||
import {
|
||||
completeQuery,
|
||||
EvaluationResult,
|
||||
progress,
|
||||
ProgressMessage,
|
||||
WithProgressId,
|
||||
} from "../pure/legacy-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../progress";
|
||||
import { ServerProcess } from "../json-rpc-server";
|
||||
import { App } from "../common/app";
|
||||
} from "../../pure/legacy-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../../progress";
|
||||
import { ServerProcess } from "../server-process";
|
||||
import { App } from "../../common/app";
|
||||
|
||||
type WithProgressReporting = (
|
||||
task: (
|
||||
@@ -3,29 +3,29 @@ import { basename } from "path";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { LSPErrorCodes, ResponseError } from "vscode-languageclient";
|
||||
|
||||
import * as cli from "../cli";
|
||||
import * as cli from "../../cli";
|
||||
import {
|
||||
DatabaseContentsWithDbScheme,
|
||||
DatabaseItem,
|
||||
DatabaseResolver,
|
||||
} from "../local-databases";
|
||||
} from "../../local-databases";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
upgradesTmpDir,
|
||||
} from "../helpers";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { QueryMetadata } from "../pure/interface-types";
|
||||
import { extLogger, Logger } from "../common";
|
||||
import * as messages from "../pure/legacy-messages";
|
||||
import * as newMessages from "../pure/new-messages";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
} from "../../helpers";
|
||||
import { ProgressCallback } from "../../progress";
|
||||
import { QueryMetadata } from "../../pure/interface-types";
|
||||
import { extLogger, Logger } from "../../common";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as newMessages from "../../pure/new-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { compileDatabaseUpgradeSequence } from "./upgrades";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../run-queries-shared";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import { Position } from "../pure/messages-shared";
|
||||
import { QueryEvaluationInfo, QueryOutputDir } from "../../run-queries-shared";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../query-runner";
|
||||
import { Position } from "../../pure/messages-shared";
|
||||
|
||||
export async function compileQuery(
|
||||
qs: qsClient.QueryServerClient,
|
||||
@@ -3,16 +3,16 @@ import {
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
tmpDir,
|
||||
} from "../helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { extLogger } from "../common";
|
||||
import * as messages from "../pure/legacy-messages";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
} from "../../helpers";
|
||||
import { ProgressCallback, UserCancellationException } from "../../progress";
|
||||
import { extLogger } from "../../common";
|
||||
import * as messages from "../../pure/legacy-messages";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import * as tmp from "tmp-promise";
|
||||
import { dirname } from "path";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { DatabaseItem } from "../../local-databases";
|
||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
|
||||
/**
|
||||
* Maximum number of lines to include from database upgrade message,
|
||||
165
extensions/ql-vscode/src/query-server/new-query-runner.ts
Normal file
165
extensions/ql-vscode/src/query-server/new-query-runner.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
clearCache,
|
||||
ClearCacheParams,
|
||||
clearPackCache,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "./query-runner";
|
||||
import { QueryServerClient } from "./query-server-client";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
import * as vscode from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { Logger } from "../common";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
|
||||
export class NewQueryRunner extends QueryRunner {
|
||||
constructor(public readonly qs: QueryServerClient) {
|
||||
super();
|
||||
}
|
||||
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return this.qs.config.customLogDirectory;
|
||||
}
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
await this.qs.restartQueryServer(progress, token);
|
||||
}
|
||||
|
||||
onStart(
|
||||
callBack: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
this.qs.onDidStartQueryServer(callBack);
|
||||
}
|
||||
|
||||
async clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error("Can't clear the cache in an invalid database.");
|
||||
}
|
||||
|
||||
const db = dbItem.databaseUri.fsPath;
|
||||
const params: ClearCacheParams = {
|
||||
dryRun: false,
|
||||
db,
|
||||
};
|
||||
await this.qs.sendRequest(clearCache, params, token, progress);
|
||||
}
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
async deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
deregisterDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
async registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
registerDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async clearPackCache(): Promise<void> {
|
||||
await this.qs.sendRequest(clearPackCache, {});
|
||||
}
|
||||
|
||||
async upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
const yesItem = { title: "Yes", isCloseAffordance: false };
|
||||
const noItem = { title: "No", isCloseAffordance: true };
|
||||
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
|
||||
|
||||
const message = `Should the database ${dbItem.databaseUri.fsPath} be destructively upgraded?\n\nThis should not be necessary to run queries
|
||||
as we will non-destructively update it anyway.`;
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
...dialogOptions,
|
||||
);
|
||||
|
||||
if (chosenItem !== yesItem) {
|
||||
throw new UserCancellationException(
|
||||
"User cancelled the database upgrade.",
|
||||
);
|
||||
}
|
||||
await this.qs.sendRequest(
|
||||
upgradeDatabase,
|
||||
{
|
||||
db: dbItem.databaseUri.fsPath,
|
||||
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||
},
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +1,72 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import {
|
||||
clearCache,
|
||||
ClearCacheParams,
|
||||
clearPackCache,
|
||||
deregisterDatabases,
|
||||
registerDatabases,
|
||||
upgradeDatabase,
|
||||
} from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner";
|
||||
import { QueryServerClient } from "./queryserver-client";
|
||||
import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries";
|
||||
import * as vscode from "vscode";
|
||||
import { getOnDiskWorkspaceFolders } from "../helpers";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { Logger } from "../common";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import { Position, QueryResultType } from "../pure/new-messages";
|
||||
import { BaseLogger, Logger } from "../common";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export class NewQueryRunner extends QueryRunner {
|
||||
constructor(public readonly qs: QueryServerClient) {
|
||||
super();
|
||||
}
|
||||
export interface CoreQueryTarget {
|
||||
/** The full path to the query. */
|
||||
queryPath: string;
|
||||
/**
|
||||
* Optional position of text to be used as QuickEval target. This need not be in the same file as
|
||||
* `query`.
|
||||
*/
|
||||
quickEvalPosition?: Position;
|
||||
}
|
||||
|
||||
get cliServer(): CodeQLCliServer {
|
||||
return this.qs.cliServer;
|
||||
}
|
||||
export interface CoreQueryResults {
|
||||
readonly resultType: QueryResultType;
|
||||
readonly message: string | undefined;
|
||||
readonly evaluationTime: number;
|
||||
}
|
||||
|
||||
get customLogDirectory(): string | undefined {
|
||||
return this.qs.config.customLogDirectory;
|
||||
}
|
||||
export interface CoreQueryRun {
|
||||
readonly queryTarget: CoreQueryTarget;
|
||||
readonly dbPath: string;
|
||||
readonly id: string;
|
||||
readonly outputDir: QueryOutputDir;
|
||||
|
||||
get logger(): Logger {
|
||||
return this.qs.logger;
|
||||
}
|
||||
|
||||
async restartQueryServer(
|
||||
evaluate(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
await this.qs.restartQueryServer(progress, token);
|
||||
}
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery>;
|
||||
}
|
||||
|
||||
onStart(
|
||||
callBack: (
|
||||
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
|
||||
export type CoreCompletedQuery = CoreQueryResults &
|
||||
Omit<CoreQueryRun, "evaluate">;
|
||||
|
||||
export abstract class QueryRunner {
|
||||
abstract restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract cliServer: CodeQLCliServer;
|
||||
abstract customLogDirectory: string | undefined;
|
||||
abstract logger: Logger;
|
||||
|
||||
abstract onStart(
|
||||
arg0: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
this.qs.onDidStartQueryServer(callBack);
|
||||
}
|
||||
|
||||
async clearCacheInDatabase(
|
||||
): void;
|
||||
abstract clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error("Can't clear the cache in an invalid database.");
|
||||
}
|
||||
): Promise<void>;
|
||||
|
||||
const db = dbItem.databaseUri.fsPath;
|
||||
const params: ClearCacheParams = {
|
||||
dryRun: false,
|
||||
db,
|
||||
};
|
||||
await this.qs.sendRequest(clearCache, params, token, progress);
|
||||
}
|
||||
|
||||
public async compileAndRunQueryAgainstDatabaseCore(
|
||||
/**
|
||||
* Overridden in subclasses to evaluate the query via the query server and return the results.
|
||||
*/
|
||||
public abstract compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
@@ -78,88 +76,76 @@ export class NewQueryRunner extends QueryRunner {
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: Logger,
|
||||
): Promise<CoreQueryResults> {
|
||||
return await compileAndRunQueryAgainstDatabaseCore(
|
||||
this.qs,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreQueryResults>;
|
||||
|
||||
abstract deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract clearPackCache(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
|
||||
* called to actually evaluate the query. The returned object also contains information about the
|
||||
* query evaluation that is known even before evaluation starts, including the unique ID of the
|
||||
* evaluation and the path to its output directory.
|
||||
*/
|
||||
public createQueryRun(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
queryStorageDir: string,
|
||||
id = `${basename(query.queryPath)}-${nanoid()}`,
|
||||
templates: Record<string, string> | undefined,
|
||||
): CoreQueryRun {
|
||||
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
|
||||
|
||||
return {
|
||||
queryTarget: query,
|
||||
dbPath,
|
||||
query,
|
||||
generateEvalLog,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
id,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
async deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
deregisterDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
async registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
if (dbItem.contents) {
|
||||
const databases: string[] = [dbItem.databaseUri.fsPath];
|
||||
await this.qs.sendRequest(
|
||||
registerDatabases,
|
||||
{ databases },
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async clearPackCache(): Promise<void> {
|
||||
await this.qs.sendRequest(clearPackCache, {});
|
||||
}
|
||||
|
||||
async upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
const yesItem = { title: "Yes", isCloseAffordance: false };
|
||||
const noItem = { title: "No", isCloseAffordance: true };
|
||||
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
|
||||
|
||||
const message = `Should the database ${dbItem.databaseUri.fsPath} be destructively upgraded?\n\nThis should not be necessary to run queries
|
||||
as we will non-destructively update it anyway.`;
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
...dialogOptions,
|
||||
);
|
||||
|
||||
if (chosenItem !== yesItem) {
|
||||
throw new UserCancellationException(
|
||||
"User cancelled the database upgrade.",
|
||||
);
|
||||
}
|
||||
await this.qs.sendRequest(
|
||||
upgradeDatabase,
|
||||
{
|
||||
db: dbItem.databaseUri.fsPath,
|
||||
additionalPacks: getOnDiskWorkspaceFolders(),
|
||||
evaluate: async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery> => {
|
||||
return {
|
||||
id,
|
||||
outputDir,
|
||||
dbPath,
|
||||
queryTarget: query,
|
||||
...(await this.compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath,
|
||||
query,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
)),
|
||||
};
|
||||
},
|
||||
token,
|
||||
progress,
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
WithProgressId,
|
||||
} from "../pure/new-messages";
|
||||
import { ProgressCallback, ProgressTask } from "../progress";
|
||||
import { ServerProcess } from "../json-rpc-server";
|
||||
import { ServerProcess } from "./server-process";
|
||||
import { App } from "../common/app";
|
||||
|
||||
type ServerOpts = {
|
||||
@@ -2,8 +2,8 @@ import { CancellationToken } from "vscode";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import * as messages from "../pure/new-messages";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import * as qsClient from "./queryserver-client";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "../queryRunner";
|
||||
import * as qsClient from "./query-server-client";
|
||||
import { CoreQueryResults, CoreQueryTarget } from "./query-runner";
|
||||
import { Logger } from "../common";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Logger } from "./common";
|
||||
import { Logger } from "../common";
|
||||
import * as cp from "child_process";
|
||||
import { Disposable } from "vscode";
|
||||
import { MessageConnection } from "vscode-jsonrpc";
|
||||
@@ -1,151 +0,0 @@
|
||||
import { CancellationToken } from "vscode";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { ProgressCallback } from "./progress";
|
||||
import { DatabaseItem } from "./local-databases";
|
||||
import { QueryOutputDir } from "./run-queries-shared";
|
||||
import { Position, QueryResultType } from "./pure/new-messages";
|
||||
import { BaseLogger, Logger } from "./common";
|
||||
import { basename, join } from "path";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface CoreQueryTarget {
|
||||
/** The full path to the query. */
|
||||
queryPath: string;
|
||||
/**
|
||||
* Optional position of text to be used as QuickEval target. This need not be in the same file as
|
||||
* `query`.
|
||||
*/
|
||||
quickEvalPosition?: Position;
|
||||
}
|
||||
|
||||
export interface CoreQueryResults {
|
||||
readonly resultType: QueryResultType;
|
||||
readonly message: string | undefined;
|
||||
readonly evaluationTime: number;
|
||||
}
|
||||
|
||||
export interface CoreQueryRun {
|
||||
readonly queryTarget: CoreQueryTarget;
|
||||
readonly dbPath: string;
|
||||
readonly id: string;
|
||||
readonly outputDir: QueryOutputDir;
|
||||
|
||||
evaluate(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery>;
|
||||
}
|
||||
|
||||
/** Includes both the results of the query and the initial information from `CoreQueryRun`. */
|
||||
export type CoreCompletedQuery = CoreQueryResults &
|
||||
Omit<CoreQueryRun, "evaluate">;
|
||||
|
||||
export abstract class QueryRunner {
|
||||
abstract restartQueryServer(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract cliServer: CodeQLCliServer;
|
||||
abstract customLogDirectory: string | undefined;
|
||||
abstract logger: Logger;
|
||||
|
||||
abstract onStart(
|
||||
arg0: (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) => Promise<void>,
|
||||
): void;
|
||||
abstract clearCacheInDatabase(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Overridden in subclasses to evaluate the query via the query server and return the results.
|
||||
*/
|
||||
public abstract compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
generateEvalLog: boolean,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates: Record<string, string> | undefined,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreQueryResults>;
|
||||
|
||||
abstract deregisterDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract registerDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbItem: DatabaseItem,
|
||||
): Promise<void>;
|
||||
|
||||
abstract upgradeDatabaseExplicit(
|
||||
dbItem: DatabaseItem,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void>;
|
||||
|
||||
abstract clearPackCache(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be
|
||||
* called to actually evaluate the query. The returned object also contains information about the
|
||||
* query evaluation that is known even before evaluation starts, including the unique ID of the
|
||||
* evaluation and the path to its output directory.
|
||||
*/
|
||||
public createQueryRun(
|
||||
dbPath: string,
|
||||
query: CoreQueryTarget,
|
||||
generateEvalLog: boolean,
|
||||
additionalPacks: string[],
|
||||
extensionPacks: string[] | undefined,
|
||||
queryStorageDir: string,
|
||||
id = `${basename(query.queryPath)}-${nanoid()}`,
|
||||
templates: Record<string, string> | undefined,
|
||||
): CoreQueryRun {
|
||||
const outputDir = new QueryOutputDir(join(queryStorageDir, id));
|
||||
|
||||
return {
|
||||
queryTarget: query,
|
||||
dbPath,
|
||||
id,
|
||||
outputDir,
|
||||
evaluate: async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
logger: BaseLogger,
|
||||
): Promise<CoreCompletedQuery> => {
|
||||
return {
|
||||
id,
|
||||
outputDir,
|
||||
dbPath,
|
||||
queryTarget: query,
|
||||
...(await this.compileAndRunQueryAgainstDatabaseCore(
|
||||
dbPath,
|
||||
query,
|
||||
additionalPacks,
|
||||
extensionPacks,
|
||||
generateEvalLog,
|
||||
outputDir,
|
||||
progress,
|
||||
token,
|
||||
templates,
|
||||
logger,
|
||||
)),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,12 @@ import { QlPackGenerator } from "./qlpack-generator";
|
||||
import { DatabaseItem, DatabaseManager } from "./local-databases";
|
||||
import { ProgressCallback, UserCancellationException } from "./progress";
|
||||
import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher";
|
||||
import { existsSync } from "fs";
|
||||
import {
|
||||
getSkeletonWizardFolder,
|
||||
isCodespacesTemplate,
|
||||
setSkeletonWizardFolder,
|
||||
} from "./config";
|
||||
import { existsSync } from "fs-extra";
|
||||
|
||||
type QueryLanguagesToDatabaseMap = Record<string, string>;
|
||||
|
||||
@@ -55,7 +60,7 @@ export class SkeletonQueryWizard {
|
||||
return;
|
||||
}
|
||||
|
||||
this.qlPackStoragePath = getFirstWorkspaceFolder();
|
||||
this.qlPackStoragePath = await this.determineStoragePath();
|
||||
|
||||
const skeletonPackAlreadyExists =
|
||||
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
|
||||
@@ -97,6 +102,38 @@ export class SkeletonQueryWizard {
|
||||
});
|
||||
}
|
||||
|
||||
public async determineStoragePath() {
|
||||
const firstStorageFolder = getFirstWorkspaceFolder();
|
||||
|
||||
if (isCodespacesTemplate()) {
|
||||
return firstStorageFolder;
|
||||
}
|
||||
|
||||
let storageFolder = getSkeletonWizardFolder();
|
||||
|
||||
if (storageFolder === undefined || !existsSync(storageFolder)) {
|
||||
storageFolder = await Window.showInputBox({
|
||||
title:
|
||||
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
|
||||
value: firstStorageFolder,
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (storageFolder === undefined) {
|
||||
throw new UserCancellationException("No storage folder entered.");
|
||||
}
|
||||
|
||||
if (!existsSync(storageFolder)) {
|
||||
throw new UserCancellationException(
|
||||
"Invalid folder. Must be a folder that already exists.",
|
||||
);
|
||||
}
|
||||
|
||||
await setSkeletonWizardFolder(storageFolder);
|
||||
return storageFolder;
|
||||
}
|
||||
|
||||
private async chooseLanguage() {
|
||||
this.progress({
|
||||
message: "Choose language",
|
||||
@@ -216,11 +253,19 @@ export class SkeletonQueryWizard {
|
||||
}
|
||||
|
||||
private async selectOrDownloadDatabase() {
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
|
||||
if (this.qlPackStoragePath === undefined) {
|
||||
throw new Error("QL Pack storage path is undefined");
|
||||
}
|
||||
|
||||
const existingDatabaseItem = await this.findExistingDatabaseItem();
|
||||
const existingDatabaseItem =
|
||||
await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
this.language,
|
||||
this.databaseManager.databaseItems,
|
||||
);
|
||||
|
||||
if (existingDatabaseItem) {
|
||||
// select the found database
|
||||
@@ -231,59 +276,68 @@ export class SkeletonQueryWizard {
|
||||
}
|
||||
}
|
||||
|
||||
public async findDatabaseItemByNwo(
|
||||
public static async findDatabaseItemByNwo(
|
||||
language: string,
|
||||
databaseNwo: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const dbItems = databaseItems || [];
|
||||
const dbs = dbItems.filter(
|
||||
(db) =>
|
||||
db.language === language &&
|
||||
db.name === databaseNwo &&
|
||||
db.error === undefined,
|
||||
const dbs = databaseItems.filter(
|
||||
(db) => db.language === language && db.name === databaseNwo,
|
||||
);
|
||||
|
||||
if (dbs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return dbs[0];
|
||||
return dbs.pop();
|
||||
}
|
||||
|
||||
public async findDatabaseItemByLanguage(
|
||||
public static async findDatabaseItemByLanguage(
|
||||
language: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const dbItems = databaseItems || [];
|
||||
const dbs = dbItems.filter(
|
||||
(db) => db.language === language && db.error === undefined,
|
||||
);
|
||||
if (dbs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return dbs[0];
|
||||
const dbs = databaseItems.filter((db) => db.language === language);
|
||||
|
||||
return dbs.pop();
|
||||
}
|
||||
|
||||
private async findExistingDatabaseItem() {
|
||||
if (this.language === undefined) {
|
||||
throw new Error("Language is undefined");
|
||||
}
|
||||
public static async findExistingDatabaseItem(
|
||||
language: string,
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const defaultDatabaseNwo = QUERY_LANGUAGE_TO_DATABASE_REPO[language];
|
||||
|
||||
const defaultDatabaseNwo = QUERY_LANGUAGE_TO_DATABASE_REPO[this.language];
|
||||
const dbItems = await SkeletonQueryWizard.sortDatabaseItemsByDateAdded(
|
||||
databaseItems,
|
||||
);
|
||||
|
||||
const defaultDatabaseItem = await this.findDatabaseItemByNwo(
|
||||
this.language,
|
||||
const defaultDatabaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
language,
|
||||
defaultDatabaseNwo,
|
||||
this.databaseManager.databaseItems,
|
||||
dbItems,
|
||||
);
|
||||
|
||||
if (defaultDatabaseItem !== undefined) {
|
||||
return defaultDatabaseItem;
|
||||
}
|
||||
|
||||
return await this.findDatabaseItemByLanguage(
|
||||
this.language,
|
||||
this.databaseManager.databaseItems,
|
||||
return await SkeletonQueryWizard.findDatabaseItemByLanguage(
|
||||
language,
|
||||
dbItems,
|
||||
);
|
||||
}
|
||||
|
||||
public static async sortDatabaseItemsByDateAdded(
|
||||
databaseItems: readonly DatabaseItem[],
|
||||
) {
|
||||
const validDbItems = databaseItems.filter((db) => db.error === undefined);
|
||||
|
||||
return validDbItems.sort((a, b) => {
|
||||
if (a.dateAdded === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.dateAdded === undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a.dateAdded - b.dateAdded;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,22 @@ const Template: ComponentStory<typeof DataExtensionsEditorComponent> = (
|
||||
|
||||
export const DataExtensionsEditor = Template.bind({});
|
||||
DataExtensionsEditor.args = {
|
||||
initialExtensionPackName: "codeql/sql2o-models",
|
||||
initialModelFilename:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/models/sql2o.yml",
|
||||
initialViewState: {
|
||||
extensionPackModelFile: {
|
||||
extensionPack: {
|
||||
path: "/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o",
|
||||
yamlPath:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/codeql-pack.yml",
|
||||
name: "codeql/sql2o-models",
|
||||
version: "0.0.0",
|
||||
extensionTargets: {},
|
||||
dataExtensions: [],
|
||||
},
|
||||
filename:
|
||||
"/home/user/vscode-codeql-starter/codeql-custom-queries-java/sql2o/models/sql2o.yml",
|
||||
},
|
||||
modelFileExists: true,
|
||||
},
|
||||
initialExternalApiUsages: [
|
||||
{
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
|
||||
import { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Repositories Filter",
|
||||
component: RepositoriesFilterComponent,
|
||||
argTypes: {
|
||||
value: {
|
||||
control: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesFilterComponent>;
|
||||
|
||||
export const RepositoriesFilter = () => {
|
||||
const [value, setValue] = useState(FilterKey.All);
|
||||
|
||||
return <RepositoriesFilterComponent value={value} onChange={setValue} />;
|
||||
};
|
||||
@@ -20,6 +20,7 @@ import { calculateModeledPercentage } from "./modeled";
|
||||
import { LinkIconButton } from "../variant-analysis/LinkIconButton";
|
||||
import { basename } from "../common/path";
|
||||
import { ViewTitle } from "../common";
|
||||
import { DataExtensionEditorViewState } from "../../data-extensions-editor/shared/view-state";
|
||||
|
||||
const DataExtensionsEditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
@@ -31,6 +32,12 @@ const DetailsContainer = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const NonExistingModelFileContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
@@ -47,24 +54,19 @@ const ProgressBar = styled.div<ProgressBarProps>`
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
initialExtensionPackName?: string;
|
||||
initialModelFilename?: string;
|
||||
initialViewState?: DataExtensionEditorViewState;
|
||||
initialExternalApiUsages?: ExternalApiUsage[];
|
||||
initialModeledMethods?: Record<string, ModeledMethod>;
|
||||
};
|
||||
|
||||
export function DataExtensionsEditor({
|
||||
initialExtensionPackName,
|
||||
initialModelFilename,
|
||||
initialViewState,
|
||||
initialExternalApiUsages = [],
|
||||
initialModeledMethods = {},
|
||||
}: Props): JSX.Element {
|
||||
const [extensionPackName, setExtensionPackName] = useState<
|
||||
string | undefined
|
||||
>(initialExtensionPackName);
|
||||
const [modelFilename, setModelFilename] = useState<string | undefined>(
|
||||
initialModelFilename,
|
||||
);
|
||||
const [viewState, setViewState] = useState<
|
||||
DataExtensionEditorViewState | undefined
|
||||
>(initialViewState);
|
||||
|
||||
const [externalApiUsages, setExternalApiUsages] = useState<
|
||||
ExternalApiUsage[]
|
||||
@@ -83,9 +85,8 @@ export function DataExtensionsEditor({
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToDataExtensionsEditorMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case "setDataExtensionEditorInitialData":
|
||||
setExtensionPackName(msg.extensionPackName);
|
||||
setModelFilename(msg.modelFilename);
|
||||
case "setDataExtensionEditorViewState":
|
||||
setViewState(msg.viewState);
|
||||
break;
|
||||
case "setExternalApiUsages":
|
||||
setExternalApiUsages(msg.externalApiUsages);
|
||||
@@ -181,17 +182,27 @@ export function DataExtensionsEditor({
|
||||
<>
|
||||
<ViewTitle>Data extensions editor</ViewTitle>
|
||||
<DetailsContainer>
|
||||
{extensionPackName && (
|
||||
<LinkIconButton onClick={onOpenExtensionPackClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
{extensionPackName}
|
||||
</LinkIconButton>
|
||||
)}
|
||||
{modelFilename && (
|
||||
<LinkIconButton onClick={onOpenModelFileClick}>
|
||||
<span slot="start" className="codicon codicon-file-code"></span>
|
||||
{basename(modelFilename)}
|
||||
</LinkIconButton>
|
||||
{viewState?.extensionPackModelFile && (
|
||||
<>
|
||||
<LinkIconButton onClick={onOpenExtensionPackClick}>
|
||||
<span slot="start" className="codicon codicon-package"></span>
|
||||
{viewState.extensionPackModelFile.extensionPack.name}
|
||||
</LinkIconButton>
|
||||
{viewState.modelFileExists ? (
|
||||
<LinkIconButton onClick={onOpenModelFileClick}>
|
||||
<span
|
||||
slot="start"
|
||||
className="codicon codicon-file-code"
|
||||
></span>
|
||||
{basename(viewState.extensionPackModelFile.filename)}
|
||||
</LinkIconButton>
|
||||
) : (
|
||||
<NonExistingModelFileContainer>
|
||||
<span className="codicon codicon-file-code"></span>
|
||||
{basename(viewState.extensionPackModelFile.filename)}
|
||||
</NonExistingModelFileContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div>{modeledPercentage.toFixed(2)}% modeled</div>
|
||||
<div>{unModeledPercentage.toFixed(2)}% unmodeled</div>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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 { FilterKey } from "../../pure/variant-analysis-filter-sort";
|
||||
|
||||
const Dropdown = styled(VSCodeDropdown)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
value: FilterKey;
|
||||
onChange: (value: FilterKey) => void;
|
||||
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const RepositoriesFilter = ({ value, onChange, className }: Props) => {
|
||||
const handleInput = useCallback(
|
||||
(e: InputEvent) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
onChange(target.value as FilterKey);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown value={value} onInput={handleInput} className={className}>
|
||||
<Codicon name="list-filter" label="Filter..." slot="indicator" />
|
||||
<VSCodeOption value={FilterKey.All}>All</VSCodeOption>
|
||||
<VSCodeOption value={FilterKey.WithResults}>With results</VSCodeOption>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -2,11 +2,13 @@ import * as React from "react";
|
||||
import { Dispatch, SetStateAction, useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
FilterKey,
|
||||
RepositoriesFilterSortState,
|
||||
SortKey,
|
||||
} from "../../pure/variant-analysis-filter-sort";
|
||||
import { RepositoriesSearch } from "./RepositoriesSearch";
|
||||
import { RepositoriesSort } from "./RepositoriesSort";
|
||||
import { RepositoriesFilter } from "./RepositoriesFilter";
|
||||
|
||||
type Props = {
|
||||
value: RepositoriesFilterSortState;
|
||||
@@ -25,6 +27,10 @@ const RepositoriesSearchColumn = styled(RepositoriesSearch)`
|
||||
flex: 3;
|
||||
`;
|
||||
|
||||
const RepositoriesFilterColumn = styled(RepositoriesFilter)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const RepositoriesSortColumn = styled(RepositoriesSort)`
|
||||
flex: 1;
|
||||
`;
|
||||
@@ -40,6 +46,16 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleFilterKeyChange = useCallback(
|
||||
(filterKey: FilterKey) => {
|
||||
onChange((oldValue) => ({
|
||||
...oldValue,
|
||||
filterKey,
|
||||
}));
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const handleSortKeyChange = useCallback(
|
||||
(sortKey: SortKey) => {
|
||||
onChange((oldValue) => ({
|
||||
@@ -56,6 +72,10 @@ export const RepositoriesSearchSortRow = ({ value, onChange }: Props) => {
|
||||
value={value.searchValue}
|
||||
onChange={handleSearchValueChange}
|
||||
/>
|
||||
<RepositoriesFilterColumn
|
||||
value={value.filterKey}
|
||||
onChange={handleFilterKeyChange}
|
||||
/>
|
||||
<RepositoriesSortColumn
|
||||
value={value.sortKey}
|
||||
onChange={handleSortKeyChange}
|
||||
|
||||
@@ -56,8 +56,8 @@ export const VariantAnalysisSkippedRepositoriesTab = ({
|
||||
}: VariantAnalysisSkippedRepositoriesTabProps) => {
|
||||
const repositories = useMemo(() => {
|
||||
return skippedRepositoryGroup.repositories
|
||||
?.filter((repo) => {
|
||||
return matchesFilter(repo, filterSortState);
|
||||
?.filter((repository) => {
|
||||
return matchesFilter({ repository }, filterSortState);
|
||||
})
|
||||
?.sort(compareRepository(filterSortState));
|
||||
}, [filterSortState, skippedRepositoryGroup.repositories]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
defaultFilterSortState,
|
||||
filterAndSortRepositoriesWithResults,
|
||||
filterAndSortRepositoriesWithResultsByName,
|
||||
FilterKey,
|
||||
matchesFilter,
|
||||
SortKey,
|
||||
} from "../../src/pure/variant-analysis-filter-sort";
|
||||
@@ -13,32 +14,93 @@ describe(matchesFilter.name, () => {
|
||||
fullName: "github/codeql",
|
||||
};
|
||||
|
||||
const testCases = [
|
||||
{ searchValue: "", matches: true },
|
||||
{ searchValue: "github/codeql", matches: true },
|
||||
{ searchValue: "github", matches: true },
|
||||
{ searchValue: "git", matches: true },
|
||||
{ searchValue: "codeql", matches: true },
|
||||
{ searchValue: "code", matches: true },
|
||||
{ searchValue: "ql", matches: true },
|
||||
{ searchValue: "/", matches: true },
|
||||
{ searchValue: "gothub/codeql", matches: false },
|
||||
{ searchValue: "hello", matches: false },
|
||||
{ searchValue: "cod*ql", matches: false },
|
||||
{ searchValue: "cod?ql", matches: false },
|
||||
];
|
||||
describe("searchValue", () => {
|
||||
const testCases = [
|
||||
{ searchValue: "", matches: true },
|
||||
{ searchValue: "github/codeql", matches: true },
|
||||
{ searchValue: "github", matches: true },
|
||||
{ searchValue: "git", matches: true },
|
||||
{ searchValue: "codeql", matches: true },
|
||||
{ searchValue: "code", matches: true },
|
||||
{ searchValue: "ql", matches: true },
|
||||
{ searchValue: "/", matches: true },
|
||||
{ searchValue: "gothub/codeql", matches: false },
|
||||
{ searchValue: "hello", matches: false },
|
||||
{ searchValue: "cod*ql", matches: false },
|
||||
{ searchValue: "cod?ql", matches: false },
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
"returns $matches if searching for $searchValue",
|
||||
({ searchValue, matches }) => {
|
||||
test.each(testCases)(
|
||||
"returns $matches if searching for $searchValue",
|
||||
({ searchValue, matches }) => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue,
|
||||
},
|
||||
),
|
||||
).toBe(matches);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("filterKey", () => {
|
||||
it("returns true if filterKey is all and resultCount is positive", () => {
|
||||
expect(
|
||||
matchesFilter(repository, {
|
||||
...defaultFilterSortState,
|
||||
searchValue,
|
||||
}),
|
||||
).toBe(matches);
|
||||
},
|
||||
);
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 1 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is all and resultCount is zero", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 0 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is all and resultCount is undefined", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.All },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true if filterKey is withResults and resultCount is positive", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 1 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false if filterKey is withResults and resultCount is zero", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository, resultCount: 0 },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false if filterKey is withResults and resultCount is undefined", () => {
|
||||
expect(
|
||||
matchesFilter(
|
||||
{ repository },
|
||||
{ ...defaultFilterSortState, filterKey: FilterKey.WithResults },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(compareRepository.name, () => {
|
||||
@@ -349,7 +411,7 @@ describe(filterAndSortRepositoriesWithResultsByName.name, () => {
|
||||
},
|
||||
];
|
||||
|
||||
describe("when sort key is given without filter", () => {
|
||||
describe("when sort key is given without search or filter", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
@@ -365,7 +427,7 @@ describe(filterAndSortRepositoriesWithResultsByName.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and search filter are given", () => {
|
||||
describe("when sort key and search are given without filter", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
@@ -376,6 +438,30 @@ describe(filterAndSortRepositoriesWithResultsByName.name, () => {
|
||||
).toEqual([repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and filter withResults are given without search", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResultsByName(repositories, {
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "r",
|
||||
}),
|
||||
).toEqual([repositories[3]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
@@ -410,7 +496,7 @@ describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
},
|
||||
];
|
||||
|
||||
describe("when sort key is given without filter", () => {
|
||||
describe("when sort key is given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
@@ -426,7 +512,7 @@ describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and search filter are given", () => {
|
||||
describe("when sort key and search are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
@@ -438,12 +524,49 @@ describe(filterAndSortRepositoriesWithResults.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search filter, and repository ids are given", () => {
|
||||
describe("when sort key and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
}),
|
||||
).toEqual([repositories[3], repositories[2], repositories[0]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, and filter withResults are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "r",
|
||||
}),
|
||||
).toEqual([repositories[3]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when sort key, search, filter withResults, and repository ids are given", () => {
|
||||
it("returns the correct results", () => {
|
||||
expect(
|
||||
filterAndSortRepositoriesWithResults(repositories, {
|
||||
sortKey: SortKey.ResultsCount,
|
||||
filterKey: FilterKey.WithResults,
|
||||
searchValue: "la",
|
||||
repositoryIds: [
|
||||
repositories[1].repository.id,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import * as CodeQLProtocol from "../../../../src/debugger/debug-protocol";
|
||||
import { DisposableObject } from "../../../../src/pure/disposable-object";
|
||||
import { QueryResultType } from "../../../../src/pure/legacy-messages";
|
||||
import { CoreCompletedQuery } from "../../../../src/queryRunner";
|
||||
import { CoreCompletedQuery } from "../../../../src/query-server/query-runner";
|
||||
import { QueryOutputDir } from "../../../../src/run-queries-shared";
|
||||
import {
|
||||
QLDebugArgs,
|
||||
|
||||
@@ -4,11 +4,11 @@ import { dirSync } from "tmp";
|
||||
import { pathToFileURL } from "url";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/legacy-messages";
|
||||
import * as qsClient from "../../../src/legacy-query-server/queryserver-client";
|
||||
import * as qsClient from "../../../src/query-server/legacy/query-server-client";
|
||||
import * as cli from "../../../src/cli";
|
||||
import { CellValue } from "../../../src/pure/bqrs-cli-types";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
|
||||
import { QueryServerClient } from "../../../src/query-server/legacy/query-server-client";
|
||||
import { extLogger, ProgressReporter } from "../../../src/common";
|
||||
import { createMockApp } from "../../__mocks__/appMock";
|
||||
import { getActivatedExtension } from "../global.helper";
|
||||
|
||||
@@ -2,11 +2,11 @@ import { join, basename } from "path";
|
||||
import { dirSync } from "tmp";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import * as messages from "../../../src/pure/new-messages";
|
||||
import * as qsClient from "../../../src/query-server/queryserver-client";
|
||||
import * as qsClient from "../../../src/query-server/query-server-client";
|
||||
import * as cli from "../../../src/cli";
|
||||
import { CellValue } from "../../../src/pure/bqrs-cli-types";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { QueryServerClient } from "../../../src/query-server/queryserver-client";
|
||||
import { QueryServerClient } from "../../../src/query-server/query-server-client";
|
||||
import { extLogger, ProgressReporter } from "../../../src/common";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
import { ensureTestDatabase, getActivatedExtension } from "../global.helper";
|
||||
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
} from "../global.helper";
|
||||
import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli";
|
||||
import { describeWithCodeQL } from "../cli";
|
||||
import { CoreCompletedQuery, QueryRunner } from "../../../src/queryRunner";
|
||||
import {
|
||||
CoreCompletedQuery,
|
||||
QueryRunner,
|
||||
} from "../../../src/query-server/query-runner";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import { LocalQueries } from "../../../src/local-queries";
|
||||
import { QueryResultType } from "../../../src/pure/new-messages";
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import * as databaseFetcher from "../../../src/databaseFetcher";
|
||||
import { createMockDB } from "../../factories/databases/databases";
|
||||
import { asError } from "../../../src/pure/helpers-pure";
|
||||
import { Setting } from "../../../src/config";
|
||||
|
||||
describe("SkeletonQueryWizard", () => {
|
||||
let mockCli: CodeQLCliServer;
|
||||
@@ -29,6 +30,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
let dir: tmp.DirResult;
|
||||
let storagePath: string;
|
||||
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
|
||||
let generateSpy: jest.SpiedFunction<
|
||||
typeof QlPackGenerator.prototype.generate
|
||||
>;
|
||||
@@ -93,6 +95,9 @@ describe("SkeletonQueryWizard", () => {
|
||||
quickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockResolvedValueOnce(mockedQuickPickItem(chosenLanguage));
|
||||
showInputBoxSpy = jest
|
||||
.spyOn(window, "showInputBox")
|
||||
.mockResolvedValue(storagePath);
|
||||
generateSpy = jest
|
||||
.spyOn(QlPackGenerator.prototype, "generate")
|
||||
.mockResolvedValue(undefined);
|
||||
@@ -315,7 +320,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
|
||||
jest.spyOn(mockDbItem, "name", "get").mockReturnValue("mock-name");
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
const databaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
mockDbItem.language,
|
||||
mockDbItem.name,
|
||||
[mockDbItem, mockDbItem2],
|
||||
@@ -325,37 +330,6 @@ describe("SkeletonQueryWizard", () => {
|
||||
JSON.stringify(mockDbItem),
|
||||
);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest.spyOn(mockDbItem, "name", "get").mockReturnValue("mock-name");
|
||||
jest.spyOn(mockDbItem3, "name", "get").mockReturnValue(mockDbItem.name);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
mockDbItem.language,
|
||||
mockDbItem.name,
|
||||
[mockDbItem, mockDbItem2, mockDbItem3],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem3),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item doesn't exist", () => {
|
||||
@@ -363,7 +337,7 @@ describe("SkeletonQueryWizard", () => {
|
||||
const mockDbItem = createMockDB(dir);
|
||||
const mockDbItem2 = createMockDB(dir);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByNwo(
|
||||
const databaseItem = await SkeletonQueryWizard.findDatabaseItemByNwo(
|
||||
"ruby",
|
||||
"mock-nwo",
|
||||
[mockDbItem, mockDbItem2],
|
||||
@@ -384,39 +358,14 @@ describe("SkeletonQueryWizard", () => {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
const databaseItem =
|
||||
await SkeletonQueryWizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
|
||||
expect(databaseItem).toEqual(mockDbItem);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
]);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem3),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item doesn't exist", () => {
|
||||
@@ -424,13 +373,258 @@ describe("SkeletonQueryWizard", () => {
|
||||
const mockDbItem = createMockDB(dir);
|
||||
const mockDbItem2 = createMockDB(dir);
|
||||
|
||||
const databaseItem = await wizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
const databaseItem =
|
||||
await SkeletonQueryWizard.findDatabaseItemByLanguage("ruby", [
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
]);
|
||||
|
||||
expect(databaseItem).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("determineStoragePath", () => {
|
||||
it("should prompt the user to provide a storage path", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ value: storagePath }),
|
||||
);
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
|
||||
it("should write the chosen folder to settings", async () => {
|
||||
const updateValueSpy = jest.spyOn(Setting.prototype, "updateValue");
|
||||
|
||||
await wizard.determineStoragePath();
|
||||
|
||||
expect(updateValueSpy).toHaveBeenCalledWith(storagePath, 1);
|
||||
});
|
||||
|
||||
describe("when the user is using the codespace template", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "pickles-folder");
|
||||
ensureDirSync(storedPath);
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
|
||||
// Set isCodespacesTemplate to true to indicate we are in the codespace template
|
||||
await workspace
|
||||
.getConfiguration("codeQL")
|
||||
.update("codespacesTemplate", true);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL")
|
||||
.update("codespacesTemplate", originalValue);
|
||||
});
|
||||
|
||||
it("should not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is already a saved storage path in settings", () => {
|
||||
describe("when the saved storage path exists", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "pickles-folder");
|
||||
ensureDirSync(storedPath);
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", storedPath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", originalValue);
|
||||
});
|
||||
|
||||
it("should return it and not prompt the user", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).not.toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storedPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the saved storage path does not exist", () => {
|
||||
let originalValue: any;
|
||||
let storedPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
storedPath = join(dir.name, "this-folder-does-not-exist");
|
||||
|
||||
originalValue = workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.get("folder");
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", storedPath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await workspace
|
||||
.getConfiguration("codeQL.createQuery")
|
||||
.update("folder", originalValue);
|
||||
});
|
||||
|
||||
it("should prompt the user for to provide a new folder name", async () => {
|
||||
const chosenPath = await wizard.determineStoragePath();
|
||||
|
||||
expect(showInputBoxSpy).toHaveBeenCalled();
|
||||
expect(chosenPath).toEqual(storagePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sortDatabaseItemsByDateAdded", () => {
|
||||
describe("should return a sorted list", () => {
|
||||
it("should sort the items by dateAdded", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
dateAdded: 678,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const sortedList =
|
||||
await SkeletonQueryWizard.sortDatabaseItemsByDateAdded([
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
mockDbItem4,
|
||||
]);
|
||||
|
||||
expect(sortedList).toEqual([
|
||||
mockDbItem3,
|
||||
mockDbItem2,
|
||||
mockDbItem4,
|
||||
mockDbItem,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should ignore databases with errors", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
dateAdded: 678,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
dateAdded: 345,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "error", "get")
|
||||
.mockReturnValue(asError("database go boom!"));
|
||||
|
||||
const sortedList =
|
||||
await SkeletonQueryWizard.sortDatabaseItemsByDateAdded([
|
||||
mockDbItem,
|
||||
mockDbItem2,
|
||||
mockDbItem3,
|
||||
mockDbItem4,
|
||||
]);
|
||||
|
||||
expect(sortedList).toEqual([mockDbItem2, mockDbItem4, mockDbItem3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findExistingDatabaseItem", () => {
|
||||
describe("when there are multiple items with the same name", () => {
|
||||
it("should choose the latest one", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 456,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 789,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
jest
|
||||
.spyOn(mockDbItem, "name", "get")
|
||||
.mockReturnValue(QUERY_LANGUAGE_TO_DATABASE_REPO["javascript"]);
|
||||
jest
|
||||
.spyOn(mockDbItem2, "name", "get")
|
||||
.mockReturnValue(QUERY_LANGUAGE_TO_DATABASE_REPO["javascript"]);
|
||||
|
||||
const databaseItem = await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
"javascript",
|
||||
[mockDbItem, mockDbItem2, mockDbItem3, mockDbItem4],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are multiple items with the same language", () => {
|
||||
it("should choose the latest one", async () => {
|
||||
const mockDbItem = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 789,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem2 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: 456,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem3 = createMockDB(dir, {
|
||||
language: "ruby",
|
||||
dateAdded: 123,
|
||||
} as FullDatabaseOptions);
|
||||
const mockDbItem4 = createMockDB(dir, {
|
||||
language: "javascript",
|
||||
dateAdded: undefined,
|
||||
} as FullDatabaseOptions);
|
||||
|
||||
const databaseItem = await SkeletonQueryWizard.findExistingDatabaseItem(
|
||||
"javascript",
|
||||
[mockDbItem, mockDbItem2, mockDbItem3, mockDbItem4],
|
||||
);
|
||||
|
||||
expect(JSON.stringify(databaseItem)).toEqual(
|
||||
JSON.stringify(mockDbItem2),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
encodeSourceArchiveUri,
|
||||
} from "../../../src/archive-filesystem-provider";
|
||||
import { testDisposeHandler } from "../test-dispose-handler";
|
||||
import { QueryRunner } from "../../../src/queryRunner";
|
||||
import { QueryRunner } from "../../../src/query-server/query-runner";
|
||||
import * as helpers from "../../../src/helpers";
|
||||
import { Setting } from "../../../src/config";
|
||||
import { QlPackGenerator } from "../../../src/qlpack-generator";
|
||||
|
||||
@@ -6,10 +6,8 @@ import { dir } from "tmp-promise";
|
||||
import { QlpacksInfo, ResolveExtensionsResult } from "../../../../src/cli";
|
||||
import * as helpers from "../../../../src/helpers";
|
||||
|
||||
import {
|
||||
ExtensionPack,
|
||||
pickExtensionPackModelFile,
|
||||
} from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||
import { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker";
|
||||
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
|
||||
|
||||
describe("pickExtensionPackModelFile", () => {
|
||||
let tmpDir: string;
|
||||
|
||||
@@ -635,7 +635,7 @@ describe("prepareCodeTour", () => {
|
||||
|
||||
describe("if the workspace is already open", () => {
|
||||
it("should not open the tutorial workspace", async () => {
|
||||
// Set isCodespaceTemplate to true to indicate the workspace has already been opened
|
||||
// Set isCodespacesTemplate to true to indicate the workspace has already been opened
|
||||
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true);
|
||||
|
||||
// set up directory to have a 'tutorial.code-workspace' file
|
||||
|
||||
@@ -9,7 +9,7 @@ import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { QueryRunner } from "../../../../src/query-server/query-runner";
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { QueryHistoryInfo } from "../../../../src/query-history/query-history-info";
|
||||
import {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { QueryRunner } from "../../../../src/query-server/query-runner";
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { QueryHistoryInfo } from "../../../../src/query-history/query-history-info";
|
||||
import {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DatabaseInfo } from "../../../../../src/pure/interface-types";
|
||||
import { CancellationTokenSource, Uri } from "vscode";
|
||||
import { tmpDir } from "../../../../../src/helpers";
|
||||
import { QueryResultType } from "../../../../../src/pure/legacy-messages";
|
||||
import { QueryInProgress } from "../../../../../src/legacy-query-server/run-queries";
|
||||
import { QueryInProgress } from "../../../../../src/query-server/legacy";
|
||||
import { VariantAnalysisHistoryItem } from "../../../../../src/query-history/variant-analysis-history-item";
|
||||
import { QueryHistoryInfo } from "../../../../../src/query-history/query-history-info";
|
||||
import { createMockVariantAnalysisHistoryItem } from "../../../../factories/query-history/variant-analysis-history-item";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { testDisposeHandler } from "../../test-dispose-handler";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { QueryRunner } from "../../../../src/query-server/query-runner";
|
||||
import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
|
||||
@@ -24,7 +24,7 @@ import { tmpDir } from "../../../src/helpers";
|
||||
import {
|
||||
formatLegacyMessage,
|
||||
QueryInProgress,
|
||||
} from "../../../src/legacy-query-server/run-queries";
|
||||
} from "../../../src/query-server/legacy/run-queries";
|
||||
import {
|
||||
EvaluationResult,
|
||||
QueryResultType,
|
||||
|
||||
@@ -10,14 +10,16 @@ import {
|
||||
} from "../../../src/pure/legacy-messages";
|
||||
import * as config from "../../../src/config";
|
||||
import { tmpDir } from "../../../src/helpers";
|
||||
import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client";
|
||||
import { CodeQLCliServer } from "../../../src/cli";
|
||||
import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder";
|
||||
import {
|
||||
QueryInProgress,
|
||||
compileQuery as compileQueryLegacy,
|
||||
} from "../../../src/legacy-query-server/run-queries";
|
||||
import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner";
|
||||
} from "../../../src/query-server/legacy/run-queries";
|
||||
import {
|
||||
LegacyQueryRunner,
|
||||
QueryServerClient,
|
||||
} from "../../../src/query-server/legacy";
|
||||
import { DatabaseItem } from "../../../src/local-databases";
|
||||
import { DeepPartial, mockedObject } from "../utils/mocking.helpers";
|
||||
import { BqrsKind } from "../../../src/pure/bqrs-cli-types";
|
||||
|
||||
Reference in New Issue
Block a user