Skip to content

Commit 3490fe5

Browse files
committed
add dashboard
1 parent f3d5e66 commit 3490fe5

19 files changed

+646
-57
lines changed

apps/web/src/components/tools/auth0/dashboard.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useQueryState } from 'nuqs'
44
import { useEffect, useState, useCallback } from 'react'
55
import { pipe } from '@/lib/tinybird'
6-
import MetricCard from './metric'
6+
import MetricCard from '@/components/tools/shared/metric-card'
77
import { DauChart, DauDataPoint } from './dau-chart'
88
import { AuthMechChart, AuthMechDataPoint } from './auth-mech-chart'
99
import { DailySignupsChart, DailySignupsDataPoint } from './daily-signups-chart'
@@ -341,17 +341,17 @@ export default function Auth0Dashboard() {
341341
<MetricCard
342342
title="Monthly Sign Ups"
343343
value={summaryMetrics.monthly_signups}
344-
description="New users signed up in the last 30 days"
344+
isLoading={isLoading}
345345
/>
346346
<MetricCard
347347
title="Monthly Active Users"
348348
value={summaryMetrics.monthly_active_users}
349-
description="Users active in the last 30 days"
349+
isLoading={isLoading}
350350
/>
351351
<MetricCard
352352
title="New Signups Rate"
353353
value={`${summaryMetrics.conversion_rate}%`}
354-
description="New users compared to total users"
354+
isLoading={isLoading}
355355
/>
356356
</div>
357357
<Card>

apps/web/src/components/tools/clerk/dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useQueryState } from 'nuqs'
44
import { useEffect, useState } from 'react'
55
import { pipe } from '@/lib/tinybird'
6-
import MetricCard from '../auth0/metric'
6+
import MetricCard from '@/components/tools/shared/metric-card'
77
import { DauChart } from './dau-chart'
88
import { AuthMechChart } from './auth-mech-chart'
99

apps/web/src/components/tools/orb/dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useQueryState } from 'nuqs'
44
import { useEffect, useState } from 'react'
55
import { pipe } from '@/lib/tinybird'
6-
import MetricCard from '../auth0/metric'
6+
import MetricCard from '@/components/tools/shared/metric-card'
77
import { SubsChart } from './subs-chart'
88

99
interface SubsDataPoint {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
2+
3+
interface MetricCardProps {
4+
title: string
5+
value: string | number
6+
description?: string
7+
}
8+
9+
export default function MetricCard({ title, value, description }: MetricCardProps) {
10+
return (
11+
<Card>
12+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
13+
<CardTitle className="text-sm font-medium">{title}</CardTitle>
14+
</CardHeader>
15+
<CardContent>
16+
<div className="text-2xl font-bold">{value}</div>
17+
{description && (
18+
<p className="text-xs text-muted-foreground">{description}</p>
19+
)}
20+
</CardContent>
21+
</Card>
22+
)
23+
}

apps/web/src/components/tools/vercel/dashboard.tsx

Lines changed: 161 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,188 @@
11
"use client"
22

33
import { useQueryState } from 'nuqs'
4-
import { useEffect } from 'react'
4+
import { useEffect, useState } from 'react'
5+
import { addDays, format } from 'date-fns'
6+
import { DateRange } from 'react-day-picker'
7+
import { pipe } from '@/lib/tinybird'
8+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
9+
import { TimeRange } from '@/components/tools/shared/time-range'
10+
import MetricCard from '@/components/tools/shared/metric-card'
11+
import { DeploymentsChart } from './deployments-chart'
12+
import { DurationChart } from './duration-chart'
13+
import { ProjectsChart } from './projects-chart'
14+
import { GitAnalyticsChart } from './git-analytics-chart'
15+
import { InfrastructureChart } from './infrastructure-chart'
16+
17+
interface GitData {
18+
analytics: any[]
19+
distribution: any[]
20+
branches: any[]
21+
}
522

623
export default function VercelDashboard() {
724
const [token] = useQueryState('token')
25+
const [timeRange, setTimeRange] = useState('daily')
26+
const [dateRange, setDateRange] = useState<DateRange>({
27+
from: addDays(new Date(), -7),
28+
to: new Date()
29+
})
30+
31+
const [isLoading, setIsLoading] = useState(true)
32+
const [metrics, setMetrics] = useState<any>()
33+
const [deploymentsData, setDeploymentsData] = useState<any[]>([])
34+
const [durationData, setDurationData] = useState<any[]>([])
35+
const [projectsData, setProjectsData] = useState<any[]>([])
36+
const [gitData, setGitData] = useState<GitData>({
37+
analytics: [],
38+
distribution: [],
39+
branches: []
40+
})
41+
const [infraData, setInfraData] = useState<any[]>([])
842

943
useEffect(() => {
10-
async function fetchMetrics() {
44+
async function fetchData() {
1145
if (!token) return
1246

47+
const params = {
48+
time_range: timeRange,
49+
...(dateRange?.from && { date_from: format(dateRange.from, 'yyyy-MM-dd HH:mm:ss') }),
50+
...(dateRange?.to && { date_to: format(dateRange.to, 'yyyy-MM-dd 23:59:59') })
51+
}
52+
1353
try {
14-
const [] = await Promise.all([
54+
setIsLoading(true)
55+
const [
56+
metricsResult,
57+
deploymentsResult,
58+
durationResult,
59+
projectsResult,
60+
gitAnalyticsResult,
61+
gitDistributionResult,
62+
branchResult,
63+
infraResult
64+
] = await Promise.all([
65+
pipe(token, 'vercel_deployment_metrics', params),
66+
pipe(token, 'vercel_deployments_over_time', params),
67+
pipe(token, 'vercel_deployment_duration', params),
68+
pipe(token, 'vercel_project_stats', params),
69+
pipe(token, 'vercel_git_analytics', params),
70+
pipe(token, 'vercel_git_distribution', params),
71+
pipe(token, 'vercel_branch_distribution', params),
72+
pipe(token, 'vercel_infrastructure_stats', params)
1573
])
74+
75+
setMetrics(metricsResult?.data?.[0])
76+
setDeploymentsData(deploymentsResult?.data ?? [])
77+
setDurationData(durationResult?.data ?? [])
78+
setProjectsData(projectsResult?.data ?? [])
79+
setGitData({
80+
analytics: gitAnalyticsResult?.data ?? [],
81+
distribution: gitDistributionResult?.data ?? [],
82+
branches: branchResult?.data ?? []
83+
})
84+
setInfraData(infraResult?.data ?? [])
1685
} catch (error) {
17-
console.error('Failed to fetch metrics:', error)
86+
console.error('Failed to fetch data:', error)
87+
} finally {
88+
setIsLoading(false)
1889
}
1990
}
2091

21-
fetchMetrics()
22-
}, [token])
92+
fetchData()
93+
}, [token, timeRange, dateRange])
2394

2495
return (
2596
<div className="space-y-8">
97+
<TimeRange
98+
timeRange={timeRange}
99+
onTimeRangeChange={setTimeRange}
100+
dateRange={dateRange}
101+
onDateRangeChange={(range) => setDateRange(range || { from: addDays(new Date(), -7), to: new Date() })}
102+
className="mb-8"
103+
/>
104+
26105
{/* Metrics Row */}
27-
<div className="grid gap-4 md:grid-cols-3">
106+
<div className="grid gap-4 md:grid-cols-4">
107+
<MetricCard
108+
title="Total Deployments"
109+
value={metrics?.total_deployments ?? 'N/A'}
110+
/>
111+
<MetricCard
112+
title="Success Rate"
113+
value={metrics?.success_rate ? `${metrics.success_rate}%` : 'N/A'}
114+
/>
115+
<MetricCard
116+
title="Average Deploy Time"
117+
value={durationData.length > 0 ? `${Math.round(durationData.reduce((acc, curr) => acc + curr.avg_duration, 0) / durationData.length)}s` : 'N/A'}
118+
/>
119+
<MetricCard
120+
title="Error Rate"
121+
value={metrics?.error_rate ? `${metrics.error_rate}%` : 'N/A'}
122+
/>
28123
</div>
29124

30125
{/* Charts Grid */}
31126
<div className="grid gap-4 md:grid-cols-2">
127+
<Card>
128+
<CardHeader>
129+
<CardTitle>Deployments Over Time</CardTitle>
130+
</CardHeader>
131+
<CardContent>
132+
<DeploymentsChart
133+
data={deploymentsData}
134+
isLoading={isLoading}
135+
/>
136+
</CardContent>
137+
</Card>
138+
139+
<Card>
140+
<CardHeader>
141+
<CardTitle>Deploy Duration</CardTitle>
142+
</CardHeader>
143+
<CardContent>
144+
<DurationChart data={durationData} />
145+
</CardContent>
146+
</Card>
147+
</div>
148+
149+
{/* Tables and Additional Charts */}
150+
<div className="grid gap-4 md:grid-cols-2">
151+
<Card>
152+
<CardHeader>
153+
<CardTitle>Top Projects</CardTitle>
154+
</CardHeader>
155+
<CardContent>
156+
<ProjectsChart
157+
data={projectsData}
158+
isLoading={isLoading}
159+
/>
160+
</CardContent>
161+
</Card>
162+
163+
<Card>
164+
<CardHeader>
165+
<CardTitle>Git Analytics</CardTitle>
166+
</CardHeader>
167+
<CardContent>
168+
<GitAnalyticsChart
169+
data={gitData.analytics}
170+
isLoading={isLoading}
171+
/>
172+
</CardContent>
173+
</Card>
174+
175+
{/* <Card>
176+
<CardHeader>
177+
<CardTitle>Infrastructure</CardTitle>
178+
</CardHeader>
179+
<CardContent>
180+
<InfrastructureChart
181+
data={infraData}
182+
isLoading={isLoading}
183+
/>
184+
</CardContent>
185+
</Card> */}
32186
</div>
33187
</div>
34188
)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { BarChart, Bar, XAxis, YAxis } from 'recharts'
2+
import { ChartContainer, ChartTooltip, ChartConfig } from '@/components/ui/chart'
3+
import { format } from 'date-fns'
4+
5+
interface DeploymentsData {
6+
period: string
7+
'deployment.succeeded': number
8+
'deployment.error': number
9+
}
10+
11+
const chartConfig = {
12+
'deployment.succeeded': {
13+
color: "hsl(var(--primary))",
14+
label: "Successful Deployments",
15+
},
16+
'deployment.error': {
17+
color: "hsl(var(--secondary))",
18+
label: "Failed Deployments",
19+
},
20+
} satisfies ChartConfig
21+
22+
export function DeploymentsChart({ data, isLoading, className }: {
23+
data: DeploymentsData[]
24+
isLoading: boolean
25+
className?: string
26+
}) {
27+
if (isLoading) return <div className={`flex items-center justify-center ${className}`}>Loading...</div>
28+
if (!data.length) return <div className={`flex items-center justify-center ${className}`}>No data available</div>
29+
30+
const chartData = data.reduce((acc: any[], curr) => {
31+
const existing = acc.find(d => d.period === curr.period)
32+
if (existing) {
33+
existing[curr.event_type] = curr.count
34+
} else {
35+
acc.push({
36+
period: curr.period,
37+
[curr.event_type]: curr.count
38+
})
39+
}
40+
return acc
41+
}, [])
42+
43+
return (
44+
<ChartContainer config={chartConfig} className={`w-full ${className}`}>
45+
<BarChart
46+
data={chartData}
47+
margin={{ left: 24, right: 24, top: 24, bottom: 24 }}
48+
>
49+
<XAxis
50+
dataKey="period"
51+
tickLine={false}
52+
axisLine={false}
53+
tickFormatter={(value) => format(new Date(value), 'd HH:mm')}
54+
/>
55+
<YAxis
56+
tickLine={false}
57+
axisLine={false}
58+
/>
59+
<ChartTooltip
60+
content={({ active, payload }) => {
61+
if (!active || !payload?.length) return null
62+
const data = payload[0].payload
63+
return (
64+
<div className="rounded-lg border bg-background p-2 shadow-sm">
65+
<div className="text-xs text-muted-foreground">
66+
{format(new Date(data.period), 'd HH:mm')}
67+
</div>
68+
{payload.map((p: any) => (
69+
<div key={p.dataKey} className="font-bold">
70+
{chartConfig[p.dataKey as keyof typeof chartConfig].label}: {p.value}
71+
</div>
72+
))}
73+
</div>
74+
)
75+
}}
76+
/>
77+
<Bar
78+
dataKey="deployment.succeeded"
79+
fill={chartConfig['deployment.succeeded'].color}
80+
radius={[4, 4, 0, 0]}
81+
stackId="stack"
82+
/>
83+
<Bar
84+
dataKey="deployment.error"
85+
fill={chartConfig['deployment.error'].color}
86+
radius={[4, 4, 0, 0]}
87+
stackId="stack"
88+
/>
89+
</BarChart>
90+
</ChartContainer>
91+
)
92+
}

0 commit comments

Comments
 (0)