From 6fd424c34d5c1dde74ae6ce8f17cbe805f6a2f88 Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Mon, 30 Sep 2024 12:23:00 +0200 Subject: [PATCH] feat: make progresses cancelable, cleanup --- src/main/kotlin/io/snyk/plugin/Utils.kt | 14 +--- .../plugin/events/SnykTaskQueueListener.kt | 7 +- .../plugin/services/SnykTaskQueueService.kt | 73 ++----------------- .../plugin/ui/toolwindow/SnykToolWindow.kt | 7 +- .../ui/toolwindow/SnykToolWindowPanel.kt | 33 +++++---- .../SnykToolWindowSnykScanListenerLS.kt | 37 ++++------ .../common/lsp/progress/ProgressManager.kt | 69 +++++++++--------- .../services/SnykTaskQueueServiceTest.kt | 1 - ...uggestionDescriptionPanelFromLSCodeTest.kt | 2 +- ...SuggestionDescriptionPanelFromLSOSSTest.kt | 3 +- 10 files changed, 83 insertions(+), 163 deletions(-) diff --git a/src/main/kotlin/io/snyk/plugin/Utils.kt b/src/main/kotlin/io/snyk/plugin/Utils.kt index 40203c24e..b82f2ef41 100644 --- a/src/main/kotlin/io/snyk/plugin/Utils.kt +++ b/src/main/kotlin/io/snyk/plugin/Utils.kt @@ -158,7 +158,7 @@ fun isUrlValid(url: String?): Boolean { } fun isOssRunning(project: Project): Boolean { - return isProductScanRunning(project, ProductType.OSS, getSnykTaskQueueService(project)?.ossScanProgressIndicator) + return isProductScanRunning(project, ProductType.OSS) } private fun isProductScanRunning(project: Project, productType: ProductType): Boolean { @@ -178,16 +178,6 @@ private fun isProductScanRunning( (progressIndicator != null && progressIndicator.isRunning && !progressIndicator.isCanceled) } -fun cancelOssIndicator(project: Project) { - val indicator = getSnykTaskQueueService(project)?.ossScanProgressIndicator - indicator?.cancel() -} - -fun cancelIacIndicator(project: Project) { - val indicator = getSnykTaskQueueService(project)?.iacScanProgressIndicator - indicator?.cancel() -} - fun isSnykCodeRunning(project: Project): Boolean { return isProductScanRunning(project, ProductType.CODE_SECURITY) || isProductScanRunning( project, @@ -196,7 +186,7 @@ fun isSnykCodeRunning(project: Project): Boolean { } fun isIacRunning(project: Project): Boolean { - return isProductScanRunning(project, ProductType.IAC, getSnykTaskQueueService(project)?.iacScanProgressIndicator) + return isProductScanRunning(project, ProductType.IAC) } fun isContainerRunning(project: Project): Boolean { diff --git a/src/main/kotlin/io/snyk/plugin/events/SnykTaskQueueListener.kt b/src/main/kotlin/io/snyk/plugin/events/SnykTaskQueueListener.kt index 1d1954f07..f554d082d 100644 --- a/src/main/kotlin/io/snyk/plugin/events/SnykTaskQueueListener.kt +++ b/src/main/kotlin/io/snyk/plugin/events/SnykTaskQueueListener.kt @@ -8,10 +8,5 @@ interface SnykTaskQueueListener { Topic.create("Snyk Task Queue", SnykTaskQueueListener::class.java) } - fun stopped( - wasOssRunning: Boolean = false, - wasSnykCodeRunning: Boolean = false, - wasIacRunning: Boolean = false, - wasContainerRunning: Boolean = false - ) + fun stopped() } diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index baeaa08e1..534d2b22d 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -1,6 +1,5 @@ package io.snyk.plugin.services -import ai.grazie.nlp.langs.Language import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger @@ -9,38 +8,29 @@ import com.intellij.openapi.progress.BackgroundTaskQueue import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project -import io.snyk.plugin.cancelOssIndicator import io.snyk.plugin.events.SnykCliDownloadListener import io.snyk.plugin.events.SnykScanListener import io.snyk.plugin.events.SnykSettingsListener import io.snyk.plugin.events.SnykTaskQueueListener import io.snyk.plugin.getContainerService -import io.snyk.plugin.getIacService import io.snyk.plugin.getSnykCachedResults import io.snyk.plugin.getSnykCliDownloaderService import io.snyk.plugin.getSnykToolWindowPanel import io.snyk.plugin.getSyncPublisher import io.snyk.plugin.isCliDownloading import io.snyk.plugin.isCliInstalled -import io.snyk.plugin.isContainerRunning -import io.snyk.plugin.isIacRunning -import io.snyk.plugin.isOssRunning -import io.snyk.plugin.isSnykCodeRunning import io.snyk.plugin.pluginSettings import io.snyk.plugin.refreshAnnotationsForOpenFiles import io.snyk.plugin.ui.SnykBalloonNotificationHelper import org.jetbrains.annotations.TestOnly -import snyk.common.SnykError import snyk.common.lsp.LanguageServerWrapper -import snyk.common.lsp.SnykLanguageClient -import snyk.common.lsp.progress.ProgressManager +import snyk.common.lsp.ScanState import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded @Service(Service.Level.PROJECT) class SnykTaskQueueService(val project: Project) { private val logger = logger() private val taskQueue = BackgroundTaskQueue(project, "Snyk") - private val taskQueueIac = BackgroundTaskQueue(project, "Snyk: Iac") private val taskQueueContainer = BackgroundTaskQueue(project, "Snyk: Container") private val settings @@ -55,12 +45,6 @@ class SnykTaskQueueService(val project: Project) { private val taskQueuePublisher get() = getSyncPublisher(project, SnykTaskQueueListener.TASK_QUEUE_TOPIC) - var ossScanProgressIndicator: ProgressIndicator? = null - private set - - var iacScanProgressIndicator: ProgressIndicator? = null - private set - var containerScanProgressIndicator: ProgressIndicator? = null private set @@ -94,7 +78,7 @@ class SnykTaskQueueService(val project: Project) { } fun scan() { - taskQueue.run(object : Task.Backgroundable(project, "Snyk: initializing...", true) { + taskQueue.run(object : Task.Backgroundable(project, "Snyk: triggering scan", true) { override fun run(indicator: ProgressIndicator) { if (!confirmScanningAndSetWorkspaceTrustedStateIfNeeded(project)) return @@ -141,7 +125,7 @@ class SnykTaskQueueService(val project: Project) { if (indicator.isCanceled) { logger.debug("cancel container scan") - taskQueuePublisher?.stopped(wasContainerRunning = true) + taskQueuePublisher?.stopped() } else { if (containerResult.isSuccessful()) { logger.debug("Container result: ->") @@ -159,49 +143,6 @@ class SnykTaskQueueService(val project: Project) { }) } - private fun scheduleIacScan() { - taskQueueIac.run(object : Task.Backgroundable(project, "Snyk Infrastructure as Code is scanning", true) { - override fun run(indicator: ProgressIndicator) { - if (!isCliInstalled()) return - val snykCachedResults = getSnykCachedResults(project) ?: return - if (snykCachedResults.currentIacResult?.iacScanNeeded == false) return - logger.debug("Starting IaC scan") - iacScanProgressIndicator = indicator - scanPublisher?.scanningStarted() - - snykCachedResults.currentIacResult = null - val iacResult = try { - getIacService(project)?.scan() - } finally { - iacScanProgressIndicator = null - } - if (iacResult == null || project.isDisposed) return - - if (indicator.isCanceled) { - logger.debug("cancel IaC scan") - taskQueuePublisher?.stopped(wasIacRunning = true) - } else { - if (iacResult.isSuccessful()) { - logger.debug("IaC result: ->") - iacResult.allCliIssues?.forEach { - logger.debug(" ${it.targetFile}, ${it.infrastructureAsCodeIssues.size} issues") - } - scanPublisher?.scanningIacFinished(iacResult) - } else { - val error = iacResult.getFirstError() - if (error == null) { - SnykError("unknown IaC error", project.basePath ?: "") - } else { - scanPublisher?.scanningIacError(error) - } - } - } - logger.debug("IaC scan completed") - refreshAnnotationsForOpenFiles(project) - } - }) - } - fun downloadLatestRelease(force: Boolean = false) { // abort even before submitting a task if (project.isDisposed || ApplicationManager.getApplication().isDisposed) return @@ -236,18 +177,14 @@ class SnykTaskQueueService(val project: Project) { fun stopScan() { val languageServerWrapper = LanguageServerWrapper.getInstance() - val wasOssRunning = isOssRunning(project) - val wasSnykCodeRunning = isSnykCodeRunning(project) - val wasIacRunning = isIacRunning(project) if (languageServerWrapper.isInitialized) { languageServerWrapper.languageClient.progressManager.cancelProgresses() + ScanState.scanInProgress.clear() } - val wasContainerRunning = containerScanProgressIndicator?.isRunning == true containerScanProgressIndicator?.cancel() - - taskQueuePublisher?.stopped(wasOssRunning, wasSnykCodeRunning, wasIacRunning, wasContainerRunning) + taskQueuePublisher?.stopped() } companion object { diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt index f9ab24016..18409b502 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt @@ -106,12 +106,7 @@ class SnykToolWindow(private val project: Project) : SimpleToolWindowPanel(false project.messageBus.connect(this) .subscribe(SnykTaskQueueListener.TASK_QUEUE_TOPIC, object : SnykTaskQueueListener { - override fun stopped( - wasOssRunning: Boolean, - wasSnykCodeRunning: Boolean, - wasIacRunning: Boolean, - wasContainerRunning: Boolean - ) = updateActionsPresentation() + override fun stopped() = updateActionsPresentation() }) } diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt index 9544cf91f..0daecbf71 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt @@ -269,10 +269,16 @@ class SnykToolWindowPanel( SnykResultsFilteringListener.SNYK_FILTERING_TOPIC, object : SnykResultsFilteringListener { override fun filtersChanged() { - val codeResultsLS = + val codeSecurityResultsLS = getSnykCachedResultsForProduct(project, ProductType.CODE_SECURITY) ?: return ApplicationManager.getApplication().invokeLater { - scanListenerLS.displaySnykCodeResults(codeResultsLS) + scanListenerLS.displaySnykCodeResults(codeSecurityResultsLS) + } + + val codeQualityResultsLS = + getSnykCachedResultsForProduct(project, ProductType.CODE_QUALITY) ?: return + ApplicationManager.getApplication().invokeLater { + scanListenerLS.displaySnykCodeResults(codeQualityResultsLS) } val ossResultsLS = @@ -281,6 +287,12 @@ class SnykToolWindowPanel( scanListenerLS.displayOssResults(ossResultsLS) } + val iacResultsLS = + getSnykCachedResultsForProduct(project, ProductType.IAC) ?: return + ApplicationManager.getApplication().invokeLater { + scanListenerLS.displayIacResults(iacResultsLS) + } + val snykCachedResults = getSnykCachedResults(project) ?: return ApplicationManager.getApplication().invokeLater { snykCachedResults.currentIacResult?.let { displayIacResults(it) } @@ -324,18 +336,13 @@ class SnykToolWindowPanel( .subscribe( SnykTaskQueueListener.TASK_QUEUE_TOPIC, object : SnykTaskQueueListener { - override fun stopped( - wasOssRunning: Boolean, - wasSnykCodeRunning: Boolean, - wasIacRunning: Boolean, - wasContainerRunning: Boolean, - ) = ApplicationManager.getApplication().invokeLater { + override fun stopped() = ApplicationManager.getApplication().invokeLater { updateTreeRootNodesPresentation( - ossResultsCount = if (wasOssRunning) NODE_INITIAL_STATE else null, - securityIssuesCount = if (wasSnykCodeRunning) NODE_INITIAL_STATE else null, - qualityIssuesCount = if (wasSnykCodeRunning) NODE_INITIAL_STATE else null, - iacResultsCount = if (wasIacRunning) NODE_INITIAL_STATE else null, - containerResultsCount = if (wasContainerRunning) NODE_INITIAL_STATE else null, + ossResultsCount = NODE_INITIAL_STATE, + securityIssuesCount = NODE_INITIAL_STATE, + qualityIssuesCount = NODE_INITIAL_STATE, + iacResultsCount = NODE_INITIAL_STATE, + containerResultsCount = NODE_INITIAL_STATE, ) displayEmptyDescription() } diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListenerLS.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListenerLS.kt index 4d6dfbc7e..0534239b2 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListenerLS.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListenerLS.kt @@ -9,8 +9,6 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.ui.tree.TreeUtil import io.snyk.plugin.Severity import io.snyk.plugin.SnykFile -import io.snyk.plugin.cancelIacIndicator -import io.snyk.plugin.cancelOssIndicator import io.snyk.plugin.events.SnykScanListenerLS import io.snyk.plugin.getSnykCachedResults import io.snyk.plugin.pluginSettings @@ -84,7 +82,6 @@ class SnykToolWindowSnykScanListenerLS( override fun scanningOssFinished() { if (disposed) return - cancelOssIndicator(project) ApplicationManager.getApplication().invokeLater { this.rootOssIssuesTreeNode.userObject = "$OSS_ROOT_TEXT (scanning finished)" this.snykToolWindowPanel.triggerSelectionListeners = false @@ -97,7 +94,6 @@ class SnykToolWindowSnykScanListenerLS( override fun scanningIacFinished() { if (disposed) return - cancelIacIndicator(project) ApplicationManager.getApplication().invokeLater { this.rootIacIssuesTreeNode.userObject = "$IAC_ROOT_TEXT (scanning finished)" this.snykToolWindowPanel.triggerSelectionListeners = false @@ -173,36 +169,35 @@ class SnykToolWindowSnykScanListenerLS( ) } - fun displayOssResults(snykResults: Map>) { + private fun displayResults(snykResults: Map>, enabledInSettings: Boolean, filterTree: Boolean, rootNode: DefaultMutableTreeNode) { if (disposed) return - if (getSnykCachedResults(project)?.currentOssError != null) return - - val settings = pluginSettings() + if (getSnykCachedResults(project)?.currentIacError != null) return displayIssues( - enabledInSettings = settings.ossScanEnable, - filterTree = settings.treeFiltering.ossResults, + enabledInSettings = enabledInSettings, + filterTree = filterTree, snykResults = snykResults, - rootNode = this.rootOssIssuesTreeNode, - ossResultsCount = snykResults.values.flatten().distinct().size, + rootNode = rootNode, + iacResultsCount = snykResults.values.flatten().distinct().size, fixableIssuesCount = snykResults.values.flatten().count { it.additionalData.isUpgradable } ) } + fun displayOssResults(snykResults: Map>) { + if (disposed) return + if (getSnykCachedResults(project)?.currentOssError != null) return + + val settings = pluginSettings() + + displayResults(snykResults, settings.ossScanEnable, settings.treeFiltering.ossResults, this.rootOssIssuesTreeNode) + } + fun displayIacResults(snykResults: Map>) { if (disposed) return if (getSnykCachedResults(project)?.currentIacError != null) return val settings = pluginSettings() - - displayIssues( - enabledInSettings = settings.iacScanEnabled, - filterTree = settings.treeFiltering.iacResults, - snykResults = snykResults, - rootNode = this.rootIacIssuesTreeNode, - iacResultsCount = snykResults.values.flatten().distinct().size, - fixableIssuesCount = snykResults.values.flatten().count { it.additionalData.isUpgradable } - ) + displayResults(snykResults, settings.iacScanEnabled, settings.treeFiltering.iacResults, this.rootIacIssuesTreeNode) } private fun displayIssues( diff --git a/src/main/kotlin/snyk/common/lsp/progress/ProgressManager.kt b/src/main/kotlin/snyk/common/lsp/progress/ProgressManager.kt index 6a2d97b07..418589cee 100644 --- a/src/main/kotlin/snyk/common/lsp/progress/ProgressManager.kt +++ b/src/main/kotlin/snyk/common/lsp/progress/ProgressManager.kt @@ -76,46 +76,49 @@ class ProgressManager : Disposable { cancellable: Boolean, progress: Progress, token: String - ) = object : Task.Backgroundable(ProjectUtil.getActiveProject(), title, cancellable) { - override fun run(indicator: ProgressIndicator) { - try { - while (!isDone(progress)) { - if (indicator.isCanceled) { - progresses.remove(token) - val workDoneProgressCancelParams = WorkDoneProgressCancelParams() - workDoneProgressCancelParams.setToken(token) - val languageServerWrapper = LanguageServerWrapper.getInstance() - if (languageServerWrapper.isInitialized) { - val languageServer = languageServerWrapper.languageServer - languageServer.cancelProgress(workDoneProgressCancelParams) + ): Task.Backgroundable { + val project = ProjectUtil.getActiveProject() + return object : Task.Backgroundable(project, title, cancellable) { + override fun run(indicator: ProgressIndicator) { + try { + while (!isDone(progress)) { + if (indicator.isCanceled) { + progresses.remove(token) + val workDoneProgressCancelParams = WorkDoneProgressCancelParams() + workDoneProgressCancelParams.setToken(token) + val languageServerWrapper = LanguageServerWrapper.getInstance() + if (languageServerWrapper.isInitialized) { + val languageServer = languageServerWrapper.languageServer + languageServer.cancelProgress(workDoneProgressCancelParams) + } + throw ProcessCanceledException() } - throw ProcessCanceledException() - } - var progressNotification: WorkDoneProgressNotification? - try { - progressNotification = progress.nextProgressNotification - } catch (e: InterruptedException) { - progresses.remove(token) - Thread.currentThread().interrupt() - throw ProcessCanceledException(e) - } - if (progressNotification != null) { - val kind = progressNotification.kind ?: return - when (kind) { - WorkDoneProgressKind.begin -> // 'begin' has been notified - begin(progressNotification as WorkDoneProgressBegin, indicator) + var progressNotification: WorkDoneProgressNotification? + try { + progressNotification = progress.nextProgressNotification + } catch (e: InterruptedException) { + progresses.remove(token) + Thread.currentThread().interrupt() + throw ProcessCanceledException(e) + } + if (progressNotification != null) { + val kind = progressNotification.kind ?: return + when (kind) { + WorkDoneProgressKind.begin -> // 'begin' has been notified + begin(progressNotification as WorkDoneProgressBegin, indicator) - WorkDoneProgressKind.report -> // 'report' has been notified - report(progressNotification as WorkDoneProgressReport, indicator) + WorkDoneProgressKind.report -> // 'report' has been notified + report(progressNotification as WorkDoneProgressReport, indicator) - WorkDoneProgressKind.end -> Unit + WorkDoneProgressKind.end -> Unit + } } } + } finally { + indicator.cancel() + progresses.remove(token) } - } finally { - indicator.cancel() - progresses.remove(token) } } } diff --git a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt index c4f8e706e..982947baf 100644 --- a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt @@ -77,7 +77,6 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) - assertNull(snykTaskQueueService.ossScanProgressIndicator) } fun testCliDownloadBeforeScanIfNeeded() { setupAppSettingsForDownloadTests() diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt index 123fbab38..4694d8510 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSCodeTest.kt @@ -62,7 +62,7 @@ class SuggestionDescriptionPanelFromLSCodeTest : BasePlatformTestCase() { every { issue.cvssV3() } returns null every { issue.cvssScore() } returns null every { issue.id() } returns "id" - every { issue.additionalData.getProductType() } returns ProductType.CODE_SECURITY + every { issue.filterableIssueType } returns ScanIssue.CODE_SECURITY every { issue.additionalData.message } returns "Test message" every { issue.additionalData.repoDatasetSize } returns 1 every { issue.additionalData.exampleCommitFixes } returns diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt index 633f05eed..9751e5b04 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelFromLSOSSTest.kt @@ -50,7 +50,6 @@ class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() { snykFile = SnykFile(psiFile.project, psiFile.virtualFile) val matchingIssue = mockk() - every { matchingIssue.getProductType() } returns ProductType.OSS every { matchingIssue.name } returns "Another test name" every { matchingIssue.from } returns listOf("from") every { matchingIssue.upgradePath } returns listOf("upgradePath") @@ -67,7 +66,7 @@ class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() { every { issue.cvssScore() } returns "cvssScore" every { issue.id() } returns "id" every { issue.ruleId() } returns "ruleId" - every { issue.additionalData.getProductType() } returns ProductType.OSS + every { issue.filterableIssueType } returns ScanIssue.OPEN_SOURCE every { issue.additionalData.name } returns "Test name" every { issue.additionalData.matchingIssues } returns matchingIssues every { issue.additionalData.fixedIn } returns listOf("fixedIn")