Skip to content

Commit

Permalink
[TOOL-3007] Dashboard: Replace chainsaw with insight (#5926)
Browse files Browse the repository at this point in the history
<!-- 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 -->
  • Loading branch information
MananTank committed Jan 11, 2025
1 parent 42a313f commit 4b5661b
Show file tree
Hide file tree
Showing 22 changed files with 1,050 additions and 655 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-ears-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---

Export `toEventSelector` utility function from "thirdweb/utils"
118 changes: 65 additions & 53 deletions apps/dashboard/src/@/components/blocks/charts/area-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import {
EmptyChartState,
LoadingChartState,
} from "../../../../components/analytics/empty-chart-state";

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

// chart className
chartClassName?: string;
isPending: boolean;
};

export function ThirdwebAreaChart<TConfig extends ChartConfig>(
props: ThirdwebAreaChartProps<TConfig>,
) {
const configKeys = useMemo(() => Object.keys(props.config), [props.config]);
return (
<div className="rounded-lg border border-border bg-muted/50 px-4 pt-10 pb-4">
<div className="rounded-lg border border-border px-4 pt-10 pb-4">
<ChartContainer config={props.config} className={props.chartClassName}>
<AreaChart
accessibilityLayer
data={props.data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => new Date(value).toLocaleDateString()}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<defs>
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart
accessibilityLayer
data={props.data}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<defs>
{configKeys.map((key) => (
<linearGradient
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
))}
</defs>
{configKeys.map((key) => (
<linearGradient
<Area
key={key}
id={`fill_${key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor={`var(--color-${key})`}
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor={`var(--color-${key})`}
stopOpacity={0.1}
/>
</linearGradient>
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
/>
))}
</defs>
{configKeys.map((key) => (
<Area
key={key}
dataKey={key}
type="natural"
fill={`url(#fill_${key})`}
fillOpacity={0.4}
stroke={`var(--color-${key})`}
stackId="a"
/>
))}

{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
)}
</AreaChart>
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} className="pt-8" />
)}
</AreaChart>
)}
</ChartContainer>
</div>
);
Expand Down
82 changes: 47 additions & 35 deletions apps/dashboard/src/@/components/blocks/charts/bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import {
EmptyChartState,
LoadingChartState,
} from "../../../../components/analytics/empty-chart-state";

type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
// metadata
Expand All @@ -30,6 +35,7 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
variant?: "stacked" | "grouped";
// chart className
chartClassName?: string;
isPending: boolean;
};

export function ThirdwebBarChart<TConfig extends ChartConfig>(
Expand All @@ -42,48 +48,54 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
return (
<Card>
<CardHeader>
<CardTitle>{props.title}</CardTitle>
<CardTitle className="mb-2">{props.title}</CardTitle>
{props.description && (
<CardDescription>{props.description}</CardDescription>
)}
</CardHeader>
<CardContent>
<ChartContainer config={props.config} className={props.chartClassName}>
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={10}
tickFormatter={(value) => new Date(value).toLocaleDateString()}
/>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} />
)}
{configKeys.map((key, idx) => (
<Bar
key={key}
dataKey={key}
// if stacked then they should all be the same stackId
// if grouped then they should all be unique stackId (so the key works great)
stackId={variant === "stacked" ? "a" : key}
fill={`var(--color-${key})`}
// if stacked then we need to figure out the radius based on the index in the array
// if grouped then we can just use the same radius for all
radius={
variant === "stacked"
? idx === 0
? [0, 0, 4, 4]
: idx === configKeys.length - 1
? [4, 4, 0, 0]
: [0, 0, 0, 0]
: [4, 4, 4, 4]
}
{props.isPending ? (
<LoadingChartState />
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<BarChart accessibilityLayer data={props.data}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={10}
tickFormatter={(value) => formatDate(new Date(value), "MMM d")}
/>
))}
</BarChart>
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
{props.showLegend && (
<ChartLegend content={<ChartLegendContent />} />
)}
{configKeys.map((key, idx) => (
<Bar
key={key}
dataKey={key}
// if stacked then they should all be the same stackId
// if grouped then they should all be unique stackId (so the key works great)
stackId={variant === "stacked" ? "a" : key}
fill={`var(--color-${key})`}
// if stacked then we need to figure out the radius based on the index in the array
// if grouped then we can just use the same radius for all
radius={
variant === "stacked"
? idx === 0
? [0, 0, 4, 4]
: idx === configKeys.length - 1
? [4, 4, 0, 0]
: [0, 0, 0, 0]
: [4, 4, 4, 4]
}
/>
))}
</BarChart>
)}
</ChartContainer>
</CardContent>
</Card>
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/src/@/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ export const BASE_URL = isProd
: "http://localhost:3000") || "https://thirdweb-dev.com";

export const NEXT_PUBLIC_NEBULA_URL = process.env.NEXT_PUBLIC_NEBULA_URL;

export const INSIGHT_SERVICE_API_KEY =
process.env.INSIGHT_SERVICE_API_KEY || "";
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import { isProd } from "@/constants/env";
import { INSIGHT_SERVICE_API_KEY } from "@/constants/env";
import { getVercelEnv } from "lib/vercel-utils";

const thirdwebDomain =
getVercelEnv() !== "production" ? "thirdweb-dev" : "thirdweb";

export async function isAnalyticsSupportedForChain(
chainId: number,
): Promise<boolean> {
try {
if (!process.env.CHAINSAW_API_KEY) {
throw new Error("Missing CHAINSAW_API_KEY env var");
}

const res = await fetch(
`https://chainsaw.${isProd ? "thirdweb" : "thirdweb-dev"}.com/service/chains/${chainId}`,
`https://insight.${thirdwebDomain}.com/service/chains/${chainId}`,
{
method: "GET",
headers: {
"content-type": "application/json",
// pass the shared secret
"x-service-api-key": process.env.CHAINSAW_API_KEY || "",
// service api key required - because this is endpoint is internal
"x-service-api-key": INSIGHT_SERVICE_API_KEY,
},
},
);

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

const { data } = await res.json();
return data;
const json = (await res.json()) as { data: boolean };

return json.data;
} catch (e) {
console.error("Error checking if analytics is supported for chain", e);
console.error(`Error checking analytics support for chain ${chainId}`);
console.error(e);
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ContractAnalyticsPage } from "./ContractAnalyticsPage";

export function ContractAnalyticsPageClient(props: {
contract: ThirdwebContract;
writeFnSelectorToNameRecord: Record<string, string>;
eventSelectorToNameRecord: Record<string, string>;
}) {
const metadataQuery = useContractPageMetadata(props.contract);

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

return <ContractAnalyticsPage contract={props.contract} />;
return (
<ContractAnalyticsPage
contract={props.contract}
writeFnSelectorToNameRecord={props.writeFnSelectorToNameRecord}
eventSelectorToNameRecord={props.eventSelectorToNameRecord}
/>
);
}
Loading

0 comments on commit 4b5661b

Please sign in to comment.