mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 21:59:16 +03:00
refactor
This commit is contained in:
1 parent
2077d7804c
commit
c9bc3d347e
8 files changed
+325
-247
No files matched your search
@@ -1,8 +1,9 @@
|
||||
import { DiffEditor } from '@monaco-editor/react'
|
||||
import { Database, Wind, Loader2 } from 'lucide-react'
|
||||
import { editor as monacoEditor } from 'monaco-editor'
|
||||
import Link from 'next/link'
|
||||
import { Card, CardContent, CardHeader, CardTitle, Skeleton } from 'ui'
|
||||
import { Database, Wind } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from 'ui'
|
||||
|
||||
import DiffViewer from 'components/ui/DiffViewer'
|
||||
import { Loading, ErrorState, EmptyState } from 'components/ui/AsyncState'
|
||||
|
||||
interface DatabaseDiffPanelProps {
|
||||
diffContent?: string
|
||||
@@ -17,64 +18,21 @@ const DatabaseDiffPanel = ({
|
||||
diffContent,
|
||||
isLoading,
|
||||
error,
|
||||
showRefreshButton = false,
|
||||
currentBranchRef,
|
||||
onRefresh,
|
||||
}: DatabaseDiffPanelProps) => {
|
||||
// Monaco editor options for diff display
|
||||
const defaultOptions: monacoEditor.IStandaloneDiffEditorConstructionOptions = {
|
||||
readOnly: true,
|
||||
renderSideBySide: false,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
folding: false,
|
||||
padding: { top: 16, bottom: 16 },
|
||||
lineNumbersMinChars: 3,
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
}
|
||||
if (isLoading) return <Loading />
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton className="h-64" />
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const isConnectionError =
|
||||
error.message?.includes('ECONNREFUSED') || error.message?.includes('Failed to diff branch')
|
||||
|
||||
return (
|
||||
<div className="p-6 text-center">
|
||||
<div className="text-red-500 mb-2">
|
||||
<p>Error loading branch diff</p>
|
||||
</div>
|
||||
{isConnectionError ? (
|
||||
<div className="text-sm text-gray-600 space-y-1">
|
||||
<p>Unable to connect to the branch database. This could be because:</p>
|
||||
<ul className="list-disc list-inside text-left max-w-md mx-auto">
|
||||
<li>The branch database is not ready yet</li>
|
||||
<li>The branch is in an inactive state</li>
|
||||
<li>There's a temporary networking issue</li>
|
||||
</ul>
|
||||
<p className="mt-2">Please try again in a few minutes.</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-600">{error.message}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (error) return <ErrorState message="Error loading branch diff" />
|
||||
|
||||
// Handle empty diff content
|
||||
if (!diffContent || diffContent.trim() === '') {
|
||||
return (
|
||||
<div className="p-6 text-center">
|
||||
<Wind size={32} strokeWidth={1.5} className="text-foreground-muted mx-auto mb-8" />
|
||||
<h3 className="mb-1">No changes detected between branches</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Any changes to your database schema will be shown here for review
|
||||
</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
title="No changes detected between branches"
|
||||
description="Any changes to your database schema will be shown here for review"
|
||||
icon={Wind}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,14 +50,7 @@ const DatabaseDiffPanel = ({
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0 h-96">
|
||||
<DiffEditor
|
||||
theme="supabase"
|
||||
language="sql"
|
||||
height="100%"
|
||||
original=""
|
||||
modified={diffContent}
|
||||
options={defaultOptions}
|
||||
/>
|
||||
<DiffViewer language="sql" original="" modified={diffContent} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { DiffEditor } from '@monaco-editor/react'
|
||||
import { editor as monacoEditor } from 'monaco-editor'
|
||||
import {
|
||||
getEdgeFunctionBody,
|
||||
type EdgeFunctionBodyData,
|
||||
} from 'data/edge-functions/edge-function-body-query'
|
||||
import type { EdgeFunctionsData } from 'data/edge-functions/edge-functions-query'
|
||||
import { Card, CardContent, CardHeader, CardTitle, cn, Skeleton } from 'ui'
|
||||
import type { EdgeFunctionBodyData } from 'data/edge-functions/edge-function-body-query'
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle, cn } from 'ui'
|
||||
import { Code, Wind } from 'lucide-react'
|
||||
import DiffViewer from 'components/ui/DiffViewer'
|
||||
import { Loading, EmptyState } from 'components/ui/AsyncState'
|
||||
import useEdgeFunctionsDiff from 'hooks/misc/useEdgeFunctionsDiff'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import { basename } from 'path'
|
||||
|
||||
interface EdgeFunctionsDiffPanelProps {
|
||||
currentBranchFunctions?: EdgeFunctionsData
|
||||
@@ -28,7 +27,7 @@ interface FunctionDiffProps {
|
||||
}
|
||||
|
||||
// Helper to canonicalize file identifiers to prevent mismatch due to differing root paths
|
||||
const fileKey = (fullPath: string) => fullPath.split('/').pop() ?? fullPath
|
||||
const fileKey = (fullPath: string) => basename(fullPath)
|
||||
|
||||
const FunctionDiff = ({
|
||||
functionSlug,
|
||||
@@ -36,20 +35,6 @@ const FunctionDiff = ({
|
||||
mainBody,
|
||||
currentBranchRef,
|
||||
}: FunctionDiffProps) => {
|
||||
// Monaco editor options for diff display
|
||||
const defaultOptions: monacoEditor.IStandaloneDiffEditorConstructionOptions = {
|
||||
readOnly: true,
|
||||
renderSideBySide: false,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
folding: false,
|
||||
padding: { top: 16, bottom: 16 },
|
||||
lineNumbersMinChars: 3,
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
}
|
||||
|
||||
// Determine list of files with differences (by canonical key)
|
||||
const diffFileKeys = useMemo(() => {
|
||||
const keys = new Set([...currentBody, ...mainBody].map((f) => fileKey(f.name)))
|
||||
@@ -126,13 +111,10 @@ const FunctionDiff = ({
|
||||
|
||||
{/* Diff viewer */}
|
||||
<div className="flex-1 min-h-0">
|
||||
<DiffEditor
|
||||
theme="supabase"
|
||||
<DiffViewer
|
||||
language={language}
|
||||
height="100%"
|
||||
original={mainFile?.content || ''}
|
||||
modified={currentFile?.content || ''}
|
||||
options={defaultOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,150 +131,40 @@ const EdgeFunctionsDiffPanel = ({
|
||||
currentBranchRef,
|
||||
mainBranchRef,
|
||||
}: EdgeFunctionsDiffPanelProps) => {
|
||||
// Compare edge functions between branches for added/removed and potential overlap
|
||||
const compareEdgeFunctions = () => {
|
||||
if (!currentBranchFunctions || !mainBranchFunctions) {
|
||||
return { added: [], removed: [], overlap: [] as typeof currentBranchFunctions }
|
||||
}
|
||||
|
||||
const currentFuncs = currentBranchFunctions || []
|
||||
const mainFuncs = mainBranchFunctions || []
|
||||
|
||||
const added = currentFuncs.filter(
|
||||
(currentFunc) => !mainFuncs.find((mainFunc) => mainFunc.slug === currentFunc.slug)
|
||||
)
|
||||
|
||||
const removed = mainFuncs.filter(
|
||||
(mainFunc) => !currentFuncs.find((currentFunc) => currentFunc.slug === mainFunc.slug)
|
||||
)
|
||||
|
||||
// Functions that exist in both branches are potential modifications
|
||||
const overlap = currentFuncs.filter((currentFunc) =>
|
||||
mainFuncs.find((f) => f.slug === currentFunc.slug)
|
||||
)
|
||||
|
||||
return { added, removed, overlap }
|
||||
}
|
||||
|
||||
const {
|
||||
added = [],
|
||||
removed = [],
|
||||
overlap = [],
|
||||
} = useMemo(compareEdgeFunctions, [currentBranchFunctions, mainBranchFunctions])
|
||||
|
||||
// Fetch bodies for overlapping functions in both branches
|
||||
const overlapSlugs = overlap.map((f) => f.slug)
|
||||
|
||||
// FIRST_EDIT: introduce slug arrays for added and removed
|
||||
const addedSlugs = added.map((f) => f.slug)
|
||||
const removedSlugs = removed.map((f) => f.slug)
|
||||
|
||||
const currentBodiesQueries = useQueries({
|
||||
queries: overlapSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', currentBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
||||
enabled: !!currentBranchRef,
|
||||
})),
|
||||
addedSlugs,
|
||||
removedSlugs,
|
||||
modifiedSlugs,
|
||||
addedBodiesMap,
|
||||
removedBodiesMap,
|
||||
currentBodiesMap,
|
||||
mainBodiesMap,
|
||||
isLoading,
|
||||
hasChanges,
|
||||
} = useEdgeFunctionsDiff({
|
||||
currentBranchFunctions,
|
||||
mainBranchFunctions,
|
||||
currentBranchRef,
|
||||
mainBranchRef,
|
||||
})
|
||||
|
||||
const mainBodiesQueries = useQueries({
|
||||
queries: overlapSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', mainBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
||||
enabled: !!mainBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
// SECOND_EDIT: fetch bodies for added and removed functions
|
||||
const addedBodiesQueries = useQueries({
|
||||
queries: addedSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', currentBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
||||
enabled: !!currentBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
const removedBodiesQueries = useQueries({
|
||||
queries: removedSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', mainBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
||||
enabled: !!mainBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
// THIRD_EDIT: include added/removed loading status
|
||||
const isBodiesLoading =
|
||||
currentBodiesQueries.some((q) => q.isLoading) ||
|
||||
mainBodiesQueries.some((q) => q.isLoading) ||
|
||||
addedBodiesQueries.some((q) => q.isLoading) ||
|
||||
removedBodiesQueries.some((q) => q.isLoading)
|
||||
|
||||
const currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
currentBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) currentBodiesMap[overlapSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
const mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
mainBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) mainBodiesMap[overlapSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
// FOURTH_EDIT: build bodies map for added and removed slugs
|
||||
const addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
addedBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) addedBodiesMap[addedSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
const removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
removedBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) removedBodiesMap[removedSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
// Determine which overlapping functions actually have modifications in any of their files
|
||||
const modifiedSlugs = overlapSlugs.filter((slug) => {
|
||||
const currentBody = currentBodiesMap[slug]
|
||||
const mainBody = mainBodiesMap[slug]
|
||||
if (!currentBody || !mainBody) return false
|
||||
|
||||
const keys = new Set([...currentBody, ...mainBody].map((f) => fileKey(f.name)))
|
||||
|
||||
for (const key of keys) {
|
||||
const currentFile = currentBody.find((f) => fileKey(f.name) === key)
|
||||
const mainFile = mainBody.find((f) => fileKey(f.name) === key)
|
||||
if ((currentFile?.content || '') !== (mainFile?.content || '')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const hasChanges = added.length > 0 || removed.length > 0 || modifiedSlugs.length > 0
|
||||
|
||||
if (isCurrentFunctionsLoading || isMainFunctionsLoading || isBodiesLoading) {
|
||||
return <Skeleton className="h-64" />
|
||||
if (isCurrentFunctionsLoading || isMainFunctionsLoading || isLoading) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
// If no changes and checking complete
|
||||
if (!hasChanges) {
|
||||
return (
|
||||
<div className="p-6 text-center">
|
||||
<Wind size={32} strokeWidth={1.5} className="text-foreground-muted mx-auto mb-8" />
|
||||
<h3 className="mb-1">No changes detected between branches</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Any changes to your edge functions will be shown here for review
|
||||
</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
title="No changes detected between branches"
|
||||
description="Any changes to your edge functions will be shown here for review"
|
||||
icon={Wind}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{added.length > 0 && (
|
||||
{addedSlugs.length > 0 && (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
{addedSlugs.map((slug) => (
|
||||
@@ -308,7 +180,7 @@ const EdgeFunctionsDiffPanel = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{removed.length > 0 && (
|
||||
{removedSlugs.length > 0 && (
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
{removedSlugs.map((slug) => (
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { sanitizeRoute } from './ProjectDropdown'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
|
||||
const BranchLink = ({
|
||||
branch,
|
||||
@@ -73,6 +74,7 @@ const BranchLink = ({
|
||||
export const BranchDropdown = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const gitlessBranching = useFlag('gitlessBranching')
|
||||
const projectDetails = useSelectedProject()
|
||||
|
||||
const isBranch = projectDetails?.parent_project_ref !== undefined
|
||||
@@ -144,28 +146,6 @@ export const BranchDropdown = () => {
|
||||
</CommandGroup_Shadcn_>
|
||||
<CommandSeparator_Shadcn_ />
|
||||
<CommandGroup_Shadcn_>
|
||||
{/* Show merge button only for non-main branches */}
|
||||
{isBranch && selectedBranch && !selectedBranch.is_default && (
|
||||
<>
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer w-full"
|
||||
onSelect={() => {
|
||||
setOpen(false)
|
||||
router.push(`/project/${ref}/merge`)
|
||||
}}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<Link
|
||||
href={`/project/${ref}/merge`}
|
||||
className="w-full flex items-center gap-2"
|
||||
>
|
||||
<GitMerge size={14} strokeWidth={1.5} />
|
||||
<p>Request a merge</p>
|
||||
</Link>
|
||||
</CommandItem_Shadcn_>
|
||||
<CommandSeparator_Shadcn_ />
|
||||
</>
|
||||
)}
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer w-full"
|
||||
onSelect={(e) => {
|
||||
@@ -179,9 +159,33 @@ export const BranchDropdown = () => {
|
||||
className="w-full flex items-center gap-2"
|
||||
>
|
||||
<ListTree size={14} strokeWidth={1.5} />
|
||||
<p>Manage branches</p>
|
||||
Manage branches
|
||||
</Link>
|
||||
</CommandItem_Shadcn_>
|
||||
{/* Show merge button only for non-main branches, gated by feature flag */}
|
||||
{gitlessBranching &&
|
||||
isBranch &&
|
||||
selectedBranch &&
|
||||
!selectedBranch.is_default && (
|
||||
<>
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer w-full"
|
||||
onSelect={() => {
|
||||
setOpen(false)
|
||||
router.push(`/project/${ref}/merge`)
|
||||
}}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<Link
|
||||
href={`/project/${ref}/merge`}
|
||||
className="w-full flex items-center gap-2"
|
||||
>
|
||||
<GitMerge size={14} strokeWidth={1.5} />
|
||||
Review changes
|
||||
</Link>
|
||||
</CommandItem_Shadcn_>
|
||||
</>
|
||||
)}
|
||||
</CommandGroup_Shadcn_>
|
||||
<CommandSeparator_Shadcn_ />
|
||||
<CommandGroup_Shadcn_>
|
||||
|
||||
@@ -81,8 +81,8 @@ export const PageLayout = ({
|
||||
className={cn(
|
||||
'w-full mx-auto',
|
||||
size === 'full' &&
|
||||
(isCompact ? 'max-w-none !px-6 border-b' : 'max-w-none p!x-8 border-b'),
|
||||
isCompact ? 'pt-4' : 'pt-12',
|
||||
(isCompact ? 'max-w-none !px-6 border-b pt-4' : 'max-w-none pt-6 border-b'),
|
||||
size !== 'full' && (isCompact ? 'pt-4' : 'pt-12'),
|
||||
navigationItems.length === 0 && size === 'full' && (isCompact ? 'pb-4' : 'pb-8'),
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Skeleton } from 'ui'
|
||||
import { CircleAlert, LucideIcon, Wind } from 'lucide-react'
|
||||
|
||||
export const Loading = () => <Skeleton className="h-64" />
|
||||
|
||||
export const ErrorState = ({
|
||||
message = 'An unexpected error has occurred',
|
||||
}: {
|
||||
message?: string
|
||||
}) => (
|
||||
<div className="p-6 text-center">
|
||||
<CircleAlert size={32} strokeWidth={1.5} className="text-foreground-muted mx-auto mb-8" />
|
||||
<h3 className="mb-1">{message}</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Please try again in a few minutes and contact support if the problem persists.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const EmptyState = ({
|
||||
title,
|
||||
description,
|
||||
icon: Icon = Wind,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
icon?: LucideIcon
|
||||
}) => (
|
||||
<div className="p-6 text-center">
|
||||
<Icon size={32} strokeWidth={1.5} className="text-foreground-muted mx-auto mb-8" />
|
||||
<h3 className="mb-1">{title}</h3>
|
||||
<p className="text-sm text-foreground-light">{description}</p>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
import { DiffEditor } from '@monaco-editor/react'
|
||||
import { editor as monacoEditor } from 'monaco-editor'
|
||||
|
||||
interface DiffViewerProps {
|
||||
/** Original/left hand side content (optional) */
|
||||
original?: string
|
||||
/** Modified/right hand side content */
|
||||
modified: string | undefined
|
||||
/** Language identifier understood by Monaco */
|
||||
language: string
|
||||
/** Height for the editor container */
|
||||
height?: string | number
|
||||
/** Whether to render diffs side-by-side */
|
||||
sideBySide?: boolean
|
||||
}
|
||||
|
||||
// Centralised set of options so all diff editors look the same
|
||||
const DEFAULT_OPTIONS: monacoEditor.IStandaloneDiffEditorConstructionOptions = {
|
||||
readOnly: true,
|
||||
renderSideBySide: false,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
folding: false,
|
||||
padding: { top: 16, bottom: 16 },
|
||||
lineNumbersMinChars: 3,
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
}
|
||||
|
||||
export const DiffViewer = ({
|
||||
original = '',
|
||||
modified = '',
|
||||
language,
|
||||
height = '100%',
|
||||
sideBySide = false,
|
||||
}: DiffViewerProps) => (
|
||||
<DiffEditor
|
||||
theme="supabase"
|
||||
language={language}
|
||||
height={height}
|
||||
original={original}
|
||||
modified={modified}
|
||||
options={{ ...DEFAULT_OPTIONS, renderSideBySide: sideBySide }}
|
||||
/>
|
||||
)
|
||||
|
||||
export default DiffViewer
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import {
|
||||
getEdgeFunctionBody,
|
||||
type EdgeFunctionBodyData,
|
||||
} from 'data/edge-functions/edge-function-body-query'
|
||||
import type { EdgeFunctionsData } from 'data/edge-functions/edge-functions-query'
|
||||
import { basename } from 'path'
|
||||
|
||||
interface UseEdgeFunctionsDiffProps {
|
||||
currentBranchFunctions?: EdgeFunctionsData
|
||||
mainBranchFunctions?: EdgeFunctionsData
|
||||
currentBranchRef?: string
|
||||
mainBranchRef?: string
|
||||
}
|
||||
|
||||
export interface EdgeFunctionsDiffResult {
|
||||
addedSlugs: string[]
|
||||
removedSlugs: string[]
|
||||
modifiedSlugs: string[]
|
||||
addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
||||
removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
||||
currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
||||
mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
|
||||
isLoading: boolean
|
||||
hasChanges: boolean
|
||||
}
|
||||
|
||||
// Small helper around path.basename but avoids importing the full Node path lib for the browser bundle
|
||||
const fileKey = (fullPath: string) => basename(fullPath)
|
||||
|
||||
export const useEdgeFunctionsDiff = ({
|
||||
currentBranchFunctions,
|
||||
mainBranchFunctions,
|
||||
currentBranchRef,
|
||||
mainBranchRef,
|
||||
}: UseEdgeFunctionsDiffProps): EdgeFunctionsDiffResult => {
|
||||
// Identify added / removed / overlapping functions
|
||||
const {
|
||||
added = [],
|
||||
removed = [],
|
||||
overlap = [],
|
||||
} = useMemo(() => {
|
||||
if (!currentBranchFunctions || !mainBranchFunctions) {
|
||||
return { added: [], removed: [], overlap: [] as typeof currentBranchFunctions }
|
||||
}
|
||||
|
||||
const currentFuncs = currentBranchFunctions ?? []
|
||||
const mainFuncs = mainBranchFunctions ?? []
|
||||
|
||||
const added = currentFuncs.filter((c) => !mainFuncs.find((m) => m.slug === c.slug))
|
||||
const removed = mainFuncs.filter((m) => !currentFuncs.find((c) => c.slug === m.slug))
|
||||
const overlap = currentFuncs.filter((c) => mainFuncs.find((m) => m.slug === c.slug))
|
||||
|
||||
return { added, removed, overlap }
|
||||
}, [currentBranchFunctions, mainBranchFunctions])
|
||||
|
||||
const overlapSlugs = overlap.map((f) => f.slug)
|
||||
const addedSlugs = added.map((f) => f.slug)
|
||||
const removedSlugs = removed.map((f) => f.slug)
|
||||
|
||||
// Fetch function bodies ---------------------------------------------------
|
||||
const currentBodiesQueries = useQueries({
|
||||
queries: overlapSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', currentBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
||||
enabled: !!currentBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
const mainBodiesQueries = useQueries({
|
||||
queries: overlapSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', mainBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
||||
enabled: !!mainBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
const addedBodiesQueries = useQueries({
|
||||
queries: addedSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', currentBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
|
||||
enabled: !!currentBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
const removedBodiesQueries = useQueries({
|
||||
queries: removedSlugs.map((slug) => ({
|
||||
queryKey: ['edge-function-body', mainBranchRef, slug],
|
||||
queryFn: ({ signal }: { signal?: AbortSignal }) =>
|
||||
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
|
||||
enabled: !!mainBranchRef,
|
||||
})),
|
||||
})
|
||||
|
||||
// Flatten loading flags ----------------------------------------------------
|
||||
const isLoading = [
|
||||
...currentBodiesQueries,
|
||||
...mainBodiesQueries,
|
||||
...addedBodiesQueries,
|
||||
...removedBodiesQueries,
|
||||
].some((q) => q.isLoading)
|
||||
|
||||
// Build lookup maps --------------------------------------------------------
|
||||
const currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
currentBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) currentBodiesMap[overlapSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
const mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
mainBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) mainBodiesMap[overlapSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
const addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
addedBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) addedBodiesMap[addedSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
const removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
|
||||
removedBodiesQueries.forEach((q, idx) => {
|
||||
if (q.data) removedBodiesMap[removedSlugs[idx]] = q.data
|
||||
})
|
||||
|
||||
// Determine modified slugs -------------------------------------------------
|
||||
const modifiedSlugs = overlapSlugs.filter((slug) => {
|
||||
const currentBody = currentBodiesMap[slug]
|
||||
const mainBody = mainBodiesMap[slug]
|
||||
if (!currentBody || !mainBody) return false
|
||||
|
||||
const keys = new Set([...currentBody, ...mainBody].map((f) => fileKey(f.name)))
|
||||
|
||||
for (const key of keys) {
|
||||
const currentFile = currentBody.find((f) => fileKey(f.name) === key)
|
||||
const mainFile = mainBody.find((f) => fileKey(f.name) === key)
|
||||
if ((currentFile?.content || '') !== (mainFile?.content || '')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const hasChanges = addedSlugs.length > 0 || removedSlugs.length > 0 || modifiedSlugs.length > 0
|
||||
|
||||
return {
|
||||
addedSlugs,
|
||||
removedSlugs,
|
||||
modifiedSlugs,
|
||||
addedBodiesMap,
|
||||
removedBodiesMap,
|
||||
currentBodiesMap,
|
||||
mainBodiesMap,
|
||||
isLoading,
|
||||
hasChanges,
|
||||
}
|
||||
}
|
||||
|
||||
export default useEdgeFunctionsDiff
|
||||
@@ -23,10 +23,17 @@ import { GitMerge } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import WorkflowLogsCard from 'components/interfaces/BranchManagement/WorkflowLogsCard'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
|
||||
const MergePage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
|
||||
const gitlessBranching = useFlag('gitlessBranching')
|
||||
if (!gitlessBranching) {
|
||||
router.push(`/project/${ref}/branches`)
|
||||
}
|
||||
|
||||
const project = useSelectedProject()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
|
||||
Reference in new issue
Block a user