Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
012b154
fix: API tooltip / chart fixes
perkinsjr Sep 3, 2025
f1d51fc
Fix keys
perkinsjr Sep 3, 2025
d3fbe99
Ratelimits
perkinsjr Sep 3, 2025
bc45baf
formatting
perkinsjr Sep 4, 2025
fe8061f
Merge branch 'main' into eng-2053-tooltip-times-are-wrong-when-lookin…
perkinsjr Sep 4, 2025
4244b92
Minor fixes
perkinsjr Sep 4, 2025
a1add6d
More fixes and code improvements
perkinsjr Sep 4, 2025
f9e4616
replaced the duplicated local `Granularity` union type with a proper …
perkinsjr Sep 8, 2025
1343ec4
optimize tooltip hover handler with O(1) timestamp lookup
perkinsjr Sep 8, 2025
d039e99
Add a constant for fallback
perkinsjr Sep 8, 2025
ff8fbb0
improve timezone accuracy in overview chart tooltips
perkinsjr Sep 8, 2025
27299c8
improve timestamp handling and type safety in logs utils
perkinsjr Sep 8, 2025
4860cb0
remove the aria from tooltip not needed
perkinsjr Sep 8, 2025
c0dbd09
increase upper boundary for edge cases
perkinsjr Sep 8, 2025
038238c
Remove redundancy for Granularity
perkinsjr Sep 8, 2025
f09ae5b
formatting
perkinsjr Sep 8, 2025
105c8a4
export ChartMouseEvent type and replace any types with proper typing
perkinsjr Sep 8, 2025
a611f0c
optimize tooltip lookups with precomputed timestamp map
perkinsjr Sep 8, 2025
ec30287
standardize time buffer constants and error handling across chart com…
perkinsjr Sep 8, 2025
bd59fff
refactor: extract tooltip formatter to shared utility
perkinsjr Sep 8, 2025
519e83d
refactor: reuse precomputed timezone abbreviation to avoid redundant …
perkinsjr Sep 8, 2025
cb0425d
extract shared parseTimestamp helper to handle microsecond timestamps
perkinsjr Sep 8, 2025
379a093
fix: improve timestamp tooltip handling for numeric strings
perkinsjr Sep 8, 2025
0f2d2d0
fix(dashboard): ensure numeric timestamps in calculateTimePoints call
perkinsjr Sep 8, 2025
138d1ed
fix: Update to 12H instead of default 24h
perkinsjr Sep 8, 2025
d703d6b
fix: normalize timestamp comparison in findIndex for reliable interva…
perkinsjr Sep 8, 2025
38d4239
fix: removed unreachable conditonal code
perkinsjr Sep 8, 2025
d02bab9
formatting
perkinsjr Sep 8, 2025
4bb804e
Merge branch 'main' into eng-2053-tooltip-times-are-wrong-when-lookin…
perkinsjr Sep 8, 2025
d2db157
feat: add memoization to timestamp formatting for better chart perfor…
perkinsjr Sep 8, 2025
e035f3b
fix(logs): skip invalid timestamps in overview chart timestamp→index map
perkinsjr Sep 8, 2025
eca153c
fix(dashboard): validate timestamps before adding to index map
perkinsjr Sep 8, 2025
3e1bb17
refactor: replace fragile digit-count logic with numeric magnitude th…
perkinsjr Sep 8, 2025
a48344d
Remove redundancy
perkinsjr Sep 8, 2025
e654219
null check to make sure we don't drop valid epoch
perkinsjr Sep 8, 2025
a8fcd5b
perf(logs): optimize timezone formatting by hoisting DateTimeFormat t…
perkinsjr Sep 8, 2025
84088f5
Merge branch 'eng-2053-tooltip-times-are-wrong-when-looking-at-differ…
perkinsjr Sep 8, 2025
08b213b
Merge branch 'main' of https://github.com/unkeyed/unkey into
MichaelUnkey Sep 24, 2025
5a7ec4b
timeseries changes from rabbit comments
MichaelUnkey Sep 24, 2025
16dd808
Merge branch 'main' of https://github.com/unkeyed/unkey into eng-2053…
MichaelUnkey Sep 26, 2025
1fa0ccc
rabbit comments and control pill tool tip value
MichaelUnkey Sep 26, 2025
e1c40dd
Merge branch 'main' of https://github.com/unkeyed/unkey into
MichaelUnkey Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,24 @@ export const KeysOverviewLogsCharts = ({
timeseries: verificationTimeseries,
isLoading: verificationIsLoading,
isError: verificationIsError,
granularity: verificationGranularity,
} = useFetchVerificationTimeseries(apiId);

const {
timeseries: activeKeysTimeseries,
isLoading: activeKeysIsLoading,
isError: activeKeysIsError,
granularity,
granularity: activeKeysGranularity,
} = useFetchActiveKeysTimeseries(apiId);

const handleSelectionChange = ({
start,
end,
granularity,
}: {
start: number;
end: number;
granularity?: typeof verificationGranularity;
}) => {
const activeFilters = filters.filter(
(f) => !["startTime", "endTime", "since"].includes(f.field),
Expand Down Expand Up @@ -90,7 +93,12 @@ export const KeysOverviewLogsCharts = ({
isError={verificationIsError}
enableSelection
onMount={onMount}
onSelectionChange={handleSelectionChange}
onSelectionChange={(selection) =>
handleSelectionChange({
...selection,
granularity: verificationGranularity,
})
}
config={createOutcomeChartConfig()}
labels={{
title: "REQUESTS",
Expand All @@ -100,6 +108,7 @@ export const KeysOverviewLogsCharts = ({
secondaryKey: "error",
}}
tooltipItems={[{ label: "Invalid", dataKey: "error" }]}
granularity={verificationGranularity}
/>
</div>
<div className="w-full md:w-1/2 max-md:h-72">
Expand All @@ -108,9 +117,15 @@ export const KeysOverviewLogsCharts = ({
isLoading={activeKeysIsLoading}
isError={activeKeysIsError}
enableSelection
onSelectionChange={handleSelectionChange}
onSelectionChange={(selection) =>
handleSelectionChange({
...selection,
granularity: activeKeysGranularity,
})
}
config={keysChartConfig}
labels={keysChartLabels}
granularity={activeKeysGranularity}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const KeyDetailsLogsChart = ({
onMount={onMount}
onSelectionChange={handleSelectionChange}
config={createOutcomeChartConfig()}
granularity={granularity}
labels={{
title: "REQUESTS",
primaryLabel: "VALID",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const RatelimitOverviewLogsCharts = ({
isError={isError}
enableSelection
onSelectionChange={handleSelectionChange}
granularity={granularity}
config={{
success: {
label: "Passed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function RatelimitLogsChart({
isLoading={isLoading}
isError={isError}
enableSelection
granularity={granularity}
/>
);
}
141 changes: 102 additions & 39 deletions apps/dashboard/components/logs/chart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@ import {
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatNumber } from "@/lib/fmt";
import type { CompoundTimeseriesGranularity } from "@/lib/trpc/routers/utils/granularity";
import { Grid } from "@unkey/icons";
import { useEffect, useRef, useState } from "react";
import { Bar, BarChart, ReferenceArea, ResponsiveContainer, YAxis } from "recharts";
import {
Bar,
BarChart,
ReferenceArea,
ResponsiveContainer,
YAxis,
} from "recharts";
import { createTimeIntervalFormatter } from "../overview-charts/utils";
import { LogsChartError } from "./components/logs-chart-error";
import { LogsChartLoading } from "./components/logs-chart-loading";
import { calculateTimePoints } from "./utils/calculate-timepoints";
import { formatTimestampLabel } from "./utils/format-timestamp";

type Selection = {
start: string | number;
end: string | number;
start: number | undefined;
end: number | undefined;
startTimestamp?: number;
endTimestamp?: number;
};
Expand All @@ -30,6 +37,13 @@ type TimeseriesData = {
[key: string]: unknown;
};

export type ChartMouseEvent = {
activeLabel?: string | number;
activePayload?: ReadonlyArray<{
payload: TimeseriesData;
}>;
};

type LogsTimeseriesBarChartProps = {
data?: TimeseriesData[];
config: ChartConfig;
Expand All @@ -39,6 +53,7 @@ type LogsTimeseriesBarChartProps = {
isLoading?: boolean;
isError?: boolean;
enableSelection?: boolean;
granularity?: CompoundTimeseriesGranularity;
};

export function LogsTimeseriesBarChart({
Expand All @@ -50,9 +65,13 @@ export function LogsTimeseriesBarChart({
isLoading,
isError,
enableSelection = false,
granularity,
}: LogsTimeseriesBarChartProps) {
const chartRef = useRef<HTMLDivElement>(null);
const [selection, setSelection] = useState<Selection>({ start: "", end: "" });
const [selection, setSelection] = useState<Selection>({
start: undefined,
end: undefined,
});

// biome-ignore lint/correctness/useExhaustiveDependencies: We need this to re-trigger distanceToTop calculation
useEffect(() => {
Expand All @@ -62,31 +81,42 @@ export function LogsTimeseriesBarChart({
}
}, [onMount, isLoading, isError]);

// biome-ignore lint/suspicious/noExplicitAny: those are safe to leave
const handleMouseDown = (e: any) => {
if (!enableSelection) {
const handleMouseDown = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}

const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
const numericLabel = Number(e.activeLabel);

if (!Number.isFinite(numericLabel) || !timestamp) {
return;
}

setSelection({
start: e.activeLabel,
end: e.activeLabel,
start: numericLabel,
end: numericLabel,
startTimestamp: timestamp,
endTimestamp: timestamp,
});
};
Comment on lines +84 to 102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against non-numeric activeLabel to avoid NaN selection.

Add a finite-number check before setting state.

   const handleMouseDown = (e: ChartMouseEvent) => {
     if (!enableSelection || e.activeLabel === undefined) {
       return;
     }
-    const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
-    const numericLabel = Number(e.activeLabel);
+    const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
+    const numericLabel = Number(e.activeLabel);
+    if (!Number.isFinite(numericLabel)) {
+      return;
+    }
+    if (timestamp === undefined) {
+      return;
+    }
     setSelection({
       start: numericLabel,
       end: numericLabel,
       startTimestamp: timestamp,
       endTimestamp: timestamp,
     });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMouseDown = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}
const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
const numericLabel = Number(e.activeLabel);
setSelection({
start: e.activeLabel,
end: e.activeLabel,
start: numericLabel,
end: numericLabel,
startTimestamp: timestamp,
endTimestamp: timestamp,
});
};
const handleMouseDown = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}
const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
const numericLabel = Number(e.activeLabel);
if (!Number.isFinite(numericLabel)) {
return;
}
if (timestamp === undefined) {
return;
}
setSelection({
start: numericLabel,
end: numericLabel,
startTimestamp: timestamp,
endTimestamp: timestamp,
});
};
🤖 Prompt for AI Agents
In apps/dashboard/components/logs/chart/index.tsx around lines 84 to 96, guard
against non-numeric e.activeLabel before calling Number(...) and setting
selection: compute numericLabel = Number(e.activeLabel) and if
!Number.isFinite(numericLabel) return (do not set selection), otherwise use
numericLabel and the timestamp as before; this prevents NaN values from being
stored in selection.


// biome-ignore lint/suspicious/noExplicitAny: those are safe to leave
const handleMouseMove = (e: any) => {
if (!enableSelection) {
const handleMouseMove = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}
if (selection.start) {
if (selection.start !== undefined) {
const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
const numericLabel = Number(e.activeLabel);

if (!Number.isFinite(numericLabel) || !timestamp) {
return;
}

setSelection((prev) => ({
...prev,
end: e.activeLabel,
startTimestamp: timestamp,
end: numericLabel,
endTimestamp: timestamp,
}));
}
};
Comment on lines +104 to 122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same NaN guard needed during drag.

   const handleMouseMove = (e: ChartMouseEvent) => {
     if (!enableSelection || e.activeLabel === undefined) {
       return;
     }
     if (selection.start !== undefined) {
-      const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
+      const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
+      const numericLabel = Number(e.activeLabel);
+      if (!Number.isFinite(numericLabel)) {
+        return;
+      }
+      if (timestamp === undefined) {
+        return;
+      }
       setSelection((prev) => ({
         ...prev,
-        end: Number(e.activeLabel),
+        end: numericLabel,
         endTimestamp: timestamp,
       }));
     }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMouseMove = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}
if (selection.start) {
if (selection.start !== undefined) {
const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
setSelection((prev) => ({
...prev,
end: e.activeLabel,
startTimestamp: timestamp,
end: Number(e.activeLabel),
endTimestamp: timestamp,
}));
}
};
const handleMouseMove = (e: ChartMouseEvent) => {
if (!enableSelection || e.activeLabel === undefined) {
return;
}
if (selection.start !== undefined) {
const timestamp = e.activePayload?.[0]?.payload?.originalTimestamp;
const numericLabel = Number(e.activeLabel);
if (!Number.isFinite(numericLabel)) {
return;
}
if (timestamp === undefined) {
return;
}
setSelection((prev) => ({
...prev,
end: numericLabel,
endTimestamp: timestamp,
}));
}
};
🤖 Prompt for AI Agents
In apps/dashboard/components/logs/chart/index.tsx around lines 98 to 110, the
mouse-move handler sets selection.end by coercing e.activeLabel to Number
without guarding against NaN which can corrupt the selection during drag; modify
the handler to coerce e.activeLabel once, check Number.isFinite (or
!Number.isNaN) and only update selection.end and endTimestamp when the coerced
label is a valid number (otherwise ignore the update or keep previous
selection), and keep extracting the originalTimestamp as the endTimestamp
fallback only when payload exists.

Expand All @@ -95,17 +125,27 @@ export function LogsTimeseriesBarChart({
if (!enableSelection) {
return;
}
if (selection.start && selection.end && onSelectionChange) {
if (!selection.startTimestamp || !selection.endTimestamp) {
if (
selection.start !== undefined &&
selection.end !== undefined &&
onSelectionChange
) {
if (
selection.startTimestamp === undefined ||
selection.endTimestamp === undefined
) {
return;
}

const [start, end] = [selection.startTimestamp, selection.endTimestamp].sort((a, b) => a - b);
const [start, end] = [
selection.startTimestamp,
selection.endTimestamp,
].sort((a, b) => a - b);
onSelectionChange({ start, end });
}
setSelection({
start: "",
end: "",
start: undefined,
end: undefined,
startTimestamp: undefined,
endTimestamp: undefined,
});
Expand All @@ -125,16 +165,19 @@ export function LogsTimeseriesBarChart({
{data
? calculateTimePoints(
data[0]?.originalTimestamp ?? Date.now(),
data.at(-1)?.originalTimestamp ?? Date.now(),
).map((time, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<div key={i} className="z-10">
data.at(-1)?.originalTimestamp ?? Date.now()
).map((time) => (
<div key={time.getTime()} className="z-10">
{formatTimestampLabel(time)}
</div>
))
: null}
</div>
<ResponsiveContainer width="100%" height={height} className="border-b border-gray-4">
<ResponsiveContainer
width="100%"
height={height}
className="border-b border-gray-4"
>
<ChartContainer config={config}>
<BarChart
data={data}
Expand All @@ -157,7 +200,11 @@ export function LogsTimeseriesBarChart({
strokeOpacity: 0.7,
}}
content={({ active, payload, label }) => {
if (!active || !payload?.length || payload?.[0]?.payload.total === 0) {
if (
!active ||
!payload?.length ||
payload?.[0]?.payload.total === 0
) {
return null;
}

Expand All @@ -175,7 +222,9 @@ export function LogsTimeseriesBarChart({
<span className="capitalize text-accent-9 text-xs w-[2ch] inline-block">
All
</span>
<span className="capitalize text-accent-12 text-xs">Total</span>
<span className="capitalize text-accent-12 text-xs">
Total
</span>
</div>
<div className="ml-auto">
<span className="font-mono tabular-nums text-accent-12">
Expand All @@ -188,25 +237,39 @@ export function LogsTimeseriesBarChart({
}
className="rounded-lg shadow-lg border border-gray-4"
labelFormatter={(_, payload) =>
//@ts-expect-error This is okay to ignore
createTimeIntervalFormatter(data, "HH:mm")(payload)
createTimeIntervalFormatter(
data,
undefined,
granularity
)(
(payload ?? []).map(
(p) => (p as { payload: TimeseriesData }).payload
)
)
}
/>
);
}}
/>
{Object.keys(config).map((key) => (
<Bar key={key} dataKey={key} stackId="a" fill={config[key].color} />
))}
{enableSelection && selection.start && selection.end && (
<ReferenceArea
isAnimationActive
x1={Math.min(Number(selection.start), Number(selection.end))}
x2={Math.max(Number(selection.start), Number(selection.end))}
fill="hsl(var(--chart-selection))"
radius={[4, 4, 0, 0]}
<Bar
key={key}
dataKey={key}
stackId="a"
fill={config[key].color}
/>
)}
))}
{enableSelection &&
selection.start !== undefined &&
selection.end !== undefined && (
<ReferenceArea
isAnimationActive
x1={Math.min(selection.start, selection.end)}
x2={Math.max(selection.start, selection.end)}
fill="hsl(var(--chart-selection))"
radius={[4, 4, 0, 0]}
/>
)}
</BarChart>
</ChartContainer>
</ResponsiveContainer>
Expand Down
Loading