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: display up to 60 chars of the filepath in tree view #451

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
)
}
Comment on lines +73 to +85
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be interesting for review


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
)
}

Comment on lines +112 to +122
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be interesting for review

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
)
}
Comment on lines +137 to +148
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be interesting for review


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)
Comment on lines +15 to +16
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be interesting for review

}

/* 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)

Comment on lines +17 to +18
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be interesting for review

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