mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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
This commit is contained in:
1 parent
c4b20e114a
commit
237fdb8db0
9 files changed
+557
-475
No files matched your search
@@ -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 = () => {
|
||||
</Modal.Content>
|
||||
</ConfirmationModal>
|
||||
|
||||
<CreateBranchSidePanel
|
||||
{/* <CreateBranchSidePanel
|
||||
visible={showCreateBranch}
|
||||
onClose={() => setShowCreateBranch(false)}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<CreateBranchModal visible={showCreateBranch} onClose={() => setShowCreateBranch(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = ({
|
||||
<span></span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end">
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-48">
|
||||
<Link passHref href={`/project/${ref}/settings/integrations`}>
|
||||
<DropdownMenuItem
|
||||
asChild
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
Button,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormItem_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
Form_Shadcn_,
|
||||
IconCheck,
|
||||
IconExternalLink,
|
||||
IconLoader,
|
||||
Input_Shadcn_,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import * as z from 'zod'
|
||||
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
|
||||
import { useBranchesQuery } from 'data/branches/branches-query'
|
||||
import { useCheckGithubBranchValidity } from 'data/integrations/integrations-github-branch-check'
|
||||
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
|
||||
import { useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
|
||||
interface CreateBranchModalProps {
|
||||
visible: boolean
|
||||
onClose: () => 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<z.infer<typeof FormSchema>>({
|
||||
mode: 'onBlur',
|
||||
reValidateMode: 'onBlur',
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: { branchName: '' },
|
||||
})
|
||||
|
||||
const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid
|
||||
const onSubmit = (data: z.infer<typeof FormSchema>) => {
|
||||
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 (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
className="space-y-4"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
onChange={() => setIsValid(false)}
|
||||
>
|
||||
<Modal
|
||||
hideFooter
|
||||
size="medium"
|
||||
modal={false}
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
header="Create a new preview branch"
|
||||
confirmText="Create Preview Branch"
|
||||
>
|
||||
<Modal.Content className="pt-3 pb-1">
|
||||
{isLoadingIntegrations && <GenericSkeletonLoader />}
|
||||
{isErrorIntegrations && (
|
||||
<AlertError
|
||||
error={integrationsError}
|
||||
subject="Failed to retrieve Github repository information"
|
||||
/>
|
||||
)}
|
||||
{isSuccessIntegrations && (
|
||||
<div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your project is currently connected to the repository:
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>{githubConnection?.metadata.name}</p>
|
||||
<Link passHref href={`https://github.com/${repoOwner}/${repoName}`}>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<IconExternalLink size={14} strokeWidth={1.5} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal.Content>
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content className="pt-1 pb-3 space-y-3">
|
||||
<p className="text-sm">
|
||||
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.
|
||||
</p>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="branchName"
|
||||
render={({ field }) => (
|
||||
<FormItem_Shadcn_ className="relative">
|
||||
<label className="text-sm text-foreground-light">
|
||||
Choose your branch to create a preview from
|
||||
</label>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} placeholder="e.g feat/some-feature" />
|
||||
</FormControl_Shadcn_>
|
||||
<div className="absolute top-9 right-3">
|
||||
{isChecking ? (
|
||||
<IconLoader className="animate-spin" />
|
||||
) : isValid ? (
|
||||
<IconCheck className="text-brand" strokeWidth={2} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<FormMessage_Shadcn_ />
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2 py-2 pb-4">
|
||||
<Button disabled={isCreating} type="default" onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
form={formId}
|
||||
disabled={isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Create Preview branch
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateBranchModal
|
||||
@@ -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<string>()
|
||||
|
||||
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 (
|
||||
<SidePanel
|
||||
hideFooter
|
||||
size="large"
|
||||
visible={visible}
|
||||
loading={isCreating}
|
||||
onCancel={onClose}
|
||||
header="Create a new database preview branch"
|
||||
>
|
||||
<div className="py-6 space-y-4 ">
|
||||
<SidePanel.Content>
|
||||
{isLoadingIntegrations && <GenericSkeletonLoader />}
|
||||
{isSuccessIntegrations && (
|
||||
<div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your project is currently connected to the repository:
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>{githubConnection?.metadata.name}</p>
|
||||
<Link passHref href={`https://github.com/${repoOwner}/${repoName}`}>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<IconExternalLink size={14} strokeWidth={1.5} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SidePanel.Content>
|
||||
|
||||
<SidePanel.Separator />
|
||||
|
||||
<SidePanel.Content>
|
||||
<div>
|
||||
<p className="text-sm prose mb-2">
|
||||
Select a Git branch to create a database preview from
|
||||
</p>
|
||||
{(isLoadingIntegrations || isLoadingBranches) && <GenericSkeletonLoader />}
|
||||
{(isErrorIntegrations || isErrorBranches) && (
|
||||
<AlertError
|
||||
error={integrationsError || githubBranchesError}
|
||||
subject="Failed to retrieve Github branches"
|
||||
/>
|
||||
)}
|
||||
{isSuccessIntegrations && isSuccessBranches && (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search branch..."
|
||||
className="mb-3"
|
||||
value={searchValue}
|
||||
icon={<IconSearch />}
|
||||
onChange={(event) => setSearchValue(event.target.value)}
|
||||
/>
|
||||
{branchOptions.length === 0 && searchValue.length === 0 && (
|
||||
<Alert_Shadcn_ variant="default" className="!pl-4">
|
||||
<AlertTitle_Shadcn_>No branches available in repository</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p>Create a branch in the {repoName} repository on Github first</p>
|
||||
<Link passHref href={`https://github.com/${repoOwner}/${repoName}`}>
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<Button type="default" iconRight={<IconExternalLink />} className="mt-2">
|
||||
Create a new branch on Github
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
{branchOptions.length === 0 && searchValue.length > 0 && (
|
||||
<NoSearchResults
|
||||
searchString={searchValue}
|
||||
onResetFilter={() => setSearchValue('')}
|
||||
/>
|
||||
)}
|
||||
<div className="[&>*:first-child]:rounded-t-md">
|
||||
{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 (
|
||||
<div
|
||||
key={branch.name}
|
||||
className="px-6 py-4 bg-surface-200 last:rounded-b-md border border-b-0 last:border-b"
|
||||
>
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<IconGitBranch size={14} strokeWidth={1.5} />
|
||||
<p className="text-sm">{branch.name}</p>
|
||||
</div>
|
||||
<Button
|
||||
type={isProductionBranch ? 'warning' : 'default'}
|
||||
loading={isCreating}
|
||||
disabled={isCreating || alreadyHasDatabaseBranch}
|
||||
onClick={() => onCreateBranch(branch.name)}
|
||||
className={
|
||||
selectedBranch !== undefined && selectedBranch !== branch.name
|
||||
? 'opacity-0'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{isProductionBranch
|
||||
? 'Production branch'
|
||||
: alreadyHasDatabaseBranch
|
||||
? 'Branch already created'
|
||||
: 'Create branch'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SidePanel.Content>
|
||||
</div>
|
||||
</SidePanel>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateBranchSidePanel
|
||||
@@ -43,10 +43,10 @@ const FreeProjectLimitWarning = ({
|
||||
|
||||
{orgLevelBilling && (
|
||||
<div>
|
||||
<Link href={`/org/${orgSlug}/billing?panel=subscriptionPlan`} passHref>
|
||||
<a target="_blank">
|
||||
<Button type="primary">Upgrade plan</Button>
|
||||
</a>
|
||||
<Link passHref href={`/org/${orgSlug}/billing?panel=subscriptionPlan`}>
|
||||
<Button asChild type="primary">
|
||||
<a>Upgrade plan</a>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+195
-134
@@ -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<string>()
|
||||
|
||||
// [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<z.infer<typeof FormSchema>>({
|
||||
mode: 'onBlur',
|
||||
reValidateMode: 'onChange',
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: { branchName: '' },
|
||||
})
|
||||
|
||||
const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid
|
||||
const onSubmit = (data: z.infer<typeof FormSchema>) => {
|
||||
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 (
|
||||
<>
|
||||
<Modal
|
||||
@@ -75,131 +122,145 @@ const EnableBranchingModal = () => {
|
||||
className="!bg"
|
||||
size="medium"
|
||||
>
|
||||
<Modal.Content className="px-7 py-5 flex items-center space-x-4">
|
||||
<IconGitBranch strokeWidth={2} size={20} />
|
||||
<div>
|
||||
<p className="text-foreground">Enable database branching</p>
|
||||
<p className="text-sm text-light">Manage environments in Supabase</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
{!isOrgBilling ? (
|
||||
<>
|
||||
<Modal.Content className="px-3 border-y bg-alternative">
|
||||
<Alert_Shadcn_ variant="default" className="rounded-none border-0">
|
||||
<IconAlertCircle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Organization-based billing migration required
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p className="!leading-normal">
|
||||
Your organization will first need to be migrated to use our new
|
||||
organization-based billing before you can enable Branching.
|
||||
</p>
|
||||
<p className="!leading-normal mt-1">
|
||||
You may do so under the billing settings of your organization.
|
||||
</p>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</Modal.Content>
|
||||
<Modal.Content className="px-4 pt-3">
|
||||
<div className="flex items-center justify-end space-x-2 py-2 pb-4">
|
||||
<Button
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
<Link passHref href={`/org/${selectedOrg?.slug}/billing`}>
|
||||
<Button asChild>
|
||||
<a>Organization settings</a>
|
||||
</Button>
|
||||
</Link>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
className="space-y-4"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
onChange={() => setIsValid(false)}
|
||||
>
|
||||
<Modal.Content className="px-7 py-5 flex items-center space-x-4">
|
||||
<IconGitBranch strokeWidth={2} size={20} />
|
||||
<div>
|
||||
<p className="text-foreground">Enable database branching</p>
|
||||
<p className="text-sm text-light">Manage environments in Supabase</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{isLoadingIntegrations && (
|
||||
|
||||
{!isOrgBilling ? (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="px-7 py-6">
|
||||
<GenericSkeletonLoader />
|
||||
<Modal.Content className="px-3 border-y bg-alternative">
|
||||
<Alert_Shadcn_ variant="default" className="rounded-none border-0">
|
||||
<IconAlertCircle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Organization-based billing migration required
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p className="!leading-normal">
|
||||
Your organization will first need to be migrated to use our new
|
||||
organization-based billing before you can enable Branching.
|
||||
</p>
|
||||
<p className="!leading-normal mt-1">
|
||||
You may do so under the billing settings of your organization.
|
||||
</p>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isErrorIntegrations && (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="px-7 py-6">
|
||||
<AlertError error={integrationsError} subject="Failed to retrieve integrations" />
|
||||
<Modal.Content className="px-4 pt-3">
|
||||
<div className="flex items-center justify-end space-x-2 py-2 pb-4">
|
||||
<Button
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
<Link passHref href={`/org/${selectedOrg?.slug}/billing`}>
|
||||
<Button asChild>
|
||||
<a>Organization settings</a>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSuccessIntegrations && (
|
||||
) : (
|
||||
<>
|
||||
<GithubRepositorySelection
|
||||
integration={githubIntegration}
|
||||
selectedBranch={selectedBranch}
|
||||
hasGithubIntegrationInstalled={hasGithubIntegrationInstalled}
|
||||
setSelectedBranch={setSelectedBranch}
|
||||
/>
|
||||
{isLoadingIntegrations && (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="px-7 py-6">
|
||||
<GenericSkeletonLoader />
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isErrorIntegrations && (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="px-7 py-6">
|
||||
<AlertError
|
||||
error={integrationsError}
|
||||
subject="Failed to retrieve integrations"
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSuccessIntegrations && (
|
||||
<>
|
||||
<GithubRepositorySelection
|
||||
form={form}
|
||||
isChecking={isChecking}
|
||||
isValid={canSubmit}
|
||||
integration={githubIntegration}
|
||||
hasGithubIntegrationInstalled={hasGithubIntegrationInstalled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Modal.Content className="px-7 py-6 flex flex-col gap-3">
|
||||
<p className="text-sm text-light">Please keep in mind the following:</p>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div>
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-300 flex items-center justify-center">
|
||||
<IconFileText className="text-amber-900" size={20} strokeWidth={2} />
|
||||
</figure>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-foreground">
|
||||
You will not be able to use the dashboard to make changes to the database
|
||||
</p>
|
||||
<p className="text-sm text-light">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content className="px-7">
|
||||
<div className="flex items-center space-x-2 py-2 pb-4">
|
||||
<Button
|
||||
size="medium"
|
||||
block
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
form={formId}
|
||||
disabled={isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
I understand, enable branching
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Modal.Content className="px-7 py-6 flex flex-col gap-3">
|
||||
<p className="text-sm text-light">Please keep in mind the following:</p>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div>
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-300 flex items-center justify-center">
|
||||
<IconFileText className="text-amber-900" size={20} strokeWidth={2} />
|
||||
</figure>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-foreground">
|
||||
You will not be able to use the dashboard to make changes to the database
|
||||
</p>
|
||||
<p className="text-sm text-light">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content className="px-7">
|
||||
<div className="flex items-center space-x-2 py-2 pb-4">
|
||||
<Button
|
||||
size="medium"
|
||||
block
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="medium"
|
||||
block
|
||||
disabled={selectedBranch === undefined || isCreating}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
onClick={() => onEnableBranching()}
|
||||
>
|
||||
I understand, enable branching
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
</Modal>
|
||||
|
||||
<SidePanelGitHubRepoLinker projectRef={ref} />
|
||||
|
||||
+36
-105
@@ -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<HTMLButtonElement>(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 = ({
|
||||
/>
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-light mb-2" htmlFor="branch-selector">
|
||||
Select your production branch:
|
||||
</label>
|
||||
{isLoadingBranches && <ShimmeringLoader />}
|
||||
{isErrorBranches && (
|
||||
<AlertError
|
||||
error={githubBranchesError}
|
||||
subject="Failed to retrieve Github branches"
|
||||
/>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="branchName"
|
||||
render={({ field }) => (
|
||||
<FormItem_Shadcn_ className="relative">
|
||||
<label className="text-sm text-foreground-light">
|
||||
Choose your production branch
|
||||
</label>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} placeholder="e.g main" />
|
||||
</FormControl_Shadcn_>
|
||||
<div className="absolute top-9 right-3">
|
||||
{isChecking ? (
|
||||
<IconLoader className="animate-spin" />
|
||||
) : isValid ? (
|
||||
<IconCheck className="text-brand" strokeWidth={2} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<FormMessage_Shadcn_ />
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
{isSuccessBranches && (
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild name="branch-selector">
|
||||
<Button
|
||||
block
|
||||
type="default"
|
||||
size="medium"
|
||||
ref={comboBoxRef}
|
||||
className={clsx(
|
||||
'justify-start',
|
||||
selectedBranch === undefined ? 'text-light' : 'text'
|
||||
)}
|
||||
iconRight={
|
||||
<span className="grow flex justify-end">
|
||||
<IconChevronDown className={''} />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{selectedBranch || 'Select a branch'}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_
|
||||
className="p-0"
|
||||
side="bottom"
|
||||
align="start"
|
||||
style={{ width: comboBoxRef.current?.offsetWidth }}
|
||||
>
|
||||
<Command_Shadcn_>
|
||||
<CommandInput_Shadcn_ placeholder="Find branch..." />
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No branches found</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{githubBranches?.map((branch) => (
|
||||
<CommandItem_Shadcn_
|
||||
asChild
|
||||
key={branch.name}
|
||||
value={branch.name}
|
||||
className="cursor-pointer w-full flex items-center justify-between"
|
||||
onSelect={() => {
|
||||
setOpen(false)
|
||||
setSelectedBranch(branch.name)
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setSelectedBranch(branch.name)
|
||||
}}
|
||||
>
|
||||
<a>
|
||||
{branch.name}
|
||||
{branch.name === selectedBranch && <IconCheck />}
|
||||
</a>
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<ReturnType<typeof checkGithubBranchValidity>>
|
||||
|
||||
export const useCheckGithubBranchValidity = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<GitHubIntegrationCreateData, ResponseError, GithubBranchVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
return useMutation<GitHubIntegrationCreateData, ResponseError, GithubBranchVariables>(
|
||||
(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,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user