Skip to content

fix: pass the folder parameter to the IDE&Project dialog #550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ pluginGroup=com.coder.gateway
# Zip file name.
pluginName=coder-gateway
# SemVer format -> https://semver.org
pluginVersion=2.20.0
pluginVersion=2.20.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we are dropping support for the minimum version, what do you think about bumping the minor version?
@code-asher, how have we done it historically?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not dropping support for the minimum version — we're just building with a newer snapshot because that's what's available in the snapshot repositories. I've updated the versions we test the plugin against in GitHub workflows and locally. Since we’re no longer using any new Gateway APIs, I think it’s unnecessary to keep verifying against so many older versions.

# 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.*
Expand All @@ -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=
Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/com/coder/gateway/util/Dialogs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@

init {
init()
title = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", CoderCLIManager.getWorkspaceParts(state.workspace, state.agent))
title = CoderGatewayBundle.message(

Check warning on line 41 in src/main/kotlin/com/coder/gateway/util/Dialogs.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Incorrect string capitalization

String 'Choose IDE and project for workspace {0}' is not properly capitalized. It should have title capitalization
"gateway.connector.view.coder.remoteproject.choose.text",
CoderCLIManager.getWorkspaceParts(state.workspace, state.agent)
)
}

override fun show() {
Expand Down Expand Up @@ -75,12 +78,13 @@
cli: CoderCLIManager,
client: CoderRestClient,
workspaces: List<Workspace>,
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()
}
Expand Down
50 changes: 34 additions & 16 deletions src/main/kotlin/com/coder/gateway/util/LinkHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
parameters: Map<String, String>,
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")
}
Expand All @@ -50,7 +51,8 @@
}

// 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
Expand All @@ -65,9 +67,9 @@
indicator,
)

var workspace : Workspace
var workspaces : List<Workspace> = emptyList()
var workspacesAndAgents : Set<Pair<Workspace, WorkspaceAgent>> = emptySet()
var workspace: Workspace

Check warning on line 70 in src/main/kotlin/com/coder/gateway/util/LinkHandler.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Local 'var' is never modified and can be declared as 'val'

Variable is never modified, so it can be declared using 'val'
var workspaces: List<Workspace> = emptyList()
var workspacesAndAgents: Set<Pair<Workspace, WorkspaceAgent>> = emptySet()
if (cli.features.wildcardSSH) {
workspace = client.workspaceByOwnerAndName(owner, workspaceName)
} else {
Expand All @@ -83,19 +85,28 @@
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
}

Expand All @@ -106,10 +117,16 @@
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.
Expand All @@ -123,12 +140,13 @@

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.
Expand Down Expand Up @@ -259,7 +277,7 @@

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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
* to select an IDE and project to run on the workspace.
*/
class CoderWorkspaceProjectIDEStepView(
private val showTitle: Boolean = true,
private val showTitle: Boolean = true
) : CoderWizardStep<WorkspaceProjectIDE>(
CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.next.text"),
) {
Expand Down Expand Up @@ -200,7 +200,9 @@
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
Expand Down Expand Up @@ -289,7 +291,7 @@
)

// Check the provided setting to see if there's a default IDE to set.
val defaultIde = ides.find { it ->

Check notice on line 294 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Redundant lambda arrow

Redundant lambda arrow
// Using contains on the displayable version of the ide means they can be as specific or as vague as they want
// CL 2023.3.6 233.15619.8 -> a specific Clion build
// CL 2023.3.6 -> a specific Clion version
Expand Down Expand Up @@ -429,7 +431,7 @@
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}" }}"

Check notice on line 434 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Argument could be converted to 'Set' to improve performance

The argument can be converted to 'Set' to improve performance
)
}
return remainingInstalledIdes.map { it.toIdeWithStatus() }.sorted() + availableIdes.map { it.toIdeWithStatus() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
// 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<Workspace>,
val remoteProjectPath: String? = null
)

/**
Expand Down Expand Up @@ -147,7 +148,8 @@
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
Expand Down Expand Up @@ -343,22 +345,26 @@
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)
Expand Down Expand Up @@ -560,7 +566,10 @@
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()
Expand Down Expand Up @@ -600,7 +609,10 @@
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(
Expand Down Expand Up @@ -788,7 +800,8 @@
WorkspaceVersionColumnInfo("Version"),
WorkspaceStatusColumnInfo("Status"),
) {
private class WorkspaceIconColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceIconColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName

override fun getRenderer(item: WorkspaceAgentListModel?): TableCellRenderer {
Expand Down Expand Up @@ -820,7 +833,8 @@
}
}

private class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceNameColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.name

override fun getComparator(): Comparator<WorkspaceAgentListModel> = Comparator { a, b ->
Expand Down Expand Up @@ -850,7 +864,8 @@
}
}

private class WorkspaceOwnerColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceOwnerColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.ownerName

override fun getComparator(): Comparator<WorkspaceAgentListModel> = Comparator { a, b ->
Expand Down Expand Up @@ -880,7 +895,8 @@
}
}

private class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceTemplateNameColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName

override fun getComparator(): java.util.Comparator<WorkspaceAgentListModel> = Comparator { a, b ->
Expand Down Expand Up @@ -909,8 +925,9 @@
}
}

private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceVersionColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentListModel?): String? = if (workspace == null) {

Check warning on line 930 in src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Redundant nullable return type

'valueOf' always returns non-null type
"Unknown"
} else if (workspace.workspace.outdated) {
"Outdated"
Expand Down Expand Up @@ -940,7 +957,8 @@
}
}

private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
private class WorkspaceStatusColumnInfo(columnName: String) :
ColumnInfo<WorkspaceAgentListModel, String>(columnName) {
override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.status?.label

override fun getComparator(): java.util.Comparator<WorkspaceAgentListModel> = Comparator { a, b ->
Expand Down
Loading