Skip to content

Commit

Permalink
feat: display up to 60 chars of the filepath in tree view (#451)
Browse files Browse the repository at this point in the history
* feat: display up to 70 chars of file path in tree view for file nodes

* docs: update CHANGELOG.md

* chore: update linter rules and fix some linter warnings

* chore: update linter rules

* fix: outofmemory and formatting in SnykToolWindowPanelIntegTest

* fix: SnykToolWindowPanelIntegTest

* fix: OssServiceTest

* fix: detekt rule file
  • Loading branch information
bastiandoetsch authored Oct 9, 2023
1 parent 7d2b272 commit bf46cf7
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 153 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120

[*.md]
trim_trailing_whitespace = false

[*.{kt,kts}]
indent_size = 4
wildcard_import_limit = 999
ij_kotlin_packages_to_use_import_on_demand = ^
ij_kotlin_name_count_to_use_star_import = 999
ij_kotlin_name_count_to_use_star_import_for_members = 999
Expand Down
6 changes: 6 additions & 0 deletions .github/detekt/detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
formatting:
Indentation:
continuationIndentSize: 8
MaxLineLength:
active: false
ParameterListWrapping:
active: false
NoWildcardImports:
active: false

complexity:
# don't count private & deprecated
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- support for arm64 CLI on macOS
- support to configure base URL for CLI downloads
- display up to 70 characters of the filepath in the tree view

## [2.5.3]

Expand Down
104 changes: 74 additions & 30 deletions src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.snyk.plugin.ui.toolwindow

import ai.deepcode.javaclient.core.SuggestionForFile
import com.intellij.icons.AllIcons
import com.intellij.ide.util.gotoByName.GotoFileCellRenderer
import com.intellij.openapi.util.Iconable
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes
Expand All @@ -26,6 +27,7 @@ import io.snyk.plugin.ui.toolwindow.nodes.root.RootSecurityIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.ErrorTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.FileTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.SnykCodeFileTreeNode
import org.jetbrains.kotlin.idea.base.util.letIf
import snyk.common.ProductType
import snyk.common.SnykError
import snyk.container.ContainerIssue
Expand All @@ -38,21 +40,17 @@ import snyk.iac.ui.toolwindow.IacFileTreeNode
import snyk.iac.ui.toolwindow.IacIssueTreeNode
import snyk.oss.OssVulnerabilitiesForFile
import snyk.oss.Vulnerability
import java.util.Locale
import java.util.*
import javax.swing.Icon
import javax.swing.JTree

private const val MAX_FILE_TREE_NODE_LENGTH = 60

class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
@Suppress("UNCHECKED_CAST")
override fun customizeCellRenderer(
tree: JTree,
value: Any,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
hasFocus: Boolean
tree: JTree, value: Any, selected: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean
) {

var nodeIcon: Icon? = null
var text: String? = null
var attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES
Expand All @@ -68,18 +66,32 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is FileTreeNode -> {
val fileVulns = value.userObject as OssVulnerabilitiesForFile
nodeIcon = PackageManagerIconProvider.getIcon(fileVulns.packageManager.lowercase(Locale.getDefault()))
text = fileVulns.sanitizedTargetFile + ProductType.OSS.getCountText(value.childCount)
val relativePath = fileVulns.virtualFile?.let {
GotoFileCellRenderer.getRelativePath(
fileVulns.virtualFile, value.project
)
} ?: ""
toolTipText =
relativePath + fileVulns.sanitizedTargetFile + ProductType.OSS.getCountText(value.childCount)

text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) {
"..." + it.substring(
it.length - MAX_FILE_TREE_NODE_LENGTH, it.length
)
}

val snykCachedResults = getSnykCachedResults(value.project)
if (snykCachedResults?.currentOssResults == null) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
text += obsoleteSuffix
text += OBSOLETE_SUFFIX
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is SuggestionTreeNode -> {
val (suggestion, index) = value.userObject as Pair<SuggestionForFile, Int>
nodeIcon = SnykIcons.getSeverityIcon(suggestion.getSeverityAsEnum())
Expand All @@ -94,31 +106,55 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is SnykCodeFileTreeNode -> {
val (file, productType) = value.userObject as Pair<SnykCodeFile, ProductType>
text = PDU.toSnykCodeFile(file).virtualFile.name + productType.getCountText(value.childCount)
toolTipText =
GotoFileCellRenderer.getRelativePath(file.virtualFile, file.project) + productType.getCountText(
value.childCount
)

text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) {
"..." + it.substring(
it.length - MAX_FILE_TREE_NODE_LENGTH, it.length
)
}

val psiFile = PDU.toPsiFile(file)
nodeIcon = psiFile?.getIcon(Iconable.ICON_FLAG_READ_STATUS)
if (!AnalysisData.instance.isFileInCache(file)) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
text += obsoleteSuffix
text += OBSOLETE_SUFFIX
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is IacFileTreeNode -> {
val iacVulnerabilitiesForFile = value.userObject as IacIssuesForFile
nodeIcon = PackageManagerIconProvider.getIcon(
iacVulnerabilitiesForFile.packageManager.lowercase(Locale.getDefault())
)
text = iacVulnerabilitiesForFile.targetFile + ProductType.IAC.getCountText(value.childCount)
val relativePath = iacVulnerabilitiesForFile.virtualFile?.let {
GotoFileCellRenderer.getRelativePath(
iacVulnerabilitiesForFile.virtualFile, value.project
)
} ?: iacVulnerabilitiesForFile.targetFilePath
toolTipText = relativePath + ProductType.IAC.getCountText(value.childCount)

text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) {
"..." + it.substring(
it.length - MAX_FILE_TREE_NODE_LENGTH, it.length
)
}

val snykCachedResults = getSnykCachedResults(value.project)
if (snykCachedResults?.currentIacResult == null || iacVulnerabilitiesForFile.obsolete) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
nodeIcon = getDisabledIcon(nodeIcon)
text += obsoleteSuffix
text += OBSOLETE_SUFFIX
}
}

is ContainerImageTreeNode -> {
val issuesForImage = value.userObject as ContainerIssuesForImage
nodeIcon = SnykIcons.CONTAINER_IMAGE
Expand All @@ -128,43 +164,47 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
if (snykCachedResults?.currentContainerResult == null || issuesForImage.obsolete) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
nodeIcon = getDisabledIcon(nodeIcon)
text += obsoleteSuffix
text += OBSOLETE_SUFFIX
}
}

is ErrorTreeNode -> {
val snykError = value.userObject as SnykError
text = snykError.path + " - " + snykError.message
nodeIcon = AllIcons.General.Error
}

is IacIssueTreeNode -> {
val issue = (value.userObject as IacIssue)
val snykCachedResults = getSnykCachedResults(value.project)
nodeIcon = SnykIcons.getSeverityIcon(issue.getSeverity())
val prefix = if (issue.lineNumber > 0) "line ${issue.lineNumber}: " else ""
text = prefix + issue.title + when {
issue.ignored -> ignoredSuffix
snykCachedResults?.currentIacResult == null || issue.obsolete -> obsoleteSuffix
issue.ignored -> IGNORED_SUFFIX
snykCachedResults?.currentIacResult == null || issue.obsolete -> OBSOLETE_SUFFIX
else -> ""
}
if (snykCachedResults?.currentIacResult == null || issue.ignored || issue.obsolete) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is ContainerIssueTreeNode -> {
val issue = value.userObject as ContainerIssue
val snykCachedResults = getSnykCachedResults(value.project)
nodeIcon = SnykIcons.getSeverityIcon(issue.getSeverity())
text = issue.title + when {
issue.ignored -> ignoredSuffix
snykCachedResults?.currentContainerResult == null || issue.obsolete -> obsoleteSuffix
issue.ignored -> IGNORED_SUFFIX
snykCachedResults?.currentContainerResult == null || issue.obsolete -> OBSOLETE_SUFFIX
else -> ""
}
if (snykCachedResults?.currentContainerResult == null || issue.ignored || issue.obsolete) {
attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES
nodeIcon = getDisabledIcon(nodeIcon)
}
}

is RootOssTreeNode -> {
val settings = pluginSettings()
if (settings.ossScanEnable && settings.treeFiltering.ossResults) {
Expand All @@ -177,9 +217,10 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
text = if (settings.ossScanEnable) {
value.userObject.toString()
} else {
SnykToolWindowPanel.OSS_ROOT_TEXT + disabledSuffix
SnykToolWindowPanel.OSS_ROOT_TEXT + DISABLED_SUFFIX
}
}

is RootSecurityIssuesTreeNode -> {
val settings = pluginSettings()
if (settings.snykCodeSecurityIssuesScanEnable && settings.treeFiltering.codeSecurityResults) {
Expand All @@ -192,10 +233,11 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
text = if (settings.snykCodeSecurityIssuesScanEnable) {
value.userObject.toString()
} else {
SnykToolWindowPanel.CODE_SECURITY_ROOT_TEXT +
snykCodeAvailabilityPostfix().ifEmpty { disabledSuffix }
SnykToolWindowPanel.CODE_SECURITY_ROOT_TEXT + snykCodeAvailabilityPostfix()
.ifEmpty { DISABLED_SUFFIX }
}
}

is RootQualityIssuesTreeNode -> {
val settings = pluginSettings()
if (settings.snykCodeQualityIssuesScanEnable && settings.treeFiltering.codeQualityResults) {
Expand All @@ -208,10 +250,11 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
text = if (settings.snykCodeQualityIssuesScanEnable) {
value.userObject.toString()
} else {
SnykToolWindowPanel.CODE_QUALITY_ROOT_TEXT +
snykCodeAvailabilityPostfix().ifEmpty { disabledSuffix }
SnykToolWindowPanel.CODE_QUALITY_ROOT_TEXT + snykCodeAvailabilityPostfix()
.ifEmpty { DISABLED_SUFFIX }
}
}

is RootIacIssuesTreeNode -> {
val settings = pluginSettings()
if (settings.iacScanEnabled && settings.treeFiltering.iacResults) {
Expand All @@ -224,9 +267,10 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
text = if (settings.iacScanEnabled) {
value.userObject.toString()
} else {
SnykToolWindowPanel.IAC_ROOT_TEXT + disabledSuffix
SnykToolWindowPanel.IAC_ROOT_TEXT + DISABLED_SUFFIX
}
}

is RootContainerIssuesTreeNode -> {
val settings = pluginSettings()
if (settings.containerScanEnabled && settings.treeFiltering.containerResults) {
Expand All @@ -239,7 +283,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
text = if (settings.containerScanEnabled) {
value.userObject.toString()
} else {
SnykToolWindowPanel.CONTAINER_ROOT_TEXT + disabledSuffix
SnykToolWindowPanel.CONTAINER_ROOT_TEXT + DISABLED_SUFFIX
}
}
}
Expand All @@ -249,8 +293,8 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() {
}

companion object {
private const val obsoleteSuffix = " (obsolete)"
private const val ignoredSuffix = " (ignored)"
private const val disabledSuffix = " (disabled in Settings)"
private const val OBSOLETE_SUFFIX = " (obsolete)"
private const val IGNORED_SUFFIX = " (ignored)"
private const val DISABLED_SUFFIX = " (disabled in Settings)"
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/snyk/iac/IacIssuesForFile.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package snyk.iac

import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile

data class IacIssuesForFile(
val infrastructureAsCodeIssues: List<IacIssue>,
val targetFile: String,
Expand All @@ -9,6 +12,8 @@ data class IacIssuesForFile(
val obsolete: Boolean get() = infrastructureAsCodeIssues.any { it.obsolete }
val ignored: Boolean get() = infrastructureAsCodeIssues.all { it.ignored }
val uniqueCount: Int get() = infrastructureAsCodeIssues.groupBy { it.id }.size

val virtualFile: VirtualFile? = LocalFileSystem.getInstance().findFileByPath(this.targetFilePath)
}

/* Real json Example: src/integTest/resources/iac-test-results/infrastructure-as-code-goof.json */
5 changes: 5 additions & 0 deletions src/main/kotlin/snyk/oss/OssVulnerabilitiesForFile.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package snyk.oss

import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile

data class OssVulnerabilitiesForFile(
val vulnerabilities: List<Vulnerability>,
private val displayTargetFile: String,
Expand All @@ -11,6 +14,8 @@ data class OssVulnerabilitiesForFile(

val sanitizedTargetFile: String get() = displayTargetFile.replace("-lock", "")

val virtualFile: VirtualFile? = LocalFileSystem.getInstance().findFileByPath(this.path)

fun toGroupedResult(): OssGroupedResult {
val id2vulnerabilities = vulnerabilities.groupBy({ it.id }, { it })
val uniqueCount = id2vulnerabilities.keys.size
Expand Down
Loading

0 comments on commit bf46cf7

Please sign in to comment.