feat: filter and scope the workspace list#318
Conversation
Until now the plugin pulled back workspaces without applying any meaningful scope, so the list it showed was effectively the whole deployment. There was no way to narrow it down or to deliberately choose whose workspaces you wanted to see. This hit administrators hardest. On a large deployment the list could be huge, mixing together the workspaces of every user, with no quick way to focus on a particular person, template, or state. This change puts that choice in the user's hands. A scope selector switches between just your own workspaces and every workspace you're allowed to access, and the selection is remembered separately for each deployment so it sticks the next time you connect. Alongside it, a search box narrows the list by name, template, owner, or status, including quoted multi-word values and a plain-text shortcut that searches by workspace name. An explicit owner in the search always takes precedence over the selected scope. Changing the scope or the search reloads the list and regenerates SSH configuration to match what is shown. Deep links can now carry an owner as well, so a link to a workspace name that several users share resolves to the right one instead of guessing. When no owner is given the lookup stays unscoped, preserving the previous behavior. To keep the list manageable out of the box, it now defaults to showing only your own workspaces. Anyone who relied on seeing everything can switch the scope to all workspaces, and that preference is then remembered per deployment.
| val scoped = if (scope == WorkspaceScope.MY_WORKSPACES && terms.none { it.key == OWNER_KEY }) { | ||
| listOf(WorkspaceSearchTerm(OWNER_KEY, CURRENT_USER)) + terms | ||
| } else { | ||
| terms |
There was a problem hiding this comment.
TBH I feel like for large deployments, if scope is all workspaces, we should force some kinda filter or at least default to something. Not really a change requested, but wanted to call out that admins would find this view pretty useless without some query params.
There was a problem hiding this comment.
they have the search bar where they can limit status, by template name, by workspace name or even by a specific owner.
|
Have not had a chance to test it out yet but wanted to call out:
The default was always |
| enum class WorkspaceScope { | ||
| MY_WORKSPACES, | ||
| ALL_WORKSPACES; | ||
|
|
||
| companion object { | ||
| fun fromValue(value: String?): WorkspaceScope = when (value?.lowercase(getDefault())) { | ||
| "all_workspaces" -> ALL_WORKSPACES | ||
| "my_workspaces" -> MY_WORKSPACES | ||
| else -> MY_WORKSPACES | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Just a suggestion so feel free to ignore but I wonder if we should store a query instead?
That way, if in the future we do get more filters we can store them all.
So we could rename this to WorkspaceFilter and for now we would store just owner:me or a blank string for all workspaces. Then we parse them to figure out the state of the radio.
There was a problem hiding this comment.
It is an interesting approach and I think it would make a lot of sense if the search bar would also be persisted. However right now this only persist the state of of a radio button. I think if I go with the proposal right now I would end up comparing a string to decide which radio button is enabled, which is disabled.
There was a problem hiding this comment.
True, you would have to parse the setting, but you already have a parser!
I think it would be something like terms.any { it.key == "owner" && it.value == "me" } and terms.none { it.key == "owner" } for the two radio conditions.
Imagine for example we add a radio button for filtering shared workspaces, no changes to the settings would be required, just another radio button that adds shared_with_user:me and does a check like terms.any { it.key == "shared_with_user" && it.value == "me"}.
And if we ever add multiple filters at once, that would just work as well.
|
|
||
| /** | ||
| * Parses a raw filter query into search terms. Recognized `key:value` pairs are kept as-is; any | ||
| * bare words are merged into a single `name:` term. A key may take its value inline (`status:running`) |
There was a problem hiding this comment.
IMO we should just send the query exactly as-is and let the backend decide what the bare words should mean, so we have parity with the dashboard.
| val workspace = restClient.workspaces().matchName(workspaceName, url) | ||
|
|
||
| val ownerName = params.owner()?.takeIf { it.isNotBlank() } | ||
| val workspaces = restClient.workspaces(workspaceOwnerSearchQuery(params)) |
There was a problem hiding this comment.
Just a random thought I had, occurs to me we could also add the name if the workspace if we have it to make the query response smaller. But not something we have to do here; just a thought that occurred to me.
There was a problem hiding this comment.
I'm pondering whether to change it or not. I'd probably do it in a separate PR as this one is already huge. But I guess you are right, it will certainly remove some of the code we have cause right now we request all workspaces and then we do a search by name in Toolbox.
| fun filterQuery(query: String?): String? = | ||
| parse(query).asQuery() | ||
|
|
||
| fun workspaceQuery(query: String?, scope: WorkspaceScope): String? { |
There was a problem hiding this comment.
I might be reading this wrong but it seems like it is possible to (for example) type owner:someone in the search box and then select "my workspaces" but the owner:someone would take priority.
Is it possible that when a radio is selected, we can update the text field with owner:me (removing any other owner)? And vice versa, when the field is edited, check to see if an owner was added/removed and sync up the radio to match?
Similar to the dashboard. But not sure if we have that kind of flexibility here.
There was a problem hiding this comment.
We do. Changed the approach completely and redesigned the search functionality to look like and work like the web dashboard.
| this.client!! to this.cli!! | ||
| } | ||
| linkHandler.handle(params, newUrl, this.client!!, this.cli!!) | ||
| linkHandler.handle(params, newUrl, activeSession.first, activeSession.second) |
There was a problem hiding this comment.
Just curious why this change, when it seems like it ends up using the same this.client and this.cli in the end in both branches anyway.
There was a problem hiding this comment.
I was trying to exploit the fact that refreshSession already returns a refreshed pair of http client and cli. And I was also trying to make the !! asertions safer.
| - notifications are now persistent popups instead of snackbars, so they survive a hidden window and no longer get dropped | ||
| - notifications are now persistent popups instead of snackbars, so they survive a hidden window and no longer get | ||
| dropped | ||
| - workspace lists now default to `My workspaces`, so users initially see only workspaces they own. Users can |
There was a problem hiding this comment.
I think we can omit the first sentence because the default was already owner:me
There was a problem hiding this comment.
Well... the default was more like: all the workspaces your RBAC rules allow you to have access to.
|
|
||
| The expandable header on the Coder Workspaces page contains workspace scope and search controls. | ||
|
|
||
| - `My workspaces` is the new default scope. It adds `owner:me`, so Coder returns workspaces owned by the authenticated |
| - `All workspaces` does not add an owner filter, so Coder returns every workspace the authenticated user is allowed to | ||
| read. The scope selection is remembered separately for each Coder deployment hostname. | ||
| - Search text is not remembered between sessions. | ||
| - The search box supports only `owner`, `template`, `name`, and `status`. Unsupported keys and empty values are ignored. |
There was a problem hiding this comment.
Yeah I feel like we should just send it all verbatim to the backend, otherwise we have to update the plugin every time we add a key, and there is risk of drift.
| - The search box supports only `owner`, `template`, `name`, and `status`. Unsupported keys and empty values are ignored. | ||
| - If the search box includes `owner`, that owner takes precedence over the selected scope. For example, `owner:bob` | ||
| still searches Bob's workspaces even when `My workspaces` is selected. | ||
| - Text without a prefix is treated as a workspace name search. For example, `bobiverse` becomes `name:bobiverse`. |
There was a problem hiding this comment.
Are you a man of culture, like myself? :)
| * @throws [APIResponseException]. | ||
| */ | ||
| suspend fun workspaces(): List<Workspace> { | ||
| val workspacesResponse = callWithRetry { retroRestClient.workspaces("owner:me") } |
There was a problem hiding this comment.
yeah default was owner:me looks like
Well actually there was no Really - the All flag is something that makes sense for admins and other types of roles that can have a global view. |
…ingful scope, so the list it showed was effectively the whole deployment. There was no way to narrow it down or to deliberately choose whose workspaces you wanted to see. This hit administrators hardest. On a large deployment the list could be huge, mixing together the workspaces of every user, with no quick way to focus on a particular person, template, or state. This change puts that choice in the user's hands and aligns the experience with the Coder web dashboard. A free-form search bar accepts the same key:value syntax the server understands — owner:, template:, status:, and others — and its text is sent verbatim to the server, which is the authoritative parser. Alongside it, three dropdowns give quick access to the most common choices: a Filters preset menu (My workspaces, All workspaces, Running, Failed, Outdated, Shared, Dormant), a Template picker populated from the deployment, and a Status picker. The dropdowns and the search bar stay in sync: picking a preset or narrowing by template or status writes the corresponding terms into the search bar, and typing directly in the search bar reflects back into the dropdowns. Any validation error from the server — for example a malformed query — is shown inline under the search bar. Changing the search reloads the list and regenerates SSH configuration to match what is shown. The filter is not persisted between sessions. Each time you connect it resets to owner:me, exactly as the web dashboard does, so you always start with a focused view of your own workspaces and can widen from there. Deep links can now carry an owner as well, so a link to a workspace name that several users share resolves to the right one instead of guessing. When no owner is given the lookup stays unscoped, preserving the previous behavior.
I could definitely be misreading it but in 0.9.0: And then: Would this not result in |
Until now the plugin pulled back workspaces without applying any meaningful scope, so the list it showed was effectively the whole deployment. There was no way to narrow it down or to deliberately choose whose workspaces you wanted to see.
This hit administrators hardest. On a large deployment the list could be huge, mixing together the workspaces of every user, with no quick way to focus on a particular person, template, or state.
This change puts that choice in the user's hands. A scope selector switches between just your own workspaces and every workspace you're allowed to access, and the selection is remembered separately for each deployment so it sticks the next time you connect. Alongside it, a search box narrows the list by name, template, owner, or status, including quoted multi-word values and a plain-text shortcut that searches by workspace name. An explicit owner in the search always takes precedence over the selected scope. Changing the scope or the search reloads the list and regenerates SSH configuration to match what is shown.
Deep links can now carry an owner as well, so a link to a workspace name that several users share resolves to the right one instead of guessing. When no owner is given the lookup stays unscoped, preserving the previous behavior.
To keep the list manageable out of the box, it now defaults to showing only your own workspaces. Anyone who relied on seeing everything can switch the scope to all workspaces, and that preference is then remembered per deployment.