|
7 | 7 | Uri, |
8 | 8 | window, |
9 | 9 | } from "vscode"; |
10 | | -import { BaseLogger, extLogger, TeeLogger } from "./common"; |
| 10 | +import { BaseLogger, extLogger, Logger, TeeLogger } from "./common"; |
11 | 11 | import { MAX_QUERIES } from "./config"; |
12 | 12 | import { gatherQlFiles } from "./pure/files"; |
13 | 13 | import { basename } from "path"; |
@@ -72,13 +72,112 @@ function formatResultMessage(result: CoreQueryResults): string { |
72 | 72 | } |
73 | 73 | } |
74 | 74 |
|
| 75 | +/** |
| 76 | + * Tracks the evaluation of a local query, including its interactions with the UI. |
| 77 | + * |
| 78 | + * The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes |
| 79 | + * the `complete()` function once the query has completed (successfully or otherwise). |
| 80 | + * |
| 81 | + * Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having |
| 82 | + * the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way |
| 83 | + * because once we move query evaluation into a Debug Adapter, the debugging UI drives the |
| 84 | + * evaluation, and we can only respond to events from the debug adapter. |
| 85 | + */ |
| 86 | +export class LocalQueryRun { |
| 87 | + public constructor( |
| 88 | + private readonly outputDir: QueryOutputDir, |
| 89 | + private readonly localQueries: LocalQueries, |
| 90 | + private readonly queryInfo: LocalQueryInfo, |
| 91 | + private readonly dbItem: DatabaseItem, |
| 92 | + public readonly logger: Logger, |
| 93 | + ) {} |
| 94 | + |
| 95 | + /** |
| 96 | + * Updates the UI based on the results of the query evaluation. This creates the evaluator log |
| 97 | + * summaries, updates the query history item for the evaluation with the results and evaluation |
| 98 | + * time, and displays the results view. |
| 99 | + * |
| 100 | + * This function must be called when the evaluation completes, whether the evaluation was |
| 101 | + * successful or not. |
| 102 | + * */ |
| 103 | + public async complete(results: CoreQueryResults): Promise<void> { |
| 104 | + const evalLogPaths = await this.localQueries.summarizeEvalLog( |
| 105 | + results.resultType, |
| 106 | + this.outputDir, |
| 107 | + this.logger, |
| 108 | + ); |
| 109 | + if (evalLogPaths !== undefined) { |
| 110 | + this.queryInfo.setEvaluatorLogPaths(evalLogPaths); |
| 111 | + } |
| 112 | + const queryWithResults = await this.getCompletedQueryInfo(results); |
| 113 | + this.localQueries.queryHistoryManager.completeQuery( |
| 114 | + this.queryInfo, |
| 115 | + queryWithResults, |
| 116 | + ); |
| 117 | + await this.localQueries.showResultsForCompletedQuery( |
| 118 | + this.queryInfo as CompletedLocalQueryInfo, |
| 119 | + WebviewReveal.Forced, |
| 120 | + ); |
| 121 | + // Note we must update the query history view after showing results as the |
| 122 | + // display and sorting might depend on the number of results |
| 123 | + await this.localQueries.queryHistoryManager.refreshTreeView(); |
| 124 | + } |
| 125 | + |
| 126 | + /** |
| 127 | + * Gets a `QueryWithResults` containing information about the evaluation of the query and its |
| 128 | + * result, in the form expected by the query history UI. |
| 129 | + */ |
| 130 | + private async getCompletedQueryInfo( |
| 131 | + results: CoreQueryResults, |
| 132 | + ): Promise<QueryWithResults> { |
| 133 | + // Read the query metadata if possible, to use in the UI. |
| 134 | + const metadata = await tryGetQueryMetadata( |
| 135 | + this.localQueries.cliServer, |
| 136 | + this.queryInfo.initialInfo.queryPath, |
| 137 | + ); |
| 138 | + const query = new QueryEvaluationInfo( |
| 139 | + this.outputDir.querySaveDir, |
| 140 | + this.dbItem.databaseUri.fsPath, |
| 141 | + await this.dbItem.hasMetadataFile(), |
| 142 | + this.queryInfo.initialInfo.quickEvalPosition, |
| 143 | + metadata, |
| 144 | + ); |
| 145 | + |
| 146 | + if (results.resultType !== QueryResultType.SUCCESS) { |
| 147 | + const message = results.message |
| 148 | + ? redactableError`${results.message}` |
| 149 | + : redactableError`Failed to run query`; |
| 150 | + void extLogger.log(message.fullMessage); |
| 151 | + void showAndLogExceptionWithTelemetry( |
| 152 | + redactableError`Failed to run query: ${message}`, |
| 153 | + ); |
| 154 | + } |
| 155 | + const message = formatResultMessage(results); |
| 156 | + const successful = results.resultType === QueryResultType.SUCCESS; |
| 157 | + return { |
| 158 | + query, |
| 159 | + result: { |
| 160 | + evaluationTime: results.evaluationTime, |
| 161 | + queryId: 0, |
| 162 | + resultType: successful |
| 163 | + ? QueryResultType.SUCCESS |
| 164 | + : QueryResultType.OTHER_ERROR, |
| 165 | + runId: 0, |
| 166 | + message, |
| 167 | + }, |
| 168 | + message, |
| 169 | + successful, |
| 170 | + }; |
| 171 | + } |
| 172 | +} |
| 173 | + |
75 | 174 | export class LocalQueries extends DisposableObject { |
76 | 175 | public constructor( |
77 | 176 | private readonly app: App, |
78 | 177 | private readonly queryRunner: QueryRunner, |
79 | | - private readonly queryHistoryManager: QueryHistoryManager, |
| 178 | + public readonly queryHistoryManager: QueryHistoryManager, |
80 | 179 | private readonly databaseManager: DatabaseManager, |
81 | | - private readonly cliServer: CodeQLCliServer, |
| 180 | + public readonly cliServer: CodeQLCliServer, |
82 | 181 | private readonly databaseUI: DatabaseUI, |
83 | 182 | private readonly localQueryResultsView: ResultsView, |
84 | 183 | private readonly queryStorageDir: string, |
@@ -234,7 +333,48 @@ export class LocalQueries extends DisposableObject { |
234 | 333 | }; |
235 | 334 | } |
236 | 335 |
|
237 | | - async compileAndRunQuery( |
| 336 | + /** |
| 337 | + * Creates a new `LocalQueryRun` object to track a query evaluation. This creates a timestamp |
| 338 | + * file in the query's output directory, creates a `LocalQueryInfo` object, and registers that |
| 339 | + * object with the query history manager. |
| 340 | + * |
| 341 | + * Once the evaluation is complete, the client must call `complete()` on the `LocalQueryRun` |
| 342 | + * object to update the UI based on the results of the query. |
| 343 | + */ |
| 344 | + public async createLocalQueryRun( |
| 345 | + queryPath: string, |
| 346 | + quickEval: boolean, |
| 347 | + range: Range | undefined, |
| 348 | + dbItem: DatabaseItem, |
| 349 | + outputDir: string, |
| 350 | + tokenSource: CancellationTokenSource, |
| 351 | + ): Promise<LocalQueryRun> { |
| 352 | + const queryOutputDir = new QueryOutputDir(outputDir); |
| 353 | + |
| 354 | + await createTimestampFile(outputDir); |
| 355 | + |
| 356 | + const initialInfo = await createInitialQueryInfo( |
| 357 | + Uri.file(queryPath), |
| 358 | + { |
| 359 | + databaseUri: dbItem.databaseUri.toString(), |
| 360 | + name: dbItem.name, |
| 361 | + }, |
| 362 | + quickEval, |
| 363 | + range, |
| 364 | + ); |
| 365 | + |
| 366 | + // When cancellation is requested from the query history view, we just stop the debug session. |
| 367 | + const queryInfo = new LocalQueryInfo(initialInfo, tokenSource); |
| 368 | + this.queryHistoryManager.addQuery(queryInfo); |
| 369 | + |
| 370 | + const logger = new TeeLogger( |
| 371 | + this.queryRunner.logger, |
| 372 | + queryOutputDir.logPath, |
| 373 | + ); |
| 374 | + return new LocalQueryRun(queryOutputDir, this, queryInfo, dbItem, logger); |
| 375 | + } |
| 376 | + |
| 377 | + public async compileAndRunQuery( |
238 | 378 | quickEval: boolean, |
239 | 379 | selectedQuery: Uri | undefined, |
240 | 380 | progress: ProgressCallback, |
@@ -296,7 +436,7 @@ export class LocalQueries extends DisposableObject { |
296 | 436 | } |
297 | 437 | } |
298 | 438 |
|
299 | | - async compileAndRunQueryOnMultipleDatabases( |
| 439 | + private async compileAndRunQueryOnMultipleDatabases( |
300 | 440 | progress: ProgressCallback, |
301 | 441 | token: CancellationToken, |
302 | 442 | uri: Uri | undefined, |
@@ -364,14 +504,14 @@ export class LocalQueries extends DisposableObject { |
364 | 504 | } |
365 | 505 | } |
366 | 506 |
|
367 | | - async showResultsForCompletedQuery( |
| 507 | + public async showResultsForCompletedQuery( |
368 | 508 | query: CompletedLocalQueryInfo, |
369 | 509 | forceReveal: WebviewReveal, |
370 | 510 | ): Promise<void> { |
371 | 511 | await this.localQueryResultsView.showResults(query, forceReveal, false); |
372 | 512 | } |
373 | 513 |
|
374 | | - async compileAndRunQueryAgainstDatabase( |
| 514 | + private async compileAndRunQueryAgainstDatabase( |
375 | 515 | db: DatabaseItem, |
376 | 516 | initialInfo: InitialQueryInfo, |
377 | 517 | queryStorageDir: string, |
@@ -466,7 +606,7 @@ export class LocalQueries extends DisposableObject { |
466 | 606 | * Gets a `QueryWithResults` containing information about the evaluation of the query and its |
467 | 607 | * result, in the form expected by the query history UI. |
468 | 608 | */ |
469 | | - public async getCompletedQueryInfo( |
| 609 | + private async getCompletedQueryInfo( |
470 | 610 | dbItem: DatabaseItem, |
471 | 611 | queryTarget: CoreQueryTarget, |
472 | 612 | outputDir: QueryOutputDir, |
|
0 commit comments