mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
c228ca4f61
## Problem The Edge Function overview converted the numeric deployment timestamp to a string before parsing it as a date. This produced an invalid date, so the invocations chart omitted the deployment annotation. ## Fix Preserve the numeric timestamp returned by the API and allow the annotation helper to parse both numeric and string timestamps. Show deployment details in an accessible tooltip when hovering or focusing the rocket marker, and add regression coverage for numeric timestamps and the existing invalid, missing, and out-of-range cases. ## How to test - Run the focused EdgeFunctionOverview utility test suite. - Open an Edge Function whose latest deployment is within the selected chart interval. - Hover or focus the rocket marker. - Expected result: the invocations chart shows the dashed deployment line and rocket marker, and the marker tooltip shows the deployment time. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Invocation update annotations now display correctly when function update times are provided as numbers. - Timestamps at the start of the Unix epoch are now supported. - Annotations no longer appear when update times are invalid or chart data is incomplete. - **Accessibility** - Deployment markers in invocation charts are now keyboard-focusable and include accessible labels. - Deployment timestamps are available in a tooltip on hover or focus. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
import { Rocket } from 'lucide-react'
|
|
import { useMemo } from 'react'
|
|
import {
|
|
Bar,
|
|
CartesianGrid,
|
|
BarChart as RechartBarChart,
|
|
ReferenceLine,
|
|
XAxis,
|
|
YAxis,
|
|
} from 'recharts'
|
|
import {
|
|
ChartContainer,
|
|
ChartTooltip,
|
|
ChartTooltipContent,
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from 'ui'
|
|
|
|
import {
|
|
formatChartTimestamp,
|
|
getChartTimeRangeLabels,
|
|
INVOCATION_CHART_CONFIG,
|
|
} from './EdgeFunctionOverview.utils'
|
|
import type { InvocationChartDatum, InvocationUpdateAnnotation } from './EdgeFunctionOverview.utils'
|
|
|
|
interface EdgeFunctionInvocationsChartProps {
|
|
chartData: InvocationChartDatum[]
|
|
dateTimeFormat: string
|
|
onChartClick: (timestamp: string) => void
|
|
updateAnnotation?: InvocationUpdateAnnotation
|
|
}
|
|
|
|
export const EdgeFunctionInvocationsChart = ({
|
|
chartData,
|
|
dateTimeFormat,
|
|
onChartClick,
|
|
updateAnnotation,
|
|
}: EdgeFunctionInvocationsChartProps) => {
|
|
const timeRangeLabels = useMemo(
|
|
() => getChartTimeRangeLabels(chartData, dateTimeFormat),
|
|
[chartData, dateTimeFormat]
|
|
)
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1">
|
|
<div className="relative h-40 w-full overflow-visible">
|
|
<ChartContainer config={INVOCATION_CHART_CONFIG} className="aspect-auto! h-full! w-full!">
|
|
<RechartBarChart
|
|
data={chartData}
|
|
className="cursor-pointer"
|
|
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
|
|
onClick={(tooltipData) => {
|
|
const timestamp = tooltipData?.activePayload?.[0]?.payload?.timestamp
|
|
if (typeof timestamp === 'string') onChartClick(timestamp)
|
|
}}
|
|
>
|
|
<CartesianGrid vertical={false} />
|
|
<YAxis hide width={0} />
|
|
<XAxis
|
|
dataKey="timestamp"
|
|
tickLine={false}
|
|
axisLine={false}
|
|
tick={false}
|
|
minTickGap={32}
|
|
/>
|
|
<ChartTooltip
|
|
cursor={false}
|
|
content={
|
|
<ChartTooltipContent
|
|
className="text-foreground-light"
|
|
labelFormatter={(value) =>
|
|
formatChartTimestamp(value as string | number | undefined, dateTimeFormat)
|
|
}
|
|
indicator="dot"
|
|
/>
|
|
}
|
|
/>
|
|
<Bar
|
|
dataKey="error_count"
|
|
stackId="invocations"
|
|
fill="var(--color-error_count)"
|
|
maxBarSize={24}
|
|
/>
|
|
<Bar
|
|
dataKey="warning_count"
|
|
stackId="invocations"
|
|
fill="var(--color-warning_count)"
|
|
maxBarSize={24}
|
|
/>
|
|
<Bar
|
|
dataKey="ok_count"
|
|
stackId="invocations"
|
|
fill="var(--color-ok_count)"
|
|
maxBarSize={24}
|
|
/>
|
|
{updateAnnotation && (
|
|
<ReferenceLine
|
|
x={updateAnnotation.timestamp}
|
|
stroke="var(--foreground-default)"
|
|
strokeDasharray="4 4"
|
|
strokeWidth={1.5}
|
|
/>
|
|
)}
|
|
</RechartBarChart>
|
|
</ChartContainer>
|
|
{updateAnnotation && (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span
|
|
className="pointer-events-auto absolute bottom-0 z-10 flex h-6 w-6 -translate-x-1/2 translate-y-1/2 items-center justify-center rounded-full border border-foreground/20 bg-background text-foreground shadow-xs"
|
|
style={{ left: `${updateAnnotation.position}%` }}
|
|
role="img"
|
|
tabIndex={0}
|
|
aria-label={`Deployment at ${formatChartTimestamp(
|
|
updateAnnotation.updatedAt,
|
|
dateTimeFormat
|
|
)}`}
|
|
>
|
|
<Rocket size={12} strokeWidth={1.75} aria-hidden />
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" align="center">
|
|
Deployment at {formatChartTimestamp(updateAnnotation.updatedAt, dateTimeFormat)}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
{timeRangeLabels && (
|
|
<div className="-mt-6 flex items-center justify-between text-[10px] font-mono text-foreground-lighter">
|
|
<span>{timeRangeLabels.start}</span>
|
|
<span>{timeRangeLabels.end}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|