Skip to content

Commit 9f79277

Browse files
[Dashboard] perf: Optimize chain and contract data processing (#6035)
1 parent 6e0fd20 commit 9f79277

File tree

6 files changed

+186
-139
lines changed

6 files changed

+186
-139
lines changed

apps/dashboard/src/app/team/[team_slug]/(team)/_components/TransactionsCard.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ export async function TransactionsChartCardUI({
2323
title?: string;
2424
description?: string;
2525
}) {
26+
const uniqueChainIds = [
27+
...new Set(data.map((item) => item.chainId).filter(Boolean)),
28+
];
2629
const chains = await Promise.all(
27-
data.map(
28-
(item) =>
29-
// eslint-disable-next-line no-restricted-syntax
30-
item.chainId && getChainMetadata(defineChain(Number(item.chainId))),
30+
uniqueChainIds.map((chainId) =>
31+
// eslint-disable-next-line no-restricted-syntax
32+
getChainMetadata(defineChain(Number(chainId))).catch(() => undefined),
3133
),
3234
).then((chains) => chains.filter((c) => c) as ChainMetadata[]);
3335

apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { TotalSponsoredChartCardUI } from "../../_components/TotalSponsoredCard"
3535
import { TransactionsChartCardUI } from "../../_components/TransactionsCard";
3636

3737
// revalidate every 5 minutes
38-
export const revalidate = 300;
38+
export const maxDuration = 300;
3939

4040
type SearchParams = {
4141
usersChart?: string;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { defineChain, getContract } from "thirdweb";
2+
import { getChainMetadata } from "thirdweb/chains";
3+
import { TransactionsChartCardUI } from "../../../(team)/_components/TransactionsCard";
4+
import { getThirdwebClient } from "../../../../../../@/constants/thirdweb.server";
5+
import { fetchDashboardContractMetadata } from "../../../../../../@3rdweb-sdk/react/hooks/useDashboardContractMetadata";
6+
import type { TransactionStats } from "../../../../../../types/analytics";
7+
import { PieChartCard } from "../../../../components/Analytics/PieChartCard";
8+
9+
export function TransactionsChartsUI({
10+
data,
11+
aggregatedData,
12+
searchParams,
13+
}: {
14+
data: TransactionStats[];
15+
aggregatedData: TransactionStats[];
16+
searchParams: { [key: string]: string | string[] | undefined };
17+
}) {
18+
return (
19+
<>
20+
<TransactionsChartCardUI
21+
searchParams={searchParams}
22+
data={data}
23+
aggregatedData={aggregatedData}
24+
className="max-md:rounded-none max-md:border-r-0 max-md:border-l-0"
25+
/>
26+
<div className="grid gap-6 max-md:px-6 md:grid-cols-2">
27+
<ChainDistributionCard data={aggregatedData} />
28+
<ContractDistributionCard data={aggregatedData} />
29+
</div>
30+
</>
31+
);
32+
}
33+
34+
async function ChainDistributionCard({ data }: { data: TransactionStats[] }) {
35+
const reducedData = await Promise.all(
36+
Object.entries(
37+
data.reduce(
38+
(acc, curr) => {
39+
acc[curr.chainId] = (acc[curr.chainId] || 0) + curr.count;
40+
return acc;
41+
},
42+
{} as Record<string, number>,
43+
),
44+
)
45+
.sort((a, b) => b[1] - a[1])
46+
.slice(0, 10) // only top ten
47+
.map(async ([key, value]) => {
48+
// eslint-disable-next-line no-restricted-syntax
49+
const chain = defineChain(Number(key));
50+
const chainMeta = await getChainMetadata(chain).catch(() => undefined);
51+
return {
52+
label: chainMeta?.slug || chain.id.toString(),
53+
value,
54+
link: `/${chain.id}`,
55+
};
56+
}),
57+
);
58+
59+
const aggregateFn = () => new Set(data.map((d) => `${d.chainId}`)).size;
60+
61+
return (
62+
<PieChartCard title="Chains" data={reducedData} aggregateFn={aggregateFn} />
63+
);
64+
}
65+
66+
async function ContractDistributionCard({
67+
data,
68+
}: { data: TransactionStats[] }) {
69+
const reducedData = await Promise.all(
70+
Object.entries(
71+
data
72+
.filter((d) => d.contractAddress)
73+
.reduce(
74+
(acc, curr) => {
75+
acc[`${curr.chainId}:${curr.contractAddress}`] =
76+
(acc[`${curr.chainId}:${curr.contractAddress}`] || 0) +
77+
curr.count;
78+
return acc;
79+
},
80+
{} as Record<string, number>,
81+
),
82+
)
83+
.sort((a, b) => b[1] - a[1])
84+
.slice(0, 10) // only top ten
85+
.map(async ([key, value]) => {
86+
const [chainId, contractAddress] = key.split(":");
87+
// eslint-disable-next-line no-restricted-syntax
88+
const chain = defineChain(Number(chainId));
89+
const chainMeta = await getChainMetadata(chain).catch(() => undefined);
90+
const contractData = await fetchDashboardContractMetadata(
91+
getContract({
92+
chain,
93+
address: contractAddress as string, // we filter above
94+
client: getThirdwebClient(),
95+
}),
96+
).catch(() => undefined);
97+
return {
98+
label: `${contractData?.name} (${chainMeta?.slug || chainId})`,
99+
link: `/${chainId}/${contractAddress}`,
100+
value,
101+
};
102+
}),
103+
);
104+
105+
const aggregateFn = () =>
106+
new Set(
107+
data
108+
.filter((d) => d.contractAddress)
109+
.map((d) => `${d.chainId}:${d.contractAddress}`),
110+
).size;
111+
112+
return (
113+
<PieChartCard
114+
title="Contracts"
115+
data={reducedData}
116+
aggregateFn={aggregateFn}
117+
/>
118+
);
119+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { LoadingChartState } from "components/analytics/empty-chart-state";
2+
import { Suspense } from "react";
3+
import type { AnalyticsQueryParams } from "types/analytics";
4+
import { getClientTransactions } from "../../../../../../@/api/analytics";
5+
import { TransactionsChartsUI } from "./TransactionCharts";
6+
7+
export function TransactionsCharts(
8+
props: AnalyticsQueryParams & {
9+
searchParams: { [key: string]: string | string[] | undefined };
10+
},
11+
) {
12+
return (
13+
// TODO: Add better LoadingChartState
14+
<Suspense
15+
fallback={
16+
<div className="h-[400px]">
17+
<LoadingChartState />
18+
</div>
19+
}
20+
>
21+
<TransactionsChartCardAsync {...props} />
22+
</Suspense>
23+
);
24+
}
25+
26+
async function TransactionsChartCardAsync(
27+
props: AnalyticsQueryParams & {
28+
searchParams: { [key: string]: string | string[] | undefined };
29+
},
30+
) {
31+
const [data, aggregatedData] = await Promise.all([
32+
getClientTransactions(props),
33+
getClientTransactions({
34+
...props,
35+
period: "all",
36+
}),
37+
]);
38+
39+
if (!aggregatedData.length) {
40+
return null;
41+
}
42+
43+
return (
44+
<TransactionsChartsUI
45+
data={data}
46+
aggregatedData={aggregatedData}
47+
searchParams={props.searchParams}
48+
/>
49+
);
50+
}

0 commit comments

Comments
 (0)