Skip to content

Commit

Permalink
feat: display branch selection in the tree node [IDE-446] (#489)
Browse files Browse the repository at this point in the history
* feat: add ui elements to open and select base branch IDE-446

* fix: updated ui elements with a pencil to indicate that branch is editable

* fix: display base branch only for Code in issue tree

* feat: render Delta Security Code based on feature flag

* feat: render Delta Code Quality based on feature flag

* fix: integration test

* fix: add gear and scan icons to delta tree views

* refactor: remove base branch placeholder command

* fix: add dark and light icons for delta

* refactor: consistent message across products when no issues are found

* chore: add CHANGELOG

---------

Co-authored-by: Catalina Oyaneder <[email protected]>
  • Loading branch information
acke and Catalina Oyaneder authored Jul 17, 2024
1 parent bb19865 commit f10f698
Show file tree
Hide file tree
Showing 22 changed files with 120 additions and 22 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Snyk Security Changelog

## [2.14.0]
- Add UI components for selecting a base branch for delta findings for Code and Code Quality behind a feature flag.

### [2.13.1]
- Refactor the Suggestion Panel for OSS so it's more secure and will be supported in other IDEs

Expand Down
3 changes: 3 additions & 0 deletions media/images/branch-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions media/images/branch-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions media/images/pencil-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions media/images/pencil-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 14 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@
{
"id": "snyk.views.analysis.code.security",
"name": "Code Security",
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error"
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error && !snyk:deltaFindingsEnabled"
},
{
"id": "snyk.views.analysis.code.security.delta",
"name": "Code Security - New Issues",
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error && snyk:deltaFindingsEnabled"
},
{
"id": "snyk.views.analysis.configuration",
Expand All @@ -262,7 +267,12 @@
{
"id": "snyk.views.analysis.code.quality",
"name": "Code Quality",
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error"
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error && !snyk:deltaFindingsEnabled"
},
{
"id": "snyk.views.analysis.code.quality.delta",
"name": "Code Quality - New Issues",
"when": "snyk:initialized && snyk:loggedIn && snyk:codeEnabled && snyk:workspaceFound && !snyk:error && snyk:deltaFindingsEnabled"
},
{
"id": "snyk.views.analysis.code.enablement",
Expand Down Expand Up @@ -310,12 +320,12 @@
"view/title": [
{
"command": "snyk.start",
"when": "view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.analysis.configuration'",
"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'",
"group": "navigation"
},
{
"command": "snyk.settings",
"when": "view == 'snyk.views.analysis.code.security' || view == 'snyk.views.analysis.code.quality' || view == 'snyk.views.analysis.oss' || view == 'snyk.views.welcome' || view == 'snyk.views.analysis.configuration'",
"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'",
"group": "navigation"
}
],
Expand Down
2 changes: 2 additions & 0 deletions src/snyk/common/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface SeverityFilter {

export type PreviewFeatures = {
advisor: boolean | undefined;
deltaFindings: boolean | undefined;
};

export interface IConfiguration {
Expand Down Expand Up @@ -423,6 +424,7 @@ export class Configuration implements IConfiguration {
getPreviewFeatures(): PreviewFeatures {
const defaultSetting: PreviewFeatures = {
advisor: false,
deltaFindings: false,
};

const userSetting =
Expand Down
3 changes: 3 additions & 0 deletions src/snyk/common/constants/views.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const SNYK_VIEW_WELCOME = 'snyk.views.welcome';
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_SECURITY_WITH_DELTA = 'snyk.views.analysis.code.security.delta';
export const SNYK_VIEW_ANALYSIS_CODE_QUALITY = 'snyk.views.analysis.code.quality';
export const SNYK_VIEW_ANALYSIS_CODE_QUALITY_WITH_DELTA = 'snyk.views.analysis.code.quality.delta';
export const SNYK_VIEW_ANALYSIS_OSS = 'snyk.views.analysis.oss';
export const SNYK_VIEW_SUPPORT = 'snyk.views.support';
export const SNYK_VIEW_SUGGESTION_CODE = 'snyk.views.suggestion.code';
Expand All @@ -21,6 +23,7 @@ export const SNYK_CONTEXT = {
ERROR: 'error',
MODE: 'mode',
ADVANCED: 'advanced',
DELTA_FINDINGS_ENABLED: 'deltaFindingsEnabled',
};

export const SNYK_ERROR_CODES = {
Expand Down
8 changes: 7 additions & 1 deletion src/snyk/common/services/productService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IConfiguration } from '../configuration/configuration';
import { IWorkspaceTrust } from '../configuration/trustedFolders';
import { CodeActionsProvider } from '../editor/codeActionsProvider';
import { ILanguageServer } from '../languageServer/languageServer';
import { Issue, Scan, ScanStatus } from '../languageServer/types';
import { Issue, Scan, ScanProduct, ScanStatus } from '../languageServer/types';
import { ILog } from '../logger/interfaces';
import { IViewManagerService } from '../services/viewManagerService';
import { IProductWebviewProvider } from '../views/webviewProvider';
Expand All @@ -29,6 +29,8 @@ export interface IProductService<T> extends AnalysisStatusProvider, Disposable {
}

export abstract class ProductService<T> extends AnalysisStatusProvider implements IProductService<T> {
abstract readonly productType: ScanProduct;

private _result: ProductResult<T>;
readonly newResultAvailable$ = new Subject<void>();

Expand Down Expand Up @@ -59,6 +61,10 @@ export abstract class ProductService<T> extends AnalysisStatusProvider implement

abstract refreshTreeView(): void;

public getSnykProductType(): ScanProduct {
return this.productType;
}

registerCodeActionsProvider(provider: CodeActionsProvider<T>) {
this.languages.registerCodeActionsProvider({ scheme: 'file', language: '*' }, provider);
}
Expand Down
28 changes: 25 additions & 3 deletions src/snyk/common/views/issueTreeProvider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import _, { flatten } from 'lodash';
import * as vscode from 'vscode'; // todo: invert dependency
import { IConfiguration, IssueViewOptions } from '../../common/configuration/configuration';
import { Issue, IssueSeverity } from '../../common/languageServer/types';
import { Issue, IssueSeverity, ScanProduct } from '../../common/languageServer/types';
import { messages as commonMessages } from '../../common/messages/analysisMessages';
import { IContextService } from '../../common/services/contextService';
import { IProductService } from '../../common/services/productService';
import { IProductService, ProductService } from '../../common/services/productService';
import { AnalysisTreeNodeProvider } from '../../common/views/analysisTreeNodeProvider';
import { INodeIcon, InternalType, NODE_ICONS, TreeNode } from '../../common/views/treeNode';
import { IVSCodeLanguages } from '../../common/vscode/languages';
Expand Down Expand Up @@ -94,6 +94,12 @@ export abstract class ProductIssueTreeProvider<T> extends AnalysisTreeNodeProvid
}),
this.getFixableIssuesNode(this.getFixableCount()),
];

const isSnykCodeProduct = (this.productService as ProductService<T>).getSnykProductType() === ScanProduct.Code;
if (isSnykCodeProduct) {
topNodes.unshift(this.getBaseBranch());
}

const noSeverityFiltersSelectedWarning = this.getNoSeverityFiltersSelectedTreeNode();
if (noSeverityFiltersSelectedWarning !== null) {
topNodes.push(noSeverityFiltersSelectedWarning);
Expand All @@ -109,6 +115,19 @@ export abstract class ProductIssueTreeProvider<T> extends AnalysisTreeNodeProvid
return nodes;
}

getBaseBranch(): TreeNode | null {
const deltaFindingsEnabled = this.configuration.getDeltaFindingsEnabled();

//TODO: get the actual base branch from Snyk Language Server
if (deltaFindingsEnabled) {
return new TreeNode({
text: 'Base branch: main',
icon: NODE_ICONS.branch,
});
}
return null;
}

getFixableIssuesNode(_fixableIssueCount: number): TreeNode | null {
return null; // optionally overridden by products
}
Expand Down Expand Up @@ -275,7 +294,10 @@ export abstract class ProductIssueTreeProvider<T> extends AnalysisTreeNodeProvid
}

protected getIssueFoundText(nIssues: number, _: number): string {
return `Snyk found ${!nIssues ? 'no issues! ✅' : `${nIssues} issue${nIssues === 1 ? '' : 's'}`}`;
if (!nIssues) {
return '✅ Congrats! No issues found!';
}
return `Snyk found ${nIssues} issue${nIssues === 1 ? '' : 's'}`;
}

protected getIssueDescriptionText(dir: string | undefined, issueCount: number): string | undefined {
Expand Down
10 changes: 9 additions & 1 deletion src/snyk/common/views/treeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface INodeIcon {
['dark']: string;
}

type NODE_ICON_TYPE = 'critical' | 'high' | 'medium' | 'low' | 'error';
type NODE_ICON_TYPE = 'critical' | 'high' | 'medium' | 'low' | 'error' | 'branch' | 'pencil';

export const NODE_ICONS: { [key in NODE_ICON_TYPE]: INodeIcon } = {
critical: {
Expand All @@ -30,6 +30,14 @@ export const NODE_ICONS: { [key in NODE_ICON_TYPE]: INodeIcon } = {
light: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'warning.svg'),
dark: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'warning.svg'),
},
branch: {
light: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'branch-light.svg'),
dark: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'branch-dark.svg'),
},
pencil: {
light: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'pencil-light.svg'),
dark: path.join(__filename, '..', '..', '..', '..', '..', 'media', 'images', 'pencil-dark.svg'),
},
};

export type InternalType = {
Expand Down
23 changes: 19 additions & 4 deletions src/snyk/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import {
SNYK_CONTEXT,
SNYK_VIEW_ANALYSIS_CODE_ENABLEMENT,
SNYK_VIEW_ANALYSIS_CODE_QUALITY,
SNYK_VIEW_ANALYSIS_CODE_QUALITY_WITH_DELTA,
SNYK_VIEW_ANALYSIS_CODE_SECURITY,
SNYK_VIEW_ANALYSIS_CODE_SECURITY_WITH_DELTA,
SNYK_VIEW_ANALYSIS_IAC,
SNYK_VIEW_ANALYSIS_OSS,
SNYK_VIEW_SUPPORT,
Expand Down Expand Up @@ -124,6 +126,11 @@ class SnykExtension extends SnykLib implements IExtension {

this.statusBarItem.show();

const previewFeatures = configuration.getPreviewFeatures();
if (previewFeatures.deltaFindings) {
await this.contextService.setContext(SNYK_CONTEXT.DELTA_FINDINGS_ENABLED, true);
}

const languageClientAdapter = new LanguageClientAdapter();
this.authService = new AuthenticationService(
this.contextService,
Expand Down Expand Up @@ -260,16 +267,24 @@ class SnykExtension extends SnykLib implements IExtension {
vsCodeLanguages,
);

const codeSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_CODE_SECURITY, {
let securityCodeView = SNYK_VIEW_ANALYSIS_CODE_SECURITY;
let codeQualityView = SNYK_VIEW_ANALYSIS_CODE_QUALITY;
if (previewFeatures.deltaFindings) {
securityCodeView = SNYK_VIEW_ANALYSIS_CODE_SECURITY_WITH_DELTA;
codeQualityView = SNYK_VIEW_ANALYSIS_CODE_QUALITY_WITH_DELTA;
}

const codeSecurityTree = vscode.window.createTreeView(securityCodeView, {
treeDataProvider: codeSecurityIssueProvider,
});
const codeQualityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_CODE_QUALITY, {

const codeQualityTree = vscode.window.createTreeView(codeQualityView, {
treeDataProvider: codeQualityIssueProvider,
});

vscodeContext.subscriptions.push(
vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_CODE_SECURITY, codeSecurityIssueProvider),
vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_CODE_QUALITY, codeQualityIssueProvider),
vscode.window.registerTreeDataProvider(securityCodeView, codeSecurityIssueProvider),
vscode.window.registerTreeDataProvider(codeQualityView, codeQualityIssueProvider),
codeSecurityTree,
codeQualityTree,
);
Expand Down
2 changes: 2 additions & 0 deletions src/snyk/snykCode/codeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { SnykCodeActionsProvider } from './codeActions/codeIssuesActionsProvider
import { ICodeSuggestionWebviewProvider } from './views/interfaces';

export class SnykCodeService extends ProductService<CodeIssueData> {
public readonly productType = ScanProduct.Code;

constructor(
extensionContext: ExtensionContext,
config: IConfiguration,
Expand Down
Loading

0 comments on commit f10f698

Please sign in to comment.