From 92234b3c086e6e6cc855343f45a9e9f804ff419d Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Fri, 16 May 2025 00:28:10 +0300 Subject: [PATCH 1/3] chore: use newer snapshot build for testing and building I've also changed the list of Gateway versions we run the verifier against to include Gateway 2025.1 while excluding some ancient releases. Without this commit the PR will fail to build, the declared snapshots are no longer available in the repository --- gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index c7842bd4..9ef00002 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ pluginName=coder-gateway pluginVersion=2.20.0 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. -pluginSinceBuild=233.6745 +pluginSinceBuild=243.26053 # This should be kept up to date with the latest EAP. If the API is incompatible # with the latest stable, use the eap branch temporarily instead. pluginUntilBuild=251.* @@ -26,11 +26,11 @@ pluginUntilBuild=251.* # that exists, ideally the most recent one, for example # 233.15325-EAP-CANDIDATE-SNAPSHOT). platformType=GW -platformVersion=241.19416-EAP-CANDIDATE-SNAPSHOT -instrumentationCompiler=243.15521-EAP-CANDIDATE-SNAPSHOT +platformVersion=243.26053-EAP-CANDIDATE-SNAPSHOT +instrumentationCompiler=243.26053-EAP-CANDIDATE-SNAPSHOT # Gateway does not have open sources. platformDownloadSources=true -verifyVersions=2023.3,2024.1,2024.2,2024.3 +verifyVersions=2024.3,2025.1 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 platformPlugins= From e4d9fbf6a3d306f448618f74826cd376bfbeb1fc Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Fri, 16 May 2025 00:32:40 +0300 Subject: [PATCH 2/3] fix: pass the folder parameter to the IDE&Project dialog The value of the `folder` URI parameter is passed to the IDE&Project dialog when handling URIs. The folder value will be rendered instead of the default home folder if the value is not blank. -resolves #466 --- CHANGELOG.md | 1 + .../kotlin/com/coder/gateway/util/Dialogs.kt | 8 ++- .../com/coder/gateway/util/LinkHandler.kt | 50 +++++++++++++------ .../steps/CoderWorkspaceProjectIDEStepView.kt | 8 +-- .../views/steps/CoderWorkspacesStepView.kt | 40 +++++++++++---- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7472dd9b..dcf36caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixed - installed EAP, RC, NIGHTLY and PREVIEW IDEs are no longer displayed if there is a higher released version available for download. +- project path is prefilled with the `folder` URI parameter when the IDE&Project dialog opens for URI handling. ## 2.19.0 - 2025-02-21 diff --git a/src/main/kotlin/com/coder/gateway/util/Dialogs.kt b/src/main/kotlin/com/coder/gateway/util/Dialogs.kt index 0e360363..f35f1e86 100644 --- a/src/main/kotlin/com/coder/gateway/util/Dialogs.kt +++ b/src/main/kotlin/com/coder/gateway/util/Dialogs.kt @@ -38,7 +38,10 @@ private class CoderWorkspaceStepDialog( init { init() - title = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", CoderCLIManager.getWorkspaceParts(state.workspace, state.agent)) + title = CoderGatewayBundle.message( + "gateway.connector.view.coder.remoteproject.choose.text", + CoderCLIManager.getWorkspaceParts(state.workspace, state.agent) + ) } override fun show() { @@ -75,12 +78,13 @@ fun askIDE( cli: CoderCLIManager, client: CoderRestClient, workspaces: List, + remoteProjectPath: String? = null ): WorkspaceProjectIDE? { var data: WorkspaceProjectIDE? = null ApplicationManager.getApplication().invokeAndWait { val dialog = CoderWorkspaceStepDialog( - CoderWorkspacesStepSelection(agent, workspace, cli, client, workspaces), + CoderWorkspacesStepSelection(agent, workspace, cli, client, workspaces, remoteProjectPath), ) data = dialog.showAndGetData() } diff --git a/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt b/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt index c32a136e..f802109c 100644 --- a/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt +++ b/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt @@ -32,7 +32,8 @@ open class LinkHandler( parameters: Map, indicator: ((t: String) -> Unit)? = null, ): WorkspaceProjectIDE { - val deploymentURL = parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") + val deploymentURL = + parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") if (deploymentURL.isNullOrBlank()) { throw MissingArgumentException("Query parameter \"$URL\" is missing") } @@ -50,7 +51,8 @@ open class LinkHandler( } // TODO: Show a dropdown and ask for the workspace if missing. - val workspaceName = parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") + val workspaceName = + parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") // The owner was added to support getting into another user's workspace // but may not exist if the Coder Gateway module is out of date. If no @@ -65,9 +67,9 @@ open class LinkHandler( indicator, ) - var workspace : Workspace - var workspaces : List = emptyList() - var workspacesAndAgents : Set> = emptySet() + var workspace: Workspace + var workspaces: List = emptyList() + var workspacesAndAgents: Set> = emptySet() if (cli.features.wildcardSSH) { workspace = client.workspaceByOwnerAndName(owner, workspaceName) } else { @@ -83,19 +85,28 @@ open class LinkHandler( WorkspaceStatus.PENDING, WorkspaceStatus.STARTING -> // TODO: Wait for the workspace to turn on. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please wait then try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please wait then try again", ) + WorkspaceStatus.STOPPING, WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED, - -> + -> // TODO: Turn on the workspace. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please start the workspace and try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please start the workspace and try again", ) + WorkspaceStatus.FAILED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; unable to connect", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; unable to connect", ) + WorkspaceStatus.RUNNING -> Unit // All is well } @@ -106,10 +117,16 @@ open class LinkHandler( if (status.pending()) { // TODO: Wait for the agent to be ready. throw IllegalArgumentException( - "The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; please wait then try again", + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; please wait then try again", ) } else if (!status.ready()) { - throw IllegalArgumentException("The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; unable to connect") + throw IllegalArgumentException( + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; unable to connect" + ) } // We only need to log in if we are using token-based auth. @@ -123,12 +140,13 @@ open class LinkHandler( val openDialog = parameters.ideProductCode().isNullOrBlank() || - parameters.ideBuildNumber().isNullOrBlank() || - (parameters.idePathOnHost().isNullOrBlank() && parameters.ideDownloadLink().isNullOrBlank()) || - parameters.folder().isNullOrBlank() + parameters.ideBuildNumber().isNullOrBlank() || + (parameters.idePathOnHost().isNullOrBlank() && parameters.ideDownloadLink().isNullOrBlank()) || + parameters.folder().isNullOrBlank() return if (openDialog) { - askIDE(agent, workspace, cli, client, workspaces) ?: throw MissingArgumentException("IDE selection aborted; unable to connect") + askIDE(agent, workspace, cli, client, workspaces, parameters.folder()) + ?: throw MissingArgumentException("IDE selection aborted; unable to connect") } else { // Check that both the domain and the redirected domain are // allowlisted. If not, check with the user whether to proceed. @@ -259,7 +277,7 @@ private fun isAllowlisted(url: URL): Triple { val allowlisted = domainAllowlist.any { url.host == it || url.host.endsWith(".$it") } && - domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } + domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } val https = url.protocol == "https" && finalUrl.protocol == "https" return Triple(allowlisted, https, linkWithRedirect) } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt index ce28903a..81f02b2a 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt @@ -95,7 +95,7 @@ private fun displayIdeWithStatus(ideWithStatus: IdeWithStatus): String = * to select an IDE and project to run on the workspace. */ class CoderWorkspaceProjectIDEStepView( - private val showTitle: Boolean = true, + private val showTitle: Boolean = true ) : CoderWizardStep( CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.next.text"), ) { @@ -200,7 +200,9 @@ class CoderWorkspaceProjectIDEStepView( val name = CoderCLIManager.getWorkspaceParts(data.workspace, data.agent) logger.info("Initializing workspace step for $name") - val homeDirectory = data.agent.expandedDirectory ?: data.agent.directory + val homeDirectory = data.remoteProjectPath.takeIf { !it.isNullOrBlank() } + ?: data.agent.expandedDirectory + ?: data.agent.directory tfProject.text = if (homeDirectory.isNullOrBlank()) "/home" else homeDirectory titleLabel.text = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", name) titleLabel.isVisible = showTitle @@ -429,7 +431,7 @@ class CoderWorkspaceProjectIDEStepView( if (remainingInstalledIdes.size < installedIdes.size) { logger.info( "Skipping the following list of installed IDEs because there is already a released version " + - "available for download: ${(installedIdes - remainingInstalledIdes).joinToString { "${it.product.productCode} ${it.presentableVersion}" }}" + "available for download: ${(installedIdes - remainingInstalledIdes).joinToString { "${it.product.productCode} ${it.presentableVersion}" }}" ) } return remainingInstalledIdes.map { it.toIdeWithStatus() }.sorted() + availableIdes.map { it.toIdeWithStatus() } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 53a67c37..1928a4c4 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -105,6 +105,7 @@ data class CoderWorkspacesStepSelection( // Pass along the latest workspaces so we can configure the CLI a bit // faster, otherwise this step would have to fetch the workspaces again. val workspaces: List, + val remoteProjectPath: String? = null ) /** @@ -147,7 +148,8 @@ class CoderWorkspacesStepView : setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.disconnected")) setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.addListSelectionListener { - nextButton.isEnabled = selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem == OS.LINUX + nextButton.isEnabled = + selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem == OS.LINUX if (selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem != OS.LINUX) { notificationBanner.apply { component.isVisible = true @@ -343,22 +345,26 @@ class CoderWorkspacesStepView : val maxWait = Duration.ofMinutes(10) while (isActive) { // Wait for the workspace to fully stop. delay(timeout.toMillis()) - val found = tableOfWorkspaces.items.firstOrNull { it.workspace.id == workspace.id } + val found = + tableOfWorkspaces.items.firstOrNull { it.workspace.id == workspace.id } when (val status = found?.workspace?.latestBuild?.status) { WorkspaceStatus.PENDING, WorkspaceStatus.STOPPING, WorkspaceStatus.RUNNING -> { logger.info("Still waiting for ${workspace.name} to stop before updating") } + WorkspaceStatus.STARTING, WorkspaceStatus.FAILED, WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED, - -> { + -> { logger.warn("Canceled ${workspace.name} update due to status change to $status") break } + null -> { logger.warn("Canceled ${workspace.name} update because it no longer exists") break } + WorkspaceStatus.STOPPED -> { logger.info("${workspace.name} has stopped; updating now") c.updateWorkspace(workspace) @@ -560,7 +566,10 @@ class CoderWorkspacesStepView : deploymentURL.host, ) tableOfWorkspaces.setEmptyState( - CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connecting", deploymentURL.host), + CoderGatewayBundle.message( + "gateway.connector.view.coder.workspaces.connect.text.connecting", + deploymentURL.host + ), ) tableOfWorkspaces.listTableModel.items = emptyList() @@ -600,7 +609,10 @@ class CoderWorkspacesStepView : client = authedClient tableOfWorkspaces.setEmptyState( - CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host), + CoderGatewayBundle.message( + "gateway.connector.view.coder.workspaces.connect.text.connected", + deploymentURL.host + ), ) tfUrlComment?.text = CoderGatewayBundle.message( @@ -788,7 +800,8 @@ class WorkspacesTableModel : WorkspaceVersionColumnInfo("Version"), WorkspaceStatusColumnInfo("Status"), ) { - private class WorkspaceIconColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceIconColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName override fun getRenderer(item: WorkspaceAgentListModel?): TableCellRenderer { @@ -820,7 +833,8 @@ class WorkspacesTableModel : } } - private class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceNameColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.name override fun getComparator(): Comparator = Comparator { a, b -> @@ -850,7 +864,8 @@ class WorkspacesTableModel : } } - private class WorkspaceOwnerColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceOwnerColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.ownerName override fun getComparator(): Comparator = Comparator { a, b -> @@ -880,7 +895,8 @@ class WorkspacesTableModel : } } - private class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceTemplateNameColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName override fun getComparator(): java.util.Comparator = Comparator { a, b -> @@ -909,7 +925,8 @@ class WorkspacesTableModel : } } - private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceVersionColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentListModel?): String? = if (workspace == null) { "Unknown" } else if (workspace.workspace.outdated) { @@ -940,7 +957,8 @@ class WorkspacesTableModel : } } - private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceStatusColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.status?.label override fun getComparator(): java.util.Comparator = Comparator { a, b -> From 3e3ce35d7f2a088d32fdb7b7c6c3cc5cf88beacd Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Fri, 16 May 2025 00:33:02 +0300 Subject: [PATCH 3/3] chore: next version is 2.20.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9ef00002..cebd25a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup=com.coder.gateway # Zip file name. pluginName=coder-gateway # SemVer format -> https://semver.org -pluginVersion=2.20.0 +pluginVersion=2.20.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. pluginSinceBuild=243.26053