edge-functions.tsx268 lines · main
1import { useQueryClient } from '@tanstack/react-query'
2import { useParams } from 'common'
3import dayjs from 'dayjs'
4import { ArrowRight, RefreshCw } from 'lucide-react'
5import { parseAsJson, useQueryState } from 'nuqs'
6import { useMemo, useState } from 'react'
7
8import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
9import ReportPadding from '@/components/interfaces/Reports/ReportPadding'
10import {
11 EDGE_FUNCTION_REGIONS,
12 REPORT_DATERANGE_HELPER_LABELS,
13} from '@/components/interfaces/Reports/Reports.constants'
14import ReportStickyNav from '@/components/interfaces/Reports/ReportStickyNav'
15import { ReportChartV2 } from '@/components/interfaces/Reports/v2/ReportChartV2'
16import {
17 numericFilterSchema,
18 ReportsNumericFilter,
19} from '@/components/interfaces/Reports/v2/ReportsNumericFilter'
20import {
21 ReportsSelectFilter,
22 selectFilterSchema,
23} from '@/components/interfaces/Reports/v2/ReportsSelectFilter'
24import { LogsDatePicker } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
25import UpgradePrompt from '@/components/interfaces/Settings/Logs/UpgradePrompt'
26import DefaultLayout from '@/components/layouts/DefaultLayout'
27import ObservabilityLayout from '@/components/layouts/ObservabilityLayout/ObservabilityLayout'
28import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
29import { ReportSettings } from '@/components/ui/Charts/ReportSettings'
30import { useChartHoverState } from '@/components/ui/Charts/useChartHoverState'
31import { ObservabilityLink } from '@/components/ui/ObservabilityLink'
32import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
33import { edgeFunctionReports } from '@/data/reports/v2/edge-functions.config'
34import { useRefreshHandler, useReportDateRange } from '@/hooks/misc/useReportDateRange'
35import { BASE_PATH } from '@/lib/constants'
36import type { NextPageWithLayout } from '@/types'
37
38const EdgeFunctionsReportV2: NextPageWithLayout = () => {
39 return (
40 <ReportPadding>
41 <EdgeFunctionsUsage />
42 </ReportPadding>
43 )
44}
45
46EdgeFunctionsReportV2.getLayout = (page) => (
47 <DefaultLayout>
48 <ObservabilityLayout title="Edge Functions">{page}</ObservabilityLayout>
49 </DefaultLayout>
50)
51
52export default EdgeFunctionsReportV2
53
54const EdgeFunctionsUsage = () => {
55 const { ref } = useParams()
56 const { data: functions } = useEdgeFunctionsQuery({
57 projectRef: ref,
58 })
59
60 const chartSyncId = `edge-functions-${ref}`
61 useChartHoverState(chartSyncId)
62
63 // Filters
64 const [statusCodeFilter, setStatusCodeFilter] = useQueryState(
65 'status_code',
66 parseAsJson(numericFilterSchema.parse)
67 )
68
69 const [regionFilter, setRegionFilter] = useQueryState(
70 'region',
71 parseAsJson(selectFilterSchema.parse)
72 )
73 const [executionTimeFilter, setExecutionTimeFilter] = useQueryState(
74 'execution_time',
75 parseAsJson(numericFilterSchema.parse)
76 )
77
78 const [functionFilter, setFunctionFilter] = useQueryState(
79 'functions',
80 parseAsJson(selectFilterSchema.parse)
81 )
82
83 const {
84 selectedDateRange,
85 updateDateRange,
86 datePickerValue,
87 datePickerHelpers,
88 showUpgradePrompt,
89 setShowUpgradePrompt,
90 handleDatePickerChange,
91 } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
92
93 const queryClient = useQueryClient()
94 const [isRefreshing, setIsRefreshing] = useState(false)
95
96 const reportConfig = useMemo(() => {
97 return edgeFunctionReports({
98 projectRef: ref!,
99 functions: functions ?? [],
100 startDate: selectedDateRange?.period_start?.date ?? '',
101 endDate: selectedDateRange?.period_end?.date ?? '',
102 interval: selectedDateRange?.interval ?? 'minute',
103 filters: {
104 functions: functionFilter ?? [],
105 status_code: statusCodeFilter,
106 region: regionFilter ?? [],
107 execution_time: executionTimeFilter,
108 },
109 })
110 }, [
111 ref,
112 functions,
113 selectedDateRange,
114 functionFilter,
115 statusCodeFilter,
116 regionFilter,
117 executionTimeFilter,
118 ])
119
120 const onRefreshReport = useRefreshHandler(
121 datePickerValue,
122 datePickerHelpers,
123 handleDatePickerChange,
124 async () => {
125 if (!selectedDateRange) return
126
127 setIsRefreshing(true)
128 queryClient.invalidateQueries({ queryKey: ['projects', ref, 'report-v2'] })
129 setTimeout(() => setIsRefreshing(false), 1000)
130 }
131 )
132
133 return (
134 <>
135 <ReportHeader title="Edge Functions" showDatabaseSelector={false} />
136 <ReportStickyNav
137 content={
138 <div className="flex flex-col gap-2">
139 <div className="flex items-center gap-2">
140 <ButtonTooltip
141 type="default"
142 disabled={isRefreshing}
143 icon={<RefreshCw className={isRefreshing ? 'animate-spin' : ''} />}
144 className="w-7"
145 tooltip={{ content: { side: 'bottom', text: 'Refresh report' } }}
146 onClick={onRefreshReport}
147 />
148
149 <ReportSettings chartId="edge-functions-charts" />
150
151 <LogsDatePicker
152 align="start"
153 value={datePickerValue}
154 helpers={datePickerHelpers}
155 onSubmit={handleDatePickerChange}
156 />
157 <UpgradePrompt
158 show={showUpgradePrompt}
159 setShowUpgradePrompt={setShowUpgradePrompt}
160 title="Report date range"
161 description="Report data can be stored for a maximum of 3 months depending on the plan that your project is on."
162 source="edgeFunctionsReportDateRange"
163 />
164
165 {selectedDateRange && (
166 <div className="flex items-center gap-x-2 text-xs">
167 <p className="text-foreground-light">
168 {dayjs(selectedDateRange.period_start.date).format('MMM D, h:mma')}
169 </p>
170 <p className="text-foreground-light">
171 <ArrowRight size={12} />
172 </p>
173 <p className="text-foreground-light">
174 {dayjs(selectedDateRange.period_end.date).format('MMM D, h:mma')}
175 </p>
176 </div>
177 )}
178 </div>
179 <div className="w-full flex items-center gap-2 flex-wrap">
180 <ReportsSelectFilter
181 label="Function"
182 options={
183 functions?.map((fn: { name: string; id: string }) => ({
184 label: fn.name,
185 value: fn.id,
186 })) ?? []
187 }
188 value={functionFilter ?? []}
189 onChange={setFunctionFilter}
190 isLoading={isRefreshing}
191 showSearch
192 />
193
194 <ReportsNumericFilter
195 label="Status Code"
196 value={statusCodeFilter}
197 onChange={setStatusCodeFilter}
198 defaultOperator="="
199 isLoading={isRefreshing}
200 />
201
202 <ReportsNumericFilter
203 label="Execution Time"
204 value={executionTimeFilter}
205 onChange={setExecutionTimeFilter}
206 placeholder="Enter time in ms"
207 min={0}
208 max={99999}
209 defaultOperator=">="
210 isLoading={isRefreshing}
211 />
212
213 <ReportsSelectFilter
214 label="Region"
215 options={EDGE_FUNCTION_REGIONS.map((region) => ({
216 value: region.key,
217 label: (
218 <div className="flex items-center gap-x-2">
219 <img
220 src={`${BASE_PATH}/img/regions/${region.key}.svg`}
221 alt={region.key}
222 className="w-4 h-4"
223 />
224 <div className="flex flex-wrap gap-x-2 items-center">
225 <span className="text-foreground text-xs">{region.label}</span>
226 <span className="text-foreground-lighter text-xs">{region.key}</span>
227 </div>
228 </div>
229 ),
230 }))}
231 value={regionFilter ?? []}
232 onChange={setRegionFilter}
233 showSearch
234 />
235 </div>
236 </div>
237 }
238 >
239 <div className="mt-8 flex flex-col gap-4 pb-8">
240 {selectedDateRange &&
241 reportConfig
242 .filter((report) => !report.hide)
243 .map((report) => (
244 <ReportChartV2
245 key={`${report.id}`}
246 report={report}
247 projectRef={ref!}
248 interval={selectedDateRange.interval}
249 startDate={selectedDateRange?.period_start?.date}
250 endDate={selectedDateRange?.period_end?.date}
251 updateDateRange={updateDateRange}
252 syncId={chartSyncId}
253 filters={{
254 functions: functionFilter,
255 status_code: statusCodeFilter,
256 region: regionFilter,
257 execution_time: executionTimeFilter,
258 }}
259 />
260 ))}
261 </div>
262 </ReportStickyNav>
263 <div className="pb-8">
264 <ObservabilityLink />
265 </div>
266 </>
267 )
268}