diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index 72d40259b7f..ef7a3de33b2 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -4,6 +4,7 @@ import { EXTERNAL_APPS, FILE_OPEN_MODES, NON_EDITOR_APPS, + PR_LINK_PROVIDERS, settings, TERMINAL_LINK_BEHAVIORS, type TerminalPreset, @@ -22,6 +23,7 @@ import { DEFAULT_CONFIRM_ON_QUIT, DEFAULT_FILE_OPEN_MODE, DEFAULT_OPEN_LINKS_IN_APP, + DEFAULT_PR_LINK_PROVIDER, DEFAULT_SHOW_PRESETS_BAR, DEFAULT_SHOW_RESOURCE_MONITOR, DEFAULT_TERMINAL_LINK_BEHAVIOR, @@ -502,6 +504,41 @@ export const createSettingsRouter = () => { return { success: true }; }), + getPrLinkProvider: publicProcedure.query(() => { + const row = getSettings(); + return { + provider: row.prLinkProvider ?? DEFAULT_PR_LINK_PROVIDER, + customDomain: row.prLinkCustomDomain ?? null, + }; + }), + + setPrLinkProvider: publicProcedure + .input( + z.object({ + provider: z.enum(PR_LINK_PROVIDERS), + customDomain: z.string().nullable().optional(), + }), + ) + .mutation(({ input }) => { + localDb + .insert(settings) + .values({ + id: 1, + prLinkProvider: input.provider, + prLinkCustomDomain: input.customDomain ?? null, + }) + .onConflictDoUpdate({ + target: settings.id, + set: { + prLinkProvider: input.provider, + prLinkCustomDomain: input.customDomain ?? null, + }, + }) + .run(); + + return { success: true }; + }), + getBranchPrefix: publicProcedure.query(() => { const row = getSettings(); return { diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx index d736246446f..9c217470872 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx @@ -39,6 +39,8 @@ import { useHasWorkspaceFailed, useIsWorkspaceInitializing, } from "renderer/stores/workspace-init"; +import { transformPrUrl } from "renderer/utils/pr-url"; +import { DEFAULT_PR_LINK_PROVIDER } from "shared/constants"; const EMPTY_HISTORY_STACK: string[] = []; @@ -376,17 +378,24 @@ function WorkspacePage() { const { createOrOpenPR } = useCreateOrOpenPR({ worktreePath: workspace?.worktreePath, }); + const { data: prLinkSettings } = + electronTrpc.settings.getPrLinkProvider.useQuery(); + const prProvider = prLinkSettings?.provider ?? DEFAULT_PR_LINK_PROVIDER; + const prCustomDomain = prLinkSettings?.customDomain; useAppHotkey( "OPEN_PR", () => { if (pr?.url) { - window.open(pr.url, "_blank"); + window.open( + transformPrUrl(pr.url, prProvider, prCustomDomain), + "_blank", + ); } else { createOrOpenPR(); } }, undefined, - [pr?.url, createOrOpenPR], + [pr?.url, createOrOpenPR, prProvider, prCustomDomain], ); const commandPalette = useCommandPalette({ diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx index 39a190cef10..ad48617ff7b 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/git/components/GitSettings/GitSettings.tsx @@ -1,4 +1,4 @@ -import type { BranchPrefixMode } from "@superset/local-db"; +import type { BranchPrefixMode, PrLinkProvider } from "@superset/local-db"; import { Input } from "@superset/ui/input"; import { Label } from "@superset/ui/label"; import { @@ -17,6 +17,7 @@ import { WorktreeLocationPicker, } from "../../../components/WorktreeLocationPicker"; import { BRANCH_PREFIX_MODE_LABELS } from "../../../utils/branch-prefix"; +import { PR_LINK_PROVIDER_LABELS } from "../../../utils/pr-link-provider"; import { isItemVisible, SETTING_ITEM_ID, @@ -36,6 +37,10 @@ export function GitSettings({ visibleItems }: GitSettingsProps) { SETTING_ITEM_ID.GIT_BRANCH_PREFIX, visibleItems, ); + const showPrLinkProvider = isItemVisible( + SETTING_ITEM_ID.GIT_PR_LINK_PROVIDER, + visibleItems, + ); const showWorktreeLocation = isItemVisible( SETTING_ITEM_ID.GIT_WORKTREE_LOCATION, visibleItems, @@ -107,6 +112,44 @@ export function GitSettings({ visibleItems }: GitSettingsProps) { }); }; + const { data: prLinkProvider, isLoading: isPrLinkProviderLoading } = + electronTrpc.settings.getPrLinkProvider.useQuery(); + + const [customDomainInput, setCustomDomainInput] = useState( + prLinkProvider?.customDomain ?? "", + ); + + useEffect(() => { + setCustomDomainInput(prLinkProvider?.customDomain ?? ""); + }, [prLinkProvider?.customDomain]); + + const setPrLinkProvider = electronTrpc.settings.setPrLinkProvider.useMutation( + { + onError: (err) => { + console.error("[settings/pr-link-provider] Failed to update:", err); + }, + onSettled: () => { + utils.settings.getPrLinkProvider.invalidate(); + }, + }, + ); + + const handlePrLinkProviderChange = (provider: PrLinkProvider) => { + setPrLinkProvider.mutate({ + provider, + customDomain: customDomainInput || null, + }); + }; + + const handleCustomDomainBlur = () => { + const trimmed = customDomainInput.trim().replace(/\/+$/, ""); + setCustomDomainInput(trimmed); + setPrLinkProvider.mutate({ + provider: "custom", + customDomain: trimmed || null, + }); + }; + const { data: worktreeBaseDir, isLoading: isWorktreeBaseDirLoading } = electronTrpc.settings.getWorktreeBaseDir.useQuery(); const setWorktreeBaseDir = @@ -228,6 +271,56 @@ export function GitSettings({ visibleItems }: GitSettingsProps) { )} + {showPrLinkProvider && ( +
+ Choose where pull request links open +
+