Skip to content

Commit c7ac3ba

Browse files
committed
Refactor BotChart and Chart components to enhance data processing and visualization
This commit updates the BotChart component to utilize the TimeSeriesChart for improved data visualization and refines the data processing logic using useMemo for better performance. It also removes the deprecated chartTimeBounds utility, consolidating time-bound calculations directly within the components. Additionally, the Chart component is updated to streamline imports and enhance type definitions for better clarity and maintainability. These changes aim to improve the overall efficiency and user experience of the charting features.
1 parent 883a6d1 commit c7ac3ba

5 files changed

Lines changed: 1094 additions & 1299 deletions

File tree

client/src/app/[site]/bots/components/BotChart.tsx

Lines changed: 59 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,66 @@
11
"use client";
22

3-
import { ResponsiveLine } from "@nivo/line";
43
import { DateTime } from "luxon";
5-
import { Tilt_Warp } from "next/font/google";
64
import Link from "next/link";
5+
import { useMemo } from "react";
6+
77
import { useGetBotTimeSeries } from "../../../../api/analytics/hooks/bots/useGetBotTimeSeries";
88
import { BucketSelection } from "../../../../components/BucketSelection";
99
import { ChartTooltip } from "../../../../components/charts/ChartTooltip";
10+
import { TimeSeriesChart } from "../../../../components/charts/TimeSeriesChart";
11+
import type { TimeSeriesChartPoint } from "../../../../components/charts/TimeSeriesChart";
12+
import { getChartTimeBounds } from "../../../../components/charts/timeSeriesChartUtils";
1013
import { RybbitTextLogo } from "../../../../components/RybbitLogo";
1114
import { Card, CardContent, CardLoader } from "../../../../components/ui/card";
1215
import { Skeleton } from "../../../../components/ui/skeleton";
1316
import { useWhiteLabel } from "../../../../hooks/useIsWhiteLabel";
1417
import { authClient } from "../../../../lib/auth";
15-
import { formatChartDateTime, hour12, userLocale } from "../../../../lib/dateTimeUtils";
16-
import { useNivoTheme } from "../../../../lib/nivo";
18+
import { formatChartDateTime } from "../../../../lib/dateTimeUtils";
1719
import { getTimezone, useStore } from "../../../../lib/store";
1820

19-
const tilt_wrap = Tilt_Warp({
20-
subsets: ["latin"],
21-
weight: "400",
22-
});
21+
type BotPoint = TimeSeriesChartPoint & {
22+
currentTime: DateTime;
23+
};
2324

2425
export function BotChart() {
2526
const session = authClient.useSession();
26-
const { site, bucket } = useStore();
27+
const { site, bucket, time } = useStore();
2728
const timezone = getTimezone();
28-
const nivoTheme = useNivoTheme();
2929
const { isWhiteLabel } = useWhiteLabel();
3030

3131
const { data: timeSeriesData, isLoading, isFetching } = useGetBotTimeSeries({ site });
3232

33-
const processedData =
34-
timeSeriesData?.data
35-
?.map(item => {
36-
const timestamp = DateTime.fromSQL(item.time, { zone: timezone }).toUTC();
37-
if (timestamp > DateTime.now()) return null;
38-
return {
39-
time: timestamp.toFormat("yyyy-MM-dd HH:mm:ss"),
40-
bot_requests: item.bot_requests,
41-
};
42-
})
43-
.filter(item => item !== null) ?? [];
33+
const { current, chartMin, chartMax, max } = useMemo(() => {
34+
const { min: boundsMin, max: boundsMax } = getChartTimeBounds(time, bucket, timezone);
35+
36+
const now = DateTime.now();
37+
const lowerBoundMs = boundsMin?.getTime();
38+
const upperBoundMs = (boundsMax ?? now.toJSDate()).getTime();
39+
const points: BotPoint[] = [];
4440

45-
const data = [
46-
{
47-
id: "Bot requests",
48-
color: "hsl(var(--red-400))",
49-
data: processedData.map(item => ({
50-
x: item.time,
41+
timeSeriesData?.data?.forEach(item => {
42+
const timestamp = DateTime.fromSQL(item.time, { zone: timezone }).toUTC();
43+
if (timestamp > now) return;
44+
const timestampMs = timestamp.toMillis();
45+
if (lowerBoundMs !== undefined && timestampMs < lowerBoundMs) return;
46+
if (timestampMs > upperBoundMs) return;
47+
points.push({
48+
x: timestamp.toJSDate(),
5149
y: item.bot_requests,
52-
})),
53-
},
54-
].filter(series => series.data.length > 0);
50+
currentTime: timestamp,
51+
});
52+
});
5553

56-
const formatXAxisValue = (value: any) => {
57-
const dt = DateTime.fromJSDate(value, { zone: "utc" }).setZone(timezone).setLocale(userLocale);
58-
if (
59-
bucket === "hour" ||
60-
bucket === "minute" ||
61-
bucket === "five_minutes" ||
62-
bucket === "ten_minutes" ||
63-
bucket === "fifteen_minutes"
64-
) {
65-
return dt.toFormat(hour12 ? "ha" : "HH:mm");
66-
}
67-
return dt.toFormat(hour12 ? "MMM d" : "dd MMM");
68-
};
54+
const dataMin = points.length ? points[0].x : undefined;
55+
const dataMax = points.length ? points[points.length - 1].x : undefined;
56+
57+
return {
58+
current: points,
59+
chartMin: boundsMin ?? dataMin,
60+
chartMax: boundsMax ?? dataMax ?? now.toJSDate(),
61+
max: points.reduce((largest, point) => Math.max(largest, point.y), 0),
62+
};
63+
}, [bucket, time, timeSeriesData, timezone]);
6964

7065
return (
7166
<Card className="overflow-visible">
@@ -90,7 +85,7 @@ export function BotChart() {
9085
<div className="space-y-3">
9186
<Skeleton className="w-full h-[300px] rounded-md" />
9287
</div>
93-
) : data.length === 0 ? (
88+
) : current.length === 0 ? (
9489
<div className="h-[300px] w-full flex items-center justify-center text-neutral-500">
9590
<div className="text-center">
9691
<p className="text-lg font-medium">No bot data available</p>
@@ -99,72 +94,27 @@ export function BotChart() {
9994
</div>
10095
) : (
10196
<div className="h-[300px] w-full">
102-
<ResponsiveLine
103-
data={data}
104-
theme={nivoTheme}
105-
margin={{ top: 10, right: 20, bottom: 30, left: 40 }}
106-
xScale={{
107-
type: "time",
108-
format: "%Y-%m-%d %H:%M:%S",
109-
precision: "second",
110-
useUTC: true,
111-
}}
112-
yScale={{
113-
type: "linear",
114-
min: 0,
115-
stacked: false,
116-
reverse: false,
117-
}}
118-
enableGridX={true}
119-
enableGridY={true}
120-
gridYValues={5}
121-
axisTop={null}
122-
axisRight={null}
123-
axisBottom={{
124-
tickSize: 5,
125-
tickPadding: 10,
126-
tickRotation: 0,
127-
truncateTickAt: 0,
128-
format: formatXAxisValue,
129-
}}
130-
axisLeft={{
131-
tickSize: 5,
132-
tickPadding: 10,
133-
tickRotation: 0,
134-
truncateTickAt: 0,
135-
tickValues: 5,
136-
format: value => Number(value).toLocaleString(),
137-
}}
138-
colors={d => d.color}
139-
enableTouchCrosshair={true}
140-
enablePoints={false}
141-
useMesh={true}
142-
animate={false}
143-
enableSlices="x"
144-
enableArea={true}
145-
lineWidth={1}
146-
sliceTooltip={({ slice }: any) => {
147-
const currentTime = DateTime.fromJSDate(new Date(slice.points[0].data.x), { zone: "utc" }).setZone(
148-
timezone
149-
);
150-
151-
return (
152-
<ChartTooltip>
153-
<div className="p-3 min-w-[150px]">
154-
<div className="mb-2">{formatChartDateTime(currentTime, bucket)}</div>
155-
{slice.points.map((point: any) => (
156-
<div key={point.seriesId} className="flex justify-between items-center gap-4">
157-
<div className="flex items-center gap-2">
158-
<div className="w-1 h-3 rounded-[3px]" style={{ backgroundColor: point.seriesColor }} />
159-
<span>{point.seriesId}</span>
160-
</div>
161-
<span className="font-medium">{Number(point.data.yFormatted).toLocaleString()}</span>
162-
</div>
163-
))}
97+
<TimeSeriesChart
98+
current={current}
99+
max={max}
100+
chartMin={chartMin}
101+
chartMax={chartMax}
102+
currentColor="hsl(var(--red-400))"
103+
yTickFormat={value => Number(value).toLocaleString()}
104+
renderTooltip={({ point, bucket }) => (
105+
<ChartTooltip>
106+
<div className="p-3 min-w-[150px]">
107+
<div className="mb-2">{formatChartDateTime(point.currentTime, bucket)}</div>
108+
<div className="flex justify-between items-center gap-4">
109+
<div className="flex items-center gap-2">
110+
<div className="w-1 h-3 rounded-[3px]" style={{ backgroundColor: "hsl(var(--red-400))" }} />
111+
<span>Bot requests</span>
112+
</div>
113+
<span className="font-medium">{point.y.toLocaleString()}</span>
164114
</div>
165-
</ChartTooltip>
166-
);
167-
}}
115+
</div>
116+
</ChartTooltip>
117+
)}
168118
/>
169119
</div>
170120
)}

0 commit comments

Comments
 (0)