Skip to content

Commit

Permalink
fix: Container node UI bugs [ROAD-648] [ROAD-647] [ROAD-749] (#312)
Browse files Browse the repository at this point in the history
fix: Container node still showing last results even if disabled
fix: Container node should handle case if no images in project found
fix: Container: invalid token shows error and doesn’t redirect to Auth panel
  • Loading branch information
ArtsiomCh authored Feb 21, 2022
1 parent 0ee1abf commit 6fcdd5e
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 25 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
- Snyk Open Source: added quickfix capability for package managers
- Snyk Code: Annotations if plugins for the language are installed

### Fixes
### Fixed

- Fix Container: invalid token shows error and does not redirect to Auth panel
- Fix Container: should handle case if no images in project found
- Fix Container: node still showing last results even if disabled
- improved Snyk Container image parsing in K8S files

## [2.4.17]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import io.mockk.unmockkAll
import io.mockk.verify
import io.snyk.plugin.Severity
import io.snyk.plugin.cli.ConsoleCommandRunner
import io.snyk.plugin.events.SnykResultsFilteringListener
import io.snyk.plugin.getCliFile
import io.snyk.plugin.getContainerService
import io.snyk.plugin.getIacService
import io.snyk.plugin.getKubernetesImageCache
import io.snyk.plugin.getSyncPublisher
import io.snyk.plugin.isCliInstalled
import io.snyk.plugin.isOssRunning
import io.snyk.plugin.pluginSettings
Expand Down Expand Up @@ -50,6 +52,7 @@ import snyk.iac.ui.toolwindow.IacFileTreeNode
import snyk.iac.ui.toolwindow.IacIssueTreeNode
import javax.swing.JButton
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTextArea
import javax.swing.tree.TreeNode

Expand Down Expand Up @@ -163,6 +166,8 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() {
fun `test should not display error when no OSS supported file found`() {
mockkObject(SnykBalloonNotificationHelper)

pluginSettings().token = "fake_token"
pluginSettings().pluginFirstRun = false
val toolWindowPanel = project.service<SnykToolWindowPanel>()

val snykError = SnykError("Could not detect supported target files in", project.basePath.toString())
Expand Down Expand Up @@ -207,6 +212,111 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() {
)
}

@Test
fun `test should display NO_CONTAINER_IMAGES_FOUND after scan when no Container images found`() {
mockkObject(SnykBalloonNotificationHelper)

pluginSettings().token = "fake_token"
pluginSettings().pluginFirstRun = false
val toolWindowPanel = project.service<SnykToolWindowPanel>()

val snykError = ContainerService.NO_IMAGES_TO_SCAN_ERROR
val snykErrorControl = SnykError("control", project.basePath.toString())

toolWindowPanel.snykScanListener.scanningContainerError(snykErrorControl)
toolWindowPanel.snykScanListener.scanningContainerError(snykError)
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()

verify(exactly = 1, timeout = 2000) {
SnykBalloonNotificationHelper.showError(any(), project)
}

assertTrue(toolWindowPanel.currentContainerError == null)
assertEquals(
SnykToolWindowPanel.CONTAINER_ROOT_TEXT + SnykToolWindowPanel.NO_CONTAINER_IMAGES_FOUND,
toolWindowPanel.getRootContainerIssuesTreeNode().userObject
)
}

@Test
fun `test OSS scan should redirect to Auth panel if token is invalid`() {
mockkObject(SnykBalloonNotificationHelper)
pluginSettings().token = "fake_token"
pluginSettings().pluginFirstRun = false
val toolWindowPanel = project.service<SnykToolWindowPanel>()
val snykErrorControl = SnykError("control", project.basePath.toString())
val snykError = SnykError("Authentication failed. Please check the API token on ", project.basePath.toString())

toolWindowPanel.snykScanListener.scanningOssError(snykErrorControl)
toolWindowPanel.snykScanListener.scanningOssError(snykError)
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()

verify(exactly = 1, timeout = 2000) {
SnykBalloonNotificationHelper.showError(snykErrorControl.message, project)
}
verify(exactly = 1, timeout = 2000) {
SnykBalloonNotificationHelper.showError(snykError.message, project)
}
assertTrue(toolWindowPanel.currentOssError == null)
assertEquals(
SnykToolWindowPanel.OSS_ROOT_TEXT,
toolWindowPanel.getRootOssIssuesTreeNode().userObject
)
val authPanel = UIComponentFinder.getComponentByName(toolWindowPanel, JPanel::class, "authPanel")
assertNotNull(authPanel)
}

@Test
fun `test Container scan should redirect to Auth panel if token is invalid`() {
mockkObject(SnykBalloonNotificationHelper)
pluginSettings().token = "fake_token"
pluginSettings().pluginFirstRun = false
val toolWindowPanel = project.service<SnykToolWindowPanel>()
val snykErrorControl = SnykError("control", project.basePath.toString())
val snykError = SnykError("Authentication failed. Please check the API token on ", project.basePath.toString())

toolWindowPanel.snykScanListener.scanningContainerError(snykErrorControl)
toolWindowPanel.snykScanListener.scanningContainerError(snykError)
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()

verify(exactly = 1, timeout = 2000) {
SnykBalloonNotificationHelper.showError(snykErrorControl.message, project)
}
verify(exactly = 1, timeout = 2000) {
SnykBalloonNotificationHelper.showError(snykError.message, project)
}
assertTrue(toolWindowPanel.currentContainerError == null)
assertEquals(
SnykToolWindowPanel.CONTAINER_ROOT_TEXT,
toolWindowPanel.getRootContainerIssuesTreeNode().userObject
)
val authPanel = UIComponentFinder.getComponentByName(toolWindowPanel, JPanel::class, "authPanel")
assertNotNull(authPanel)
}

@Test
fun `test Container node should show no child when disabled`() {
mockkObject(SnykBalloonNotificationHelper)
pluginSettings().token = "fake_token"
pluginSettings().pluginFirstRun = false
val toolWindowPanel = project.service<SnykToolWindowPanel>()
val rootContainerNode = toolWindowPanel.getRootContainerIssuesTreeNode()

// assert child shown when Container results exist
toolWindowPanel.snykScanListener.scanningContainerFinished(fakeContainerResult)
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()

assertEquals(fakeContainerResult, toolWindowPanel.currentContainerResult)
assertTrue(rootContainerNode.childCount > 0)

// assert no child shown when Container node disabled
pluginSettings().containerScanEnabled = false
getSyncPublisher(project, SnykResultsFilteringListener.SNYK_FILTERING_TOPIC)?.filtersChanged()
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()

assertTrue(rootContainerNode.childCount == 0)
}

@Test
fun `test should display '(error)' in OSS root tree node when result is empty and error occurs`() {
val toolWindowPanel = project.service<SnykToolWindowPanel>()
Expand Down
56 changes: 35 additions & 21 deletions src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import snyk.common.SnykError
import snyk.container.ContainerIssue
import snyk.container.ContainerIssuesForImage
import snyk.container.ContainerResult
import snyk.container.ContainerService
import snyk.container.KubernetesImageCache
import snyk.container.ui.BaseImageRemediationDetailPanel
import snyk.container.ui.ContainerImageTreeNode
Expand Down Expand Up @@ -235,7 +236,6 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {

override fun scanningOssError(snykError: SnykError) {
currentOssResults = null
var cleanupToolwindow = true
var ossResultsCount: Int? = null
ApplicationManager.getApplication().invokeLater {
if (snykError.message.startsWith(NO_OSS_FILES)) {
Expand All @@ -245,17 +245,14 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
SnykBalloonNotificationHelper.showError(snykError.message, project)
if (snykError.message.startsWith("Authentication failed. Please check the API token on ")) {
pluginSettings().token = null
displayAuthPanel()
cleanupToolwindow = false
currentOssError = null
} else {
currentOssError = snykError
}
}
if (cleanupToolwindow) {
removeAllChildren(listOf(rootOssTreeNode))
updateTreeRootNodesPresentation(ossResultsCount)
displayEmptyDescription()
}
removeAllChildren(listOf(rootOssTreeNode))
updateTreeRootNodesPresentation(ossResultsCount = ossResultsCount)
chooseMainPanelToDisplay()
refreshAnnotationsForOpenFiles(project)
}
service<SnykAnalyticsService>().logAnalysisIsReady(
Expand Down Expand Up @@ -294,12 +291,23 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {

override fun scanningContainerError(snykError: SnykError) {
currentContainerResult = null
var containerResultsCount: Int? = null
ApplicationManager.getApplication().invokeLater {
SnykBalloonNotificationHelper.showError(snykError.message, project)
currentContainerError = snykError
if (snykError == ContainerService.NO_IMAGES_TO_SCAN_ERROR) {
currentContainerError = null
containerResultsCount = NODE_NOT_SUPPORTED_STATE
} else {
SnykBalloonNotificationHelper.showError(snykError.message, project)
if (snykError.message.startsWith("Authentication failed. Please check the API token on ")) {
pluginSettings().token = null
currentContainerError = null
} else {
currentContainerError = snykError
}
}
removeAllChildren(listOf(rootContainerIssuesTreeNode))
updateTreeRootNodesPresentation()
displayEmptyDescription()
updateTreeRootNodesPresentation(containerResultsCount = containerResultsCount)
chooseMainPanelToDisplay()
refreshAnnotationsForOpenFiles(project)
}
service<SnykAnalyticsService>().logAnalysisIsReady(
Expand Down Expand Up @@ -609,8 +617,13 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
}

private fun removeAllChildren(
rootNodesToUpdate: List<DefaultMutableTreeNode> =
listOf(rootOssTreeNode, rootSecurityIssuesTreeNode, rootQualityIssuesTreeNode, rootIacIssuesTreeNode)
rootNodesToUpdate: List<DefaultMutableTreeNode> = listOf(
rootOssTreeNode,
rootSecurityIssuesTreeNode,
rootQualityIssuesTreeNode,
rootIacIssuesTreeNode,
rootContainerIssuesTreeNode
)
) {
rootNodesToUpdate.forEach {
it.removeAllChildren()
Expand Down Expand Up @@ -646,13 +659,12 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
}

fun displayAuthPanel() {
removeAll()
val splitter = OnePixelSplitter(TOOL_WINDOW_SPLITTER_PROPORTION_KEY, 0.4f)
add(splitter, BorderLayout.CENTER)
splitter.firstComponent = TreePanel(vulnerabilitiesTree)
displayTreeAndDescriptionPanels()
doCleanUi()
descriptionPanel.removeAll()
val authPanel = SnykAuthPanel(project)
Disposer.register(this, authPanel)
splitter.secondComponent = authPanel
descriptionPanel.add(CenterOneComponentPanel(authPanel), BorderLayout.CENTER)
revalidate()

service<SnykAnalyticsService>().logWelcomeIsViewed(
Expand Down Expand Up @@ -790,6 +802,7 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
count == NODE_INITIAL_STATE -> ""
count == 0 -> NO_ISSUES_FOUND_TEXT
count > 0 -> " - $count vulnerabilit${if (count > 1) "ies" else "y"}$addHMLPostfix"
count == NODE_NOT_SUPPORTED_STATE -> NO_CONTAINER_IMAGES_FOUND
else -> throw IllegalStateException("ResultsCount is meaningful")
}
}
Expand Down Expand Up @@ -1150,8 +1163,9 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
const val NO_ISSUES_FOUND_TEXT = " - No issues found"
const val NO_OSS_FILES = "Could not detect supported target files in"
const val NO_IAC_FILES = "Could not find any valid IaC files"
const val NO_SUPPORTED_IAC_FILES_FOUND = "- No supported IaC files found"
const val NO_SUPPORTED_PACKAGE_MANAGER_FOUND = "- No supported package manager found"
const val NO_SUPPORTED_IAC_FILES_FOUND = " - No supported IaC files found"
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 NODE_INITIAL_STATE = -1
private const val NODE_NOT_SUPPORTED_STATE = -2
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/io/snyk/plugin/ui/toolwindow/TreePanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class TreePanel(tree: Tree) : SimpleToolWindowPanel(true, true) {
private val actionManager = ActionManager.getInstance()

init {
name = "treePanel"
val severityToolbarPanel = JPanel(BorderLayout())
severityToolbarPanel.add(JLabel("Severity: "), BorderLayout.WEST)
severityToolbarPanel.add(getSeverityToolbar().component, BorderLayout.CENTER)
Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/snyk/container/ContainerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ContainerService(project: Project) : CliAdapter<ContainerResult>(
val imageNames = images.map { it.image }
LOG.debug("container scan requested for ${imageNames.size} images: $imageNames")
if (imageNames.isEmpty()) {
return ContainerResult(emptyList(), null)
return ContainerResult(emptyList(), NO_IMAGES_TO_SCAN_ERROR)
}
val tempResult = execute(commands + imageNames)
if (!tempResult.isSuccessful()) {
Expand Down Expand Up @@ -142,4 +142,8 @@ class ContainerService(project: Project) : CliAdapter<ContainerResult>(
jsonStr.contains("\"vulnerabilities\":") && !jsonStr.contains("\"error\":")

override fun buildExtraOptions(): List<String> = listOf("--json")

companion object {
val NO_IMAGES_TO_SCAN_ERROR = SnykError("", "")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ class SnykToolWindowPanelTest : LightPlatform4TestCase() {

cut = SnykToolWindowPanel(project)

val vulnerabilityTree = cut.vulnerabilitiesTree
val authPanel = UIComponentFinder.getJPanelByName(cut, "authPanel")
assertNotNull(authPanel)
assertEquals(findOnePixelSplitter(vulnerabilityTree), authPanel!!.parent)
val treePanel = UIComponentFinder.getJPanelByName(cut, "treePanel")
assertNotNull(treePanel)
}

@Test
Expand Down

0 comments on commit 6fcdd5e

Please sign in to comment.