diff --git a/CHANGELOG.md b/CHANGELOG.md index e87ca97..08ac799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- progress reporting while handling URIs + ### Changed - workspaces status is now refresh every time Coder Toolbox becomes visible diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 596255e..eb9997e 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -7,12 +7,14 @@ import com.coder.toolbox.sdk.ex.APIResponseException import com.coder.toolbox.sdk.v2.models.WorkspaceStatus import com.coder.toolbox.util.CoderProtocolHandler import com.coder.toolbox.util.DialogUi +import com.coder.toolbox.util.toURL import com.coder.toolbox.util.waitForTrue import com.coder.toolbox.util.withPath import com.coder.toolbox.views.Action import com.coder.toolbox.views.CoderCliSetupWizardPage import com.coder.toolbox.views.CoderSettingsPage import com.coder.toolbox.views.NewEnvironmentPage +import com.coder.toolbox.views.state.CoderCliSetupContext import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.coder.toolbox.views.state.WizardStep import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon @@ -35,7 +37,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.selects.onTimeout import kotlinx.coroutines.selects.select import java.net.URI -import java.util.UUID import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource @@ -66,19 +67,18 @@ class CoderRemoteProvider( private var firstRun = true private val isInitialized: MutableStateFlow = MutableStateFlow(false) private val coderHeaderPage = NewEnvironmentPage(context.i18n.pnotr(context.deploymentUrl.toString())) - private val linkHandler = CoderProtocolHandler(context, dialogUi, isInitialized) - - override val loadingEnvironmentsDescription: LocalizableString = context.i18n.ptrl("Loading workspaces...") - override val environments: MutableStateFlow>> = MutableStateFlow( - LoadableState.Loading - ) - private val visibilityState = MutableStateFlow( ProviderVisibilityState( applicationVisible = false, providerVisible = false ) ) + private val linkHandler = CoderProtocolHandler(context, dialogUi, settingsPage, visibilityState, isInitialized) + + override val loadingEnvironmentsDescription: LocalizableString = context.i18n.ptrl("Loading workspaces...") + override val environments: MutableStateFlow>> = MutableStateFlow( + LoadableState.Loading + ) private val errorBuffer = mutableListOf() @@ -311,17 +311,8 @@ class CoderRemoteProvider( override suspend fun handleUri(uri: URI) { try { linkHandler.handle( - uri, shouldDoAutoSetup(), - { - coderHeaderPage.isBusyCreatingNewEnvironment.update { - true - } - }, - { - coderHeaderPage.isBusyCreatingNewEnvironment.update { - false - } - } + uri, + shouldDoAutoSetup() ) { restClient, cli -> // stop polling and de-initialize resources close() @@ -337,23 +328,16 @@ class CoderRemoteProvider( isInitialized.waitForTrue() } } catch (ex: Exception) { - context.logger.error(ex, "") val textError = if (ex is APIResponseException) { if (!ex.reason.isNullOrBlank()) { ex.reason } else ex.message } else ex.message - - context.ui.showSnackbar( - UUID.randomUUID().toString(), - context.i18n.ptrl("Error encountered while handling Coder URI"), - context.i18n.pnotr(textError ?: ""), - context.i18n.ptrl("Dismiss") + context.logAndShowError( + "Error encountered while handling Coder URI", + textError ?: "" ) - } finally { - coderHeaderPage.isBusyCreatingNewEnvironment.update { - false - } + context.envPageManager.showPluginEnvironmentsPage() } } @@ -369,8 +353,17 @@ class CoderRemoteProvider( // When coming back to the application, initializeSession immediately. if (shouldDoAutoSetup()) { try { + CoderCliSetupContext.apply { + url = context.secrets.lastDeploymentURL.toURL() + token = context.secrets.lastToken + } CoderCliSetupWizardState.goToStep(WizardStep.CONNECT) - return CoderCliSetupWizardPage(context, settingsPage, visibilityState, true, ::onConnect) + return CoderCliSetupWizardPage( + context, settingsPage, visibilityState, + initialAutoSetup = true, + jumpToMainPageOnError = false, + onConnect = ::onConnect + ) } catch (ex: Exception) { errorBuffer.add(ex) } finally { diff --git a/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt b/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt index 4291321..baac820 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt @@ -88,4 +88,9 @@ data class CoderToolboxContext( i18n.ptrl("OK") ) } + + fun popupPluginMainPage() { + this.ui.showWindow() + this.envPageManager.showPluginEnvironmentsPage(true) + } } diff --git a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt index f0e84b9..76877cf 100644 --- a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt +++ b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt @@ -10,10 +10,17 @@ import com.coder.toolbox.sdk.v2.models.Workspace import com.coder.toolbox.sdk.v2.models.WorkspaceAgent import com.coder.toolbox.sdk.v2.models.WorkspaceStatus import com.coder.toolbox.util.WebUrlValidationResult.Invalid +import com.coder.toolbox.views.CoderCliSetupWizardPage +import com.coder.toolbox.views.CoderSettingsPage +import com.coder.toolbox.views.state.CoderCliSetupContext +import com.coder.toolbox.views.state.CoderCliSetupWizardState +import com.coder.toolbox.views.state.WizardStep +import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.time.withTimeout @@ -25,12 +32,13 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI" -private val noOpTextProgress: (String) -> Unit = { _ -> } @Suppress("UnstableApiUsage") open class CoderProtocolHandler( private val context: CoderToolboxContext, private val dialogUi: DialogUi, + private val settingsPage: CoderSettingsPage, + private val visibilityState: MutableStateFlow, private val isInitialized: StateFlow, ) { private val settings = context.settingsStore.readOnly() @@ -45,8 +53,6 @@ open class CoderProtocolHandler( suspend fun handle( uri: URI, shouldWaitForAutoLogin: Boolean, - markAsBusy: () -> Unit, - unmarkAsBusy: () -> Unit, reInitialize: suspend (CoderRestClient, CoderCLIManager) -> Unit ) { val params = uri.toQueryParameters() @@ -58,7 +64,6 @@ open class CoderProtocolHandler( // this switches to the main plugin screen, even // if last opened provider was not Coder context.envPageManager.showPluginEnvironmentsPage() - markAsBusy() if (shouldWaitForAutoLogin) { isInitialized.waitForTrue() } @@ -67,13 +72,16 @@ open class CoderProtocolHandler( val deploymentURL = resolveDeploymentUrl(params) ?: return val token = if (!context.settingsStore.requireTokenAuth) null else resolveToken(params) ?: return val workspaceName = resolveWorkspaceName(params) ?: return - val restClient = buildRestClient(deploymentURL, token) ?: return - val workspace = restClient.workspaces().matchName(workspaceName, deploymentURL) ?: return - val cli = configureCli(deploymentURL, restClient) - - var agent: WorkspaceAgent - try { + suspend fun onConnect( + restClient: CoderRestClient, + cli: CoderCLIManager + ) { + val workspace = restClient.workspaces().matchName(workspaceName, deploymentURL) + if (workspace == null) { + context.envPageManager.showPluginEnvironmentsPage() + return + } reInitialize(restClient, cli) context.envPageManager.showPluginEnvironmentsPage() if (!prepareWorkspace(workspace, restClient, workspaceName, deploymentURL)) return @@ -81,25 +89,36 @@ open class CoderProtocolHandler( // errors like: no agent available while workspace is starting or stopping // we also need to retrieve the workspace again to have the latest resources (ex: agent) // attached to the workspace. - agent = resolveAgent( + val agent: WorkspaceAgent = resolveAgent( params, restClient.workspace(workspace.id) ) ?: return if (!ensureAgentIsReady(workspace, agent)) return - } finally { - unmarkAsBusy() - } - delay(2.seconds) - val environmentId = "${workspace.name}.${agent.name}" - context.showEnvironmentPage(environmentId) + delay(2.seconds) + val environmentId = "${workspace.name}.${agent.name}" + context.showEnvironmentPage(environmentId) - val productCode = params.ideProductCode() - val buildNumber = params.ideBuildNumber() - val projectFolder = params.projectFolder() + val productCode = params.ideProductCode() + val buildNumber = params.ideBuildNumber() + val projectFolder = params.projectFolder() + + if (!productCode.isNullOrBlank() && !buildNumber.isNullOrBlank()) { + launchIde(environmentId, productCode, buildNumber, projectFolder) + } + } - if (!productCode.isNullOrBlank() && !buildNumber.isNullOrBlank()) { - launchIde(environmentId, productCode, buildNumber, projectFolder) + CoderCliSetupContext.apply { + url = deploymentURL.toURL() + CoderCliSetupContext.token = token } + CoderCliSetupWizardState.goToStep(WizardStep.CONNECT) + context.ui.showUiPage( + CoderCliSetupWizardPage( + context, settingsPage, visibilityState, true, + jumpToMainPageOnError = true, + onConnect = ::onConnect + ) + ) } private suspend fun resolveDeploymentUrl(params: Map): String? { @@ -308,13 +327,14 @@ open class CoderProtocolHandler( private suspend fun configureCli( deploymentURL: String, - restClient: CoderRestClient + restClient: CoderRestClient, + progressReporter: (String) -> Unit ): CoderCLIManager { val cli = ensureCLI( context, deploymentURL.toURL(), restClient.buildInfo().version, - noOpTextProgress + progressReporter ) // We only need to log in if we are using token-based auth. @@ -455,12 +475,6 @@ open class CoderProtocolHandler( } } - -private fun CoderToolboxContext.popupPluginMainPage() { - this.ui.showWindow() - this.envPageManager.showPluginEnvironmentsPage(true) -} - private suspend fun CoderToolboxContext.showEnvironmentPage(envId: String) { this.ui.showWindow() this.envPageManager.showEnvironmentPage(envId, false) diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt index 5115204..1e6b152 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt @@ -4,8 +4,6 @@ import com.coder.toolbox.CoderToolboxContext import com.coder.toolbox.cli.CoderCLIManager import com.coder.toolbox.sdk.CoderRestClient import com.coder.toolbox.sdk.ex.APIResponseException -import com.coder.toolbox.util.toURL -import com.coder.toolbox.views.state.CoderCliSetupContext import com.coder.toolbox.views.state.CoderCliSetupWizardState import com.coder.toolbox.views.state.WizardStep import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState @@ -21,6 +19,7 @@ class CoderCliSetupWizardPage( private val settingsPage: CoderSettingsPage, private val visibilityState: MutableStateFlow, initialAutoSetup: Boolean = false, + jumpToMainPageOnError: Boolean = false, onConnect: suspend ( client: CoderRestClient, cli: CoderCLIManager, @@ -35,7 +34,8 @@ class CoderCliSetupWizardPage( private val tokenStep = TokenStep(context) private val connectStep = ConnectStep( context, - shouldAutoSetup, + shouldAutoLogin = shouldAutoSetup, + jumpToMainPageOnError, this::notify, this::displaySteps, onConnect @@ -49,13 +49,6 @@ class CoderCliSetupWizardPage( private val errorBuffer = mutableListOf() - init { - if (shouldAutoSetup.value) { - CoderCliSetupContext.url = context.secrets.lastDeploymentURL.toURL() - CoderCliSetupContext.token = context.secrets.lastToken - } - } - override fun beforeShow() { displaySteps() if (errorBuffer.isNotEmpty() && visibilityState.value.applicationVisible) { diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 363d618..eec0765 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -1,6 +1,5 @@ package com.coder.toolbox.views -import com.coder.toolbox.CoderToolboxContext import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType import com.jetbrains.toolbox.api.localization.LocalizableString @@ -43,12 +42,6 @@ abstract class CoderPage( } else { SvgIcon(byteArrayOf(), type = IconType.Masked) } - - override val isBusyCreatingNewEnvironment: MutableStateFlow = MutableStateFlow(false) - - companion object { - fun emptyPage(ctx: CoderToolboxContext): UiPage = UiPage(ctx.i18n.pnotr("")) - } } /** diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index 7ea93e4..40db0cb 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -25,6 +25,7 @@ private const val USER_HIT_THE_BACK_BUTTON = "User hit the back button" class ConnectStep( private val context: CoderToolboxContext, private val shouldAutoLogin: StateFlow, + private val jumpToMainPageOnError: Boolean, private val notify: (String, Throwable) -> Unit, private val refreshWizard: () -> Unit, private val onConnect: suspend ( @@ -127,7 +128,11 @@ class ConnectStep( } finally { if (shouldAutoLogin.value) { CoderCliSetupContext.reset() - CoderCliSetupWizardState.goToFirstStep() + if (jumpToMainPageOnError) { + context.popupPluginMainPage() + } else { + CoderCliSetupWizardState.goToFirstStep() + } } else { if (context.settingsStore.requireTokenAuth) { CoderCliSetupWizardState.goToPreviousStep() diff --git a/src/test/kotlin/com/coder/toolbox/util/CoderProtocolHandlerTest.kt b/src/test/kotlin/com/coder/toolbox/util/CoderProtocolHandlerTest.kt index b26acde..56402e5 100644 --- a/src/test/kotlin/com/coder/toolbox/util/CoderProtocolHandlerTest.kt +++ b/src/test/kotlin/com/coder/toolbox/util/CoderProtocolHandlerTest.kt @@ -5,9 +5,11 @@ import com.coder.toolbox.sdk.DataGen import com.coder.toolbox.settings.Environment import com.coder.toolbox.store.CoderSecretsStore import com.coder.toolbox.store.CoderSettingsStore +import com.coder.toolbox.views.CoderSettingsPage import com.jetbrains.toolbox.api.core.diagnostics.Logger import com.jetbrains.toolbox.api.core.os.LocalDesktopManager import com.jetbrains.toolbox.api.localization.LocalizableStringFactory +import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings @@ -16,6 +18,7 @@ import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager import com.jetbrains.toolbox.api.ui.ToolboxUi import io.mockk.mockk import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.DisplayName @@ -43,6 +46,8 @@ internal class CoderProtocolHandlerTest { private val protocolHandler = CoderProtocolHandler( context, DialogUi(context), + CoderSettingsPage(context, Channel(Channel.CONFLATED)), + MutableStateFlow(ProviderVisibilityState(applicationVisible = true, providerVisible = true)), MutableStateFlow(false) ) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy