Files
PaperClipAI/ui/storybook/support/github-bot-management/fixture-api.tsx
T
DottaandPaperclip f329b24ee1 feat: give tasks durable project-optional workspaces (#15749)
Give tasks durable company-scoped workspace bindings independent of project assignment, channel defaults and authorized repository preparation. Reuse verified input seeds and sparse output recovery while preserving trust, privacy and controller provisioning boundaries.

Full CI and E2E, Superagent, legacy checks and Greptile Apex 5/5 passed on reviewed head 753680b61c. All review findings are resolved; the PR records focused verification and historical acceptance limits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-12 20:45:18 -05:00

195 lines
9.2 KiB
TypeScript

import { useLayoutEffect, useState, type ReactNode } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { queryKeys } from "@/lib/queryKeys";
import { buildSkillMentionHref, type ChatExecutionDefaults } from "@paperclipai/shared";
import { EditorAutocompleteProvider } from "@/context/EditorAutocompleteContext";
import {
endpoint,
agent,
configuration,
resources,
reviews,
conversations,
links,
members,
} from "./fixtures";
export type FixtureState = "populated" | "empty" | "loading" | "error" | "long" | "many" | "setup" | "skills" | "guests" | "mentions";
const reviewSkill = {
id: "11111111-1111-4111-8111-111111111111", slug: "code-review", name: "Code review",
key: "company/company-storybook/code-review", description: "Follow the team's review playbook.",
};
/** Only fixture IDs are intercepted. All shell requests use Storybook's shared API fixtures. */
export function FixtureApi({
state = "populated",
children,
}: {
state?: FixtureState;
children: ReactNode;
}) {
const [client] = useState(() => {
const client = new QueryClient({
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
});
client.setQueryData(queryKeys.chatEndpoints.detail(endpoint.id), endpoint);
client.setQueryData(queryKeys.agents.detail(agent.id), agent);
client.setQueryData(["github-setup", endpoint.id], state === "setup"
? { ...endpoint, status: "draft", setup: { github: { stage: "setup" } } } : endpoint);
client.setQueryData(["github-wizard", endpoint.id], { endpointId: endpoint.id, state: state === "setup" ? "create" : "connected" });
client.setQueryData(queryKeys.chatEndpoints.list(endpoint.companyId), [endpoint]);
client.setQueryData(queryKeys.projects.list(endpoint.companyId), []);
client.setQueryData(["channel-workspace-choices", endpoint.companyId], []);
client.setQueryData(["project-repositories", endpoint.companyId], {
repositories: [
{ id: "100", fullName: "acme/web", ownerType: "organization", url: "https://github.com/acme/web", connections: ["GitHub"] },
{ id: "101", fullName: "paperclipai/api", ownerType: "organization", url: "https://github.com/paperclipai/api", connections: ["GitHub"] },
{ id: "102", fullName: "mayacoder/site", ownerType: "personal", url: "https://github.com/mayacoder/site", connections: ["GitHub"] },
], connectionCount: 1, failedConnectionCount: 0,
});
client.setQueryData(["github-members", endpoint.companyId], members);
client.setQueryData(queryKeys.companySkills.list(endpoint.companyId), [reviewSkill]);
return client;
});
const [ready, setReady] = useState(false);
useLayoutEffect(() => {
const original = window.fetch;
let currentEndpoint = state === "setup"
? { ...endpoint, status: "draft", setup: { github: { stage: "setup" } } }
: structuredClone(endpoint);
let saved = { revision: 2, configuration: structuredClone(configuration) };
if (state === "skills") saved.configuration.defaults = {
...saved.configuration.defaults,
instructions: `Use [/code-review](${buildSkillMentionHref(reviewSkill.id, reviewSkill.slug)}) when reviewing pull requests.`,
invocation: "linked_authors",
};
if (state === "guests") saved.configuration.people.push({ kind: "guest", githubUserId: "99", login: "community-contributor", sponsorUserId: "user-board", permissionProfile: "restricted", automaticReviews: true });
if (state === "mentions") { saved.configuration.memberAccess = "all_linked"; saved.configuration.defaults.invocation = "mentions_only"; }
let repos = structuredClone(resources);
if (state === "many") repos = Array.from({ length: 1000 }, (_, index) => ({
...repos[0], id: `repository-${index}`, providerResourceId: `acme/repository-${String(index).padStart(4, "0")}`,
label: `acme/repository-${String(index).padStart(4, "0")}`, enabled: true,
}));
let identities = structuredClone(links);
const failedPaths = new Set<string>();
window.fetch = async (input, init) => {
const raw =
typeof input === "string"
? input
: input instanceof URL
? input.href
: input.url;
const url = new URL(raw, window.location.origin);
const path = url.pathname;
if (!path.includes(`/chat-endpoints/${endpoint.id}`))
return original(input, init);
if (state === "loading") return new Promise<Response>(() => {});
if (
state === "error" &&
(path.endsWith("/configuration") || path.endsWith("/resources") || path.endsWith("/github/repositories")) &&
!failedPaths.has(path)
) {
failedPaths.add(path);
return Response.json(
{ error: "Could not reach GitHub. Try again." },
{ status: 503 },
);
}
const body = init?.body ? JSON.parse(String(init.body)) : {};
if (path === `/api/chat-endpoints/${endpoint.id}` && init?.method === "PATCH") {
currentEndpoint = { ...currentEndpoint, ...body };
}
if (path.endsWith("/github/setup")) return Response.json({ endpointId: endpoint.id, state: state === "setup" ? "create" : "connected" });
if (path.endsWith("/repositories/access")) {
repos = repos.map((r) => (!body.enabled || r.availability === "available") ? { ...r, enabled: body.enabled } : r);
return Response.json({ success: true });
}
if (path.endsWith("/github/repositories")) {
const offset = Number(url.searchParams.get("offset") ?? 0);
const limit = Number(url.searchParams.get("limit") ?? 20);
const search = (url.searchParams.get("search") ?? "").toLowerCase();
const all = state === "empty" ? [] : state === "long" ? repos.map((r) => ({ ...r,
label: `acme/platform-services-production-web-${r.id}-accessibility-improvements` })) : repos;
const filtered = all.filter((r) => r.label.toLowerCase().includes(search));
return Response.json({ items: filtered.slice(offset, offset + limit),
nextOffset: offset + limit < filtered.length ? offset + limit : null,
totalCount: all.length, enabledCount: all.filter((r) => r.enabled).length,
availableCount: all.filter((r) => r.availability === "available").length });
}
if (path.endsWith("/configuration")) {
if (init?.method === "PUT")
saved = {
revision: saved.revision + 1,
configuration: body.configuration,
};
return Response.json(saved);
}
if (path.endsWith("/repositories/refresh")) return Response.json(repos);
if (path.endsWith("/resources")) {
if (init?.method === "PUT")
repos = repos.map((r) => {
const change = body.resources?.find(
(entry: { id: string; enabled?: boolean; executionDefaults?: ChatExecutionDefaults | null }) => entry.id === r.id,
);
return {
...r,
enabled: change?.enabled ?? r.enabled,
executionDefaults: change?.executionDefaults === undefined ? r.executionDefaults : change.executionDefaults,
};
});
const rows =
state === "empty"
? []
: state === "long"
? repos.map((r) => ({
...r,
label: `acme/platform-services-production-web-${r.id}-accessibility-improvements`,
}))
: repos;
return Response.json({ items: rows });
}
const reviewDetail = /^\/api\/chat-endpoints\/[^/]+\/github\/reviews\/([^/]+)$/.exec(path);
if (reviewDetail) {
const review = state === "empty" ? undefined : reviews.find((r) => r.id === decodeURIComponent(reviewDetail[1]));
return review ? Response.json(review) : Response.json({ error: "Review not found" }, { status: 404 });
}
if (path.endsWith("/reviews"))
return Response.json(state === "empty" ? [] : reviews);
if (path.endsWith("/activity"))
return Response.json({ items: [], nextCursor: null });
if (path.endsWith("/conversations"))
return Response.json({
items:
state === "empty"
? []
: state === "long"
? conversations.map((r) => ({
...r,
issueTitle: `${r.issueTitle} for every keyboard, screen reader, and narrow screen entry point`,
}))
: conversations,
});
if (path.endsWith("/principals"))
return Response.json({ items: state === "empty" ? [] : identities });
if (path.endsWith("/link") && init?.method === "DELETE") {
identities = identities.filter(
(link) => !path.includes(link.principalId),
);
return Response.json({ success: true });
}
if (path.endsWith("/people/lookup"))
return Response.json({ githubUserId: "44", login: body.login });
return Response.json(currentEndpoint);
};
setReady(true);
return () => {
window.fetch = original;
client.clear();
};
}, [client, state]);
return ready ? (
<QueryClientProvider client={client}>
<EditorAutocompleteProvider>{children}</EditorAutocompleteProvider>
</QueryClientProvider>
) : null;
}