diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx index c90247eb7..72bfda765 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx @@ -111,6 +111,9 @@ export default async function APIKeyDetailPage(props: { .then((res) => res.val?.at(0)?.time ?? 0), ]); + // Sort all verifications by time first + const sortedVerifications = verifications.val!.sort((a, b) => a.time - b.time); + const successOverTime: { x: string; y: number }[] = []; const ratelimitedOverTime: { x: string; y: number }[] = []; const usageExceededOverTime: { x: string; y: number }[] = []; @@ -119,30 +122,47 @@ export default async function APIKeyDetailPage(props: { const expiredOverTime: { x: string; y: number }[] = []; const forbiddenOverTime: { x: string; y: number }[] = []; - for (const d of verifications.val!.sort((a, b) => a.time - b.time)) { + // Get all unique timestamps + const uniqueDates = [...new Set(sortedVerifications.map((d) => d.time))].sort((a, b) => a - b); + + // Ensure each array has entries for all timestamps with zero counts + for (const timestamp of uniqueDates) { + const x = new Date(timestamp).toISOString(); + successOverTime.push({ x, y: 0 }); + ratelimitedOverTime.push({ x, y: 0 }); + usageExceededOverTime.push({ x, y: 0 }); + disabledOverTime.push({ x, y: 0 }); + insufficientPermissionsOverTime.push({ x, y: 0 }); + expiredOverTime.push({ x, y: 0 }); + forbiddenOverTime.push({ x, y: 0 }); + } + + for (const d of sortedVerifications) { const x = new Date(d.time).toISOString(); + const index = uniqueDates.indexOf(d.time); + switch (d.outcome) { case "": case "VALID": - successOverTime.push({ x, y: d.count }); + successOverTime[index] = { x, y: d.count }; break; case "RATE_LIMITED": - ratelimitedOverTime.push({ x, y: d.count }); + ratelimitedOverTime[index] = { x, y: d.count }; break; case "USAGE_EXCEEDED": - usageExceededOverTime.push({ x, y: d.count }); + usageExceededOverTime[index] = { x, y: d.count }; break; case "DISABLED": - disabledOverTime.push({ x, y: d.count }); + disabledOverTime[index] = { x, y: d.count }; break; case "INSUFFICIENT_PERMISSIONS": - insufficientPermissionsOverTime.push({ x, y: d.count }); + insufficientPermissionsOverTime[index] = { x, y: d.count }; break; case "EXPIRED": - expiredOverTime.push({ x, y: d.count }); + expiredOverTime[index] = { x, y: d.count }; break; case "FORBIDDEN": - forbiddenOverTime.push({ x, y: d.count }); + forbiddenOverTime[index] = { x, y: d.count }; break; } } diff --git a/apps/dashboard/components/dashboard/charts.tsx b/apps/dashboard/components/dashboard/charts.tsx index 26d3b26b1..a0a3b1f18 100644 --- a/apps/dashboard/components/dashboard/charts.tsx +++ b/apps/dashboard/components/dashboard/charts.tsx @@ -8,7 +8,10 @@ type ColorName = "primary" | "warn" | "danger"; export const useColors = (colorNames: Array) => { const { resolvedTheme } = useTheme(); - const colors: { light: Record; dark: Record } = { + const colors: { + light: Record; + dark: Record; + } = { light: { primary: "#1c1917", warn: "#FFCD07", @@ -133,9 +136,9 @@ export const LineChart: React.FC<{ tooltip={{ formatter: (datum) => ({ name: datum.category, - value: `${Intl.NumberFormat(undefined, { notation: "compact" }).format( - Number(datum.y), - )} ms`, + value: `${Intl.NumberFormat(undefined, { + notation: "compact", + }).format(Number(datum.y))} ms`, }), }} /> @@ -202,6 +205,53 @@ export const StackedColumnChart: React.FC<{ colors: Array; }> = ({ data, timeGranularity, colors }) => { const { axisColor } = useColors(colors); + + const formatDate = (date: string) => { + const d = new Date(date); + if (Number.isNaN(d.getTime())) { + return date; + } + + switch (timeGranularity) { + case "minute": + return d.toLocaleString(undefined, { + hour: "numeric", + minute: "2-digit", + hour12: true, + month: "short", + day: "numeric", + }); + case "hour": + return d.toLocaleString(undefined, { + hour: "numeric", + hour12: true, + month: "short", + day: "numeric", + year: "numeric", + }); + case "day": + return d.toLocaleString(undefined, { + weekday: "short", + month: "short", + day: "numeric", + year: "numeric", + }); + case "month": + return d.toLocaleString(undefined, { + month: "long", + year: "numeric", + }); + default: + return d.toLocaleString(undefined, { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "2-digit", + }); + } + }; + return ( ({ name: datum.category, - value: Intl.NumberFormat(undefined, { notation: "compact" }).format(Number(datum.y)), + value: Intl.NumberFormat(undefined, { + notation: "compact", + maximumFractionDigits: 1, + compactDisplay: "short", + }).format(Number(datum.y)), }), }} />