fix(connections): preserve all-repository warning

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-09-05 08:50:45 -05:00
parent fec6dd2bf4
commit 408c5996e2
2 changed files with 70 additions and 27 deletions

View File

@ -262,6 +262,32 @@ function personalGrant(overrides: Record<string, unknown> = {}) {
};
}
function dedicatedGitHubGrant(overrides: Record<string, unknown> = {}) {
return organizationGrant({
id: "grant-agent",
kind: "agent",
subjectAgentId: "agent-1",
subjectUserId: null,
isDefault: false,
providerTenant: {
github: {
userId: "123",
login: "dottabot",
installationCount: 1,
repositoryCount: 1,
repositorySelection: "selected",
installationIds: ["456"],
installationOwnerLogins: ["paperclipai"],
managementUrl: "https://github.com/settings/installations/456",
webhookHealth: "pending",
lastWebhookAt: null,
lastAccessRefreshAt: "2026-09-05T12:00:00.000Z",
},
},
...overrides,
});
}
function catalogEntry(overrides: Record<string, unknown> = {}) {
return {
id: "catalog-read",
@ -1433,28 +1459,7 @@ describe("AppDetail", () => {
}));
listConnectionGrantsMock.mockResolvedValue({
connection: { id: "conn-1", uid: "conn-1" },
grants: [organizationGrant({
id: "grant-agent",
kind: "agent",
subjectAgentId: "agent-1",
subjectUserId: null,
isDefault: false,
providerTenant: {
github: {
userId: "123",
login: "dottabot",
installationCount: 1,
repositoryCount: 1,
repositorySelection: "selected",
installationIds: ["456"],
installationOwnerLogins: ["paperclipai"],
managementUrl: "https://github.com/settings/installations/456",
webhookHealth: "pending",
lastWebhookAt: null,
lastAccessRefreshAt: "2026-09-05T12:00:00.000Z",
},
},
})],
grants: [dedicatedGitHubGrant()],
capabilities: fullCapabilities(),
currentUserId: "user-1",
members: [],
@ -1476,6 +1481,39 @@ describe("AppDetail", () => {
expect(container.textContent).not.toContain("Last access refresh");
});
it("warns about all-repository GitHub access within the repository row", async () => {
mockParams.tab = "permissions";
getConnectionMock.mockResolvedValue(connection({
credentialPolicy: "per_agent",
authKind: "oauth",
}));
listConnectionGrantsMock.mockResolvedValue({
connection: { id: "conn-1", uid: "conn-1" },
grants: [dedicatedGitHubGrant({
providerTenant: {
github: {
userId: "123",
login: "dottabot",
installationCount: 1,
repositoryCount: 0,
repositorySelection: "all",
installationIds: ["456"],
installationOwnerLogins: ["paperclipai"],
managementUrl: "https://github.com/settings/installations/456",
},
},
})],
capabilities: fullCapabilities(),
currentUserId: "user-1",
members: [],
});
await renderAppDetail();
expect(container.textContent).toContain("All current and future repositories");
expect(container.textContent).not.toContain("selected repositories");
});
it("persists an empty audience as all organization members", async () => {
mockParams.tab = "permissions";
getConnectionMock.mockResolvedValue(connection({ createdByUserId: "user-1" }));

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { Building2, Loader2, UserRound } from "lucide-react";
import { Building2, Loader2, TriangleAlert, UserRound } from "lucide-react";
import type {
ConnectionAudienceMember,
ConnectionGrant,
@ -296,15 +296,20 @@ function GitHubConnectionSummary({
}) {
const github = grant.providerTenant?.github;
if (!github) return null;
const repositorySummary = github.repositorySelection === "all"
? "All repositories"
: `${github.repositoryCount} selected repositories`;
const allRepositories = github.repositorySelection === "all";
return (
<div className="divide-y divide-border border-y border-border">
<div className="flex flex-wrap items-center justify-between gap-3 py-3">
<div className="min-w-0">
<div className="text-sm font-medium text-foreground">Repositories</div>
<div className="text-xs text-muted-foreground">{repositorySummary}</div>
{allRepositories ? (
<div className="flex items-center gap-1.5 text-xs text-amber-800 dark:text-amber-200">
<TriangleAlert className="h-3.5 w-3.5 shrink-0" />
All current and future repositories
</div>
) : (
<div className="text-xs text-muted-foreground">{github.repositoryCount} selected repositories</div>
)}
</div>
{github.managementUrl ? (
<Button asChild size="sm" variant="outline">