Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions apps/code/src/renderer/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,7 @@ export class PostHogAPIClient {
repo: string,
offset: number,
limit: number,
search?: string,
): Promise<{
branches: string[];
defaultBranch: string | null;
Expand All @@ -1265,6 +1266,9 @@ export class PostHogAPIClient {
url.searchParams.set("repo", repo);
url.searchParams.set("offset", String(offset));
url.searchParams.set("limit", String(limit));
if (search?.trim()) {
url.searchParams.set("search", search.trim());
}
const response = await this.api.fetcher.fetch({
method: "get",
url,
Expand Down Expand Up @@ -1305,13 +1309,44 @@ export class PostHogAPIClient {
}

const data = await response.json();
return this.normalizeGithubRepositories(data);
}

const repos =
data.repositories ?? data.results ?? (Array.isArray(data) ? data : []);
return repos.map((repo: string | { full_name?: string; name?: string }) => {
if (typeof repo === "string") return repo;
return (repo.full_name ?? repo.name ?? "").toLowerCase();
async refreshGithubRepositories(
integrationId: string | number,
): Promise<string[]> {
const teamId = await this.getTeamId();
const url = new URL(
`${this.api.baseUrl}/api/environments/${teamId}/integrations/${integrationId}/github_repos/refresh/`,
);
const response = await this.api.fetcher.fetch({
method: "post",
url,
path: `/api/environments/${teamId}/integrations/${integrationId}/github_repos/refresh/`,
});

if (!response.ok) {
throw new Error(
`Failed to refresh GitHub repositories: ${response.statusText}`,
);
}

const data = await response.json();
return this.normalizeGithubRepositories(data);
}

private normalizeGithubRepositories(data: unknown): string[] {
const repos =
(data as { repositories?: unknown[] }).repositories ??
(data as { results?: unknown[] }).results ??
(Array.isArray(data) ? data : []);

return (repos as (string | { full_name?: string; name?: string })[]).map(
(repo) => {
if (typeof repo === "string") return repo;
return (repo.full_name ?? repo.name ?? "").toLowerCase();
},
);
}
Comment thread
tatoalo marked this conversation as resolved.

async getAgents() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Tooltip } from "@components/ui/Tooltip";
import { GithubLogo } from "@phosphor-icons/react";
import { ArrowClockwise, GithubLogo } from "@phosphor-icons/react";
import {
Button,
Combobox,
Expand All @@ -16,7 +16,7 @@ const COMBOBOX_LIMIT = 50;

interface GitHubRepoPickerProps {
value: string | null;
onChange: (repo: string) => void;
onChange: (repo: string | null) => void;
repositories: string[];
isLoading: boolean;
placeholder?: string;
Expand All @@ -25,6 +25,8 @@ interface GitHubRepoPickerProps {
anchor?: RefObject<HTMLElement | null>;
/** When false, the list is shown without a filter field (e.g. short lists in modals). */
showSearchInput?: boolean;
onRefresh?: () => void;
isRefreshing?: boolean;
}

export function GitHubRepoPicker({
Expand All @@ -36,6 +38,8 @@ export function GitHubRepoPicker({
disabled = false,
anchor,
showSearchInput = true,
onRefresh,
isRefreshing = false,
}: GitHubRepoPickerProps) {
const triggerRef = useRef<HTMLButtonElement>(null);
const [searchQuery, setSearchQuery] = useState("");
Expand Down Expand Up @@ -91,7 +95,7 @@ export function GitHubRepoPicker({
limit={COMBOBOX_LIMIT}
value={value}
onValueChange={(v) => {
if (v) onChange(v as string);
onChange(v ? (v as string) : null);
}}
inputValue={searchQuery}
onInputValueChange={setSearchQuery}
Expand All @@ -118,7 +122,33 @@ export function GitHubRepoPicker({
className="min-w-[280px]"
>
{showSearchInput ? (
<ComboboxInput placeholder="Search repositories..." />
<div className="flex min-w-0 items-center gap-1 pe-2">
<div className="min-w-0 flex-1">
<ComboboxInput placeholder="Search repositories..." />
</div>
{onRefresh ? (
<Button
variant="outline"
size="sm"
disabled={disabled || isRefreshing}
aria-label="Refresh repositories"
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onRefresh();
}}
>
<ArrowClockwise
size={14}
className={isRefreshing ? "animate-spin" : undefined}
/>
</Button>
) : null}
</div>
) : null}
<ComboboxEmpty>No repositories found.</ComboboxEmpty>
<ComboboxList>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useGitInteractionStore } from "@features/git-interaction/state/gitInteractionStore";
import { getSuggestedBranchName } from "@features/git-interaction/utils/getSuggestedBranchName";
import { invalidateGitBranchQueries } from "@features/git-interaction/utils/gitCacheKeys";
import { CaretDown, GitBranch, Plus, Spinner } from "@phosphor-icons/react";
import {
ArrowClockwise,
CaretDown,
GitBranch,
Plus,
Spinner,
} from "@phosphor-icons/react";
import {
Button,
Combobox,
Expand All @@ -10,6 +16,7 @@ import {
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxListFooter,
ComboboxTrigger,
} from "@posthog/quill";
import { useTRPC } from "@renderer/trpc";
Expand All @@ -30,10 +37,17 @@ interface BranchSelectorProps {
selectedBranch?: string | null;
onBranchSelect?: (branch: string | null) => void;
cloudBranches?: string[];
cloudBranchesHasMore?: boolean;
cloudBranchesLoading?: boolean;
cloudBranchesFetchingMore?: boolean;
cloudSearchQuery?: string;
onCloudPickerOpen?: () => void;
onCloudPickerClose?: () => void;
onCloudSearchChange?: (value: string) => void;
onCloudLoadMore?: () => void;
onCloudBranchCommit?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
taskId?: string;
anchor?: RefObject<HTMLElement | null>;
}
Expand All @@ -48,10 +62,17 @@ export function BranchSelector({
selectedBranch,
onBranchSelect,
cloudBranches,
cloudBranchesHasMore,
cloudBranchesLoading,
cloudBranchesFetchingMore,
cloudSearchQuery,
onCloudPickerOpen,
onCloudPickerClose,
onCloudSearchChange,
onCloudLoadMore,
onCloudBranchCommit,
onRefresh,
isRefreshing = false,
taskId,
anchor,
}: BranchSelectorProps) {
Expand Down Expand Up @@ -101,14 +122,14 @@ export function BranchSelector({
const handleBranchChange = (value: string | null) => {
if (!value) return;
if (isSelectionOnly) {
onBranchSelect?.(value || null);
} else if (value && value !== currentBranch) {
onBranchSelect?.(value);
} else if (value !== currentBranch) {
checkoutMutation.mutate({
directoryPath: repoPath as string,
branchName: value,
});
}
if (isCloudMode && value) {
if (isCloudMode) {
onCloudBranchCommit?.();
}
setOpen(false);
Expand All @@ -118,6 +139,8 @@ export function BranchSelector({
setOpen(next);
if (isCloudMode && next) {
onCloudPickerOpen?.();
} else if (isCloudMode && !next) {
onCloudPickerClose?.();
}
};

Expand All @@ -129,18 +152,24 @@ export function BranchSelector({
effectiveLoading || (isCloudMode && open && cloudBranchesFetchingMore);

const isDisabled = !!(disabled || !repoPath || cloudStillLoading);
const inputValue = isCloudMode ? (cloudSearchQuery ?? "") : searchQuery;

return (
<Combobox
items={branches}
limit={COMBOBOX_LIMIT}
value={displayedBranch}
inputValue={inputValue}
onInputValueChange={
isCloudMode
? (value) => onCloudSearchChange?.((value as string | null) ?? "")
: setSearchQuery
}
onValueChange={(v) => handleBranchChange(v as string | null)}
inputValue={searchQuery}
onInputValueChange={setSearchQuery}
open={open}
onOpenChange={(nextOpen) => handleOpenChange(nextOpen)}
onOpenChange={handleOpenChange}
disabled={isDisabled}
filter={isCloudMode ? null : undefined}
>
<ComboboxTrigger
render={
Expand Down Expand Up @@ -172,14 +201,43 @@ export function BranchSelector({
sideOffset={6}
className="min-w-[240px]"
>
<ComboboxInput placeholder="Search branches..." showTrigger={false} />
<div className="flex min-w-0 items-center gap-1 pe-2">
<div className="min-w-0 flex-1">
<ComboboxInput
placeholder="Search branches..."
showTrigger={false}
/>
</div>
{onRefresh ? (
<Button
variant="outline"
size="sm"
disabled={isDisabled || isRefreshing}
aria-label="Refresh branches"
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onRefresh();
}}
>
<ArrowClockwise
size={14}
className={isRefreshing ? "animate-spin" : undefined}
/>
</Button>
) : null}
</div>

{isCloudMode && cloudBranchesFetchingMore && (
{isCloudMode && cloudBranchesFetchingMore ? (
<div className="flex items-center gap-1 px-2 py-1.5 text-muted-foreground text-xs">
<Spinner size={12} className="animate-spin" />
Loading more ({branches.length})…
</div>
)}
) : null}

<ComboboxEmpty>No branches found.</ComboboxEmpty>

Expand All @@ -196,7 +254,7 @@ export function BranchSelector({
)}
</ComboboxList>

{!isCloudMode && (
{!isCloudMode ? (
<button
type="button"
className="flex w-full items-center gap-2 border-t px-2 py-1.5 text-accent-foreground text-xs hover:bg-accent/10"
Expand All @@ -212,15 +270,46 @@ export function BranchSelector({
<Plus size={11} weight="bold" />
Create new branch
</button>
)}
) : null}

{isCloudMode && cloudBranchesHasMore ? (
<ComboboxListFooter>
<div className="px-2 pb-2">
<Button
variant="outline"
size="sm"
className="w-full justify-center"
disabled={cloudBranchesFetchingMore}
onMouseDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
onCloudLoadMore?.();
}}
>
{cloudBranchesFetchingMore ? (
<>
<Spinner size={14} className="animate-spin" />
Loading more…
</>
) : (
"Load more"
)}
</Button>
</div>
</ComboboxListFooter>
) : null}

{branches.length > COMBOBOX_LIMIT && (
{!isCloudMode && branches.length > COMBOBOX_LIMIT ? (
<div className="px-2 py-1.5 text-center text-muted-foreground text-xs">
{searchQuery
? `Showing up to ${COMBOBOX_LIMIT} matches refine your search`
: `Showing ${COMBOBOX_LIMIT} of ${branches.length} type to filter`}
? `Showing up to ${COMBOBOX_LIMIT} matches - refine your search`
: `Showing ${COMBOBOX_LIMIT} of ${branches.length} - type to filter`}
</div>
)}
) : null}
</ComboboxContent>
</Combobox>
);
Expand Down
Loading
Loading