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 1 commit
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
Prev Previous commit
Next Next commit
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
  • Loading branch information
fioan89 committed May 15, 2025
commit e4d9fbf6a3d306f448618f74826cd376bfbeb1fc
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
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 @@ 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() {
Expand Down Expand Up @@ -75,12 +78,13 @@ fun askIDE(
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 @@ open class LinkHandler(
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 @@ 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
Expand All @@ -65,9 +67,9 @@ open class LinkHandler(
indicator,
)

var workspace : Workspace
var workspaces : List<Workspace> = emptyList()
var workspacesAndAgents : Set<Pair<Workspace, WorkspaceAgent>> = emptySet()
var workspace: Workspace
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 @@ 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
}

Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -259,7 +277,7 @@ private fun isAllowlisted(url: URL): Triple<Boolean, Boolean, String> {

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 @@ 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<WorkspaceProjectIDE>(
CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.next.text"),
) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Workspace>,
val remoteProjectPath: String? = null
)

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -788,7 +800,8 @@ class WorkspacesTableModel :
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 @@ class WorkspacesTableModel :
}
}

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 @@ class WorkspacesTableModel :
}
}

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 @@ class WorkspacesTableModel :
}
}

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,7 +925,8 @@ class WorkspacesTableModel :
}
}

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) {
"Unknown"
} else if (workspace.workspace.outdated) {
Expand Down Expand Up @@ -940,7 +957,8 @@ class WorkspacesTableModel :
}
}

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