Feature: Duplicate functions from the studio dashboard (#39821)

* added feature to duplicate functions

* updated formatting for index file

* removed unused ai assitant code

---------

Co-authored-by: Ali Waseem <awaseem@Alis-MacBook-Pro.local>
This commit is contained in:
Ali Waseem
2025-10-23 15:48:53 -06:00
committed by GitHub
parent f236876270
commit c41a5be8a5
5 changed files with 74 additions and 69 deletions

View File

@@ -2,15 +2,15 @@ import { X } from 'lucide-react'
import { SheetClose, SheetHeader, SheetTitle, cn } from 'ui'
interface CreateFunctionHeaderProps {
selectedFunction?: string
isDuplicating?: boolean
}
export const CreateFunctionHeader = ({
selectedFunction,
assistantVisible,
setAssistantVisible,
}: {
selectedFunction?: string
assistantVisible: boolean
setAssistantVisible: (v: boolean) => void
}) => {
isDuplicating,
}: CreateFunctionHeaderProps) => {
return (
<SheetHeader className="py-3 flex flex-row justify-between items-center border-b-0">
<div className="flex flex-row gap-3 items-center max-w-[75%]">
@@ -27,33 +27,12 @@ export const CreateFunctionHeader = ({
</SheetClose>
<SheetTitle className="truncate">
{selectedFunction !== undefined
? `Edit '${selectedFunction}' function`
? isDuplicating
? `Duplicate function`
: `Edit '${selectedFunction}' function`
: 'Add a new function'}
</SheetTitle>
</div>
{/* <Tooltip>
<TooltipTrigger asChild>
<button
aria-expanded={assistantVisible}
aria-controls="ai-chat-assistant"
className={cn(
!assistantVisible ? 'text-foreground-lighter' : 'text-light',
'hover:text-foreground',
'transition'
)}
onClick={() => setAssistantVisible(!assistantVisible)}
>
{!assistantVisible ? (
<PanelLeftClose size={19} strokeWidth={1} />
) : (
<PanelRightClose size={19} strokeWidth={1} />
)}
</button>
</TooltipTrigger>
<TooltipContent side="left">
{assistantVisible ? 'Hide' : 'Show'} tools
</TooltipContent>
</Tooltip> */}
</SheetHeader>
)
}

View File

@@ -50,8 +50,9 @@ const FORM_ID = 'create-function-sidepanel'
interface CreateFunctionProps {
func?: DatabaseFunction
isDuplicating?: boolean
visible: boolean
setVisible: (value: boolean) => void
onClose: () => void
}
const FormSchema = z.object({
@@ -68,15 +69,13 @@ const FormSchema = z.object({
.optional(),
})
const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
const CreateFunction = ({ func, visible, isDuplicating = false, onClose }: CreateFunctionProps) => {
const { data: project } = useSelectedProjectQuery()
const [isClosingPanel, setIsClosingPanel] = useState(false)
const [advancedSettingsShown, setAdvancedSettingsShown] = useState(false)
// For now, there's no AI assistant for functions
const [assistantVisible, setAssistantVisible] = useState(false)
const [focusedEditor, setFocusedEditor] = useState(false)
const isEditing = !!func?.id
const isEditing = !isDuplicating && !!func?.id
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
@@ -89,7 +88,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
useDatabaseFunctionUpdateMutation()
function isClosingSidePanel() {
form.formState.isDirty ? setIsClosingPanel(true) : setVisible(!visible)
form.formState.isDirty ? setIsClosingPanel(true) : onClose()
}
const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (data) => {
@@ -111,7 +110,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
{
onSuccess: () => {
toast.success(`Successfully updated function ${data.name}`)
setVisible(!visible)
onClose()
},
}
)
@@ -125,7 +124,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
{
onSuccess: () => {
toast.success(`Successfully created function ${data.name}`)
setVisible(!visible)
onClose()
},
}
)
@@ -155,19 +154,11 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
<Sheet open={visible} onOpenChange={() => isClosingSidePanel()}>
<SheetContent
showClose={false}
size={assistantVisible ? 'lg' : 'default'}
className={cn(
// 'bg-surface-200',
'p-0 flex flex-row gap-0',
assistantVisible ? '!min-w-screen lg:!min-w-[1200px]' : '!min-w-screen lg:!min-w-[600px]'
)}
size={'default'}
className={'p-0 flex flex-row gap-0 !min-w-screen lg:!min-w-[600px]'}
>
<div className={cn('flex flex-col grow w-full', assistantVisible && 'w-[60%]')}>
<CreateFunctionHeader
selectedFunction={func?.name}
assistantVisible={assistantVisible}
setAssistantVisible={setAssistantVisible}
/>
<div className="flex flex-col grow w-full">
<CreateFunctionHeader selectedFunction={func?.name} isDuplicating={isDuplicating} />
<Separator />
<Form_Shadcn_ {...form}>
<form
@@ -405,11 +396,6 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
</Button>
</SheetFooter>
</div>
{assistantVisible ? (
<div className="border-l shadow-[rgba(0,0,0,0.13)_-4px_0px_6px_0px] z-10 w-[50%] bg-studio">
{/* This is where the AI assistant would be added */}
</div>
) : null}
<ConfirmationModal
visible={isClosingPanel}
title="Discard changes"
@@ -417,7 +403,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
onCancel={() => setIsClosingPanel(false)}
onConfirm={() => {
setIsClosingPanel(false)
setVisible(!visible)
onClose()
}}
>
<p className="text-sm text-foreground-light">

View File

@@ -1,6 +1,6 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { includes, noop, sortBy } from 'lodash'
import { Edit, Edit2, FileText, MoreVertical, Trash } from 'lucide-react'
import { Copy, Edit, Edit2, FileText, MoreVertical, Trash } from 'lucide-react'
import { useRouter } from 'next/router'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -23,6 +23,7 @@ interface FunctionListProps {
schema: string
filterString: string
isLocked: boolean
duplicateFunction: (fn: any) => void
editFunction: (fn: any) => void
deleteFunction: (fn: any) => void
}
@@ -31,6 +32,7 @@ const FunctionList = ({
schema,
filterString,
isLocked,
duplicateFunction = noop,
editFunction = noop,
deleteFunction = noop,
}: FunctionListProps) => {
@@ -132,6 +134,7 @@ const FunctionList = ({
<p>Client API docs</p>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem className="space-x-2" onClick={() => editFunction(x)}>
<Edit2 size={14} />
<p>Edit function</p>
@@ -169,6 +172,13 @@ const FunctionList = ({
<Edit size={14} />
<p>Edit function with Assistant</p>
</DropdownMenuItem>
<DropdownMenuItem
className="space-x-2"
onClick={() => duplicateFunction(x)}
>
<Copy size={14} />
<p>Duplicate function</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="space-x-2" onClick={() => deleteFunction(x)}>
<Trash size={14} className="text-destructive" />

View File

@@ -32,6 +32,7 @@ import FunctionList from './FunctionList'
interface FunctionsListProps {
createFunction: () => void
duplicateFunction: (fn: PostgresFunction) => void
editFunction: (fn: PostgresFunction) => void
deleteFunction: (fn: PostgresFunction) => void
}
@@ -40,6 +41,7 @@ const FunctionsList = ({
createFunction = noop,
editFunction = noop,
deleteFunction = noop,
duplicateFunction = noop,
}: FunctionsListProps) => {
const router = useRouter()
const { search } = useParams()
@@ -199,6 +201,7 @@ const FunctionsList = ({
schema={selectedSchema}
filterString={filterString}
isLocked={isSchemaLocked}
duplicateFunction={duplicateFunction}
editFunction={editFunction}
deleteFunction={deleteFunction}
/>

View File

@@ -19,6 +19,7 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
const [selectedFunction, setSelectedFunction] = useState<DatabaseFunction | undefined>()
const [showCreateFunctionForm, setShowCreateFunctionForm] = useState(false)
const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useState(false)
const [isDuplicating, setIsDuplicating] = useState(false)
const isInlineEditorEnabled = useIsInlineEditorEnabled()
// Local editor panel state
@@ -42,6 +43,23 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
}
}
const duplicateFunction = (fn: DatabaseFunction) => {
setIsDuplicating(true)
const dupFn = {
...fn,
name: `${fn.name}_duplicate`,
}
if (isInlineEditorEnabled) {
setSelectedFunctionForEditor(dupFn)
setEditorPanelOpen(true)
} else {
setSelectedFunction(dupFn)
setShowCreateFunctionForm(true)
}
}
const editFunction = (fn: DatabaseFunction) => {
if (isInlineEditorEnabled) {
setSelectedFunctionForEditor(fn)
@@ -57,6 +75,12 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
setShowDeleteFunctionForm(true)
}
const resetEditorPanel = () => {
setIsDuplicating(false)
setEditorPanelOpen(false)
setSelectedFunctionForEditor(undefined)
}
if (isPermissionsLoaded && !canReadFunctions) {
return <NoPermission isFullPage resourceText="view database functions" />
}
@@ -72,6 +96,7 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
/>
<FunctionsList
createFunction={createFunction}
duplicateFunction={duplicateFunction}
editFunction={editFunction}
deleteFunction={deleteFunction}
/>
@@ -81,7 +106,11 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
<CreateFunction
func={selectedFunction}
visible={showCreateFunctionForm}
setVisible={setShowCreateFunctionForm}
onClose={() => {
setShowCreateFunctionForm(false)
setIsDuplicating(false)
}}
isDuplicating={isDuplicating}
/>
<DeleteFunction
func={selectedFunction}
@@ -91,14 +120,8 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => {
<EditorPanel
open={editorPanelOpen}
onRunSuccess={() => {
setEditorPanelOpen(false)
setSelectedFunctionForEditor(undefined)
}}
onClose={() => {
setEditorPanelOpen(false)
setSelectedFunctionForEditor(undefined)
}}
onRunSuccess={resetEditorPanel}
onClose={resetEditorPanel}
initialValue={
selectedFunctionForEditor
? selectedFunctionForEditor.complete_statement
@@ -113,12 +136,16 @@ $$;`
}
label={
selectedFunctionForEditor
? `Edit function "${selectedFunctionForEditor.name}"`
? isDuplicating
? `Duplicate function "${selectedFunctionForEditor.name}"`
: `Edit function "${selectedFunctionForEditor.name}"`
: 'Create new database function'
}
initialPrompt={
selectedFunctionForEditor
? `Update the database function "${selectedFunctionForEditor.name}" to...`
? isDuplicating
? `Duplicate the database function "${selectedFunctionForEditor.name}" to...`
: `Update the database function "${selectedFunctionForEditor.name}" to...`
: 'Create a new database function that...'
}
/>