Skip to content

Commit 4b5661b

Browse files
committed
[TOOL-3007] Dashboard: Replace chainsaw with insight (#5926)
<!-- start pr-codex --> ## PR-Codex overview This PR focuses on enhancing the analytics functionality within the `dashboard` application by introducing new utility functions, updating existing API calls, and improving the data handling for contract analytics. ### Detailed summary - Added export of `toEventSelector` from `thirdweb/utils`. - Introduced `INSIGHT_SERVICE_API_KEY` in environment constants. - Modified `ContractAnalyticsPageClient` to accept new props for function and event selectors. - Created new analytics functions for total contract events, unique wallets, and transactions. - Updated `isAnalyticsSupportedForChain` to use `INSIGHT_SERVICE_API_KEY`. - Refactored analytics components to use new hooks for contract events and functions. - Improved data fetching and error handling in analytics hooks. - Enhanced UI components for displaying analytics data with loading states. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 42a313f commit 4b5661b

File tree

22 files changed

+1050
-655
lines changed

22 files changed

+1050
-655
lines changed

.changeset/blue-ears-sit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Export `toEventSelector` utility function from "thirdweb/utils"

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

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import {
88
ChartTooltip,
99
ChartTooltipContent,
1010
} from "@/components/ui/chart";
11+
import { formatDate } from "date-fns";
1112
import { useMemo } from "react";
1213
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
14+
import {
15+
EmptyChartState,
16+
LoadingChartState,
17+
} from "../../../../components/analytics/empty-chart-state";
1318

1419
type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
1520
// chart config
@@ -19,71 +24,78 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
1924

2025
// chart className
2126
chartClassName?: string;
27+
isPending: boolean;
2228
};
2329

2430
export function ThirdwebAreaChart<TConfig extends ChartConfig>(
2531
props: ThirdwebAreaChartProps<TConfig>,
2632
) {
2733
const configKeys = useMemo(() => Object.keys(props.config), [props.config]);
2834
return (
29-
<div className="rounded-lg border border-border bg-muted/50 px-4 pt-10 pb-4">
35+
<div className="rounded-lg border border-border px-4 pt-10 pb-4">
3036
<ChartContainer config={props.config} className={props.chartClassName}>
31-
<AreaChart
32-
accessibilityLayer
33-
data={props.data}
34-
margin={{
35-
left: 12,
36-
right: 12,
37-
}}
38-
>
39-
<CartesianGrid vertical={false} />
40-
<XAxis
41-
dataKey="time"
42-
tickLine={false}
43-
axisLine={false}
44-
tickMargin={20}
45-
tickFormatter={(value) => new Date(value).toLocaleDateString()}
46-
/>
47-
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
48-
<defs>
37+
{props.isPending ? (
38+
<LoadingChartState />
39+
) : props.data.length === 0 ? (
40+
<EmptyChartState />
41+
) : (
42+
<AreaChart
43+
accessibilityLayer
44+
data={props.data}
45+
margin={{
46+
left: 12,
47+
right: 12,
48+
}}
49+
>
50+
<CartesianGrid vertical={false} />
51+
<XAxis
52+
dataKey="time"
53+
tickLine={false}
54+
axisLine={false}
55+
tickMargin={20}
56+
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
57+
/>
58+
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
59+
<defs>
60+
{configKeys.map((key) => (
61+
<linearGradient
62+
key={key}
63+
id={`fill_${key}`}
64+
x1="0"
65+
y1="0"
66+
x2="0"
67+
y2="1"
68+
>
69+
<stop
70+
offset="5%"
71+
stopColor={`var(--color-${key})`}
72+
stopOpacity={0.8}
73+
/>
74+
<stop
75+
offset="95%"
76+
stopColor={`var(--color-${key})`}
77+
stopOpacity={0.1}
78+
/>
79+
</linearGradient>
80+
))}
81+
</defs>
4982
{configKeys.map((key) => (
50-
<linearGradient
83+
<Area
5184
key={key}
52-
id={`fill_${key}`}
53-
x1="0"
54-
y1="0"
55-
x2="0"
56-
y2="1"
57-
>
58-
<stop
59-
offset="5%"
60-
stopColor={`var(--color-${key})`}
61-
stopOpacity={0.8}
62-
/>
63-
<stop
64-
offset="95%"
65-
stopColor={`var(--color-${key})`}
66-
stopOpacity={0.1}
67-
/>
68-
</linearGradient>
85+
dataKey={key}
86+
type="natural"
87+
fill={`url(#fill_${key})`}
88+
fillOpacity={0.4}
89+
stroke={`var(--color-${key})`}
90+
stackId="a"
91+
/>
6992
))}
70-
</defs>
71-
{configKeys.map((key) => (
72-
<Area
73-
key={key}
74-
dataKey={key}
75-
type="natural"
76-
fill={`url(#fill_${key})`}
77-
fillOpacity={0.4}
78-
stroke={`var(--color-${key})`}
79-
stackId="a"
80-
/>
81-
))}
8293

83-
{props.showLegend && (
84-
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
85-
)}
86-
</AreaChart>
94+
{props.showLegend && (
95+
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
96+
)}
97+
</AreaChart>
98+
)}
8799
</ChartContainer>
88100
</div>
89101
);

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

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ import {
1717
ChartTooltip,
1818
ChartTooltipContent,
1919
} from "@/components/ui/chart";
20+
import { formatDate } from "date-fns";
2021
import { useMemo } from "react";
22+
import {
23+
EmptyChartState,
24+
LoadingChartState,
25+
} from "../../../../components/analytics/empty-chart-state";
2126

2227
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
2328
// metadata
@@ -30,6 +35,7 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
3035
variant?: "stacked" | "grouped";
3136
// chart className
3237
chartClassName?: string;
38+
isPending: boolean;
3339
};
3440

3541
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -42,48 +48,54 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
4248
return (
4349
<Card>
4450
<CardHeader>
45-
<CardTitle>{props.title}</CardTitle>
51+
<CardTitle className="mb-2">{props.title}</CardTitle>
4652
{props.description && (
4753
<CardDescription>{props.description}</CardDescription>
4854
)}
4955
</CardHeader>
5056
<CardContent>
5157
<ChartContainer config={props.config} className={props.chartClassName}>
52-
<BarChart accessibilityLayer data={props.data}>
53-
<CartesianGrid vertical={false} />
54-
<XAxis
55-
dataKey="time"
56-
tickLine={false}
57-
axisLine={false}
58-
tickMargin={10}
59-
tickFormatter={(value) => new Date(value).toLocaleDateString()}
60-
/>
61-
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
62-
{props.showLegend && (
63-
<ChartLegend content={<ChartLegendContent />} />
64-
)}
65-
{configKeys.map((key, idx) => (
66-
<Bar
67-
key={key}
68-
dataKey={key}
69-
// if stacked then they should all be the same stackId
70-
// if grouped then they should all be unique stackId (so the key works great)
71-
stackId={variant === "stacked" ? "a" : key}
72-
fill={`var(--color-${key})`}
73-
// if stacked then we need to figure out the radius based on the index in the array
74-
// if grouped then we can just use the same radius for all
75-
radius={
76-
variant === "stacked"
77-
? idx === 0
78-
? [0, 0, 4, 4]
79-
: idx === configKeys.length - 1
80-
? [4, 4, 0, 0]
81-
: [0, 0, 0, 0]
82-
: [4, 4, 4, 4]
83-
}
58+
{props.isPending ? (
59+
<LoadingChartState />
60+
) : props.data.length === 0 ? (
61+
<EmptyChartState />
62+
) : (
63+
<BarChart accessibilityLayer data={props.data}>
64+
<CartesianGrid vertical={false} />
65+
<XAxis
66+
dataKey="time"
67+
tickLine={false}
68+
axisLine={false}
69+
tickMargin={10}
70+
tickFormatter={(value) => formatDate(new Date(value), "MMM d")}
8471
/>
85-
))}
86-
</BarChart>
72+
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
73+
{props.showLegend && (
74+
<ChartLegend content={<ChartLegendContent />} />
75+
)}
76+
{configKeys.map((key, idx) => (
77+
<Bar
78+
key={key}
79+
dataKey={key}
80+
// if stacked then they should all be the same stackId
81+
// if grouped then they should all be unique stackId (so the key works great)
82+
stackId={variant === "stacked" ? "a" : key}
83+
fill={`var(--color-${key})`}
84+
// if stacked then we need to figure out the radius based on the index in the array
85+
// if grouped then we can just use the same radius for all
86+
radius={
87+
variant === "stacked"
88+
? idx === 0
89+
? [0, 0, 4, 4]
90+
: idx === configKeys.length - 1
91+
? [4, 4, 0, 0]
92+
: [0, 0, 0, 0]
93+
: [4, 4, 4, 4]
94+
}
95+
/>
96+
))}
97+
</BarChart>
98+
)}
8799
</ChartContainer>
88100
</CardContent>
89101
</Card>

apps/dashboard/src/@/constants/env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ export const BASE_URL = isProd
4141
: "http://localhost:3000") || "https://thirdweb-dev.com";
4242

4343
export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL;
44+
45+
export const INSIGHT_SERVICE_API_KEY =
46+
process.env.INSIGHT_SERVICE_API_KEY || "";
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
import { isProd } from "@/constants/env";
1+
import { INSIGHT_SERVICE_API_KEY } from "@/constants/env";
2+
import { getVercelEnv } from "lib/vercel-utils";
3+
4+
const thirdwebDomain =
5+
getVercelEnv() !== "production" ? "thirdweb-dev" : "thirdweb";
26

37
export async function isAnalyticsSupportedForChain(
48
chainId: number,
59
): Promise<boolean> {
610
try {
7-
if (!process.env.CHAINSAW_API_KEY) {
8-
throw new Error("Missing CHAINSAW_API_KEY env var");
9-
}
10-
1111
const res = await fetch(
12-
`https://chainsaw.${isProd ? "thirdweb" : "thirdweb-dev"}.com/service/chains/${chainId}`,
12+
`https://insight.${thirdwebDomain}.com/service/chains/${chainId}`,
1313
{
14-
method: "GET",
1514
headers: {
16-
"content-type": "application/json",
17-
// pass the shared secret
18-
"x-service-api-key": process.env.CHAINSAW_API_KEY || "",
15+
// service api key required - because this is endpoint is internal
16+
"x-service-api-key": INSIGHT_SERVICE_API_KEY,
1917
},
2018
},
2119
);
2220

2321
if (!res.ok) {
24-
// assume not supported if we get a non-200 response
2522
return false;
2623
}
2724

28-
const { data } = await res.json();
29-
return data;
25+
const json = (await res.json()) as { data: boolean };
26+
27+
return json.data;
3028
} catch (e) {
31-
console.error("Error checking if analytics is supported for chain", e);
29+
console.error(`Error checking analytics support for chain ${chainId}`);
30+
console.error(e);
3231
}
33-
3432
return false;
3533
}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.client.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ContractAnalyticsPage } from "./ContractAnalyticsPage";
88

99
export function ContractAnalyticsPageClient(props: {
1010
contract: ThirdwebContract;
11+
writeFnSelectorToNameRecord: Record<string, string>;
12+
eventSelectorToNameRecord: Record<string, string>;
1113
}) {
1214
const metadataQuery = useContractPageMetadata(props.contract);
1315

@@ -23,5 +25,11 @@ export function ContractAnalyticsPageClient(props: {
2325
return <RedirectToContractOverview contract={props.contract} />;
2426
}
2527

26-
return <ContractAnalyticsPage contract={props.contract} />;
28+
return (
29+
<ContractAnalyticsPage
30+
contract={props.contract}
31+
writeFnSelectorToNameRecord={props.writeFnSelectorToNameRecord}
32+
eventSelectorToNameRecord={props.eventSelectorToNameRecord}
33+
/>
34+
);
2735
}

0 commit comments

Comments
 (0)