From d1f27903a9c68cd7a3476177aced902907bacbfb Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 20 Aug 2026 18:50:45 +0000 Subject: [PATCH] fix(ui): repair rebased storybook types Co-Authored-By: Paperclip --- .../panels/GatewayActivityPanel.test.tsx | 4 +- .../stories/activity-feed.stories.tsx | 1 + ui/storybook/stories/policies-tab.stories.tsx | 319 ------------------ .../stories/runtime-health.stories.tsx | 246 -------------- 4 files changed, 4 insertions(+), 566 deletions(-) delete mode 100644 ui/storybook/stories/policies-tab.stories.tsx delete mode 100644 ui/storybook/stories/runtime-health.stories.tsx diff --git a/ui/src/pages/apps/gateways/panels/GatewayActivityPanel.test.tsx b/ui/src/pages/apps/gateways/panels/GatewayActivityPanel.test.tsx index 3e6c521048f..db45297608b 100644 --- a/ui/src/pages/apps/gateways/panels/GatewayActivityPanel.test.tsx +++ b/ui/src/pages/apps/gateways/panels/GatewayActivityPanel.test.tsx @@ -108,7 +108,9 @@ describe("GatewayActivityPanel", () => { function clickButton(text: string) { const button = [...container.querySelectorAll("button")].find((item) => item.textContent?.includes(text)); expect(button, `button containing ${text}`).toBeTruthy(); - return act(async () => button!.dispatchEvent(new MouseEvent("click", { bubbles: true }))); + return act(async () => { + button!.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); } it("filters by gateway and expands redacted call details", async () => { diff --git a/ui/storybook/stories/activity-feed.stories.tsx b/ui/storybook/stories/activity-feed.stories.tsx index 77b8df695f4..27cfb821244 100644 --- a/ui/storybook/stories/activity-feed.stories.tsx +++ b/ui/storybook/stories/activity-feed.stories.tsx @@ -36,6 +36,7 @@ function event(overrides: Partial = {}): ToolGatewayAc }, createdAt: new Date(Date.now() - 5 * MIN).toISOString(), ...overrides, + invocation: overrides.invocation ?? null, }; } diff --git a/ui/storybook/stories/policies-tab.stories.tsx b/ui/storybook/stories/policies-tab.stories.tsx deleted file mode 100644 index 14ae10f9256..00000000000 --- a/ui/storybook/stories/policies-tab.stories.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import { useEffect, useMemo, useRef } from "react"; -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import type { ToolCatalogEntry, ToolConnection, ToolPolicy } from "@paperclipai/shared"; -import { queryKeys } from "@/lib/queryKeys"; -import { PoliciesTab } from "@/pages/tools/PoliciesTab"; - -const COMPANY = "company-storybook"; - -function makeTool( - id: string, - toolName: string, - cap: "read" | "write" | "destructive", - title: string, - applicationId: string, - connectionId: string, -): ToolCatalogEntry { - return { - id, - companyId: COMPANY, - applicationId, - connectionId, - entryKind: "tool", - name: toolName, - toolName, - title, - description: title, - inputSchema: null, - outputSchema: null, - annotations: null, - riskLevel: cap, - isReadOnly: cap === "read", - isWrite: cap !== "read", - isDestructive: cap === "destructive", - status: "active", - addedAt: new Date("2026-06-01T00:00:00Z"), - version: null, - versionHash: null, - schemaHash: null, - firstSeenAt: new Date("2026-06-01T00:00:00Z"), - lastSeenAt: new Date("2026-06-01T00:00:00Z"), - reviewedAt: null, - reviewedByAgentId: null, - reviewedByUserId: null, - createdAt: new Date("2026-06-01T00:00:00Z"), - updatedAt: new Date("2026-06-01T00:00:00Z"), - } as ToolCatalogEntry; -} - -const GMAIL: ToolCatalogEntry[] = [ - makeTool("g-list", "gmail.list_messages", "read", "List messages", "app-gmail", "conn-gmail"), - makeTool("g-read", "gmail.get_message", "read", "Read a message", "app-gmail", "conn-gmail"), - makeTool("g-send", "gmail.send_message", "write", "Send a message", "app-gmail", "conn-gmail"), - makeTool("g-draft", "gmail.create_draft", "write", "Create a draft", "app-gmail", "conn-gmail"), - makeTool("g-trash", "gmail.trash_message", "destructive", "Move to trash", "app-gmail", "conn-gmail"), - makeTool("g-delete", "gmail.delete_message", "destructive", "Permanently delete", "app-gmail", "conn-gmail"), -]; - -const SLACK: ToolCatalogEntry[] = [ - makeTool("s-list", "slack.list_channels", "read", "List channels", "app-slack", "conn-slack"), - makeTool("s-post", "slack.post_message", "write", "Post a message", "app-slack", "conn-slack"), - makeTool("s-archive", "slack.archive_channel", "destructive", "Archive a channel", "app-slack", "conn-slack"), -]; - -const CATALOG: ToolCatalogEntry[] = [...GMAIL, ...SLACK]; - -const CONNECTIONS: ToolConnection[] = [ - { - id: "conn-gmail", - companyId: COMPANY, - applicationId: "app-gmail", - name: "Gmail", - uid: "gmail/gmail", - connectionKind: "managed", - ownership: "customer", - transport: "mcp_remote", - authKind: "oauth", - status: "active", - transportConfig: {}, - credentialSecretRefs: [], - healthStatus: "ok", - healthCheckedAt: null, - lastError: null, - enabled: true, - createdByAgentId: null, - createdByUserId: null, - createdAt: new Date("2026-06-01T00:00:00Z"), - updatedAt: new Date("2026-06-01T00:00:00Z"), - } as ToolConnection, - { - id: "conn-slack", - companyId: COMPANY, - applicationId: "app-slack", - name: "Slack", - uid: "slack/slack", - connectionKind: "managed", - ownership: "customer", - transport: "mcp_remote", - authKind: "oauth", - status: "active", - transportConfig: {}, - credentialSecretRefs: [], - healthStatus: "ok", - healthCheckedAt: null, - lastError: null, - enabled: true, - createdByAgentId: null, - createdByUserId: null, - createdAt: new Date("2026-06-01T00:00:00Z"), - updatedAt: new Date("2026-06-01T00:00:00Z"), - } as ToolConnection, -]; - -const AGENTS = [ - { id: "agent-fable", name: "Fable" }, - { id: "agent-sage", name: "Sage" }, - { id: "agent-atlas", name: "Atlas" }, -]; - -const PROJECTS = [ - { id: "project-launch", name: "Launch" }, - { id: "project-support", name: "Support" }, -]; - -function rule(partial: Partial & { id: string; policyType: ToolPolicy["policyType"]; priority: number }): ToolPolicy { - return { - id: partial.id, - companyId: COMPANY, - name: partial.name ?? partial.id, - description: partial.description ?? null, - policyType: partial.policyType, - priority: partial.priority, - enabled: partial.enabled ?? true, - selectors: partial.selectors ?? {}, - conditions: partial.conditions ?? null, - config: partial.config ?? null, - createdByAgentId: null, - createdByUserId: null, - createdAt: new Date("2026-06-01T00:00:00Z"), - updatedAt: new Date("2026-06-11T00:00:00Z"), - }; -} - -const POLICIES: ToolPolicy[] = [ - rule({ - id: "p1", - name: "Block destructive actions everywhere", - policyType: "block", - priority: 50, - selectors: { riskLevel: "destructive" }, - }), - rule({ - id: "p2", - name: "Ask first when Fable sends mail", - policyType: "require_approval", - priority: 100, - selectors: { agentId: "agent-fable", toolName: "gmail.send_message" }, - }), - rule({ - id: "p3", - name: "Limit Slack posts", - policyType: "rate_limit", - priority: 150, - selectors: { toolName: "slack.post_message" }, - config: { rateLimit: { limit: 25, windowSeconds: 3600, keyBy: ["agent", "tool"] } }, - }), - rule({ - id: "p4", - name: "Allow Sage to use Gmail", - policyType: "allow", - priority: 200, - selectors: { agentId: "agent-sage", applicationId: "app-gmail" }, - }), - rule({ - id: "p5", - name: "Block critical actions", - policyType: "block", - priority: 300, - selectors: { riskLevel: "critical" }, - }), -]; - -const TRUST_RULES: ToolPolicy[] = [ - rule({ - id: "trust-1", - policyType: "trust_rule", - priority: 1000, - selectors: { agentId: "agent-fable", toolName: "gmail.send_message" }, - }), - rule({ - id: "trust-2", - policyType: "trust_rule", - priority: 1000, - selectors: { agentId: "agent-sage", toolName: "slack.post_message" }, - }), -]; - -const AUDIT_HITS = [ - { policyId: "p1", count: 14 }, - { policyId: "p2", count: 6 }, - { policyId: "p3", count: 22 }, - { policyId: "p4", count: 3 }, - { policyId: "p5", count: 1 }, -]; - -function buildAudit() { - const now = Date.now(); - const rows: Array<{ createdAt: string; details: { matchedPolicyIds: string[] } }> = []; - for (const hit of AUDIT_HITS) { - for (let i = 0; i < hit.count; i += 1) { - rows.push({ - createdAt: new Date(now - i * 60_000).toISOString(), - details: { matchedPolicyIds: [hit.policyId] }, - }); - } - } - return rows; -} - -function seededClient({ - policies, - trustRules, -}: { - policies: ToolPolicy[]; - trustRules: ToolPolicy[]; -}) { - const c = new QueryClient({ - defaultOptions: { - queries: { staleTime: Infinity, gcTime: Infinity, retry: false, refetchOnMount: false }, - }, - }); - c.setQueryData(queryKeys.tools.policies(COMPANY), { policies }); - c.setQueryData(queryKeys.tools.trustRules(COMPANY), { trustRules }); - c.setQueryData(queryKeys.tools.audit(COMPANY, 250), buildAudit()); - c.setQueryData(queryKeys.agents.list(COMPANY), AGENTS); - c.setQueryData(queryKeys.projects.list(COMPANY), PROJECTS); - c.setQueryData(queryKeys.tools.applications(COMPANY), { - applications: [ - { id: "app-gmail", name: "Gmail" }, - { id: "app-slack", name: "Slack" }, - ], - }); - c.setQueryData(queryKeys.tools.connections(COMPANY), { connections: CONNECTIONS }); - c.setQueryData(queryKeys.tools.catalog("conn-gmail"), { catalog: GMAIL }); - c.setQueryData(queryKeys.tools.catalog("conn-slack"), { catalog: SLACK }); - return c; -} - -function findButtonByText(label: string): HTMLButtonElement | null { - const buttons = Array.from(document.querySelectorAll("button")); - return buttons.find((b) => (b.textContent ?? "").trim() === label) ?? null; -} - -function PoliciesHost({ - policies = POLICIES, - trustRules = TRUST_RULES, - autoClick, -}: { - policies?: ToolPolicy[]; - trustRules?: ToolPolicy[]; - autoClick?: "new-rule" | "test-rule"; -}) { - const client = useMemo(() => seededClient({ policies, trustRules }), [policies, trustRules]); - const triggered = useRef(false); - useEffect(() => { - if (!autoClick || triggered.current) return; - let cancelled = false; - const tryClick = (attempt: number) => { - if (cancelled || triggered.current) return; - const label = autoClick === "new-rule" ? "New rule" : "Test a rule"; - const button = findButtonByText(label); - if (button) { - triggered.current = true; - button.click(); - return; - } - if (attempt < 30) window.setTimeout(() => tryClick(attempt + 1), 50); - }; - tryClick(0); - return () => { - cancelled = true; - }; - }, [autoClick]); - return ( - -
- -
-
- ); -} - -const meta: Meta = { - title: "Tools/Rules (PAP-11049)", - parameters: { layout: "fullscreen" }, -}; -export default meta; - -type Story = StoryObj; - -export const IndexPopulated: Story = { - name: "Rules index — populated", - render: () => , -}; - -export const Empty: Story = { - name: "Rules — empty state", - render: () => , -}; - -export const BuilderNewRule: Story = { - name: "Rule builder — after New rule", - render: () => , -}; - -export const TestRuleSlideover: Story = { - name: "Test a rule — slide-over", - render: () => , -}; diff --git a/ui/storybook/stories/runtime-health.stories.tsx b/ui/storybook/stories/runtime-health.stories.tsx deleted file mode 100644 index cf15204a3b5..00000000000 --- a/ui/storybook/stories/runtime-health.stories.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { useMemo } from "react"; -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import type { - ToolConnection, - ToolRuntimeAlertRecommendation, - ToolRuntimeHealthSummary, - ToolRuntimeSlot, -} from "@paperclipai/shared"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { queryKeys } from "@/lib/queryKeys"; -import { RuntimeTab } from "@/pages/tools/RuntimeTab"; - -const COMPANY = "company-storybook"; - -function slot(overrides: Partial = {}): ToolRuntimeSlot { - return { - id: "slot-gmail", - companyId: COMPANY, - applicationId: "app-gmail", - connectionId: "conn-gmail", - projectWorkspaceId: null, - executionWorkspaceId: null, - issueId: null, - ownerScopeType: "company", - ownerScopeId: null, - runtimeKind: "local_stdio", - slotKey: "gmail-stdio-local", - status: "running", - reuseKey: null, - workspaceScope: null, - credentialScopeHash: null, - provider: null, - providerRef: null, - processId: 41832, - commandTemplateKey: "gmail", - healthStatus: "healthy", - lastHealthCheckAt: null, - idleExpiresAt: null, - startedAt: new Date("2026-06-13T10:46:00Z"), - stoppedAt: null, - lastUsedAt: new Date("2026-06-13T12:55:00Z"), - lastError: null, - metadata: null, - createdAt: new Date("2026-06-13T10:46:00Z"), - updatedAt: new Date("2026-06-13T13:00:00Z"), - ...overrides, - } as ToolRuntimeSlot; -} - -function connection(overrides: Partial = {}): ToolConnection { - return { - id: "conn-gmail", - companyId: COMPANY, - applicationId: "app-gmail", - name: "Gmail", - connectionKind: "managed", - transport: "local_stdio", - status: "active", - transportConfig: {}, - credentialSecretRefs: [], - healthStatus: "healthy", - healthCheckedAt: null, - lastError: null, - enabled: true, - createdByAgentId: null, - createdByUserId: null, - createdAt: new Date("2026-06-13T10:46:00Z"), - updatedAt: new Date("2026-06-13T13:00:00Z"), - ...overrides, - } as ToolConnection; -} - -const SLOTS: ToolRuntimeSlot[] = [ - slot(), - slot({ - id: "slot-sheets", - applicationId: "app-sheets", - connectionId: "conn-sheets", - slotKey: "sheets-stdio-local", - commandTemplateKey: "google_sheets", - processId: 41877, - lastUsedAt: new Date("2026-06-13T12:51:00Z"), - }), - slot({ - id: "slot-slack", - applicationId: "app-slack", - connectionId: "conn-slack", - runtimeKind: "remote_session", - slotKey: "slack-remote", - commandTemplateKey: null, - providerRef: "slack", - processId: null, - lastUsedAt: new Date("2026-06-13T12:42:00Z"), - }), - slot({ - id: "slot-github", - applicationId: "app-github", - connectionId: "conn-github", - slotKey: "github-stdio-local", - commandTemplateKey: "github", - processId: 41901, - lastUsedAt: new Date("2026-06-13T12:59:00Z"), - }), -]; - -const CONNECTIONS: ToolConnection[] = [ - connection(), - connection({ id: "conn-sheets", applicationId: "app-sheets", name: "Google Sheets" }), - connection({ id: "conn-slack", applicationId: "app-slack", name: "Slack", transport: "mcp_remote" }), - connection({ id: "conn-github", applicationId: "app-github", name: "GitHub" }), -]; - -function alert(overrides: Partial = {}): ToolRuntimeAlertRecommendation { - return { - name: "mcp_runtime_stuck_running_slot", - severity: "critical", - status: "firing", - threshold: "Any running slot with no progress for 5 minutes.", - observed: "1 stuck running slot(s).", - description: "A runtime slot is running but has not recorded progress inside the supervisor stuck-slot window.", - firstResponderAction: "Inspect recent audit events and active tool calls; restart the slot only after confirming no healthy call is still in progress.", - runbookSection: "runbook.md#stuck-running-slot", - ...overrides, - }; -} - -function health(overrides: Partial = {}): ToolRuntimeHealthSummary { - return { - status: "ok", - generatedAt: new Date("2026-06-13T13:00:00Z"), - runbookPath: "docs/runbooks/mcp-runtime.md", - metrics: { - windowStartedAt: new Date("2026-06-13T12:00:00Z"), - windowEndedAt: new Date("2026-06-13T13:00:00Z"), - activeSlots: 4, - startingSlots: 0, - runningSlots: 4, - idleSlots: 0, - failedSlots: 0, - stoppedSlots: 0, - stuckStartingSlots: 0, - stuckRunningSlots: 0, - capacityDeferralsLastHour: 0, - restartAttemptsLastHour: 0, - restartSuppressionsLastHour: 0, - idleEvictionsLastHour: 0, - toolCallsLastHour: 128, - toolTimeoutsLastHour: 0, - toolFailuresLastHour: 0, - timeoutRateLastHour: 0, - failureRateLastHour: 0, - averageToolLatencyMsLastHour: 1200, - p95ToolLatencyMsLastHour: 2100, - missingSecretFailuresLastHour: 0, - auditWriteFailuresLastHour: 0, - activeConnections: 4, - disabledConnections: 0, - degradedConnections: 0, - remoteHttpConnections: 1, - localStdioConnections: 3, - }, - supportMatrix: { - remoteHttp: { supported: true, note: "" }, - localStdio: { supported: true, note: "" }, - }, - alerts: [], - recommendations: [], - ...overrides, - }; -} - -function Seeded({ - slots, - connections, - summary, -}: { - slots: ToolRuntimeSlot[]; - connections: ToolConnection[]; - summary: ToolRuntimeHealthSummary; -}) { - const client = useMemo(() => { - const c = new QueryClient({ - defaultOptions: { queries: { staleTime: Infinity, gcTime: Infinity, retry: false, refetchOnMount: false } }, - }); - c.setQueryData(queryKeys.tools.runtimeSlots(COMPANY), { runtimeSlots: slots }); - c.setQueryData(queryKeys.tools.runtimeHealth(COMPANY), summary); - c.setQueryData(queryKeys.tools.connections(COMPANY), { connections }); - return c; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - return ( - - -
- -
-
-
- ); -} - -const meta: Meta = { - title: "Tools/Health (runtime)", - parameters: { layout: "fullscreen" }, -}; -export default meta; - -type Story = StoryObj; - -export const AllGood: Story = { - name: "All good", - render: () => , -}; - -export const NeedsAttention: Story = { - name: "Needs attention", - render: () => ( - - ), -}; - -export const RowExpanded: Story = { - name: "Row expanded (use the chevron)", - render: () => , -};