mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
fix(ui): repair rebased storybook types
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
1880c9d21e
commit
d1f27903a9
4 files changed
+4
-566
No files matched your search
@@ -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 () => {
|
||||
|
||||
@@ -36,6 +36,7 @@ function event(overrides: Partial<ToolGatewayActivityEvent> = {}): ToolGatewayAc
|
||||
},
|
||||
createdAt: new Date(Date.now() - 5 * MIN).toISOString(),
|
||||
...overrides,
|
||||
invocation: overrides.invocation ?? 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<ToolPolicy> & { 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<HTMLButtonElement>("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 (
|
||||
<QueryClientProvider client={client}>
|
||||
<div className="mx-auto max-w-6xl p-6">
|
||||
<PoliciesTab companyId={COMPANY} />
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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: () => <PoliciesHost />,
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
name: "Rules — empty state",
|
||||
render: () => <PoliciesHost policies={[]} trustRules={[]} />,
|
||||
};
|
||||
|
||||
export const BuilderNewRule: Story = {
|
||||
name: "Rule builder — after New rule",
|
||||
render: () => <PoliciesHost autoClick="new-rule" />,
|
||||
};
|
||||
|
||||
export const TestRuleSlideover: Story = {
|
||||
name: "Test a rule — slide-over",
|
||||
render: () => <PoliciesHost autoClick="test-rule" />,
|
||||
};
|
||||
@@ -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> = {}): 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> = {}): 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> = {}): 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> = {}): 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 (
|
||||
<QueryClientProvider client={client}>
|
||||
<TooltipProvider>
|
||||
<div className="mx-auto max-w-5xl p-6">
|
||||
<RuntimeTab companyId={COMPANY} />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Tools/Health (runtime)",
|
||||
parameters: { layout: "fullscreen" },
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj;
|
||||
|
||||
export const AllGood: Story = {
|
||||
name: "All good",
|
||||
render: () => <Seeded slots={SLOTS} connections={CONNECTIONS} summary={health()} />,
|
||||
};
|
||||
|
||||
export const NeedsAttention: Story = {
|
||||
name: "Needs attention",
|
||||
render: () => (
|
||||
<Seeded
|
||||
slots={[
|
||||
slot({ healthStatus: "error", lastError: "MCP request timed out after 30000ms" }),
|
||||
...SLOTS.slice(1),
|
||||
]}
|
||||
connections={[connection({ healthStatus: "degraded" }), ...CONNECTIONS.slice(1)]}
|
||||
summary={health({
|
||||
status: "degraded",
|
||||
metrics: {
|
||||
...health().metrics,
|
||||
runningSlots: 3,
|
||||
degradedConnections: 1,
|
||||
toolFailuresLastHour: 11,
|
||||
toolTimeoutsLastHour: 3,
|
||||
timeoutRateLastHour: 12,
|
||||
averageToolLatencyMsLastHour: 2400,
|
||||
p95ToolLatencyMsLastHour: 5200,
|
||||
},
|
||||
alerts: [alert(), alert({ name: "mcp_runtime_connection_health_degraded", observed: "1 degraded connection(s), 0 disabled connection(s)." })],
|
||||
})}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
export const RowExpanded: Story = {
|
||||
name: "Row expanded (use the chevron)",
|
||||
render: () => <Seeded slots={SLOTS} connections={CONNECTIONS} summary={health()} />,
|
||||
};
|
||||
Reference in new issue
Block a user