diff --git a/CHANGELOG.md b/CHANGELOG.md index 976d2e18..83331c08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- support Toolbox 3.5 provider header behavior + ### Fixed - snackbar dismissal no longer cancels the calling coroutine, so error popups during URI handling don't leave the page diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index a8b5fb61..b302b547 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -339,7 +339,7 @@ class CoderRemoteEnvironment( while (context.cs.isActive && workspaceStillExists) { if (environmentStatus is WorkspaceAndAgentStatus.Deleting || environmentStatus is WorkspaceAndAgentStatus.Deleted) { workspaceStillExists = false - context.envPageManager.showPluginEnvironmentsPage() + context.envPageManager.showPluginEnvironmentsPage(false) } else { delay(1.seconds) } diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 0bdc5dd2..5273bed6 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -100,7 +100,9 @@ class CoderRemoteProvider( ) private val accountDropdownField = dropDownFactory(context.i18n.pnotr("")) { logout() - context.envPageManager.showPluginEnvironmentsPage() + context.envPageManager.showPluginEnvironmentsPage(false) + }.apply { + visibility.update { false } } private val router = PageRouter() @@ -152,7 +154,7 @@ class CoderRemoteProvider( } else { if ((ex is APIResponseException && ex.isTokenExpired) || ex is OAuthTokenResponseException) { close() - context.envPageManager.showPluginEnvironmentsPage() + context.envPageManager.showPluginEnvironmentsPage(false) context.logAndShowError( "Error encountered while setting up Coder", "Your Coder session has expired. Please re-authenticate and try again.", @@ -265,6 +267,7 @@ class CoderRemoteProvider( lastEnvironments.clear() environments.value = LoadableState.Value(emptyList()) isInitialized.update { false } + accountDropdownField.visibility.update { false } router.clear() context.logger.info("Coder plugin is now closed") } @@ -299,15 +302,12 @@ class CoderRemoteProvider( */ override val noEnvironmentsDescription: String? = "No workspaces yet" - /** - * TODO@JB: Supposedly, setting this to false causes the new environment - * page to not show but it shows anyway. For now we have it - * displaying the deployment URL, which is actually useful, so if - * this changes it would be nice to have a new spot to show the - * URL. + * Toolbox 3.5 removes the entire top section when this is false. Coder uses + * the new-environment page as a provider header so the deployment URL and + * account dropdown remain visible above the workspace list. */ - override val canCreateNewEnvironments: Boolean = false + override val canCreateNewEnvironments: Boolean = true /** * Just displays the deployment URL at the moment, but we could use this as @@ -459,7 +459,7 @@ class CoderRemoteProvider( ) router.navigate(wizard) - context.envPageManager.showPluginEnvironmentsPage(true) + context.envPageManager.showPluginEnvironmentsPage(false) context.ui.showUiPage(wizard) } catch (e: Exception) { context.logAndShowError("OAuth Error", "Exception during token exchange: ${e.message}", e) @@ -626,6 +626,7 @@ class CoderRemoteProvider( accountDropdownField.labelState.update { context.i18n.pnotr(client.me.username) } + accountDropdownField.visibility.update { true } pollJob = poll(client, cli) context.logger.info("Workspace poll job with name ${pollJob.toString()} was created") } diff --git a/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt b/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt index c371c33b..a75186be 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt @@ -104,6 +104,6 @@ data class CoderToolboxContext( fun popupPluginMainPage() { this.ui.showWindow() - this.envPageManager.showPluginEnvironmentsPage(true) + this.envPageManager.showPluginEnvironmentsPage(false) } } diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt index bea6ef80..16234da7 100644 --- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt +++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt @@ -138,7 +138,7 @@ class ConnectStep( } // The provider's onConnect ran close() which clears the router; combined // with client now being non-null this drops the wizard from getOverrideUiPage. - context.envPageManager.showPluginEnvironmentsPage() + context.envPageManager.showPluginEnvironmentsPage(false) } catch (ex: CancellationException) { // Back-button cancellation already navigates in onBack(), while // dispose() must cancel without navigating. Treat these control-flow diff --git a/src/test/kotlin/com/coder/toolbox/CoderRemoteProviderTest.kt b/src/test/kotlin/com/coder/toolbox/CoderRemoteProviderTest.kt index 55d65b84..e038266e 100644 --- a/src/test/kotlin/com/coder/toolbox/CoderRemoteProviderTest.kt +++ b/src/test/kotlin/com/coder/toolbox/CoderRemoteProviderTest.kt @@ -27,6 +27,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertSame @@ -259,6 +260,27 @@ class CoderRemoteProviderTest { verify { mockContext.popupPluginMainPage() } } + @Test + fun `given Toolbox 3_5 provider page then header surface is available and account dropdown starts hidden`() { + // Toolbox 3.5 hides the whole top section when this flag is false. + // The Coder header page keeps the deployment URL and account dropdown renderable. + assertTrue(remoteProvider.canCreateNewEnvironments) + assertNotNull(remoteProvider.getNewEnvironmentUiPage()) + + val accountDropdown = assertNotNull(remoteProvider.getAccountDropDown()) + assertFalse(accountDropdown.visibility.value) + } + + @Test + fun `given visible account dropdown when provider closes then dropdown is hidden`() { + val accountDropdown = assertNotNull(remoteProvider.getAccountDropDown()) + accountDropdown.visibility.value = true + + remoteProvider.close() + + assertFalse(accountDropdown.visibility.value) + } + @Test fun `given mTLS is required when auto setup has stored credentials then mTLS takes precedence`() { // given