Skip to content

Allow exporting of results for non-alert queries #929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [UNRELEASED]

- Add support for filename pattern in history view. [#930](https://github.com/github/vscode-codeql/pull/930)
- Add an option _View Results (CSV)_ to view the results of a non-alert query. The existing options for alert queries have been renamed to _View Alerts_ to avoid confusion. [#929](https://github.com/github/vscode-codeql/pull/929)

## 1.5.3 - 18 August 2021

Expand Down
21 changes: 17 additions & 4 deletions extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,12 @@
"title": "View Results (CSV)"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"title": "View Results (SARIF)"
"command": "codeQLQueryHistory.viewCsvAlerts",
"title": "View Alerts (CSV)"
},
{
"command": "codeQLQueryHistory.viewSarifAlerts",
"title": "View Alerts (SARIF)"
},
{
"command": "codeQLQueryHistory.viewDil",
Expand Down Expand Up @@ -646,10 +650,15 @@
{
"command": "codeQLQueryHistory.viewCsvResults",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem != interpretedResultsItem"
},
{
"command": "codeQLQueryHistory.viewCsvAlerts",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"command": "codeQLQueryHistory.viewSarifAlerts",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
},
Expand Down Expand Up @@ -805,7 +814,11 @@
"when": "false"
},
{
"command": "codeQLQueryHistory.viewSarifResults",
"command": "codeQLQueryHistory.viewCsvAlerts",
"when": "false"
},
{
"command": "codeQLQueryHistory.viewSarifAlerts",
"when": "false"
},
{
Expand Down
2 changes: 1 addition & 1 deletion extensions/ql-vscode/src/contextual/locationFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ProgressCallback } from '../commandRunner';
import { KeyType } from './keyType';
import { qlpackOfDatabase, resolveQueries } from './queryResolver';

const SELECT_QUERY_NAME = '#select';
export const SELECT_QUERY_NAME = '#select';
export const TEMPLATE_NAME = 'selectedSourceFile';

export interface FullLocationLink extends vscode.LocationLink {
Expand Down
30 changes: 27 additions & 3 deletions extensions/ql-vscode/src/query-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,14 @@ export class QueryHistoryManager extends DisposableObject {
);
this.push(
commandRunner(
'codeQLQueryHistory.viewSarifResults',
this.handleViewSarifResults.bind(this)
'codeQLQueryHistory.viewCsvAlerts',
this.handleViewCsvAlerts.bind(this)
)
);
this.push(
commandRunner(
'codeQLQueryHistory.viewSarifAlerts',
this.handleViewSarifAlerts.bind(this)
)
);
this.push(
Expand Down Expand Up @@ -550,7 +556,7 @@ export class QueryHistoryManager extends DisposableObject {
await vscode.window.showTextDocument(doc, { preview: false });
}

async handleViewSarifResults(
async handleViewSarifAlerts(
singleItem: CompletedQuery,
multiSelect: CompletedQuery[]
) {
Expand Down Expand Up @@ -578,6 +584,24 @@ export class QueryHistoryManager extends DisposableObject {
if (!this.assertSingleQuery(multiSelect)) {
return;
}
if (await singleItem.query.hasCsv()) {
void this.tryOpenExternalFile(singleItem.query.csvPath);
return;
}
await singleItem.query.exportCsvResults(this.qs, singleItem.query.csvPath, () => {
void this.tryOpenExternalFile(
singleItem.query.csvPath
);
});
}

async handleViewCsvAlerts(
singleItem: CompletedQuery,
multiSelect: CompletedQuery[]
) {
if (!this.assertSingleQuery(multiSelect)) {
return;
}

await this.tryOpenExternalFile(
await singleItem.query.ensureCsvProduced(this.qs)
Expand Down
25 changes: 25 additions & 0 deletions extensions/ql-vscode/src/run-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import * as qsClient from './queryserver-client';
import { isQuickQueryPath } from './quick-query';
import { compileDatabaseUpgradeSequence, hasNondestructiveUpgradeCapabilities, upgradeDatabaseExplicit } from './upgrades';
import { ensureMetadataIsComplete } from './query-results';
import { SELECT_QUERY_NAME } from './contextual/locationFinder';
import { DecodedBqrsChunk } from './pure/bqrs-cli-types';

/**
* run-queries.ts
Expand Down Expand Up @@ -216,6 +218,29 @@ export class QueryInfo {
return this.dilPath;
}

async exportCsvResults(qs: qsClient.QueryServerClient, csvPath: string, onFinish: () => void): Promise<void> {
let stopDecoding = false;
const out = fs.createWriteStream(csvPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the csv file already exists, then maybe no need to recreate it? Though if the operation is fast, not a big deal either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is already not being called if the file already exists (see this line)

out.on('finish', onFinish);
out.on('error', () => {
if (!stopDecoding) {
stopDecoding = true;
void showAndLogErrorMessage(`Failed to write CSV results to ${csvPath}`);
}
});
let nextOffset: number | undefined = 0;
while (nextOffset !== undefined && !stopDecoding) {
const chunk: DecodedBqrsChunk = await qs.cliServer.bqrsDecode(this.resultsPaths.resultsPath, SELECT_QUERY_NAME, {
pageSize: 100,
offset: nextOffset,
});
for (const tuple of chunk.tuples)
out.write(tuple.join(',') + '\n');
nextOffset = chunk.next;
}
out.end();
}

async ensureCsvProduced(qs: qsClient.QueryServerClient): Promise<string> {
if (await this.hasCsv()) {
return this.csvPath;
Expand Down