From 2dcdb1fda1e310c0ddfe0dd314f670ec43f28007 Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 6 Nov 2025 18:26:18 +0100 Subject: [PATCH] chore(studio): fix param on load --- .../EnumeratedTypes/EnumeratedTypes.tsx | 39 +++- .../Functions/FunctionsList/FunctionList.tsx | 9 +- .../Functions/FunctionsList/FunctionsList.tsx | 168 ++++++++++++++++-- .../project/[ref]/database/functions.tsx | 142 ++------------- 4 files changed, 200 insertions(+), 158 deletions(-) diff --git a/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx b/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx index dd85b940aac..fb17ab3080b 100644 --- a/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx +++ b/apps/studio/components/interfaces/Database/EnumeratedTypes/EnumeratedTypes.tsx @@ -1,6 +1,7 @@ import { Edit, MoreVertical, Search, Trash } from 'lucide-react' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' +import { toast } from 'sonner' import AlertError from 'components/ui/AlertError' import { DocsButton } from 'components/ui/DocsButton' @@ -45,7 +46,6 @@ export const EnumeratedTypes = () => { 'edit', parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) ) - const [selectedTypeToDelete, setSelectedTypeToDelete] = useQueryState( 'delete', parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) @@ -55,6 +55,33 @@ export const EnumeratedTypes = () => { projectRef: project?.ref, connectionString: project?.connectionString, }) + + const typeToEdit = data?.find((type: EnumeratedType) => type.id.toString() === selectedTypeToEdit) + const showTypeToEdit = selectedTypeToEdit !== '' && !!typeToEdit + const typeToEditNotFound = selectedTypeToEdit !== '' && !typeToEdit + + const typeToDelete = data?.find( + (type: EnumeratedType) => type.id.toString() === selectedTypeToDelete + ) + const showTypeToDelete = selectedTypeToDelete !== '' && !!typeToDelete + const typeToDeleteNotFound = selectedTypeToDelete !== '' && !typeToDelete + + // Error handling if edit panel is open and type is not found + useEffect(() => { + if (!isLoading && typeToEditNotFound) { + toast.error('Enumerated Type not found') + setSelectedTypeToEdit('') + } + }, [selectedTypeToEdit, typeToEdit, isLoading]) + + // Error handling if delete panel is open and type is not found + useEffect(() => { + if (!isLoading && typeToDeleteNotFound) { + toast.error('Enumerated Type not found') + setSelectedTypeToDelete('') + } + }, [selectedTypeToDelete, typeToDelete]) + const enumeratedTypes = (data ?? []).filter((type) => type.enums.length > 0) const filteredEnumeratedTypes = search.length > 0 @@ -193,14 +220,14 @@ export const EnumeratedTypes = () => { /> type.id.toString() === selectedTypeToEdit)} + visible={showTypeToEdit} + selectedEnumeratedType={typeToEdit} onClose={() => setSelectedTypeToEdit('')} /> type.id.toString() === selectedTypeToDelete)} + visible={showTypeToDelete} + selectedEnumeratedType={typeToDelete} onClose={() => setSelectedTypeToDelete('')} /> diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx index 1f0953c369d..f1998e99c42 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx @@ -6,7 +6,6 @@ import { useRouter } from 'next/router' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' -import { useDatabaseFunctionsQuery } from 'data/database-functions/database-functions-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' @@ -21,6 +20,7 @@ import { TableCell, TableRow, } from 'ui' +import type { DatabaseFunction } from 'data/database-functions/database-functions-query' interface FunctionListProps { schema: string @@ -31,6 +31,7 @@ interface FunctionListProps { duplicateFunction: (fn: any) => void editFunction: (fn: any) => void deleteFunction: (fn: any) => void + functions: DatabaseFunction[] } const FunctionList = ({ @@ -42,17 +43,13 @@ const FunctionList = ({ duplicateFunction = noop, editFunction = noop, deleteFunction = noop, + functions, }: FunctionListProps) => { const router = useRouter() const { data: selectedProject } = useSelectedProjectQuery() const aiSnap = useAiAssistantStateSnapshot() const { openSidebar } = useSidebarManagerSnapshot() - const { data: functions } = useDatabaseFunctionsQuery({ - projectRef: selectedProject?.ref, - connectionString: selectedProject?.connectionString, - }) - const filteredFunctions = (functions ?? []).filter((x) => { const matchesName = includes(x.name.toLowerCase(), filterString.toLowerCase()) const matchesReturnType = diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index d504ba1bc1a..e51c40298c8 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -1,9 +1,9 @@ -import type { PostgresFunction } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { noop } from 'lodash' +import { useEffect, useState } from 'react' import { Search } from 'lucide-react' import { useRouter } from 'next/router' -import { parseAsJson, useQueryState } from 'nuqs' +import { parseAsJson, parseAsBoolean, parseAsString, useQueryState } from 'nuqs' +import { toast } from 'sonner' import { useParams } from 'common' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' @@ -36,26 +36,77 @@ import { } from 'components/interfaces/Reports/v2/ReportsSelectFilter' import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning' import FunctionList from './FunctionList' +import type { DatabaseFunction } from 'data/database-functions/database-functions-query' -interface FunctionsListProps { - createFunction: () => void - duplicateFunction: (fn: PostgresFunction) => void - editFunction: (fn: PostgresFunction) => void - deleteFunction: (fn: PostgresFunction) => void -} +import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' +import { CreateFunction } from 'components/interfaces/Database/Functions/CreateFunction' +import { DeleteFunction } from 'components/interfaces/Database/Functions/DeleteFunction' +import { useEditorPanelStateSnapshot } from 'state/editor-panel-state' -const FunctionsList = ({ - createFunction = noop, - editFunction = noop, - deleteFunction = noop, - duplicateFunction = noop, -}: FunctionsListProps) => { +const FunctionsList = () => { const router = useRouter() const { search } = useParams() const { data: project } = useSelectedProjectQuery() const aiSnap = useAiAssistantStateSnapshot() const { openSidebar } = useSidebarManagerSnapshot() const { selectedSchema, setSelectedSchema } = useQuerySchemaState() + const isInlineEditorEnabled = useIsInlineEditorEnabled() + const { + setValue: setEditorPanelValue, + setTemplates: setEditorPanelTemplates, + setInitialPrompt: setEditorPanelInitialPrompt, + } = useEditorPanelStateSnapshot() + + const createFunction = () => { + setSelectedFunctionIdToDuplicate('') + if (isInlineEditorEnabled) { + setEditorPanelInitialPrompt('Create a new database function that...') + setEditorPanelValue(`create function function_name() +returns void +language plpgsql +as $$ +begin + -- Write your function logic here +end; +$$;`) + setEditorPanelTemplates([]) + openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) + } else { + setShowCreateFunctionForm(true) + } + } + + const duplicateFunction = (fn: DatabaseFunction) => { + const dupFn = { + ...fn, + name: `${fn.name}_duplicate`, + } + + if (isInlineEditorEnabled) { + setEditorPanelInitialPrompt('Create new database function that...') + setEditorPanelValue(dupFn.complete_statement) + setEditorPanelTemplates([]) + openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) + } else { + !selectedFunctionIdToDuplicate && setSelectedFunctionIdToDuplicate(dupFn.id.toString()) + functionToDuplicate = dupFn + } + } + + const editFunction = (fn: DatabaseFunction) => { + setSelectedFunctionIdToDuplicate('') + if (isInlineEditorEnabled) { + setEditorPanelValue(fn.complete_statement) + setEditorPanelTemplates([]) + openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) + } else { + setSelectedFunctionToEdit(fn.id.toString()) + } + } + + const deleteFunction = (fn: any) => { + setSelectedFunctionToDelete(fn.id.toString()) + } const filterString = search ?? '' @@ -114,6 +165,68 @@ const FunctionsList = ({ ...(hasInvoker ? [{ label: 'Invoker', value: 'invoker' }] : []), ] + const [showCreateFunctionForm, setShowCreateFunctionForm] = useQueryState( + 'new', + parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true }) + ) + const [selectedFunctionToEdit, setSelectedFunctionToEdit] = useQueryState( + 'edit', + parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) + ) + const [selectedFunctionIdToDuplicate, setSelectedFunctionIdToDuplicate] = useQueryState( + 'duplicate', + parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) + ) + const [selectedFunctionToDelete, setSelectedFunctionToDelete] = useQueryState( + 'delete', + parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) + ) + + const functionToEdit = functions?.find((fn) => fn.id.toString() === selectedFunctionToEdit) + const showFunctionToEdit = selectedFunctionToEdit !== '' && !!functionToEdit + const functionToEditNotFound = selectedFunctionToEdit !== '' && !functionToEdit + + let functionToDuplicate = functions?.find( + (fn) => fn.id.toString() === selectedFunctionIdToDuplicate + ) + const showFunctionToDuplicate = + !isLoading && selectedFunctionIdToDuplicate !== '' && !!functionToDuplicate + + const functionToDelete = functions?.find((fn) => fn.id.toString() === selectedFunctionToDelete) + const showFunctionToDelete = selectedFunctionToDelete !== '' && !!functionToDelete + const functionToDeleteNotFound = selectedFunctionToDelete !== '' && !functionToDelete + + // Error handling if edit panel is open and function is not found + useEffect(() => { + if (!isLoading && functionToEditNotFound) { + toast.error('Database Function not found') + setSelectedFunctionToEdit('') + } + }, [selectedFunctionToEdit, functionToEdit, isLoading]) + + // Error handling if duplicate panel is open and function is not found + useEffect(() => { + if (selectedFunctionIdToDuplicate !== '') { + const fnToDuplicate = functions?.find( + (fn) => fn.id.toString() === selectedFunctionIdToDuplicate + ) + if (fnToDuplicate) { + duplicateFunction(fnToDuplicate) + } else { + toast.error('Database Function not found') + setSelectedFunctionIdToDuplicate('') + } + } + }, [functionToDuplicate, selectedFunctionToDelete, isLoading]) + + // Error handling if delete panel is open and function is not found + useEffect(() => { + if (!isLoading && functionToDeleteNotFound) { + toast.error('Database Function not found') + setSelectedFunctionToDelete('') + } + }, [selectedFunctionToDelete, functionToDelete, isLoading]) + if (isLoading) return if (isError) return @@ -252,12 +365,37 @@ const FunctionsList = ({ duplicateFunction={duplicateFunction} editFunction={editFunction} deleteFunction={deleteFunction} + functions={functions ?? []} /> )} + + { + setShowCreateFunctionForm(false) + }} + /> + + { + setSelectedFunctionToEdit('') + setSelectedFunctionIdToDuplicate('') + functionToDuplicate = undefined + }} + isDuplicating={selectedFunctionIdToDuplicate !== '' && !!functionToDuplicate} + /> + + ) } diff --git a/apps/studio/pages/project/[ref]/database/functions.tsx b/apps/studio/pages/project/[ref]/database/functions.tsx index 1ccfbb56909..a240f6207e0 100644 --- a/apps/studio/pages/project/[ref]/database/functions.tsx +++ b/apps/studio/pages/project/[ref]/database/functions.tsx @@ -1,157 +1,37 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useState } from 'react' -import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' -import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import { CreateFunction } from 'components/interfaces/Database/Functions/CreateFunction' -import { DeleteFunction } from 'components/interfaces/Database/Functions/DeleteFunction' import FunctionsList from 'components/interfaces/Database/Functions/FunctionsList/FunctionsList' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' import DefaultLayout from 'components/layouts/DefaultLayout' -import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { FormHeader } from 'components/ui/Forms/FormHeader' import NoPermission from 'components/ui/NoPermission' -import { DatabaseFunction } from 'data/database-functions/database-functions-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { DOCS_URL } from 'lib/constants' -import { useEditorPanelStateSnapshot } from 'state/editor-panel-state' -import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state' import type { NextPageWithLayout } from 'types' const DatabaseFunctionsPage: NextPageWithLayout = () => { - const [selectedFunction, setSelectedFunction] = useState() - const [showCreateFunctionForm, setShowCreateFunctionForm] = useQueryState( - 'new', - parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true }) - ) - const [showEditFunctionForm, setShowEditFunctionForm] = useQueryState( - 'edit', - parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) - ) - const [showDeleteFunctionForm, setShowDeleteFunctionForm] = useQueryState( - 'delete', - parseAsString.withDefault('').withOptions({ history: 'push', clearOnDefault: true }) - ) - const [isDuplicating, setIsDuplicating] = useState(false) - const isInlineEditorEnabled = useIsInlineEditorEnabled() - const { openSidebar } = useSidebarManagerSnapshot() - const { - setValue: setEditorPanelValue, - setTemplates: setEditorPanelTemplates, - setInitialPrompt: setEditorPanelInitialPrompt, - } = useEditorPanelStateSnapshot() - const { can: canReadFunctions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'functions' ) - const createFunction = () => { - setIsDuplicating(false) - if (isInlineEditorEnabled) { - setEditorPanelInitialPrompt('Create a new database function that...') - setEditorPanelValue(`create function function_name() -returns void -language plpgsql -as $$ -begin - -- Write your function logic here -end; -$$;`) - setEditorPanelTemplates([]) - openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) - } else { - setSelectedFunction(undefined) - setShowCreateFunctionForm(true) - } - } - - const duplicateFunction = (fn: DatabaseFunction) => { - setIsDuplicating(true) - - const dupFn = { - ...fn, - name: `${fn.name}_duplicate`, - } - - if (isInlineEditorEnabled) { - setEditorPanelInitialPrompt('Create new database function that...') - setEditorPanelValue(dupFn.complete_statement) - setEditorPanelTemplates([]) - openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) - } else { - setSelectedFunction(dupFn) - setShowEditFunctionForm(dupFn.id.toString()) - } - } - - const editFunction = (fn: DatabaseFunction) => { - setIsDuplicating(false) - if (isInlineEditorEnabled) { - setEditorPanelValue(fn.complete_statement) - setEditorPanelTemplates([]) - openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) - } else { - setSelectedFunction(fn) - setShowEditFunctionForm(fn.id.toString()) - } - } - - const deleteFunction = (fn: any) => { - setSelectedFunction(fn) - setShowDeleteFunctionForm(fn.id.toString()) - } - if (isPermissionsLoaded && !canReadFunctions) { return } return ( - <> - - -
- - -
-
-
- - { - setShowCreateFunctionForm(false) - setIsDuplicating(false) - }} - isDuplicating={isDuplicating} - /> - - { - setShowEditFunctionForm('') - setIsDuplicating(false) - }} - isDuplicating={isDuplicating} - /> - - - + + +
+ + +
+
+
) }