From 9c838f7629cbaf9ebae878cba7eccd549ec783e1 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Thu, 21 May 2026 12:03:32 +1000 Subject: [PATCH] update edge function design --- .../EdgeFunctionDetails.tsx | 494 +++++++++--------- .../EdgeFunctionInvocationsSection.tsx | 164 +++--- .../EdgeFunctionOverview.tsx | 113 ++-- .../EdgeFunctionOverview.utils.test.ts | 14 +- .../EdgeFunctionOverview.utils.ts | 26 + .../EdgeFunctionOverviewHeader.tsx | 115 ++++ .../EdgeFunctionPerformanceSection.tsx | 69 ++- .../EdgeFunctionRecentErrors.tsx | 329 ++++++------ .../EdgeFunctionUsageSection.tsx | 116 ++-- .../[ref]/functions/[functionSlug]/code.tsx | 11 +- .../functions/[functionSlug]/details.tsx | 9 +- .../[ref]/functions/[functionSlug]/index.tsx | 11 +- .../functions/[functionSlug]/invocations.tsx | 9 +- .../[ref]/functions/[functionSlug]/layout.tsx | 10 + .../[ref]/functions/[functionSlug]/logs.tsx | 9 +- 15 files changed, 807 insertions(+), 692 deletions(-) create mode 100644 apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverviewHeader.tsx create mode 100644 apps/studio/pages/project/[ref]/functions/[functionSlug]/layout.tsx diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index a0feafae502..5e5f58f4e0e 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -30,6 +30,13 @@ import { Input } from 'ui-patterns/DataInputs/Input' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageContainer } from 'ui-patterns/PageContainer' +import { + PageHeader, + PageHeaderDescription, + PageHeaderMeta, + PageHeaderSummary, + PageHeaderTitle, +} from 'ui-patterns/PageHeader' import { PageSection, PageSectionContent, @@ -142,247 +149,262 @@ export const EdgeFunctionDetails = () => { }, [selectedFunction]) return ( - - - - - Function configuration - - - -
- - - - ( - - - - - - )} - /> - - {IS_PLATFORM && ( - <> - - ( - -

- Requires a JWT signed{' '} - - only by the legacy secret - {' '} - in the{' '} - - Authorization - {' '} - header. The anon key - satisfies this. -

-

- Recommended: OFF with JWT and custom auth logic in your function - code. -

- - } - > - - - -
- )} - /> -
+ <> + + + + Settings + + Configure function behavior, security, and deployment options. + + + + - - {form.formState.isDirty && ( - + + + + + Function configuration + + + + + + + + ( + + + + + )} - - - - )} -
-
- -
-
+ /> + + {IS_PLATFORM && ( + <> + + ( + +

+ Requires a JWT signed{' '} + + only by the legacy secret + {' '} + in the{' '} + + Authorization + {' '} + header. The anon key + satisfies this. +

+

+ Recommended: OFF with JWT and custom auth logic in your function + code. +

+ + } + > + + + +
+ )} + /> +
- - - - Invoke function - - - - - - - - {invocationTabs.map((tab) => ( - - {tab.label} - - ))} - {selectedTab === 'curl' && ( - - )} - - {invocationTabs.map((tab) => { - const code = tab.code({ - showKey, - functionUrl, - functionName: selectedFunction?.name ?? '', - apiKey, - }) - - return ( - - code]:break-all' : '[&>code]:wrap-break-word' + + {form.formState.isDirty && ( + )} - language={tab.language} - wrapLines={false} - hideLineNumbers={tab.hideLineNumbers} - handleCopy={() => { - copyToClipboard( - tab.code({ - showKey: true, - functionUrl, - functionName: selectedFunction?.name ?? '', - apiKey, - }) - ) - }} - /> - - ) - })} - - - - - + + + + )} + + + + + - {IS_PLATFORM && ( - <> - - - - Develop locally - - - -
-
- ( - <> - supabase functions download{' '} - {selectedFunction?.slug} - - ), - comment: '1. Download the function', - }, - ]} - /> - - + + + + Invoke function + + + + + + + + {invocationTabs.map((tab) => ( + + {tab.label} + + ))} + {selectedTab === 'curl' && ( + + )} + + {invocationTabs.map((tab) => { + const code = tab.code({ + showKey, + functionUrl, + functionName: selectedFunction?.name ?? '', + apiKey, + }) + + return ( + + code]:break-all' : '[&>code]:wrap-break-word' + )} + language={tab.language} + wrapLines={false} + hideLineNumbers={tab.hideLineNumbers} + handleCopy={() => { + copyToClipboard( + tab.code({ + showKey: true, + functionUrl, + functionName: selectedFunction?.name ?? '', + apiKey, + }) + ) + }} + /> + + ) + })} + + + + + + + {IS_PLATFORM && ( + <> + + + + Develop locally + + + +
+
+ ( + <> + supabase functions download{' '} + {selectedFunction?.slug} + + ), + comment: '1. Download the function', + }, + ]} + /> + + +
-
- - - - - - Delete function - - - - - - Once your function is deleted, it can no longer be restored - - Make sure you have made a backup if you want to restore your edge function - - - - - - - - setShowDeleteModal(false)} - onConfirm={onConfirmDelete} - alert={{ - base: { variant: 'destructive' }, - title: 'This action cannot be undone', - description: - 'Ensure that you have made a backup if you want to restore your edge function', - }} - /> - - )} - + + + + + + Delete function + + + + + + + Once your function is deleted, it can no longer be restored + + + Make sure you have made a backup if you want to restore your edge function + + + + + + + + setShowDeleteModal(false)} + onConfirm={onConfirmDelete} + alert={{ + base: { variant: 'destructive' }, + title: 'This action cannot be undone', + description: + 'Ensure that you have made a backup if you want to restore your edge function', + }} + /> + + )} + + ) } diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionInvocationsSection.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionInvocationsSection.tsx index 86699312c17..c9a0a7cbc1c 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionInvocationsSection.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionInvocationsSection.tsx @@ -1,16 +1,23 @@ import type { ComponentProps } from 'react' import { useMemo } from 'react' import { Button } from 'ui' -import { Chart, ChartActions, ChartLoadingState, ChartMetric } from 'ui-patterns/Chart' -import { PageContainer } from 'ui-patterns/PageContainer' +import { + Chart, + ChartActions, + ChartCard, + ChartContent, + ChartHeader, + ChartLoadingState, + ChartMetric, +} from 'ui-patterns/Chart' import { PageSection, PageSectionAside, PageSectionContent, PageSectionMeta, PageSectionSummary, + PageSectionTitle, } from 'ui-patterns/PageSection' -import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { EdgeFunctionChartEmptyState } from './EdgeFunctionChartEmptyState' import { EdgeFunctionInvocationsChart } from './EdgeFunctionInvocationsChart' @@ -21,8 +28,6 @@ import { getSegmentedButtonClassName, } from './EdgeFunctionOverview.utils' import type { InvocationChartDatum, InvocationUpdateAnnotation } from './EdgeFunctionOverview.utils' -import { toAlertError } from './EdgeFunctionRecentErrors.utils' -import AlertError from '@/components/ui/AlertError' import type { ChartIntervals } from '@/types' interface EdgeFunctionInvocationsSectionProps { @@ -33,9 +38,6 @@ interface EdgeFunctionInvocationsSectionProps { totalInvocationCount: number totalErrorCount: number totalWarningCount: number - isLoadingFunction: boolean - isErrorFunction: boolean - functionError?: unknown isLoadingChart: boolean isErrorChart: boolean chartErrorMessage?: string @@ -52,9 +54,6 @@ export const EdgeFunctionInvocationsSection = ({ totalInvocationCount, totalErrorCount, totalWarningCount, - isLoadingFunction, - isErrorFunction, - functionError, isLoadingChart, isErrorChart, chartErrorMessage, @@ -67,89 +66,72 @@ export const EdgeFunctionInvocationsSection = ({ () => getChartEmptyStateCopy('invocations', isErrorChart, chartErrorMessage), [chartErrorMessage, isErrorChart] ) + const invocationTitle = isLoadingChart + ? 'Invocations' + : `${totalInvocationCount.toLocaleString('en-US')} total invocations` return ( - - - -
- - -
- - - -
-
- -
- {EDGE_FUNCTION_CHART_INTERVALS.map((item, index) => { - return ( - - ) - })} -
- -
-
- -
- {isLoadingFunction && } - {isErrorFunction && ( - - )} -
- -
- - {isLoadingChart ? ( - - ) : isErrorChart || chartData.length === 0 ? ( - - ) : ( - - )} - -
+ + + + {invocationTitle} + + +
+ {EDGE_FUNCTION_CHART_INTERVALS.map((item, index) => ( + + ))}
- + {actions && actions.length > 0 ? : null} +
+
+ + + + +
+ + +
+
+ + } + loadingState={} + > +
+ +
+
+
+
) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx index 14589b9d9b7..0b8aa0b75a5 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx @@ -3,6 +3,7 @@ import { IS_PLATFORM, useParams } from 'common' import { ExternalLink } from 'lucide-react' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' +import { PageContainer } from 'ui-patterns/PageContainer' import { EdgeFunctionInvocationsSection } from './EdgeFunctionInvocationsSection' import { @@ -17,6 +18,7 @@ import { toEdgeFunctionChartData, } from './EdgeFunctionOverview.utils' import type { EdgeFunctionChartRawDatum } from './EdgeFunctionOverview.utils' +import { EdgeFunctionOverviewHeader } from './EdgeFunctionOverviewHeader' import { EdgeFunctionPerformanceSection } from './EdgeFunctionPerformanceSection' import { EdgeFunctionRecentErrors } from './EdgeFunctionRecentErrors' import { EdgeFunctionUsageSection } from './EdgeFunctionUsageSection' @@ -40,12 +42,7 @@ export const EdgeFunctionOverview = () => { const selectedInterval = EDGE_FUNCTION_CHART_INTERVALS.find((item) => item.key === interval) || EDGE_FUNCTION_CHART_INTERVALS[1] - const { - data: selectedFunction, - error: functionError, - isPending: isLoadingFunction, - isError: isErrorFunction, - } = useEdgeFunctionQuery({ + const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef, slug: functionSlug, }) @@ -189,61 +186,61 @@ export const EdgeFunctionOverview = () => { return ( <> - { - router.push( - `/project/${projectRef}/functions/${functionSlug}/${ - isUnifiedLogsEnabled ? 'logs' : 'invocations' - }${isUnifiedLogsEnabled ? '' : `?its=${startDate.toISOString()}`}` - ) - }} - updateAnnotation={invocationUpdateAnnotation} - /> + + + { + router.push( + `/project/${projectRef}/functions/${functionSlug}/${ + isUnifiedLogsEnabled ? 'logs' : 'invocations' + }${isUnifiedLogsEnabled ? '' : `?its=${startDate.toISOString()}`}` + ) + }} + updateAnnotation={invocationUpdateAnnotation} + /> - + - + - + + ) } diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.test.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.test.ts index 855ae983fbc..315093a8fd3 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.test.ts +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.test.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { EDGE_FUNCTION_CHART_INTERVALS, @@ -7,6 +7,7 @@ import { formatMetric, formatRate, formatReferenceDelta, + formatShortFromNow, getBucketedTimeRange, getChartEmptyStateCopy, getChartTimeRangeLabels, @@ -196,6 +197,17 @@ describe('EdgeFunctionOverview.utils', () => { expect(rollingEnd.toISOString()).toBe('2026-03-20T10:37:00.000Z') }) + it('formats short relative timestamps', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date('2026-03-20T10:00:00.000Z')) + + expect(formatShortFromNow('2026-03-20T09:28:00.000Z')).toBe('32m ago') + expect(formatShortFromNow('2026-03-20T09:59:30.000Z')).toBe('30s ago') + expect(formatShortFromNow('2026-03-19T10:00:00.000Z')).toBe('1d ago') + + vi.useRealTimers() + }) + it('formats metric, rate, and reference deltas consistently', () => { expect(formatMetric(12.34, 'MB')).toBe('12.3MB') expect(formatMetric(1234, 'ms')).toBe('1,234ms') diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.ts index c773c098118..37df6fae4b4 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.ts +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.utils.ts @@ -278,6 +278,32 @@ export const formatMetric = (value?: number, unit?: string) => { return unit ? `${formatted}${unit}` : formatted } +export const formatShortFromNow = (input: string | number | Date): string => { + const then = dayjs(input) + if (!then.isValid()) return '' + + const seconds = Math.max(0, dayjs().diff(then, 'second')) + if (seconds < 60) return `${seconds}s ago` + + const minutes = Math.floor(seconds / 60) + if (minutes < 60) return `${minutes}m ago` + + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours}h ago` + + const days = Math.floor(hours / 24) + if (days < 7) return `${days}d ago` + + const weeks = Math.floor(days / 7) + if (weeks < 5) return `${weeks}w ago` + + const months = Math.floor(days / 30) + if (months < 12) return `${months}mo ago` + + const years = Math.floor(days / 365) + return `${years}y ago` +} + export const formatRate = (count: number, total: number) => new Intl.NumberFormat('en-US', { style: 'percent', diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverviewHeader.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverviewHeader.tsx new file mode 100644 index 00000000000..67d85313a6e --- /dev/null +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverviewHeader.tsx @@ -0,0 +1,115 @@ +import { useParams } from 'common' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import { Clock } from 'lucide-react' +import { useState } from 'react' +import { HoverCard, HoverCardContent, HoverCardTrigger } from 'ui' +import { TimestampInfo } from 'ui-patterns' +import { + PageHeader, + PageHeaderDescription, + PageHeaderMeta, + PageHeaderSummary, + PageHeaderTitle, +} from 'ui-patterns/PageHeader' + +import { formatShortFromNow } from './EdgeFunctionOverview.utils' +import CopyButton from '@/components/ui/CopyButton' +import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' +import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' +import { SHORTCUT_IDS } from '@/state/shortcuts/registry' + +dayjs.extend(relativeTime) + +export const EdgeFunctionOverviewHeader = () => { + const { ref: projectRef, functionSlug } = useParams() + const [isTimestampHoverCardOpen, setIsTimestampHoverCardOpen] = useState(false) + + const { data: selectedFunction } = useEdgeFunctionQuery({ + projectRef, + slug: functionSlug, + }) + const { data: endpoint } = useProjectApiUrl({ projectRef }) + + const name = selectedFunction?.name || '' + const functionUrl = + endpoint && selectedFunction?.slug ? `${endpoint}/functions/v1/${selectedFunction.slug}` : '' + const createdRelative = selectedFunction?.created_at + ? dayjs(selectedFunction.created_at).fromNow() + : undefined + const updatedRelative = selectedFunction?.updated_at + ? dayjs(selectedFunction.updated_at).fromNow() + : undefined + const updatedShortRelative = selectedFunction?.updated_at + ? formatShortFromNow(selectedFunction.updated_at) + : undefined + + return ( + + + + {name || functionSlug} + + {functionUrl ? ( +
+ {functionUrl} + + + +
+ ) : null} + + + + + + + {createdRelative && ( +
+

Created

+ {!!selectedFunction && ( + + )} +
+ )} + {updatedRelative && ( +
+

Last deployed

+ {!!selectedFunction && ( + + )} +
+ )} + {selectedFunction?.version !== undefined && ( +
+

Deployments

+

{selectedFunction.version}

+
+ )} +
+
+
+
+
+
+ ) +} diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionPerformanceSection.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionPerformanceSection.tsx index bd837b5ce60..123e847180a 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionPerformanceSection.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionPerformanceSection.tsx @@ -1,6 +1,5 @@ import { useMemo } from 'react' import { ChartMetric } from 'ui-patterns/Chart' -import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent, @@ -59,45 +58,37 @@ export const EdgeFunctionPerformanceSection = ({ return ( + + + Performance + + - -
- - - Performance - - - -
- `${Math.round(value)}ms`, - }} - /> -
-
-
+ `${Math.round(value)}ms`, + }} + />
) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx index 7253d00f01c..3515b30f23f 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionRecentErrors.tsx @@ -13,7 +13,6 @@ import { TableHeader, TableRow, } from 'ui' -import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionAside, @@ -196,175 +195,169 @@ export const EdgeFunctionRecentErrors = ({ return ( + + + Errors since last deploy + + + + + + - -
- - - Errors since last deploy - - - - - - - {recentErrorInvocationsError || functionRuntimeLogsError ? ( - - ) : isLoadingRecentErrorInvocations || - isLoadingFunctionRuntimeLogs || - isLoadingSinceLastDeployInvocationCount ? ( - - ) : recentErrorGroups.length === 0 ? ( -
-
- {emptyStateIcon} -
{emptyStateMessage}
-
-
- ) : ( - - - - - Error - Count - Last Seen - Method - Status - Duration - Troubleshoot - - - - {recentErrorGroups.map((group) => { - const displayMessage = getDisplayErrorMessage(group) - const docsUrl = buildTroubleshootingDocsUrl({ - statusCode: group.lastStatusCode, - }) - - return ( - - - - - {displayMessage} - - - {group.count} - - {formatLogTimestamp(group.lastSeen, 'relative')} - - - {group.lastMethod ?? '-'} - - - {group.lastStatusCode ? ( - - {group.lastStatusCode} - - ) : ( - - Error - - )} - - - {group.executionTime ?? '-'} - - -
- buildGroupAssistantPrompt(group, functionSlug)} - onOpenAssistant={() => handleOpenAssistant(group)} - additionalDropdownItems={[ - { - label: 'View troubleshooting guide', - icon: , - onClick: () => - window.open(docsUrl, '_blank', 'noopener,noreferrer'), - }, - ]} - /> -
-
-
- - -
- {group.logs.length === 0 ? ( -
- No related runtime logs found for this error group. -
- ) : ( - group.logs.map((log, index) => { - const isError = log.level === 'error' - return ( -
- - {formatLogTimestamp(log.lastSeen, 'time')} - - - {log.level} - - {log.count > 1 && ( - - ×{log.count} - - )} - - {formatSingleLineMessage(log.message)} - -
- ) - }) - )} -
-
-
-
- ) - })} -
-
-
- )} + {recentErrorInvocationsError || functionRuntimeLogsError ? ( + + ) : isLoadingRecentErrorInvocations || + isLoadingFunctionRuntimeLogs || + isLoadingSinceLastDeployInvocationCount ? ( + + ) : recentErrorGroups.length === 0 ? ( +
+
+ {emptyStateIcon} +
{emptyStateMessage}
+
- + ) : ( + + + + + Error + Count + Last Seen + Method + Status + Duration + Troubleshoot + + + + {recentErrorGroups.map((group) => { + const displayMessage = getDisplayErrorMessage(group) + const docsUrl = buildTroubleshootingDocsUrl({ + statusCode: group.lastStatusCode, + }) + + return ( + + + + + {displayMessage} + + + {group.count} + + {formatLogTimestamp(group.lastSeen, 'relative')} + + + {group.lastMethod ?? '-'} + + + {group.lastStatusCode ? ( + + {group.lastStatusCode} + + ) : ( + + Error + + )} + + + {group.executionTime ?? '-'} + + +
+ buildGroupAssistantPrompt(group, functionSlug)} + onOpenAssistant={() => handleOpenAssistant(group)} + additionalDropdownItems={[ + { + label: 'View troubleshooting guide', + icon: , + onClick: () => + window.open(docsUrl, '_blank', 'noopener,noreferrer'), + }, + ]} + /> +
+
+
+ + +
+ {group.logs.length === 0 ? ( +
+ No related runtime logs found for this error group. +
+ ) : ( + group.logs.map((log, index) => { + const isError = log.level === 'error' + return ( +
+ + {formatLogTimestamp(log.lastSeen, 'time')} + + + {log.level} + + {log.count > 1 && ( + + ×{log.count} + + )} + + {formatSingleLineMessage(log.message)} + +
+ ) + }) + )} +
+
+
+
+ ) + })} +
+
+
+ )} ) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionUsageSection.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionUsageSection.tsx index 1de5fbffd0c..0799ae3f05e 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionUsageSection.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionUsageSection.tsx @@ -1,6 +1,5 @@ import { useMemo } from 'react' import { ChartMetric } from 'ui-patterns/Chart' -import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent, @@ -90,68 +89,63 @@ export const EdgeFunctionUsageSection = ({ return ( + + + Usage + + - -
- - - Usage - - +
+ `${Math.round(value)}ms`, + }} + /> -
- `${Math.round(value)}ms`, - }} - /> - - `${Number(value).toFixed(1)}MB`, - }} - /> -
-
- + `${Number(value).toFixed(1)}MB`, + }} + /> +
) diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx index d730efb3b9f..6d18894d335 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx @@ -6,10 +6,9 @@ import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { LogoLoader } from 'ui' +import { getEdgeFunctionDetailsPageLayout } from './layout' import { DeployEdgeFunctionWarningModal } from '@/components/interfaces/EdgeFunctions/DeployEdgeFunctionWarningModal' import { formatFunctionBodyToFiles } from '@/components/interfaces/EdgeFunctions/EdgeFunctions.utils' -import { DefaultLayout } from '@/components/layouts/DefaultLayout' -import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' import { PreventNavigationOnUnsavedChanges } from '@/components/ui-patterns/Dialogs/PreventNavigationOnUnsavedChanges' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { FileExplorerAndEditor } from '@/components/ui/FileExplorerAndEditor' @@ -241,12 +240,6 @@ const CodePage = () => { ) } -CodePage.getLayout = (page: React.ReactNode) => { - return ( - - {page} - - ) -} +CodePage.getLayout = getEdgeFunctionDetailsPageLayout export default CodePage diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx index 5dd7dbee8d9..32edab9e19b 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/details.tsx @@ -1,14 +1,9 @@ +import { getEdgeFunctionDetailsPageLayout } from './layout' import { EdgeFunctionDetails } from '@/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails' -import DefaultLayout from '@/components/layouts/DefaultLayout' -import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' import type { NextPageWithLayout } from '@/types' const PageLayout: NextPageWithLayout = () => -PageLayout.getLayout = (page) => ( - - {page} - -) +PageLayout.getLayout = getEdgeFunctionDetailsPageLayout export default PageLayout diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx index cde7da576c9..159109d4e99 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx @@ -10,11 +10,10 @@ import { Alert, AlertDescription, AlertTitle, Button, LogoLoader, WarningIcon } import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' +import { getEdgeFunctionDetailsPageLayout } from './layout' import { EdgeFunctionOverview } from '@/components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview' import { EdgeFunctionRecentInvocations } from '@/components/interfaces/Functions/EdgeFunctionRecentInvocations' import ReportWidget from '@/components/interfaces/Reports/ReportWidget' -import DefaultLayout from '@/components/layouts/DefaultLayout' -import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' import AreaChart from '@/components/ui/Charts/AreaChart' import StackedBarChart from '@/components/ui/Charts/StackedBarChart' import NoPermission from '@/components/ui/NoPermission' @@ -172,7 +171,7 @@ const LegacyEdgeFunctionOverview = () => {
-
+
{ return } -PageLayout.getLayout = (page) => ( - - {page} - -) +PageLayout.getLayout = getEdgeFunctionDetailsPageLayout export default PageLayout diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx index db6a67fb74f..b560c0b318a 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/invocations.tsx @@ -1,8 +1,7 @@ import { useParams } from 'common' +import { getEdgeFunctionDetailsPageLayout } from './layout' import { LogsPreviewer } from '@/components/interfaces/Settings/Logs/LogsPreviewer' -import DefaultLayout from '@/components/layouts/DefaultLayout' -import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' import type { NextPageWithLayout } from '@/types' @@ -27,10 +26,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => ( - - {page} - -) +LogPage.getLayout = getEdgeFunctionDetailsPageLayout export default LogPage diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/layout.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/layout.tsx new file mode 100644 index 00000000000..1588d2d7d8d --- /dev/null +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/layout.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from 'react' + +import DefaultLayout from '@/components/layouts/DefaultLayout' +import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' + +export const getEdgeFunctionDetailsPageLayout = (page: ReactNode) => ( + + {page} + +) diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx index 3052a78b1c2..7fd468dfba4 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/logs.tsx @@ -1,8 +1,7 @@ import { useParams } from 'common' +import { getEdgeFunctionDetailsPageLayout } from './layout' import { LogsPreviewer } from '@/components/interfaces/Settings/Logs/LogsPreviewer' -import DefaultLayout from '@/components/layouts/DefaultLayout' -import EdgeFunctionDetailsLayout from '@/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' import type { NextPageWithLayout } from '@/types' @@ -28,10 +27,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => ( - - {page} - -) +LogPage.getLayout = getEdgeFunctionDetailsPageLayout export default LogPage