Skip to content

Commit

Permalink
Merge pull request #648 from snyk/feat/scan-summary-panel
Browse files Browse the repository at this point in the history
feat: scan summary panel [IDE-894]
  • Loading branch information
andrewrobinsonhodges-snyk authored Feb 7, 2025
2 parents 55703d0 + 5c0c403 commit c8ccdec
Show file tree
Hide file tree
Showing 23 changed files with 408 additions and 79 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/resource-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Static Resource Checking
on:
push:
branches: [ master ]
pull_request:

jobs:
static-resource-checks:
runs-on: ubuntu-latest
steps:
- name: Fetch Sources
uses: actions/checkout@v4

- name: Check Static Resources
run: |
declare -A resources
# Add each resource as a kay, value pair, mapping the local resource to the reference wile (which should be stored in the lanaguage server repository). For example:
# resources["<path_to_local_file>"]="<url_of_reference_file>"
resources["src/main/resources/html/ScanSummaryInit.html"]="https://raw.githubusercontent.com/snyk/snyk-ls/refs/heads/main/shared_ide_resources/ui/html/ScanSummaryInit.html"
for key in ${!resources[@]}; do
candidate=$(sha512sum $key | awk {'print $1'})
candidate=${candidate:="null"}
reference=$(curl -s ${resources[$key]} | sha512sum | awk {'print $1'})
echo "Candidate file $key has sha512sum $candidate"
echo "Reference file ${resources[$key]} has sha512sum $reference"
[[ $candidate == $reference ]]
done
5 changes: 5 additions & 0 deletions src/main/kotlin/io/snyk/plugin/events/SnykScanListenerLS.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ interface SnykScanListenerLS {
companion object {
val SNYK_SCAN_TOPIC =
Topic.create("Snyk scan LS", SnykScanListenerLS::class.java)

val PRODUCT_CODE = "code"
val PRODUCT_OSS = "oss"
val PRODUCT_IAC = "iac"
val PRODUCT_CONTAINER = "container"
}

fun scanningStarted(snykScan: SnykScanParams) {}
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/io/snyk/plugin/events/SnykScanSummaryListenerLS.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.snyk.plugin.events

import com.intellij.util.messages.Topic
import snyk.common.lsp.SnykScanSummaryParams

interface SnykScanSummaryListenerLS {
companion object {
val SNYK_SCAN_SUMMARY_TOPIC =
Topic.create("Snyk scan summary LS", SnykScanSummaryListenerLS::class.java)
}

fun onSummaryReceived(summaryParams: SnykScanSummaryParams) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplica
var cliBaseDownloadURL: String = "https://downloads.snyk.io"
var cliPath: String = getPluginPath() + separator + Platform.current().snykWrapperFileName
var cliReleaseChannel = "stable"
var displayAllIssues: String = "All issues"
var issuesToDisplay: String = DISPLAY_ALL_ISSUES
var manageBinariesAutomatically: Boolean = true
var fileListenerEnabled: Boolean = true
// TODO migrate to https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html?from=jetbrains.org
Expand Down Expand Up @@ -105,7 +105,7 @@ class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplica
}

fun isDeltaFindingsEnabled(): Boolean =
(displayAllIssues == "Net new issues")
(issuesToDisplay == DISPLAY_NEW_ISSUES)

fun getAdditionalParameters(project: Project? = null): String? =
if (isProjectSettingsAvailable(project)) {
Expand Down Expand Up @@ -180,8 +180,17 @@ class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplica
treeFiltering.containerResults = containerScanEnabled
}

fun setDeltaEnabled() {
displayAllIssues = "Net new issues"
fun setDeltaEnabled(enabled: Boolean) {
issuesToDisplay = if (enabled) {
DISPLAY_NEW_ISSUES
} else {
DISPLAY_ALL_ISSUES
}
}

companion object {
const val DISPLAY_ALL_ISSUES = "All issues"
const val DISPLAY_NEW_ISSUES = "Net new issues"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SnykProjectSettingsConfigurable(
snykSettingsDialog.getCliBaseDownloadURL() != settingsStateService.cliBaseDownloadURL ||
snykSettingsDialog.isScanOnSaveEnabled() != settingsStateService.scanOnSave ||
snykSettingsDialog.getCliReleaseChannel() != settingsStateService.cliReleaseChannel ||
snykSettingsDialog.getDisplayIssuesSelection() != settingsStateService.displayAllIssues ||
snykSettingsDialog.getDisplayIssuesSelection() != settingsStateService.issuesToDisplay ||

isAuthenticationMethodModified()

Expand Down Expand Up @@ -116,8 +116,8 @@ class SnykProjectSettingsConfigurable(
handleReleaseChannelChanged()
}

if (snykSettingsDialog.getDisplayIssuesSelection() != pluginSettings().displayAllIssues) {
settingsStateService.displayAllIssues = snykSettingsDialog.getDisplayIssuesSelection()
if (snykSettingsDialog.getDisplayIssuesSelection() != pluginSettings().issuesToDisplay) {
settingsStateService.issuesToDisplay = snykSettingsDialog.getDisplayIssuesSelection()
val cache = getSnykCachedResults(project)
cache?.currentOSSResultsLS?.clear()
cache?.currentSnykCodeResultsLS?.clear()
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class SnykSettingsDialog(

baseBranchInfoLabel.text = service<FolderConfigSettings>().getAll()
.values.joinToString("\n") { "Base branch for ${it.folderPath}: ${it.baseBranch}" }
netNewIssuesDropDown.selectedItem = applicationSettings.displayAllIssues
netNewIssuesDropDown.selectedItem = applicationSettings.issuesToDisplay
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import org.cef.browser.CefBrowser
Expand All @@ -18,7 +19,7 @@ class ThemeBasedStylingGenerator {
fun toCssHex(color: Color): String {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}
fun replaceWithCustomStyles(htmlToReplace: String):String {
fun replaceWithCustomStyles(htmlToReplace: String): String {
var html = htmlToReplace;
val editorColorsManager = EditorColorsManager.getInstance()
val editorUiTheme = editorColorsManager.schemeForCurrentUITheme
Expand All @@ -31,8 +32,10 @@ class ThemeBasedStylingGenerator {
val isHighContrast =
EditorColorsManager.getInstance().globalScheme.name.contains("High contrast", ignoreCase = true)
html = html.replace("--default-font: ", "--default-font: \"${JBUI.Fonts.label().asPlain().family}\", ")
html = html.replace("var(--main-font-size)", JBFont.small().size.toString() + "px")
html = html.replace("var(--text-color)", UIUtil.getLabelForeground().toHex())
html = html.replace("var(--background-color)", UIUtil.getPanelBackground().toHex())
html = html.replace("var(--ide-background-color)", UIUtil.getPanelBackground().toHex())
html = html.replace("var(--border-color)", borderColor)
html = html.replace("var(--horizontal-border-color)", borderColor)
html = html.replace("var(--link-color)", JBUI.CurrentTheme.Link.Foreground.ENABLED.toHex())
Expand Down
39 changes: 39 additions & 0 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/ToggleDeltaHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.snyk.plugin.ui.jcef

import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.ui.jcef.JBCefJSQuery
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.runInBackground
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import snyk.common.lsp.LanguageServerWrapper

class ToggleDeltaHandler {
fun generate(jbCefBrowser: JBCefBrowserBase): CefLoadHandlerAdapter {
val toggleDeltaQuery = JBCefJSQuery.create(jbCefBrowser)
toggleDeltaQuery.addHandler {deltaEnabled ->
pluginSettings().setDeltaEnabled(deltaEnabled.toBoolean())
runInBackground("Snyk: updating configuration") {
LanguageServerWrapper.getInstance().updateConfiguration()
}
return@addHandler JBCefJSQuery.Response("success")
}

return object : CefLoadHandlerAdapter() {
override fun onLoadEnd(browser: CefBrowser, frame: CefFrame, httpStatusCode: Int) {
if (frame.isMain) {
val script = """
(function() {
if (window.toggleDeltaQuery) {
return;
}
window.toggleDeltaQuery = function(isEnabled) { ${toggleDeltaQuery.inject("isEnabled")} };
})()
""".trimIndent()
browser.executeJavaScript(script, browser.url, 0)
}
}
}
}
}
8 changes: 4 additions & 4 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import com.intellij.ui.jcef.JBCefBrowser
import com.intellij.ui.jcef.JBCefBrowserBuilder
import com.intellij.ui.jcef.JBCefClient
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Component

typealias LoadHandlerGenerator = (jbCefBrowser: JBCefBrowser) -> CefLoadHandlerAdapter

object JCEFUtils {
private val jbCefPair : Pair<JBCefClient, JBCefBrowser>? = null

fun getJBCefBrowserComponentIfSupported(
fun getJBCefBrowserIfSupported(
html: String,
loadHandlerGenerators: List<LoadHandlerGenerator>,
): Component? {
): JBCefBrowser? {
if (!JBCefApp.isSupported()) {
return null
}

val (cefClient, jbCefBrowser) = getBrowser()

for (loadHandlerGenerator in loadHandlerGenerators) {
Expand All @@ -27,7 +27,7 @@ object JCEFUtils {
}
jbCefBrowser.loadHTML(html, jbCefBrowser.cefBrowser.url)

return jbCefBrowser.component
return jbCefBrowser
}

private fun getBrowser(): Pair<JBCefClient, JBCefBrowser> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ class SnykToolWindow(private val project: Project) : SimpleToolWindowPanel(false
Unit
})


project.messageBus.connect(this)
.subscribe(SnykTaskQueueListener.TASK_QUEUE_TOPIC, object : SnykTaskQueueListener {
override fun stopped() = updateActionsPresentation()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ import com.intellij.ui.treeStructure.Tree
import com.intellij.util.containers.Convertor
import com.intellij.util.ui.tree.TreeUtil
import io.snyk.plugin.Severity
import io.snyk.plugin.SnykFile
import io.snyk.plugin.cli.CliResult
import io.snyk.plugin.events.SnykCliDownloadListener
import io.snyk.plugin.events.SnykResultsFilteringListener
import io.snyk.plugin.events.SnykScanListener
import io.snyk.plugin.events.SnykScanListenerLS
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_CODE
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_IAC
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_OSS
import io.snyk.plugin.events.SnykSettingsListener
import io.snyk.plugin.events.SnykTaskQueueListener
import io.snyk.plugin.getKubernetesImageCache
Expand Down Expand Up @@ -62,6 +66,7 @@ import io.snyk.plugin.ui.toolwindow.panels.IssueDescriptionPanel
import io.snyk.plugin.ui.toolwindow.panels.SnykAuthPanel
import io.snyk.plugin.ui.toolwindow.panels.SnykErrorPanel
import io.snyk.plugin.ui.toolwindow.panels.StatePanel
import io.snyk.plugin.ui.toolwindow.panels.SummaryPanel
import io.snyk.plugin.ui.toolwindow.panels.TreePanel
import io.snyk.plugin.ui.wrapWithScrollPane
import org.jetbrains.annotations.TestOnly
Expand All @@ -70,6 +75,7 @@ import snyk.common.ProductType
import snyk.common.SnykError
import snyk.common.lsp.LanguageServerWrapper
import snyk.common.lsp.ScanIssue
import snyk.common.lsp.SnykScanParams
import snyk.common.lsp.settings.FolderConfigSettings
import snyk.container.ContainerIssuesForImage
import snyk.container.ContainerResult
Expand All @@ -94,6 +100,7 @@ class SnykToolWindowPanel(
) : JPanel(),
Disposable {
private val descriptionPanel = SimpleToolWindowPanel(true, true).apply { name = "descriptionPanel" }
private val summaryPanel = SimpleToolWindowPanel(true, true).apply { name = "summaryPanel" }
private val logger = Logger.getInstance(this::class.java)
private val rootTreeNode = ChooseBranchNode(project = project)
private val rootOssTreeNode = RootOssTreeNode(project)
Expand Down Expand Up @@ -149,6 +156,7 @@ class SnykToolWindowPanel(

createTreeAndDescriptionPanel()
chooseMainPanelToDisplay()
updateSummaryPanel()

vulnerabilitiesTree.selectionModel.addTreeSelectionListener { treeSelectionEvent ->
runAsync {
Expand All @@ -175,6 +183,33 @@ class SnykToolWindowPanel(
scanListener
}

project.messageBus
.connect(this)
.subscribe(
SnykScanListenerLS.SNYK_SCAN_TOPIC,
object : SnykScanListenerLS {
override fun onPublishDiagnostics(product: String, snykFile: SnykFile, issueList: List<ScanIssue>) {
// Refresh the tree view on receiving new diags from the Language Server
getSnykCachedResults(project)?.let {
when (product) {
PRODUCT_CODE -> it.currentSnykCodeResultsLS[snykFile] = issueList
PRODUCT_OSS -> it.currentOSSResultsLS[snykFile] = issueList
PRODUCT_IAC -> it.currentIacResultsLS[snykFile] = issueList
}
}

Tree(rootTreeNode).apply {
this.isRootVisible = pluginSettings().isDeltaFindingsEnabled()
}
}

override fun scanningSnykCodeFinished() = Unit
override fun scanningOssFinished() = Unit
override fun scanningIacFinished() = Unit
override fun scanningError(snykScan: SnykScanParams) = Unit
},
)

project.messageBus
.connect(this)
.subscribe(
Expand Down Expand Up @@ -498,7 +533,12 @@ class SnykToolWindowPanel(
removeAll()
val vulnerabilitiesSplitter = OnePixelSplitter(TOOL_WINDOW_SPLITTER_PROPORTION_KEY, 0.4f)
add(vulnerabilitiesSplitter, BorderLayout.CENTER)
vulnerabilitiesSplitter.firstComponent = TreePanel(vulnerabilitiesTree)

val treeSplitter = OnePixelSplitter(true, TOOL_TREE_SPLITTER_PROPORTION_KEY, 0.25f)
treeSplitter.firstComponent = summaryPanel
treeSplitter.secondComponent = TreePanel(vulnerabilitiesTree)

vulnerabilitiesSplitter.firstComponent = treeSplitter
vulnerabilitiesSplitter.secondComponent = descriptionPanel
}

Expand All @@ -512,6 +552,14 @@ class SnykToolWindowPanel(
}
}

private fun updateSummaryPanel() {
val summaryPanelContent = SummaryPanel(project)
summaryPanel.removeAll()
Disposer.register(this, summaryPanelContent)
summaryPanel.add(summaryPanelContent)
revalidate()
}

private fun noIssuesInAnyProductFound() =
rootOssTreeNode.childCount == 0 &&
rootSecurityIssuesTreeNode.childCount == 0 &&
Expand Down Expand Up @@ -954,6 +1002,7 @@ class SnykToolWindowPanel(
const val NO_CONTAINER_IMAGES_FOUND = " - No container images found"
const val NO_SUPPORTED_PACKAGE_MANAGER_FOUND = " - No supported package manager found"
private const val TOOL_WINDOW_SPLITTER_PROPORTION_KEY = "SNYK_TOOL_WINDOW_SPLITTER_PROPORTION"
private const val TOOL_TREE_SPLITTER_PROPORTION_KEY = "SNYK_TOOL_TREE_SPLITTER_PROPORTION"
internal const val NODE_INITIAL_STATE = -1
const val NODE_NOT_SUPPORTED_STATE = -2

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import com.intellij.util.ui.tree.TreeUtil
import io.snyk.plugin.Severity
import io.snyk.plugin.SnykFile
import io.snyk.plugin.events.SnykScanListenerLS
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_CODE
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_CONTAINER
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_IAC
import io.snyk.plugin.events.SnykScanListenerLS.Companion.PRODUCT_OSS
import io.snyk.plugin.getSnykCachedResults
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.refreshAnnotationsForOpenFiles
Expand Down Expand Up @@ -108,24 +112,24 @@ class SnykToolWindowSnykScanListenerLS(

override fun scanningError(snykScan: SnykScanParams) {
when (snykScan.product) {
"oss" -> {
PRODUCT_OSS -> {
this.rootOssIssuesTreeNode.removeAllChildren()
this.rootOssIssuesTreeNode.userObject = "$OSS_ROOT_TEXT (error)"
}

"code" -> {
PRODUCT_CODE -> {
this.rootSecurityIssuesTreeNode.removeAllChildren()
this.rootSecurityIssuesTreeNode.userObject = "$CODE_SECURITY_ROOT_TEXT (error)"
this.rootQualityIssuesTreeNode.removeAllChildren()
this.rootQualityIssuesTreeNode.userObject = "$CODE_QUALITY_ROOT_TEXT (error)"
}

"iac" -> {
PRODUCT_IAC -> {
this.rootIacIssuesTreeNode.removeAllChildren()
this.rootOssIssuesTreeNode.userObject = "$IAC_ROOT_TEXT (error)"
}

"container" -> {
PRODUCT_CONTAINER -> {
// TODO implement
}
}
Expand Down
Loading

0 comments on commit c8ccdec

Please sign in to comment.