This commit is contained in:
Saxon Fletcher committed 2025-07-04 11:51:31 +10:00
1 parent 67eebdd850
commit 862e30864a
8 files changed
+139 -96

No files matched your search

@@ -27,13 +27,8 @@ import { Button } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal'
import { BranchLoader, BranchManagementSection, BranchRow, MergeRequestRow } from './BranchPanels'
import { CreateBranchModal } from './CreateBranchModal'
import {
BranchingEmptyState,
PreviewBranchesEmptyState,
PullRequestsEmptyState,
} from './EmptyStates'
import Overview from './Overview'
import { PreviewBranchesEmptyState, PullRequestsEmptyState } from './EmptyStates'
import { Overview } from './Overview'
type Tab = 'overview' | 'prs' | 'branches'
@@ -36,6 +36,7 @@ import {
} from 'ui'
import { sanitizeRoute } from './ProjectDropdown'
import { useFlag } from 'hooks/ui/useFlag'
import { useAppStateSnapshot } from 'state/app-state'
const BranchLink = ({
branch,
@@ -80,11 +81,11 @@ export const BranchDropdown = () => {
const gitlessBranching = useFlag('gitlessBranching')
const projectDetails = useSelectedProject()
const snap = useAppStateSnapshot()
const [open, setOpen] = useState(false)
const [showDeployRequestModal, setShowDeployRequestModal] = useState(false)
const projectRef = projectDetails?.parent_project_ref || ref
const [open, setOpen] = useState(false)
const [showDeployRequestModal, setShowDeployRequestModal] = useState(false)
const {
data: branches,
isLoading,
@@ -116,8 +117,6 @@ export const BranchDropdown = () => {
const BRANCHING_GITHUB_DISCUSSION_LINK = 'https://github.com/orgs/supabase/discussions/18937'
const [open, setOpen] = useState(false)
const MenuContent = ({
branchList,
showSearch,
@@ -162,23 +161,26 @@ export const BranchDropdown = () => {
<p>Create branch</p>
</div>
</CommandItem_Shadcn_>
{gitlessBranching && isBranch && selectedBranch && !selectedBranch.is_default && (
<>
<CommandItem_Shadcn_
className="cursor-pointer w-full"
onSelect={() => {
setOpen(false)
setShowDeployRequestModal(true)
}}
onClick={() => setOpen(false)}
>
<div className="w-full flex items-center gap-2">
<Send size={14} strokeWidth={1.5} />
Create deploy request
</div>
</CommandItem_Shadcn_>
</>
)}
{gitlessBranching &&
isBranchingEnabled &&
selectedBranch &&
!selectedBranch.is_default && (
<>
<CommandItem_Shadcn_
className="cursor-pointer w-full"
onSelect={() => {
setOpen(false)
setShowDeployRequestModal(true)
}}
onClick={() => setOpen(false)}
>
<div className="w-full flex items-center gap-2">
<Send size={14} strokeWidth={1.5} />
Create deploy request
</div>
</CommandItem_Shadcn_>
</>
)}
<CommandItem_Shadcn_
className="cursor-pointer w-full"
onSelect={() => {
@@ -27,7 +27,7 @@ export async function createMergeRequest({
id: `mr-${Date.now()}`,
project_ref: projectRef,
base: '6b3b94fe-f9f9-4c12-8796-ff67db250757',
head: '7902c891-5286-4366-a44d-007b39e0782d',
head: '3b13fe5b-a529-4bc4-a0c8-a333f83f03e9',
title,
description,
merge_requested_by: 'current-user-id',
@@ -0,0 +1,61 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { handleError, del } from 'data/fetchers'
import type { ResponseError } from 'types'
import { mergeRequestKeys } from './keys'
export type MergeRequestDeleteVariables = {
id: string
projectRef: string
}
export async function deleteMergeRequest({
id,
projectRef,
}: MergeRequestDeleteVariables): Promise<void> {
// Mock data for development
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 500)
})
// Real API call (commented out for now)
// const { error } = await del('/v1/merge-requests/{merge_request_id}', {
// params: {
// path: { merge_request_id: id },
// },
// })
// if (error) handleError(error)
}
export const useMergeRequestDeleteMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<void, ResponseError, MergeRequestDeleteVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<void, ResponseError, MergeRequestDeleteVariables>(
(vars) => deleteMergeRequest(vars),
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries(mergeRequestKeys.list(projectRef))
await queryClient.invalidateQueries(mergeRequestKeys.detail(projectRef, variables.id))
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to delete merge request: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
}
)
}
@@ -22,14 +22,12 @@ export async function getMergeRequest(
id: id,
project_ref: 'mock-project-ref',
base: '6b3b94fe-f9f9-4c12-8796-ff67db250757',
head: '7902c891-5286-4366-a44d-007b39e0782d',
head: '3b13fe5b-a529-4bc4-a0c8-a333f83f03e9',
title: 'Deploy feature-auth to production',
description:
'This deploy request includes new authentication features, improved error handling, and several bug fixes. The changes have been thoroughly tested in the staging environment.',
merge_requested_by: 'user-123',
merge_approved_by: null,
merge_requested_at: '2024-01-15T10:30:00Z',
merge_approved_at: null,
created_at: '2024-01-15T10:30:00Z',
updated_at: '2024-01-15T10:30:00Z',
})
@@ -22,7 +22,7 @@ export async function updateMergeRequest({
id: id,
project_ref: 'mock-project-ref',
base: '6b3b94fe-f9f9-4c12-8796-ff67db250757',
head: '7902c891-5286-4366-a44d-007b39e0782d',
head: '3b13fe5b-a529-4bc4-a0c8-a333f83f03e9',
title: 'Deploy feature-auth to production',
description:
'This deploy request includes new authentication features, improved error handling, and several bug fixes.',
@@ -15,9 +15,7 @@ export type MergeRequest = {
title: string
description?: string
merge_requested_by: string | null
merge_approved_by: string | null
merge_requested_at: string | null
merge_approved_at: string | null
created_at: string
updated_at: string
}
@@ -36,13 +34,11 @@ export async function getMergeRequests(
id: 'mr-1',
project_ref: projectRef,
base: '6b3b94fe-f9f9-4c12-8796-ff67db250757',
head: '7902c891-5286-4366-a44d-007b39e0782d',
head: '3b13fe5b-a529-4bc4-a0c8-a333f83f03e9',
title: 'Deploy feature-auth to production',
description: 'This PR includes new authentication features and bug fixes.',
merge_requested_by: 'user-123',
merge_approved_by: null,
merge_requested_at: '2024-01-15T10:30:00Z',
merge_approved_at: null,
created_at: '2024-01-15T10:30:00Z',
updated_at: '2024-01-15T10:30:00Z',
},
@@ -50,13 +46,11 @@ export async function getMergeRequests(
id: 'mr-2',
project_ref: projectRef,
base: '6b3b94fe-f9f9-4c12-8796-ff67db250757',
head: '7902c891-5286-4366-a44d-007b39e0782d',
head: '3b13fe5b-a529-4bc4-a0c8-a333f83f03e9',
title: 'Deploy hotfix-db-migration to production',
description: 'Critical database schema updates for user table.',
merge_requested_by: 'user-456',
merge_approved_by: 'user-789',
merge_requested_at: '2024-01-14T14:20:00Z',
merge_approved_at: '2024-01-14T15:45:00Z',
created_at: '2024-01-14T14:20:00Z',
updated_at: '2024-01-14T15:45:00Z',
},
@@ -12,7 +12,7 @@ import { useBranchesQuery } from 'data/branches/branches-query'
import { useBranchMergeMutation } from 'data/branches/branch-merge-mutation'
import { useBranchPushMutation } from 'data/branches/branch-push-mutation'
import { useMergeRequestQuery } from 'data/merge-requests/merge-request-query'
import { useMergeRequestUpdateMutation } from 'data/merge-requests/merge-request-update-mutation'
import { useMergeRequestDeleteMutation } from 'data/merge-requests/merge-request-delete-mutation'
import { useBranchMergeDiff } from 'hooks/branches/useBranchMergeDiff'
import { useWorkflowManagement } from 'hooks/branches/useWorkflowManagement'
import DatabaseDiffPanel from 'components/interfaces/BranchManagement/DatabaseDiffPanel'
@@ -31,6 +31,7 @@ import {
AlertTriangle,
CheckCircle,
Clock,
Trash,
} from 'lucide-react'
import Link from 'next/link'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -49,7 +50,6 @@ const MergeRequestPage: NextPageWithLayout = () => {
const [isSubmitting, setIsSubmitting] = useState(false)
const [workflowFinalStatus, setWorkflowFinalStatus] = useState<string | null>(null)
const isBranch = project?.parent_project_ref !== undefined
const parentProjectRef = project?.parent_project_ref || project?.ref
const parentProject = useProjectByRef(parentProjectRef)
@@ -124,8 +124,7 @@ const MergeRequestPage: NextPageWithLayout = () => {
onWorkflowComplete: (status) => {
setWorkflowFinalStatus(status)
refetchDiff()
edgeFunctionsDiff.refetchCurrentBranchFunctions()
edgeFunctionsDiff.refetchMainBranchFunctions()
edgeFunctionsDiff.clearDiffsOptimistically()
},
})
@@ -136,8 +135,7 @@ const MergeRequestPage: NextPageWithLayout = () => {
onWorkflowComplete: (status) => {
setWorkflowFinalStatus(status)
refetchDiff()
edgeFunctionsDiff.refetchCurrentBranchFunctions()
edgeFunctionsDiff.refetchMainBranchFunctions()
edgeFunctionsDiff.clearDiffsOptimistically()
},
})
@@ -186,18 +184,11 @@ const MergeRequestPage: NextPageWithLayout = () => {
const { mutate: mergeBranch, isLoading: isMerging } = useBranchMergeMutation({
onSuccess: (data) => {
setIsSubmitting(false)
if (data.hadChanges) {
if (data.migrationCreated) {
toast.success('Migration created and branch merge initiated!')
} else {
toast.success('Branch merge initiated!')
}
// Add workflow run ID to URL for persistence
if (data.workflowRunId) {
addWorkflowRun(data.workflowRunId)
}
if (data.workflowRunId) {
toast.success('Branch merge initiated!')
addWorkflowRun(data.workflowRunId)
} else {
toast.info('No changes to merge - branch merged successfully!')
toast.info('No changes to merge')
}
},
onError: (error) => {
@@ -206,15 +197,15 @@ const MergeRequestPage: NextPageWithLayout = () => {
},
})
const { mutate: updateMergeRequest, isLoading: isUpdatingMergeRequest } =
useMergeRequestUpdateMutation({
const { mutate: deleteMergeRequest, isLoading: isDeletingMergeRequest } =
useMergeRequestDeleteMutation({
onSuccess: () => {
toast.success('Merge request approved!')
// Proceed with actual merge
handleMerge()
toast.success('Merge request deleted!')
// Navigate back to merge requests list
router.push(`/project/${parentProjectRef}/branches?tab=prs`)
},
onError: (error) => {
toast.error(`Failed to approve merge request: ${error.message}`)
onError: (error: any) => {
toast.error(`Failed to delete merge request: ${error.message}`)
},
})
@@ -237,13 +228,11 @@ const MergeRequestPage: NextPageWithLayout = () => {
})
}
const handleApproveMerge = () => {
const handleDeleteMergeRequest = () => {
if (!mergeRequest?.id || !parentProjectRef) return
// TODO: Get current user ID
updateMergeRequest({
deleteMergeRequest({
id: mergeRequest.id,
projectRef: parentProjectRef,
merge_approved_by: 'current-user-id', // This should be replaced with actual user ID
})
}
@@ -355,25 +344,11 @@ const MergeRequestPage: NextPageWithLayout = () => {
)
}
const isMergeDisabled =
!combinedHasChanges ||
isCombinedDiffLoading ||
isBranchOutOfDateOverall ||
!mergeRequest.merge_approved_by
const isMergeDisabled = !combinedHasChanges || isCombinedDiffLoading || isBranchOutOfDateOverall
// Update primary actions - separate approve and merge actions
// Update primary actions - just merge action
const primaryActions = (
<div className="flex items-end gap-2">
{!mergeRequest.merge_approved_by && (
<Button
type="default"
loading={isUpdatingMergeRequest}
onClick={handleApproveMerge}
icon={<CheckCircle size={16} strokeWidth={1.5} />}
>
Approve
</Button>
)}
{isMergeDisabled ? (
<ButtonTooltip
tooltip={{
@@ -403,20 +378,15 @@ const MergeRequestPage: NextPageWithLayout = () => {
</div>
)
const pageTitle = () => (
<div className="flex items-center gap-4 flex-wrap">
<span>{mergeRequest?.title || `${mergeRequest?.head} → ${mergeRequest?.base}`}</span>
</div>
)
const pageTitle = () => mergeRequest?.title || `${mergeRequest?.head} → ${mergeRequest?.base}`
const pageSubtitle = () => {
if (!mergeRequest?.merge_requested_at) return 'Merge request information unavailable'
const requestedTime = dayjs(mergeRequest.merge_requested_at).fromNow()
const approvalStatus = mergeRequest.merge_approved_by ? 'Approved' : 'Open'
return (
<div className="flex items-center gap-2 mt-2">
<Badge variant="warning">{approvalStatus}</Badge>
<Badge variant="warning">Open</Badge>
{mergeRequest.merge_requested_by} requested to merge
<Link
href={`/project/${headBranch?.project_ref}/editor`}
@@ -485,13 +455,36 @@ const MergeRequestPage: NextPageWithLayout = () => {
}
overrideAction={
hasCurrentWorkflowFailed ? (
<div className="flex items-center gap-2">
<Button
type="default"
asChild
icon={<GitBranchIcon size={16} strokeWidth={1.5} />}
className="shrink-0"
>
<Link href={`/project/${parentProjectRef}/branches`}>
Create new branch
</Link>
</Button>
<Button
type="outline"
loading={isDeletingMergeRequest}
onClick={handleDeleteMergeRequest}
icon={<Trash size={16} strokeWidth={1.5} />}
className="shrink-0"
>
Delete merge request
</Button>
</div>
) : currentWorkflowRun?.status === 'FUNCTIONS_DEPLOYED' ? (
<Button
type="default"
asChild
icon={<GitBranchIcon size={16} strokeWidth={1.5} />}
type="outline"
loading={isDeletingMergeRequest}
onClick={handleDeleteMergeRequest}
icon={<Trash size={16} strokeWidth={1.5} />}
className="shrink-0"
>
<Link href={`/project/${parentProjectRef}/branches`}>Create new branch</Link>
Delete merge request
</Button>
) : undefined
}