Merge branch 'main' into elena/force-workspace-in-codespace
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## [UNRELEASED]
|
||||
|
||||
- Show data flow paths of a variant analysis in a new tab
|
||||
- Show labels of entities in exported CSV results [#2170](https://github.com/github/vscode-codeql/pull/2170)
|
||||
|
||||
## 1.8.0 - 9 March 2023
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CommandManager } from "../packages/commands";
|
||||
import type { Uri } from "vscode";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
@@ -9,6 +11,12 @@ export type SelectionCommandFunction<Item> = (
|
||||
multiSelect: Item[],
|
||||
) => Promise<void>;
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command on a selection is invoked when canSelectMany is false.
|
||||
export type SingleSelectionCommandFunction<Item> = (
|
||||
singleItem: Item,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Contains type definitions for all commands used by the extension.
|
||||
*
|
||||
@@ -53,6 +61,31 @@ export type QueryHistoryCommands = {
|
||||
"codeQLQueryHistory.copyRepoList": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
};
|
||||
|
||||
// Commands used for the local databases panel
|
||||
export type LocalDatabasesCommands = {
|
||||
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
|
||||
"codeQL.setDefaultTourDatabase": () => Promise<void>;
|
||||
"codeQL.upgradeCurrentDatabase": () => Promise<void>;
|
||||
"codeQL.clearCache": () => Promise<void>;
|
||||
|
||||
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseArchive": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseInternet": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseGithub": () => Promise<void>;
|
||||
"codeQLDatabases.setCurrentDatabase": (
|
||||
databaseItem: DatabaseItem,
|
||||
) => Promise<void>;
|
||||
"codeQLDatabases.sortByName": () => Promise<void>;
|
||||
"codeQLDatabases.sortByDateAdded": () => Promise<void>;
|
||||
"codeQLDatabases.removeOrphanedDatabases": () => Promise<void>;
|
||||
|
||||
"codeQLDatabases.removeDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.upgradeDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.renameDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.openDatabaseFolder": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.addDatabaseSource": SelectionCommandFunction<DatabaseItem>;
|
||||
};
|
||||
|
||||
// Commands tied to variant analysis
|
||||
export type VariantAnalysisCommands = {
|
||||
"codeQL.openVariantAnalysisLogs": (
|
||||
@@ -62,8 +95,23 @@ export type VariantAnalysisCommands = {
|
||||
"codeQL.runVariantAnalysisContextEditor": (uri?: Uri) => Promise<void>;
|
||||
};
|
||||
|
||||
export type DatabasePanelCommands = {
|
||||
"codeQLVariantAnalysisRepositories.openConfigFile": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.addNewDatabase": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.addNewList": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.setupControllerRepository": () => Promise<void>;
|
||||
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItem": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.renameItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.removeItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
};
|
||||
|
||||
export type AllCommands = BaseCommands &
|
||||
QueryHistoryCommands &
|
||||
VariantAnalysisCommands;
|
||||
LocalDatabasesCommands &
|
||||
VariantAnalysisCommands &
|
||||
DatabasePanelCommands;
|
||||
|
||||
export type AppCommandManager = CommandManager<AllCommands>;
|
||||
|
||||
@@ -6,10 +6,12 @@ import { DbConfigStore } from "./config/db-config-store";
|
||||
import { DbManager } from "./db-manager";
|
||||
import { DbPanel } from "./ui/db-panel";
|
||||
import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider";
|
||||
import { DatabasePanelCommands } from "../common/commands";
|
||||
|
||||
export class DbModule extends DisposableObject {
|
||||
public readonly dbManager: DbManager;
|
||||
private readonly dbConfigStore: DbConfigStore;
|
||||
private dbPanel: DbPanel | undefined;
|
||||
|
||||
private constructor(app: App) {
|
||||
super();
|
||||
@@ -26,15 +28,24 @@ export class DbModule extends DisposableObject {
|
||||
return dbModule;
|
||||
}
|
||||
|
||||
public getCommands(): DatabasePanelCommands {
|
||||
if (!this.dbPanel) {
|
||||
throw new Error("Database panel not initialized");
|
||||
}
|
||||
|
||||
return {
|
||||
...this.dbPanel.getCommands(),
|
||||
};
|
||||
}
|
||||
|
||||
private async initialize(app: App): Promise<void> {
|
||||
void extLogger.log("Initializing database module");
|
||||
|
||||
await this.dbConfigStore.initialize();
|
||||
|
||||
const dbPanel = new DbPanel(this.dbManager, app.credentials);
|
||||
await dbPanel.initialize();
|
||||
this.dbPanel = new DbPanel(this.dbManager, app.credentials);
|
||||
|
||||
this.push(dbPanel);
|
||||
this.push(this.dbPanel);
|
||||
this.push(this.dbConfigStore);
|
||||
|
||||
const dbSelectionDecorationProvider = new DbSelectionDecorationProvider();
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { commandRunner, UserCancellationException } from "../../commandRunner";
|
||||
import { UserCancellationException } from "../../commandRunner";
|
||||
import {
|
||||
getNwoFromGitHubUrl,
|
||||
isValidGitHubNwo,
|
||||
@@ -32,6 +32,7 @@ import { getGitHubUrl } from "./db-tree-view-item-action";
|
||||
import { getControllerRepo } from "../../variant-analysis/run-remote-query";
|
||||
import { getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { Credentials } from "../../common/authentication";
|
||||
import { DatabasePanelCommands } from "../../common/commands";
|
||||
|
||||
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
|
||||
kind: string;
|
||||
@@ -72,58 +73,28 @@ export class DbPanel extends DisposableObject {
|
||||
this.push(this.treeView);
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
this.push(
|
||||
commandRunner("codeQLVariantAnalysisRepositories.openConfigFile", () =>
|
||||
this.openConfigFile(),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner("codeQLVariantAnalysisRepositories.addNewDatabase", () =>
|
||||
this.addNewRemoteDatabase(),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner("codeQLVariantAnalysisRepositories.addNewList", () =>
|
||||
this.addNewList(),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
(treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
|
||||
(treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
|
||||
(treeViewItem: DbTreeViewItem) => this.openOnGitHub(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.renameItemContextMenu",
|
||||
(treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.removeItemContextMenu",
|
||||
(treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLVariantAnalysisRepositories.setupControllerRepository",
|
||||
() => this.setupControllerRepository(),
|
||||
),
|
||||
);
|
||||
public getCommands(): DatabasePanelCommands {
|
||||
return {
|
||||
"codeQLVariantAnalysisRepositories.openConfigFile":
|
||||
this.openConfigFile.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.addNewDatabase":
|
||||
this.addNewRemoteDatabase.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.addNewList":
|
||||
this.addNewList.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.setupControllerRepository":
|
||||
this.setupControllerRepository.bind(this),
|
||||
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItem":
|
||||
this.setSelectedItem.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu":
|
||||
this.setSelectedItem.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu":
|
||||
this.openOnGitHub.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.renameItemContextMenu":
|
||||
this.renameItem.bind(this),
|
||||
"codeQLVariantAnalysisRepositories.removeItemContextMenu":
|
||||
this.removeItem.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private async openConfigFile(): Promise<void> {
|
||||
|
||||
@@ -35,7 +35,6 @@ import { CodeQLCliServer } from "./cli";
|
||||
import {
|
||||
CliConfigListener,
|
||||
DistributionConfigListener,
|
||||
isCanary,
|
||||
joinOrderWarningThreshold,
|
||||
MAX_QUERIES,
|
||||
QueryHistoryConfigListener,
|
||||
@@ -110,10 +109,7 @@ import {
|
||||
handleInstallPackDependencies,
|
||||
} from "./packaging";
|
||||
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";
|
||||
import {
|
||||
exportSelectedVariantAnalysisResults,
|
||||
exportVariantAnalysisResults,
|
||||
} from "./variant-analysis/export-results";
|
||||
import { exportSelectedVariantAnalysisResults } from "./variant-analysis/export-results";
|
||||
import { EvalLogViewer } from "./eval-log-viewer";
|
||||
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
||||
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
||||
@@ -651,7 +647,6 @@ async function activateWithInstalledDistribution(
|
||||
getContextStoragePath(ctx),
|
||||
ctx.extensionPath,
|
||||
);
|
||||
databaseUI.init();
|
||||
ctx.subscriptions.push(databaseUI);
|
||||
|
||||
void extLogger.log("Initializing evaluator log viewer.");
|
||||
@@ -1105,6 +1100,8 @@ async function activateWithInstalledDistribution(
|
||||
...getCommands(),
|
||||
...qhm.getCommands(),
|
||||
...variantAnalysisManager.getCommands(),
|
||||
...databaseUI.getCommands(),
|
||||
...dbModule.getCommands(),
|
||||
};
|
||||
|
||||
for (const [commandName, command] of Object.entries(allCommands)) {
|
||||
@@ -1157,35 +1154,10 @@ async function activateWithInstalledDistribution(
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner("codeQL.exportSelectedVariantAnalysisResults", async () => {
|
||||
await exportSelectedVariantAnalysisResults(qhm);
|
||||
await exportSelectedVariantAnalysisResults(variantAnalysisManager, qhm);
|
||||
}),
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.exportVariantAnalysisResults",
|
||||
async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
variantAnalysisId: number,
|
||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||
) => {
|
||||
await exportVariantAnalysisResults(
|
||||
variantAnalysisManager,
|
||||
variantAnalysisId,
|
||||
filterSort,
|
||||
app.credentials,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
},
|
||||
{
|
||||
title: "Exporting variant analysis results",
|
||||
cancellable: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
"codeQL.loadVariantAnalysisRepoResults",
|
||||
@@ -1261,7 +1233,7 @@ async function activateWithInstalledDistribution(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.chooseDatabaseFolder",
|
||||
(progress: ProgressCallback, token: CancellationToken) =>
|
||||
databaseUI.handleChooseDatabaseFolder(progress, token),
|
||||
databaseUI.chooseDatabaseFolder(progress, token),
|
||||
{
|
||||
title: "Choose a Database from a Folder",
|
||||
},
|
||||
@@ -1271,7 +1243,7 @@ async function activateWithInstalledDistribution(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.chooseDatabaseArchive",
|
||||
(progress: ProgressCallback, token: CancellationToken) =>
|
||||
databaseUI.handleChooseDatabaseArchive(progress, token),
|
||||
databaseUI.chooseDatabaseArchive(progress, token),
|
||||
{
|
||||
title: "Choose a Database from an Archive",
|
||||
},
|
||||
@@ -1281,12 +1253,7 @@ async function activateWithInstalledDistribution(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.chooseDatabaseGithub",
|
||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||
const credentials = isCanary() ? app.credentials : undefined;
|
||||
await databaseUI.handleChooseDatabaseGithub(
|
||||
credentials,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
await databaseUI.chooseDatabaseGithub(progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from GitHub",
|
||||
@@ -1297,7 +1264,7 @@ async function activateWithInstalledDistribution(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.chooseDatabaseInternet",
|
||||
(progress: ProgressCallback, token: CancellationToken) =>
|
||||
databaseUI.handleChooseDatabaseInternet(progress, token),
|
||||
databaseUI.chooseDatabaseInternet(progress, token),
|
||||
|
||||
{
|
||||
title: "Adding database from URL",
|
||||
|
||||
@@ -21,11 +21,7 @@ import {
|
||||
DatabaseItem,
|
||||
DatabaseManager,
|
||||
} from "./local-databases";
|
||||
import {
|
||||
commandRunner,
|
||||
commandRunnerWithProgress,
|
||||
ProgressCallback,
|
||||
} from "./commandRunner";
|
||||
import { ProgressCallback, withProgress } from "./commandRunner";
|
||||
import {
|
||||
isLikelyDatabaseRoot,
|
||||
isLikelyDbLanguageFolder,
|
||||
@@ -42,8 +38,8 @@ import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { isCanary } from "./config";
|
||||
import { App } from "./common/app";
|
||||
import { Credentials } from "./common/authentication";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { LocalDatabasesCommands } from "./common/commands";
|
||||
|
||||
enum SortOrder {
|
||||
NameAsc = "NameAsc",
|
||||
@@ -73,12 +69,12 @@ class DatabaseTreeDataProvider
|
||||
|
||||
this.push(
|
||||
this.databaseManager.onDidChangeDatabaseItem(
|
||||
this.handleDidChangeDatabaseItem,
|
||||
this.handleDidChangeDatabaseItem.bind(this),
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
this.databaseManager.onDidChangeCurrentDatabaseItem(
|
||||
this.handleDidChangeCurrentDatabaseItem,
|
||||
this.handleDidChangeCurrentDatabaseItem.bind(this),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -87,18 +83,18 @@ class DatabaseTreeDataProvider
|
||||
return this._onDidChangeTreeData.event;
|
||||
}
|
||||
|
||||
private handleDidChangeDatabaseItem = (event: DatabaseChangedEvent): void => {
|
||||
private handleDidChangeDatabaseItem(event: DatabaseChangedEvent): void {
|
||||
// Note that events from the database manager are instances of DatabaseChangedEvent
|
||||
// and events fired by the UI are instances of DatabaseItem
|
||||
|
||||
// When event.item is undefined, then the entire tree is refreshed.
|
||||
// When event.item is a db item, then only that item is refreshed.
|
||||
this._onDidChangeTreeData.fire(event.item);
|
||||
};
|
||||
}
|
||||
|
||||
private handleDidChangeCurrentDatabaseItem = (
|
||||
private handleDidChangeCurrentDatabaseItem(
|
||||
event: DatabaseChangedEvent,
|
||||
): void => {
|
||||
): void {
|
||||
if (this.currentDatabaseItem) {
|
||||
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
|
||||
}
|
||||
@@ -106,7 +102,7 @@ class DatabaseTreeDataProvider
|
||||
if (this.currentDatabaseItem) {
|
||||
this._onDidChangeTreeData.fire(this.currentDatabaseItem);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getTreeItem(element: DatabaseItem): TreeItem {
|
||||
const item = new TreeItem(element.name);
|
||||
@@ -210,149 +206,46 @@ export class DatabaseUI extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
init() {
|
||||
void extLogger.log("Registering database panel commands.");
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.setCurrentDatabase",
|
||||
this.handleSetCurrentDatabase,
|
||||
{
|
||||
title: "Importing database from archive",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.setDefaultTourDatabase",
|
||||
this.handleSetDefaultTourDatabase,
|
||||
{
|
||||
title: "Set Default Database for Codespace CodeQL Tour",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQL.upgradeCurrentDatabase",
|
||||
this.handleUpgradeCurrentDatabase,
|
||||
{
|
||||
title: "Upgrading current database",
|
||||
cancellable: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress("codeQL.clearCache", this.handleClearCache, {
|
||||
title: "Clearing Cache",
|
||||
}),
|
||||
);
|
||||
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.chooseDatabaseFolder",
|
||||
this.handleChooseDatabaseFolder,
|
||||
{
|
||||
title: "Adding database from folder",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.chooseDatabaseArchive",
|
||||
this.handleChooseDatabaseArchive,
|
||||
{
|
||||
title: "Adding database from archive",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.chooseDatabaseInternet",
|
||||
this.handleChooseDatabaseInternet,
|
||||
{
|
||||
title: "Adding database from URL",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.chooseDatabaseGithub",
|
||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||
const credentials = isCanary() ? this.app.credentials : undefined;
|
||||
await this.handleChooseDatabaseGithub(credentials, progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from GitHub",
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLDatabases.setCurrentDatabase",
|
||||
this.handleMakeCurrentDatabase,
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner("codeQLDatabases.sortByName", this.handleSortByName),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLDatabases.sortByDateAdded",
|
||||
this.handleSortByDateAdded,
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.removeDatabase",
|
||||
this.handleRemoveDatabase,
|
||||
{
|
||||
title: "Removing database",
|
||||
cancellable: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunnerWithProgress(
|
||||
"codeQLDatabases.upgradeDatabase",
|
||||
this.handleUpgradeDatabase,
|
||||
{
|
||||
title: "Upgrading database",
|
||||
cancellable: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLDatabases.renameDatabase",
|
||||
this.handleRenameDatabase,
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLDatabases.openDatabaseFolder",
|
||||
this.handleOpenFolder,
|
||||
),
|
||||
);
|
||||
this.push(
|
||||
commandRunner("codeQLDatabases.addDatabaseSource", this.handleAddSource),
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
"codeQLDatabases.removeOrphanedDatabases",
|
||||
this.handleRemoveOrphanedDatabases,
|
||||
),
|
||||
);
|
||||
public getCommands(): LocalDatabasesCommands {
|
||||
return {
|
||||
"codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this),
|
||||
"codeQL.setDefaultTourDatabase":
|
||||
this.handleSetDefaultTourDatabase.bind(this),
|
||||
"codeQL.upgradeCurrentDatabase":
|
||||
this.handleUpgradeCurrentDatabase.bind(this),
|
||||
"codeQL.clearCache": this.handleClearCache.bind(this),
|
||||
"codeQLDatabases.chooseDatabaseFolder":
|
||||
this.handleChooseDatabaseFolder.bind(this),
|
||||
"codeQLDatabases.chooseDatabaseArchive":
|
||||
this.handleChooseDatabaseArchive.bind(this),
|
||||
"codeQLDatabases.chooseDatabaseInternet":
|
||||
this.handleChooseDatabaseInternet.bind(this),
|
||||
"codeQLDatabases.chooseDatabaseGithub":
|
||||
this.handleChooseDatabaseGithub.bind(this),
|
||||
"codeQLDatabases.setCurrentDatabase":
|
||||
this.handleMakeCurrentDatabase.bind(this),
|
||||
"codeQLDatabases.sortByName": this.handleSortByName.bind(this),
|
||||
"codeQLDatabases.sortByDateAdded": this.handleSortByDateAdded.bind(this),
|
||||
"codeQLDatabases.removeDatabase": this.handleRemoveDatabase.bind(this),
|
||||
"codeQLDatabases.upgradeDatabase": this.handleUpgradeDatabase.bind(this),
|
||||
"codeQLDatabases.renameDatabase": this.handleRenameDatabase.bind(this),
|
||||
"codeQLDatabases.openDatabaseFolder": this.handleOpenFolder.bind(this),
|
||||
"codeQLDatabases.addDatabaseSource": this.handleAddSource.bind(this),
|
||||
"codeQLDatabases.removeOrphanedDatabases":
|
||||
this.handleRemoveOrphanedDatabases.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
private handleMakeCurrentDatabase = async (
|
||||
private async handleMakeCurrentDatabase(
|
||||
databaseItem: DatabaseItem,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
};
|
||||
}
|
||||
|
||||
handleChooseDatabaseFolder = async (
|
||||
public async chooseDatabaseFolder(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.chooseAndSetDatabase(true, progress, token);
|
||||
} catch (e) {
|
||||
@@ -362,47 +255,62 @@ export class DatabaseUI extends DisposableObject {
|
||||
)`Failed to choose and set database: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private handleSetDefaultTourDatabase = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!workspace.workspaceFolders?.length) {
|
||||
throw new Error("No workspace folder is open.");
|
||||
} else {
|
||||
// This specifically refers to the database folder in
|
||||
// https://github.com/github/codespaces-codeql
|
||||
const uri = Uri.parse(
|
||||
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
|
||||
);
|
||||
private async handleChooseDatabaseFolder(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.chooseDatabaseFolder(progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from folder",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
const isTutorialDatabase = true;
|
||||
if (databaseItem === undefined) {
|
||||
databaseItem = await this.databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
uri,
|
||||
"CodeQL Tutorial Database",
|
||||
isTutorialDatabase,
|
||||
private async handleSetDefaultTourDatabase(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
try {
|
||||
if (!workspace.workspaceFolders?.length) {
|
||||
throw new Error("No workspace folder is open.");
|
||||
} else {
|
||||
// This specifically refers to the database folder in
|
||||
// https://github.com/github/codespaces-codeql
|
||||
const uri = Uri.parse(
|
||||
`${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`,
|
||||
);
|
||||
|
||||
let databaseItem = this.databaseManager.findDatabaseItem(uri);
|
||||
const isTutorialDatabase = true;
|
||||
if (databaseItem === undefined) {
|
||||
databaseItem = await this.databaseManager.openDatabase(
|
||||
progress,
|
||||
token,
|
||||
uri,
|
||||
"CodeQL Tutorial Database",
|
||||
isTutorialDatabase,
|
||||
);
|
||||
}
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
await this.handleTourDependencies();
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
throw new Error(
|
||||
`Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
await this.handleTourDependencies();
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
throw new Error(
|
||||
`Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
title: "Set Default Database for Codespace CodeQL Tour",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private handleTourDependencies = async (): Promise<void> => {
|
||||
private async handleTourDependencies(): Promise<void> {
|
||||
if (!workspace.workspaceFolders?.length) {
|
||||
throw new Error("No workspace folder is open.");
|
||||
} else {
|
||||
@@ -416,9 +324,10 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
await cli.packInstall(tutorialQueriesPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleRemoveOrphanedDatabases = async (): Promise<void> => {
|
||||
// Public because it's used in tests
|
||||
public async handleRemoveOrphanedDatabases(): Promise<void> {
|
||||
void extLogger.log("Removing orphaned databases from workspace storage.");
|
||||
let dbDirs = undefined;
|
||||
|
||||
@@ -481,12 +390,12 @@ export class DatabaseUI extends DisposableObject {
|
||||
)}).\nTo delete unused databases, please remove them manually from the storage folder ${dirname}.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleChooseDatabaseArchive = async (
|
||||
public async chooseDatabaseArchive(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.chooseAndSetDatabase(false, progress, token);
|
||||
} catch (e: unknown) {
|
||||
@@ -496,12 +405,23 @@ export class DatabaseUI extends DisposableObject {
|
||||
)`Failed to choose and set database: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleChooseDatabaseInternet = async (
|
||||
private async handleChooseDatabaseArchive(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.chooseDatabaseArchive(progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from archive",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async chooseDatabaseInternet(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
return await promptImportInternetDatabase(
|
||||
this.databaseManager,
|
||||
this.storagePath,
|
||||
@@ -509,13 +429,25 @@ export class DatabaseUI extends DisposableObject {
|
||||
token,
|
||||
this.queryServer?.cliServer,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
handleChooseDatabaseGithub = async (
|
||||
credentials: Credentials | undefined,
|
||||
private async handleChooseDatabaseInternet(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.chooseDatabaseInternet(progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from URL",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async chooseDatabaseGithub(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const credentials = isCanary() ? this.app.credentials : undefined;
|
||||
|
||||
return await promptImportGithubDatabase(
|
||||
this.databaseManager,
|
||||
this.storagePath,
|
||||
@@ -524,53 +456,82 @@ export class DatabaseUI extends DisposableObject {
|
||||
token,
|
||||
this.queryServer?.cliServer,
|
||||
);
|
||||
};
|
||||
|
||||
async tryUpgradeCurrentDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
) {
|
||||
await this.handleUpgradeCurrentDatabase(progress, token);
|
||||
}
|
||||
|
||||
private handleSortByName = async () => {
|
||||
private async handleChooseDatabaseGithub(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.chooseDatabaseGithub(progress, token);
|
||||
},
|
||||
{
|
||||
title: "Adding database from GitHub",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async handleSortByName() {
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) {
|
||||
this.treeDataProvider.sortOrder = SortOrder.NameDesc;
|
||||
} else {
|
||||
this.treeDataProvider.sortOrder = SortOrder.NameAsc;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private handleSortByDateAdded = async () => {
|
||||
private async handleSortByDateAdded() {
|
||||
if (this.treeDataProvider.sortOrder === SortOrder.DateAddedAsc) {
|
||||
this.treeDataProvider.sortOrder = SortOrder.DateAddedDesc;
|
||||
} else {
|
||||
this.treeDataProvider.sortOrder = SortOrder.DateAddedAsc;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private handleUpgradeCurrentDatabase = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
await this.handleUpgradeDatabase(
|
||||
progress,
|
||||
token,
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
[],
|
||||
private async handleUpgradeCurrentDatabase(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
await this.handleUpgradeDatabaseInternal(
|
||||
progress,
|
||||
token,
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
[],
|
||||
);
|
||||
},
|
||||
{
|
||||
title: "Upgrading current database",
|
||||
cancellable: true,
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private handleUpgradeDatabase = async (
|
||||
private async handleUpgradeDatabase(
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
return await this.handleUpgradeDatabaseInternal(
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
multiSelect,
|
||||
);
|
||||
},
|
||||
{
|
||||
title: "Upgrading database",
|
||||
cancellable: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async handleUpgradeDatabaseInternal(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) =>
|
||||
this.handleUpgradeDatabase(progress, token, dbItem, []),
|
||||
this.handleUpgradeDatabaseInternal(progress, token, dbItem, []),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -602,78 +563,91 @@ export class DatabaseUI extends DisposableObject {
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private handleClearCache = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
if (
|
||||
this.queryServer !== undefined &&
|
||||
this.databaseManager.currentDatabaseItem !== undefined
|
||||
) {
|
||||
await this.queryServer.clearCacheInDatabase(
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
}
|
||||
};
|
||||
private async handleClearCache(): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
if (
|
||||
this.queryServer !== undefined &&
|
||||
this.databaseManager.currentDatabaseItem !== undefined
|
||||
) {
|
||||
await this.queryServer.clearCacheInDatabase(
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Clearing cache",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private handleSetCurrentDatabase = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Assume user has selected an archive if the file has a .zip extension
|
||||
if (uri.path.endsWith(".zip")) {
|
||||
await importArchiveDatabase(
|
||||
uri.toString(true),
|
||||
this.databaseManager,
|
||||
this.storagePath,
|
||||
progress,
|
||||
token,
|
||||
this.queryServer?.cliServer,
|
||||
);
|
||||
} else {
|
||||
await this.setCurrentDatabase(progress, token, uri);
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
throw new Error(
|
||||
`Could not set database to ${basename(
|
||||
uri.fsPath,
|
||||
)}. Reason: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
private async handleSetCurrentDatabase(uri: Uri): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
try {
|
||||
// Assume user has selected an archive if the file has a .zip extension
|
||||
if (uri.path.endsWith(".zip")) {
|
||||
await importArchiveDatabase(
|
||||
uri.toString(true),
|
||||
this.databaseManager,
|
||||
this.storagePath,
|
||||
progress,
|
||||
token,
|
||||
this.queryServer?.cliServer,
|
||||
);
|
||||
} else {
|
||||
await this.setCurrentDatabase(progress, token, uri);
|
||||
}
|
||||
} catch (e) {
|
||||
// rethrow and let this be handled by default error handling.
|
||||
throw new Error(
|
||||
`Could not set database to ${basename(
|
||||
uri.fsPath,
|
||||
)}. Reason: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Importing database from archive",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private handleRemoveDatabase = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
private async handleRemoveDatabase(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> => {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await this.databaseManager.removeDatabaseItem(
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
);
|
||||
}
|
||||
};
|
||||
): Promise<void> {
|
||||
return withProgress(
|
||||
async (progress, token) => {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(progress, token, dbItem),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await this.databaseManager.removeDatabaseItem(
|
||||
progress,
|
||||
token,
|
||||
databaseItem,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Removing database",
|
||||
cancellable: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private handleRenameDatabase = async (
|
||||
private async handleRenameDatabase(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
this.assertSingleDatabase(multiSelect);
|
||||
|
||||
const newName = await window.showInputBox({
|
||||
@@ -684,12 +658,12 @@ export class DatabaseUI extends DisposableObject {
|
||||
if (newName) {
|
||||
await this.databaseManager.renameDatabaseItem(databaseItem, newName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private handleOpenFolder = async (
|
||||
private async handleOpenFolder(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri)),
|
||||
@@ -697,17 +671,17 @@ export class DatabaseUI extends DisposableObject {
|
||||
} else {
|
||||
await env.openExternal(databaseItem.databaseUri);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the source folder of a CodeQL database to the workspace.
|
||||
* When a database is first added in the "Databases" view, its source folder is added to the workspace.
|
||||
* If the source folder is removed from the workspace for some reason, we want to be able to re-add it if need be.
|
||||
*/
|
||||
private handleAddSource = async (
|
||||
private async handleAddSource(
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined,
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
if (multiSelect?.length) {
|
||||
for (const dbItem of multiSelect) {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
|
||||
@@ -715,7 +689,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
} else {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current database directory. If we don't already have a
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
import {
|
||||
deserializeQueryHistory,
|
||||
serializeQueryHistory,
|
||||
} from "../query-serialization";
|
||||
} from "./store/query-history-store";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { CliVersionConstraint } from "../cli";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
@@ -1122,8 +1122,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand(
|
||||
"codeQL.exportVariantAnalysisResults",
|
||||
await this.variantAnalysisManager.exportResults(
|
||||
finalSingleItem.variantAnalysis.id,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { pathExists, readFile, remove, mkdir, writeFile } from "fs-extra";
|
||||
import { dirname } from "path";
|
||||
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { showAndLogExceptionWithTelemetry } from "../../helpers";
|
||||
import {
|
||||
asError,
|
||||
asyncFilter,
|
||||
getErrorMessage,
|
||||
getErrorStack,
|
||||
} from "./pure/helpers-pure";
|
||||
import { CompletedQueryInfo, LocalQueryInfo } from "./query-results";
|
||||
import { QueryHistoryInfo } from "./query-history/query-history-info";
|
||||
import { QueryEvaluationInfo } from "./run-queries-shared";
|
||||
import { QueryResultType } from "./pure/legacy-messages";
|
||||
import { redactableError } from "./pure/errors";
|
||||
} from "../../pure/helpers-pure";
|
||||
import { CompletedQueryInfo, LocalQueryInfo } from "../../query-results";
|
||||
import { QueryHistoryInfo } from "../query-history-info";
|
||||
import { QueryEvaluationInfo } from "../../run-queries-shared";
|
||||
import { QueryResultType } from "../../pure/legacy-messages";
|
||||
import { redactableError } from "../../pure/errors";
|
||||
|
||||
export async function deserializeQueryHistory(
|
||||
fsPath: string,
|
||||
@@ -30,7 +30,7 @@ import { nanoid } from "nanoid";
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { SELECT_QUERY_NAME } from "./contextual/locationFinder";
|
||||
import { DatabaseManager } from "./local-databases";
|
||||
import { DecodedBqrsChunk } from "./pure/bqrs-cli-types";
|
||||
import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types";
|
||||
import { extLogger, Logger } from "./common";
|
||||
import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
@@ -351,11 +351,17 @@ export class QueryEvaluationInfo {
|
||||
chunk.tuples.forEach((tuple) => {
|
||||
out.write(
|
||||
`${tuple
|
||||
.map((v, i) =>
|
||||
chunk.columns[i].kind === "String"
|
||||
? `"${typeof v === "string" ? v.replaceAll('"', '""') : v}"`
|
||||
: v,
|
||||
)
|
||||
.map((v, i) => {
|
||||
if (chunk.columns[i].kind === "String") {
|
||||
return `"${
|
||||
typeof v === "string" ? v.replaceAll('"', '""') : v
|
||||
}"`;
|
||||
} else if (chunk.columns[i].kind === "Entity") {
|
||||
return (v as EntityValue).label;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
})
|
||||
.join(",")}\n`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { ProgressCallback, UserCancellationException } from "../commandRunner";
|
||||
import {
|
||||
ProgressCallback,
|
||||
UserCancellationException,
|
||||
withProgress,
|
||||
} from "../commandRunner";
|
||||
import { showInformationMessageWithAction } from "../helpers";
|
||||
import { extLogger } from "../common";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
@@ -37,6 +41,7 @@ import { Credentials } from "../common/authentication";
|
||||
* Exports the results of the currently-selected variant analysis.
|
||||
*/
|
||||
export async function exportSelectedVariantAnalysisResults(
|
||||
variantAnalysisManager: VariantAnalysisManager,
|
||||
queryHistoryManager: QueryHistoryManager,
|
||||
): Promise<void> {
|
||||
const queryHistoryItem = queryHistoryManager.getCurrentQueryHistoryItem();
|
||||
@@ -46,8 +51,7 @@ export async function exportSelectedVariantAnalysisResults(
|
||||
);
|
||||
}
|
||||
|
||||
return commands.executeCommand(
|
||||
"codeQL.exportVariantAnalysisResults",
|
||||
await variantAnalysisManager.exportResults(
|
||||
queryHistoryItem.variantAnalysis.id,
|
||||
);
|
||||
}
|
||||
@@ -63,108 +67,117 @@ export async function exportVariantAnalysisResults(
|
||||
variantAnalysisId: number,
|
||||
filterSort: RepositoriesFilterSortStateWithIds | undefined,
|
||||
credentials: Credentials,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
const variantAnalysis = await variantAnalysisManager.getVariantAnalysis(
|
||||
variantAnalysisId,
|
||||
);
|
||||
if (!variantAnalysis) {
|
||||
void extLogger.log(
|
||||
`Could not find variant analysis with id ${variantAnalysisId}`,
|
||||
);
|
||||
throw new Error(
|
||||
"There was an error when trying to retrieve variant analysis information",
|
||||
);
|
||||
}
|
||||
await withProgress(
|
||||
async (progress: ProgressCallback, token: CancellationToken) => {
|
||||
const variantAnalysis = await variantAnalysisManager.getVariantAnalysis(
|
||||
variantAnalysisId,
|
||||
);
|
||||
if (!variantAnalysis) {
|
||||
void extLogger.log(
|
||||
`Could not find variant analysis with id ${variantAnalysisId}`,
|
||||
);
|
||||
throw new Error(
|
||||
"There was an error when trying to retrieve variant analysis information",
|
||||
);
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
throw new UserCancellationException("Cancelled");
|
||||
}
|
||||
if (token.isCancellationRequested) {
|
||||
throw new UserCancellationException("Cancelled");
|
||||
}
|
||||
|
||||
const repoStates = await variantAnalysisManager.getRepoStates(
|
||||
variantAnalysisId,
|
||||
);
|
||||
|
||||
void extLogger.log(
|
||||
`Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`,
|
||||
);
|
||||
|
||||
progress({
|
||||
maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS,
|
||||
step: 0,
|
||||
message: "Determining export format",
|
||||
});
|
||||
|
||||
const exportFormat = await determineExportFormat();
|
||||
if (!exportFormat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
throw new UserCancellationException("Cancelled");
|
||||
}
|
||||
|
||||
const repositories = filterAndSortRepositoriesWithResults(
|
||||
variantAnalysis.scannedRepos,
|
||||
filterSort,
|
||||
)?.filter(
|
||||
(repo) =>
|
||||
repo.resultCount &&
|
||||
repoStates.find((r) => r.repositoryId === repo.repository.id)
|
||||
?.downloadStatus ===
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
);
|
||||
|
||||
async function* getAnalysesResults(): AsyncGenerator<
|
||||
[VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult]
|
||||
> {
|
||||
if (!variantAnalysis) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repositories) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const repo of repositories) {
|
||||
const result = await variantAnalysisManager.loadResults(
|
||||
variantAnalysis.id,
|
||||
repo.repository.fullName,
|
||||
{
|
||||
skipCacheStore: true,
|
||||
},
|
||||
const repoStates = await variantAnalysisManager.getRepoStates(
|
||||
variantAnalysisId,
|
||||
);
|
||||
|
||||
yield [repo, result];
|
||||
}
|
||||
}
|
||||
void extLogger.log(
|
||||
`Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`,
|
||||
);
|
||||
|
||||
const exportDirectory =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
progress({
|
||||
maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS,
|
||||
step: 0,
|
||||
message: "Determining export format",
|
||||
});
|
||||
|
||||
// The date will be formatted like the following: 20221115T123456Z. The time is in UTC.
|
||||
const formattedDate = new Date()
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, "")
|
||||
.replace(/\.\d+Z$/, "Z");
|
||||
const exportedResultsDirectory = join(
|
||||
exportDirectory,
|
||||
"exported-results",
|
||||
`results_${formattedDate}`,
|
||||
);
|
||||
const exportFormat = await determineExportFormat();
|
||||
if (!exportFormat) {
|
||||
return;
|
||||
}
|
||||
|
||||
await exportVariantAnalysisAnalysisResults(
|
||||
exportedResultsDirectory,
|
||||
variantAnalysis,
|
||||
getAnalysesResults(),
|
||||
repositories?.length ?? 0,
|
||||
exportFormat,
|
||||
credentials,
|
||||
progress,
|
||||
token,
|
||||
if (token.isCancellationRequested) {
|
||||
throw new UserCancellationException("Cancelled");
|
||||
}
|
||||
|
||||
const repositories = filterAndSortRepositoriesWithResults(
|
||||
variantAnalysis.scannedRepos,
|
||||
filterSort,
|
||||
)?.filter(
|
||||
(repo) =>
|
||||
repo.resultCount &&
|
||||
repoStates.find((r) => r.repositoryId === repo.repository.id)
|
||||
?.downloadStatus ===
|
||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded,
|
||||
);
|
||||
|
||||
async function* getAnalysesResults(): AsyncGenerator<
|
||||
[
|
||||
VariantAnalysisScannedRepository,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
]
|
||||
> {
|
||||
if (!variantAnalysis) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repositories) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const repo of repositories) {
|
||||
const result = await variantAnalysisManager.loadResults(
|
||||
variantAnalysis.id,
|
||||
repo.repository.fullName,
|
||||
{
|
||||
skipCacheStore: true,
|
||||
},
|
||||
);
|
||||
|
||||
yield [repo, result];
|
||||
}
|
||||
}
|
||||
|
||||
const exportDirectory =
|
||||
variantAnalysisManager.getVariantAnalysisStorageLocation(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
// The date will be formatted like the following: 20221115T123456Z. The time is in UTC.
|
||||
const formattedDate = new Date()
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, "")
|
||||
.replace(/\.\d+Z$/, "Z");
|
||||
const exportedResultsDirectory = join(
|
||||
exportDirectory,
|
||||
"exported-results",
|
||||
`results_${formattedDate}`,
|
||||
);
|
||||
|
||||
await exportVariantAnalysisAnalysisResults(
|
||||
exportedResultsDirectory,
|
||||
variantAnalysis,
|
||||
getAnalysesResults(),
|
||||
repositories?.length ?? 0,
|
||||
exportFormat,
|
||||
credentials,
|
||||
progress,
|
||||
token,
|
||||
);
|
||||
},
|
||||
{
|
||||
title: "Exporting variant analysis results",
|
||||
cancellable: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import { DbManager } from "../databases/db-manager";
|
||||
import { App } from "../common/app";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { AppCommandManager, VariantAnalysisCommands } from "../common/commands";
|
||||
import { exportVariantAnalysisResults } from "./export-results";
|
||||
|
||||
export class VariantAnalysisManager
|
||||
extends DisposableObject
|
||||
@@ -690,6 +691,18 @@ export class VariantAnalysisManager
|
||||
await env.clipboard.writeText(text.join(EOL));
|
||||
}
|
||||
|
||||
public async exportResults(
|
||||
variantAnalysisId: number,
|
||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||
) {
|
||||
await exportVariantAnalysisResults(
|
||||
this,
|
||||
variantAnalysisId,
|
||||
filterSort,
|
||||
this.app.credentials,
|
||||
);
|
||||
}
|
||||
|
||||
private getRepoStatesStoragePath(variantAnalysisId: number): string {
|
||||
return join(
|
||||
this.getVariantAnalysisStorageLocation(variantAnalysisId),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
} from "./shared/variant-analysis";
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
|
||||
|
||||
export interface VariantAnalysisViewInterface {
|
||||
variantAnalysisId: number;
|
||||
@@ -27,4 +28,8 @@ export interface VariantAnalysisViewManager<
|
||||
openQueryFile(variantAnalysisId: number): Promise<void>;
|
||||
openQueryText(variantAnalysisId: number): Promise<void>;
|
||||
cancelVariantAnalysis(variantAnalysisId: number): Promise<void>;
|
||||
exportResults(
|
||||
variantAnalysisId: number,
|
||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -138,8 +138,7 @@ export class VariantAnalysisView
|
||||
);
|
||||
break;
|
||||
case "exportResults":
|
||||
void commands.executeCommand(
|
||||
"codeQL.exportVariantAnalysisResults",
|
||||
await this.manager.exportResults(
|
||||
this.variantAnalysisId,
|
||||
msg.filterSort,
|
||||
);
|
||||
|
||||
@@ -139,6 +139,7 @@ export function VariantAnalysis({
|
||||
repositoryIds: selectedRepositoryIds,
|
||||
},
|
||||
});
|
||||
sendTelemetry("variant-analysis-export-results");
|
||||
}, [filterSortState, selectedRepositoryIds]);
|
||||
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[
|
||||
"v2.12.4",
|
||||
"v2.12.5",
|
||||
"v2.11.6",
|
||||
"v2.7.6",
|
||||
"v2.8.5",
|
||||
|
||||
@@ -86,6 +86,7 @@ describe("QueryHistoryManager", () => {
|
||||
onVariantAnalysisRemoved: jest.fn(),
|
||||
removeVariantAnalysis: jest.fn(),
|
||||
cancelVariantAnalysis: jest.fn(),
|
||||
exportResults: jest.fn(),
|
||||
showView: jest.fn(),
|
||||
} as any as VariantAnalysisManager;
|
||||
|
||||
@@ -862,7 +863,7 @@ describe("QueryHistoryManager", () => {
|
||||
const item = localQueryHistory[4];
|
||||
await queryHistoryManager.handleExportResults(item, [item]);
|
||||
|
||||
expect(executeCommandSpy).not.toBeCalled();
|
||||
expect(variantAnalysisManagerStub.exportResults).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("should export results for a single variant analysis", async () => {
|
||||
@@ -870,8 +871,7 @@ describe("QueryHistoryManager", () => {
|
||||
|
||||
const item = variantAnalysisHistory[1];
|
||||
await queryHistoryManager.handleExportResults(item, [item]);
|
||||
expect(executeCommandSpy).toBeCalledWith(
|
||||
"codeQL.exportVariantAnalysisResults",
|
||||
expect(variantAnalysisManagerStub.exportResults).toBeCalledWith(
|
||||
item.variantAnalysis.id,
|
||||
);
|
||||
});
|
||||
@@ -882,7 +882,7 @@ describe("QueryHistoryManager", () => {
|
||||
const item1 = variantAnalysisHistory[1];
|
||||
const item2 = variantAnalysisHistory[3];
|
||||
await queryHistoryManager.handleExportResults(item1, [item1, item2]);
|
||||
expect(executeCommandSpy).not.toBeCalled();
|
||||
expect(variantAnalysisManagerStub.exportResults).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import {
|
||||
deserializeQueryHistory,
|
||||
serializeQueryHistory,
|
||||
} from "../../../src/query-serialization";
|
||||
} from "../../../../../src/query-history/store/query-history-store";
|
||||
import { join } from "path";
|
||||
import { writeFileSync, mkdirpSync, writeFile } from "fs-extra";
|
||||
import { LocalQueryInfo, InitialQueryInfo } from "../../../src/query-results";
|
||||
import { QueryWithResults } from "../../../src/run-queries-shared";
|
||||
import { DatabaseInfo } from "../../../src/pure/interface-types";
|
||||
import {
|
||||
LocalQueryInfo,
|
||||
InitialQueryInfo,
|
||||
} from "../../../../../src/query-results";
|
||||
import { QueryWithResults } from "../../../../../src/run-queries-shared";
|
||||
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 { 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";
|
||||
import { tmpDir } from "../../../../../src/helpers";
|
||||
import { QueryResultType } from "../../../../../src/pure/legacy-messages";
|
||||
import { QueryInProgress } from "../../../../../src/legacy-query-server/run-queries";
|
||||
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";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
describe("serialize and deserialize", () => {
|
||||
Reference in New Issue
Block a user