Files
supabase/apps/studio/components/ui/EdgeFunctionBlock/EdgeFunctionBlock.tsx
Joshen Lim cab0585533 Fe 1799/consolidate to useselectedprojectquery and (#37684)
* Replace all usage of useProjectContext with useSelectedProjectQuery

* Replace all usage of useSelectedProject with useSelectedProjectQuery

* Replace all usage of useProjectByRef with useProjectByRefQuery

* Replace all usage of useSelectedOrganization with useSelectedOrganizationQuery

* Deprecate useSelectedProject, useSelectedOrganization, and useProjectByRef hooks

* Deprecate ProjecContext
2025-08-06 10:53:10 +07:00

221 lines
7.2 KiB
TypeScript

import { Code } from 'lucide-react'
import Link from 'next/link'
import { DragEvent, ReactNode, useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { ReportBlockContainer } from 'components/interfaces/Reports/ReportBlock/ReportBlockContainer'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query'
import { useEdgeFunctionDeployMutation } from 'data/edge-functions/edge-functions-deploy-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { Button, cn, CodeBlock, CodeBlockLang } from 'ui'
import { Admonition } from 'ui-patterns'
interface EdgeFunctionBlockProps {
/** Title of the EdgeFunctionBlock */
label: string
/** Function code to display */
code: string
/** Function name/slug */
functionName: string
/** Any other actions specific to the parent to be rendered in the header */
actions?: ReactNode
/** Toggle visiblity of code on render */
showCode?: boolean
/** Whether function block is draggable */
draggable?: boolean
/** Tooltip when hovering over the header of the block */
tooltip?: ReactNode
/** Optional callback on drag start */
onDragStart?: (e: DragEvent<Element>) => void
}
export const EdgeFunctionBlock = ({
label,
code,
functionName,
actions,
showCode: _showCode = false,
tooltip,
}: EdgeFunctionBlockProps) => {
const { ref } = useParams()
const [isDeployed, setIsDeployed] = useState(false)
const [showWarning, setShowWarning] = useState(false)
const { data: settings } = useProjectSettingsV2Query({ projectRef: ref })
const { data: existingFunction } = useEdgeFunctionQuery({ projectRef: ref, slug: functionName })
const { mutate: sendEvent } = useSendEventMutation()
const { data: org } = useSelectedOrganizationQuery()
const { mutateAsync: deployFunction, isLoading: isDeploying } = useEdgeFunctionDeployMutation({
onSuccess: () => {
setIsDeployed(true)
toast.success('Successfully deployed edge function')
},
})
const handleDeploy = async () => {
if (!code || isDeploying || !ref) return
if (existingFunction) {
return setShowWarning(true)
}
try {
await deployFunction({
projectRef: ref,
slug: functionName,
metadata: {
entrypoint_path: 'index.ts',
name: functionName,
verify_jwt: true,
},
files: [{ name: 'index.ts', content: code }],
})
sendEvent({
action: 'edge_function_deploy_button_clicked',
properties: { origin: 'functions_ai_assistant' },
groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
})
} catch (error) {
toast.error(
`Failed to deploy function: ${error instanceof Error ? error.message : 'Unknown error'}`
)
}
}
let functionUrl = 'Function URL not available'
const endpoint = settings?.app_config?.endpoint
if (endpoint) {
const restUrl = `https://${endpoint}`
const restUrlTld = restUrl ? new URL(restUrl).hostname.split('.').pop() : 'co'
functionUrl =
ref && functionName && restUrlTld
? `https://${ref}.supabase.${restUrlTld}/functions/v1/${functionName}`
: 'Function URL will be available after deployment'
}
return (
<ReportBlockContainer
tooltip={tooltip}
icon={<Code size={16} strokeWidth={1.5} className="text-foreground-muted" />}
label={label}
actions={
ref && functionName ? (
<>
<Button
type="outline"
size="tiny"
loading={isDeploying}
disabled={!ref}
onClick={handleDeploy}
>
{isDeploying ? 'Deploying...' : 'Deploy'}
</Button>
{actions}
</>
) : null
}
>
{showWarning && ref && functionName && (
<Admonition
type="warning"
className="mb-0 rounded-none border-0 border-b shrink-0 bg-background-100"
>
<p>An edge function with the name "{functionName}" already exists.</p>
<p className="text-foreground-light">
Deploying will replace the existing function. Are you sure you want to proceed?
</p>
<div className="flex justify-stretch mt-2 gap-2">
<Button
type="outline"
size="tiny"
className="w-full flex-1"
onClick={() => setShowWarning(false)}
>
Cancel
</Button>
<Button
type="danger"
size="tiny"
className="w-full flex-1"
onClick={async () => {
setShowWarning(false)
try {
await deployFunction({
projectRef: ref,
slug: functionName,
metadata: {
entrypoint_path: 'index.ts',
name: functionName,
verify_jwt: true,
},
files: [{ name: 'index.ts', content: code }],
})
} catch (error) {
toast.error(
`Failed to deploy function: ${error instanceof Error ? error.message : 'Unknown error'}`
)
}
}}
>
Replace function
</Button>
</div>
</Admonition>
)}
<div className="shrink-0 w-full max-h-96 overflow-y-auto">
<CodeBlock
hideLineNumbers
wrapLines={false}
value={code}
language={'typescript' as CodeBlockLang}
className={cn(
'max-w-none block !bg-transparent !py-3 !px-3.5 prose dark:prose-dark border-0 text-foreground !rounded-none w-full',
'[&>code]:m-0 [&>code>span]:text-foreground'
)}
/>
</div>
{(isDeploying || isDeployed) && (
<div className="p-4 w-full border-t bg-surface-75 text-xs">
{isDeploying ? (
<p className="text-foreground-light">Deploying function...</p>
) : (
<>
<p className="text-foreground-light mb-2">
The{' '}
<Link
className="text-foreground"
href={`/project/${ref}/functions/${functionName}/details`}
>
new function
</Link>{' '}
is now live at:
</p>
<CodeBlock
language="bash"
hideLineNumbers
value={functionUrl}
className="text-xs p-2"
/>
<p className="text-foreground-light mt-4 mb-2">
To download and work on this function locally, use the CLI command:
</p>
<CodeBlock
hideLineNumbers
language="bash"
value={`supabase functions download ${functionName}`}
className="text-xs p-2"
/>
</>
)}
</div>
)}
</ReportBlockContainer>
)
}