Skip to content

Commit a42284e

Browse files
committed
chore(code): loading all branches
1 parent 02b26ae commit a42284e

5 files changed

Lines changed: 122 additions & 11 deletions

File tree

apps/code/src/renderer/api/posthogClient.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,43 @@ export class PostHogAPIClient {
995995
};
996996
}
997997

998+
async getGithubBranchesPage(
999+
integrationId: string | number,
1000+
repo: string,
1001+
offset: number,
1002+
limit: number,
1003+
): Promise<{
1004+
branches: string[];
1005+
defaultBranch: string | null;
1006+
hasMore: boolean;
1007+
}> {
1008+
const teamId = await this.getTeamId();
1009+
const url = new URL(
1010+
`${this.api.baseUrl}/api/environments/${teamId}/integrations/${integrationId}/github_branches/`,
1011+
);
1012+
url.searchParams.set("repo", repo);
1013+
url.searchParams.set("offset", String(offset));
1014+
url.searchParams.set("limit", String(limit));
1015+
const response = await this.api.fetcher.fetch({
1016+
method: "get",
1017+
url,
1018+
path: `/api/environments/${teamId}/integrations/${integrationId}/github_branches/`,
1019+
});
1020+
1021+
if (!response.ok) {
1022+
throw new Error(
1023+
`Failed to fetch GitHub branches: ${response.statusText}`,
1024+
);
1025+
}
1026+
1027+
const data = await response.json();
1028+
return {
1029+
branches: data.branches ?? data.results ?? data ?? [],
1030+
defaultBranch: data.default_branch ?? null,
1031+
hasMore: data.has_more ?? false,
1032+
};
1033+
}
1034+
9981035
async getGithubRepositories(
9991036
integrationId: string | number,
10001037
): Promise<string[]> {

apps/code/src/renderer/features/git-interaction/components/BranchSelector.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface BranchSelectorProps {
2121
onBranchSelect?: (branch: string | null) => void;
2222
cloudBranches?: string[];
2323
cloudBranchesLoading?: boolean;
24+
cloudBranchesFetchingMore?: boolean;
2425
taskId?: string;
2526
}
2627

@@ -36,6 +37,7 @@ export function BranchSelector({
3637
onBranchSelect,
3738
cloudBranches,
3839
cloudBranchesLoading,
40+
cloudBranchesFetchingMore,
3941
taskId,
4042
}: BranchSelectorProps) {
4143
const [open, setOpen] = useState(false);
@@ -61,6 +63,8 @@ export function BranchSelector({
6163

6264
const branches = isCloudMode ? (cloudBranches ?? []) : localBranches;
6365
const effectiveLoading = loading || (isCloudMode && cloudBranchesLoading);
66+
const cloudStillLoading =
67+
isCloudMode && cloudBranchesLoading && branches.length === 0;
6468

6569
const checkoutMutation = useMutation(
6670
trpc.git.checkoutBranch.mutationOptions({
@@ -93,9 +97,12 @@ export function BranchSelector({
9397
? "Loading..."
9498
: (displayedBranch ?? "No branch");
9599

100+
const showSpinner =
101+
effectiveLoading || (isCloudMode && cloudBranchesFetchingMore);
102+
96103
const triggerContent = (
97104
<Flex align="center" gap="1" style={{ minWidth: 0 }}>
98-
{effectiveLoading ? (
105+
{showSpinner ? (
99106
<Spinner size="1" />
100107
) : (
101108
<GitBranch size={16} weight="regular" style={{ flexShrink: 0 }} />
@@ -104,6 +111,8 @@ export function BranchSelector({
104111
</Flex>
105112
);
106113

114+
console.log("branches", branches);
115+
107116
return (
108117
<Tooltip content={displayedBranch} delayDuration={300}>
109118
<Combobox.Root
@@ -112,7 +121,7 @@ export function BranchSelector({
112121
open={open}
113122
onOpenChange={setOpen}
114123
size="1"
115-
disabled={disabled || !repoPath}
124+
disabled={disabled || !repoPath || cloudStillLoading}
116125
>
117126
<Combobox.Trigger variant={variant} placeholder="No branch">
118127
{triggerContent}
@@ -126,6 +135,17 @@ export function BranchSelector({
126135
{({ filtered, hasMore, moreCount }) => (
127136
<>
128137
<Combobox.Input placeholder="Search branches" />
138+
{isCloudMode && cloudBranchesFetchingMore && (
139+
<Flex
140+
align="center"
141+
gap="1"
142+
className="combobox-label"
143+
style={{ padding: "6px 8px" }}
144+
>
145+
<Spinner size="1" />
146+
Loading more ({branches.length})…
147+
</Flex>
148+
)}
129149
<Combobox.Empty>No branches found.</Combobox.Empty>
130150

131151
{filtered.length > 0 && (

apps/code/src/renderer/features/onboarding/components/TutorialStep.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,11 @@ export function TutorialStep({ onComplete, onBack }: TutorialStepProps) {
102102
? getIntegrationIdForRepo(selectedRepository)
103103
: undefined;
104104

105-
const { data: cloudBranchData, isPending: cloudBranchesLoading } =
106-
useGithubBranches(selectedIntegrationId, selectedRepository);
105+
const {
106+
data: cloudBranchData,
107+
isPending: cloudBranchesLoading,
108+
isFetchingMore: cloudBranchesFetchingMore,
109+
} = useGithubBranches(selectedIntegrationId, selectedRepository);
107110
const cloudBranches = cloudBranchData?.branches;
108111
const cloudDefaultBranch = cloudBranchData?.defaultBranch ?? null;
109112

@@ -359,6 +362,7 @@ export function TutorialStep({ onComplete, onBack }: TutorialStepProps) {
359362
onBranchSelect={setSelectedBranch}
360363
cloudBranches={cloudBranches}
361364
cloudBranchesLoading={cloudBranchesLoading}
365+
cloudBranchesFetchingMore={cloudBranchesFetchingMore}
362366
/>
363367
</TourHighlight>
364368
</Flex>

apps/code/src/renderer/features/task-detail/components/TaskInput.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ export function TaskInput({
119119
? getIntegrationIdForRepo(selectedCloudRepository)
120120
: undefined;
121121

122-
const { data: cloudBranchData, isPending: cloudBranchesLoading } =
123-
useGithubBranches(selectedIntegrationId, selectedCloudRepository);
122+
const {
123+
data: cloudBranchData,
124+
isPending: cloudBranchesLoading,
125+
isFetchingMore: cloudBranchesFetchingMore,
126+
} = useGithubBranches(selectedIntegrationId, selectedCloudRepository);
124127
const cloudBranches = cloudBranchData?.branches;
125128
const cloudDefaultBranch = cloudBranchData?.defaultBranch ?? null;
126129

@@ -464,6 +467,7 @@ export function TaskInput({
464467
onBranchSelect={setSelectedBranch}
465468
cloudBranches={cloudBranches}
466469
cloudBranchesLoading={cloudBranchesLoading}
470+
cloudBranchesFetchingMore={cloudBranchesFetchingMore}
467471
/>
468472
{workspaceMode === "worktree" && (
469473
<EnvironmentSelector

apps/code/src/renderer/hooks/useIntegrations.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "@features/integrations/stores/integrationStore";
88
import { useQueries } from "@tanstack/react-query";
99
import { useCallback, useEffect, useMemo } from "react";
10+
import { useAuthenticatedInfiniteQuery } from "./useAuthenticatedInfiniteQuery";
1011
import { useAuthenticatedQuery } from "./useAuthenticatedQuery";
1112

1213
const integrationKeys = {
@@ -67,18 +68,63 @@ function useAllGithubRepositories(githubIntegrations: Integration[]) {
6768
});
6869
}
6970

71+
const BRANCHES_PAGE_SIZE = 1000;
72+
73+
interface GithubBranchesPage {
74+
branches: string[];
75+
defaultBranch: string | null;
76+
hasMore: boolean;
77+
}
78+
7079
export function useGithubBranches(
7180
integrationId?: number,
7281
repo?: string | null,
7382
) {
74-
return useAuthenticatedQuery(
83+
const query = useAuthenticatedInfiniteQuery<GithubBranchesPage, number>(
7584
integrationKeys.branches(integrationId, repo),
76-
async (client) => {
77-
if (!integrationId || !repo) return { branches: [], defaultBranch: null };
78-
return await client.getGithubBranches(integrationId, repo);
85+
async (client, offset) => {
86+
if (!integrationId || !repo) {
87+
return { branches: [], defaultBranch: null, hasMore: false };
88+
}
89+
return await client.getGithubBranchesPage(
90+
integrationId,
91+
repo,
92+
offset,
93+
BRANCHES_PAGE_SIZE,
94+
);
95+
},
96+
{
97+
initialPageParam: 0,
98+
getNextPageParam: (lastPage, allPages) => {
99+
if (!lastPage.hasMore) return undefined;
100+
return allPages.reduce((n, p) => n + p.branches.length, 0);
101+
},
102+
staleTime: 0,
79103
},
80-
{ staleTime: 0, refetchOnMount: "always" },
81104
);
105+
106+
// Auto-fetch remaining pages in background once the first page arrives
107+
useEffect(() => {
108+
if (query.hasNextPage && !query.isFetchingNextPage) {
109+
query.fetchNextPage();
110+
}
111+
}, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);
112+
113+
const data = useMemo(() => {
114+
if (!query.data?.pages.length) {
115+
return { branches: [] as string[], defaultBranch: null };
116+
}
117+
return {
118+
branches: query.data.pages.flatMap((p) => p.branches),
119+
defaultBranch: query.data.pages[0]?.defaultBranch ?? null,
120+
};
121+
}, [query.data?.pages]);
122+
123+
return {
124+
data,
125+
isPending: query.isPending,
126+
isFetchingMore: query.isFetchingNextPage || (query.hasNextPage ?? false),
127+
};
82128
}
83129

84130
export function useRepositoryIntegration() {

0 commit comments

Comments
 (0)