Skip to content

Commit

Permalink
fix: issue description retrieval & styles
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiandoetsch committed Oct 23, 2024
1 parent 4a0deb6 commit f18123f
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 87 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ repositories {
dependencies {
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
implementation(platform("com.squareup.retrofit2:retrofit-bom:2.11.0"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1")

implementation("org.commonmark:commonmark:0.21.0")
implementation("com.google.code.gson:gson:2.10.1")
Expand Down
92 changes: 61 additions & 31 deletions src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import io.snyk.plugin.ui.toolwindow.panels.StatePanel
import io.snyk.plugin.ui.toolwindow.panels.TreePanel
import io.snyk.plugin.ui.wrapWithScrollPane
import org.jetbrains.annotations.TestOnly
import org.jetbrains.concurrency.runAsync
import snyk.common.ProductType
import snyk.common.SnykError
import snyk.common.lsp.LanguageServerWrapper
Expand All @@ -79,6 +80,7 @@ import java.awt.BorderLayout
import java.util.Objects.nonNull
import javax.swing.JPanel
import javax.swing.JScrollPane
import javax.swing.event.TreeSelectionEvent
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
Expand All @@ -93,7 +95,7 @@ class SnykToolWindowPanel(
Disposable {
private val descriptionPanel = SimpleToolWindowPanel(true, true).apply { name = "descriptionPanel" }
private val logger = Logger.getInstance(this::class.java)
private val rootTreeNode = ChooseBranchNode(project = project)
private val rootTreeNode = ChooseBranchNode(project = project)
private val rootOssTreeNode = RootOssTreeNode(project)
private val rootSecurityIssuesTreeNode = RootSecurityIssuesTreeNode(project)
private val rootQualityIssuesTreeNode = RootQualityIssuesTreeNode(project)
Expand Down Expand Up @@ -129,10 +131,10 @@ class SnykToolWindowPanel(
}



init {
val folderConfig = service<FolderConfigSettings>().getFolderConfig(project.basePath.toString())
val rootNodeText = folderConfig?.let { getRootNodeText(it.folderPath, it.baseBranch) } ?: "Choose branch on ${project.basePath}"
val rootNodeText = folderConfig?.let { getRootNodeText(it.folderPath, it.baseBranch) }
?: "Choose branch on ${project.basePath}"
rootTreeNode.info = rootNodeText

vulnerabilitiesTree.cellRenderer = SnykTreeCellRenderer()
Expand All @@ -148,8 +150,10 @@ class SnykToolWindowPanel(
createTreeAndDescriptionPanel()
chooseMainPanelToDisplay()

vulnerabilitiesTree.selectionModel.addTreeSelectionListener {
updateDescriptionPanelBySelectedTreeNode()
vulnerabilitiesTree.selectionModel.addTreeSelectionListener { treeSelectionEvent ->
runAsync {
updateDescriptionPanelBySelectedTreeNode(treeSelectionEvent)
}
}

val scanListenerLS =
Expand Down Expand Up @@ -316,45 +320,74 @@ class SnykToolWindowPanel(
)
}

private fun updateDescriptionPanelBySelectedTreeNode() {
private fun updateDescriptionPanelBySelectedTreeNode(treeSelectionEvent: TreeSelectionEvent) {
val capturedSmartReloadMode = smartReloadMode
val capturedNavigateToSourceEnabled = triggerSelectionListeners

ApplicationManager.getApplication().invokeLater {
descriptionPanel.removeAll()
val selectionPath = vulnerabilitiesTree.selectionPath
if (nonNull(selectionPath)) {
val lastPathComponent = selectionPath!!.lastPathComponent
val selectionPath = treeSelectionEvent.path
if (nonNull(selectionPath) && treeSelectionEvent.isAddedPath) {
val lastPathComponent = selectionPath.lastPathComponent

if (lastPathComponent is ChooseBranchNode && capturedNavigateToSourceEnabled && !capturedSmartReloadMode) {
if (lastPathComponent is ChooseBranchNode && capturedNavigateToSourceEnabled && !capturedSmartReloadMode) {
invokeLater {
BranchChooserComboBoxDialog(project).show()
}
}

if (!capturedSmartReloadMode &&
capturedNavigateToSourceEnabled &&
lastPathComponent is NavigatableToSourceTreeNode
) {
lastPathComponent.navigateToSource()
}
when (val selectedNode: DefaultMutableTreeNode = lastPathComponent as DefaultMutableTreeNode) {
is DescriptionHolderTreeNode -> {
if (!capturedSmartReloadMode &&
capturedNavigateToSourceEnabled &&
lastPathComponent is NavigatableToSourceTreeNode
) {
lastPathComponent.navigateToSource()
}
when (val selectedNode: DefaultMutableTreeNode = lastPathComponent as DefaultMutableTreeNode) {
is DescriptionHolderTreeNode -> {
if (selectedNode is SuggestionTreeNode) {
val cache = getSnykCachedResults(project) ?: return
val issue = selectedNode.issue
val productIssues = when (issue.filterableIssueType) {
ScanIssue.CODE_SECURITY, ScanIssue.CODE_QUALITY -> cache.currentSnykCodeResultsLS
ScanIssue.OPEN_SOURCE -> cache.currentOSSResultsLS
ScanIssue.INFRASTRUCTURE_AS_CODE -> cache.currentIacResultsLS
ScanIssue.CONTAINER -> cache.currentContainerResultsLS
else -> {
emptyMap()
}
}
productIssues.values.flatten().filter { issue.id == it.id }.forEach { _ ->
val newDescriptionPanel = selectedNode.getDescriptionPanel()
descriptionPanel.removeAll()
descriptionPanel.add(
newDescriptionPanel,
BorderLayout.CENTER,
)
}
} else {
descriptionPanel.removeAll()
descriptionPanel.add(
selectedNode.getDescriptionPanel(),
BorderLayout.CENTER,
)
}

is ErrorHolderTreeNode -> {
selectedNode.getSnykError()?.let {
displaySnykError(it)
} ?: displayEmptyDescription()
}
}

else -> displayEmptyDescription()
is ErrorHolderTreeNode -> {
descriptionPanel.removeAll()
selectedNode.getSnykError()?.let {
displaySnykError(it)
} ?: displayEmptyDescription()
}

else -> {
descriptionPanel.removeAll()
displayEmptyDescription()
}
} else {
displayEmptyDescription()
}
} else {
displayEmptyDescription()
}
invokeLater {
descriptionPanel.revalidate()
descriptionPanel.repaint()
}
Expand Down Expand Up @@ -842,9 +875,6 @@ class SnykToolWindowPanel(
smartReloadMode = true
try {
selectedNode?.let { TreeUtil.selectNode(vulnerabilitiesTree, it) }
// for some reason TreeSelectionListener is not initiated here on node selection
// also we need to update Description panel in case if no selection was made before
updateDescriptionPanelBySelectedTreeNode()
} finally {
smartReloadMode = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.snyk.plugin.events.SnykScanListenerLS
import io.snyk.plugin.getSnykCachedResults
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.refreshAnnotationsForOpenFiles
import io.snyk.plugin.ui.expandTreeNodeRecursively
import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel.Companion.CODE_QUALITY_ROOT_TEXT
import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel.Companion.CODE_SECURITY_ROOT_TEXT
import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel.Companion.IAC_ROOT_TEXT
Expand Down Expand Up @@ -422,12 +423,14 @@ class SnykToolWindowSnykScanListenerLS(
.forEach { issue ->
fileTreeNode.add(
SuggestionTreeNode(
project,
issue,
navigateToSource(entry.key.virtualFile, issue.textRange ?: TextRange(0, 0)),
),
)
}
}
expandTreeNodeRecursively(snykToolWindowPanel.vulnerabilitiesTree, rootNode)
}

private fun buildSeveritiesPostfixForFileNode(results: Map<SnykFile, List<ScanIssue>>): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
package io.snyk.plugin.ui.toolwindow.nodes.leaf

import io.snyk.plugin.SnykFile
import com.intellij.openapi.project.Project
import io.snyk.plugin.ui.toolwindow.nodes.DescriptionHolderTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.NavigatableToSourceTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.SnykFileTreeNode
import io.snyk.plugin.ui.toolwindow.panels.IssueDescriptionPanelBase
import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS
import snyk.common.ProductType
import snyk.common.lsp.ScanIssue
import javax.swing.tree.DefaultMutableTreeNode

class SuggestionTreeNode(
private val issue: ScanIssue,
val project: Project,
val issue: ScanIssue,
override val navigateToSource: () -> Unit
) : DefaultMutableTreeNode(issue), NavigatableToSourceTreeNode, DescriptionHolderTreeNode {

@Suppress("UNCHECKED_CAST")
override fun getDescriptionPanel(): IssueDescriptionPanelBase {
val snykFileTreeNode = this.parent as? SnykFileTreeNode
?: throw IllegalArgumentException(this.toString())

@Suppress("UNCHECKED_CAST")
val entry =
(snykFileTreeNode.userObject as Pair<Map.Entry<SnykFile, List<ScanIssue>>, ProductType>).first
val snykFile = entry.key
return SuggestionDescriptionPanelFromLS(snykFile, issue)
return SuggestionDescriptionPanelFromLS(project, issue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package io.snyk.plugin.ui.toolwindow.panels

import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.uiDesigner.core.GridLayoutManager
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import io.snyk.plugin.SnykFile
import io.snyk.plugin.toVirtualFile
import io.snyk.plugin.ui.DescriptionHeaderPanel
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
Expand All @@ -31,13 +31,12 @@ import javax.swing.JPanel
import kotlin.collections.set

class SuggestionDescriptionPanelFromLS(
snykFile: SnykFile,
val project: Project,
private val issue: ScanIssue,
) : IssueDescriptionPanelBase(
title = issue.title(),
severity = issue.getSeverityAsEnum(),
) {
val project = snykFile.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."

Expand All @@ -57,7 +56,7 @@ class SuggestionDescriptionPanelFromLS(
virtualFiles[dataFlow.filePath] = dataFlow.filePath.toVirtualFile()
}

val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles)
val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(project, virtualFiles)
loadHandlerGenerators += {
openFileLoadHandlerGenerator.generate(it)
}
Expand All @@ -67,7 +66,7 @@ class SuggestionDescriptionPanelFromLS(
generateAIFixHandler.generateAIFixCommand(it)
}

val applyFixHandler = ApplyFixHandler(snykFile.project)
val applyFixHandler = ApplyFixHandler(project)
loadHandlerGenerators += {
applyFixHandler.generateApplyFixCommand(it)
}
Expand Down Expand Up @@ -193,6 +192,11 @@ class SuggestionDescriptionPanelFromLS(
editorBackground
)

html = html.replace(
"var(--editor-color)",
editorBackground
)

return html
}

Expand Down
44 changes: 22 additions & 22 deletions src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import io.snyk.plugin.pluginSettings
import io.snyk.plugin.runInBackground
import io.snyk.plugin.toLanguageServerURL
import io.snyk.plugin.ui.toolwindow.SnykPluginDisposable
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.eclipse.lsp4j.ClientCapabilities
import org.eclipse.lsp4j.ClientInfo
import org.eclipse.lsp4j.CodeActionCapabilities
Expand Down Expand Up @@ -120,7 +117,6 @@ class LanguageServerWrapper(

var isInitialized: Boolean = false

@OptIn(DelicateCoroutinesApi::class)
private fun initialize() {
if (disposed) return
if (lsPath.toNioPathOrNull()?.exists() == false) {
Expand All @@ -143,7 +139,8 @@ class LanguageServerWrapper(
EnvironmentHelper.updateEnvironment(processBuilder.environment(), pluginSettings().token ?: "")

process = processBuilder.start()
GlobalScope.launch {

runAsync {
if (!disposed) {
try {
process.errorStream.bufferedReader().forEachLine { logger.debug(it) }
Expand All @@ -160,14 +157,16 @@ class LanguageServerWrapper(

runAsync {
listenerFuture.get()
logger.info("Snyk Language Server was terminated, listener has ended.")
isInitialized = false
}

if (!listenerFuture.isDone) {
if (!(listenerFuture.isDone || listenerFuture.isCancelled)) {
sendInitializeMessage()
isInitialized = true
// listen for downloads / restarts
LanguageServerRestartListener.getInstance()
refreshFeatureFlags()
} else {
logger.warn("Language Server initialization did not succeed")
}
Expand Down Expand Up @@ -525,27 +524,29 @@ class LanguageServerWrapper(
}
}

fun generateIssueDescription(issueID: String): String? {
fun generateIssueDescription(issue: ScanIssue): String? {
if (!ensureLanguageServerInitialized()) return null
try {
val generateIssueCommand = ExecuteCommandParams(SNYK_GENERATE_ISSUE_DESCRIPTION, listOf(issueID))
val result =
languageServer.workspaceService
.executeCommand(generateIssueCommand)
.get(2, TimeUnit.SECONDS)
.toString()
return result
val key = issue.additionalData.key
if (key.isBlank()) throw RuntimeException("Issue ID is required")
val generateIssueCommand = ExecuteCommandParams(SNYK_GENERATE_ISSUE_DESCRIPTION, listOf(key))
return try {
logger.info("########### calling generateIssueDescription")
executeCommand(generateIssueCommand, 10000).toString()
} catch (e: TimeoutException) {
logger.warn("could not generate html description", e)
return null
val exceptionMessage = "generate issue description failed"
logger.warn(exceptionMessage, e)
null
} catch (e: Exception) {
logger.error("generate issue description failed", e)
null
}
}

fun logout() {
if (!ensureLanguageServerInitialized()) return
val cmd = ExecuteCommandParams(COMMAND_LOGOUT, emptyList())
try {
languageServer.workspaceService.executeCommand(cmd).get(5, TimeUnit.SECONDS)
executeCommand(cmd)
} catch (e: TimeoutException) {
logger.warn("could not logout", e)
}
Expand All @@ -567,7 +568,7 @@ class LanguageServerWrapper(
val param = ExecuteCommandParams()
param.command = COMMAND_CODE_FIX_DIFFS
param.arguments = listOf(folderURI, fileURI, issueID)
val result = languageServer.workspaceService.executeCommand(param).get(120, TimeUnit.SECONDS) as List<*>
val result = executeCommand(param, 120000) as List<*>
val diffList: MutableList<Fix> = mutableListOf()

result.forEach {
Expand Down Expand Up @@ -595,7 +596,7 @@ class LanguageServerWrapper(
val param = ExecuteCommandParams()
param.command = COMMAND_CODE_SUBMIT_FIX_FEEDBACK
param.arguments = listOf(fixId, feedback)
languageServer.workspaceService.executeCommand(param)
executeCommand(param)
} catch (err: Exception) {
logger.warn("Error in submitAutofixFeedbackCommand", err)
}
Expand Down Expand Up @@ -631,8 +632,7 @@ class LanguageServerWrapper(
if (!ensureLanguageServerInitialized()) return null
try {
val executeCommandParams = ExecuteCommandParams(COMMAND_GET_SETTINGS_SAST_ENABLED, emptyList())
val response =
languageServer.workspaceService.executeCommand(executeCommandParams).get(10, TimeUnit.SECONDS)
val response = executeCommand(executeCommandParams, 10000)
if (response is Map<*, *>) {
val localCodeEngineMap: Map<String, *> = response["localCodeEngine"] as Map<String, *>
return SastSettings(
Expand Down
Loading

0 comments on commit f18123f

Please sign in to comment.