Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/unkeyed/unkey into eng-1597…
Browse files Browse the repository at this point in the history
…-add-override-routes-in-unkeyratelimit-sdk-docs
  • Loading branch information
MichaelUnkey committed Dec 12, 2024
2 parents 39ce036 + 19548f8 commit 63ece8e
Show file tree
Hide file tree
Showing 31 changed files with 874 additions and 512 deletions.
3 changes: 3 additions & 0 deletions apps/dashboard/app/(app)/@breadcrumb/logs/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function NoBreadcrumb() {
return null;
}
142 changes: 0 additions & 142 deletions apps/dashboard/app/(app)/logs/components/chart.tsx

This file was deleted.

61 changes: 61 additions & 0 deletions apps/dashboard/app/(app)/logs/components/charts/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { trpc } from "@/lib/trpc/client";
import type { LogsTimeseriesDataPoint } from "@unkey/clickhouse/src/logs";
import { addMinutes, format } from "date-fns";
import { useLogSearchParams } from "../../query-state";
import { type TimeseriesGranularity, getTimeseriesGranularity } from "../../utils";

const roundToSecond = (timestamp: number) => Math.floor(timestamp / 1000) * 1000;

const formatTimestamp = (value: string | number, granularity: TimeseriesGranularity) => {
const date = new Date(value);
const offset = new Date().getTimezoneOffset() * -1;
const localDate = addMinutes(date, offset);

switch (granularity) {
case "perMinute":
return format(localDate, "HH:mm:ss");
case "perHour":
return format(localDate, "MMM d, HH:mm");
case "perDay":
return format(localDate, "MMM d");
default:
return format(localDate, "Pp");
}
};

export const useFetchTimeseries = (initialTimeseries: LogsTimeseriesDataPoint[]) => {
const { searchParams } = useLogSearchParams();

const filters = {
host: searchParams.host,
path: searchParams.path,
method: searchParams.method,
responseStatus: searchParams.responseStatus,
};

const {
startTime: rawStartTime,
endTime: rawEndTime,
granularity,
} = getTimeseriesGranularity(searchParams.startTime, searchParams.endTime);

const { data, isLoading } = trpc.logs.queryTimeseries.useQuery(
{
startTime: roundToSecond(rawStartTime),
endTime: roundToSecond(rawEndTime),
...filters,
},
{
refetchInterval: searchParams.endTime ? false : 10_000,
initialData: initialTimeseries,
},
);

const timeseries = data.map((data) => ({
displayX: formatTimestamp(data.x, granularity),
originalTimestamp: data.x,
...data.y,
}));

return { timeseries, isLoading };
};
131 changes: 131 additions & 0 deletions apps/dashboard/app/(app)/logs/components/charts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"use client";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import type { LogsTimeseriesDataPoint } from "@unkey/clickhouse/src/logs";
import { addMinutes, format } from "date-fns";
import { useEffect, useState } from "react";
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from "recharts";
import { useFetchTimeseries } from "./hooks";

const chartConfig = {
success: {
label: "Success",
color: "hsl(var(--chart-3))",
},
warning: {
label: "Warning",
color: "hsl(var(--chart-4))",
},
error: {
label: "Error",
color: "hsl(var(--chart-1))",
},
} satisfies ChartConfig;

const formatTimestampTooltip = (value: string | number) => {
const date = new Date(value);
const offset = new Date().getTimezoneOffset() * -1;
const localDate = addMinutes(date, offset);
return format(localDate, "dd MMM HH:mm:ss");
};

const calculateTickInterval = (dataLength: number, containerWidth: number) => {
const pixelsPerTick = 80; // Adjust this value to control density
const suggestedInterval = Math.ceil((dataLength * pixelsPerTick) / containerWidth);

const intervals = [1, 2, 5, 10, 15, 30, 60];
return intervals.find((i) => i >= suggestedInterval) || intervals[intervals.length - 1];
};

export function LogsChart({
initialTimeseries,
}: {
initialTimeseries: LogsTimeseriesDataPoint[];
}) {
const { timeseries } = useFetchTimeseries(initialTimeseries);
const [tickInterval, setTickInterval] = useState(5);
const [containerWidth, setContainerWidth] = useState(0);

useEffect(() => {
if (containerWidth > 0 && timeseries.length > 0) {
const newInterval = calculateTickInterval(timeseries.length, containerWidth);
setTickInterval(newInterval);
}
}, [timeseries.length, containerWidth]);

const maxValue = Math.max(
...timeseries.map((item) => (item.success || 0) + (item.warning || 0) + (item.error || 0)),
);
const yAxisMax = Math.ceil(maxValue * 1.1);

return (
<div className="w-full h-[120px] p-1 rounded-lg bg-card">
<ResponsiveContainer
width="100%"
height="60px"
onResize={(width) => setContainerWidth(width)}
>
<ChartContainer config={chartConfig}>
<BarChart data={timeseries} barCategoryGap={4} maxBarSize={20}>
<XAxis
dataKey="displayX"
tickLine={false}
axisLine={false}
className="font-mono"
minTickGap={30}
tick={{
fontSize: 11,
fill: "hsl(var(--muted-foreground))",
dy: 10,
}}
interval={tickInterval - 1}
/>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
vertical={false}
strokeOpacity={0.4}
/>
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent
className="rounded-lg shadow-lg border border-border"
labelFormatter={(_, payload) => {
const originalTimestamp = payload[0]?.payload?.originalTimestamp;
return originalTimestamp ? (
<span className="font-mono text-muted-foreground text-xs">
{formatTimestampTooltip(originalTimestamp)}
</span>
) : (
""
);
}}
/>
}
/>
<YAxis domain={[0, yAxisMax]} hide />
{["success", "warning", "error"].map((key, index) => (
<Bar
key={key}
dataKey={key}
stackId="a"
fill={`var(--color-${key})`}
radius={[
index === 0 ? 2 : 0,
index === 2 ? 2 : 0,
index === 2 ? 2 : 0,
index === 0 ? 2 : 0,
]}
/>
))}
</BarChart>
</ChartContainer>
</ResponsiveContainer>
</div>
);
}
Loading

0 comments on commit 63ece8e

Please sign in to comment.