From b9b748391d56d09fc94b924be89914ff4e4871d6 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 31 Mar 2025 14:06:25 +0800 Subject: [PATCH] Add download edge functions CTA to edge function details header (#34501) * Add download edge functions CTA to edge function details header * Add CLI command * Update copy * Small tweak based on feedback --- .../EdgeFunctionDetailsLayout.tsx | 73 ++++++++++++++++++- .../edge-function-body-query.ts | 6 +- .../[ref]/functions/[functionSlug]/code.tsx | 7 +- packages/ui-patterns/DataInputs/Input.tsx | 5 +- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx index 7e9d7998d2..ac1b7e243a 100644 --- a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx +++ b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx @@ -1,9 +1,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { Send } from 'lucide-react' +import { Download, FileArchive, Send } from 'lucide-react' import { useRouter } from 'next/router' import { useEffect, useState, type PropsWithChildren } from 'react' import { toast } from 'sonner' +import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js' import { useParams } from 'common' import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { EdgeFunctionTesterSheet } from 'components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet' @@ -11,13 +12,21 @@ import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import APIDocsButton from 'components/ui/APIDocsButton' import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' +import { useEdgeFunctionBodyQuery } from 'data/edge-functions/edge-function-body-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { withAuth } from 'hooks/misc/withAuth' import { useFlag } from 'hooks/ui/useFlag' -import { Button } from 'ui' +import { + Button, + Popover_Shadcn_, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Separator, +} from 'ui' +import { Input } from 'ui-patterns/DataInputs/Input' import ProjectLayout from '../ProjectLayout/ProjectLayout' import EdgeFunctionsLayout from './EdgeFunctionsLayout' @@ -49,7 +58,13 @@ const EdgeFunctionDetailsLayout = ({ isError, } = useEdgeFunctionQuery({ projectRef: ref, slug: functionSlug }) + const { data: functionFiles = [], error: filesError } = useEdgeFunctionBodyQuery({ + projectRef: ref, + slug: functionSlug, + }) + const name = selectedFunction?.name || '' + const cliCommand = `supabase functions download ${functionSlug}` const breadcrumbItems = [ { @@ -87,6 +102,29 @@ const EdgeFunctionDetailsLayout = ({ ] : [] + const downloadFunction = async () => { + if (filesError) return toast.error('Failed to retrieve edge function files') + + const zipFileWriter = new BlobWriter('application/zip') + const zipWriter = new ZipWriter(zipFileWriter, { bufferedWrite: true }) + functionFiles.forEach((file) => { + const nameSections = file.name.split('/') + const slugIndex = nameSections.indexOf(functionSlug ?? '') + const fileName = nameSections.slice(slugIndex + 1).join('/') + + const fileBlob = new Blob([file.content]) + zipWriter.add(fileName, new BlobReader(fileBlob)) + }) + + const blobURL = URL.createObjectURL(await zipWriter.close()) + const link = document.createElement('a') + link.href = blobURL + link.setAttribute('download', `${functionSlug}.zip`) + document.body.appendChild(link) + link.click() + link.parentNode?.removeChild(link) + } + useEffect(() => { let cancel = false @@ -126,6 +164,37 @@ const EdgeFunctionDetailsLayout = ({ /> )} + + + + + +
+

Download via CLI

+ +
+ +
+ +
+
+
{edgeFunctionCreate && !!functionSlug && (