diff --git a/src/snyk/common/views/analysisTreeNodeProvider.ts b/src/snyk/common/views/analysisTreeNodeProvider.ts index 990602efa..8cea9f592 100644 --- a/src/snyk/common/views/analysisTreeNodeProvider.ts +++ b/src/snyk/common/views/analysisTreeNodeProvider.ts @@ -36,16 +36,6 @@ export abstract class AnalysisTreeNodeProvider extends TreeNodeProvider { return 0; }; - protected getDurationTreeNode(): TreeNode { - const ts = new Date(this.statusProvider.lastAnalysisTimestamp); - const time = ts.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - const day = ts.toLocaleDateString([], { year: '2-digit', month: '2-digit', day: '2-digit' }); - - return new TreeNode({ - text: messages.duration(time, day), - }); - } - protected getNoSeverityFiltersSelectedTreeNode(): TreeNode | null { const anyFilterEnabled = Object.values(this.configuration.severityFilter).find(enabled => !!enabled); if (anyFilterEnabled) { @@ -81,6 +71,4 @@ export abstract class AnalysisTreeNodeProvider extends TreeNodeProvider { }, }); } - - protected abstract getFilteredIssues(issues: readonly unknown[]): readonly unknown[]; } diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 6f59d4931..638d03d68 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import _, { flatten } from 'lodash'; import * as vscode from 'vscode'; // todo: invert dependency import { IConfiguration } from '../../common/configuration/configuration'; import { CodeIssueData, Issue, IssueSeverity } from '../../common/languageServer/types'; @@ -13,9 +13,6 @@ import { Command, Range } from '../../common/vscode/types'; interface ISeverityCounts { [severity: string]: number; } -interface IssueAdditionalData { - hasAIFix?: boolean; -} export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvider { constructor( @@ -86,8 +83,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid ]; } - const [resultNodes, nIssues, AIFixVulnCount] = this.getResultNodes(); - nodes.push(...resultNodes); + nodes.push(...this.getResultNodes()); const folderResults = Array.from(this.productService.result.values()); const allFailed = folderResults.every(folderResult => folderResult instanceof Error); @@ -99,29 +95,40 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid const topNodes: (TreeNode | null)[] = [ new TreeNode({ - text: this.getIssueFoundText(nIssues), + text: this.getIssueFoundText(this.getTotalIssueCount()), }), + this.getFixableIssuesNode(this.getFixableCount()), + this.getNoSeverityFiltersSelectedTreeNode(), ]; - if ('codeService' in this) { - topNodes.push( - new TreeNode({ - text: (this as any).getAIFixableIssuesText(AIFixVulnCount), - }), - ); - } - - // this.getDurationTreeNode(), - topNodes.push(this.getNoSeverityFiltersSelectedTreeNode()); - nodes.unshift(...topNodes.filter((n): n is TreeNode => n !== null)); return nodes; } - getResultNodes(): [TreeNode[], number, number | null] { + getFixableIssuesNode(_fixableIssueCount: number): TreeNode | null { + return null; // optionally overridden by products + } + + getFilteredIssues(): Issue[] { + const folderResults = Array.from(this.productService.result.values()); + const successfulResults = flatten(folderResults.filter((result): result is Issue[] => Array.isArray(result))); + return this.filterIssues(successfulResults); + } + + getTotalIssueCount(): number { + return this.getFilteredIssues().length; + } + + getFixableCount(): number { + return this.getFilteredIssues().filter(issue => this.isFixableIssue(issue)).length; + } + + isFixableIssue(_issue: Issue) { + return false; // optionally overridden by products + } + + getResultNodes(): TreeNode[] { const nodes: TreeNode[] = []; - let totalVulnCount = 0; - let AIFixVulnCoun = 0; for (const result of this.productService.result.entries()) { const folderPath = result[0]; @@ -155,13 +162,8 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid const issueNodes = filteredIssues.map(issue => { fileSeverityCounts[issue.severity] += 1; - totalVulnCount++; folderVulnCount++; - if (this.getAIFix(issue)) { - AIFixVulnCoun++; - } - const issueRange = this.getIssueRange(issue); const params: { text: string; @@ -235,29 +237,17 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid } } - return [nodes, totalVulnCount, AIFixVulnCoun]; + return nodes; } protected getIssueFoundText(nIssues: number): string { return `Snyk found ${!nIssues ? 'no issues! ✅' : `${nIssues} issue${nIssues === 1 ? '' : 's'}`}`; } - protected getAIFixableIssuesText(issuesCount: number | null): string { - return issuesCount && issuesCount > 0 - ? `⚡️ ${issuesCount} ${issuesCount === 1 ? 'vulnerability' : 'vulnerabilities'} can be fixed by Snyk DeepCode AI` - : 'There are no vulnerabilities fixable by Snyk DeepCode AI'; - } - protected getIssueDescriptionText(dir: string | undefined, issueCount: number): string | undefined { return `${dir} - ${issueCount} issue${issueCount === 1 ? '' : 's'}`; } - // todo: Obsolete. Remove after OSS scans migration to LS - protected getFilteredIssues(diagnostics: readonly unknown[]): readonly unknown[] { - // Diagnostics are already filtered by the analyzer - return diagnostics; - } - static getHighestSeverity(counts: ISeverityCounts): IssueSeverity { for (const s of [IssueSeverity.Critical, IssueSeverity.High, IssueSeverity.Medium, IssueSeverity.Low]) { if (counts[s]) return s; diff --git a/src/snyk/snykCode/views/issueTreeProvider.ts b/src/snyk/snykCode/views/issueTreeProvider.ts index 25820c40b..eb862269e 100644 --- a/src/snyk/snykCode/views/issueTreeProvider.ts +++ b/src/snyk/snykCode/views/issueTreeProvider.ts @@ -10,6 +10,7 @@ import { IVSCodeLanguages } from '../../common/vscode/languages'; import { messages } from '../messages/analysis'; import { IssueUtils } from '../utils/issueUtils'; import { CodeIssueCommandArg } from './interfaces'; +import { TreeNode } from '../../common/views/treeNode'; export class IssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -67,4 +68,20 @@ export class IssueTreeProvider extends ProductIssueTreeProvider { ], }; } + + isFixableIssue(issue: Issue): boolean { + return issue.additionalData.hasAIFix; + } + + getFixableIssuesNode(fixableIssueCount: number): TreeNode { + return new TreeNode({ + text: this.getAIFixableIssuesText(fixableIssueCount), + }); + } + + private getAIFixableIssuesText(issuesCount: number): string { + return issuesCount > 0 + ? `⚡️ ${issuesCount} ${issuesCount === 1 ? 'vulnerability' : 'vulnerabilities'} can be fixed by Snyk DeepCode AI` + : 'There are no vulnerabilities fixable by Snyk DeepCode AI'; + } } diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index 19b073808..1aadf6c2d 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -38,9 +38,8 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider) => { fileSeverityCounts[issue.severity] += 1; - totalVulnCount++; folderVulnCount++; return new TreeNode({ @@ -140,7 +138,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider