MRVA: Add command to export markdown results to gist
This commit is contained in:
Родитель
7947afb1b4
Коммит
c829c30688
|
@ -301,6 +301,10 @@
|
|||
"command": "codeQL.runVariantAnalysis",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.exportVariantAnalysisResults",
|
||||
"title": "CodeQL: Export Variant Analysis Results"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"title": "CodeQL: Run Queries in Selected Files"
|
||||
|
@ -837,6 +841,10 @@
|
|||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.exportVariantAnalysisResults",
|
||||
"when": "config.codeQL.canary"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"when": "false"
|
||||
|
|
|
@ -3,9 +3,10 @@ import * as Octokit from '@octokit/rest';
|
|||
|
||||
const GITHUB_AUTH_PROVIDER_ID = 'github';
|
||||
|
||||
// 'repo' scope should be enough for triggering workflows. For a comprehensive list, see:
|
||||
// We need 'repo' scope for triggering workflows and 'gist' scope for exporting results to Gist.
|
||||
// For a comprehensive list of scopes, see:
|
||||
// https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps
|
||||
const SCOPES = ['repo'];
|
||||
const SCOPES = ['repo', 'gist'];
|
||||
|
||||
/**
|
||||
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
||||
|
|
|
@ -895,6 +895,11 @@ async function activateWithInstalledDistribution(
|
|||
await rqm.autoDownloadRemoteQueryResults(queryResult, token);
|
||||
}));
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.exportVariantAnalysisResults', async () => {
|
||||
await rqm.exportVariantAnalysisResults();
|
||||
}));
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
'codeQL.openReferencedFile',
|
||||
|
|
|
@ -590,6 +590,10 @@ export class QueryHistoryManager extends DisposableObject {
|
|||
}
|
||||
}
|
||||
|
||||
getCurrentQueryHistoryItem(): QueryHistoryInfo | undefined {
|
||||
return this.treeDataProvider.getCurrent();
|
||||
}
|
||||
|
||||
async handleRemoveHistoryItem(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[] = []
|
||||
|
|
|
@ -311,3 +311,25 @@ function getWorkflowError(conclusion: string | null): string {
|
|||
|
||||
return `Unexpected variant analysis execution conclusion: ${conclusion}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a gist with the given description and files.
|
||||
* Returns the URL of the created gist.
|
||||
*/
|
||||
export async function createGist(
|
||||
credentials: Credentials,
|
||||
description: string,
|
||||
files: { [key: string]: { content: string } }
|
||||
): Promise<string | undefined> {
|
||||
const octokit = await credentials.getOctokit();
|
||||
console.log(description, files);
|
||||
const response = await octokit.request('POST /gists', {
|
||||
description,
|
||||
files,
|
||||
public: false,
|
||||
});
|
||||
if (response.status >= 300) {
|
||||
throw new Error(`Error exporting variant analysis results: ${response.status} ${response?.data || ''}`);
|
||||
}
|
||||
return response.data.html_url;
|
||||
}
|
||||
|
|
|
@ -305,4 +305,9 @@ export class RemoteQueriesInterfaceManager {
|
|||
fileSize: this.formatFileSize(analysisResult.fileSizeInBytes)
|
||||
}));
|
||||
}
|
||||
|
||||
/** Gets the current query ID */
|
||||
public getCurrentQueryId(): string | undefined {
|
||||
return this.currentQueryId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ import * as fs from 'fs-extra';
|
|||
|
||||
import { Credentials } from '../authentication';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { ProgressCallback } from '../commandRunner';
|
||||
import { ProgressCallback, UserCancellationException } from '../commandRunner';
|
||||
import { createTimestampFile, showAndLogErrorMessage, showAndLogInformationMessage, showInformationMessageWithAction } from '../helpers';
|
||||
import { Logger } from '../logging';
|
||||
import { runRemoteQuery } from './run-remote-query';
|
||||
import { RemoteQueriesInterfaceManager } from './remote-queries-interface';
|
||||
import { RemoteQuery } from './remote-query';
|
||||
import { RemoteQueriesMonitor } from './remote-queries-monitor';
|
||||
import { getRemoteQueryIndex } from './gh-actions-api-client';
|
||||
import { createGist, getRemoteQueryIndex } from './gh-actions-api-client';
|
||||
import { RemoteQueryResultIndex } from './remote-query-result-index';
|
||||
import { RemoteQueryResult } from './remote-query-result';
|
||||
import { DownloadLink } from './download-link';
|
||||
|
@ -23,6 +23,7 @@ import { QueryHistoryManager } from '../query-history';
|
|||
import { QueryStatus } from '../query-status';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { QueryHistoryInfo } from '../query-results';
|
||||
import { generateMarkdown } from './remote-queries-markdown-generation';
|
||||
|
||||
const autoDownloadMaxSize = 300 * 1024;
|
||||
const autoDownloadMaxCount = 100;
|
||||
|
@ -38,7 +39,7 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly qhm: QueryHistoryManager,
|
||||
private readonly storagePath: string,
|
||||
logger: Logger,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
super();
|
||||
this.analysesResultsManager = new AnalysesResultsManager(ctx, cliServer, storagePath, logger);
|
||||
|
@ -301,4 +302,59 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||
queryItem.status = QueryStatus.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
public async exportVariantAnalysisResults(): Promise<void> {
|
||||
const queryId = this.interfaceManager.getCurrentQueryId();
|
||||
const queryHistoryItem = this.qhm.getCurrentQueryHistoryItem();
|
||||
|
||||
if (!queryId || !queryHistoryItem || !queryHistoryItem.completed || queryHistoryItem.t !== 'remote') {
|
||||
throw new Error('No variant analysis results currently open. To open results, click an item in the query history view.');
|
||||
}
|
||||
|
||||
void this.logger.log(`Exporting variant analysis results for query: ${queryId}`);
|
||||
const query = queryHistoryItem.remoteQuery;
|
||||
const analysesResults = this.analysesResultsManager.getAnalysesResults(queryId);
|
||||
|
||||
const credentials = await Credentials.initialize(this.ctx);
|
||||
|
||||
const gistOption = {
|
||||
label: '$(ports-open-browser-icon) Create Gist (GitHub)',
|
||||
};
|
||||
const localMarkdownOption = {
|
||||
label: '$(markdown) Save as markdown',
|
||||
};
|
||||
|
||||
// User selects export format in quick pick
|
||||
const exportFormat = await window.showQuickPick(
|
||||
[gistOption, localMarkdownOption],
|
||||
{
|
||||
placeHolder: 'Select export format',
|
||||
canPickMany: false,
|
||||
ignoreFocusOut: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!exportFormat || !exportFormat.label) {
|
||||
throw new UserCancellationException('No export format selected', true);
|
||||
}
|
||||
|
||||
if (exportFormat === gistOption) {
|
||||
const description = 'CodeQL Variant Analysis Results';
|
||||
|
||||
const markdownFiles = generateMarkdown(query, analysesResults, 'gist');
|
||||
|
||||
// Convert markdownFiles to the appropriate format for uploading to gist
|
||||
const gistFiles = markdownFiles.reduce((acc, cur) => {
|
||||
acc[`${cur.fileName}.md`] = { content: cur.content.join('\n') };
|
||||
return acc;
|
||||
}, {} as { [key: string]: { content: string } });
|
||||
|
||||
const gistUrl = await createGist(credentials, description, gistFiles);
|
||||
void showAndLogInformationMessage(`Variant analysis results exported to [gist](${gistUrl}).`);
|
||||
} else if (exportFormat === localMarkdownOption) {
|
||||
// TODO: Write function that creates local markdown files
|
||||
// const markdownFiles = generateMarkdown(query, analysesResults, 'local');
|
||||
void showAndLogInformationMessage('Local markdown export not yet available');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче