mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 02:26:12 +02:00
Separate GitHub bot opt-in and add review detail pages
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7c1d2503db
commit
7dcdbe7eb6
36 files changed
+647
-204
No files matched your search
@@ -39,7 +39,7 @@ export function AppsSidebar() {
|
||||
);
|
||||
|
||||
if (pathname.endsWith("/apps/chat/connect")) return <ChatSetupSidebar />;
|
||||
const chatDetail = pathname.match(/\/apps\/chat\/([^/]+)(?:\/(?:settings|access|reviews|conversations|activity))?\/?$/);
|
||||
const chatDetail = pathname.match(/\/apps\/chat\/([^/]+)(?:\/(?:settings|access|conversations|activity)|\/reviews(?:\/[^/]+)?)?\/?$/);
|
||||
if (chatDetail) return <ChatDetailSidebar endpointId={chatDetail[1]} NavItem={SidebarNavItem} />;
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,7 +27,7 @@ export function AppsSidebar() {
|
||||
});
|
||||
|
||||
if (pathname.endsWith("/apps/chat/connect")) return <ChatSetupSidebar />;
|
||||
const chatDetail = pathname.match(/\/apps\/chat\/([^/]+)(?:\/(?:settings|access|reviews|conversations|activity))?\/?$/);
|
||||
const chatDetail = pathname.match(/\/apps\/chat\/([^/]+)(?:\/(?:settings|access|conversations|activity)|\/reviews(?:\/[^/]+)?)?\/?$/);
|
||||
if (chatDetail) return <ChatDetailSidebar endpointId={chatDetail[1]} />;
|
||||
|
||||
return (
|
||||
|
||||
@@ -104,6 +104,31 @@ describe("Chat connectors visibility gate", () => {
|
||||
await vi.waitFor(() => expect(Boolean(container.querySelector("[data-chat-setup]"))).toBe(provider === "agentmail"));
|
||||
if (provider !== "agentmail") expect(container.querySelector("[data-redirect]")).not.toBeNull();
|
||||
});
|
||||
it.each([true, false])("gates GitHub setup by its own flag, not chat (%s)", async (githubEnabled) => {
|
||||
api.provider = "github";
|
||||
api.settings.mockResolvedValue({ enableChatConnectors: !githubEnabled, enableGitHubReviewBots: githubEnabled });
|
||||
await render();
|
||||
expect(Boolean(container.querySelector("[data-chat-setup]"))).toBe(githubEnabled);
|
||||
});
|
||||
it.each(["github", "slack"])("gates each existing endpoint by provider with only GitHub enabled (%s)", async (provider) => {
|
||||
api.endpointId = "endpoint-1";
|
||||
api.get.mockResolvedValue({ provider });
|
||||
api.settings.mockResolvedValue({ enableChatConnectors: false, enableGitHubReviewBots: true });
|
||||
await render();
|
||||
await vi.waitFor(() => expect(api.get).toHaveBeenCalledWith("endpoint-1"));
|
||||
await vi.waitFor(() => expect(Boolean(container.querySelector("[data-chat-setup]"))).toBe(provider === "github"));
|
||||
});
|
||||
it("keeps GitHub agent channels visible while hiding Slack under the independent flag", async () => {
|
||||
api.settings.mockResolvedValue({ enableChatConnectors: false, enableGitHubReviewBots: true });
|
||||
api.list.mockResolvedValue([
|
||||
{ provider: "github", id: "github", assignedAgentId: "agent-1", status: "active" },
|
||||
{ provider: "slack", id: "slack", assignedAgentId: "agent-1", status: "active" },
|
||||
]);
|
||||
flushSync(() => root.render(<QueryClientProvider client={client}><AgentChannelsPanel companyId="company-1" agentId="agent-1" /></QueryClientProvider>));
|
||||
await flushReact();
|
||||
expect(container.querySelector('a[href="/apps/chat/github/settings"]')).not.toBeNull();
|
||||
expect(container.querySelector('a[href="/apps/chat/slack/settings"]')).toBeNull();
|
||||
});
|
||||
it("waits without exposing setup, then enables the route after explicit opt-in", async () => {
|
||||
let resolve!: (value: unknown) => void;
|
||||
api.settings.mockReturnValue(
|
||||
|
||||
@@ -3,22 +3,41 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { chatEndpointsApi } from "@/api/chatEndpoints";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useChatConnectorsEnabled } from "@/hooks/useChatConnectorsEnabled";
|
||||
import {
|
||||
useChatConnectorsEnabled,
|
||||
chatProviderVisible,
|
||||
} from "@/hooks/useChatConnectorsEnabled";
|
||||
import { Navigate, useParams, useSearchParams } from "@/lib/router";
|
||||
|
||||
function DefaultEmailEndpointGate({ endpointId, children }: { endpointId: string; children: ReactNode }) {
|
||||
function ProviderEndpointGate({
|
||||
endpointId,
|
||||
children,
|
||||
}: {
|
||||
endpointId: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const endpoint = useQuery({
|
||||
queryKey: queryKeys.chatEndpoints.detail(endpointId),
|
||||
queryFn: () => chatEndpointsApi.get(endpointId),
|
||||
});
|
||||
if (endpoint.isError) return (
|
||||
<div className="space-y-3 p-6">
|
||||
<p role="alert" className="text-sm text-destructive">{endpoint.error.message}</p>
|
||||
<Button variant="outline" onClick={() => void endpoint.refetch()}>Retry</Button>
|
||||
</div>
|
||||
);
|
||||
const { enabled, githubEnabled } = useChatConnectorsEnabled();
|
||||
if (endpoint.isError)
|
||||
return (
|
||||
<div className="space-y-3 p-6">
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{endpoint.error.message}
|
||||
</p>
|
||||
<Button variant="outline" onClick={() => void endpoint.refetch()}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
if (!endpoint.data) return null;
|
||||
return endpoint.data.provider === "agentmail" ? <>{children}</> : <Navigate to="/apps" replace />;
|
||||
return chatProviderVisible(endpoint.data.provider, enabled, githubEnabled) ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<Navigate to="/apps" replace />
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatConnectorsExperimentalGate({
|
||||
@@ -26,13 +45,21 @@ export function ChatConnectorsExperimentalGate({
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { enabled, loaded } = useChatConnectorsEnabled();
|
||||
const { enabled, githubEnabled, loaded } = useChatConnectorsEnabled();
|
||||
const [params] = useSearchParams();
|
||||
const { endpointId } = useParams<{ endpointId: string }>();
|
||||
if (!endpointId && params.get("provider") === "agentmail") return <>{children}</>;
|
||||
if (!endpointId && params.get("provider") === "agentmail")
|
||||
return <>{children}</>;
|
||||
if (!loaded) return null;
|
||||
if (!enabled) return endpointId
|
||||
? <DefaultEmailEndpointGate endpointId={endpointId}>{children}</DefaultEmailEndpointGate>
|
||||
: <Navigate to="/apps" replace />;
|
||||
return <>{children}</>;
|
||||
if (endpointId)
|
||||
return (
|
||||
<ProviderEndpointGate endpointId={endpointId}>
|
||||
{children}
|
||||
</ProviderEndpointGate>
|
||||
);
|
||||
return chatProviderVisible(params.get("provider"), enabled, githubEnabled) ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<Navigate to="/apps" replace />
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { Link } from "@/lib/router";
|
||||
import { useChatConnectorsEnabled } from "@/hooks/useChatConnectorsEnabled";
|
||||
import { useChatConnectorsEnabled, chatProviderVisible } from "@/hooks/useChatConnectorsEnabled";
|
||||
|
||||
const providerNames: Record<ChatProvider, string> = {
|
||||
slack: "Slack",
|
||||
@@ -24,7 +24,7 @@ export function AgentChannelsPanel({
|
||||
companyId: string;
|
||||
agentId: string;
|
||||
}) {
|
||||
const { enabled } = useChatConnectorsEnabled();
|
||||
const { enabled, githubEnabled } = useChatConnectorsEnabled();
|
||||
const query = useQuery({
|
||||
queryKey: queryKeys.chatEndpoints.list(companyId),
|
||||
queryFn: () => chatEndpointsApi.list(companyId),
|
||||
@@ -32,7 +32,7 @@ export function AgentChannelsPanel({
|
||||
});
|
||||
const endpoints = (query.data ?? []).filter(
|
||||
(endpoint) =>
|
||||
endpoint.assignedAgentId === agentId && endpoint.status !== "archived" && (endpoint.provider === "agentmail" || enabled),
|
||||
endpoint.assignedAgentId === agentId && endpoint.status !== "archived" && chatProviderVisible(endpoint.provider, enabled, githubEnabled),
|
||||
);
|
||||
return (
|
||||
<section className="max-w-3xl space-y-5">
|
||||
@@ -56,7 +56,7 @@ export function AgentChannelsPanel({
|
||||
<div className="rounded-lg border border-dashed border-border p-5">
|
||||
<p className="text-sm font-medium">No channels connected</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{enabled ? "Connect AgentMail, Slack, GitHub Code Review Bot, Discord, Microsoft Teams, or Telegram from Connectors." : "Connect AgentMail from Connectors."}
|
||||
{enabled || githubEnabled ? "Connect an agent identity from Connectors." : "Connect AgentMail from Connectors."}
|
||||
</p>
|
||||
<Button asChild className="mt-3" variant="outline" size="sm">
|
||||
<Link to="/apps">Open Connectors</Link>
|
||||
|
||||
@@ -51,7 +51,6 @@ export function ChatDetailSidebar({
|
||||
to={`/apps/chat/${endpointId}/reviews`}
|
||||
label="Reviews"
|
||||
icon={GitPullRequest}
|
||||
end
|
||||
/>
|
||||
)}
|
||||
<NavItem
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { useToast } from "@/context/ToastContext";
|
||||
import { Link } from "@/lib/router";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { useChatConnectorsEnabled } from "@/hooks/useChatConnectorsEnabled";
|
||||
import { useChatConnectorsEnabled, chatProviderVisible } from "@/hooks/useChatConnectorsEnabled";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import {
|
||||
boardSendDraftKey,
|
||||
@@ -109,15 +109,15 @@ const filePhaseLabels: Record<ChatFileTransferPhase, string> = {
|
||||
};
|
||||
|
||||
export function useIssueChatBinding(companyId: string, issueId: string) {
|
||||
const { enabled } = useChatConnectorsEnabled();
|
||||
const queryEnabled = enabled && Boolean(companyId && issueId) && !issueId.startsWith("chat:");
|
||||
const { enabled, githubEnabled } = useChatConnectorsEnabled();
|
||||
const queryEnabled = (enabled || githubEnabled) && Boolean(companyId && issueId) && !issueId.startsWith("chat:");
|
||||
const query = useQuery({
|
||||
queryKey: ["issue-chat-binding", companyId, issueId],
|
||||
queryFn: () => chatEndpointsApi.getIssueBinding(issueId),
|
||||
enabled: queryEnabled,
|
||||
});
|
||||
return {
|
||||
binding: queryEnabled ? (query.data ?? null) : null,
|
||||
binding: queryEnabled && chatProviderVisible(query.data?.provider, enabled, githubEnabled) ? (query.data ?? null) : null,
|
||||
isLoading: queryEnabled && query.isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user