Skip to content

Commit 4d065c3

Browse files
committed
[PRO-137] Add indexer and RPC charts in project overview page (#8464)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the chart components in the dashboard by updating properties for better data visualization and integrating new functionalities for displaying RPC and status code requests. ### Detailed summary - Updated `xAxis` property from `sameDay` to `showHour` in `rate-graph.tsx` and `count-graph.tsx`. - Increased height of `Card` in `RpcMethodBarChartCardUI.tsx`. - Changed title and description handling in `RequestsByStatusGraph.tsx`. - Added `className` prop to `ThirdwebBarChart` component. - Replaced `RequestsGraph` with `RPCRequestsChartUI` in `RPCAnalytics.tsx`. - Introduced a new component `TotalValueChartHeader` for displaying total values. - Updated `RequestsGraph` to include a breakdown by hour. - Added asynchronous functions for fetching indexer and RPC request data in `page.tsx`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 4262290 commit 4d065c3

File tree

10 files changed

+221
-34
lines changed

10 files changed

+221
-34
lines changed

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"use client";
22

33
import { format } from "date-fns";
4+
import { ArrowUpRightIcon } from "lucide-react";
5+
import Link from "next/link";
46
import { useMemo } from "react";
57
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
68
import {
79
EmptyChartState,
810
LoadingChartState,
911
} from "@/components/analytics/empty-chart-state";
12+
import { Button } from "@/components/ui/button";
1013
import {
1114
Card,
1215
CardContent,
@@ -23,6 +26,7 @@ import {
2326
ChartTooltipContent,
2427
} from "@/components/ui/chart";
2528
import { cn } from "@/lib/utils";
29+
import { SkeletonContainer } from "../../ui/skeleton";
2630

2731
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
2832
header?: {
@@ -40,7 +44,7 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
4044

4145
yAxis?: boolean;
4246
xAxis?: {
43-
sameDay?: boolean;
47+
showHour?: boolean;
4448
};
4549

4650
variant?: "stacked" | "individual";
@@ -112,7 +116,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
112116
tickFormatter={(value) =>
113117
format(
114118
new Date(value),
115-
props.xAxis?.sameDay ? "MMM dd, HH:mm" : "MMM dd",
119+
props.xAxis?.showHour ? "MMM dd, HH:mm" : "MMM dd",
116120
)
117121
}
118122
tickLine={false}
@@ -189,3 +193,49 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
189193
</Card>
190194
);
191195
}
196+
197+
export function TotalValueChartHeader(props: {
198+
total: number;
199+
title: string;
200+
isPending: boolean;
201+
viewMoreLink: string | undefined;
202+
}) {
203+
return (
204+
<div className="space-y-1 p-6 flex justify-between items-start gap-3">
205+
<div>
206+
<SkeletonContainer
207+
loadedData={!props.isPending ? props.total : undefined}
208+
skeletonData={100}
209+
render={(value) => {
210+
return (
211+
<p className="text-3xl font-semibold tracking-tight">
212+
{compactNumberFormatter.format(value)}
213+
</p>
214+
);
215+
}}
216+
/>
217+
218+
<h3 className="text-muted-foreground">{props.title}</h3>
219+
</div>
220+
221+
{props.viewMoreLink && (
222+
<Button
223+
asChild
224+
size="sm"
225+
variant="outline"
226+
className="gap-2 rounded-full text-muted-foreground hover:text-foreground"
227+
>
228+
<Link href={props.viewMoreLink}>
229+
<span>View More</span>
230+
<ArrowUpRightIcon className="size-4" />
231+
</Link>
232+
</Button>
233+
)}
234+
</div>
235+
);
236+
}
237+
238+
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
239+
notation: "compact",
240+
maximumFractionDigits: 2,
241+
});

apps/dashboard/src/@/components/blocks/charts/bar-chart.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
4444
toolTipValueFormatter?: (value: unknown) => React.ReactNode;
4545
hideLabel?: boolean;
4646
emptyChartState?: React.ReactElement;
47+
className?: string;
4748
};
4849

4950
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -55,7 +56,7 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
5556
props.variant || configKeys.length > 4 ? "stacked" : "grouped";
5657

5758
return (
58-
<Card>
59+
<Card className={props.className}>
5960
{props.header && (
6061
<CardHeader>
6162
<CardTitle className={cn("mb-2", props.header.titleClassName)}>

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function CountGraph(props: {
5757
return format(label, "MMM dd, HH:mm");
5858
}}
5959
xAxis={{
60-
sameDay: true,
60+
showHour: true,
6161
}}
6262
yAxis
6363
/>

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function RateGraph(props: {
5757
return format(label, "MMM dd, HH:mm");
5858
}}
5959
xAxis={{
60-
sameDay: true,
60+
showHour: true,
6161
}}
6262
yAxis
6363
/>

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/RpcMethodBarChartCard/RpcMethodBarChartCardUI.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function RpcMethodBarChartCardUI({
116116
</CardHeader>
117117
<CardContent className="px-2 sm:p-6 sm:pl-0">
118118
<ChartContainer
119-
className="aspect-auto h-[250px] w-full pt-6"
119+
className="aspect-auto h-[275px] w-full pt-6"
120120
config={chartConfig}
121121
>
122122
<RechartsBarChart

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/gateway/indexer/components/InsightAnalytics.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,8 @@ export async function InsightAnalytics(props: {
183183
) : (
184184
<RequestsByStatusGraph
185185
data={"data" in statusCodesData ? statusCodesData.data : []}
186-
description="The number of requests by status code over time."
187186
isPending={false}
188-
title="Requests by Status Code"
187+
viewMoreLink={undefined}
189188
/>
190189
)}
191190

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/gateway/indexer/components/RequestsByStatusGraph.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useMemo } from "react";
44
import { shortenLargeNumber } from "thirdweb/utils";
55
import type { InsightStatusCodeStats } from "@/api/analytics";
66
import { EmptyChartState } from "@/components/analytics/empty-chart-state";
7+
import { TotalValueChartHeader } from "@/components/blocks/charts/area-chart";
78
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
89
import type { ChartConfig } from "@/components/ui/chart";
910

@@ -15,11 +16,14 @@ const defaultLabel = 200;
1516
export function RequestsByStatusGraph(props: {
1617
data: InsightStatusCodeStats[];
1718
isPending: boolean;
18-
title: string;
19-
description: string;
19+
viewMoreLink: string | undefined;
2020
}) {
2121
const topStatusCodesToShow = 10;
2222

23+
const total = useMemo(() => {
24+
return props.data.reduce((acc, curr) => acc + curr.totalRequests, 0);
25+
}, [props.data]);
26+
2327
const { chartConfig, chartData } = useMemo(() => {
2428
const _chartConfig: ChartConfig = {};
2529
const _chartDataMap: Map<string, ChartData> = new Map();
@@ -90,17 +94,15 @@ export function RequestsByStatusGraph(props: {
9094

9195
return (
9296
<ThirdwebBarChart
93-
chartClassName="aspect-[1.5] lg:aspect-[3.5]"
97+
chartClassName="aspect-auto h-[275px]"
9498
config={chartConfig}
9599
customHeader={
96-
<div className="relative px-6 pt-6">
97-
<h3 className="mb-0.5 font-semibold text-xl tracking-tight">
98-
{props.title}
99-
</h3>
100-
<p className="mb-3 text-muted-foreground text-sm">
101-
{props.description}
102-
</p>
103-
</div>
100+
<TotalValueChartHeader
101+
isPending={props.isPending}
102+
total={total}
103+
title="Indexer Requests"
104+
viewMoreLink={props.viewMoreLink}
105+
/>
104106
}
105107
data={chartData}
106108
emptyChartState={<EmptyChartState type="bar" />}

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/gateway/rpc/components/RequestsGraph.tsx

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
"use client";
22

3-
import { format } from "date-fns";
4-
import { shortenLargeNumber } from "thirdweb/utils";
3+
import { differenceInCalendarDays, format } from "date-fns";
4+
import { useMemo } from "react";
55
import type { RpcUsageTypeStats } from "@/api/analytics";
6-
import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";
6+
import {
7+
ThirdwebAreaChart,
8+
TotalValueChartHeader,
9+
} from "@/components/blocks/charts/area-chart";
10+
11+
export function RPCRequestsChartUI(props: {
12+
data: RpcUsageTypeStats[];
13+
viewMoreLink: string | undefined;
14+
}) {
15+
const total = useMemo(() => {
16+
return props.data.reduce((acc, curr) => acc + curr.count, 0);
17+
}, [props.data]);
18+
19+
const showBreakdownByHour = useMemo(() => {
20+
if (props.data.length === 0) return true;
21+
const firstData = props.data[0];
22+
const lastData = props.data[props.data.length - 1];
23+
if (!firstData || !lastData) return true;
24+
const firstDate = new Date(firstData.date);
25+
const lastDate = new Date(lastData.date);
26+
return Math.abs(differenceInCalendarDays(lastDate, firstDate)) < 2;
27+
}, [props.data]);
728

8-
export function RequestsGraph(props: { data: RpcUsageTypeStats[] }) {
929
return (
1030
<ThirdwebAreaChart
11-
chartClassName="aspect-[1.5] lg:aspect-[4]"
31+
chartClassName="aspect-auto h-[275px]"
1232
config={{
1333
requests: {
1434
color: "hsl(var(--chart-1))",
@@ -32,21 +52,33 @@ export function RequestsGraph(props: { data: RpcUsageTypeStats[] }) {
3252
},
3353
[] as { requests: number; time: string }[],
3454
)}
35-
header={{
36-
title: "RPC Requests",
37-
titleClassName: "tracking-tight font-semibold text-lg",
38-
}}
55+
customHeader={
56+
<TotalValueChartHeader
57+
total={total}
58+
isPending={false}
59+
title="RPC Requests"
60+
viewMoreLink={props.viewMoreLink}
61+
/>
62+
}
3963
hideLabel={false}
4064
isPending={false}
4165
toolTipLabelFormatter={(label) => {
42-
return format(label, "MMM dd, HH:mm");
66+
if (showBreakdownByHour) {
67+
return format(label, "MMM dd, HH:mm");
68+
}
69+
return format(label, "MMM dd");
4370
}}
4471
toolTipValueFormatter={(value) => {
45-
return shortenLargeNumber(value as number);
72+
return compactNumberFormatter.format(value as number);
4673
}}
4774
xAxis={{
48-
sameDay: true,
75+
showHour: showBreakdownByHour,
4976
}}
5077
/>
5178
);
5279
}
80+
81+
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
82+
notation: "compact",
83+
maximumFractionDigits: 2,
84+
});

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/gateway/rpc/components/RpcAnalytics.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { StatCard } from "@/components/analytics/stat";
88
import { Skeleton } from "@/components/ui/skeleton";
99
import { RpcMethodBarChartCardAsync } from "../../../components/RpcMethodBarChartCard";
1010
import { TopRPCMethodsTable } from "./MethodsTable";
11-
import { RequestsGraph } from "./RequestsGraph";
11+
import { RPCRequestsChartUI } from "./RequestsGraph";
1212
import { RpcAnalyticsFilter } from "./RpcAnalyticsFilter";
1313
import { RpcFTUX } from "./RpcFtux";
1414

@@ -23,7 +23,7 @@ export async function RPCAnalytics(props: {
2323
}) {
2424
const { projectId, teamId, range, interval, authToken } = props;
2525

26-
// TODO: add requests by status code, but currently not performant enough
26+
// TODO: add requests by status code filter, but currently not performant enough
2727
const allRequestsByUsageTypePromise = getRpcUsageByType(
2828
{
2929
from: range.from,
@@ -98,7 +98,7 @@ export async function RPCAnalytics(props: {
9898
value={totalRequests}
9999
/>
100100
</div>
101-
<RequestsGraph data={usageData} />
101+
<RPCRequestsChartUI data={usageData} viewMoreLink={undefined} />
102102
<TopRPCMethodsTable
103103
client={props.client}
104104
data={evmMethodsData || []}

0 commit comments

Comments
 (0)