From fd67a8014f133239a12eece5dff6d4f7e72b6687 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 31 Jul 2026 17:52:12 +0800 Subject: [PATCH] Joshen/fe 4018 bug gh branch validation in branch modal fails silently if (#48432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Realised that if the project has a GH integration, but the user's account is not connected to GH - the branch validation in the "Sync with Git Branch" field will not work. The Edit branch modal also obfuscates the error being returned from the validation API so its not clear what the issue is image Opting to show an "Authorize" CTA for this scenario so it's clear from the user's POV what to do (Refer to "To test" below for screenshots) ## To test - [ ] Verify that on an account which isn't connect to GH + project with no GH integration - CTA is as per normal ("Configure") which should direct you to the settings -> integrations page (Same for edit branch) image image - [ ] Set up a GH integration and check Create / Edit branch - the branch input field should work with proper branch name validation image - [ ] Now go to Account settings and remove the GH connection, then check the Create / Edit branch modals - should have the "Authorize" CTA (instead of the input field) image image ## Summary by CodeRabbit * **New Features** * Added a “Sync with a GitHub branch” connection entry with an inline authorize flow. * Integrated GitHub authorization awareness into branch create/edit modals so users are guided to authorize or proceed to syncing. * **Bug Fixes** * Unified loading, success, and error handling for GitHub authorization/connection checks across create and edit flows. * Improved Git branch validation messaging to show cleaner error text. * **Accessibility/UX** * Updated modal UI text and added an explicit label for the “Include data” toggle. --- .../BranchManagement/ConnectToGitHub.tsx | 56 +++++++++++++++++ .../BranchManagement/CreateBranchModal.tsx | 56 +++++++++-------- .../BranchManagement/EditBranchModal.tsx | 60 ++++++++----------- 3 files changed, 112 insertions(+), 60 deletions(-) create mode 100644 apps/studio/components/interfaces/BranchManagement/ConnectToGitHub.tsx diff --git a/apps/studio/components/interfaces/BranchManagement/ConnectToGitHub.tsx b/apps/studio/components/interfaces/BranchManagement/ConnectToGitHub.tsx new file mode 100644 index 00000000000..d4256ee9e34 --- /dev/null +++ b/apps/studio/components/interfaces/BranchManagement/ConnectToGitHub.tsx @@ -0,0 +1,56 @@ +import { useParams } from 'common' +import { Github } from 'lucide-react' +import { useRouter } from 'next/router' +import { Button } from 'ui' + +import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query' +import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query' +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { openInstallGitHubIntegrationWindow } from '@/lib/github' +import { useAppStateSnapshot } from '@/state/app-state' + +export const ConnectToGitHub = () => { + const router = useRouter() + const { ref } = useParams() + const { data: project } = useSelectedProjectQuery() + const { data: selectedOrg } = useSelectedOrganizationQuery() + const { showCreateBranchModal, setShowCreateBranchModal } = useAppStateSnapshot() + + const isBranch = project?.parent_project_ref !== undefined + const projectRef = + project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined + + const { data: gitHubAuthorization } = useGitHubAuthorizationQuery() + + const { data: connections } = useGitHubConnectionsQuery( + { organizationId: selectedOrg?.id }, + { enabled: showCreateBranchModal } + ) + const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) + + const showAuthorizeCta = githubConnection && !gitHubAuthorization + + const onClick = () => { + if (showAuthorizeCta) { + openInstallGitHubIntegrationWindow('authorize') + } else { + if (showCreateBranchModal) setShowCreateBranchModal(false) + router.push(`/project/${projectRef}/settings/integrations`) + } + } + + return ( +
+
+ Sync with a GitHub branch +

+ Keep this preview branch in sync with a chosen GitHub branch +

+
+ +
+ ) +} diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx index 1f50e17d6d6..d282a1e5eeb 100644 --- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx @@ -3,7 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { useDebounce } from '@uidotdev/usehooks' import { useFlag, useParams } from 'common' -import { Check, DatabaseZap, DollarSign, Github, GitMerge, Loader2 } from 'lucide-react' +import { Check, DatabaseZap, DollarSign, GitMerge, Loader2 } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' @@ -25,7 +25,6 @@ import { FormControl, FormField, Input, - Label, Switch, Tooltip, TooltipContent, @@ -40,6 +39,7 @@ import { estimateDiskCost, estimateRestoreTime, } from './BranchManagement.utils' +import { ConnectToGitHub } from './ConnectToGitHub' import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer' import { getInfrastructurePath } from '@/components/interfaces/Settings/Infrastructure/Infrastructure.utils' import { BranchingPITRNotice } from '@/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice' @@ -50,6 +50,7 @@ import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useBranchCreateMutation } from '@/data/branches/branch-create-mutation' import { useBranchesQuery } from '@/data/branches/branches-query' import { DiskAttributesData, useDiskAttributesQuery } from '@/data/config/disk-attributes-query' +import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query' import { useCheckGithubBranchValidity } from '@/data/integrations/github-branch-check-query' import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query' import { projectKeys } from '@/data/projects/keys' @@ -118,6 +119,14 @@ export const CreateBranchModal = () => { }) const debouncedGitBranchName = useDebounce(gitBranchName, 500) + const { + data: githubAuthorization, + error: authorizationError, + isPending: isLoadingAuthorization, + isSuccess: isSuccessAuthorization, + isError: isErrorAuthorization, + } = useGitHubAuthorizationQuery() + const { data: connections, error: connectionsError, @@ -129,6 +138,11 @@ export const CreateBranchModal = () => { { enabled: showCreateBranchModal } ) + const isLoading = isLoadingAuthorization || isLoadingConnections + const isSuccess = isSuccessAuthorization && isSuccessConnections + const isError = isErrorAuthorization || isErrorConnections + const error = authorizationError || connectionsError + const { data: branches } = useBranchesQuery({ projectRef }) const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery( { projectRef }, @@ -260,11 +274,6 @@ export const CreateBranchModal = () => { }) } - const handleGitHubClick = () => { - setShowCreateBranchModal(false) - router.push(`/project/${projectRef}/settings/integrations`) - } - useEffect(() => { if (showCreateBranchModal) form.reset() }, [form, showCreateBranchModal]) @@ -323,22 +332,24 @@ export const CreateBranchModal = () => { )} /> - {isLoadingConnections && ( + {isLoading && (
)} - {isErrorConnections && ( + {isError && ( )} - {isSuccessConnections && - (githubConnection ? ( + {isSuccess && + (!githubAuthorization || !githubConnection ? ( + + ) : ( { } labelOptional="Optional" - description="Automatically deploy changes on every commit" + description={ + githubAuthorization + ? 'Automatically deploy changes on every commit' + : undefined + } >
@@ -394,18 +409,6 @@ export const CreateBranchModal = () => { )} /> - ) : ( -
-
- -

- Keep this preview branch in sync with a chosen GitHub branch -

-
- -
))} {allowDataBranching && ( @@ -416,7 +419,7 @@ export const CreateBranchModal = () => { - + Include data {!hasPitrEnabled && Requires PITR} } @@ -426,6 +429,7 @@ export const CreateBranchModal = () => { > { const { ref } = useParams() - const router = useRouter() const { data: projectDetails } = useSelectedProjectQuery() const { data: selectedOrg } = useSelectedOrganizationQuery() @@ -55,6 +54,14 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro const projectRef = projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined + const { + data: githubAuthorization, + error: authorizationError, + isPending: isLoadingAuthorization, + isSuccess: isSuccessAuthorization, + isError: isErrorAuthorization, + } = useGitHubAuthorizationQuery() + const { data: connections, error: connectionsError, @@ -65,6 +72,11 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro organizationId: selectedOrg?.id, }) + const isLoading = isLoadingAuthorization || isLoadingConnections + const isSuccess = isSuccessAuthorization && isSuccessConnections + const isError = isErrorAuthorization || isErrorConnections + const error = authorizationError || connectionsError + const { data: branches } = useBranchesQuery({ projectRef }) const { mutate: checkGithubBranchValidity, isPending: isChecking } = useCheckGithubBranchValidity( { onError: () => {} } @@ -113,14 +125,6 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro const isFormValid = form.formState.isValid && (!gitBranchName || isGitBranchValid) const canSubmit = isFormValid && !isUpdating && !isChecking - const openLinkerPanel = () => { - onClose() - - if (projectRef) { - router.push(`/project/${projectRef}/settings/integrations`) - } - } - const onSubmit = (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') if (!branch?.project_ref) return console.error('Branch ref is required') @@ -179,8 +183,9 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro if (form.getValues('gitBranchName') !== requested) return setIsGitBranchValid(false) form.setError('gitBranchName', { - ...error, - message: `Unable to find branch "${branchName}" in ${repoOwner}/${repoName}`, + message: + error?.message ?? + `Unable to find branch "${branchName}" in ${repoOwner}/${repoName}`, }) }, } @@ -234,22 +239,24 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro )} /> - {isLoadingConnections && ( + {isLoading && (
)} - {isErrorConnections && ( + {isError && ( )} - {isSuccessConnections && - (githubConnection ? ( + {isSuccess && + (!githubAuthorization || !githubConnection ? ( + + ) : ( )} /> - ) : ( -
-
-
- -
-

- Optionally connect to a GitHub repository to manage migrations automatically - for this branch. -

-
- -
))}