Repository navigation
api, ui: show and filter sandboxes by organization - #1248
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
The sandbox list spans every organization but had no organization column or filter, so "show me this org's sandboxes" meant opening each row. It also hid every sandbox older than 24 hours by default, which are the long-running sandboxes support cases tend to be about. The list now has an Org column linking to the organization, and an org filter that the admin API applies (`org_id`), so the loaded rows are that organization's newest sandboxes rather than a filtered slice of everyone's; it is kept in the URL as `?org=`. The age window defaults to all time; links that set `age=` keep working.
…ads with Reset cleared the organization selector but not the value the list loads with, and did not reload: the selector read "Org: any" while the rows, and every later poll, stayed filtered to the previous organization. Reset now clears the organization filter the same way the selector does, which reloads the list for every organization.
The admin sandbox list appended node-reported rows (rowless warm-pool slots, observed-local sandboxes) to every answer. Those rows belong to no organization, so an org-filtered list carried the whole fleet's pool slots and the org page's sandbox total counted them. Only the unscoped list carries them now; a new database-backed test covers both answers. In the console, `?org=` is used only when it is a UUID, so a mistyped link no longer fails every list request with 400. The org selector is searchable and labels repeated names with their slug, and the capped-list notice names the selected org.
1f2f248 to
40dd74d
Compare
- The list compared an answer's org and size with the current ones, so after a change from org A to B and back to A, the first A answer could still arrive last and replace the newer one, and an outdated request ended `loading` for the newer one. Every list request now takes the next number, and only the newest applies its rows, its error and the end of loading: the initial load, polling, Refresh, org and size changes and Reset alike. - When the load for a newly selected org or size fails, the previous scope's rows are cleared, and the error, naming the org, shows above the table with Retry. A failed refresh of the same scope keeps its rows, as before. - The refresh key includes the org and the size (`dashboard:sandboxes:org-<id>:limit-<n>`), taken from the same scope as the request, so windows showing different orgs no longer share one timestamp and starve each other. The unscoped default keeps the plain key. - A sandbox found by direct lookup while an org is selected says whose it is: "It belongs to <org>, not <selected org>." (or "another organization" / "no organization"), or that it is not among the newest rows of the selected org.
…d loads as failures - A poll or Refresh captured its org and size before its Web Locks callback ran, but took its request number inside it: a poll that captured org A and ran after the operator selected org B got the newer number, B's answer was dropped, and A's rows showed under B until the next poll. A refresh whose scope is no longer selected now returns without a number, and is reported as skipped: the view is not marked fresh, and Refresh says it was skipped. - A failed load for a newly selected org or size set the error and returned normally, so the view was marked fresh and Refresh said "Sandboxes refreshed" next to the error. It now fails, with the error marked as shown, so no toast repeats the alert. - Selecting another org or size clears the previous scope's error. - Only the unscoped list at the default size uses the plain refresh key; the unscoped 100-row list gets its own. - The lookup notice and the alerts name orgs by the selector's labels, which add the slug where a name repeats. - A comment in sandbox-id.ts still said every admin list appends node-reported rows; only the unscoped one does.
zibi-pplx
left a comment
There was a problem hiding this comment.
One nit.
- [nit] The title doesn't mention the admin API change.
crates/libs/api/src/routes/admin/sandboxes.rs:107changes whatGET /admin/api/v1/sandboxes?org_id=returns (it no longer includes observed-only rows). The title isui:only, so a reader of the log or release notes won't see an API behaviour change. #1262, on a separate branch, also relies on it. Consider anapi, ui:title, or a line about it in the commit message.
Withdrawing this approval for now. Any feedback above still stands.
johnlouie-pplx
left a comment
There was a problem hiding this comment.
Approving the org-filter changes at 244193e. I rechecked that the server filters before the limit and still enforces managing-org admin access. The component tests I ran confirm that the newest org response wins, a failed scope change clears the old rows, and the #1244 spinner case is fixed. All 79 UI tests and the production build pass at this head. The CI log also explicitly records org_scoped_list_leaves_out_observed_only_rows passing. The separate lookup follow-up is on #1244; I haven't reproduced the author's browser tests.
Summary
The sandbox list can now answer "which org owns this" and "show me this org's sandboxes".
The list spans every organization but had no organization column and no organization filter, so answering either question meant opening rows one by one. It also hid every sandbox older than 24 hours by default ("Last 24h"), and the long-running sandboxes support cases tend to be about are exactly those.
Changes:
GET /admin/api/v1/sandboxes?org_id=). The loaded rows are therefore that organization's newest sandboxes, not a filtered slice of everyone's newest. It is kept in the URL as?org=, which is used only when it is a UUID; anything else is ignored instead of failing every list request with 400.name (slug). When an org is selected, the capped-list notice names it, and a sandbox found by direct lookup says whose it is: "It belongs to , not ." (or "another organization" or "no organization"), or that it is not among the newest rows of the selected org. The notices and the error alert use the selector's labels, so orgs that share a name are told apart by slug.dashboard:sandboxes:org-<id>:limit-<n>), so windows showing different orgs do not share one and slow each other's refresh. Only the unscoped list at the default size keeps the plain key; the unscoped 100-row list has its own.org_idset) no longer appends the rows nodes report without a database row (rowless warm-pool slots, observed-local sandboxes). Those rows belong to no organization, so an org-filtered list used to include the whole fleet's pool slots, and the org page's sandbox total counted them. The unscoped list still carries them. The change is incrates/libs/api/src/routes/admin/sandboxes.rs, with a new database-backed test,org_scoped_list_leaves_out_observed_only_rows.age=keep working, and Reset returns to the new default.Stack
Stacked on #1244 (which is stacked on #1242). Merge those first. This branch is restacked on #1244's review follow-ups. "ui: offer Start, Resume and Restore in sandbox rows" (#1257) is stacked on this PR.
Validation
npm ci,npm test(79 pass, includingsrc/lib/org-filter.test.mjsfor the?org=check, the repeated-name labels, the per-scope refresh keys and the lookup notice) andnpm run buildinui/.cargo fmt --all -- --check;cargo test -p pplx-space-api --all-features --lib -- routes::admin::sandboxes::(16 pass, with the new test against a Postgres test container);cargo clippy -p pplx-space-api --all-targets --all-features -- -D warnings.space-uiimage):?org=<acme>shows exactly the rowsGET /admin/api/v1/sandboxes?org_id=<acme>returns;main.Visual confirmation
Video: after.
Severity and risk
Severity: medium. This is the first pivot of most org-level support questions.
Risk: low. The default view now includes sandboxes of any age, which can mean more rows on large fleets. The list is still capped by "Load newest". The admin list's org-scoped answer drops node-reported rows, which belong to no org. In this repository only the console passes
org_id(this list and the org page); the unscoped list is unchanged. Roll back by reverting the commits.AI assistance: Claude Code wrote the code and this description, and ran the validation; a maintainer reviews it.