Skip to content

Commit 8f44105

Browse files
chore: drop event field and clean charts (#712)
1 parent 89d4a98 commit 8f44105

File tree

13 files changed

+435
-132
lines changed

13 files changed

+435
-132
lines changed

apps/cli/src/utils/analytics.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export async function trackProjectCreation(config: ProjectConfig, disableAnalyti
3030

3131
try {
3232
await sendConvexEvent({
33-
event: "project_created",
3433
...safeConfig,
3534
cli_version: getLatestCLIVersion(),
3635
node_version: typeof process !== "undefined" ? process.version : "",

apps/web/src/app/(home)/analytics/_components/analytics-page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { Loader2 } from "lucide-react";
34
import Footer from "../../_components/footer";
45
import { AnalyticsHeader } from "./analytics-header";
56
import { DevToolsSection } from "./dev-environment-charts";
@@ -13,6 +14,7 @@ export default function AnalyticsPage({
1314
range,
1415
onRangeChange,
1516
legacy,
17+
isLoading,
1618
}: {
1719
data: AggregatedAnalyticsData;
1820
range: "all" | "30d" | "7d" | "1d";
@@ -23,6 +25,7 @@ export default function AnalyticsPage({
2325
lastUpdatedIso: string;
2426
source: string;
2527
};
28+
isLoading?: boolean;
2629
}) {
2730
return (
2831
<div className="mx-auto min-h-svh">
@@ -33,7 +36,7 @@ export default function AnalyticsPage({
3336
legacy={legacy}
3437
/>
3538

36-
<RangeSelector value={range} onChange={onRangeChange} />
39+
<RangeSelector value={range} onChange={onRangeChange} isLoading={isLoading} />
3740

3841
<MetricsCards data={data} />
3942

@@ -51,9 +54,11 @@ export default function AnalyticsPage({
5154
function RangeSelector({
5255
value,
5356
onChange,
57+
isLoading,
5458
}: {
5559
value: "all" | "30d" | "7d" | "1d";
5660
onChange: (val: "all" | "30d" | "7d" | "1d") => void;
61+
isLoading?: boolean;
5762
}) {
5863
const options: Array<{ value: "all" | "30d" | "7d" | "1d"; label: string }> = [
5964
{ value: "all", label: "All time" },
@@ -63,15 +68,16 @@ function RangeSelector({
6368
];
6469

6570
return (
66-
<div className="flex flex-wrap items-center gap-2">
71+
<div className="flex flex-wrap items-center gap-3">
6772
<span className="text-muted-foreground text-sm">Range:</span>
6873
<div className="flex flex-wrap gap-2">
6974
{options.map((opt) => (
7075
<button
7176
key={opt.value}
7277
type="button"
7378
onClick={() => onChange(opt.value)}
74-
className={`rounded border px-3 py-1 text-sm transition-colors ${
79+
disabled={isLoading}
80+
className={`rounded border px-3 py-1 text-sm transition-colors disabled:opacity-50 ${
7581
value === opt.value
7682
? "border-primary bg-primary/10 text-primary"
7783
: "border-border text-foreground hover:bg-muted/60"
@@ -81,6 +87,7 @@ function RangeSelector({
8187
</button>
8288
))}
8389
</div>
90+
{isLoading && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
8491
</div>
8592
);
8693
}

apps/web/src/app/(home)/analytics/_components/dev-environment-charts.tsx

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,56 @@
1-
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, XAxis, YAxis } from "recharts";
2-
import {
3-
ChartContainer,
4-
ChartLegend,
5-
ChartLegendContent,
6-
ChartTooltip,
7-
ChartTooltipContent,
8-
} from "@/components/ui/chart";
1+
import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, Tooltip, XAxis, YAxis } from "recharts";
2+
import { ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
93
import type { AggregatedAnalyticsData, Distribution, VersionDistribution } from "./types";
10-
import { chartConfig, getColor } from "./types";
4+
import { CHART_COLORS, chartConfig, getColor, truncateLabel } from "./types";
5+
6+
function CustomYAxisTick({
7+
x,
8+
y,
9+
payload,
10+
maxChars = 10,
11+
}: {
12+
x: number;
13+
y: number;
14+
payload: { value: string };
15+
maxChars?: number;
16+
}) {
17+
const label = truncateLabel(String(payload.value), maxChars);
18+
return (
19+
<g transform={`translate(${x},${y})`}>
20+
<text x={-4} y={0} dy={4} textAnchor="end" fill="hsl(var(--muted-foreground))" fontSize={11}>
21+
{label}
22+
</text>
23+
</g>
24+
);
25+
}
26+
27+
function CustomXAxisTick({
28+
x,
29+
y,
30+
payload,
31+
maxChars = 7,
32+
}: {
33+
x: number;
34+
y: number;
35+
payload: { value: string };
36+
maxChars?: number;
37+
}) {
38+
const label = truncateLabel(String(payload.value), maxChars);
39+
return (
40+
<g transform={`translate(${x},${y})`}>
41+
<text
42+
x={0}
43+
y={0}
44+
dy={12}
45+
textAnchor="middle"
46+
fill="hsl(var(--muted-foreground))"
47+
fontSize={10}
48+
>
49+
{label}
50+
</text>
51+
</g>
52+
);
53+
}
1154

1255
function ChartCard({
1356
title,
@@ -32,22 +75,39 @@ function ChartCard({
3275
);
3376
}
3477

78+
function CustomTooltip({
79+
active,
80+
payload,
81+
}: {
82+
active?: boolean;
83+
payload?: Array<{ value: number; payload: { name: string } }>;
84+
}) {
85+
if (!active || !payload?.length) return null;
86+
const item = payload[0];
87+
return (
88+
<div className="rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg">
89+
<p className="font-medium">{item.payload.name}</p>
90+
<p className="text-muted-foreground">{item.value.toLocaleString()} projects</p>
91+
</div>
92+
);
93+
}
94+
3595
function HorizontalBarChart({ data, height = 280 }: { data: Distribution; height?: number }) {
3696
return (
3797
<ChartContainer config={chartConfig} className="w-full" style={{ height }}>
38-
<BarChart data={data} layout="vertical" margin={{ left: 0 }}>
39-
<CartesianGrid horizontal={false} className="stroke-border" />
40-
<XAxis type="number" tickLine={false} axisLine={false} className="text-xs" />
98+
<BarChart data={data} layout="vertical" margin={{ left: 4, right: 12, top: 4, bottom: 4 }}>
99+
<CartesianGrid horizontal={false} className="stroke-border/40" />
100+
<XAxis type="number" tickLine={false} axisLine={false} tick={{ fontSize: 10 }} />
41101
<YAxis
42102
dataKey="name"
43103
type="category"
44104
tickLine={false}
45105
axisLine={false}
46-
width={80}
47-
className="text-xs"
106+
width={75}
107+
tick={(props) => <CustomYAxisTick {...props} maxChars={10} />}
48108
/>
49-
<ChartTooltip content={<ChartTooltipContent />} />
50-
<Bar dataKey="value" radius={4}>
109+
<Tooltip content={<CustomTooltip />} cursor={{ fill: "hsl(var(--muted))", opacity: 0.3 }} />
110+
<Bar dataKey="value" radius={3}>
51111
{data.map((entry, i) => (
52112
<Cell key={entry.name} fill={getColor(i)} />
53113
))}
@@ -60,36 +120,78 @@ function HorizontalBarChart({ data, height = 280 }: { data: Distribution; height
60120
function VersionBarChart({ data, height = 280 }: { data: VersionDistribution; height?: number }) {
61121
return (
62122
<ChartContainer config={chartConfig} className="w-full" style={{ height }}>
63-
<BarChart data={data}>
64-
<CartesianGrid vertical={false} className="stroke-border" />
65-
<XAxis dataKey="version" tickLine={false} axisLine={false} className="text-xs" />
66-
<YAxis tickLine={false} axisLine={false} className="text-xs" />
67-
<ChartTooltip content={<ChartTooltipContent />} />
68-
<Bar dataKey="count" radius={4} fill="hsl(var(--chart-1))" />
123+
<BarChart data={data} margin={{ left: -10, right: 8, top: 8, bottom: 4 }}>
124+
<CartesianGrid vertical={false} className="stroke-border/40" />
125+
<XAxis
126+
dataKey="version"
127+
tickLine={false}
128+
axisLine={false}
129+
tick={(props) => <CustomXAxisTick {...props} maxChars={7} />}
130+
interval={0}
131+
/>
132+
<YAxis tickLine={false} axisLine={false} tick={{ fontSize: 10 }} width={35} />
133+
<Tooltip
134+
content={({ active, payload }) => {
135+
if (!active || !payload?.length) return null;
136+
const item = payload[0].payload as { version: string; count: number };
137+
return (
138+
<div className="rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg">
139+
<p className="font-medium">{item.version}</p>
140+
<p className="text-muted-foreground">{item.count.toLocaleString()} projects</p>
141+
</div>
142+
);
143+
}}
144+
cursor={{ fill: "hsl(var(--muted))", opacity: 0.3 }}
145+
/>
146+
<Bar dataKey="count" radius={3}>
147+
{data.map((_, i) => (
148+
<Cell key={i} fill={CHART_COLORS[4]} />
149+
))}
150+
</Bar>
69151
</BarChart>
70152
</ChartContainer>
71153
);
72154
}
73155

74156
function PieChartComponent({ data }: { data: Distribution }) {
157+
const total = data.reduce((sum, d) => sum + d.value, 0);
158+
75159
return (
76160
<ChartContainer config={chartConfig} className="h-[280px] w-full">
77161
<PieChart>
78-
<ChartTooltip content={<ChartTooltipContent nameKey="name" />} />
162+
<Tooltip
163+
content={({ active, payload }) => {
164+
if (!active || !payload?.length) return null;
165+
const item = payload[0].payload as { name: string; value: number };
166+
const percent = ((item.value / total) * 100).toFixed(1);
167+
return (
168+
<div className="rounded border border-border/50 bg-background px-3 py-2 text-xs shadow-lg">
169+
<p className="font-medium">{item.name}</p>
170+
<p className="text-muted-foreground">
171+
{item.value.toLocaleString()} ({percent}%)
172+
</p>
173+
</div>
174+
);
175+
}}
176+
/>
79177
<Pie
80178
data={data}
81179
cx="50%"
82-
cy="50%"
83-
outerRadius={80}
180+
cy="45%"
181+
outerRadius={65}
182+
innerRadius={35}
84183
dataKey="value"
85-
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
86-
labelLine={false}
184+
paddingAngle={2}
87185
>
88186
{data.map((entry, i) => (
89187
<Cell key={entry.name} fill={getColor(i)} />
90188
))}
91189
</Pie>
92-
<ChartLegend content={<ChartLegendContent />} />
190+
<ChartLegend
191+
content={<ChartLegendContent nameKey="name" />}
192+
formatter={(value) => truncateLabel(String(value), 8)}
193+
wrapperStyle={{ fontSize: 11 }}
194+
/>
93195
</PieChart>
94196
</ChartContainer>
95197
);

0 commit comments

Comments
 (0)