From 5c17ee3cf6da458d8ef091699461d854bcd85861 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 29 Sep 2026 23:21:45 -0700 Subject: [PATCH] fix(web): grants show bench and credential names, never raw ids (CL-9602) --- apps/web/src/bench/grant-names.ts | 69 +++++++++++++++++++++++++++++++ apps/web/src/bench/grants-tab.tsx | 13 +++--- 2 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/bench/grant-names.ts diff --git a/apps/web/src/bench/grant-names.ts b/apps/web/src/bench/grant-names.ts new file mode 100644 index 000000000..ca42ef998 --- /dev/null +++ b/apps/web/src/bench/grant-names.ts @@ -0,0 +1,69 @@ +import { useQueries, useQuery } from "@tanstack/react-query"; + +import { fetchTenantDetail } from "../api"; +import { tenantKeys } from "../query-client"; +import { listCredentials } from "../settings/credentials-api"; + +const WORKSPACE = "Workspace"; +const UNKNOWN_CREDENTIAL = "A credential"; +const CREDENTIAL_PREFIX = "credential:"; +const TENANT_PREFIX = "tenant:"; + +export type GrantNames = { + /** "credential:crd_…" -> the credential's name; "tenant:" -> the + * bench name or "Workspace". Undefined for any other resource. */ + readonly resource: (resource: string) => string | undefined; + /** Replaces every known tenant id inside `text` with its name. */ + readonly replaceTenantIds: (text: string) => string; +}; + +/** Names for ids that appear in a workbench's grants, from the tenant and + * credential reads the rest of the app already caches. */ +export function useGrantNames(workbenchTenantId: string): GrantNames { + const own = useQuery({ + queryKey: tenantKeys.detail(workbenchTenantId), + queryFn: () => fetchTenantDetail(workbenchTenantId), + staleTime: 30_000, + }); + const parentId = own.data?.parentId ?? null; + const parent = useQuery({ + queryKey: tenantKeys.detail(parentId ?? "none"), + queryFn: () => fetchTenantDetail(parentId ?? ""), + enabled: parentId !== null, + staleTime: 30_000, + }); + const credentialScopes = parentId === null ? [workbenchTenantId] : [workbenchTenantId, parentId]; + const credentials = useQueries({ + queries: credentialScopes.map((tenantId) => ({ + queryKey: tenantKeys.credentials(tenantId), + queryFn: () => listCredentials(tenantId), + })), + }); + + const tenantNames = new Map(); + if (own.data !== undefined) { + tenantNames.set(workbenchTenantId, own.data.parentId === null ? WORKSPACE : own.data.name); + } + if (parentId !== null && parent.data !== undefined) tenantNames.set(parentId, WORKSPACE); + const credentialNames = new Map(); + for (const result of credentials) { + for (const credential of result.data ?? []) credentialNames.set(credential.id, credential.name); + } + + return { + resource: (resource) => { + if (resource.startsWith(CREDENTIAL_PREFIX)) { + return credentialNames.get(resource.slice(CREDENTIAL_PREFIX.length)) ?? UNKNOWN_CREDENTIAL; + } + if (resource.startsWith(TENANT_PREFIX)) { + return tenantNames.get(resource.slice(TENANT_PREFIX.length)) ?? WORKSPACE; + } + return undefined; + }, + replaceTenantIds: (text) => { + let out = text; + for (const [id, name] of tenantNames) out = out.split(id).join(name); + return out; + }, + }; +} diff --git a/apps/web/src/bench/grants-tab.tsx b/apps/web/src/bench/grants-tab.tsx index 0cde8bc77..d76fde299 100644 --- a/apps/web/src/bench/grants-tab.tsx +++ b/apps/web/src/bench/grants-tab.tsx @@ -8,6 +8,7 @@ import { tenantKeys } from "@/query-client"; import { GRANT_RESOURCE_LABEL, type GrantResource } from "../settings/resource-vocabulary"; import { principalLabel } from "../settings/identity"; import { listGrants, revokeGrant, type Grant } from "../settings/tenancy-api"; +import { useGrantNames } from "./grant-names"; const MODE: Record = { allow: "Always allow", @@ -38,13 +39,15 @@ export function GrantsTab({ readonly workbenchTenantId: string; readonly participants: readonly WorkbenchParticipant[]; }) { + const names = useGrantNames(workbenchTenantId); const who = (grant: Grant): string => { if (grant.roleName !== undefined && grant.roleName !== null) return grant.roleName; const known = participants.find((p) => p.id === grant.principalId); if (known !== undefined) return known.name; - return grant.principalName === undefined || grant.principalName === null - ? "Everyone here" - : principalLabel(grant.principalName).label; + if (grant.principalName === undefined || grant.principalName === null) return "Everyone here"; + const named = names.replaceTenantIds(grant.principalName); + // A raw run id is never shown, and never turned into words. + return principalLabel(named).raw === null ? named : "A worker"; }; const queryClient = useQueryClient(); const query = toAPIQuery( @@ -81,8 +84,8 @@ export function GrantsTab({ {grants.map((grant) => (
  • - - {resourceName(grant.resource)} + + {names.resource(grant.resource) ?? resourceName(grant.resource)} {who(grant)}