Skip to content
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

feat: added summary panel #573

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@
"name": "Snyk",
"when": "!snyk:loggedIn || snyk:error || !snyk:workspaceFound || snyk:authenticationChanged"
},
{
"type": "webview",
"id": "snyk.views.summary",
"name": "SUMMARY",
"when": "snyk:initialized && snyk:loggedIn && snyk:workspaceFound && !snyk:error",
"content": "${scanSummaryHtml}"
},
{
"id": "snyk.views.analysis.oss",
"name": "Open Source Security",
Expand Down Expand Up @@ -417,12 +424,12 @@
"view/title": [
{
"command": "snyk.start",
"when": "view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.security.delta' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.code.quality.delta' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.analysis.configuration'",
"when": "view == 'snyk.views.summary' || view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.security.delta' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.code.quality.delta' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.analysis.configuration'",
"group": "navigation"
},
{
"command": "snyk.settings",
"when": "view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.security.delta' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.code.quality.delta' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.welcome' || view == 'snyk.views.analysis.configuration'",
"when": "view == 'snyk.views.summary' || view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.security.delta' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.code.quality.delta' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.welcome' || view == 'snyk.views.analysis.configuration'",
"group": "navigation"
}
],
Expand Down
4 changes: 4 additions & 0 deletions src/snyk/common/commands/commandController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export class CommandController {
await this.folderConfigs.setBranch(this.window, this.configuration, folderPath);
}

async toggleDelta(isEnabled: boolean): Promise<void> {
await this.configuration.setDeltaFindingsEnabled(isEnabled);
}

openSettings(): void {
void this.commands.executeCommand(VSCODE_GO_TO_SETTINGS_COMMAND, `@ext:${this.configuration.getExtensionId()}`);
}
Expand Down
15 changes: 15 additions & 0 deletions src/snyk/common/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { IVSCodeWorkspace } from '../vscode/workspace';
import { CliExecutable } from '../../cli/cliExecutable';

const NEWISSUES = 'Net new issues';
const ALLISSUES = 'All issues';

export type FeaturesConfiguration = {
ossEnabled: boolean | undefined;
Expand Down Expand Up @@ -143,6 +144,7 @@ export interface IConfiguration {
setEndpoint(endpoint: string): Promise<void>;

getDeltaFindingsEnabled(): boolean;
setDeltaFindingsEnabled(isEnabled: boolean): Promise<void>;

getOssQuickFixCodeActionsEnabled(): boolean;

Expand Down Expand Up @@ -361,6 +363,19 @@ export class Configuration implements IConfiguration {
);
}

async setDeltaFindingsEnabled(isEnabled: boolean): Promise<void> {
let deltaValue = NEWISSUES;
if (!isEnabled) {
deltaValue = ALLISSUES;
}
await this.workspace.updateConfiguration(
CONFIGURATION_IDENTIFIER,
this.getConfigName(DELTA_FINDINGS),
deltaValue,
true,
);
}

async clearToken(): Promise<void> {
return new Promise<void>((resolve, reject) => {
SecretStorageAdapter.instance
Expand Down
1 change: 1 addition & 0 deletions src/snyk/common/constants/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const SNYK_SHOW_ERROR_FROM_CONTEXT_COMMAND = 'snyk.showErrorFromContext';
export const SNYK_GET_LESSON_COMMAND = 'snyk.getLearnLesson';
export const SNYK_GET_SETTINGS_SAST_ENABLED = 'snyk.getSettingsSastEnabled';
export const SNYK_SET_BASE_BRANCH_COMMAND = 'snyk.setBaseBranch';
export const SNYK_TOGGLE_DELTA = 'snyk.toggleDelta';
export const SNYK_LOGIN_COMMAND = 'snyk.login';
export const SNYK_WORKSPACE_SCAN_COMMAND = 'snyk.workspace.scan';
export const SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND = 'snyk.trustWorkspaceFolders';
Expand Down
1 change: 1 addition & 0 deletions src/snyk/common/constants/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const SNYK_HAS_AUTHENTICATED = '$/snyk.hasAuthenticated';
export const SNYK_ADD_TRUSTED_FOLDERS = '$/snyk.addTrustedFolders';
export const SNYK_SCAN = '$/snyk.scan';
export const SNYK_FOLDERCONFIG = '$/snyk.folderConfigs';
export const SNYK_SCANSUMMARY = '$/snyk.scanSummary';
2 changes: 2 additions & 0 deletions src/snyk/common/constants/views.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// see https://code.visualstudio.com/api/references/contribution-points#contributes.viewsWelcome

export const SNYK_VIEW_WELCOME = 'snyk.views.welcome';
export const SNYK_VIEW_SUMMARY = 'snyk.views.summary';
export const SNYK_VIEW_ANALYSIS_CODE_ENABLEMENT = 'snyk.views.analysis.code.enablement';
export const SNYK_VIEW_ANALYSIS_CODE_SECURITY = 'snyk.views.analysis.code.security';
export const SNYK_VIEW_ANALYSIS_CODE_QUALITY = 'snyk.views.analysis.code.quality';
Expand All @@ -25,6 +26,7 @@ export const SNYK_CONTEXT = {
MODE: 'mode',
ADVANCED: 'advanced',
DELTA_FINDINGS_ENABLED: 'deltaFindingsEnabled',
SCANSUMMARY: 'scanSummaryHtml',
};

export const SNYK_ANALYSIS_STATUS = {
Expand Down
20 changes: 20 additions & 0 deletions src/snyk/common/languageServer/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
SNYK_HAS_AUTHENTICATED,
SNYK_LANGUAGE_SERVER_NAME,
SNYK_SCAN,
SNYK_SCANSUMMARY,
} from '../constants/languageServer';
import { CONFIGURATION_IDENTIFIER } from '../constants/settings';
import { ErrorHandler } from '../error/errorHandler';
Expand All @@ -19,11 +20,12 @@
import { LanguageClient, LanguageClientOptions, ServerOptions } from '../vscode/types';
import { IVSCodeWindow } from '../vscode/window';
import { IVSCodeWorkspace } from '../vscode/workspace';
import { CliExecutable } from '../../cli/cliExecutable';

Check warning on line 23 in src/snyk/common/languageServer/languageServer.ts

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

'CliExecutable' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 23 in src/snyk/common/languageServer/languageServer.ts

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest)

'CliExecutable' is defined but never used. Allowed unused vars must match /^_/u
import { LanguageClientMiddleware } from './middleware';
import { LanguageServerSettings, ServerSettings } from './settings';
import { CodeIssueData, IacIssueData, OssIssueData, Scan } from './types';
import { ExtensionContext } from '../vscode/extensionContext';
import { SummaryWebviewViewProvider } from '../views/summaryWebviewProvider';

export interface ILanguageServer {
start(): Promise<void>;
Expand Down Expand Up @@ -156,6 +158,24 @@
this.logger.info(`${_.capitalize(scan.product)} scan for ${scan.folderPath}: ${scan.status}.`);
this.scan$.next(scan);
});

client.onNotification(SNYK_SCANSUMMARY, ({ scanSummary }: { scanSummary: string }) => {
this.updateSummaryPanel(scanSummary);
});
}

protected updateSummaryPanel(scanSummary: string) {
const SummaryProvider = SummaryWebviewViewProvider.getInstance();

if (!SummaryProvider) {
this.logger.error('Summary Webview Provider was not initialized.');
return;
}
try {
SummaryProvider.updateWebviewContent(scanSummary);
} catch (error) {
this.logger.error('Failed to update Summary panel');
}
}

// Initialization options are not semantically equal to server settings, thus separated here
Expand Down
11 changes: 11 additions & 0 deletions src/snyk/common/languageServer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,14 @@ export type AutofixUnifiedDiffSuggestion = {
fixId: string;
unifiedDiffsPerFile: { [key: string]: string };
};

export type Summary = {
toggleDelta: boolean;
};

export type SummaryMessage = {
type: string;
args: {
summary: Summary;
};
};
2 changes: 1 addition & 1 deletion src/snyk/common/services/downloadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class DownloadService {
}

async isCliInstalled() {
const cliExecutableExists = await CliExecutable.exists(this.extensionContext.extensionPath);
const cliExecutableExists = await CliExecutable.exists(await this.configuration.getCliPath());
const cliChecksumWritten = !!this.getCliChecksum();

return cliExecutableExists && cliChecksumWritten;
Expand Down
70 changes: 70 additions & 0 deletions src/snyk/common/views/summaryWebviewProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as vscode from 'vscode';
import { readFileSync } from 'fs';
import { getNonce } from './nonce';
import { SummaryMessage } from '../languageServer/types';
import { SNYK_TOGGLE_DELTA } from '../constants/commands';
import { Logger } from '../logger/logger';
export class SummaryWebviewViewProvider implements vscode.WebviewViewProvider {
private static instance: SummaryWebviewViewProvider;
private webviewView: vscode.WebviewView | undefined;
private context: vscode.ExtensionContext;

private constructor(context: vscode.ExtensionContext) {
this.context = context;
}

public static getInstance(extensionContext?: vscode.ExtensionContext): SummaryWebviewViewProvider | undefined {
if (!SummaryWebviewViewProvider.instance) {
if (!extensionContext) {
console.log('ExtensionContext is required for the first initialization of SnykDiagnosticsWebviewViewProvider');
return undefined;
} else {
SummaryWebviewViewProvider.instance = new SummaryWebviewViewProvider(extensionContext);
}
}
return SummaryWebviewViewProvider.instance;
}

resolveWebviewView(webviewView: vscode.WebviewView) {
this.webviewView = webviewView;
webviewView.webview.options = {
enableScripts: true,
};
this.webviewView.webview.onDidReceiveMessage((msg: SummaryMessage) => this.handleMessage(msg));
}

private async handleMessage(message: SummaryMessage) {
try {
switch (message.type) {
case 'sendSummaryParams': {
const { summary } = message.args;
await vscode.commands.executeCommand(SNYK_TOGGLE_DELTA, summary.toggleDelta);
break;
}
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Logger.error(error);
}
}

public updateWebviewContent(html: string) {
if (this.webviewView) {
const nonce = getNonce();
const ideScriptPath = vscode.Uri.joinPath(
vscode.Uri.file(this.context.extensionPath),
'out',
'snyk',
'common',
'views',
'summaryWebviewScript.js',
);
const ideScript = readFileSync(ideScriptPath.fsPath, 'utf8');

html = html.replace('${ideStyle}', `<style nonce=${nonce}>` + '' + '</style>');
html = html.replace('${ideScript}', `<script nonce=${nonce}>` + ideScript + '</script>');

this.webviewView.webview.html = html;
}
}
}
46 changes: 46 additions & 0 deletions src/snyk/common/views/summaryWebviewScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */

/* eslint-disable @typescript-eslint/no-unsafe-call */
/// <reference lib="dom" />

// This script will be run within the webview itself
// It cannot access the main VS Code APIs directly.
(function () {
type SummaryMessage = {
type: 'sendSummaryParams';
args: {
summary: Summary;
};
};

type Summary = {
toggleDelta: boolean;
};
const vscode = acquireVsCodeApi();

Check warning on line 22 in src/snyk/common/views/summaryWebviewScript.ts

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest)

Unsafe assignment of an `any` value

Check warning on line 22 in src/snyk/common/views/summaryWebviewScript.ts

View workflow job for this annotation

GitHub Actions / Build and Test (macos-latest)

Unsafe assignment of an `any` value

function sendMessage(message: SummaryMessage) {
vscode.postMessage(message);
}

document.getElementById('totalIssues')?.addEventListener('click', () => {
toggleDelta(false);
});
document.getElementById('newIssues')!.addEventListener('click', () => {
toggleDelta(true);
});

function toggleDelta(toggle: boolean) {
const summary: Summary = {
toggleDelta: toggle,
};

const message: SummaryMessage = {
type: 'sendSummaryParams',
args: { summary },
};
sendMessage(message);
}
})();
2 changes: 1 addition & 1 deletion src/snyk/common/watchers/configurationWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ConfigurationWatcher implements IWatcher {
extension.initDependencyDownload();
return;
} else if (key === FOLDER_CONFIGS || key == DELTA_FINDINGS) {
extension.viewManagerService.refreshAllViews();
return extension.viewManagerService.refreshAllViews();
} else if (key === TRUSTED_FOLDERS) {
extension.workspaceTrust.resetTrustedFoldersCache();
extension.viewManagerService.refreshAllViews();
Expand Down
17 changes: 17 additions & 0 deletions src/snyk/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SNYK_SHOW_LS_OUTPUT_COMMAND,
SNYK_SHOW_OUTPUT_COMMAND,
SNYK_START_COMMAND,
SNYK_TOGGLE_DELTA,
SNYK_WORKSPACE_SCAN_COMMAND,
} from './common/constants/commands';
import {
Expand All @@ -34,6 +35,7 @@ import {
SNYK_VIEW_ANALYSIS_CODE_SECURITY,
SNYK_VIEW_ANALYSIS_IAC,
SNYK_VIEW_ANALYSIS_OSS,
SNYK_VIEW_SUMMARY,
SNYK_VIEW_SUPPORT,
SNYK_VIEW_WELCOME,
} from './common/constants/views';
Expand Down Expand Up @@ -83,9 +85,20 @@ import { GitAPI, GitExtension, Repository } from './common/git';
import { AnalyticsSender } from './common/analytics/AnalyticsSender';
import { MEMENTO_ANALYTICS_PLUGIN_INSTALLED_SENT } from './common/constants/globalState';
import { AnalyticsEvent } from './common/analytics/AnalyticsEvent';
import { SummaryWebviewViewProvider } from './common/views/summaryWebviewProvider';

class SnykExtension extends SnykLib implements IExtension {
public async activate(vscodeContext: vscode.ExtensionContext): Promise<void> {
const summaryWebviewViewProvider = SummaryWebviewViewProvider.getInstance(vscodeContext);
if (!summaryWebviewViewProvider) {
console.log('Summary panel not initialized.');
} else {
vscodeContext.subscriptions.push(
vscode.window.registerWebviewViewProvider(SNYK_VIEW_SUMMARY, summaryWebviewViewProvider),
);
}

SummaryWebviewViewProvider.getInstance(vscodeContext);
extensionContext.setContext(vscodeContext);
this.context = extensionContext;
const snykConfiguration = await this.getSnykConfiguration();
Expand Down Expand Up @@ -498,6 +511,7 @@ class SnykExtension extends SnykLib implements IExtension {
),
vscode.commands.registerCommand(SNYK_START_COMMAND, async () => {
await vscode.commands.executeCommand(SNYK_WORKSPACE_SCAN_COMMAND);
await vscode.commands.executeCommand('setContext', 'scanSummaryHtml', 'scanSummary');
}),
vscode.commands.registerCommand(SNYK_SETTINGS_COMMAND, () => this.commandController.openSettings()),
vscode.commands.registerCommand(SNYK_DCIGNORE_COMMAND, (custom: boolean, path?: string) =>
Expand All @@ -512,6 +526,9 @@ class SnykExtension extends SnykLib implements IExtension {
vscode.commands.registerCommand(SNYK_SET_BASE_BRANCH_COMMAND, (folderPath: string) =>
this.commandController.setBaseBranch(folderPath),
),
vscode.commands.registerCommand(SNYK_TOGGLE_DELTA, (isEnabled: boolean) =>
this.commandController.toggleDelta(isEnabled),
),
vscode.commands.registerCommand(SNYK_SHOW_ERROR_FROM_CONTEXT_COMMAND, () => {
const err = this.contextService.viewContext[SNYK_CONTEXT.ERROR] as Error;
void this.notificationService.showErrorNotification(err.message);
Expand Down
Loading