From 3f9880f3a43abd12f1738b091ac9b4be15c09b69 Mon Sep 17 00:00:00 2001 From: Teodora Sandu Date: Mon, 18 Mar 2024 13:56:49 +0000 Subject: [PATCH 1/5] feat: render html from snyk-ls --- CHANGELOG.md | 5 ++ .../ui/SnykBalloonNotificationHelper.kt | 8 ++++ .../panels/IssueDescriptionPanelBase.kt | 46 ++++++++++++++++--- .../panels/SuggestionDescriptionPanel.kt | 6 ++- .../SuggestionDescriptionPanelFromLS.kt | 6 ++- .../panels/VulnerabilityDescriptionPanel.kt | 5 +- src/main/kotlin/snyk/common/lsp/Types.kt | 5 +- .../ui/BaseImageRemediationDetailPanel.kt | 5 +- .../container/ui/ContainerIssueDetailPanel.kt | 6 ++- .../snyk/iac/IacSuggestionDescriptionPanel.kt | 2 +- 10 files changed, 79 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5d76cff..992f0617b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Snyk Security Changelog +## [2.7.14] + +### Added +- Render Snyk Code vulnerabilities using HTML served by the Language Server behind a feature flag. + ## [2.7.13] ### Added - (LS Preview) added timeout to commands executed via code lenses diff --git a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt index 553dbbb26..9243db881 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt @@ -110,4 +110,12 @@ object SnykBalloonNotificationHelper { ) showBalloonForComponent(balloon, component, showAbove) } + fun showErrorBalloonForComponent(message: String, component: Component, showAbove: Boolean = false) { + val balloon = createBalloon( + message, + AllIcons.General.BalloonError, + MessageType.ERROR.popupBackground + ) + showBalloonForComponent(balloon, component, showAbove) + } } diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt index 7927277ab..4ce846ede 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt @@ -2,15 +2,20 @@ package io.snyk.plugin.ui.toolwindow.panels import com.intellij.ui.IdeBorderFactory import com.intellij.ui.SideBorder +import com.intellij.ui.jcef.JBCefApp +import com.intellij.ui.jcef.JBCefBrowserBuilder import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBUI import com.intellij.util.ui.JBUI.Borders import icons.SnykIcons import io.snyk.plugin.Severity +import io.snyk.plugin.pluginSettings import io.snyk.plugin.ui.DescriptionHeaderPanel +import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.addSpacer import io.snyk.plugin.ui.baseGridConstraints import io.snyk.plugin.ui.baseGridConstraintsAnchorWest +import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel import io.snyk.plugin.ui.wrapWithScrollPane import java.awt.BorderLayout import java.awt.Font @@ -23,18 +28,47 @@ private val EMPTY_PANEL = JBUI.Panels.simplePanel() abstract class IssueDescriptionPanelBase( private val title: String, - private val severity: Severity + private val severity: Severity, + private val details: String? ) : JPanel(BorderLayout()), IssueDescriptionPanel { - + private val unexpectedErrorMessage = "Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused."; + /** * **MUST** be invoked in derived class to actually create the UI elements. * Can't be part of constructor due to `state` usage in underling abstract/open methods/props: */ protected fun createUI() { - this.add( - wrapWithScrollPane(descriptionBodyPanel()), - BorderLayout.CENTER - ) + if (pluginSettings().isGlobalIgnoresFeatureEnabled) { + if (!JBCefApp.isSupported()) { + val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) + this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) + SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) + return + } + + val cefClient = JBCefApp.getInstance().createClient() + val jbCefBrowser = JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true).build() + + val panel = JPanel() + panel.add(jbCefBrowser.component, BorderLayout()) + this.add( + wrapWithScrollPane(panel), + BorderLayout.CENTER + ) + + if (this.details == null) { + SnykBalloonNotificationHelper.showError("Something went wrong.", null) + return + } + + jbCefBrowser.loadHTML(this.details, jbCefBrowser.cefBrowser.url) + + } else { + this.add( + wrapWithScrollPane(descriptionBodyPanel()), + BorderLayout.CENTER + ) + } if (isBottomPanelNeeded) { this.add( bottomPanel(), diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt index e6d83aa27..370d180d6 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt @@ -37,7 +37,11 @@ class SuggestionDescriptionPanel( private val snykCodeFile: SnykCodeFile, private val suggestion: SuggestionForFile, private val suggestionIndex: Int -) : IssueDescriptionPanelBase(title = suggestion.title, severity = suggestion.getSeverityAsEnum()) { +) : IssueDescriptionPanelBase( + title = suggestion.title, + severity = suggestion.getSeverityAsEnum(), + details = null +) { val project = snykCodeFile.project private val suggestionRange: MyTextRange? = suggestion.ranges?.getOrNull(suggestionIndex) diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt index 9e68b37e1..ea5b7a727 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt @@ -33,7 +33,11 @@ import kotlin.math.max class SuggestionDescriptionPanelFromLS( snykCodeFile: SnykCodeFile, private val issue: ScanIssue -) : IssueDescriptionPanelBase(title = getIssueTitle(issue), severity = issue.getSeverityAsEnum()) { +) : IssueDescriptionPanelBase( + title = getIssueTitle(issue), + severity = issue.getSeverityAsEnum(), + details = issue.additionalData.details +) { val project = snykCodeFile.project init { diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt index 9ec35659b..8e5cecfcb 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt @@ -20,7 +20,10 @@ import javax.swing.JPanel class VulnerabilityDescriptionPanel( private val groupedVulns: Collection -) : IssueDescriptionPanelBase(title = groupedVulns.first().title, severity = groupedVulns.first().getSeverity()) { +) : IssueDescriptionPanelBase( + title = groupedVulns.first().title, + severity = groupedVulns.first().getSeverity(), + details = null) { private val labelProvider: LabelProvider = LabelProvider() private val vulnerability = groupedVulns.first() diff --git a/src/main/kotlin/snyk/common/lsp/Types.kt b/src/main/kotlin/snyk/common/lsp/Types.kt index 64563370e..07f6c8dee 100644 --- a/src/main/kotlin/snyk/common/lsp/Types.kt +++ b/src/main/kotlin/snyk/common/lsp/Types.kt @@ -201,8 +201,7 @@ data class IssueData( @SerializedName("priorityScore") val priorityScore: Int, @SerializedName("hasAIFix") val hasAIFix: Boolean, @SerializedName("dataFlow") val dataFlow: List, - @SerializedName("isIgnored") val isIgnored: Boolean?, - @SerializedName("ignoreDetails") val ignoreDetails: IgnoreDetails?, + @SerializedName("details") val details: String, ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -231,6 +230,7 @@ data class IssueData( if (priorityScore != other.priorityScore) return false if (hasAIFix != other.hasAIFix) return false if (dataFlow != other.dataFlow) return false + if (details != other.details) return false return true } @@ -251,6 +251,7 @@ data class IssueData( result = 31 * result + priorityScore result = 31 * result + hasAIFix.hashCode() result = 31 * result + dataFlow.hashCode() + result = 31 * result + details.hashCode() return result } } diff --git a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt index 31e9c7ab5..254030189 100644 --- a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt +++ b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt @@ -27,10 +27,11 @@ import javax.swing.JTextArea class BaseImageRemediationDetailPanel( private val project: Project, - private val imageIssues: ContainerIssuesForImage + private val imageIssues: ContainerIssuesForImage, ) : IssueDescriptionPanelBase( title = imageIssues.imageName, - severity = imageIssues.getSeverities().maxOrNull() ?: Severity.UNKNOWN + severity = imageIssues.getSeverities().maxOrNull() ?: Severity.UNKNOWN, + details = null ) { private val targetImages: List = getKubernetesImageCache(project) diff --git a/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt b/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt index aa5ca7e0b..16ac07155 100644 --- a/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt +++ b/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt @@ -18,7 +18,11 @@ import javax.swing.JPanel class ContainerIssueDetailPanel( private val groupedVulns: Collection -) : IssueDescriptionPanelBase(title = groupedVulns.first().title, severity = groupedVulns.first().getSeverity()) { +) : IssueDescriptionPanelBase( + title = groupedVulns.first().title, + severity = groupedVulns.first().getSeverity(), + details = null +) { private val issue = groupedVulns.first() diff --git a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt b/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt index eb651d365..ce01ac92d 100644 --- a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt +++ b/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt @@ -29,7 +29,7 @@ class IacSuggestionDescriptionPanel( val issue: IacIssue, val psiFile: PsiFile?, val project: Project -) : IssueDescriptionPanelBase(title = issue.title, severity = issue.getSeverity()) { +) : IssueDescriptionPanelBase(title = issue.title, severity = issue.getSeverity(), details = null) { private val labelProvider = LabelProvider() From ab461daf99f5e0a926254ceda247d67b014a8ebc Mon Sep 17 00:00:00 2001 From: Teodora Sandu Date: Wed, 27 Mar 2024 11:34:08 +0000 Subject: [PATCH 2/5] test: add tests --- .../panels/IssueDescriptionPanelBase.kt | 9 +- src/main/kotlin/snyk/common/lsp/Types.kt | 2 +- .../SuggestionDescriptionPanelFromLSTest.kt | 150 ++++++++++++++++++ src/test/kotlin/snyk/UIComponentFinder.kt | 17 ++ 4 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt index 4ce846ede..a3652b0f8 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt @@ -32,13 +32,13 @@ abstract class IssueDescriptionPanelBase( private val details: String? ) : JPanel(BorderLayout()), IssueDescriptionPanel { private val unexpectedErrorMessage = "Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused."; - + /** * **MUST** be invoked in derived class to actually create the UI elements. * Can't be part of constructor due to `state` usage in underling abstract/open methods/props: */ protected fun createUI() { - if (pluginSettings().isGlobalIgnoresFeatureEnabled) { + if (pluginSettings().isGlobalIgnoresFeatureEnabled && details != null) { if (!JBCefApp.isSupported()) { val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) @@ -56,11 +56,6 @@ abstract class IssueDescriptionPanelBase( BorderLayout.CENTER ) - if (this.details == null) { - SnykBalloonNotificationHelper.showError("Something went wrong.", null) - return - } - jbCefBrowser.loadHTML(this.details, jbCefBrowser.cefBrowser.url) } else { diff --git a/src/main/kotlin/snyk/common/lsp/Types.kt b/src/main/kotlin/snyk/common/lsp/Types.kt index 07f6c8dee..5fa2ae934 100644 --- a/src/main/kotlin/snyk/common/lsp/Types.kt +++ b/src/main/kotlin/snyk/common/lsp/Types.kt @@ -201,7 +201,7 @@ data class IssueData( @SerializedName("priorityScore") val priorityScore: Int, @SerializedName("hasAIFix") val hasAIFix: Boolean, @SerializedName("dataFlow") val dataFlow: List, - @SerializedName("details") val details: String, + @SerializedName("details") val details: String?, ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt new file mode 100644 index 000000000..3ae87485a --- /dev/null +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt @@ -0,0 +1,150 @@ +@file:Suppress("FunctionName") +package io.snyk.plugin.ui.toolwindow + +import com.google.gson.Gson +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings +import com.intellij.openapi.application.Application +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.command.undo.UndoManager +import com.intellij.openapi.progress.impl.CoreProgressManager +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.RefreshQueue +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager +import com.intellij.psi.PsiFile +import com.intellij.psi.codeStyle.CodeStyleSchemes +import com.intellij.psi.impl.DocumentCommitProcessor +import com.intellij.psi.stubs.StubIndex +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.intellij.ui.components.ActionLink +import com.intellij.util.indexing.FileBasedIndex +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.snyk.plugin.DEFAULT_TIMEOUT_FOR_SCAN_WAITING_MS +import io.snyk.plugin.Severity +import io.snyk.plugin.pluginSettings +import io.snyk.plugin.resetSettings +import io.snyk.plugin.services.SnykApplicationSettingsStateService +import io.snyk.plugin.snykcode.core.SnykCodeFile +import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS +import io.snyk.plugin.ui.toolwindow.panels.VulnerabilityDescriptionPanel +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.Range +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import snyk.UIComponentFinder.getJBCEFBrowser +import snyk.UIComponentFinder.getJButtonByText +import snyk.UIComponentFinder.getJLabelByText +import snyk.code.annotator.SnykCodeAnnotator +import snyk.common.lsp.CommitChangeLine +import snyk.common.lsp.DataFlow +import snyk.common.lsp.ExampleCommitFix +import snyk.common.lsp.ScanIssue +import snyk.oss.Vulnerability +import java.io.FileReader +import java.nio.file.Paths + +class SuggestionDescriptionPanelFromLSTest : BasePlatformTestCase() { + private lateinit var cut: SuggestionDescriptionPanelFromLS + private val fileName = "app.js" + private lateinit var snykCodeFile: SnykCodeFile + private lateinit var issue: ScanIssue + + private lateinit var file: VirtualFile + private lateinit var psiFile: PsiFile + + override fun getTestDataPath(): String { + val resource = SnykCodeAnnotator::class.java.getResource("/test-fixtures/code/annotator") + requireNotNull(resource) { "Make sure that the resource $resource exists!" } + return Paths.get(resource.toURI()).toString() + } + + @Before + override fun setUp() { + super.setUp() + unmockkAll() + resetSettings(project) + + file = myFixture.copyFileToProject(fileName) + psiFile = WriteAction.computeAndWait { psiManager.findFile(file)!! } + snykCodeFile = SnykCodeFile(psiFile.project, psiFile.virtualFile) + + issue = mockk() + every { issue.additionalData.message } returns "Test message" + every { issue.additionalData.isSecurityType } returns true + every { issue.additionalData.cwe } returns null + every { issue.additionalData.repoDatasetSize } returns 1 + every { issue.additionalData.exampleCommitFixes } returns listOf(ExampleCommitFix("https://commit-url", listOf( + CommitChangeLine("1", 1, "lineChange") + ))) + every { issue.additionalData.dataFlow } returns listOf(DataFlow(0, getTestDataPath(), Range(Position(1, 1), Position(1, 1)), "")) + every { issue.title } returns "Test title" + every { issue.getSeverityAsEnum() } returns Severity.CRITICAL + } + + @Test + fun `test createUI should build panel with issue message as overview label if the feature flag is not enabled`() { + every { issue.additionalData.details } returns "HTML message" + every { issue.additionalData.details } returns null + cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) + + val actual = getJLabelByText(cut, "Test message") + assertNotNull(actual) + + val actualBrowser = getJBCEFBrowser(cut) + assertNull(actualBrowser) + } + + @Test + fun `test createUI should build panel with issue message as overview label if the details are empty even if feature flag is enabled`() { + pluginSettings().isGlobalIgnoresFeatureEnabled = true + + every { issue.additionalData.details } returns null + cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) + + val actual = getJLabelByText(cut, "Test message") + assertNotNull(actual) + + val actualBrowser = getJBCEFBrowser(cut) + assertNull(actualBrowser) + } + + @Test + fun `test createUI should show nothing if feature flag is enabled but JCEF is not`() { + pluginSettings().isGlobalIgnoresFeatureEnabled = true + + every { issue.additionalData.details } returns "HTML message" + cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) + + Registry.get("ide.browser.jcef.enabled").setValue("false") + + val actual = getJLabelByText(cut, "Test message") + assertNull(actual) + + val actualBrowser = getJBCEFBrowser(cut) + assertNull(actualBrowser) + } + + @Test + fun `test createUI should build panel with HTML from details if feature flag is enabled`() { + pluginSettings().isGlobalIgnoresFeatureEnabled = true + + every { issue.additionalData.details } returns "HTML message" + cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) + + Registry.get("ide.browser.jcef.enabled").setValue("true") + + val actual = getJLabelByText(cut, "Test message") + assertNull(actual) + + val actualBrowser = getJBCEFBrowser(cut) + assertNotNull(actualBrowser) + } +} diff --git a/src/test/kotlin/snyk/UIComponentFinder.kt b/src/test/kotlin/snyk/UIComponentFinder.kt index 1b20702ef..250f76cfc 100644 --- a/src/test/kotlin/snyk/UIComponentFinder.kt +++ b/src/test/kotlin/snyk/UIComponentFinder.kt @@ -1,5 +1,6 @@ package snyk +import com.intellij.ui.jcef.JBCefBrowser import java.awt.Container import javax.swing.JButton import javax.swing.JLabel @@ -53,4 +54,20 @@ object UIComponentFinder { } return found } + + fun getJBCEFBrowser(parent: Container): JBCefBrowser.MyPanel? { + val components = parent.components + var found: JBCefBrowser.MyPanel? = null + for (component in components) { + if (component is JBCefBrowser.MyPanel) { + found = component + } else if (component is Container) { + found = getJBCEFBrowser(component) + } + if (found != null) { + break + } + } + return found + } } From 86b558125bd54fc44f0f3e46ac30c6f54a7c3ff6 Mon Sep 17 00:00:00 2001 From: Teodora Sandu Date: Wed, 27 Mar 2024 17:53:04 +0000 Subject: [PATCH 3/5] refactor: move code to the right panel --- .../panels/IssueDescriptionPanelBase.kt | 39 +++---------------- .../panels/SuggestionDescriptionPanel.kt | 3 +- .../SuggestionDescriptionPanelFromLS.kt | 34 ++++++++++++++-- .../panels/VulnerabilityDescriptionPanel.kt | 4 +- .../ui/BaseImageRemediationDetailPanel.kt | 3 +- .../container/ui/ContainerIssueDetailPanel.kt | 3 +- .../snyk/iac/IacSuggestionDescriptionPanel.kt | 2 +- 7 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt index a3652b0f8..7927277ab 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/IssueDescriptionPanelBase.kt @@ -2,20 +2,15 @@ package io.snyk.plugin.ui.toolwindow.panels import com.intellij.ui.IdeBorderFactory import com.intellij.ui.SideBorder -import com.intellij.ui.jcef.JBCefApp -import com.intellij.ui.jcef.JBCefBrowserBuilder import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBUI import com.intellij.util.ui.JBUI.Borders import icons.SnykIcons import io.snyk.plugin.Severity -import io.snyk.plugin.pluginSettings import io.snyk.plugin.ui.DescriptionHeaderPanel -import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.addSpacer import io.snyk.plugin.ui.baseGridConstraints import io.snyk.plugin.ui.baseGridConstraintsAnchorWest -import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel import io.snyk.plugin.ui.wrapWithScrollPane import java.awt.BorderLayout import java.awt.Font @@ -28,42 +23,18 @@ private val EMPTY_PANEL = JBUI.Panels.simplePanel() abstract class IssueDescriptionPanelBase( private val title: String, - private val severity: Severity, - private val details: String? + private val severity: Severity ) : JPanel(BorderLayout()), IssueDescriptionPanel { - private val unexpectedErrorMessage = "Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused."; /** * **MUST** be invoked in derived class to actually create the UI elements. * Can't be part of constructor due to `state` usage in underling abstract/open methods/props: */ protected fun createUI() { - if (pluginSettings().isGlobalIgnoresFeatureEnabled && details != null) { - if (!JBCefApp.isSupported()) { - val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) - this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) - SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) - return - } - - val cefClient = JBCefApp.getInstance().createClient() - val jbCefBrowser = JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true).build() - - val panel = JPanel() - panel.add(jbCefBrowser.component, BorderLayout()) - this.add( - wrapWithScrollPane(panel), - BorderLayout.CENTER - ) - - jbCefBrowser.loadHTML(this.details, jbCefBrowser.cefBrowser.url) - - } else { - this.add( - wrapWithScrollPane(descriptionBodyPanel()), - BorderLayout.CENTER - ) - } + this.add( + wrapWithScrollPane(descriptionBodyPanel()), + BorderLayout.CENTER + ) if (isBottomPanelNeeded) { this.add( bottomPanel(), diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt index 370d180d6..7c9297159 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt @@ -39,8 +39,7 @@ class SuggestionDescriptionPanel( private val suggestionIndex: Int ) : IssueDescriptionPanelBase( title = suggestion.title, - severity = suggestion.getSeverityAsEnum(), - details = null + severity = suggestion.getSeverityAsEnum() ) { val project = snykCodeFile.project diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt index ea5b7a727..79f679ad3 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt @@ -4,20 +4,27 @@ import com.intellij.icons.AllIcons import com.intellij.ui.HyperlinkLabel import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.components.JBTabbedPane +import com.intellij.ui.jcef.JBCefApp +import com.intellij.ui.jcef.JBCefBrowserBuilder import com.intellij.uiDesigner.core.GridConstraints import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBInsets import com.intellij.util.ui.UIUtil import io.snyk.plugin.getDocument import io.snyk.plugin.navigateToSource +import io.snyk.plugin.pluginSettings import io.snyk.plugin.snykcode.core.SnykCodeFile import io.snyk.plugin.toVirtualFile import io.snyk.plugin.ui.DescriptionHeaderPanel +import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.baseGridConstraintsAnchorWest import io.snyk.plugin.ui.descriptionHeaderPanel import io.snyk.plugin.ui.panelGridConstraints +import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel +import io.snyk.plugin.ui.wrapWithScrollPane import snyk.common.lsp.DataFlow import snyk.common.lsp.ScanIssue +import java.awt.BorderLayout import java.awt.Color import java.awt.Dimension import java.awt.Font @@ -35,13 +42,34 @@ class SuggestionDescriptionPanelFromLS( private val issue: ScanIssue ) : IssueDescriptionPanelBase( title = getIssueTitle(issue), - severity = issue.getSeverityAsEnum(), - details = issue.additionalData.details + severity = issue.getSeverityAsEnum() ) { val project = snykCodeFile.project + private val unexpectedErrorMessage = "Snyk encountered an issue while rendering the vulnerability description. Please try again, or contact support if the problem persists. We apologize for any inconvenience caused."; init { - createUI() + if (pluginSettings().isGlobalIgnoresFeatureEnabled && issue.additionalData.details != null) { + if (!JBCefApp.isSupported()) { + val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) + this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) + SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) + } else { + val cefClient = JBCefApp.getInstance().createClient() + val jbCefBrowser = JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true).build() + + val panel = JPanel() + panel.add(jbCefBrowser.component, BorderLayout()) + this.add( + wrapWithScrollPane(panel), + BorderLayout.CENTER + ) + + jbCefBrowser.loadHTML(issue.additionalData.details, jbCefBrowser.cefBrowser.url) + } + } else { + + createUI() + } } override fun secondRowTitlePanel(): DescriptionHeaderPanel = descriptionHeaderPanel( diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt index 8e5cecfcb..26254bbb1 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/VulnerabilityDescriptionPanel.kt @@ -22,8 +22,8 @@ class VulnerabilityDescriptionPanel( private val groupedVulns: Collection ) : IssueDescriptionPanelBase( title = groupedVulns.first().title, - severity = groupedVulns.first().getSeverity(), - details = null) { + severity = groupedVulns.first().getSeverity() +) { private val labelProvider: LabelProvider = LabelProvider() private val vulnerability = groupedVulns.first() diff --git a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt index 254030189..7a2a61f66 100644 --- a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt +++ b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt @@ -30,8 +30,7 @@ class BaseImageRemediationDetailPanel( private val imageIssues: ContainerIssuesForImage, ) : IssueDescriptionPanelBase( title = imageIssues.imageName, - severity = imageIssues.getSeverities().maxOrNull() ?: Severity.UNKNOWN, - details = null + severity = imageIssues.getSeverities().maxOrNull() ?: Severity.UNKNOWN ) { private val targetImages: List = getKubernetesImageCache(project) diff --git a/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt b/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt index 16ac07155..e080340ba 100644 --- a/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt +++ b/src/main/kotlin/snyk/container/ui/ContainerIssueDetailPanel.kt @@ -20,8 +20,7 @@ class ContainerIssueDetailPanel( private val groupedVulns: Collection ) : IssueDescriptionPanelBase( title = groupedVulns.first().title, - severity = groupedVulns.first().getSeverity(), - details = null + severity = groupedVulns.first().getSeverity() ) { private val issue = groupedVulns.first() diff --git a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt b/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt index ce01ac92d..eb651d365 100644 --- a/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt +++ b/src/main/kotlin/snyk/iac/IacSuggestionDescriptionPanel.kt @@ -29,7 +29,7 @@ class IacSuggestionDescriptionPanel( val issue: IacIssue, val psiFile: PsiFile?, val project: Project -) : IssueDescriptionPanelBase(title = issue.title, severity = issue.getSeverity(), details = null) { +) : IssueDescriptionPanelBase(title = issue.title, severity = issue.getSeverity()) { private val labelProvider = LabelProvider() From 9c02aee039daa86f639c9818db9dd10ada969ad8 Mon Sep 17 00:00:00 2001 From: Teodora Sandu Date: Thu, 28 Mar 2024 14:17:12 +0000 Subject: [PATCH 4/5] refactor: remove unused code --- .../ui/SnykBalloonNotificationHelper.kt | 8 ------- .../SuggestionDescriptionPanelFromLSTest.kt | 24 ------------------- 2 files changed, 32 deletions(-) diff --git a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt index 9243db881..553dbbb26 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt @@ -110,12 +110,4 @@ object SnykBalloonNotificationHelper { ) showBalloonForComponent(balloon, component, showAbove) } - fun showErrorBalloonForComponent(message: String, component: Component, showAbove: Boolean = false) { - val balloon = createBalloon( - message, - AllIcons.General.BalloonError, - MessageType.ERROR.popupBackground - ) - showBalloonForComponent(balloon, component, showAbove) - } } diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt index 3ae87485a..2f8b03b9b 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt @@ -1,54 +1,30 @@ @file:Suppress("FunctionName") package io.snyk.plugin.ui.toolwindow -import com.google.gson.Gson -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings -import com.intellij.openapi.application.Application -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.WriteAction -import com.intellij.openapi.command.undo.UndoManager -import com.intellij.openapi.progress.impl.CoreProgressManager import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.newvfs.RefreshQueue -import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager import com.intellij.psi.PsiFile -import com.intellij.psi.codeStyle.CodeStyleSchemes -import com.intellij.psi.impl.DocumentCommitProcessor -import com.intellij.psi.stubs.StubIndex import com.intellij.testFramework.fixtures.BasePlatformTestCase -import com.intellij.ui.components.ActionLink -import com.intellij.util.indexing.FileBasedIndex import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.unmockkAll -import io.snyk.plugin.DEFAULT_TIMEOUT_FOR_SCAN_WAITING_MS import io.snyk.plugin.Severity import io.snyk.plugin.pluginSettings import io.snyk.plugin.resetSettings -import io.snyk.plugin.services.SnykApplicationSettingsStateService import io.snyk.plugin.snykcode.core.SnykCodeFile import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS -import io.snyk.plugin.ui.toolwindow.panels.VulnerabilityDescriptionPanel -import junit.framework.TestCase.assertEquals -import junit.framework.TestCase.assertNotNull import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range import org.junit.Before -import org.junit.Ignore import org.junit.Test import snyk.UIComponentFinder.getJBCEFBrowser -import snyk.UIComponentFinder.getJButtonByText import snyk.UIComponentFinder.getJLabelByText import snyk.code.annotator.SnykCodeAnnotator import snyk.common.lsp.CommitChangeLine import snyk.common.lsp.DataFlow import snyk.common.lsp.ExampleCommitFix import snyk.common.lsp.ScanIssue -import snyk.oss.Vulnerability -import java.io.FileReader import java.nio.file.Paths class SuggestionDescriptionPanelFromLSTest : BasePlatformTestCase() { From 458cea89b44626134cc5539016580592ae7876c0 Mon Sep 17 00:00:00 2001 From: Teodora Sandu Date: Thu, 28 Mar 2024 16:13:27 +0000 Subject: [PATCH 5/5] test: try to fix tests --- src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt | 14 +++++++++++ .../SuggestionDescriptionPanelFromLS.kt | 12 ++++------ .../SuggestionDescriptionPanelFromLSTest.kt | 23 ++++++++++++------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt index 655716b01..330d35ea9 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt @@ -6,6 +6,9 @@ import com.intellij.ui.BrowserHyperlinkListener import com.intellij.ui.ColorUtil import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.components.ActionLink +import com.intellij.ui.jcef.JBCefApp +import com.intellij.ui.jcef.JBCefBrowser +import com.intellij.ui.jcef.JBCefBrowserBuilder import com.intellij.uiDesigner.core.GridConstraints import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.uiDesigner.core.Spacer @@ -373,3 +376,14 @@ fun expandTreeNodeRecursively(tree: JTree, node: DefaultMutableTreeNode) { expandTreeNodeRecursively(tree, it as DefaultMutableTreeNode) } } + +//JComponent, String +fun getJBCefBrowserIfSupported () : Pair { + if (!JBCefApp.isSupported()) { + return null to "" + } + val cefClient = JBCefApp.getInstance().createClient() + val jbCefBrowser = JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true).build() + + return jbCefBrowser to jbCefBrowser.cefBrowser.url +} diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt index 79f679ad3..65061eede 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanelFromLS.kt @@ -4,8 +4,6 @@ import com.intellij.icons.AllIcons import com.intellij.ui.HyperlinkLabel import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.components.JBTabbedPane -import com.intellij.ui.jcef.JBCefApp -import com.intellij.ui.jcef.JBCefBrowserBuilder import com.intellij.uiDesigner.core.GridConstraints import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.util.ui.JBInsets @@ -19,6 +17,7 @@ import io.snyk.plugin.ui.DescriptionHeaderPanel import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.baseGridConstraintsAnchorWest import io.snyk.plugin.ui.descriptionHeaderPanel +import io.snyk.plugin.ui.getJBCefBrowserIfSupported import io.snyk.plugin.ui.panelGridConstraints import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel import io.snyk.plugin.ui.wrapWithScrollPane @@ -49,14 +48,12 @@ class SuggestionDescriptionPanelFromLS( init { if (pluginSettings().isGlobalIgnoresFeatureEnabled && issue.additionalData.details != null) { - if (!JBCefApp.isSupported()) { + val (jbCefBrowser, jbCefBrowserUrl) = getJBCefBrowserIfSupported() + if (jbCefBrowser == null) { val statePanel = StatePanel(SnykToolWindowPanel.SELECT_ISSUE_TEXT) this.add(wrapWithScrollPane(statePanel), BorderLayout.CENTER) SnykBalloonNotificationHelper.showError(unexpectedErrorMessage, null) } else { - val cefClient = JBCefApp.getInstance().createClient() - val jbCefBrowser = JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true).build() - val panel = JPanel() panel.add(jbCefBrowser.component, BorderLayout()) this.add( @@ -64,10 +61,9 @@ class SuggestionDescriptionPanelFromLS( BorderLayout.CENTER ) - jbCefBrowser.loadHTML(issue.additionalData.details, jbCefBrowser.cefBrowser.url) + jbCefBrowser.loadHTML(issue.additionalData.details, jbCefBrowserUrl) } } else { - createUI() } } diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt index 2f8b03b9b..13bd831de 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSTest.kt @@ -2,18 +2,22 @@ package io.snyk.plugin.ui.toolwindow import com.intellij.openapi.application.WriteAction -import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.intellij.ui.jcef.JBCefBrowser import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.unmockkAll import io.snyk.plugin.Severity import io.snyk.plugin.pluginSettings import io.snyk.plugin.resetSettings import io.snyk.plugin.snykcode.core.SnykCodeFile +import io.snyk.plugin.ui.getJBCefBrowserIfSupported import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS +import org.cef.browser.CefBrowserFactory +import org.cef.browser.CefRequestContext import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.Range import org.junit.Before @@ -26,6 +30,7 @@ import snyk.common.lsp.DataFlow import snyk.common.lsp.ExampleCommitFix import snyk.common.lsp.ScanIssue import java.nio.file.Paths +import javax.swing.JPanel class SuggestionDescriptionPanelFromLSTest : BasePlatformTestCase() { private lateinit var cut: SuggestionDescriptionPanelFromLS @@ -96,11 +101,12 @@ class SuggestionDescriptionPanelFromLSTest : BasePlatformTestCase() { fun `test createUI should show nothing if feature flag is enabled but JCEF is not`() { pluginSettings().isGlobalIgnoresFeatureEnabled = true + mockkStatic("io.snyk.plugin.ui.UIUtilsKt") + every { getJBCefBrowserIfSupported() } returns (null to "") + every { issue.additionalData.details } returns "HTML message" cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) - Registry.get("ide.browser.jcef.enabled").setValue("false") - val actual = getJLabelByText(cut, "Test message") assertNull(actual) @@ -112,15 +118,16 @@ class SuggestionDescriptionPanelFromLSTest : BasePlatformTestCase() { fun `test createUI should build panel with HTML from details if feature flag is enabled`() { pluginSettings().isGlobalIgnoresFeatureEnabled = true + val mockJBCefBrowser = mockk() + every { mockJBCefBrowser.component } returns JPanel() + every { mockJBCefBrowser.loadHTML(eq("HTML message"), eq("http://foo/bar")) } returns + mockkStatic("io.snyk.plugin.ui.UIUtilsKt") + every { getJBCefBrowserIfSupported() } returns (mockJBCefBrowser to "http://foo/bar") + every { issue.additionalData.details } returns "HTML message" cut = SuggestionDescriptionPanelFromLS(snykCodeFile, issue) - Registry.get("ide.browser.jcef.enabled").setValue("true") - val actual = getJLabelByText(cut, "Test message") assertNull(actual) - - val actualBrowser = getJBCEFBrowser(cut) - assertNotNull(actualBrowser) } }