From 237fdb8db04415a2238c5fb1e348484bb40c0a03 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 24 Oct 2023 18:29:42 +0800 Subject: [PATCH] Chore/update branch selection UI (#18402) * Set up branch selection UI in EnableBranchingModal to be text input * Scaffold new CreateBranchModal * Refactor Enable Branching Modal to use RHF and implement async validation * midway updating create branch modal * Update create branch modal with branch checking through text input * Deprecate create branch side panel * Remove unused import * Small improvement to validation * Remove console log --- .../BranchManagement/BranchManagement.tsx | 8 +- .../BranchManagement/BranchPanels.tsx | 12 +- .../BranchManagement/CreateBranchModal.tsx | 233 +++++++++++++ .../CreateBranchSidePanel.tsx | 223 ------------ .../NewProject/FreeProjectLimitWarning.tsx | 8 +- .../EnableBranchingModal.tsx | 329 +++++++++++------- .../GithubRepositorySelection.tsx | 141 ++------ .../integrations-github-branch-check.ts | 65 ++++ studio/data/integrations/keys.ts | 13 + 9 files changed, 557 insertions(+), 475 deletions(-) create mode 100644 studio/components/interfaces/BranchManagement/CreateBranchModal.tsx delete mode 100644 studio/components/interfaces/BranchManagement/CreateBranchSidePanel.tsx create mode 100644 studio/data/integrations/integrations-github-branch-check.ts diff --git a/studio/components/interfaces/BranchManagement/BranchManagement.tsx b/studio/components/interfaces/BranchManagement/BranchManagement.tsx index d1853e4145d..bc2d4f5258b 100644 --- a/studio/components/interfaces/BranchManagement/BranchManagement.tsx +++ b/studio/components/interfaces/BranchManagement/BranchManagement.tsx @@ -27,9 +27,9 @@ import { useSelectedOrganization, useSelectedProject, useStore } from 'hooks' import { OPT_IN_TAGS } from 'lib/constants' import { useAppStateSnapshot } from 'state/app-state' import { MainBranchPanel } from './BranchPanels' -import CreateBranchSidePanel from './CreateBranchSidePanel' import PreviewBranches from './PreviewBranches' import PullRequests from './PullRequests' +import CreateBranchModal from './CreateBranchModal' const BranchManagement = () => { const { ui } = useStore() @@ -247,10 +247,12 @@ const BranchManagement = () => { - setShowCreateBranch(false)} - /> + /> */} + + setShowCreateBranch(false)} /> ) } diff --git a/studio/components/interfaces/BranchManagement/BranchPanels.tsx b/studio/components/interfaces/BranchManagement/BranchPanels.tsx index 51350384d3c..88500274555 100644 --- a/studio/components/interfaces/BranchManagement/BranchPanels.tsx +++ b/studio/components/interfaces/BranchManagement/BranchPanels.tsx @@ -4,11 +4,6 @@ import dayjs from 'dayjs' import { noop } from 'lodash' import Link from 'next/link' import { forwardRef, PropsWithChildren, useState } from 'react' - -import { Markdown } from 'components/interfaces/Markdown' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { Branch } from 'data/branches/branches-query' -import { GitHubPullRequest } from 'data/integrations/integrations-github-pull-requests-query' import { Badge, Button, @@ -26,6 +21,11 @@ import { IconTrash, } from 'ui' +import { Markdown } from 'components/interfaces/Markdown' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { Branch } from 'data/branches/branches-query' +import { GitHubPullRequest } from 'data/integrations/integrations-github-pull-requests-query' + interface BranchPanelProps { repo?: string branch?: Branch @@ -99,7 +99,7 @@ const MainBranchPanel = ({ - + void +} + +const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps) => { + const { ref } = useParams() + const projectDetails = useSelectedProject() + const selectedOrg = useSelectedOrganization() + + // [Joshen] There's something weird with RHF that I can't figure out atm + // but calling form.formState.isValid somehow removes the onBlur check, + // and makes the validation run onChange instead. This is a workaround + const [isValid, setIsValid] = useState(false) + + const isBranch = projectDetails?.parent_project_ref !== undefined + const projectRef = + projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined + + const { + data: integrations, + error: integrationsError, + isLoading: isLoadingIntegrations, + isSuccess: isSuccessIntegrations, + isError: isErrorIntegrations, + } = useOrgIntegrationsQuery({ + orgSlug: selectedOrg?.slug, + }) + + const { data: branches } = useBranchesQuery({ projectRef }) + const { mutateAsync: checkGithubBranchValidity, isLoading: isChecking } = + useCheckGithubBranchValidity({ + onError: () => {}, + }) + + const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ + onSuccess: () => { + toast.success('Successfully created new branch') + onClose() + }, + }) + + const githubIntegration = integrations?.find( + (integration) => + integration.integration.name === 'GitHub' && + integration.connections.some( + (connection) => connection.supabase_project_ref === projectDetails?.parentRef + ) + ) + const githubConnection = githubIntegration?.connections?.find( + (connection) => connection.supabase_project_ref === projectDetails?.parentRef + ) + const [repoOwner, repoName] = githubConnection?.metadata.name.split('/') || [] + + const formId = 'create-branch-form' + const FormSchema = z.object({ + branchName: z.string().superRefine(async (val, ctx) => { + if ((branches ?? []).some((branch) => branch.git_branch === val)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'This branch already has a Preview Branch', + }) + return + } + + if (val.length > 0) { + try { + await checkGithubBranchValidity({ + organizationIntegrationId: githubIntegration?.id, + repoOwner, + repoName, + branchName: val, + }) + setIsValid(true) + } catch (error) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Unable to find branch from ${repoOwner}/${repoName}`, + }) + setIsValid(false) + return + } + } + }), + }) + const form = useForm>({ + mode: 'onBlur', + reValidateMode: 'onBlur', + resolver: zodResolver(FormSchema), + defaultValues: { branchName: '' }, + }) + + const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid + const onSubmit = (data: z.infer) => { + if (!projectRef) return console.error('Project ref is required') + createBranch({ projectRef, branchName: data.branchName, gitBranch: data.branchName }) + } + + useEffect(() => { + if (form && visible) { + setIsValid(false) + form.reset() + } + }, [form, visible]) + + return ( + +
setIsValid(false)} + > + + + {isLoadingIntegrations && } + {isErrorIntegrations && ( + + )} + {isSuccessIntegrations && ( +
+

+ Your project is currently connected to the repository: +

+
+

{githubConnection?.metadata.name}

+ + + + + +
+
+ )} +
+ + + + +

+ Choose a Git Branch to base your Preview Branch on. Any migration changes added to + this Git Branch will be run on this new Preview Branch. +

+ ( + + + + + +
+ {isChecking ? ( + + ) : isValid ? ( + + ) : null} +
+ + +
+ )} + /> +
+ + + + +
+ + +
+
+
+
+
+ ) +} + +export default CreateBranchModal diff --git a/studio/components/interfaces/BranchManagement/CreateBranchSidePanel.tsx b/studio/components/interfaces/BranchManagement/CreateBranchSidePanel.tsx deleted file mode 100644 index bb50dcdb4db..00000000000 --- a/studio/components/interfaces/BranchManagement/CreateBranchSidePanel.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { useParams } from 'common' -import Link from 'next/link' -import { useEffect, useState } from 'react' -import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, - Button, - IconExternalLink, - IconGitBranch, - IconSearch, - Input, - SidePanel, -} from 'ui' - -import AlertError from 'components/ui/AlertError' -import NoSearchResults from 'components/ui/NoSearchResults' -import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useBranchCreateMutation } from 'data/branches/branch-create-mutation' -import { useBranchesQuery } from 'data/branches/branches-query' -import { useGithubBranchesQuery } from 'data/integrations/integrations-github-branches-query' -import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only' -import { useSelectedOrganization, useSelectedProject, useStore } from 'hooks' - -interface CreateBranchSidePanelProps { - visible: boolean - onClose: () => void -} - -// [Joshen] Optimization: Remove Form component, just use Combobox - -const CreateBranchSidePanel = ({ visible, onClose }: CreateBranchSidePanelProps) => { - const { ui } = useStore() - const { ref } = useParams() - const projectDetails = useSelectedProject() - const selectedOrg = useSelectedOrganization() - - const [searchValue, setSearchValue] = useState('') - const [selectedBranch, setSelectedBranch] = useState() - - useEffect(() => { - if (visible) { - setSelectedBranch(undefined) - } - }, [visible]) - - const isBranch = projectDetails?.parent_project_ref !== undefined - const projectRef = - projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined - - const { data: branches } = useBranchesQuery({ projectRef }) - const { - data: integrations, - error: integrationsError, - isLoading: isLoadingIntegrations, - isSuccess: isSuccessIntegrations, - isError: isErrorIntegrations, - } = useOrgIntegrationsQuery({ - orgSlug: selectedOrg?.slug, - }) - const githubIntegration = integrations?.find( - (integration) => - integration.integration.name === 'GitHub' && - integration.connections.some( - (connection) => connection.supabase_project_ref === projectDetails?.parentRef - ) - ) - - const githubConnection = githubIntegration?.connections?.find( - (connection) => connection.supabase_project_ref === projectDetails?.parentRef - ) - const [repoOwner, repoName] = githubConnection?.metadata.name.split('/') || [] - const { - data: githubBranches, - error: githubBranchesError, - isLoading: isLoadingBranches, - isSuccess: isSuccessBranches, - isError: isErrorBranches, - } = useGithubBranchesQuery({ - organizationIntegrationId: githubIntegration?.id, - repoOwner, - repoName, - }) - const branchOptions = - searchValue.length > 0 - ? (githubBranches || [])?.filter((branch) => branch.name.includes(searchValue)) - : githubBranches || [] - - const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ - onSuccess: () => { - ui.setNotification({ category: 'success', message: `Successfully created new branch` }) - onClose() - }, - }) - - const onCreateBranch = (branch: string) => { - if (!projectRef) return console.error('Project ref is required') - setSelectedBranch(branch) - createBranch({ projectRef, branchName: branch, gitBranch: branch }) - } - - return ( - -
- - {isLoadingIntegrations && } - {isSuccessIntegrations && ( -
-

- Your project is currently connected to the repository: -

-
-

{githubConnection?.metadata.name}

- - - - - -
-
- )} -
- - - - -
-

- Select a Git branch to create a database preview from -

- {(isLoadingIntegrations || isLoadingBranches) && } - {(isErrorIntegrations || isErrorBranches) && ( - - )} - {isSuccessIntegrations && isSuccessBranches && ( - <> - } - onChange={(event) => setSearchValue(event.target.value)} - /> - {branchOptions.length === 0 && searchValue.length === 0 && ( - - No branches available in repository - -

Create a branch in the {repoName} repository on Github first

- - - - - -
-
- )} - {branchOptions.length === 0 && searchValue.length > 0 && ( - setSearchValue('')} - /> - )} -
- {branchOptions?.map((branch) => { - const isProductionBranch = branches?.find( - (b) => b.git_branch === branch.name && b.is_default - ) - const alreadyHasDatabaseBranch = - branches?.some((b) => b.git_branch === branch.name) ?? false - return ( -
-
-
- -

{branch.name}

-
- -
-
- ) - })} -
- - )} -
-
-
-
- ) -} - -export default CreateBranchSidePanel diff --git a/studio/components/interfaces/Organization/NewProject/FreeProjectLimitWarning.tsx b/studio/components/interfaces/Organization/NewProject/FreeProjectLimitWarning.tsx index 6d2cf6b26bb..fd9d77af898 100644 --- a/studio/components/interfaces/Organization/NewProject/FreeProjectLimitWarning.tsx +++ b/studio/components/interfaces/Organization/NewProject/FreeProjectLimitWarning.tsx @@ -43,10 +43,10 @@ const FreeProjectLimitWarning = ({ {orgLevelBilling && ( )} diff --git a/studio/components/layouts/AppLayout/EnableBranchingButton/EnableBranchingModal.tsx b/studio/components/layouts/AppLayout/EnableBranchingButton/EnableBranchingModal.tsx index 31df2b56070..67d7013b7fc 100644 --- a/studio/components/layouts/AppLayout/EnableBranchingButton/EnableBranchingModal.tsx +++ b/studio/components/layouts/AppLayout/EnableBranchingButton/EnableBranchingModal.tsx @@ -1,21 +1,26 @@ +import { zodResolver } from '@hookform/resolvers/zod' import { useParams } from 'common' import Link from 'next/link' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { useForm } from 'react-hook-form' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, + Form_Shadcn_, IconAlertCircle, IconFileText, IconGitBranch, Modal, } from 'ui' +import * as z from 'zod' import SidePanelGitHubRepoLinker from 'components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker' import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useBranchCreateMutation } from 'data/branches/branch-create-mutation' +import { useCheckGithubBranchValidity } from 'data/integrations/integrations-github-branch-check' import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only' import { useSelectedOrganization, useStore } from 'hooks' import { useAppStateSnapshot } from 'state/app-state' @@ -24,17 +29,15 @@ import GithubRepositorySelection from './GithubRepositorySelection' const EnableBranchingModal = () => { const { ui } = useStore() const { ref } = useParams() + const snap = useAppStateSnapshot() const selectedOrg = useSelectedOrganization() - const snap = useAppStateSnapshot() - const [selectedBranch, setSelectedBranch] = useState() - + // [Joshen] There's something weird with RHF that I can't figure out atm + // but calling form.formState.isValid somehow removes the onBlur check, + // and makes the validation run onChange instead. This is a workaround + const [isValid, setIsValid] = useState(false) const isOrgBilling = !!selectedOrg?.subscription_id - useEffect(() => { - if (snap.showEnableBranchingModal) setSelectedBranch(undefined) - }, [snap.showEnableBranchingModal]) - const { data: integrations, error: integrationsError, @@ -45,13 +48,6 @@ const EnableBranchingModal = () => { orgSlug: selectedOrg?.slug, }) - const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ - onSuccess: () => { - ui.setNotification({ category: 'success', message: `Successfully created new branch` }) - snap.setShowEnableBranchingModal(false) - }, - }) - const hasGithubIntegrationInstalled = integrations?.some((integration) => integration.integration.name === 'GitHub') ?? false const githubIntegration = integrations?.find( @@ -59,13 +55,64 @@ const EnableBranchingModal = () => { integration.integration.name === 'GitHub' && integration.organization.slug === selectedOrg?.slug ) + const githubConnection = githubIntegration?.connections.find( + (connection) => connection.supabase_project_ref === ref + ) + const [repoOwner, repoName] = githubConnection?.metadata.name.split('/') ?? [] - const onEnableBranching = () => { + const { mutateAsync: checkGithubBranchValidity, isLoading: isChecking } = + useCheckGithubBranchValidity({ onError: () => {} }) + + const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ + onSuccess: () => { + ui.setNotification({ category: 'success', message: `Successfully created new branch` }) + snap.setShowEnableBranchingModal(false) + }, + }) + + const formId = 'enable-branching-form' + const FormSchema = z.object({ + branchName: z + .string() + .refine((val) => val.length > 1, `Please enter a branch name from ${repoOwner}/${repoName}`) + .refine(async (val) => { + try { + if (val.length > 0) { + await checkGithubBranchValidity({ + organizationIntegrationId: githubIntegration?.id, + repoOwner, + repoName, + branchName: val, + }) + setIsValid(true) + } + return true + } catch (error) { + setIsValid(false) + return false + } + }, `Unable to find branch from ${repoOwner}/${repoName}`), + }) + const form = useForm>({ + mode: 'onBlur', + reValidateMode: 'onChange', + resolver: zodResolver(FormSchema), + defaultValues: { branchName: '' }, + }) + + const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid + const onSubmit = (data: z.infer) => { if (!ref) return console.error('Project ref is required') - if (!selectedBranch) return console.error('No branch selected') - createBranch({ projectRef: ref, branchName: selectedBranch, gitBranch: selectedBranch }) + createBranch({ projectRef: ref, branchName: data.branchName, gitBranch: data.branchName }) } + useEffect(() => { + if (form && snap.showEnableBranchingModal) { + setIsValid(false) + form.reset() + } + }, [form, snap.showEnableBranchingModal]) + return ( <> { className="!bg" size="medium" > - - -
-

Enable database branching

-

Manage environments in Supabase

-
-
- - {!isOrgBilling ? ( - <> - - - - - Organization-based billing migration required - - -

- Your organization will first need to be migrated to use our new - organization-based billing before you can enable Branching. -

-

- You may do so under the billing settings of your organization. -

-
-
-
- -
- - - - + +
setIsValid(false)} + > + + +
+

Enable database branching

+

Manage environments in Supabase

- - ) : ( - <> - {isLoadingIntegrations && ( + + {!isOrgBilling ? ( <> - - - + + + + + Organization-based billing migration required + + +

+ Your organization will first need to be migrated to use our new + organization-based billing before you can enable Branching. +

+

+ You may do so under the billing settings of your organization. +

+
+
- - - )} - - {isErrorIntegrations && ( - <> - - - + +
+ + + + +
- - )} - - {isSuccessIntegrations && ( + ) : ( <> - + {isLoadingIntegrations && ( + <> + + + + + + + )} + + {isErrorIntegrations && ( + <> + + + + + + + )} + + {isSuccessIntegrations && ( + <> + + + )} + + +

Please keep in mind the following:

+
+
+
+ +
+
+
+

+ You will not be able to use the dashboard to make changes to the database +

+

+ Schema changes for database preview branches must be done via Git. We are + nonetheless working on allowing the dashboard to make schema changes for + preview branches. +

+
+
+
+ + + + +
+ + +
+
)} - - -

Please keep in mind the following:

-
-
-
- -
-
-
-

- You will not be able to use the dashboard to make changes to the database -

-

- Schema changes for database preview branches must be done via Git. We are - nonetheless working on allowing the dashboard to make schema changes for preview - branches. -

-
-
-
- - - - -
- - -
-
- - )} + +
diff --git a/studio/components/layouts/AppLayout/EnableBranchingButton/GithubRepositorySelection.tsx b/studio/components/layouts/AppLayout/EnableBranchingButton/GithubRepositorySelection.tsx index 1a7ccceaede..00294773c8f 100644 --- a/studio/components/layouts/AppLayout/EnableBranchingButton/GithubRepositorySelection.tsx +++ b/studio/components/layouts/AppLayout/EnableBranchingButton/GithubRepositorySelection.tsx @@ -1,57 +1,48 @@ import clsx from 'clsx' import { useParams } from 'common' import Link from 'next/link' -import { useRef, useState } from 'react' import { Badge, Button, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - Command_Shadcn_, + FormControl_Shadcn_, + FormField_Shadcn_, + FormItem_Shadcn_, + FormMessage_Shadcn_, IconCheck, - IconChevronDown, + IconLoader, + Input_Shadcn_, Modal, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - Popover_Shadcn_, } from 'ui' import { EmptyIntegrationConnection, IntegrationConnection, } from 'components/interfaces/Integrations/IntegrationPanels' -import AlertError from 'components/ui/AlertError' -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useGithubBranchesQuery } from 'data/integrations/integrations-github-branches-query' import { Integration } from 'data/integrations/integrations.types' -import { useSidePanelsStateSnapshot } from 'state/side-panels' import { useSelectedOrganization } from 'hooks' +import { useSidePanelsStateSnapshot } from 'state/side-panels' interface GithubRepositorySelectionProps { + form: any + isChecking: boolean + isValid: boolean integration?: Integration - selectedBranch?: string hasGithubIntegrationInstalled: boolean - setSelectedBranch: (name: string) => void } const GithubRepositorySelection = ({ + form, + isChecking, + isValid, integration, - selectedBranch, hasGithubIntegrationInstalled, - setSelectedBranch, }: GithubRepositorySelectionProps) => { - const org = useSelectedOrganization() const { ref } = useParams() - const [open, setOpen] = useState(false) - const comboBoxRef = useRef(null) + const org = useSelectedOrganization() const githubConnection = integration?.connections.find( (connection) => connection.supabase_project_ref === ref ) - const [repoOwner, repoName] = githubConnection?.metadata.name.split('/') ?? [] const sidePanels = useSidePanelsStateSnapshot() const githubIntegrationAppUrl = @@ -61,18 +52,6 @@ const GithubRepositorySelection = ({ ? `https://github.com/apps/supabase-staging/installations/new?state=${ref}` : `https://github.com/apps/supabase-local-testing/installations/new?state=${ref}` - const { - data: githubBranches, - error: githubBranchesError, - isLoading: isLoadingBranches, - isSuccess: isSuccessBranches, - isError: isErrorBranches, - } = useGithubBranchesQuery({ - organizationIntegrationId: integration?.id, - repoOwner, - repoName, - }) - function onSelectConnectRepo() { if (integration) { sidePanels.setGithubConnectionsOpen(true) @@ -130,77 +109,29 @@ const GithubRepositorySelection = ({ /> -
- - {isLoadingBranches && } - {isErrorBranches && ( - + ( + + + + + +
+ {isChecking ? ( + + ) : isValid ? ( + + ) : null} +
+ + +
)} - {isSuccessBranches && ( - - - - - - - - - No branches found - - {githubBranches?.map((branch) => ( - { - setOpen(false) - setSelectedBranch(branch.name) - }} - onClick={() => { - setOpen(false) - setSelectedBranch(branch.name) - }} - > - - {branch.name} - {branch.name === selectedBranch && } - - - ))} - - - - - - )} -
+ /> )}
diff --git a/studio/data/integrations/integrations-github-branch-check.ts b/studio/data/integrations/integrations-github-branch-check.ts new file mode 100644 index 00000000000..6b5ff72dde7 --- /dev/null +++ b/studio/data/integrations/integrations-github-branch-check.ts @@ -0,0 +1,65 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { toast } from 'react-hot-toast' + +import { get } from 'data/fetchers' +import { ResponseError } from 'types' + +export type GithubBranchVariables = { + organizationIntegrationId?: string + repoOwner: string + repoName: string + branchName: string +} + +export async function checkGithubBranchValidity( + { organizationIntegrationId, repoOwner, repoName, branchName }: GithubBranchVariables, + signal?: AbortSignal +) { + if (!organizationIntegrationId) throw new Error('Organization integration ID is required') + + const { data, error } = await get( + '/platform/integrations/github/branches/{organization_integration_id}/{repo_owner}/{repo_name}/{branch_name}', + { + params: { + path: { + organization_integration_id: organizationIntegrationId, + repo_owner: repoOwner, + repo_name: repoName, + branch_name: branchName, + }, + }, + signal, + } + ) + + if (error) throw error + return data +} + +type GitHubIntegrationCreateData = Awaited> + +export const useCheckGithubBranchValidity = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + return useMutation( + (vars) => checkGithubBranchValidity(vars), + { + async onSuccess(data, variables, context) { + await onSuccess?.(data, variables, context) + }, + async onError(data, variables, context) { + if (onError === undefined) { + toast.error(`Failed to check Github branch: ${data.message}`) + } else { + onError(data, variables, context) + } + }, + ...options, + } + ) +} diff --git a/studio/data/integrations/keys.ts b/studio/data/integrations/keys.ts index f881bb9d572..ae8cdf03c13 100644 --- a/studio/data/integrations/keys.ts +++ b/studio/data/integrations/keys.ts @@ -8,6 +8,19 @@ export const integrationKeys = { ['organizations', organization_integration_id, 'github-repos'] as const, vercelConnectionsList: (organization_integration_id: string | undefined) => ['organizations', organization_integration_id, 'vercel-connections'] as const, + githubBranch: ( + organization_integration_id: string | undefined, + repo_owner: string, + repo_name: string, + branch_name: string + ) => [ + 'organizations', + organization_integration_id, + 'branches', + repo_owner, + repo_name, + branch_name, + ], githubBranchesList: ( organization_integration_id: string | undefined, repo_owner: string,