Split query history file
This commit is contained in:
@@ -83,7 +83,7 @@ import {
|
||||
ProgressReporter,
|
||||
queryServerLogger,
|
||||
} from "./common";
|
||||
import { QueryHistoryManager } from "./query-history/query-history";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results";
|
||||
import { QueryServerClient as LegacyQueryServerClient } from "./legacy-query-server/queryserver-client";
|
||||
import { QueryServerClient } from "./query-server/queryserver-client";
|
||||
@@ -1639,7 +1639,7 @@ const checkForUpdatesCommand = "codeQL.checkForUpdatesToCLI";
|
||||
/**
|
||||
* This text provider lets us open readonly files in the editor.
|
||||
*
|
||||
* TODO: Consolidate this with the 'codeql' text provider in query-history.ts.
|
||||
* TODO: Consolidate this with the 'codeql' text provider in history-tree-data-provider.ts.
|
||||
*/
|
||||
function registerRemoteQueryTextProvider() {
|
||||
workspace.registerTextDocumentContentProvider("remote-query", {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Diagnostic, DiagnosticSeverity, languages, Range, Uri } from "vscode";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { QueryHistoryManager } from "../query-history/query-history";
|
||||
import { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import {
|
||||
EvaluationLogProblemReporter,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
import { PipelineInfo, SummarySymbols } from "./summary-parser";
|
||||
import { readFile } from "fs-extra";
|
||||
import { extLogger } from "../common";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
|
||||
/**
|
||||
* Compute the key used to find a predicate in the summary symbols.
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
env,
|
||||
Event,
|
||||
EventEmitter,
|
||||
ProviderResult,
|
||||
ThemeColor,
|
||||
ThemeIcon,
|
||||
TreeDataProvider,
|
||||
TreeItem,
|
||||
} from "vscode";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { assertNever } from "../pure/helpers-pure";
|
||||
import { QueryHistoryInfo } from "./query-history-info";
|
||||
import { QueryStatus } from "../query-status";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
|
||||
export enum SortOrder {
|
||||
NameAsc = "NameAsc",
|
||||
NameDesc = "NameDesc",
|
||||
DateAsc = "DateAsc",
|
||||
DateDesc = "DateDesc",
|
||||
CountAsc = "CountAsc",
|
||||
CountDesc = "CountDesc",
|
||||
}
|
||||
|
||||
/**
|
||||
* Tree data provider for the query history view.
|
||||
*/
|
||||
export class HistoryTreeDataProvider
|
||||
extends DisposableObject
|
||||
implements TreeDataProvider<QueryHistoryInfo>
|
||||
{
|
||||
private _sortOrder = SortOrder.DateAsc;
|
||||
|
||||
private _onDidChangeTreeData = super.push(
|
||||
new EventEmitter<QueryHistoryInfo | undefined>(),
|
||||
);
|
||||
|
||||
readonly onDidChangeTreeData: Event<QueryHistoryInfo | undefined> =
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
private _onDidChangeCurrentQueryItem = super.push(
|
||||
new EventEmitter<QueryHistoryInfo | undefined>(),
|
||||
);
|
||||
|
||||
public readonly onDidChangeCurrentQueryItem =
|
||||
this._onDidChangeCurrentQueryItem.event;
|
||||
|
||||
private history: QueryHistoryInfo[] = [];
|
||||
|
||||
private current: QueryHistoryInfo | undefined;
|
||||
|
||||
constructor(private readonly labelProvider: HistoryItemLabelProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getTreeItem(element: QueryHistoryInfo): Promise<TreeItem> {
|
||||
const treeItem = new TreeItem(this.labelProvider.getLabel(element));
|
||||
|
||||
treeItem.command = {
|
||||
title: "Query History Item",
|
||||
command: "codeQLQueryHistory.itemClicked",
|
||||
arguments: [element],
|
||||
tooltip: element.failureReason || this.labelProvider.getLabel(element),
|
||||
};
|
||||
|
||||
// Populate the icon and the context value. We use the context value to
|
||||
// control which commands are visible in the context menu.
|
||||
treeItem.iconPath = this.getIconPath(element);
|
||||
treeItem.contextValue = await this.getContextValue(element);
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
private getIconPath(element: QueryHistoryInfo): ThemeIcon | string {
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
return new ThemeIcon("sync~spin");
|
||||
case QueryStatus.Completed:
|
||||
if (element.t === "local") {
|
||||
return new ThemeIcon("database");
|
||||
} else {
|
||||
return new ThemeIcon("cloud");
|
||||
}
|
||||
case QueryStatus.Failed:
|
||||
return new ThemeIcon("error", new ThemeColor("errorForeground"));
|
||||
default:
|
||||
assertNever(element.status);
|
||||
}
|
||||
}
|
||||
|
||||
private async getContextValue(element: QueryHistoryInfo): Promise<string> {
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
if (element.t === "local") {
|
||||
return "inProgressResultsItem";
|
||||
} else if (
|
||||
element.t === "variant-analysis" &&
|
||||
element.variantAnalysis.actionsWorkflowRunId === undefined
|
||||
) {
|
||||
return "pendingRemoteResultsItem";
|
||||
} else {
|
||||
return "inProgressRemoteResultsItem";
|
||||
}
|
||||
case QueryStatus.Completed:
|
||||
if (element.t === "local") {
|
||||
const hasResults =
|
||||
await element.completedQuery?.query.hasInterpretedResults();
|
||||
return hasResults ? "interpretedResultsItem" : "rawResultsItem";
|
||||
} else {
|
||||
return "remoteResultsItem";
|
||||
}
|
||||
case QueryStatus.Failed:
|
||||
return element.t === "local"
|
||||
? "cancelledResultsItem"
|
||||
: "cancelledRemoteResultsItem";
|
||||
default:
|
||||
assertNever(element.status);
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(element?: QueryHistoryInfo): ProviderResult<QueryHistoryInfo[]> {
|
||||
return element
|
||||
? []
|
||||
: this.history.sort((h1, h2) => {
|
||||
const h1Label = this.labelProvider.getLabel(h1).toLowerCase();
|
||||
const h2Label = this.labelProvider.getLabel(h2).toLowerCase();
|
||||
|
||||
const h1Date = this.getItemDate(h1);
|
||||
|
||||
const h2Date = this.getItemDate(h2);
|
||||
|
||||
const resultCount1 =
|
||||
h1.t === "local"
|
||||
? h1.completedQuery?.resultCount ?? -1
|
||||
: h1.resultCount ?? -1;
|
||||
const resultCount2 =
|
||||
h2.t === "local"
|
||||
? h2.completedQuery?.resultCount ?? -1
|
||||
: h2.resultCount ?? -1;
|
||||
|
||||
switch (this.sortOrder) {
|
||||
case SortOrder.NameAsc:
|
||||
return h1Label.localeCompare(h2Label, env.language);
|
||||
|
||||
case SortOrder.NameDesc:
|
||||
return h2Label.localeCompare(h1Label, env.language);
|
||||
|
||||
case SortOrder.DateAsc:
|
||||
return h1Date - h2Date;
|
||||
|
||||
case SortOrder.DateDesc:
|
||||
return h2Date - h1Date;
|
||||
|
||||
case SortOrder.CountAsc:
|
||||
// If the result counts are equal, sort by name.
|
||||
return resultCount1 - resultCount2 === 0
|
||||
? h1Label.localeCompare(h2Label, env.language)
|
||||
: resultCount1 - resultCount2;
|
||||
|
||||
case SortOrder.CountDesc:
|
||||
// If the result counts are equal, sort by name.
|
||||
return resultCount2 - resultCount1 === 0
|
||||
? h2Label.localeCompare(h1Label, env.language)
|
||||
: resultCount2 - resultCount1;
|
||||
default:
|
||||
assertNever(this.sortOrder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getParent(_element: QueryHistoryInfo): ProviderResult<QueryHistoryInfo> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getCurrent(): QueryHistoryInfo | undefined {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
pushQuery(item: QueryHistoryInfo): void {
|
||||
this.history.push(item);
|
||||
this.setCurrentItem(item);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setCurrentItem(item?: QueryHistoryInfo) {
|
||||
if (item !== this.current) {
|
||||
this.current = item;
|
||||
this._onDidChangeCurrentQueryItem.fire(item);
|
||||
}
|
||||
}
|
||||
|
||||
remove(item: QueryHistoryInfo) {
|
||||
const isCurrent = this.current === item;
|
||||
if (isCurrent) {
|
||||
this.setCurrentItem();
|
||||
}
|
||||
const index = this.history.findIndex((i) => i === item);
|
||||
if (index >= 0) {
|
||||
this.history.splice(index, 1);
|
||||
if (isCurrent && this.history.length > 0) {
|
||||
// Try to keep a current item, near the deleted item if there
|
||||
// are any available.
|
||||
this.setCurrentItem(
|
||||
this.history[Math.min(index, this.history.length - 1)],
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
get allHistory(): QueryHistoryInfo[] {
|
||||
return this.history;
|
||||
}
|
||||
|
||||
set allHistory(history: QueryHistoryInfo[]) {
|
||||
this.history = history;
|
||||
this.setCurrentItem(history[0]);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
public get sortOrder() {
|
||||
return this._sortOrder;
|
||||
}
|
||||
|
||||
public set sortOrder(newSortOrder: SortOrder) {
|
||||
this._sortOrder = newSortOrder;
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
private getItemDate(item: QueryHistoryInfo) {
|
||||
switch (item.t) {
|
||||
case "local":
|
||||
return item.initialInfo.start.getTime();
|
||||
case "remote":
|
||||
return item.remoteQuery.executionStartTime;
|
||||
case "variant-analysis":
|
||||
return item.variantAnalysis.executionStartTime;
|
||||
default:
|
||||
assertNever(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,10 @@ import {
|
||||
commands,
|
||||
Disposable,
|
||||
env,
|
||||
Event,
|
||||
EventEmitter,
|
||||
ExtensionContext,
|
||||
ProviderResult,
|
||||
Range,
|
||||
ThemeColor,
|
||||
ThemeIcon,
|
||||
TreeDataProvider,
|
||||
TreeItem,
|
||||
TreeView,
|
||||
Uri,
|
||||
ViewColumn,
|
||||
@@ -70,9 +65,10 @@ import { VariantAnalysisManager } from "../remote-queries/variant-analysis-manag
|
||||
import { VariantAnalysisHistoryItem } from "./variant-analysis-history-item";
|
||||
import { getTotalResultCount } from "../remote-queries/shared/variant-analysis";
|
||||
import { App } from "../common/app";
|
||||
import { HistoryTreeDataProvider } from "./history-tree-data-provider";
|
||||
|
||||
/**
|
||||
* query-history.ts
|
||||
* history-tree-data-provider.ts
|
||||
* ------------
|
||||
* Managing state of previous queries that we've executed.
|
||||
*
|
||||
@@ -119,229 +115,6 @@ const DOUBLE_CLICK_TIME = 500;
|
||||
|
||||
const WORKSPACE_QUERY_HISTORY_FILE = "workspace-query-history.json";
|
||||
|
||||
/**
|
||||
* Tree data provider for the query history view.
|
||||
*/
|
||||
export class HistoryTreeDataProvider
|
||||
extends DisposableObject
|
||||
implements TreeDataProvider<QueryHistoryInfo>
|
||||
{
|
||||
private _sortOrder = SortOrder.DateAsc;
|
||||
|
||||
private _onDidChangeTreeData = super.push(
|
||||
new EventEmitter<QueryHistoryInfo | undefined>(),
|
||||
);
|
||||
|
||||
readonly onDidChangeTreeData: Event<QueryHistoryInfo | undefined> =
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
private _onDidChangeCurrentQueryItem = super.push(
|
||||
new EventEmitter<QueryHistoryInfo | undefined>(),
|
||||
);
|
||||
|
||||
public readonly onDidChangeCurrentQueryItem =
|
||||
this._onDidChangeCurrentQueryItem.event;
|
||||
|
||||
private history: QueryHistoryInfo[] = [];
|
||||
|
||||
private current: QueryHistoryInfo | undefined;
|
||||
|
||||
constructor(private readonly labelProvider: HistoryItemLabelProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getTreeItem(element: QueryHistoryInfo): Promise<TreeItem> {
|
||||
const treeItem = new TreeItem(this.labelProvider.getLabel(element));
|
||||
|
||||
treeItem.command = {
|
||||
title: "Query History Item",
|
||||
command: "codeQLQueryHistory.itemClicked",
|
||||
arguments: [element],
|
||||
tooltip: element.failureReason || this.labelProvider.getLabel(element),
|
||||
};
|
||||
|
||||
// Populate the icon and the context value. We use the context value to
|
||||
// control which commands are visible in the context menu.
|
||||
treeItem.iconPath = this.getIconPath(element);
|
||||
treeItem.contextValue = await this.getContextValue(element);
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
private getIconPath(element: QueryHistoryInfo): ThemeIcon | string {
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
return new ThemeIcon("sync~spin");
|
||||
case QueryStatus.Completed:
|
||||
if (element.t === "local") {
|
||||
return new ThemeIcon("database");
|
||||
} else {
|
||||
return new ThemeIcon("cloud");
|
||||
}
|
||||
case QueryStatus.Failed:
|
||||
return new ThemeIcon("error", new ThemeColor("errorForeground"));
|
||||
default:
|
||||
assertNever(element.status);
|
||||
}
|
||||
}
|
||||
|
||||
private async getContextValue(element: QueryHistoryInfo): Promise<string> {
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
if (element.t === "local") {
|
||||
return "inProgressResultsItem";
|
||||
} else if (
|
||||
element.t === "variant-analysis" &&
|
||||
element.variantAnalysis.actionsWorkflowRunId === undefined
|
||||
) {
|
||||
return "pendingRemoteResultsItem";
|
||||
} else {
|
||||
return "inProgressRemoteResultsItem";
|
||||
}
|
||||
case QueryStatus.Completed:
|
||||
if (element.t === "local") {
|
||||
const hasResults =
|
||||
await element.completedQuery?.query.hasInterpretedResults();
|
||||
return hasResults ? "interpretedResultsItem" : "rawResultsItem";
|
||||
} else {
|
||||
return "remoteResultsItem";
|
||||
}
|
||||
case QueryStatus.Failed:
|
||||
return element.t === "local"
|
||||
? "cancelledResultsItem"
|
||||
: "cancelledRemoteResultsItem";
|
||||
default:
|
||||
assertNever(element.status);
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(element?: QueryHistoryInfo): ProviderResult<QueryHistoryInfo[]> {
|
||||
return element
|
||||
? []
|
||||
: this.history.sort((h1, h2) => {
|
||||
const h1Label = this.labelProvider.getLabel(h1).toLowerCase();
|
||||
const h2Label = this.labelProvider.getLabel(h2).toLowerCase();
|
||||
|
||||
const h1Date = this.getItemDate(h1);
|
||||
|
||||
const h2Date = this.getItemDate(h2);
|
||||
|
||||
const resultCount1 =
|
||||
h1.t === "local"
|
||||
? h1.completedQuery?.resultCount ?? -1
|
||||
: h1.resultCount ?? -1;
|
||||
const resultCount2 =
|
||||
h2.t === "local"
|
||||
? h2.completedQuery?.resultCount ?? -1
|
||||
: h2.resultCount ?? -1;
|
||||
|
||||
switch (this.sortOrder) {
|
||||
case SortOrder.NameAsc:
|
||||
return h1Label.localeCompare(h2Label, env.language);
|
||||
|
||||
case SortOrder.NameDesc:
|
||||
return h2Label.localeCompare(h1Label, env.language);
|
||||
|
||||
case SortOrder.DateAsc:
|
||||
return h1Date - h2Date;
|
||||
|
||||
case SortOrder.DateDesc:
|
||||
return h2Date - h1Date;
|
||||
|
||||
case SortOrder.CountAsc:
|
||||
// If the result counts are equal, sort by name.
|
||||
return resultCount1 - resultCount2 === 0
|
||||
? h1Label.localeCompare(h2Label, env.language)
|
||||
: resultCount1 - resultCount2;
|
||||
|
||||
case SortOrder.CountDesc:
|
||||
// If the result counts are equal, sort by name.
|
||||
return resultCount2 - resultCount1 === 0
|
||||
? h2Label.localeCompare(h1Label, env.language)
|
||||
: resultCount2 - resultCount1;
|
||||
default:
|
||||
assertNever(this.sortOrder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getParent(_element: QueryHistoryInfo): ProviderResult<QueryHistoryInfo> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getCurrent(): QueryHistoryInfo | undefined {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
pushQuery(item: QueryHistoryInfo): void {
|
||||
this.history.push(item);
|
||||
this.setCurrentItem(item);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setCurrentItem(item?: QueryHistoryInfo) {
|
||||
if (item !== this.current) {
|
||||
this.current = item;
|
||||
this._onDidChangeCurrentQueryItem.fire(item);
|
||||
}
|
||||
}
|
||||
|
||||
remove(item: QueryHistoryInfo) {
|
||||
const isCurrent = this.current === item;
|
||||
if (isCurrent) {
|
||||
this.setCurrentItem();
|
||||
}
|
||||
const index = this.history.findIndex((i) => i === item);
|
||||
if (index >= 0) {
|
||||
this.history.splice(index, 1);
|
||||
if (isCurrent && this.history.length > 0) {
|
||||
// Try to keep a current item, near the deleted item if there
|
||||
// are any available.
|
||||
this.setCurrentItem(
|
||||
this.history[Math.min(index, this.history.length - 1)],
|
||||
);
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
get allHistory(): QueryHistoryInfo[] {
|
||||
return this.history;
|
||||
}
|
||||
|
||||
set allHistory(history: QueryHistoryInfo[]) {
|
||||
this.history = history;
|
||||
this.setCurrentItem(history[0]);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
public get sortOrder() {
|
||||
return this._sortOrder;
|
||||
}
|
||||
|
||||
public set sortOrder(newSortOrder: SortOrder) {
|
||||
this._sortOrder = newSortOrder;
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
private getItemDate(item: QueryHistoryInfo) {
|
||||
switch (item.t) {
|
||||
case "local":
|
||||
return item.initialInfo.start.getTime();
|
||||
case "remote":
|
||||
return item.remoteQuery.executionStartTime;
|
||||
case "variant-analysis":
|
||||
return item.variantAnalysis.executionStartTime;
|
||||
default:
|
||||
assertNever(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryHistoryManager extends DisposableObject {
|
||||
treeDataProvider: HistoryTreeDataProvider;
|
||||
treeView: TreeView<QueryHistoryInfo>;
|
||||
@@ -3,7 +3,7 @@ import { EOL } from "os";
|
||||
import { join } from "path";
|
||||
import { Disposable, ExtensionContext } from "vscode";
|
||||
import { extLogger } from "../common";
|
||||
import { QueryHistoryManager } from "./query-history";
|
||||
import { QueryHistoryManager } from "./query-history-manager";
|
||||
|
||||
const LAST_SCRUB_TIME_KEY = "lastScrubTime";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { ProgressCallback, UserCancellationException } from "../commandRunner";
|
||||
import { showInformationMessageWithAction } from "../helpers";
|
||||
import { extLogger } from "../common";
|
||||
import { QueryHistoryManager } from "../query-history/query-history";
|
||||
import { QueryHistoryManager } from "../query-history/query-history-manager";
|
||||
import { createGist } from "./gh-api/gh-api-client";
|
||||
import { RemoteQueriesManager } from "./remote-queries-manager";
|
||||
import {
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "./shared/remote-query-result";
|
||||
import { showAndLogWarningMessage } from "../helpers";
|
||||
import { URLSearchParams } from "url";
|
||||
import { SHOW_QUERY_TEXT_MSG } from "../query-history/query-history";
|
||||
import { SHOW_QUERY_TEXT_MSG } from "../query-history/query-history-manager";
|
||||
import { AnalysesResultsManager } from "./analyses-results-manager";
|
||||
import { AnalysisResults } from "./shared/analysis-result";
|
||||
import { humanizeUnit } from "../pure/time";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TextDocumentContentProvider, Uri } from "vscode";
|
||||
import { URLSearchParams } from "url";
|
||||
import { showAndLogWarningMessage } from "../helpers";
|
||||
import { SHOW_QUERY_TEXT_MSG } from "../query-history/query-history";
|
||||
import { SHOW_QUERY_TEXT_MSG } from "../query-history/query-history-manager";
|
||||
import { VariantAnalysisManager } from "./variant-analysis-manager";
|
||||
|
||||
export const createVariantAnalysisContentProvider = (
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
import { join } from "path";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import {
|
||||
HistoryTreeDataProvider,
|
||||
SortOrder,
|
||||
} from "../../../../src/query-history/history-tree-data-provider";
|
||||
import {
|
||||
QueryHistoryConfig,
|
||||
QueryHistoryConfigListener,
|
||||
} from "../../../../src/config";
|
||||
import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager";
|
||||
import { QueryHistoryInfo } from "../../../../src/query-history/query-history-info";
|
||||
import {
|
||||
createMockLocalQueryInfo,
|
||||
createMockQueryWithResults,
|
||||
} from "../../../factories/query-history/local-query-history-item";
|
||||
import { createMockRemoteQueryHistoryItem } from "../../../factories/query-history/remote-query-history-item";
|
||||
import { RemoteQueryHistoryItem } from "../../../../src/remote-queries/remote-query-history-item";
|
||||
import { shuffleHistoryItems } from "../../utils/query-history-helpers";
|
||||
import { createMockVariantAnalysisHistoryItem } from "../../../factories/query-history/variant-analysis-history-item";
|
||||
import { VariantAnalysisHistoryItem } from "../../../../src/query-history/variant-analysis-history-item";
|
||||
import { QueryStatus } from "../../../../src/query-status";
|
||||
import { VariantAnalysisStatus } from "../../../../src/remote-queries/shared/variant-analysis";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
|
||||
describe("HistoryTreeDataProvider", () => {
|
||||
const mockExtensionLocation = join(tmpDir.name, "mock-extension-location");
|
||||
let configListener: QueryHistoryConfigListener;
|
||||
const doCompareCallback = jest.fn();
|
||||
|
||||
let queryHistoryManager: QueryHistoryManager;
|
||||
|
||||
let localQueriesResultsViewStub: ResultsView;
|
||||
let remoteQueriesManagerStub: RemoteQueriesManager;
|
||||
let variantAnalysisManagerStub: VariantAnalysisManager;
|
||||
|
||||
let allHistory: QueryHistoryInfo[];
|
||||
let localQueryHistory: LocalQueryInfo[];
|
||||
let remoteQueryHistory: RemoteQueryHistoryItem[];
|
||||
let variantAnalysisHistory: VariantAnalysisHistoryItem[];
|
||||
|
||||
let historyTreeDataProvider: HistoryTreeDataProvider;
|
||||
let labelProvider: HistoryItemLabelProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||
|
||||
configListener = new QueryHistoryConfigListener();
|
||||
localQueriesResultsViewStub = {
|
||||
showResults: jest.fn(),
|
||||
} as any as ResultsView;
|
||||
remoteQueriesManagerStub = {
|
||||
onRemoteQueryAdded: jest.fn(),
|
||||
onRemoteQueryRemoved: jest.fn(),
|
||||
onRemoteQueryStatusUpdate: jest.fn(),
|
||||
removeRemoteQuery: jest.fn(),
|
||||
openRemoteQueryResults: jest.fn(),
|
||||
} as any as RemoteQueriesManager;
|
||||
|
||||
variantAnalysisManagerStub = {
|
||||
onVariantAnalysisAdded: jest.fn(),
|
||||
onVariantAnalysisStatusUpdated: jest.fn(),
|
||||
onVariantAnalysisRemoved: jest.fn(),
|
||||
removeVariantAnalysis: jest.fn(),
|
||||
showView: jest.fn(),
|
||||
} as any as VariantAnalysisManager;
|
||||
|
||||
localQueryHistory = [
|
||||
// completed
|
||||
createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: true,
|
||||
}),
|
||||
}),
|
||||
// completed
|
||||
createMockLocalQueryInfo({
|
||||
dbName: "b",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: true,
|
||||
}),
|
||||
}),
|
||||
// failed
|
||||
createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: false,
|
||||
}),
|
||||
}),
|
||||
// completed
|
||||
createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: true,
|
||||
}),
|
||||
}),
|
||||
// in progress
|
||||
createMockLocalQueryInfo({ resultCount: 0 }),
|
||||
// in progress
|
||||
createMockLocalQueryInfo({ resultCount: 0 }),
|
||||
];
|
||||
remoteQueryHistory = [
|
||||
createMockRemoteQueryHistoryItem({ status: QueryStatus.Completed }),
|
||||
createMockRemoteQueryHistoryItem({ status: QueryStatus.Failed }),
|
||||
createMockRemoteQueryHistoryItem({ status: QueryStatus.InProgress }),
|
||||
createMockRemoteQueryHistoryItem({ status: QueryStatus.InProgress }),
|
||||
];
|
||||
variantAnalysisHistory = [
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
historyItemStatus: QueryStatus.Completed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
historyItemStatus: QueryStatus.InProgress,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
historyItemStatus: QueryStatus.Failed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
historyItemStatus: QueryStatus.InProgress,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
|
||||
}),
|
||||
];
|
||||
allHistory = shuffleHistoryItems([
|
||||
...localQueryHistory,
|
||||
...remoteQueryHistory,
|
||||
...variantAnalysisHistory,
|
||||
]);
|
||||
|
||||
labelProvider = new HistoryItemLabelProvider({
|
||||
/**/
|
||||
} as QueryHistoryConfig);
|
||||
historyTreeDataProvider = new HistoryTreeDataProvider(labelProvider);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (queryHistoryManager) {
|
||||
queryHistoryManager.dispose();
|
||||
}
|
||||
historyTreeDataProvider.dispose();
|
||||
});
|
||||
|
||||
describe("getTreeItem", () => {
|
||||
it("should get a tree item with raw results", async () => {
|
||||
const mockQueryWithRawResults = createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: true,
|
||||
hasInterpretedResults: false,
|
||||
}),
|
||||
});
|
||||
|
||||
const treeItem = await historyTreeDataProvider.getTreeItem(
|
||||
mockQueryWithRawResults,
|
||||
);
|
||||
expect(treeItem.command).toEqual({
|
||||
title: "Query History Item",
|
||||
command: "codeQLQueryHistory.itemClicked",
|
||||
arguments: [mockQueryWithRawResults],
|
||||
tooltip: labelProvider.getLabel(mockQueryWithRawResults),
|
||||
});
|
||||
expect(treeItem.label).toContain("query-file.ql");
|
||||
expect(treeItem.contextValue).toBe("rawResultsItem");
|
||||
expect(treeItem.iconPath).toEqual(new vscode.ThemeIcon("database"));
|
||||
});
|
||||
|
||||
it("should get a tree item with interpreted results", async () => {
|
||||
const mockQueryWithInterpretedResults = createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: true,
|
||||
hasInterpretedResults: true,
|
||||
}),
|
||||
});
|
||||
|
||||
const treeItem = await historyTreeDataProvider.getTreeItem(
|
||||
mockQueryWithInterpretedResults,
|
||||
);
|
||||
expect(treeItem.contextValue).toBe("interpretedResultsItem");
|
||||
expect(treeItem.iconPath).toEqual(new vscode.ThemeIcon("database"));
|
||||
});
|
||||
|
||||
it("should get a tree item that did not complete successfully", async () => {
|
||||
const mockQuery = createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
failureReason: "failure reason",
|
||||
queryWithResults: createMockQueryWithResults({
|
||||
didRunSuccessfully: false,
|
||||
}),
|
||||
});
|
||||
|
||||
const treeItem = await historyTreeDataProvider.getTreeItem(mockQuery);
|
||||
expect(treeItem.iconPath).toEqual(
|
||||
new vscode.ThemeIcon("error", new vscode.ThemeColor("errorForeground")),
|
||||
);
|
||||
});
|
||||
|
||||
it("should get a tree item that failed before creating any results", async () => {
|
||||
const mockQuery = createMockLocalQueryInfo({
|
||||
dbName: "a",
|
||||
failureReason: "failure reason",
|
||||
});
|
||||
|
||||
const treeItem = await historyTreeDataProvider.getTreeItem(mockQuery);
|
||||
expect(treeItem.iconPath).toEqual(
|
||||
new vscode.ThemeIcon("error", new vscode.ThemeColor("errorForeground")),
|
||||
);
|
||||
});
|
||||
|
||||
it("should get a tree item that is in progress", async () => {
|
||||
const mockQuery = createMockLocalQueryInfo({ dbName: "a" });
|
||||
|
||||
const treeItem = await historyTreeDataProvider.getTreeItem(mockQuery);
|
||||
expect(treeItem.iconPath).toEqual({
|
||||
id: "sync~spin",
|
||||
color: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getChildren", () => {
|
||||
it("fetch children correctly", () => {
|
||||
const mockQuery = createMockLocalQueryInfo({});
|
||||
historyTreeDataProvider.allHistory.push(mockQuery);
|
||||
expect(historyTreeDataProvider.getChildren()).toEqual([mockQuery]);
|
||||
expect(historyTreeDataProvider.getChildren(mockQuery)).toEqual([]);
|
||||
});
|
||||
|
||||
describe("sorting", () => {
|
||||
const history = [
|
||||
createMockRemoteQueryHistoryItem({
|
||||
userSpecifiedLabel: "a",
|
||||
executionStartTime: 2,
|
||||
resultCount: 24,
|
||||
status: QueryStatus.Completed,
|
||||
}),
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "b",
|
||||
startTime: new Date(10),
|
||||
resultCount: 20,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "c",
|
||||
executionStartTime: 15,
|
||||
resultCount: 456,
|
||||
historyItemStatus: QueryStatus.Completed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
|
||||
}),
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "d",
|
||||
startTime: new Date(5),
|
||||
resultCount: 30,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "e",
|
||||
executionStartTime: 50,
|
||||
resultCount: 15,
|
||||
historyItemStatus: QueryStatus.InProgress,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.InProgress,
|
||||
}),
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "f",
|
||||
startTime: new Date(1),
|
||||
resultCount: 13,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "g",
|
||||
executionStartTime: 7,
|
||||
resultCount: 30,
|
||||
historyItemStatus: QueryStatus.Failed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
createMockRemoteQueryHistoryItem({
|
||||
userSpecifiedLabel: "h",
|
||||
executionStartTime: 6,
|
||||
resultCount: 5,
|
||||
status: QueryStatus.InProgress,
|
||||
}),
|
||||
];
|
||||
|
||||
let treeDataProvider: HistoryTreeDataProvider;
|
||||
|
||||
beforeEach(async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
(queryHistoryManager.treeDataProvider as any).history = [...history];
|
||||
treeDataProvider = queryHistoryManager.treeDataProvider;
|
||||
});
|
||||
|
||||
it("should get children for name ascending", async () => {
|
||||
const expected = [...history];
|
||||
treeDataProvider.sortOrder = SortOrder.NameAsc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should get children for name descending", async () => {
|
||||
const expected = [...history].reverse();
|
||||
treeDataProvider.sortOrder = SortOrder.NameDesc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should get children for date ascending", async () => {
|
||||
const expected = [
|
||||
history[5],
|
||||
history[0],
|
||||
history[3],
|
||||
history[7],
|
||||
history[6],
|
||||
history[1],
|
||||
history[2],
|
||||
history[4],
|
||||
];
|
||||
treeDataProvider.sortOrder = SortOrder.DateAsc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should get children for date descending", async () => {
|
||||
const expected = [
|
||||
history[5],
|
||||
history[0],
|
||||
history[3],
|
||||
history[7],
|
||||
history[6],
|
||||
history[1],
|
||||
history[2],
|
||||
history[4],
|
||||
].reverse();
|
||||
|
||||
treeDataProvider.sortOrder = SortOrder.DateDesc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should get children for result count ascending", async () => {
|
||||
const expected = [
|
||||
history[7],
|
||||
history[5],
|
||||
history[4],
|
||||
history[1],
|
||||
history[0],
|
||||
history[3],
|
||||
history[6],
|
||||
history[2],
|
||||
];
|
||||
treeDataProvider.sortOrder = SortOrder.CountAsc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should get children for result count descending", async () => {
|
||||
const expected = [
|
||||
history[7],
|
||||
history[5],
|
||||
history[4],
|
||||
history[1],
|
||||
history[0],
|
||||
history[3],
|
||||
history[6],
|
||||
history[2],
|
||||
].reverse();
|
||||
treeDataProvider.sortOrder = SortOrder.CountDesc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should fall back to name ascending when there are no results", async () => {
|
||||
const thisHistory = [
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "a",
|
||||
resultCount: 0,
|
||||
startTime: new Date(10),
|
||||
}),
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "b",
|
||||
resultCount: 0,
|
||||
startTime: new Date(1),
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "c",
|
||||
resultCount: 0,
|
||||
historyItemStatus: QueryStatus.Completed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
createMockRemoteQueryHistoryItem({
|
||||
userSpecifiedLabel: "d",
|
||||
resultCount: 0,
|
||||
executionStartTime: 50,
|
||||
status: QueryStatus.Completed,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "e",
|
||||
resultCount: 0,
|
||||
historyItemStatus: QueryStatus.InProgress,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
];
|
||||
(queryHistoryManager!.treeDataProvider as any).history = [
|
||||
...thisHistory,
|
||||
];
|
||||
const expected = [...thisHistory];
|
||||
|
||||
treeDataProvider.sortOrder = SortOrder.CountAsc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should fall back to name descending when there are no results", async () => {
|
||||
const thisHistory = [
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "a",
|
||||
resultCount: 0,
|
||||
startTime: new Date(10),
|
||||
}),
|
||||
createMockLocalQueryInfo({
|
||||
userSpecifiedLabel: "b",
|
||||
resultCount: 0,
|
||||
startTime: new Date(1),
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "c",
|
||||
resultCount: 0,
|
||||
historyItemStatus: QueryStatus.Completed,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
createMockRemoteQueryHistoryItem({
|
||||
userSpecifiedLabel: "d",
|
||||
resultCount: 0,
|
||||
executionStartTime: 50,
|
||||
status: QueryStatus.Completed,
|
||||
}),
|
||||
createMockVariantAnalysisHistoryItem({
|
||||
userSpecifiedLabel: "e",
|
||||
resultCount: 0,
|
||||
historyItemStatus: QueryStatus.InProgress,
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Failed,
|
||||
}),
|
||||
];
|
||||
(queryHistoryManager!.treeDataProvider as any).history = [
|
||||
...thisHistory,
|
||||
];
|
||||
const expected = [...thisHistory].reverse();
|
||||
treeDataProvider.sortOrder = SortOrder.CountDesc;
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
expect(children).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function createMockQueryHistory(allHistory: QueryHistoryInfo[]) {
|
||||
const qhm = new QueryHistoryManager(
|
||||
{} as QueryRunner,
|
||||
{} as DatabaseManager,
|
||||
localQueriesResultsViewStub,
|
||||
remoteQueriesManagerStub,
|
||||
variantAnalysisManagerStub,
|
||||
{} as EvalLogViewer,
|
||||
"xxx",
|
||||
{
|
||||
globalStorageUri: vscode.Uri.file(mockExtensionLocation),
|
||||
extensionPath: vscode.Uri.file("/x/y/z").fsPath,
|
||||
} as vscode.ExtensionContext,
|
||||
configListener,
|
||||
new HistoryItemLabelProvider({} as QueryHistoryConfig),
|
||||
doCompareCallback,
|
||||
);
|
||||
(qhm.treeDataProvider as any).history = [...allHistory];
|
||||
await vscode.workspace.saveAll();
|
||||
await qhm.refreshTreeView();
|
||||
return qhm;
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,6 @@ import {
|
||||
import { QueryHistoryConfig } from "../../../../src/config";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { tmpDir, walkDirectory } from "../../../../src/helpers";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history";
|
||||
import { AnalysesResultsManager } from "../../../../src/remote-queries/analyses-results-manager";
|
||||
import { RemoteQueryResult } from "../../../../src/remote-queries/shared/remote-query-result";
|
||||
import { DisposableBucket } from "../../disposable-bucket";
|
||||
@@ -34,6 +33,7 @@ import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-a
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { createMockApp } from "../../../__mocks__/appMock";
|
||||
import { testCredentialsWithStub } from "../../../factories/authentication";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
|
||||
// set a higher timeout since recursive delete may take a while, expecially on Windows.
|
||||
jest.setTimeout(120000);
|
||||
|
||||
@@ -12,7 +12,6 @@ import { commands, ExtensionContext, Uri } from "vscode";
|
||||
import { QueryHistoryConfig } from "../../../../src/config";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { tmpDir, walkDirectory } from "../../../../src/helpers";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history";
|
||||
import { DisposableBucket } from "../../disposable-bucket";
|
||||
import { testDisposeHandler } from "../../test-dispose-handler";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
@@ -23,6 +22,7 @@ import { QueryRunner } from "../../../../src/queryRunner";
|
||||
import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager";
|
||||
import { App } from "../../../../src/common/app";
|
||||
import { createMockApp } from "../../../__mocks__/appMock";
|
||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||
|
||||
// set a higher timeout since recursive delete may take a while, expecially on Windows.
|
||||
jest.setTimeout(120000);
|
||||
|
||||
Reference in New Issue
Block a user