Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
69 changes: 69 additions & 0 deletions apps/web/src/bench/grant-names.ts
Original file line number Diff line number Diff line change
@@ -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:<id>" -> 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<string, string>();
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<string, string>();
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;
},
};
}
13 changes: 8 additions & 5 deletions apps/web/src/bench/grants-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<GrantEffect, string> = {
allow: "Always allow",
Expand Down Expand Up @@ -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<readonly Grant[]>(
Expand Down Expand Up @@ -81,8 +84,8 @@ export function GrantsTab({
{grants.map((grant) => (
<li key={grant.id} className="bench-tab-row">
<span className="bench-tab-text">
<span className="workbench-info-cell-primary" title={grant.resource}>
{resourceName(grant.resource)}
<span className="workbench-info-cell-primary">
{names.resource(grant.resource) ?? resourceName(grant.resource)}
</span>
<span className="workbench-info-cell-context">{who(grant)}</span>
</span>
Expand Down
Loading