Skip to content

Commit 9928c33

Browse files
committed
Store query output dir on history items
This will add the `QueryOutputDir` to the `InitialQueryInfo` and populate it when creating a local query history item. This will allow us to open the results directory or show the evaluator log without a completed query.
1 parent df55e03 commit 9928c33

File tree

10 files changed

+101
-14
lines changed

10 files changed

+101
-14
lines changed

extensions/ql-vscode/src/local-queries/local-queries.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,15 @@ export class LocalQueries extends DisposableObject {
362362
);
363363
}
364364

365-
const initialInfo = await createInitialQueryInfo(selectedQuery, {
366-
databaseUri: dbItem.databaseUri.toString(),
367-
name: dbItem.name,
368-
language: tryGetQueryLanguage(dbItem.language),
369-
});
365+
const initialInfo = await createInitialQueryInfo(
366+
selectedQuery,
367+
{
368+
databaseUri: dbItem.databaseUri.toString(),
369+
name: dbItem.name,
370+
language: tryGetQueryLanguage(dbItem.language),
371+
},
372+
outputDir,
373+
);
370374

371375
// When cancellation is requested from the query history view, we just stop the debug session.
372376
const queryInfo = new LocalQueryInfo(initialInfo, tokenSource);

extensions/ql-vscode/src/query-history/query-history-manager.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -723,11 +723,12 @@ export class QueryHistoryManager extends DisposableObject {
723723
async handleOpenQueryDirectory(item: QueryHistoryInfo) {
724724
let externalFilePath: string | undefined;
725725
if (item.t === "local") {
726-
if (item.completedQuery) {
727-
externalFilePath = join(
728-
item.completedQuery.query.querySaveDir,
729-
"timestamp",
730-
);
726+
const querySaveDir =
727+
item.initialInfo.outputDir?.querySaveDir ??
728+
item.completedQuery?.query.querySaveDir;
729+
730+
if (querySaveDir) {
731+
externalFilePath = join(querySaveDir, "timestamp");
731732
}
732733
} else if (item.t === "variant-analysis") {
733734
externalFilePath = join(
@@ -761,9 +762,18 @@ export class QueryHistoryManager extends DisposableObject {
761762
`Failed to open ${externalFilePath}: ${getErrorMessage(e)}`,
762763
);
763764
}
765+
} else {
766+
this.warnNoQueryDir();
764767
}
765768
}
766769

770+
private warnNoQueryDir() {
771+
void showAndLogWarningMessage(
772+
this.app.logger,
773+
`Results directory is not available for this run.`,
774+
);
775+
}
776+
767777
private warnNoEvalLogs() {
768778
void showAndLogWarningMessage(
769779
this.app.logger,

extensions/ql-vscode/src/query-history/store/query-history-local-query-domain-mapper.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ function mapInitialQueryInfoToDto(
109109
},
110110
start: localQueryInitialInfo.start,
111111
id: localQueryInitialInfo.id,
112+
outputDir: localQueryInitialInfo.outputDir
113+
? {
114+
querySaveDir: localQueryInitialInfo.outputDir.querySaveDir,
115+
}
116+
: undefined,
112117
};
113118
}
114119

extensions/ql-vscode/src/query-history/store/query-history-local-query-dto-mapper.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
CompletedQueryInfo,
44
InitialQueryInfo,
55
} from "../../query-results";
6-
import { QueryEvaluationInfo } from "../../run-queries-shared";
6+
import { QueryEvaluationInfo, QueryOutputDir } from "../../run-queries-shared";
77
import {
88
CompletedQueryInfoDto,
99
QueryEvaluationInfoDto,
@@ -26,7 +26,10 @@ export function mapLocalQueryItemToDomainModel(
2626
localQuery: QueryHistoryLocalQueryDto,
2727
): LocalQueryInfo {
2828
return new LocalQueryInfo(
29-
mapInitialQueryInfoToDomainModel(localQuery.initialInfo),
29+
mapInitialQueryInfoToDomainModel(
30+
localQuery.initialInfo,
31+
localQuery.completedQuery?.query?.querySaveDir,
32+
),
3033
undefined,
3134
localQuery.failureReason,
3235
localQuery.completedQuery &&
@@ -72,7 +75,14 @@ function mapCompletedQueryInfoToDomainModel(
7275

7376
function mapInitialQueryInfoToDomainModel(
7477
initialInfo: InitialQueryInfoDto,
78+
// The completedQuerySaveDir is a migration to support old query items that don't have
79+
// the querySaveDir in the initialInfo. It should be removed once all query
80+
// items have the querySaveDir in the initialInfo.
81+
completedQuerySaveDir?: string,
7582
): InitialQueryInfo {
83+
const querySaveDir =
84+
initialInfo.outputDir?.querySaveDir ?? completedQuerySaveDir;
85+
7686
return {
7787
userSpecifiedLabel: initialInfo.userSpecifiedLabel,
7888
queryText: initialInfo.queryText,
@@ -90,6 +100,7 @@ function mapInitialQueryInfoToDomainModel(
90100
},
91101
start: new Date(initialInfo.start),
92102
id: initialInfo.id,
103+
outputDir: querySaveDir ? new QueryOutputDir(querySaveDir) : undefined,
93104
};
94105
}
95106

extensions/ql-vscode/src/query-history/store/query-history-local-query-dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export interface InitialQueryInfoDto {
2424
databaseInfo: DatabaseInfoDto;
2525
start: Date;
2626
id: string;
27+
outputDir?: QueryOutputDirDto; // Undefined for backwards compatibility
28+
}
29+
30+
interface QueryOutputDirDto {
31+
querySaveDir: string;
2732
}
2833

2934
interface DatabaseInfoDto {

extensions/ql-vscode/src/query-results.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { QueryStatus } from "./query-history/query-status";
1919
import {
2020
EvaluatorLogPaths,
2121
QueryEvaluationInfo,
22+
QueryOutputDir,
2223
QueryWithResults,
2324
} from "./run-queries-shared";
2425
import { formatLegacyMessage } from "./query-server/legacy";
@@ -47,6 +48,7 @@ export interface InitialQueryInfo {
4748
readonly databaseInfo: DatabaseInfo;
4849
readonly start: Date;
4950
readonly id: string; // unique id for this query.
51+
readonly outputDir?: QueryOutputDir; // If missing, we do not have a query save dir. The query may have been cancelled. This is only for backwards compatibility.
5052
}
5153

5254
export class CompletedQueryInfo implements QueryWithResults {

extensions/ql-vscode/src/run-queries-shared.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,11 +562,13 @@ async function convertToQlPath(filePath: string): Promise<string> {
562562
*
563563
* @param selectedQuery The query to run, including any quickeval info.
564564
* @param databaseInfo The database to run the query against.
565+
* @param outputDir The output directory for this query.
565566
* @returns The initial information for the query to be run.
566567
*/
567568
export async function createInitialQueryInfo(
568569
selectedQuery: SelectedQuery,
569570
databaseInfo: DatabaseInfo,
571+
outputDir: QueryOutputDir,
570572
): Promise<InitialQueryInfo> {
571573
const isQuickEval = selectedQuery.quickEval !== undefined;
572574
const isQuickEvalCount =
@@ -587,6 +589,7 @@ export async function createInitialQueryInfo(
587589
: {
588590
queryText: await readFile(selectedQuery.queryPath, "utf8"),
589591
}),
592+
outputDir,
590593
};
591594
}
592595

extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { faker } from "@faker-js/faker";
22
import { InitialQueryInfo, LocalQueryInfo } from "../../../src/query-results";
33
import {
44
QueryEvaluationInfo,
5+
QueryOutputDir,
56
QueryWithResults,
67
} from "../../../src/run-queries-shared";
78
import { CancellationTokenSource } from "vscode";
@@ -18,6 +19,7 @@ export function createMockLocalQueryInfo({
1819
hasMetadata = false,
1920
queryWithResults = undefined,
2021
language = undefined,
22+
outputDir = new QueryOutputDir("/a/b/c"),
2123
}: {
2224
startTime?: Date;
2325
resultCount?: number;
@@ -27,6 +29,7 @@ export function createMockLocalQueryInfo({
2729
hasMetadata?: boolean;
2830
queryWithResults?: QueryWithResults | undefined;
2931
language?: QueryLanguage;
32+
outputDir?: QueryOutputDir | undefined;
3033
}): LocalQueryInfo {
3134
const cancellationToken = {
3235
dispose: () => {
@@ -48,6 +51,7 @@ export function createMockLocalQueryInfo({
4851
start: startTime,
4952
id: faker.number.int().toString(),
5053
userSpecifiedLabel,
54+
outputDir,
5155
} as InitialQueryInfo;
5256

5357
const localQuery = new LocalQueryInfo(initialQueryInfo, cancellationToken);

extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
LocalQueryInfo,
99
InitialQueryInfo,
1010
} from "../../../../../src/query-results";
11-
import { QueryWithResults } from "../../../../../src/run-queries-shared";
11+
import {
12+
QueryOutputDir,
13+
QueryWithResults,
14+
} from "../../../../../src/run-queries-shared";
1215
import { DatabaseInfo } from "../../../../../src/common/interface-types";
1316
import { CancellationTokenSource, Uri } from "vscode";
1417
import { tmpDir } from "../../../../../src/tmp-dir";
@@ -130,6 +133,38 @@ describe("write and read", () => {
130133
expect(allHistoryActual.length).toEqual(expectedHistory.length);
131134
});
132135

136+
it("should read query output dir from completed query if not present", async () => {
137+
const historyPath = join(tmpDir.name, "workspace-query-history.json");
138+
139+
const queryItem = createMockFullQueryInfo(
140+
"a",
141+
createMockQueryWithResults(
142+
`${queryPath}-a`,
143+
false,
144+
false,
145+
"/a/b/c/a",
146+
false,
147+
),
148+
false,
149+
null,
150+
);
151+
152+
// write and read
153+
await writeQueryHistoryToFile([queryItem], historyPath);
154+
const actual = await readQueryHistoryFromFile(historyPath);
155+
156+
expect(actual).toHaveLength(1);
157+
158+
expect(actual[0].t).toEqual("local");
159+
160+
if (actual[0].t === "local") {
161+
expect(actual[0].initialInfo.outputDir?.querySaveDir).not.toBeUndefined();
162+
expect(actual[0].initialInfo.outputDir?.querySaveDir).toEqual(
163+
queryItem.completedQuery?.query?.querySaveDir,
164+
);
165+
}
166+
});
167+
133168
it("should remove remote queries from the history", async () => {
134169
const path = join(tmpDir.name, "query-history-with-remote.json");
135170
await writeFile(
@@ -205,6 +240,9 @@ describe("write and read", () => {
205240
dbName = "a",
206241
queryWithResults?: QueryWithResults,
207242
isFail = false,
243+
outputDir: QueryOutputDir | null = new QueryOutputDir(
244+
"/path/to/output/dir",
245+
),
208246
): LocalQueryInfo {
209247
const fqi = new LocalQueryInfo(
210248
{
@@ -218,6 +256,7 @@ describe("write and read", () => {
218256
isQuickQuery: false,
219257
isQuickEval: false,
220258
id: `some-id-${dbName}`,
259+
outputDir: outputDir ? outputDir : undefined,
221260
} as InitialQueryInfo,
222261
{
223262
dispose: () => {

extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
InitialQueryInfo,
1313
interpretResultsSarif,
1414
} from "../../../src/query-results";
15-
import { QueryWithResults } from "../../../src/run-queries-shared";
15+
import {
16+
QueryOutputDir,
17+
QueryWithResults,
18+
} from "../../../src/run-queries-shared";
1619
import {
1720
DatabaseInfo,
1821
SortDirection,
@@ -506,6 +509,7 @@ describe("query-results", () => {
506509
isQuickQuery: false,
507510
isQuickEval: false,
508511
id: `some-id-${dbName}`,
512+
outputDir: new QueryOutputDir("path/to/output/dir"),
509513
} as InitialQueryInfo,
510514
{
511515
dispose: () => {

0 commit comments

Comments
 (0)