Split query history file

This commit is contained in:
Nora
2023-01-25 14:47:07 +00:00
parent ca1d63a3fc
commit c993d30b34
13 changed files with 2433 additions and 2279 deletions

View File

@@ -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", {

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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>;

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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";

View File

@@ -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 = (

View File

@@ -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;
}
});

View File

@@ -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);

View File

@@ -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);