Refactor query history to handle remote and local
This is a step on the way towards storing remote query history across restarts. This PR adds a `QueryHistoryInfo` type that is a union of two types: `LocalQueryInfo` and `RemoteQueryInfo`. `LocalQueryInfo` used to be called `FullQueryInfo` and `RemoteQueryInfo` is only a skeleton right now. The body will be added later. This PR only introduces it and changes types to make future PRs simpler. Also, `slurp` and `splat` have been moved to the `query-serialization.ts` module.
This commit is contained in:
@@ -20,11 +20,11 @@ import { DatabaseManager } from '../databases';
|
||||
import { getHtmlForWebview, jumpToLocation } from '../interface-utils';
|
||||
import { transformBqrsResultSet, RawResultSet, BQRSInfo } from '../pure/bqrs-cli-types';
|
||||
import resultsDiff from './resultsDiff';
|
||||
import { FullCompletedQueryInfo } from '../query-results';
|
||||
import { CompletedLocalQueryInfo } from '../query-results';
|
||||
|
||||
interface ComparePair {
|
||||
from: FullCompletedQueryInfo;
|
||||
to: FullCompletedQueryInfo;
|
||||
from: CompletedLocalQueryInfo;
|
||||
to: CompletedLocalQueryInfo;
|
||||
}
|
||||
|
||||
export class CompareInterfaceManager extends DisposableObject {
|
||||
@@ -39,15 +39,15 @@ export class CompareInterfaceManager extends DisposableObject {
|
||||
private cliServer: CodeQLCliServer,
|
||||
private logger: Logger,
|
||||
private showQueryResultsCallback: (
|
||||
item: FullCompletedQueryInfo
|
||||
item: CompletedLocalQueryInfo
|
||||
) => Promise<void>
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async showResults(
|
||||
from: FullCompletedQueryInfo,
|
||||
to: FullCompletedQueryInfo,
|
||||
from: CompletedLocalQueryInfo,
|
||||
to: CompletedLocalQueryInfo,
|
||||
selectedResultSetName?: string
|
||||
) {
|
||||
this.comparePair = { from, to };
|
||||
@@ -188,8 +188,8 @@ export class CompareInterfaceManager extends DisposableObject {
|
||||
}
|
||||
|
||||
private async findCommonResultSetNames(
|
||||
from: FullCompletedQueryInfo,
|
||||
to: FullCompletedQueryInfo,
|
||||
from: CompletedLocalQueryInfo,
|
||||
to: CompletedLocalQueryInfo,
|
||||
selectedResultSetName: string | undefined
|
||||
): Promise<[string[], string, RawResultSet, RawResultSet]> {
|
||||
const fromSchemas = await this.cliServer.bqrsInfo(
|
||||
|
||||
@@ -69,7 +69,7 @@ import { InterfaceManager } from './interface';
|
||||
import { WebviewReveal } from './interface-utils';
|
||||
import { ideServerLogger, logger, queryServerLogger } from './logging';
|
||||
import { QueryHistoryManager } from './query-history';
|
||||
import { FullCompletedQueryInfo, FullQueryInfo } from './query-results';
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from './query-results';
|
||||
import * as qsClient from './queryserver-client';
|
||||
import { displayQuickQuery } from './quick-query';
|
||||
import { compileAndRunQueryAgainstDatabase, createInitialQueryInfo } from './run-queries';
|
||||
@@ -442,7 +442,7 @@ async function activateWithInstalledDistribution(
|
||||
void logger.log('Initializing query history manager.');
|
||||
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
|
||||
ctx.subscriptions.push(queryHistoryConfigurationListener);
|
||||
const showResults = async (item: FullCompletedQueryInfo) =>
|
||||
const showResults = async (item: CompletedLocalQueryInfo) =>
|
||||
showResultsForCompletedQuery(item, WebviewReveal.Forced);
|
||||
const queryStorageDir = path.join(ctx.globalStorageUri.fsPath, 'queries');
|
||||
await fs.ensureDir(queryStorageDir);
|
||||
@@ -455,7 +455,7 @@ async function activateWithInstalledDistribution(
|
||||
ctx,
|
||||
queryHistoryConfigurationListener,
|
||||
showResults,
|
||||
async (from: FullCompletedQueryInfo, to: FullCompletedQueryInfo) =>
|
||||
async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>
|
||||
showResultsForComparison(from, to),
|
||||
);
|
||||
await qhm.readQueryHistory();
|
||||
@@ -479,8 +479,8 @@ async function activateWithInstalledDistribution(
|
||||
archiveFilesystemProvider.activate(ctx);
|
||||
|
||||
async function showResultsForComparison(
|
||||
from: FullCompletedQueryInfo,
|
||||
to: FullCompletedQueryInfo
|
||||
from: CompletedLocalQueryInfo,
|
||||
to: CompletedLocalQueryInfo
|
||||
): Promise<void> {
|
||||
try {
|
||||
await cmpm.showResults(from, to);
|
||||
@@ -490,7 +490,7 @@ async function activateWithInstalledDistribution(
|
||||
}
|
||||
|
||||
async function showResultsForCompletedQuery(
|
||||
query: FullCompletedQueryInfo,
|
||||
query: CompletedLocalQueryInfo,
|
||||
forceReveal: WebviewReveal
|
||||
): Promise<void> {
|
||||
await intm.showResults(query, forceReveal, false);
|
||||
@@ -520,7 +520,7 @@ async function activateWithInstalledDistribution(
|
||||
token.onCancellationRequested(() => source.cancel());
|
||||
|
||||
const initialInfo = await createInitialQueryInfo(selectedQuery, databaseInfo, quickEval, range);
|
||||
const item = new FullQueryInfo(initialInfo, queryHistoryConfigurationListener, source);
|
||||
const item = new LocalQueryInfo(initialInfo, queryHistoryConfigurationListener, source);
|
||||
qhm.addQuery(item);
|
||||
try {
|
||||
const completedQueryInfo = await compileAndRunQueryAgainstDatabase(
|
||||
@@ -534,7 +534,7 @@ async function activateWithInstalledDistribution(
|
||||
);
|
||||
item.completeThisQuery(completedQueryInfo);
|
||||
await qhm.writeQueryHistory();
|
||||
await showResultsForCompletedQuery(item as FullCompletedQueryInfo, WebviewReveal.NotForced);
|
||||
await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.NotForced);
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
} catch (e) {
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
import { getDefaultResultSetName, ParsedResultSets } from './pure/interface-types';
|
||||
import { RawResultSet, transformBqrsResultSet, ResultSetSchema } from './pure/bqrs-cli-types';
|
||||
import { PAGE_SIZE } from './config';
|
||||
import { FullCompletedQueryInfo } from './query-results';
|
||||
import { CompletedLocalQueryInfo } from './query-results';
|
||||
|
||||
/**
|
||||
* interface.ts
|
||||
@@ -97,7 +97,7 @@ function numInterpretedPages(interpretation: Interpretation | undefined): number
|
||||
}
|
||||
|
||||
export class InterfaceManager extends DisposableObject {
|
||||
private _displayedQuery?: FullCompletedQueryInfo;
|
||||
private _displayedQuery?: CompletedLocalQueryInfo;
|
||||
private _interpretation?: Interpretation;
|
||||
private _panel: vscode.WebviewPanel | undefined;
|
||||
private _panelLoaded = false;
|
||||
@@ -357,7 +357,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
* history entry.
|
||||
*/
|
||||
public async showResults(
|
||||
fullQuery: FullCompletedQueryInfo,
|
||||
fullQuery: CompletedLocalQueryInfo,
|
||||
forceReveal: WebviewReveal,
|
||||
shouldKeepOldResultsWhileRendering = false
|
||||
): Promise<void> {
|
||||
|
||||
@@ -29,9 +29,11 @@ import { QueryServerClient } from './queryserver-client';
|
||||
import { DisposableObject } from './pure/disposable-object';
|
||||
import { commandRunner } from './commandRunner';
|
||||
import { assertNever, ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from './pure/helpers-pure';
|
||||
import { FullCompletedQueryInfo, FullQueryInfo, QueryStatus } from './query-results';
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo as LocalQueryInfo, QueryHistoryInfo } from './query-results';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { registerQueryHistoryScubber } from './query-history-scrubber';
|
||||
import { QueryStatus } from './query-status';
|
||||
import { slurpQueryHistory, splatQueryHistory } from './query-serialization';
|
||||
|
||||
/**
|
||||
* query-history.ts
|
||||
@@ -99,18 +101,18 @@ const WORKSPACE_QUERY_HISTORY_FILE = 'workspace-query-history.json';
|
||||
export class HistoryTreeDataProvider extends DisposableObject {
|
||||
private _sortOrder = SortOrder.DateAsc;
|
||||
|
||||
private _onDidChangeTreeData = super.push(new EventEmitter<FullQueryInfo | undefined>());
|
||||
private _onDidChangeTreeData = super.push(new EventEmitter<QueryHistoryInfo | undefined>());
|
||||
|
||||
readonly onDidChangeTreeData: Event<FullQueryInfo | undefined> = this
|
||||
readonly onDidChangeTreeData: Event<QueryHistoryInfo | undefined> = this
|
||||
._onDidChangeTreeData.event;
|
||||
|
||||
private history: FullQueryInfo[] = [];
|
||||
private history: QueryHistoryInfo[] = [];
|
||||
|
||||
private failedIconPath: string;
|
||||
|
||||
private localSuccessIconPath: string;
|
||||
|
||||
private current: FullQueryInfo | undefined;
|
||||
private current: QueryHistoryInfo | undefined;
|
||||
|
||||
constructor(extensionPath: string) {
|
||||
super();
|
||||
@@ -124,7 +126,7 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async getTreeItem(element: FullQueryInfo): Promise<TreeItem> {
|
||||
async getTreeItem(element: QueryHistoryInfo): Promise<TreeItem> {
|
||||
const treeItem = new TreeItem(element.label);
|
||||
|
||||
treeItem.command = {
|
||||
@@ -134,36 +136,52 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
tooltip: element.failureReason || element.label
|
||||
};
|
||||
|
||||
// Populate the icon and the context value. We use the context value to
|
||||
// control which commands are visible in the context menu.
|
||||
let hasResults;
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
treeItem.iconPath = new ThemeIcon('sync~spin');
|
||||
treeItem.contextValue = 'inProgressResultsItem';
|
||||
break;
|
||||
case QueryStatus.Completed:
|
||||
hasResults = await element.completedQuery?.query.hasInterpretedResults();
|
||||
treeItem.iconPath = this.localSuccessIconPath;
|
||||
treeItem.contextValue = hasResults
|
||||
? 'interpretedResultsItem'
|
||||
: 'rawResultsItem';
|
||||
break;
|
||||
case QueryStatus.Failed:
|
||||
treeItem.iconPath = this.failedIconPath;
|
||||
treeItem.contextValue = 'cancelledResultsItem';
|
||||
break;
|
||||
default:
|
||||
assertNever(element.status);
|
||||
if (element.t === 'local') {
|
||||
// Populate the icon and the context value. We use the context value to
|
||||
// control which commands are visible in the context menu.
|
||||
let hasResults;
|
||||
switch (element.status) {
|
||||
case QueryStatus.InProgress:
|
||||
treeItem.iconPath = new ThemeIcon('sync~spin');
|
||||
treeItem.contextValue = 'inProgressResultsItem';
|
||||
break;
|
||||
case QueryStatus.Completed:
|
||||
hasResults = await element.completedQuery?.query.hasInterpretedResults();
|
||||
treeItem.iconPath = this.localSuccessIconPath;
|
||||
treeItem.contextValue = hasResults
|
||||
? 'interpretedResultsItem'
|
||||
: 'rawResultsItem';
|
||||
break;
|
||||
case QueryStatus.Failed:
|
||||
treeItem.iconPath = this.failedIconPath;
|
||||
treeItem.contextValue = 'cancelledResultsItem';
|
||||
break;
|
||||
default:
|
||||
assertNever(element.status);
|
||||
}
|
||||
} else {
|
||||
// TODO remote queries are not implemented yet.
|
||||
}
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
getChildren(
|
||||
element?: FullQueryInfo
|
||||
): ProviderResult<FullQueryInfo[]> {
|
||||
element?: QueryHistoryInfo
|
||||
): ProviderResult<QueryHistoryInfo[]> {
|
||||
return element ? [] : this.history.sort((h1, h2) => {
|
||||
|
||||
// TODO remote queries are not implemented yet.
|
||||
if (h1.t !== 'local' && h2.t !== 'local') {
|
||||
return 0;
|
||||
}
|
||||
if (h1.t !== 'local') {
|
||||
return -1;
|
||||
}
|
||||
if (h2.t !== 'local') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const resultCount1 = h1.completedQuery?.resultCount ?? -1;
|
||||
const resultCount2 = h2.completedQuery?.resultCount ?? -1;
|
||||
|
||||
@@ -192,25 +210,25 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
});
|
||||
}
|
||||
|
||||
getParent(_element: FullQueryInfo): ProviderResult<FullQueryInfo> {
|
||||
getParent(_element: QueryHistoryInfo): ProviderResult<QueryHistoryInfo> {
|
||||
return null;
|
||||
}
|
||||
|
||||
getCurrent(): FullQueryInfo | undefined {
|
||||
getCurrent(): QueryHistoryInfo | undefined {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
pushQuery(item: FullQueryInfo): void {
|
||||
pushQuery(item: QueryHistoryInfo): void {
|
||||
this.history.push(item);
|
||||
this.setCurrentItem(item);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setCurrentItem(item?: FullQueryInfo) {
|
||||
setCurrentItem(item?: QueryHistoryInfo) {
|
||||
this.current = item;
|
||||
}
|
||||
|
||||
remove(item: FullQueryInfo) {
|
||||
remove(item: QueryHistoryInfo) {
|
||||
const isCurrent = this.current === item;
|
||||
if (isCurrent) {
|
||||
this.setCurrentItem();
|
||||
@@ -227,11 +245,11 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
get allHistory(): FullQueryInfo[] {
|
||||
get allHistory(): QueryHistoryInfo[] {
|
||||
return this.history;
|
||||
}
|
||||
|
||||
set allHistory(history: FullQueryInfo[]) {
|
||||
set allHistory(history: QueryHistoryInfo[]) {
|
||||
this.history = history;
|
||||
this.current = history[0];
|
||||
this.refresh();
|
||||
@@ -254,9 +272,9 @@ export class HistoryTreeDataProvider extends DisposableObject {
|
||||
export class QueryHistoryManager extends DisposableObject {
|
||||
|
||||
treeDataProvider: HistoryTreeDataProvider;
|
||||
treeView: TreeView<FullQueryInfo>;
|
||||
lastItemClick: { time: Date; item: FullQueryInfo } | undefined;
|
||||
compareWithItem: FullQueryInfo | undefined;
|
||||
treeView: TreeView<QueryHistoryInfo>;
|
||||
lastItemClick: { time: Date; item: QueryHistoryInfo } | undefined;
|
||||
compareWithItem: LocalQueryInfo | undefined;
|
||||
queryHistoryScrubber: Disposable | undefined;
|
||||
private queryMetadataStorageLocation;
|
||||
|
||||
@@ -266,10 +284,10 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
private queryStorageDir: string,
|
||||
ctx: ExtensionContext,
|
||||
private queryHistoryConfigListener: QueryHistoryConfig,
|
||||
private selectedCallback: (item: FullCompletedQueryInfo) => Promise<void>,
|
||||
private selectedCallback: (item: CompletedLocalQueryInfo) => Promise<void>,
|
||||
private doCompareCallback: (
|
||||
from: FullCompletedQueryInfo,
|
||||
to: FullCompletedQueryInfo
|
||||
from: CompletedLocalQueryInfo,
|
||||
to: CompletedLocalQueryInfo
|
||||
) => Promise<void>
|
||||
) {
|
||||
super();
|
||||
@@ -303,7 +321,12 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
} else {
|
||||
this.treeDataProvider.setCurrentItem(ev.selection[0]);
|
||||
}
|
||||
this.updateCompareWith([...ev.selection]);
|
||||
if (ev.selection.some(item => item.t !== 'local')) {
|
||||
// Don't allow comparison of non-local items
|
||||
this.updateCompareWith([]);
|
||||
} else {
|
||||
this.updateCompareWith([...ev.selection] as LocalQueryInfo[]);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -395,7 +418,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.itemClicked',
|
||||
async (item: FullQueryInfo) => {
|
||||
async (item: LocalQueryInfo) => {
|
||||
return this.handleItemClicked(item, [item]);
|
||||
}
|
||||
)
|
||||
@@ -449,28 +472,28 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
|
||||
async readQueryHistory(): Promise<void> {
|
||||
void logger.log(`Reading cached query history from '${this.queryMetadataStorageLocation}'.`);
|
||||
const history = await FullQueryInfo.slurp(this.queryMetadataStorageLocation, this.queryHistoryConfigListener);
|
||||
const history = await slurpQueryHistory(this.queryMetadataStorageLocation, this.queryHistoryConfigListener);
|
||||
this.treeDataProvider.allHistory = history;
|
||||
}
|
||||
|
||||
async writeQueryHistory(): Promise<void> {
|
||||
const toSave = this.treeDataProvider.allHistory.filter(q => q.isCompleted());
|
||||
await FullQueryInfo.splat(toSave, this.queryMetadataStorageLocation);
|
||||
await splatQueryHistory(toSave, this.queryMetadataStorageLocation);
|
||||
}
|
||||
|
||||
async invokeCallbackOn(queryHistoryItem: FullQueryInfo) {
|
||||
async invokeCallbackOn(queryHistoryItem: QueryHistoryInfo) {
|
||||
if (this.selectedCallback && queryHistoryItem.isCompleted()) {
|
||||
const sc = this.selectedCallback;
|
||||
await sc(queryHistoryItem as FullCompletedQueryInfo);
|
||||
await sc(queryHistoryItem as CompletedLocalQueryInfo);
|
||||
}
|
||||
}
|
||||
|
||||
async handleOpenQuery(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
): Promise<void> {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem.t !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -499,12 +522,17 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleRemoveHistoryItem(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
const toDelete = (finalMultiSelect || [finalSingleItem]);
|
||||
await Promise.all(toDelete.map(async (item) => {
|
||||
// TODO Remote queries are not implemented yet
|
||||
if (item.t !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Removing in progress queries is not supported. They must be cancelled first.
|
||||
if (item.status !== QueryStatus.InProgress) {
|
||||
this.treeDataProvider.remove(item);
|
||||
@@ -548,12 +576,12 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleSetLabel(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
): Promise<void> {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem.t !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -571,12 +599,16 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleCompareWith(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
try {
|
||||
if (finalSingleItem.t !== 'local') {
|
||||
throw new Error('Please select a local query.');
|
||||
}
|
||||
|
||||
if (!finalSingleItem.completedQuery?.didRunSuccessfully) {
|
||||
throw new Error('Please select a successful query.');
|
||||
}
|
||||
@@ -585,7 +617,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
const to = await this.findOtherQueryToCompare(from, finalMultiSelect);
|
||||
|
||||
if (from.isCompleted() && to?.isCompleted()) {
|
||||
await this.doCompareCallback(from as FullCompletedQueryInfo, to as FullCompletedQueryInfo);
|
||||
await this.doCompareCallback(from as CompletedLocalQueryInfo, to as CompletedLocalQueryInfo);
|
||||
}
|
||||
} catch (e) {
|
||||
void showAndLogErrorMessage(e.message);
|
||||
@@ -593,8 +625,8 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleItemClicked(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
@@ -625,8 +657,8 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleShowQueryLog(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
if (!this.assertSingleQuery(multiSelect)) {
|
||||
return;
|
||||
@@ -644,25 +676,25 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleCancel(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
(finalMultiSelect || [finalSingleItem]).forEach((item) => {
|
||||
if (item.status === QueryStatus.InProgress) {
|
||||
if (item.status === QueryStatus.InProgress && item.t === 'local') {
|
||||
item.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleShowQueryText(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem.t !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -682,12 +714,12 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleViewSarifAlerts(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem.completedQuery) {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem.t !== 'local' || !finalSingleItem.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -706,15 +738,15 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleViewCsvResults(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
return;
|
||||
}
|
||||
if (!finalSingleItem.completedQuery) {
|
||||
if (finalSingleItem.t !== 'local' || !finalSingleItem.completedQuery) {
|
||||
return;
|
||||
}
|
||||
const query = finalSingleItem.completedQuery.query;
|
||||
@@ -730,12 +762,12 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleViewCsvAlerts(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem.completedQuery) {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem.t !== 'local' || !finalSingleItem.completedQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -745,14 +777,19 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
|
||||
async handleViewDil(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[],
|
||||
singleItem: LocalQueryInfo,
|
||||
multiSelect: LocalQueryInfo[],
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
if (!this.assertSingleQuery(finalMultiSelect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalSingleItem.t !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!finalSingleItem.completedQuery) {
|
||||
return;
|
||||
}
|
||||
@@ -762,11 +799,11 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
async getQueryText(queryHistoryItem: FullQueryInfo): Promise<string> {
|
||||
async getQueryText(queryHistoryItem: LocalQueryInfo): Promise<string> {
|
||||
return queryHistoryItem.initialInfo.queryText;
|
||||
}
|
||||
|
||||
addQuery(item: FullQueryInfo) {
|
||||
addQuery(item: LocalQueryInfo) {
|
||||
this.treeDataProvider.pushQuery(item);
|
||||
this.updateTreeViewSelectionIfVisible();
|
||||
}
|
||||
@@ -825,9 +862,15 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
}
|
||||
|
||||
private async findOtherQueryToCompare(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
): Promise<FullQueryInfo | undefined> {
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
): Promise<CompletedLocalQueryInfo | undefined> {
|
||||
|
||||
// Remote queries cannot be compared
|
||||
if (singleItem.t !== 'local' || multiSelect.some(s => s.t !== 'local')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!singleItem.completedQuery) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -837,7 +880,7 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
if (multiSelect?.length === 2) {
|
||||
// return the query that is not the first selected one
|
||||
const otherQuery =
|
||||
singleItem === multiSelect[0] ? multiSelect[1] : multiSelect[0];
|
||||
(singleItem === multiSelect[0] ? multiSelect[1] : multiSelect[0]) as LocalQueryInfo;
|
||||
if (!otherQuery.completedQuery) {
|
||||
throw new Error('Please select a completed query.');
|
||||
}
|
||||
@@ -847,10 +890,10 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
if (otherQuery.initialInfo.databaseInfo.name !== dbName) {
|
||||
throw new Error('Query databases must be the same.');
|
||||
}
|
||||
return otherQuery;
|
||||
return otherQuery as CompletedLocalQueryInfo;
|
||||
}
|
||||
|
||||
if (multiSelect?.length > 1) {
|
||||
if (multiSelect?.length > 2) {
|
||||
throw new Error('Please select no more than 2 queries.');
|
||||
}
|
||||
|
||||
@@ -859,15 +902,16 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
.filter(
|
||||
(otherQuery) =>
|
||||
otherQuery !== singleItem &&
|
||||
otherQuery.t === 'local' &&
|
||||
otherQuery.completedQuery &&
|
||||
otherQuery.completedQuery.didRunSuccessfully &&
|
||||
otherQuery.initialInfo.databaseInfo.name === dbName
|
||||
)
|
||||
.map((item) => ({
|
||||
label: item.label,
|
||||
description: item.initialInfo.databaseInfo.name,
|
||||
detail: item.completedQuery!.statusString,
|
||||
query: item,
|
||||
description: (item as CompletedLocalQueryInfo).initialInfo.databaseInfo.name,
|
||||
detail: (item as CompletedLocalQueryInfo).completedQuery.statusString,
|
||||
query: item as CompletedLocalQueryInfo,
|
||||
}));
|
||||
if (comparableQueryLabels.length < 1) {
|
||||
throw new Error('No other queries available to compare with.');
|
||||
@@ -876,7 +920,7 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
return choice?.query;
|
||||
}
|
||||
|
||||
private assertSingleQuery(multiSelect: FullQueryInfo[] = [], message = 'Please select a single query.') {
|
||||
private assertSingleQuery(multiSelect: QueryHistoryInfo[] = [], message = 'Please select a single query.') {
|
||||
if (multiSelect.length > 1) {
|
||||
void showAndLogErrorMessage(
|
||||
message
|
||||
@@ -903,7 +947,7 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
*
|
||||
* @param newSelection the new selection after the most recent selection change
|
||||
*/
|
||||
private updateCompareWith(newSelection: FullQueryInfo[]) {
|
||||
private updateCompareWith(newSelection: LocalQueryInfo[]) {
|
||||
if (newSelection.length === 1) {
|
||||
this.compareWithItem = newSelection[0];
|
||||
} else if (
|
||||
@@ -927,11 +971,11 @@ the file in the file explorer and dragging it into the workspace.`
|
||||
* @param multiSelect a multi-select or undefined if no items are selected
|
||||
*/
|
||||
private determineSelection(
|
||||
singleItem: FullQueryInfo,
|
||||
multiSelect: FullQueryInfo[]
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
): {
|
||||
finalSingleItem: FullQueryInfo;
|
||||
finalMultiSelect: FullQueryInfo[]
|
||||
finalSingleItem: QueryHistoryInfo;
|
||||
finalMultiSelect: QueryHistoryInfo[]
|
||||
} {
|
||||
if (!singleItem && !multiSelect?.[0]) {
|
||||
const selection = this.treeView.selection;
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from './pure/interface-types';
|
||||
import { QueryHistoryConfig } from './config';
|
||||
import { DatabaseInfo } from './pure/interface-types';
|
||||
import { showAndLogErrorMessage } from './helpers';
|
||||
import { asyncFilter } from './pure/helpers-pure';
|
||||
import { QueryStatus } from './query-status';
|
||||
import { RemoteQueryInfo } from './remote-queries/remote-query-info';
|
||||
|
||||
/**
|
||||
* query-results.ts
|
||||
@@ -42,12 +42,6 @@ export interface InitialQueryInfo {
|
||||
readonly id: string; // unique id for this query.
|
||||
}
|
||||
|
||||
export enum QueryStatus {
|
||||
InProgress = 'InProgress',
|
||||
Completed = 'Completed',
|
||||
Failed = 'Failed',
|
||||
}
|
||||
|
||||
export class CompletedQueryInfo implements QueryWithResults {
|
||||
readonly query: QueryEvaluationInfo;
|
||||
readonly result: messages.EvaluationResult;
|
||||
@@ -191,81 +185,14 @@ export function ensureMetadataIsComplete(metadata: QueryMetadata | undefined) {
|
||||
/**
|
||||
* Used in Interface and Compare-Interface for queries that we know have been complated.
|
||||
*/
|
||||
export type FullCompletedQueryInfo = FullQueryInfo & {
|
||||
export type CompletedLocalQueryInfo = LocalQueryInfo & {
|
||||
completedQuery: CompletedQueryInfo
|
||||
};
|
||||
|
||||
export class FullQueryInfo {
|
||||
export type QueryHistoryInfo = LocalQueryInfo | RemoteQueryInfo;
|
||||
|
||||
static async slurp(fsPath: string, config: QueryHistoryConfig): Promise<FullQueryInfo[]> {
|
||||
try {
|
||||
if (!(await fs.pathExists(fsPath))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await fs.readFile(fsPath, 'utf8');
|
||||
const queries = JSON.parse(data);
|
||||
const parsedQueries = queries.map((q: FullQueryInfo) => {
|
||||
|
||||
// Need to explicitly set prototype since reading in from JSON will not
|
||||
// do this automatically. Note that we can't call the constructor here since
|
||||
// the constructor invokes extra logic that we don't want to do.
|
||||
Object.setPrototypeOf(q, FullQueryInfo.prototype);
|
||||
|
||||
// The config object is a global, se we need to set it explicitly
|
||||
// and ensure it is not serialized to JSON.
|
||||
q.setConfig(config);
|
||||
|
||||
// Date instances are serialized as strings. Need to
|
||||
// convert them back to Date instances.
|
||||
(q.initialInfo as any).start = new Date(q.initialInfo.start);
|
||||
if (q.completedQuery) {
|
||||
// Again, need to explicitly set prototypes.
|
||||
Object.setPrototypeOf(q.completedQuery, CompletedQueryInfo.prototype);
|
||||
Object.setPrototypeOf(q.completedQuery.query, QueryEvaluationInfo.prototype);
|
||||
// slurped queries do not need to be disposed
|
||||
q.completedQuery.dispose = () => { /**/ };
|
||||
}
|
||||
return q;
|
||||
});
|
||||
|
||||
// filter out queries that have been deleted on disk
|
||||
// most likely another workspace has deleted them because the
|
||||
// queries aged out.
|
||||
return asyncFilter(parsedQueries, async (q) => {
|
||||
const resultsPath = q.completedQuery?.query.resultsPaths.resultsPath;
|
||||
return !!resultsPath && await fs.pathExists(resultsPath);
|
||||
});
|
||||
} catch (e) {
|
||||
void showAndLogErrorMessage('Error loading query history.', {
|
||||
fullMessage: ['Error loading query history.', e.stack].join('\n'),
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the query history to disk. It is not necessary that the parent directory
|
||||
* exists, but if it does, it must be writable. An existing file will be overwritten.
|
||||
*
|
||||
* Any errors will be rethrown.
|
||||
*
|
||||
* @param queries the list of queries to save.
|
||||
* @param fsPath the path to save the queries to.
|
||||
*/
|
||||
static async splat(queries: FullQueryInfo[], fsPath: string): Promise<void> {
|
||||
try {
|
||||
if (!(await fs.pathExists(fsPath))) {
|
||||
await fs.mkdir(path.dirname(fsPath), { recursive: true });
|
||||
}
|
||||
// remove incomplete queries since they cannot be recreated on restart
|
||||
const filteredQueries = queries.filter(q => q.completedQuery !== undefined);
|
||||
const data = JSON.stringify(filteredQueries, null, 2);
|
||||
await fs.writeFile(fsPath, data);
|
||||
} catch (e) {
|
||||
throw new Error(`Error saving query history to ${fsPath}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
export class LocalQueryInfo {
|
||||
readonly t = 'local';
|
||||
|
||||
public failureReason: string | undefined;
|
||||
public completedQuery: CompletedQueryInfo | undefined;
|
||||
@@ -401,7 +328,7 @@ export class FullQueryInfo {
|
||||
*
|
||||
* @param config the global query history config object
|
||||
*/
|
||||
private setConfig(config: QueryHistoryConfig) {
|
||||
setConfig(config: QueryHistoryConfig) {
|
||||
// avoid serializing config property
|
||||
Object.defineProperty(this, 'config', {
|
||||
enumerable: false,
|
||||
|
||||
85
extensions/ql-vscode/src/query-serialization.ts
Normal file
85
extensions/ql-vscode/src/query-serialization.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
import { QueryHistoryConfig } from './config';
|
||||
import { showAndLogErrorMessage } from './helpers';
|
||||
import { asyncFilter } from './pure/helpers-pure';
|
||||
import { CompletedQueryInfo, LocalQueryInfo, QueryHistoryInfo } from './query-results';
|
||||
import { QueryEvaluationInfo } from './run-queries';
|
||||
|
||||
export async function slurpQueryHistory(fsPath: string, config: QueryHistoryConfig): Promise<QueryHistoryInfo[]> {
|
||||
try {
|
||||
if (!(await fs.pathExists(fsPath))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await fs.readFile(fsPath, 'utf8');
|
||||
const queries = JSON.parse(data);
|
||||
const parsedQueries = queries.map((q: QueryHistoryInfo) => {
|
||||
|
||||
// Need to explicitly set prototype since reading in from JSON will not
|
||||
// do this automatically. Note that we can't call the constructor here since
|
||||
// the constructor invokes extra logic that we don't want to do.
|
||||
if (q.t === 'local') {
|
||||
Object.setPrototypeOf(q, LocalQueryInfo.prototype);
|
||||
|
||||
// The config object is a global, se we need to set it explicitly
|
||||
// and ensure it is not serialized to JSON.
|
||||
q.setConfig(config);
|
||||
|
||||
// Date instances are serialized as strings. Need to
|
||||
// convert them back to Date instances.
|
||||
(q.initialInfo as any).start = new Date(q.initialInfo.start);
|
||||
if (q.completedQuery) {
|
||||
// Again, need to explicitly set prototypes.
|
||||
Object.setPrototypeOf(q.completedQuery, CompletedQueryInfo.prototype);
|
||||
Object.setPrototypeOf(q.completedQuery.query, QueryEvaluationInfo.prototype);
|
||||
// slurped queries do not need to be disposed
|
||||
q.completedQuery.dispose = () => { /**/ };
|
||||
}
|
||||
} else if (q.t === 'remote') {
|
||||
// TODO Remote queries are not implemented yet.
|
||||
}
|
||||
return q;
|
||||
});
|
||||
|
||||
// filter out queries that have been deleted on disk
|
||||
// most likely another workspace has deleted them because the
|
||||
// queries aged out.
|
||||
return asyncFilter(parsedQueries, async (q) => {
|
||||
if (q.t !== 'local') {
|
||||
return false;
|
||||
}
|
||||
const resultsPath = q.completedQuery?.query.resultsPaths.resultsPath;
|
||||
return !!resultsPath && await fs.pathExists(resultsPath);
|
||||
});
|
||||
} catch (e) {
|
||||
void showAndLogErrorMessage('Error loading query history.', {
|
||||
fullMessage: ['Error loading query history.', e.stack].join('\n'),
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the query history to disk. It is not necessary that the parent directory
|
||||
* exists, but if it does, it must be writable. An existing file will be overwritten.
|
||||
*
|
||||
* Any errors will be rethrown.
|
||||
*
|
||||
* @param queries the list of queries to save.
|
||||
* @param fsPath the path to save the queries to.
|
||||
*/
|
||||
export async function splatQueryHistory(queries: QueryHistoryInfo[], fsPath: string): Promise<void> {
|
||||
try {
|
||||
if (!(await fs.pathExists(fsPath))) {
|
||||
await fs.mkdir(path.dirname(fsPath), { recursive: true });
|
||||
}
|
||||
// remove incomplete queries since they cannot be recreated on restart
|
||||
const filteredQueries = queries.filter(q => q.t === 'local' && q.completedQuery !== undefined);
|
||||
const data = JSON.stringify(filteredQueries, null, 2);
|
||||
await fs.writeFile(fsPath, data);
|
||||
} catch (e) {
|
||||
throw new Error(`Error saving query history to ${fsPath}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
5
extensions/ql-vscode/src/query-status.ts
Normal file
5
extensions/ql-vscode/src/query-status.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum QueryStatus {
|
||||
InProgress = 'InProgress',
|
||||
Completed = 'Completed',
|
||||
Failed = 'Failed',
|
||||
}
|
||||
15
extensions/ql-vscode/src/remote-queries/remote-query-info.ts
Normal file
15
extensions/ql-vscode/src/remote-queries/remote-query-info.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
// TODO This is a stub and will be filled implemented in later PRs.
|
||||
|
||||
import { QueryStatus } from '../query-status';
|
||||
|
||||
/**
|
||||
* Information about a remote query.
|
||||
*/
|
||||
export interface RemoteQueryInfo {
|
||||
readonly t: 'remote';
|
||||
label: string;
|
||||
failureReason: string | undefined;
|
||||
status: QueryStatus;
|
||||
isCompleted(): boolean;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { QueryEvaluationInfo, QueryWithResults } from '../../run-queries';
|
||||
import { QueryHistoryConfigListener } from '../../config';
|
||||
import * as messages from '../../pure/messages';
|
||||
import { QueryServerClient } from '../../queryserver-client';
|
||||
import { FullQueryInfo, InitialQueryInfo } from '../../query-results';
|
||||
import { LocalQueryInfo, InitialQueryInfo } from '../../query-results';
|
||||
import { DatabaseManager } from '../../databases';
|
||||
import * as tmp from 'tmp-promise';
|
||||
import { ONE_DAY_IN_MS, ONE_HOUR_IN_MS, TWO_HOURS_IN_MS, THREE_HOURS_IN_MS } from '../../pure/helpers-pure';
|
||||
@@ -107,7 +107,7 @@ describe('query-history', () => {
|
||||
});
|
||||
});
|
||||
|
||||
let allHistory: FullQueryInfo[];
|
||||
let allHistory: LocalQueryInfo[];
|
||||
|
||||
beforeEach(() => {
|
||||
allHistory = [
|
||||
@@ -520,13 +520,14 @@ describe('query-history', () => {
|
||||
},
|
||||
completedQuery: {
|
||||
resultCount,
|
||||
}
|
||||
},
|
||||
t: 'local'
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function createMockFullQueryInfo(dbName = 'a', queryWitbResults?: QueryWithResults, isFail = false): FullQueryInfo {
|
||||
const fqi = new FullQueryInfo(
|
||||
function createMockFullQueryInfo(dbName = 'a', queryWitbResults?: QueryWithResults, isFail = false): LocalQueryInfo {
|
||||
const fqi = new LocalQueryInfo(
|
||||
{
|
||||
databaseInfo: { name: dbName },
|
||||
start: new Date(),
|
||||
@@ -736,7 +737,7 @@ describe('query-history', () => {
|
||||
};
|
||||
}
|
||||
|
||||
async function createMockQueryHistory(allHistory: FullQueryInfo[]) {
|
||||
async function createMockQueryHistory(allHistory: LocalQueryInfo[]) {
|
||||
const qhm = new QueryHistoryManager(
|
||||
{} as QueryServerClient,
|
||||
{} as DatabaseManager,
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'mocha';
|
||||
import 'sinon-chai';
|
||||
import * as sinon from 'sinon';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import { FullQueryInfo, InitialQueryInfo, interpretResults } from '../../query-results';
|
||||
import { LocalQueryInfo, InitialQueryInfo, interpretResults } from '../../query-results';
|
||||
import { QueryEvaluationInfo, QueryWithResults } from '../../run-queries';
|
||||
import { QueryHistoryConfig } from '../../config';
|
||||
import { EvaluationResult, QueryResultType } from '../../pure/messages';
|
||||
@@ -13,6 +13,7 @@ import { DatabaseInfo, SortDirection, SortedResultSetInfo } from '../../pure/int
|
||||
import { CodeQLCliServer, SourceInfo } from '../../cli';
|
||||
import { CancellationTokenSource, Uri, env } from 'vscode';
|
||||
import { tmpDir } from '../../helpers';
|
||||
import { slurpQueryHistory, splatQueryHistory } from '../../query-serialization';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
@@ -277,12 +278,12 @@ describe('query-results', () => {
|
||||
const allHistoryPath = path.join(tmpDir.name, 'workspace-query-history.json');
|
||||
|
||||
// splat and slurp
|
||||
await FullQueryInfo.splat(allHistory, allHistoryPath);
|
||||
const allHistoryActual = await FullQueryInfo.slurp(allHistoryPath, mockConfig);
|
||||
await splatQueryHistory(allHistory, allHistoryPath);
|
||||
const allHistoryActual = await slurpQueryHistory(allHistoryPath, mockConfig);
|
||||
|
||||
// the dispose methods will be different. Ignore them.
|
||||
allHistoryActual.forEach(info => {
|
||||
if (info.completedQuery) {
|
||||
if (info.t === 'local' && info.completedQuery) {
|
||||
const completedQuery = info.completedQuery;
|
||||
(completedQuery as any).dispose = undefined;
|
||||
|
||||
@@ -355,8 +356,8 @@ describe('query-results', () => {
|
||||
return result;
|
||||
}
|
||||
|
||||
function createMockFullQueryInfo(dbName = 'a', queryWitbResults?: QueryWithResults, isFail = false): FullQueryInfo {
|
||||
const fqi = new FullQueryInfo(
|
||||
function createMockFullQueryInfo(dbName = 'a', queryWitbResults?: QueryWithResults, isFail = false): LocalQueryInfo {
|
||||
const fqi = new LocalQueryInfo(
|
||||
{
|
||||
databaseInfo: {
|
||||
name: dbName,
|
||||
|
||||
Reference in New Issue
Block a user