feat: scope cluster version selector to org-deployed versions#1539
Conversation
Pass the current organizationId into the /HarperVersions query so the deploy version selector can include versions currently deployed on the org's clusters (the backend merges them in, tagged `deployed`, for enterprise orgs). - getHarperVersions / getHarperVersionsOptions take an optional organizationId, appended as a query param and folded into the query key so the cache is scoped per org. - UpsertCluster passes the route's organizationId through. Requires the companion central-manager change adding organizationId to GET /HarperVersions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the Harper versions query to support scoping by an optional organizationId. The getHarperVersions API call now appends the organizationId as a URL-encoded query parameter when provided, and the React Query cache key is updated to include the organization ID (or null if absent). The cluster upsert component was updated to pass the organizationId to this query, and corresponding unit tests were added to verify the new behavior. There are no review comments, and I have no additional feedback to provide.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
| const path = organizationId | ||
| ? `/HarperVersions/?organizationId=${encodeURIComponent(organizationId)}` | ||
| : `/HarperVersions/`; | ||
| const { data } = await apiClient.get(path as any); |
There was a problem hiding this comment.
Can you shift this to instead use the params? We don't need to manage the query string building ourselves.
const { data } = await apiClient.get(`/HarperVersions/` as any, {
params: {
organizationId,
},
});
There was a problem hiding this comment.
Done — switched to axios params: { organizationId } so we no longer hand-build the query string (axios handles encoding). Matches the getPlanTypesQuery pattern. (d6cb4a8)
— devain (Claude Opus 4.8)
| } | ||
|
|
||
| async function getHarperVersions() { | ||
| async function getHarperVersions(organizationId?: string) { |
There was a problem hiding this comment.
We'll never make this request without an organizationId, too, so just make it a required parameter. That may splash outward more, preventing this from loading until we know the organizationId definitively from the route. That will simplify the testing, too.
There was a problem hiding this comment.
Good call — made organizationId a required string on both getHarperVersions and getHarperVersionsOptions, so it splashes outward to the route (same shape as getPlanTypesOptions(organizationId)). Dropped the no-arg cases from the tests accordingly. (d6cb4a8)
— devain (Claude Opus 4.8)
The edit-flow version picker injects a synthetic "current" entry for the cluster being edited. Now that the backend also returns org-deployed versions, the current cluster's own version comes back as a "deployed on <cluster>" entry — so exclude every version the edited cluster runs (not just the max) to avoid a duplicate row. Also copy the array before sort() (sort mutates) so the full instance-version set is intact for the exclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es axios params Per review (dawsontoth): - Make organizationId a required parameter of getHarperVersions/getHarperVersionsOptions — the picker never requests versions without an org, so require it (matches the sibling getPlanTypesOptions pattern) and let it splash outward to the route. - Pass organizationId via axios `params` instead of hand-building the query string (axios handles encoding). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DavidCockerill
left a comment
There was a problem hiding this comment.
LGTM — clean org-id threading. organizationId comes from the route (useParams, the same source already feeding the sibling plan/region queries, so it's the current org context, not a stale global), and the react-query cache is org-scoped (['HarperVersions', organizationId]) so switching orgs can't serve the previous org's version list. Making the param required + moving to axios params (your two earlier asks) both landed, and the undefined edge degrades safely to the old global behavior. Org id in the query is just an identifier, so nothing sensitive rides the URL. Tests cover the with-org param, cache key, and the deployed-tag/encoding cases.
One cosmetic nit, non-blocking: the cache key here is namespace-first (['HarperVersions', organizationId]) while the sibling getPlanTypesOptions is org-first ([organizationId, 'instancePlan']) — both correctly include the org, just inconsistent ordering.
— DAIvid (Claude Fable 5)
|
Sticking the org id first is more consistent across the repo, and allows for easier scoped cache busting. |
Review (DavidCockerill, dawsontoth): use an org-first query key
`[organizationId, 'HarperVersions']` to match the sibling queries
(getPlanTypesQuery, getRegionLocationsQuery, …). Beyond consistency, this makes
the list participate in the repo's org-scoped cache busting
(`invalidateQueries({ queryKey: [organizationId] })`, used after cluster ops) —
with the old namespace-first key, prefix matching skipped it, so deployed
versions could go stale after a cluster change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done in 7b8675e — switched to an org-first cache key Worth flagging it turned out to be more than cosmetic: the org-scoped busts already in the repo ( — devain (Claude Opus 4.8) |
What & why
Companion to central-manager #466 (
feat: org-scoped Harper versions on /HarperVersions).The deploy version selector is fed by
GET /HarperVersions, which returns the global default list. This threads the currentorganizationIdinto that query so the backend can additionally return the versions currently deployed on the org's clusters (taggeddeployed), letting operators deploy a new cluster at a version they already run. The merge/authorization all happens server-side; this is just the wiring.Changes
getHarperVersions/getHarperVersionsOptionstake an optionalorganizationId, appended as a URL-encoded query param and folded into the React Query key (['HarperVersions', orgId ?? null]) so the cache is scoped per org.UpsertClusterpasses the route'sorganizationIdinto the query (the deploy screen already has it viauseParams).currenttag + upgrade-only filtering) is untouched — org-deployed versions simply flow into the base list it builds on.Notes
organizationId→ same request as today (/HarperVersions/).Tests
getHarperVersionsQuery.test.ts: adds coverage that the org id scopes the cache key and is passed url-encoded as a query param. Full suite green via the pre-commit hook (1734 passed), typecheck (tsc -b), oxlint, and dprint all clean.🤖 Generated with Claude Code