import { Book, Save, X } from 'lucide-react' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { useParams } from 'common' import { createSqlSnippetSkeletonV2, suffixWithLimit, } from 'components/interfaces/SQLEditor/SQLEditor.utils' import Results from 'components/interfaces/SQLEditor/UtilityPanel/Results' import { SqlRunButton } from 'components/interfaces/SQLEditor/UtilityPanel/RunButton' import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation' import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { BASE_PATH } from 'lib/constants' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' import { Button, cn, CodeBlock, Command_Shadcn_, CommandEmpty_Shadcn_, CommandGroup_Shadcn_, CommandInput_Shadcn_, CommandItem_Shadcn_, CommandList_Shadcn_, Form_Shadcn_, FormField_Shadcn_, HoverCard_Shadcn_, HoverCardContent_Shadcn_, HoverCardTrigger_Shadcn_, Input_Shadcn_ as Input, KeyboardShortcut, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SQL_ICON, } from 'ui' import { Admonition } from 'ui-patterns' import { containsUnknownFunction, isReadOnlySelect } from '../AIAssistantPanel/AIAssistant.utils' import AIEditor from '../AIEditor' import { ButtonTooltip } from '../ButtonTooltip' import { InlineLink } from '../InlineLink' import { SqlWarningAdmonition } from '../SqlWarningAdmonition' type Template = { name: string description: string content: string } interface EditorPanelProps { open: boolean onClose: () => void initialValue?: string label?: string saveLabel?: string saveValue?: string onSave?: (value: string, saveValue: string) => void onRunSuccess?: (value: any[]) => void onRunError?: (value: any) => void functionName?: string templates?: Template[] initialPrompt?: string onChange?: (value: string) => void } export const EditorPanel = ({ open, onClose, initialValue = '', label = '', saveLabel = 'Save', saveValue = '', onSave, onRunSuccess, onRunError, templates = [], initialPrompt = '', onChange, }: EditorPanelProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const { profile } = useProfile() const snapV2 = useSqlEditorV2StateSnapshot() const { mutateAsync: generateSqlTitle } = useSqlTitleGenerateMutation() const { data: org } = useSelectedOrganizationQuery() const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState() const [results, setResults] = useState(undefined) const [showWarning, setShowWarning] = useState<'hasWriteOperation' | 'hasUnknownFunctions'>() const [currentValue, setCurrentValue] = useState(initialValue) const [showResults, setShowResults] = useState(true) const [isTemplatesOpen, setIsTemplatesOpen] = useState(false) const saveForm = useForm({ defaultValues: { saveValue: saveValue || '', }, }) const errorHeader = error?.formattedError?.split('\n')?.filter((x: string) => x.length > 0)?.[0] const errorContent = 'formattedError' in (error || {}) ? error?.formattedError ?.split('\n') ?.filter((x: string) => x.length > 0) ?.slice(1) ?? [] : [error?.message ?? ''] const { mutate: executeSql, isLoading: isExecuting } = useExecuteSqlMutation({ onSuccess: async (res) => { setResults(res.result) if (onRunSuccess) { onRunSuccess(res.result) } }, onError: (error) => { setError(error) setResults([]) if (onRunError) { onRunError(error) } }, }) const onExecuteSql = (skipValidation = false) => { setError(undefined) setShowWarning(undefined) if (currentValue.length === 0) return if (!skipValidation) { const isReadOnlySelectSQL = isReadOnlySelect(currentValue) if (!isReadOnlySelectSQL) { const hasUnknownFunctions = containsUnknownFunction(currentValue) setShowWarning(hasUnknownFunctions ? 'hasUnknownFunctions' : 'hasWriteOperation') return } } executeSql({ sql: suffixWithLimit(currentValue, 100), projectRef: project?.ref, connectionString: project?.connectionString, handleError: (error) => { throw error }, contextualInvalidation: true, }) } const handleChange = (value: string) => { setCurrentValue(value) onChange?.(value) } const onSelectTemplate = (content: string) => { handleChange(content) setIsTemplatesOpen(false) } useEffect(() => { if (initialValue !== undefined && initialValue !== currentValue) { setCurrentValue(initialValue) setResults(undefined) setError(undefined) setShowWarning(undefined) } }, [initialValue]) useEffect(() => { saveForm.reset({ saveValue: saveValue || '', }) }, [saveValue, saveForm]) return ( !open && onClose()}>
SQL Editor {label && {label}}
{templates.length > 0 && ( No templates found. {templates.map((template) => ( onSelectTemplate(template.content)} className="cursor-pointer" >

{template.name}

{template.description}

))}
)} } onClick={async () => { if (!ref) return console.error('Project ref is required') if (!project) return console.error('Project is required') if (!profile) return console.error('Profile is required') try { setIsSaving(true) const { title: name } = await generateSqlTitle({ sql: currentValue, }) const snippet = createSqlSnippetSkeletonV2({ id: uuidv4(), name, sql: currentValue, owner_id: profile.id, project_id: project.id, }) snapV2.addSnippet({ projectRef: ref, snippet }) snapV2.addNeedsSaving(snippet.id) toast.success(
Saved snippet! View it{' '} here
) } catch (error: any) { toast.error(`Failed to create new query: ${error.message}`) } finally { setIsSaving(false) } }} /> } tooltip={{ content: { side: 'bottom', text: (
Close Editor
), }, }} />
onClose()} />
{error !== undefined && (
{errorContent.length > 0 ? ( errorContent.map((errorText: string, i: number) => (
                          {errorText}
                        
)) ) : (

{error.error}

)}
} />
)} {showWarning && ( setShowWarning(undefined)} onConfirm={() => { setShowWarning(undefined) onExecuteSql(true) }} /> )} {results !== undefined && results.length > 0 && (
{showResults && (
)}

{results.length} rows{results.length >= 100 && ` (Limited to only 100 rows)`}

)} {results !== undefined && results.length === 0 && !error && (

Success. No rows returned.

)}
{onSave && (
{ onSave(currentValue, values.saveValue) })} className="flex items-center gap-2" > {saveValue && ( ( )} /> )}
)}
) } export default EditorPanel