diff --git a/ui/src/pages/Secrets.render.test.tsx b/ui/src/pages/Secrets.render.test.tsx index ca75e981dd0..fedc48e4da1 100644 --- a/ui/src/pages/Secrets.render.test.tsx +++ b/ui/src/pages/Secrets.render.test.tsx @@ -575,13 +575,11 @@ describe("Secrets page layout", () => { }); await flushReact(); - const usageButton = Array.from(document.body.querySelectorAll("button")).find( - (button) => button.textContent?.includes("Usage"), + const viewUsageButton = Array.from(document.body.querySelectorAll("button")).find( + (button) => button.textContent?.includes("View in Usage"), ) as HTMLButtonElement | undefined; await act(async () => { - usageButton?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true })); - usageButton?.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter" })); - usageButton?.click(); + viewUsageButton?.click(); }); await flushReact(); @@ -620,6 +618,17 @@ describe("Secrets page layout", () => { expect(container.textContent).toContain("Each user"); expect(container.textContent).toContain("3/5 set"); expect(container.textContent).not.toContain("User secret definitions"); + + const listContainer = container.querySelector('[data-testid="secrets-list-container"]'); + const tableView = container.querySelector('[data-testid="secrets-table-view"]'); + const cardView = container.querySelector('[data-testid="secrets-card-view"]'); + expect(listContainer?.className).toContain("@container"); + expect(tableView?.className).toContain("@min-[40rem]:block"); + expect(tableView?.className).not.toContain("md:block"); + expect(tableView?.querySelector("[role='row']")?.className).toContain("minmax(12rem,2.4fr)"); + expect(cardView?.className).toContain("@min-[40rem]:hidden"); + expect(cardView?.className).not.toContain("md:hidden"); + expect(mockSecretsApi.list).toHaveBeenCalledWith("company-1"); expect(mockSecretsApi.listUserSecretDefinitions).toHaveBeenCalledWith("company-1"); @@ -704,8 +713,8 @@ describe("Secrets page layout", () => { }); it("opens the New secret dialog when provider queries fail", async () => { - mockSecretsApi.providers.mockRejectedValueOnce(new ApiError(403, "Providers unavailable")); - mockSecretsApi.providerConfigs.mockRejectedValueOnce(new ApiError(403, "Provider vaults unavailable")); + mockSecretsApi.providers.mockRejectedValueOnce(new ApiError("Providers unavailable", 403, null)); + mockSecretsApi.providerConfigs.mockRejectedValueOnce(new ApiError("Provider vaults unavailable", 403, null)); const root = createRoot(container); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, @@ -774,13 +783,11 @@ describe("Secrets page layout", () => { expect(document.body.textContent).toContain("Usage"); expect(document.body.textContent).toContain("Access events"); - const coverageButton = Array.from(document.body.querySelectorAll("button")).find( - (button) => button.textContent?.trim() === "Coverage", + const viewCoverageButton = Array.from(document.body.querySelectorAll("button")).find( + (button) => button.textContent?.includes("View in Coverage"), ) as HTMLButtonElement | undefined; await act(async () => { - coverageButton?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true })); - coverageButton?.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter" })); - coverageButton?.click(); + viewCoverageButton?.click(); }); await flushReact(); diff --git a/ui/src/pages/Secrets.tsx b/ui/src/pages/Secrets.tsx index d3924b7d850..0a9cf3ce476 100644 --- a/ui/src/pages/Secrets.tsx +++ b/ui/src/pages/Secrets.tsx @@ -1514,15 +1514,16 @@ export function Secrets() { ) : filteredRows.length === 0 ? ( ) : ( -
+
Secret
Status
@@ -1547,7 +1548,7 @@ export function Secrets() { key={row.id} role="row" className={cn( - "grid cursor-pointer grid-cols-[minmax(0,2.4fr)_minmax(5.25rem,0.75fr)_minmax(7rem,1fr)_minmax(5rem,0.7fr)_2.75rem] items-center gap-3 border-b border-border/60 px-3 py-3 hover:bg-accent/40", + "grid cursor-pointer grid-cols-[minmax(12rem,2.4fr)_minmax(5.25rem,0.75fr)_minmax(7rem,1fr)_minmax(5rem,0.7fr)_2.75rem] items-center gap-3 border-b border-border/60 px-3 py-3 hover:bg-accent/40", row.kind === "company" && selectedSecretId === row.secret.id && "bg-accent/60", row.kind === "user" && selectedDefinitionId === row.definition.id && "bg-accent/60", )} @@ -1619,7 +1620,7 @@ export function Secrets() {
-
+
{filteredRows.map((row) => { const status = row.kind === "company" ? row.secret.status : row.definition.status; return ( @@ -1732,10 +1733,12 @@ export function Secrets() { {selectedSecret ? ( <> - + {selectedSecret.name} - + + + {providerLabel(providers, selectedSecret.provider)} secret {selectedSecret.key} @@ -1832,7 +1835,12 @@ export function Secrets() {
- + setSecretDetailTab("usage")} + /> @@ -1850,10 +1858,12 @@ export function Secrets() { ) : selectedDefinition ? ( <> - + {selectedDefinition.name} - + + + Each user secret definition {selectedDefinition.key} @@ -1958,7 +1968,11 @@ export function Secrets() {
- + setSecretDetailTab("coverage")} + /> void; }) { return (
@@ -3497,7 +3513,14 @@ function UserSecretDetailsTab({ - + {formatRelative(definition.createdAt)} {formatRelative(definition.updatedAt)} @@ -3594,11 +3617,17 @@ function SecretDetailsTab({ secret, providers, providerConfigs, + onViewUsage, }: { secret: CompanySecret; providers: SecretProviderDescriptor[]; providerConfigs: CompanySecretProviderConfig[]; + onViewUsage: () => void; }) { + const bindingLabel = (secret.referenceCount ?? 0) === 1 + ? "1 binding" + : `${secret.referenceCount ?? 0} bindings`; + return (
@@ -3617,9 +3646,14 @@ function SecretDetailsTab({ v{secret.latestVersion} - {(secret.referenceCount ?? 0) === 1 - ? "1 binding" - : `${secret.referenceCount ?? 0} bindings`} + {formatRelative(secret.createdAt)} {formatRelative(secret.updatedAt)}