diff --git a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowDataContextService.kt b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowDataContextService.kt index 842c72a..f5bb637 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowDataContextService.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowDataContextService.kt @@ -47,57 +47,40 @@ class WorkflowDataContextService(private val project: Project) { toolWindow: ToolWindow, ): CompletableFuture { return repositories.getOrPut(repositoryMapping.remote.url) { - LazyCancellableBackgroundProcessValue.create { indicator -> + LazyCancellableBackgroundProcessValue.create(ProgressManager.getInstance()) { indicator -> LOG.debug("Creating data context for ${repositoryMapping.remote.url}") - ProgressManager.getInstance().submitIOTask(indicator) { - try { - getContext(checkedDisposable, account, repositoryMapping, toolWindow) - } catch (e: Exception) { - if (e !is ProcessCanceledException) - LOG.warn("Error occurred while creating data context", e) - throw e + try { + val token = if (settingsService.state.useGitHubSettings) { + GHCompatibilityUtil.getOrRequestToken(account, toolWindow.project) + ?: throw GithubMissingTokenException(account) + } else { + settingsService.state.apiToken } - }.successOnEdt { ctx -> - LOG.debug("Registering context for ${repositoryMapping.remote.url}") - Disposer.register(toolWindow.disposable, ctx) - ctx + + val requestExecutor = GithubApiRequestExecutor.Factory.getInstance().create(token = token) + val singleRunDataLoader = SingleRunDataLoader(requestExecutor) + if (checkedDisposable.isDisposed) { + throw ProcessCanceledException( + RuntimeException("Skipped creating data context for ${repositoryMapping.remote.url} because it was disposed") + ) + } + WorkflowRunSelectionContext( + checkedDisposable, + toolWindow, + account, + singleRunDataLoader, + repositoryMapping, + requestExecutor, + ) + } catch (e: Exception) { + if (e !is ProcessCanceledException) + LOG.warn("Error occurred while creating data context", e) + throw e } } }.value } - @RequiresBackgroundThread - @Throws(IOException::class) - private fun getContext( - checkedDisposable: CheckedDisposable, - account: GithubAccount, - repositoryMapping: GHGitRepositoryMapping, - toolWindow: ToolWindow, - ): WorkflowRunSelectionContext { - val token = if (settingsService.state.useGitHubSettings) { - GHCompatibilityUtil.getOrRequestToken(account, toolWindow.project) - ?: throw GithubMissingTokenException(account) - } else { - settingsService.state.apiToken - } - - val requestExecutor = GithubApiRequestExecutor.Factory.getInstance().create(token = token) - val singleRunDataLoader = SingleRunDataLoader(requestExecutor) - if (checkedDisposable.isDisposed) { - throw ProcessCanceledException( - RuntimeException("Skipped creating data context for ${repositoryMapping.remote.url} because it was disposed") - ) - } - return WorkflowRunSelectionContext( - checkedDisposable, - toolWindow, - account, - singleRunDataLoader, - repositoryMapping, - requestExecutor, - ) - } - companion object { private val LOG = logger() fun getInstance(project: Project) = project.service() diff --git a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunListLoader.kt b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunListLoader.kt index 0f8abc6..042f350 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunListLoader.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/data/WorkflowRunListLoader.kt @@ -11,6 +11,7 @@ import com.intellij.collaboration.async.CompletableFutureUtil import com.intellij.collaboration.async.CompletableFutureUtil.handleOnEdt import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask import com.intellij.collaboration.ui.SimpleEventListener +import com.intellij.collaboration.util.ProgressIndicatorsProvider import com.intellij.openapi.Disposable import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger @@ -24,7 +25,6 @@ import org.jetbrains.plugins.github.api.GithubApiRequestExecutor import org.jetbrains.plugins.github.api.GithubApiRequests import org.jetbrains.plugins.github.api.data.GHUser import org.jetbrains.plugins.github.api.data.request.GithubRequestPagination -import org.jetbrains.plugins.github.util.NonReusableEmptyProgressIndicator import java.util.concurrent.CompletableFuture import java.util.concurrent.ScheduledFuture import kotlin.properties.Delegates @@ -35,6 +35,7 @@ class WorkflowRunListLoader( private val requestExecutor: GithubApiRequestExecutor, private val repositoryCoordinates: RepositoryCoordinates, private var filter: WorkflowRunFilter, + private val indicatorsProvider: ProgressIndicatorsProvider = ProgressIndicatorsProvider(), ) : Disposable { private val settingsService = toolWindow.project.service() val workflowRunsListModel = CollectionListModel() @@ -50,7 +51,6 @@ class WorkflowRunListLoader( private val page: Int = 1 private val task: ScheduledFuture<*> var refreshRuns: Boolean = true - private var progressIndicator = NonReusableEmptyProgressIndicator() var error: Throwable? by Delegates.observable(null) { _, _, _ -> errorChangeEventDispatcher.multicaster.eventOccurred() } @@ -62,6 +62,7 @@ class WorkflowRunListLoader( init { LOG.debug("Initialize WorkflowRunListLoader for ${repositoryCoordinates.repositoryPath}") Disposer.register(parentDisposable, this) + Disposer.register(this, indicatorsProvider) task = ToolbarUtil.executeTaskAtSettingsFrequency(toolWindow.project) { if (refreshRuns && error == null) loadMore(update = true) } @@ -72,8 +73,7 @@ class WorkflowRunListLoader( fun loadMore(update: Boolean = false) { if (canLoadMore() || update) { loading = true - requestLoadMore(progressIndicator, update).handleOnEdt { list, error -> - if (progressIndicator.isCanceled) return@handleOnEdt + requestLoadMore(update).handleOnEdt { list, error -> loading = false if (error != null) { if (!CompletableFutureUtil.isCancellation(error)) this.error = error @@ -85,15 +85,15 @@ class WorkflowRunListLoader( } } - private fun requestLoadMore(indicator: ProgressIndicator, update: Boolean): CompletableFuture> { + private fun requestLoadMore(update: Boolean): CompletableFuture> { applyIf(repoCollaborators.isEmpty()) { - progressManager.submitIOTask(NonReusableEmptyProgressIndicator()) { updateCollaborators(it) } + progressManager.submitIOTask(indicatorsProvider) { updateCollaborators(it) } }.applyIf(repoBranches.isEmpty()) { - progressManager.submitIOTask(NonReusableEmptyProgressIndicator()) { updateBranches(it) } + progressManager.submitIOTask(indicatorsProvider) { updateBranches(it) } }.applyIf(workflowTypes.isEmpty()) { - progressManager.submitIOTask(NonReusableEmptyProgressIndicator()) { updateWorkflowTypes(it) } + progressManager.submitIOTask(indicatorsProvider) { updateWorkflowTypes(it) } } - lastFuture = progressManager.submitIOTask(indicator) { doLoadMore(indicator, update) } + lastFuture = progressManager.submitIOTask(indicatorsProvider) { doLoadMore(it, update) } return lastFuture } @@ -163,7 +163,6 @@ class WorkflowRunListLoader( } override fun dispose() { - progressIndicator.cancel() task.cancel(true) } @@ -172,8 +171,6 @@ class WorkflowRunListLoader( lastFuture = lastFuture.handle { _, _ -> listOf() } - progressIndicator.cancel() - progressIndicator = NonReusableEmptyProgressIndicator() error = null loading = false workflowRunsListModel.removeAll() diff --git a/src/main/kotlin/com/dsoftware/ghmanager/ui/GhActionsMgrToolWindowContent.kt b/src/main/kotlin/com/dsoftware/ghmanager/ui/GhActionsMgrToolWindowContent.kt index 05c52f7..ac2fc72 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/ui/GhActionsMgrToolWindowContent.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/ui/GhActionsMgrToolWindowContent.kt @@ -41,7 +41,6 @@ class GhActionsMgrToolWindowContent(val toolWindow: ToolWindow) : Disposable { private var state: GhActionsMgrToolWindowState = GhActionsMgrToolWindowState.UNINITIALIZED private var currentReposWithPanels: Set = emptySet() - private val checkedDisposable = Disposer.newCheckedDisposable(this, "GhActionsMgrToolWindowContent") init { val project = toolWindow.project @@ -64,7 +63,7 @@ class GhActionsMgrToolWindowContent(val toolWindow: ToolWindow) : Disposable { fun createContent() { - runInEdtAsync(checkedDisposable) { + ApplicationManager.getApplication().invokeLater { val projectRepos = ghActionsService.knownRepositories val countRepos = projectRepos.count { settingsService.state.customRepos[it.remote.url]?.included ?: false @@ -79,7 +78,7 @@ class GhActionsMgrToolWindowContent(val toolWindow: ToolWindow) : Disposable { } if (state == nextState && nextState != GhActionsMgrToolWindowState.REPOS) { LOG.debug("createContent: state is the same, not updating") - return@runInEdtAsync + return@invokeLater } LOG.debug("createContent: state changed from $state to $nextState") state = nextState diff --git a/src/main/kotlin/com/dsoftware/ghmanager/ui/panels/wfruns/WorkflowRunListPanel.kt b/src/main/kotlin/com/dsoftware/ghmanager/ui/panels/wfruns/WorkflowRunListPanel.kt index 5049311..b837d9a 100644 --- a/src/main/kotlin/com/dsoftware/ghmanager/ui/panels/wfruns/WorkflowRunListPanel.kt +++ b/src/main/kotlin/com/dsoftware/ghmanager/ui/panels/wfruns/WorkflowRunListPanel.kt @@ -73,7 +73,7 @@ class WorkflowRunsListPanel( val searchVm = WfRunsSearchPanelViewModel(scope, context) scope.launch { searchVm.searchState.collectLatest { -// context.updateFilter(it.toWorkflowRunFilter()) + context.updateFilter(it.toWorkflowRunFilter()) } } diff --git a/src/test/kotlin/com/dsoftware/ghmanager/GitHubActionsManagerBaseTest.kt b/src/test/kotlin/com/dsoftware/ghmanager/GitHubActionsManagerBaseTest.kt index c928595..435e2c8 100644 --- a/src/test/kotlin/com/dsoftware/ghmanager/GitHubActionsManagerBaseTest.kt +++ b/src/test/kotlin/com/dsoftware/ghmanager/GitHubActionsManagerBaseTest.kt @@ -10,12 +10,14 @@ import com.intellij.openapi.wm.ToolWindow import com.intellij.platform.ide.progress.runWithModalProgressBlocking import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.common.initTestApplication import com.intellij.testFramework.junit5.RunInEdt -import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.registerServiceInstance import com.intellij.testFramework.rules.ProjectModelExtension +import com.intellij.testFramework.waitUntil import com.intellij.toolWindow.ToolWindowHeadlessManagerImpl import com.intellij.util.concurrency.annotations.RequiresEdt +import com.intellij.util.ui.UIUtil import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -23,6 +25,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import org.jetbrains.plugins.github.api.GithubServerPath import org.jetbrains.plugins.github.authentication.accounts.GHAccountManager @@ -34,8 +37,10 @@ import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.extension.RegisterExtension @RunInEdt(writeIntent = true) -@TestApplication abstract class GitHubActionsManagerBaseTest { + init { + initTestApplication() + } private val host: GithubServerPath = GithubServerPath.from("github.com") private lateinit var testInfo: TestInfo @@ -48,7 +53,7 @@ abstract class GitHubActionsManagerBaseTest { protected lateinit var toolWindowContent: GhActionsMgrToolWindowContent @BeforeEach - open fun beforeEach(testInfo: TestInfo) { + open fun setUp(testInfo: TestInfo) { this.testInfo = testInfo val toolWindowManager = ToolWindowHeadlessManagerImpl(projectRule.project) toolWindow = toolWindowManager.doRegisterToolWindow("GitHub Actions") @@ -106,6 +111,7 @@ fun executeSomeCoroutineTasksAndDispatchAllInvocationEvents(project: Project) { runWithModalProgressBlocking(project, "") { yield() } + UIUtil.dispatchAllInvocationEvents() PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() } } \ No newline at end of file diff --git a/src/test/kotlin/com/dsoftware/ghmanager/TestRepoTabControllerWorkflowRunsPanel.kt b/src/test/kotlin/com/dsoftware/ghmanager/TestRepoTabControllerWorkflowRunsPanel.kt index 4623572..7ebdd39 100644 --- a/src/test/kotlin/com/dsoftware/ghmanager/TestRepoTabControllerWorkflowRunsPanel.kt +++ b/src/test/kotlin/com/dsoftware/ghmanager/TestRepoTabControllerWorkflowRunsPanel.kt @@ -8,6 +8,7 @@ import com.dsoftware.ghmanager.data.WorkflowDataContextService import com.dsoftware.ghmanager.ui.GhActionsMgrToolWindowContent import com.dsoftware.ghmanager.ui.panels.wfruns.WorkflowRunsListPanel import com.intellij.openapi.components.service +import com.intellij.testFramework.waitUntil import com.intellij.ui.OnePixelSplitter import io.mockk.MockKMatcherScope import io.mockk.every @@ -41,8 +42,8 @@ class TestRepoTabControllerWorkflowRunsPanel : GitHubActionsManagerBaseTest() { } @BeforeEach - override fun beforeEach(testInfo: TestInfo) { - super.beforeEach(testInfo) + override fun setUp(testInfo: TestInfo) { + super.setUp(testInfo) mockGhActionsService(setOf("http://github.com/owner/repo"), setOf("account1")) toolWindowContent = GhActionsMgrToolWindowContent(toolWindow) executeSomeCoroutineTasksAndDispatchAllInvocationEvents(projectRule.project) @@ -132,7 +133,7 @@ class TestRepoTabControllerWorkflowRunsPanel : GitHubActionsManagerBaseTest() { val workflowDataContextService = projectRule.project.service() Assertions.assertEquals(1, workflowDataContextService.repositories.size) executeSomeCoroutineTasksAndDispatchAllInvocationEvents(projectRule.project) - verify(atLeast = 1) { + verify(atLeast = 1, timeout = 3000) { executorMock.execute(any(), matchApiRequestUrl("/actions/runs")).hint(WorkflowRuns::class) executorMock.execute( any(), matchApiRequestUrl>("/collaborators") @@ -140,8 +141,10 @@ class TestRepoTabControllerWorkflowRunsPanel : GitHubActionsManagerBaseTest() { executorMock.execute(any(), matchApiRequestUrl>("/branches")) executorMock.execute(any(), matchApiRequestUrl("/actions/workflows")) } + executeSomeCoroutineTasksAndDispatchAllInvocationEvents(projectRule.project) + executeSomeCoroutineTasksAndDispatchAllInvocationEvents(projectRule.project) Assertions.assertEquals(1, (tabWrapPanel.components[0] as JPanel).componentCount) - Assertions.assertTrue((tabWrapPanel.components[0] as JPanel).components[0] is OnePixelSplitter) + Assertions.assertTrue((tabWrapPanel.components[0] as JPanel).components[0] is OnePixelSplitter, "Expected tab to have OnePixelSplitter") val splitterComponent = ((tabWrapPanel.components[0] as JPanel).components[0] as OnePixelSplitter) Assertions.assertEquals(3, splitterComponent.componentCount) Assertions.assertTrue(splitterComponent.firstComponent is WorkflowRunsListPanel) diff --git a/src/test/kotlin/com/dsoftware/ghmanager/ToolWindowFactoryTest.kt b/src/test/kotlin/com/dsoftware/ghmanager/ToolWindowFactoryTest.kt index 2c3ca39..396d4ea 100644 --- a/src/test/kotlin/com/dsoftware/ghmanager/ToolWindowFactoryTest.kt +++ b/src/test/kotlin/com/dsoftware/ghmanager/ToolWindowFactoryTest.kt @@ -28,8 +28,8 @@ class ToolWindowFactoryTest : GitHubActionsManagerBaseTest() { private lateinit var requestExecutorfactoryMock: GithubApiRequestExecutor.Factory @BeforeEach - override fun beforeEach(testInfo: TestInfo) { - super.beforeEach(testInfo) + override fun setUp(testInfo: TestInfo) { + super.setUp(testInfo) requestExecutorfactoryMock.apply { every { create(token = any()) } throws Exception("No executor") }