Skip to content

Commit

Permalink
Merge pull request #492 from snyk/feat/render-ignore-settings
Browse files Browse the repository at this point in the history
feat: render ignore settings [IDE-173]
  • Loading branch information
cat2608 authored Mar 21, 2024
2 parents 73d18d2 + b8dd463 commit ea56d7b
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 11 deletions.
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Snyk Security Changelog

## [2.7.11]
### Added
- Consistent ignores for Snyk Code behind a feature flag.
- Render ignores settings behind a feature flag.

## [2.7.10]
### Fixed
- (LS Preview) Fix content root handling for Snyk Code scans
Expand All @@ -10,10 +15,6 @@
- (LS Preview) Fix long-running UI operation to run outside of UI thread
- Remove duplicated annotations in Snyk Code

## [2.8.0]
### Added
- Consistent ignores for Snyk Code behind a feature flag.

## [2.7.8]
### Fixed
- (LS Preview) UI freezes and initialization errors caused by CodeVision and Code annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import java.util.UUID
)
class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplicationSettingsStateService> {

var isGlobalIgnoresFeatureEnabled = false
var cliBaseDownloadURL: String = "https://static.snyk.io"
var cliPath: String = getPluginPath() + separator + Platform.current().snykWrapperFileName
var manageBinariesAutomatically: Boolean = true
Expand Down Expand Up @@ -61,6 +62,9 @@ class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplica
var highSeverityEnabled = true
var criticalSeverityEnabled = true

var openIssuesEnabled = true
var ignoredIssuesEnabled = false

var treeFiltering = TreeFiltering()

var lastCheckDate: Date? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.snyk.plugin.getSnykProjectSettingsService
import io.snyk.plugin.getSnykToolWindowPanel
import io.snyk.plugin.getSyncPublisher
import io.snyk.plugin.isProjectSettingsAvailable
import io.snyk.plugin.isSnykCodeLSEnabled
import io.snyk.plugin.isUrlValid
import io.snyk.plugin.net.RetrofitClientFactory
import io.snyk.plugin.pluginSettings
Expand Down Expand Up @@ -98,8 +99,16 @@ class SnykProjectSettingsConfigurable(val project: Project) : SearchableConfigur
snykProjectSettingsService?.additionalParameters = snykSettingsDialog.getAdditionalParameters()
}

val params = DidChangeConfigurationParams(LanguageServerWrapper.getInstance().getSettings())
LanguageServerWrapper.getInstance().languageServer.workspaceService.didChangeConfiguration(params)
val wrapper = LanguageServerWrapper.getInstance()
val params = DidChangeConfigurationParams(wrapper.getSettings())
wrapper.languageServer.workspaceService.didChangeConfiguration(params)

if (isSnykCodeLSEnabled()) {
runBackgroundableTask("Updating Snyk Code settings", project, true) {
settingsStateService.isGlobalIgnoresFeatureEnabled =
wrapper.getFeatureFlagStatus("snykCodeConsistentIgnores")
}
}

if (rescanNeeded) {
getSnykToolWindowPanel(project)?.cleanUiAndCaches()
Expand Down
44 changes: 42 additions & 2 deletions src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import io.snyk.plugin.getSnykCliDownloaderService
import io.snyk.plugin.isProjectSettingsAvailable
import io.snyk.plugin.isSnykCodeLSEnabled
import io.snyk.plugin.isUrlValid
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.services.SnykApplicationSettingsStateService
import io.snyk.plugin.settings.SnykProjectSettingsConfigurable
import io.snyk.plugin.ui.settings.IssueViewOptionsPanel
import io.snyk.plugin.ui.settings.ScanTypesPanel
import io.snyk.plugin.ui.settings.SeveritiesEnablementPanel
import snyk.SnykBundle
Expand Down Expand Up @@ -76,6 +78,8 @@ class SnykSettingsDialog(
private val codeAlertPanel = scanTypesPanelOuter.codeAlertPanel
private val scanTypesPanel = scanTypesPanelOuter.panel

private val issueViewOptionsPanel = IssueViewOptionsPanel(project).panel

private val severityEnablementPanel = SeveritiesEnablementPanel().panel

private val manageBinariesAutomatically: JCheckBox = JCheckBox()
Expand Down Expand Up @@ -274,12 +278,48 @@ class SnykSettingsDialog(

/** Products and Severities selection ------------------ */

val productAndSeveritiesPanel = JPanel(UIGridLayoutManager(5, 4, Insets(0, 0, 0, 0), 30, -1))
if (pluginSettings().isGlobalIgnoresFeatureEnabled) {
val issueViewPanel = JPanel(UIGridLayoutManager(3, 2, Insets(0, 0, 0, 0), 30, -1))
issueViewPanel.border = IdeBorderFactory.createTitledBorder("Issue view options")

val issueViewLabel = JLabel("Show the following issues:")
issueViewPanel.add(
issueViewLabel,
baseGridConstraintsAnchorWest(
row = 0,
indent = 0
)
)

rootPanel.add(
issueViewPanel,
baseGridConstraints(
row = 1,
anchor = UIGridConstraints.ANCHOR_NORTHWEST,
fill = UIGridConstraints.FILL_HORIZONTAL,
hSizePolicy = UIGridConstraints.SIZEPOLICY_CAN_SHRINK or UIGridConstraints.SIZEPOLICY_CAN_GROW,
indent = 0
)
)

issueViewPanel.add(
this.issueViewOptionsPanel,
baseGridConstraints(
row = 1,
anchor = UIGridConstraints.ANCHOR_NORTHWEST,
fill = UIGridConstraints.FILL_NONE,
hSizePolicy = UIGridConstraints.SIZEPOLICY_CAN_SHRINK or UIGridConstraints.SIZEPOLICY_CAN_GROW,
vSizePolicy = UIGridConstraints.SIZEPOLICY_CAN_SHRINK or UIGridConstraints.SIZEPOLICY_CAN_GROW,
indent = 0
)
)
}
val productAndSeveritiesPanel = JPanel(UIGridLayoutManager(6, 4, Insets(0, 0, 0, 0), 30, -1))

rootPanel.add(
productAndSeveritiesPanel,
baseGridConstraints(
row = 1,
row = 2,
anchor = UIGridConstraints.ANCHOR_NORTHWEST,
fill = UIGridConstraints.FILL_HORIZONTAL,
hSizePolicy = UIGridConstraints.SIZEPOLICY_CAN_SHRINK or UIGridConstraints.SIZEPOLICY_CAN_GROW,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.snyk.plugin.ui.settings

import com.intellij.openapi.project.Project
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.layout.panel
import com.intellij.util.ui.JBUI
import io.snyk.plugin.getSnykTaskQueueService
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.ui.SnykBalloonNotificationHelper

class IssueViewOptionsPanel(
private val project: Project,
) {
private val settings
get() = pluginSettings()

private var currentOpenIssuesEnabled = settings.openIssuesEnabled
private var currentIgnoredIssuesEnabled = settings.ignoredIssuesEnabled

val panel = panel {
row {
cell {
checkBox(
text = "Open issues",
getter = { settings.openIssuesEnabled },
setter = { settings.openIssuesEnabled = it }
).component.apply {
this.addItemListener {
if (canOptionChange(this, currentOpenIssuesEnabled)) {
currentOpenIssuesEnabled = this.isSelected
settings.openIssuesEnabled = currentOpenIssuesEnabled
getSnykTaskQueueService(project)?.scan()
}
}
name = "Open issues"
}
}
}
row {
cell {
checkBox(
text = "Ignored issues",
getter = { settings.ignoredIssuesEnabled },
setter = { settings.ignoredIssuesEnabled = it }
).component.apply {
this.addItemListener {
if (canOptionChange(this, currentIgnoredIssuesEnabled)) {
currentIgnoredIssuesEnabled = this.isSelected
settings.ignoredIssuesEnabled =currentIgnoredIssuesEnabled
getSnykTaskQueueService(project)?.scan()
}
}
name = "Ignored issues"
}
}
}
}.apply {
name = "issueViewPanel"
border = JBUI.Borders.empty(2)
}

private fun canOptionChange(component: JBCheckBox, wasEnabled: Boolean): Boolean {
val onlyOneEnabled = arrayOf(
currentOpenIssuesEnabled,
currentIgnoredIssuesEnabled,
).count { it } == 1

if (onlyOneEnabled && wasEnabled) {
component.isSelected = true
SnykBalloonNotificationHelper.showWarnBalloonForComponent(
"At least one option should be selected",
component
)
return false
}
return true
}
}
36 changes: 35 additions & 1 deletion src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class LanguageServerWrapper(
val snykLanguageClient = SnykLanguageClient()
languageClient = snykLanguageClient
val logLevel = if (snykLanguageClient.logger.isDebugEnabled) "debug" else "info"
val cmd = listOf(lsPath, "language-server", "-l", logLevel, "-f", "/tmp/snyk-ls.log")
val cmd = listOf(lsPath, "language-server", "-l", logLevel)

val processBuilder = ProcessBuilder(cmd)
pluginSettings().token?.let { EnvironmentHelper.updateEnvironment(processBuilder.environment(), it) }
Expand All @@ -118,6 +118,9 @@ class LanguageServerWrapper(
} finally {
isInitializing = false
}

// update feature flags
pluginSettings().isGlobalIgnoresFeatureEnabled = getFeatureFlagStatus("snykCodeConsistentIgnores")
}

fun shutdown(): Future<*> {
Expand Down Expand Up @@ -250,6 +253,37 @@ class LanguageServerWrapper(
}
}

fun getFeatureFlagStatus(featureFlag: String): Boolean {
ensureLanguageServerInitialized()
if (!isSnykCodeLSEnabled()) {
return false
}

try {
val param = ExecuteCommandParams()
param.command = "snyk.getFeatureFlagStatus"
param.arguments = listOf(featureFlag)
val result = languageServer.workspaceService.executeCommand(param).get(5, TimeUnit.SECONDS)

val resultMap = result as? Map<*, *>
val ok = resultMap?.get("ok") as? Boolean ?: false
val userMessage = resultMap?.get("userMessage") as? String ?: "No message provided"


if (ok) {
logger.info("Feature flag $featureFlag is enabled.")
return true
} else {
logger.warn("Feature flag $featureFlag is disabled. Message: $userMessage")
return false
}

} catch (e: Exception) {
logger.error("Error while checking feature flag: ${e.message}", e)
return false
}
}

private fun sendFolderScanCommand(folder: String) {
try {
val param = ExecuteCommandParams()
Expand Down
13 changes: 12 additions & 1 deletion src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,18 @@ class SnykLanguageClient : LanguageClient {
private fun getSnykCodeResult(project: Project, snykScan: SnykScanParams): Map<SnykCodeFile, List<ScanIssue>> {
check(snykScan.product == "code") { "Expected Snyk Code scan result" }
if (snykScan.issues.isNullOrEmpty()) return emptyMap()
val map = snykScan.issues

val pluginSettings = pluginSettings()
val includeIgnoredIssues = pluginSettings.ignoredIssuesEnabled
val includeOpenedIssues = pluginSettings.openIssuesEnabled

val processedIssues = if (pluginSettings.isGlobalIgnoresFeatureEnabled) { //
snykScan.issues.filter { it.isVisible(includeOpenedIssues, includeIgnoredIssues) }
} else {
snykScan.issues
}

val map = processedIssues
.groupBy { it.filePath }
.mapNotNull { (file, issues) -> SnykCodeFile(project, file.toVirtualFile()) to issues.sorted() }
.map {
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/snyk/common/lsp/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ data class ScanIssue(
}
}

fun isVisible(includeOpenedIssues: Boolean, includeIgnoredIssues: Boolean): Boolean {
if (includeIgnoredIssues && includeOpenedIssues){
return true
}
if (includeIgnoredIssues) {
return this.isIgnored == true
}
if (includeOpenedIssues){
return this.isIgnored != true
}
return false
}

override fun compareTo(other: ScanIssue): Int {
this.filePath.compareTo(other.filePath).let { if (it != 0) it else 0 }
this.range.start.line.compareTo(other.range.start.line).let { if (it != 0) it else 0 }
Expand Down
24 changes: 23 additions & 1 deletion src/test/java/snyk/common/lsp/LanguageServerWrapperTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.eclipse.lsp4j.InitializeParams
import org.eclipse.lsp4j.services.LanguageServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import snyk.pluginInfo
import snyk.trust.WorkspaceTrustService
Expand All @@ -40,7 +41,6 @@ class LanguageServerWrapperTest {
unmockkAll()
mockkStatic("io.snyk.plugin.UtilsKt")
mockkStatic(ApplicationManager::class)
mockkStatic(ApplicationManager::class)
every { ApplicationManager.getApplication() } returns applicationMock
every { applicationMock.getService(WorkspaceTrustService::class.java) } returns trustServiceMock

Expand Down Expand Up @@ -111,4 +111,26 @@ class LanguageServerWrapperTest {
assertEquals("${settings.ignoreUnknownCA}", actual.insecure)
assertEquals(getCliFile().absolutePath, actual.cliPath)
}

@Test
@Ignore
fun `sendFeatureFlagCommand should return true if feature flag is enabled`() {
// Arrange
cut.languageClient = mockk(relaxed = true)
val processMock = mockk<Process>(relaxed = true)
cut.process = processMock
val featureFlag = "testFeatureFlag"
every { processMock.info().startInstant().isPresent } returns true
every { processMock.isAlive } returns true
every {
lsMock.workspaceService.executeCommand(any<ExecuteCommandParams>())
} returns CompletableFuture.completedFuture(mapOf("ok" to true))

// Act
val result = cut.getFeatureFlagStatus(featureFlag)

// Assert
assertEquals(true, result)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.replaceService
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.unmockkAll
Expand All @@ -18,6 +19,7 @@ import io.snyk.plugin.resetSettings
import io.snyk.plugin.services.download.LatestReleaseInfo
import io.snyk.plugin.services.download.SnykCliDownloaderService
import org.awaitility.Awaitility.await
import snyk.common.lsp.LanguageServerWrapper
import snyk.oss.OssResult
import snyk.oss.OssService
import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded
Expand Down Expand Up @@ -46,6 +48,12 @@ class SnykControllerImplTest : LightPlatformTestCase() {
every { getSnykCliDownloaderService() } returns downloaderServiceMock
every { downloaderServiceMock.isFourDaysPassedSinceLastCheck() } returns false
every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any()) } returns true

val languageServerWrapper = mockk<LanguageServerWrapper>(relaxed = true)
mockkObject(LanguageServerWrapper.Companion)
every { LanguageServerWrapper.getInstance() } returns languageServerWrapper
every { languageServerWrapper.getFeatureFlagStatus(any()) } returns true

}

override fun tearDown() {
Expand Down

0 comments on commit ea56d7b

Please sign in to comment.