Merge branch 'main' into robertbrignull/always_trigger_monitoring
This commit is contained in:
6
extensions/ql-vscode/.eslintignore
Normal file
6
extensions/ql-vscode/.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.vscode-test/
|
||||
node_modules/
|
||||
out/
|
||||
|
||||
# Include the Storybook config
|
||||
!.storybook
|
||||
@@ -3,7 +3,7 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json"],
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json", "./scripts/tsconfig.json", "./.storybook/tsconfig.json"],
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
env: {
|
||||
|
||||
@@ -7,7 +7,7 @@ import '@vscode/codicons/dist/codicon.css';
|
||||
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
|
||||
export const parameters = {
|
||||
// All props starting with `on` will automatically receive an action as a prop
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
// All props matching these names will automatically get the correct control
|
||||
controls: {
|
||||
matchers: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export function config(entry = []) {
|
||||
return [...entry, require.resolve("./preview.ts")];
|
||||
return [...entry, require.resolve('./preview.ts')];
|
||||
}
|
||||
|
||||
export function managerEntries(entry = []) {
|
||||
return [...entry, require.resolve("./manager.tsx")];
|
||||
return [...entry, require.resolve('./manager.tsx')];
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ export enum VSCodeTheme {
|
||||
export const themeNames: { [key in VSCodeTheme]: string } = {
|
||||
[VSCodeTheme.Dark]: 'Dark+',
|
||||
[VSCodeTheme.Light]: 'Light+',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,11 @@ import type { AnyFramework, PartialStoryFn as StoryFunction, StoryContext } from
|
||||
import { VSCodeTheme } from './theme';
|
||||
|
||||
const themeFiles: { [key in VSCodeTheme]: string } = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
[VSCodeTheme.Dark]: require('!file-loader?modules!../../src/stories/vscode-theme-dark.css').default,
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
[VSCodeTheme.Light]: require('!file-loader?modules!../../src/stories/vscode-theme-light.css').default,
|
||||
}
|
||||
};
|
||||
|
||||
export const withTheme = (
|
||||
StoryFn: StoryFunction<AnyFramework>,
|
||||
@@ -18,7 +20,7 @@ export const withTheme = (
|
||||
const styleSelectorId =
|
||||
context.viewMode === 'docs'
|
||||
? `addon-vscode-theme-docs-${context.id}`
|
||||
: `addon-vscode-theme-theme`;
|
||||
: 'addon-vscode-theme-theme';
|
||||
|
||||
const theme = Object.values(VSCodeTheme).includes(vscodeTheme) ? vscodeTheme as VSCodeTheme : VSCodeTheme.Dark;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"onLanguage:ql",
|
||||
"onLanguage:ql-summary",
|
||||
"onView:codeQLDatabases",
|
||||
"onView:codeQLDatabasesExperimental",
|
||||
"onView:codeQLQueryHistory",
|
||||
"onView:codeQLAstViewer",
|
||||
"onView:codeQLEvalLogViewer",
|
||||
@@ -1208,6 +1209,11 @@
|
||||
"id": "codeQLDatabases",
|
||||
"name": "Databases"
|
||||
},
|
||||
{
|
||||
"id": "codeQLDatabasesExperimental",
|
||||
"name": "Databases",
|
||||
"when": "config.codeQL.canary && config.codeQL.newQueryRunExperience"
|
||||
},
|
||||
{
|
||||
"id": "codeQLQueryHistory",
|
||||
"name": "Query History"
|
||||
@@ -1254,8 +1260,8 @@
|
||||
"integration": "node ./out/vscode-tests/run-integration-tests.js no-workspace,minimal-workspace",
|
||||
"cli-integration": "npm run preintegration && node ./out/vscode-tests/run-integration-tests.js cli-integration",
|
||||
"update-vscode": "node ./node_modules/vscode/bin/install",
|
||||
"format": "tsfmt -r && eslint src test --ext .ts,.tsx --fix",
|
||||
"lint": "eslint src test --ext .ts,.tsx --max-warnings=0",
|
||||
"format": "tsfmt -r && eslint . --ext .ts,.tsx --fix",
|
||||
"lint": "eslint . --ext .ts,.tsx --max-warnings=0",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
|
||||
6
extensions/ql-vscode/scripts/tsconfig.json
Normal file
6
extensions/ql-vscode/scripts/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ViewColumn,
|
||||
Uri,
|
||||
WebviewPanelOptions,
|
||||
WebviewOptions
|
||||
WebviewOptions,
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -27,6 +27,8 @@ export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMess
|
||||
protected panelLoaded = false;
|
||||
protected panelLoadedCallBacks: (() => void)[] = [];
|
||||
|
||||
private panelResolves?: Array<(panel: WebviewPanel) => void>;
|
||||
|
||||
constructor(
|
||||
protected readonly ctx: ExtensionContext
|
||||
) {
|
||||
@@ -35,20 +37,36 @@ export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMess
|
||||
|
||||
public async restoreView(panel: WebviewPanel): Promise<void> {
|
||||
this.panel = panel;
|
||||
this.setupPanel(panel);
|
||||
const config = await this.getPanelConfig();
|
||||
this.setupPanel(panel, config);
|
||||
}
|
||||
|
||||
protected get isShowingPanel() {
|
||||
return !!this.panel;
|
||||
}
|
||||
|
||||
protected getPanel(): WebviewPanel {
|
||||
protected async getPanel(): Promise<WebviewPanel> {
|
||||
if (this.panel == undefined) {
|
||||
const { ctx } = this;
|
||||
|
||||
const config = this.getPanelConfig();
|
||||
// This is an async method, so in theory this method can be called concurrently. To ensure that we don't
|
||||
// create two panels, we use a promise that resolves when the panel is created. This way, if the panel is
|
||||
// being created, the promise will resolve when it is done.
|
||||
if (this.panelResolves !== undefined) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.panel !== undefined) {
|
||||
resolve(this.panel);
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel = Window.createWebviewPanel(
|
||||
this.panelResolves?.push(resolve);
|
||||
});
|
||||
}
|
||||
this.panelResolves = [];
|
||||
|
||||
const config = await this.getPanelConfig();
|
||||
|
||||
const panel = Window.createWebviewPanel(
|
||||
config.viewId,
|
||||
config.title,
|
||||
{ viewColumn: config.viewColumn, preserveFocus: config.preserveFocus },
|
||||
@@ -64,14 +82,17 @@ export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMess
|
||||
],
|
||||
}
|
||||
);
|
||||
this.setupPanel(this.panel);
|
||||
this.panel = panel;
|
||||
|
||||
this.setupPanel(panel, config);
|
||||
|
||||
this.panelResolves.forEach((resolve) => resolve(panel));
|
||||
this.panelResolves = undefined;
|
||||
}
|
||||
return this.panel;
|
||||
}
|
||||
|
||||
protected setupPanel(panel: WebviewPanel): void {
|
||||
const config = this.getPanelConfig();
|
||||
|
||||
protected setupPanel(panel: WebviewPanel, config: WebviewPanelConfig): void {
|
||||
this.push(
|
||||
panel.onDidDispose(
|
||||
() => {
|
||||
@@ -101,7 +122,7 @@ export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMess
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract getPanelConfig(): WebviewPanelConfig;
|
||||
protected abstract getPanelConfig(): WebviewPanelConfig | Promise<WebviewPanelConfig>;
|
||||
|
||||
protected abstract onPanelDispose(): void;
|
||||
|
||||
@@ -123,8 +144,9 @@ export abstract class AbstractWebview<ToMessage extends WebviewMessage, FromMess
|
||||
this.panelLoadedCallBacks = [];
|
||||
}
|
||||
|
||||
protected postMessage(msg: ToMessage): Thenable<boolean> {
|
||||
return this.getPanel().webview.postMessage(msg);
|
||||
protected async postMessage(msg: ToMessage): Promise<boolean> {
|
||||
const panel = await this.getPanel();
|
||||
return panel.webview.postMessage(msg);
|
||||
}
|
||||
|
||||
public dispose(disposeHandler?: DisposeHandler) {
|
||||
|
||||
@@ -46,7 +46,8 @@ export class CompareView extends AbstractWebview<ToCompareViewMessage, FromCompa
|
||||
selectedResultSetName?: string
|
||||
) {
|
||||
this.comparePair = { from, to };
|
||||
this.getPanel().reveal(undefined, true);
|
||||
const panel = await this.getPanel();
|
||||
panel.reveal(undefined, true);
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
const [
|
||||
|
||||
@@ -438,6 +438,16 @@ export function isVariantAnalysisLiveResultsEnabled(): boolean {
|
||||
return !!LIVE_RESULTS.getValue<boolean>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A flag indicating whether to use the new query run experience which involves
|
||||
* using a new database panel.
|
||||
*/
|
||||
const NEW_QUERY_RUN_EXPERIENCE = new Setting('newQueryRunExperience', ROOT_SETTING);
|
||||
|
||||
export function isNewQueryRunExperienceEnabled(): boolean {
|
||||
return !!NEW_QUERY_RUN_EXPERIENCE.getValue<boolean>();
|
||||
}
|
||||
|
||||
// Settings for mocking the GitHub API.
|
||||
const MOCK_GH_API_SERVER = new Setting('mockGitHubApiServer', ROOT_SETTING);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
languages,
|
||||
Uri,
|
||||
window as Window,
|
||||
env
|
||||
env, WebviewPanel
|
||||
} from 'vscode';
|
||||
import * as cli from './cli';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
@@ -341,6 +341,8 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
return;
|
||||
}
|
||||
|
||||
const panel = await this.getPanel();
|
||||
|
||||
this._interpretation = undefined;
|
||||
const interpretationPage = await this.interpretResultsInfo(
|
||||
fullQuery.completedQuery.query,
|
||||
@@ -350,12 +352,11 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
const sortedResultsMap: SortedResultsMap = {};
|
||||
Object.entries(fullQuery.completedQuery.sortedResultsInfo).forEach(
|
||||
([k, v]) =>
|
||||
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v))
|
||||
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(panel, v))
|
||||
);
|
||||
|
||||
this._displayedQuery = fullQuery;
|
||||
|
||||
const panel = this.getPanel();
|
||||
await this.waitForPanelLoaded();
|
||||
if (!panel.visible) {
|
||||
if (forceReveal === WebviewReveal.Forced) {
|
||||
@@ -426,6 +427,7 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
interpretation: interpretationPage,
|
||||
origResultsPaths: fullQuery.completedQuery.query.resultsPaths,
|
||||
resultsPath: this.convertPathToWebviewUri(
|
||||
panel,
|
||||
fullQuery.completedQuery.query.resultsPaths.resultsPath
|
||||
),
|
||||
parsedResultSets,
|
||||
@@ -498,10 +500,12 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
throw new Error('trying to view a page of a query that is not loaded');
|
||||
}
|
||||
|
||||
const panel = await this.getPanel();
|
||||
|
||||
const sortedResultsMap: SortedResultsMap = {};
|
||||
Object.entries(results.completedQuery.sortedResultsInfo).forEach(
|
||||
([k, v]) =>
|
||||
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v))
|
||||
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(panel, v))
|
||||
);
|
||||
|
||||
const resultSetSchemas = await this.getResultSetSchemas(results.completedQuery, sorted ? selectedTable : '');
|
||||
@@ -544,6 +548,7 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
interpretation: this._interpretation,
|
||||
origResultsPaths: results.completedQuery.query.resultsPaths,
|
||||
resultsPath: this.convertPathToWebviewUri(
|
||||
panel,
|
||||
results.completedQuery.query.resultsPaths.resultsPath
|
||||
),
|
||||
parsedResultSets,
|
||||
@@ -812,15 +817,16 @@ export class ResultsView extends AbstractWebview<IntoResultsViewMsg, FromResults
|
||||
this._diagnosticCollection.set(diagnostics);
|
||||
}
|
||||
|
||||
private convertPathToWebviewUri(path: string): string {
|
||||
return fileUriToWebviewUri(this.getPanel(), Uri.file(path));
|
||||
private convertPathToWebviewUri(panel: WebviewPanel, path: string): string {
|
||||
return fileUriToWebviewUri(panel, Uri.file(path));
|
||||
}
|
||||
|
||||
private convertPathPropertiesToWebviewUris(
|
||||
panel: WebviewPanel,
|
||||
info: SortedResultSetInfo
|
||||
): SortedResultSetInfo {
|
||||
return {
|
||||
resultsPath: this.convertPathToWebviewUri(info.resultsPath),
|
||||
resultsPath: this.convertPathToWebviewUri(panel, info.resultsPath),
|
||||
sortState: info.sortState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -477,6 +477,10 @@ export interface OpenQueryTextMessage {
|
||||
t: 'openQueryText';
|
||||
}
|
||||
|
||||
export interface OpenLogsMessage {
|
||||
t: 'openLogs';
|
||||
}
|
||||
|
||||
export type ToVariantAnalysisMessage =
|
||||
| SetVariantAnalysisMessage
|
||||
| SetRepoResultsMessage
|
||||
@@ -487,4 +491,5 @@ export type FromVariantAnalysisMessage =
|
||||
| StopVariantAnalysisMessage
|
||||
| RequestRepositoryResultsMessage
|
||||
| OpenQueryFileMessage
|
||||
| OpenQueryTextMessage;
|
||||
| OpenQueryTextMessage
|
||||
| OpenLogsMessage;
|
||||
|
||||
@@ -3,7 +3,10 @@ import { VariantAnalysisHistoryItem } from './remote-queries/variant-analysis-hi
|
||||
import { LocalQueryInfo } from './query-results';
|
||||
import { assertNever } from './pure/helpers-pure';
|
||||
import { pluralize } from './pure/word';
|
||||
import { hasRepoScanCompleted } from './remote-queries/shared/variant-analysis';
|
||||
import {
|
||||
hasRepoScanCompleted,
|
||||
getActionsWorkflowRunUrl as getVariantAnalysisActionsWorkflowRunUrl
|
||||
} from './remote-queries/shared/variant-analysis';
|
||||
|
||||
export type QueryHistoryInfo = LocalQueryInfo | RemoteQueryHistoryItem | VariantAnalysisHistoryItem;
|
||||
|
||||
@@ -71,3 +74,14 @@ export function buildRepoLabel(item: RemoteQueryHistoryItem | VariantAnalysisHis
|
||||
assertNever(item);
|
||||
}
|
||||
}
|
||||
|
||||
export function getActionsWorkflowRunUrl(item: RemoteQueryHistoryItem | VariantAnalysisHistoryItem): string {
|
||||
if (item.t === 'remote') {
|
||||
const { actionsWorkflowRunId: workflowRunId, controllerRepository: { owner, name } } = item.remoteQuery;
|
||||
return `https://github.com/${owner}/${name}/actions/runs/${workflowRunId}`;
|
||||
} else if (item.t === 'variant-analysis') {
|
||||
return getVariantAnalysisActionsWorkflowRunUrl(item.variantAnalysis);
|
||||
} else {
|
||||
assertNever(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import { commandRunner } from './commandRunner';
|
||||
import { ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from './pure/time';
|
||||
import { assertNever, getErrorMessage, getErrorStack } from './pure/helpers-pure';
|
||||
import { CompletedLocalQueryInfo, LocalQueryInfo } from './query-results';
|
||||
import { getQueryId, getQueryText, QueryHistoryInfo } from './query-history-info';
|
||||
import { getActionsWorkflowRunUrl, getQueryId, getQueryText, QueryHistoryInfo } from './query-history-info';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { registerQueryHistoryScrubber } from './query-history-scrubber';
|
||||
import { QueryStatus, variantAnalysisStatusToQueryStatus } from './query-status';
|
||||
@@ -1213,17 +1213,17 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
// Remote queries only
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem || finalSingleItem.t !== 'remote') {
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { actionsWorkflowRunId: workflowRunId, controllerRepository: { owner, name } } = finalSingleItem.remoteQuery;
|
||||
if (finalSingleItem.t === 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand(
|
||||
'vscode.open',
|
||||
Uri.parse(`https://github.com/${owner}/${name}/actions/runs/${workflowRunId}`)
|
||||
);
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(finalSingleItem);
|
||||
|
||||
await commands.executeCommand('vscode.open', Uri.parse(actionsWorkflowRunUrl));
|
||||
}
|
||||
|
||||
async handleCopyRepoList(
|
||||
|
||||
@@ -49,7 +49,8 @@ export class RemoteQueriesView extends AbstractWebview<ToRemoteQueriesMessage, F
|
||||
}
|
||||
|
||||
async showResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
|
||||
this.getPanel().reveal(undefined, true);
|
||||
const panel = await this.getPanel();
|
||||
panel.reveal(undefined, true);
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
const model = this.buildViewModel(query, queryResult);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AnalysisAlert, AnalysisRawResults } from './analysis-result';
|
||||
|
||||
export interface VariantAnalysis {
|
||||
id: number,
|
||||
controllerRepoId: number,
|
||||
controllerRepo: Repository;
|
||||
query: {
|
||||
name: string,
|
||||
filePath: string,
|
||||
@@ -203,3 +203,8 @@ export function getSkippedRepoCount(skippedRepos: VariantAnalysisSkippedReposito
|
||||
|
||||
return Object.values(skippedRepos).reduce((acc, group) => acc + group.repositoryCount, 0);
|
||||
}
|
||||
|
||||
export function getActionsWorkflowRunUrl(variantAnalysis: VariantAnalysis): string {
|
||||
const { actionsWorkflowRunId, controllerRepo: { fullName } } = variantAnalysis;
|
||||
return `https://github.com/${fullName}/actions/runs/${actionsWorkflowRunId}`;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export class VariantAnalysisMonitor extends DisposableObject {
|
||||
|
||||
variantAnalysisSummary = await ghApiClient.getVariantAnalysis(
|
||||
credentials,
|
||||
variantAnalysis.controllerRepoId,
|
||||
variantAnalysis.controllerRepo.id,
|
||||
variantAnalysis.id
|
||||
);
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ export function processUpdatedVariantAnalysis(
|
||||
|
||||
const variantAnalysis: VariantAnalysis = {
|
||||
id: response.id,
|
||||
controllerRepoId: response.controller_repo.id,
|
||||
controllerRepo: {
|
||||
id: response.controller_repo.id,
|
||||
fullName: response.controller_repo.full_name,
|
||||
private: response.controller_repo.private,
|
||||
},
|
||||
query: previousVariantAnalysis.query,
|
||||
databases: previousVariantAnalysis.databases,
|
||||
executionStartTime: previousVariantAnalysis.executionStartTime,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { logger } from '../logging';
|
||||
import { FromVariantAnalysisMessage, ToVariantAnalysisMessage } from '../pure/interface-types';
|
||||
import { assertNever } from '../pure/helpers-pure';
|
||||
import {
|
||||
getActionsWorkflowRunUrl,
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
@@ -26,7 +27,8 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
||||
}
|
||||
|
||||
public async openView() {
|
||||
this.getPanel().reveal(undefined, true);
|
||||
const panel = await this.getPanel();
|
||||
panel.reveal(undefined, true);
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
}
|
||||
@@ -40,6 +42,9 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
||||
t: 'setVariantAnalysis',
|
||||
variantAnalysis,
|
||||
});
|
||||
|
||||
const panel = await this.getPanel();
|
||||
panel.title = `${variantAnalysis.query.name} - CodeQL Query Results`;
|
||||
}
|
||||
|
||||
public async updateRepoState(repoState: VariantAnalysisScannedRepositoryState): Promise<void> {
|
||||
@@ -64,10 +69,12 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
||||
});
|
||||
}
|
||||
|
||||
protected getPanelConfig(): WebviewPanelConfig {
|
||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||
const variantAnalysis = await this.manager.getVariantAnalysis(this.variantAnalysisId);
|
||||
|
||||
return {
|
||||
viewId: VariantAnalysisView.viewType,
|
||||
title: `CodeQL Query Results for ${this.variantAnalysisId}`,
|
||||
title: variantAnalysis ? `${variantAnalysis.query.name} - CodeQL Query Results` : `Variant analysis ${this.variantAnalysisId} - CodeQL Query Results`,
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: 'variant-analysis',
|
||||
@@ -96,6 +103,9 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
||||
case 'openQueryText':
|
||||
await this.openQueryText();
|
||||
break;
|
||||
case 'openLogs':
|
||||
await this.openLogs();
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
@@ -159,4 +169,16 @@ export class VariantAnalysisView extends AbstractWebview<ToVariantAnalysisMessag
|
||||
void showAndLogWarningMessage('Could not open variant analysis query text. Failed to open text document.');
|
||||
}
|
||||
}
|
||||
|
||||
private async openLogs(): Promise<void> {
|
||||
const variantAnalysis = await this.manager.getVariantAnalysis(this.variantAnalysisId);
|
||||
if (!variantAnalysis) {
|
||||
void showAndLogWarningMessage('Could not open variant analysis logs. Variant analysis not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis);
|
||||
|
||||
await commands.executeCommand('vscode.open', Uri.parse(actionsWorkflowRunUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,11 @@ const Template: ComponentStory<typeof VariantAnalysisComponent> = (args) => (
|
||||
|
||||
const variantAnalysis: VariantAnalysisDomainModel = {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
actionsWorkflowRunId: 789263,
|
||||
query: {
|
||||
name: 'Example query',
|
||||
|
||||
@@ -37,7 +37,11 @@ export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
variantAnalysis: {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
query: {
|
||||
name: 'Query name',
|
||||
filePath: 'example.ql',
|
||||
|
||||
@@ -68,7 +68,11 @@ const Template: ComponentStory<typeof VariantAnalysisHeader> = (args) => (
|
||||
|
||||
const buildVariantAnalysis = (data: Partial<VariantAnalysis>) => ({
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
query: {
|
||||
name: 'Query name',
|
||||
filePath: 'example.ql',
|
||||
|
||||
@@ -30,7 +30,11 @@ const Template: ComponentStory<typeof VariantAnalysisOutcomePanels> = (args) =>
|
||||
|
||||
const buildVariantAnalysis = (data: Partial<VariantAnalysis>) => ({
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
query: {
|
||||
name: 'Query name',
|
||||
filePath: 'example.ql',
|
||||
|
||||
@@ -30,6 +30,12 @@ const openQueryText = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const openLogs = () => {
|
||||
vscode.postMessage({
|
||||
t: 'openLogs',
|
||||
});
|
||||
};
|
||||
|
||||
export function VariantAnalysis({
|
||||
variantAnalysis: initialVariantAnalysis,
|
||||
repoStates: initialRepoStates = [],
|
||||
@@ -85,7 +91,7 @@ export function VariantAnalysis({
|
||||
onStopQueryClick={() => console.log('Stop query')}
|
||||
onCopyRepositoryListClick={() => console.log('Copy repository list')}
|
||||
onExportResultsClick={() => console.log('Export results')}
|
||||
onViewLogsClick={() => console.log('View logs')}
|
||||
onViewLogsClick={openLogs}
|
||||
/>
|
||||
<VariantAnalysisOutcomePanels
|
||||
variantAnalysis={variantAnalysis}
|
||||
|
||||
@@ -11,7 +11,11 @@ import { VariantAnalysisAnalyzedRepos, VariantAnalysisAnalyzedReposProps } from
|
||||
describe(VariantAnalysisAnalyzedRepos.name, () => {
|
||||
const defaultVariantAnalysis = {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
actionsWorkflowRunId: 789263,
|
||||
query: {
|
||||
name: 'Example query',
|
||||
|
||||
@@ -10,7 +10,11 @@ import { VariantAnalysisOutcomePanelProps, VariantAnalysisOutcomePanels } from '
|
||||
describe(VariantAnalysisOutcomePanels.name, () => {
|
||||
const defaultVariantAnalysis = {
|
||||
id: 1,
|
||||
controllerRepoId: 1,
|
||||
controllerRepo: {
|
||||
id: 1,
|
||||
fullName: 'octodemo/variant-analysis-controller',
|
||||
private: false,
|
||||
},
|
||||
actionsWorkflowRunId: 789263,
|
||||
query: {
|
||||
name: 'Example query',
|
||||
|
||||
@@ -26,7 +26,11 @@ describe('Variant Analysis processor', function() {
|
||||
|
||||
expect(result).to.eql({
|
||||
'id': mockApiResponse.id,
|
||||
'controllerRepoId': mockApiResponse.controller_repo.id,
|
||||
'controllerRepo': {
|
||||
'id': mockApiResponse.controller_repo.id,
|
||||
'fullName': mockApiResponse.controller_repo.full_name,
|
||||
'private': mockApiResponse.controller_repo.private
|
||||
},
|
||||
'query': {
|
||||
'filePath': 'query-file-path',
|
||||
'language': VariantAnalysisQueryLanguage.Javascript,
|
||||
|
||||
@@ -16,7 +16,13 @@ export function createMockVariantAnalysis(
|
||||
): VariantAnalysis {
|
||||
const variantAnalysis: VariantAnalysis = {
|
||||
id: faker.datatype.number(),
|
||||
controllerRepoId: faker.datatype.number(),
|
||||
controllerRepo: {
|
||||
id: faker.datatype.number(),
|
||||
fullName: 'github/' + faker.datatype.hexadecimal({
|
||||
prefix: '',
|
||||
}),
|
||||
private: faker.datatype.boolean(),
|
||||
},
|
||||
query: {
|
||||
name: 'a-query-name',
|
||||
filePath: 'a-query-file-path',
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { QueryStatus } from '../../src/query-status';
|
||||
import { buildRepoLabel, getQueryId, getQueryText, getRawQueryName } from '../../src/query-history-info';
|
||||
import {
|
||||
buildRepoLabel,
|
||||
getActionsWorkflowRunUrl,
|
||||
getQueryId,
|
||||
getQueryText,
|
||||
getRawQueryName
|
||||
} from '../../src/query-history-info';
|
||||
import { VariantAnalysisHistoryItem } from '../../src/remote-queries/variant-analysis-history-item';
|
||||
import { createMockVariantAnalysis } from '../../src/vscode-tests/factories/remote-queries/shared/variant-analysis';
|
||||
import { createMockScannedRepos } from '../../src/vscode-tests/factories/remote-queries/shared/scanned-repositories';
|
||||
@@ -144,4 +150,26 @@ describe('Query history info', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActionsWorkflowRunUrl', () => {
|
||||
it('should get the run url for remote query history items', () => {
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(remoteQueryHistoryItem);
|
||||
|
||||
const remoteQuery = remoteQueryHistoryItem.remoteQuery;
|
||||
const fullName = `${remoteQuery.controllerRepository.owner}/${remoteQuery.controllerRepository.name}`;
|
||||
expect(actionsWorkflowRunUrl).to.equal(
|
||||
`https://github.com/${fullName}/actions/runs/${remoteQuery.actionsWorkflowRunId}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should get the run url for variant analysis history items', () => {
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysisHistoryItem);
|
||||
|
||||
const variantAnalysis = variantAnalysisHistoryItem.variantAnalysis;
|
||||
const fullName = variantAnalysis.controllerRepo.fullName;
|
||||
expect(actionsWorkflowRunUrl).to.equal(
|
||||
`https://github.com/${fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import { VariantAnalysis, parseVariantAnalysisQueryLanguage, VariantAnalysisQueryLanguage, VariantAnalysisStatus, isVariantAnalysisComplete, VariantAnalysisRepoStatus } from '../../src/remote-queries/shared/variant-analysis';
|
||||
import { VariantAnalysis, parseVariantAnalysisQueryLanguage, VariantAnalysisQueryLanguage, VariantAnalysisStatus, isVariantAnalysisComplete, VariantAnalysisRepoStatus, getActionsWorkflowRunUrl } from '../../src/remote-queries/shared/variant-analysis';
|
||||
import { createMockScannedRepo } from '../../src/vscode-tests/factories/remote-queries/shared/scanned-repositories';
|
||||
import { createMockVariantAnalysis } from '../../src/vscode-tests/factories/remote-queries/shared/variant-analysis';
|
||||
|
||||
@@ -101,3 +101,13 @@ describe('isVariantAnalysisComplete', async () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('getActionsWorkflowRunUrl', () => {
|
||||
it('should get the run url', () => {
|
||||
const variantAnalysis = createMockVariantAnalysis();
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis);
|
||||
|
||||
expect(actionsWorkflowRunUrl).to.equal(`https://github.com/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user