Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/web/src/components/tools/auth0/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useQueryState } from 'nuqs'
import { useEffect, useState, useCallback } from 'react'
import { pipe } from '@/lib/tinybird'
import MetricCard from './metric'
import MetricCard from '@/components/tools/shared/metric-card'
import { DauChart, DauDataPoint } from './dau-chart'
import { AuthMechChart, AuthMechDataPoint } from './auth-mech-chart'
import { DailySignupsChart, DailySignupsDataPoint } from './daily-signups-chart'
Expand Down Expand Up @@ -341,17 +341,17 @@ export default function Auth0Dashboard() {
<MetricCard
title="Monthly Sign Ups"
value={summaryMetrics.monthly_signups}
description="New users signed up in the last 30 days"
isLoading={isLoading}
/>
<MetricCard
title="Monthly Active Users"
value={summaryMetrics.monthly_active_users}
description="Users active in the last 30 days"
isLoading={isLoading}
/>
<MetricCard
title="New Signups Rate"
value={`${summaryMetrics.conversion_rate}%`}
description="New users compared to total users"
isLoading={isLoading}
/>
</div>
<Card>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/tools/clerk/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useQueryState } from 'nuqs'
import { useEffect, useState } from 'react'
import { pipe } from '@/lib/tinybird'
import MetricCard from '../auth0/metric'
import MetricCard from '@/components/tools/shared/metric-card'
import { DauChart } from './dau-chart'
import { AuthMechChart } from './auth-mech-chart'

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/tools/orb/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useQueryState } from 'nuqs'
import { useEffect, useState } from 'react'
import { pipe } from '@/lib/tinybird'
import MetricCard from '../auth0/metric'
import MetricCard from '@/components/tools/shared/metric-card'
import { SubsChart } from './subs-chart'

interface SubsDataPoint {
Expand Down
26 changes: 26 additions & 0 deletions apps/web/src/components/tools/shared/metric-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'

interface MetricCardProps {
title: string
value: string | number
description?: string
isLoading?: boolean
}

export default function MetricCard({ title, value, description, isLoading }: MetricCardProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{isLoading ? 'Loading...' : value}
</div>
{description && (
<p className="text-xs text-muted-foreground">{description}</p>
)}
</CardContent>
</Card>
)
}
158 changes: 151 additions & 7 deletions apps/web/src/components/tools/vercel/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,178 @@
"use client"

import { useQueryState } from 'nuqs'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { addDays, format } from 'date-fns'
import { DateRange } from 'react-day-picker'
import { pipe } from '@/lib/tinybird'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { TimeRange } from '@/components/tools/shared/time-range'
import MetricCard from '@/components/tools/shared/metric-card'
import { DeploymentsChart } from './deployments-chart'
import { DurationChart } from './duration-chart'
import { ProjectsChart } from './projects-chart'
import { GitAnalyticsChart } from './git-analytics-chart'

interface GitData {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
analytics: any[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
distribution: any[]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
branches: any[]
}

export default function VercelDashboard() {
const [token] = useQueryState('token')
const [timeRange, setTimeRange] = useState('daily')
const [dateRange, setDateRange] = useState<DateRange>({
from: addDays(new Date(), -7),
to: new Date()
})

const [isLoading, setIsLoading] = useState(true)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [metrics, setMetrics] = useState<any>()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [deploymentsData, setDeploymentsData] = useState<any[]>([])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [durationData, setDurationData] = useState<any[]>([])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [projectsData, setProjectsData] = useState<any[]>([])
const [gitData, setGitData] = useState<GitData>({
analytics: [],
distribution: [],
branches: []
})

useEffect(() => {
async function fetchMetrics() {
async function fetchData() {
if (!token) return

const params = {
time_range: timeRange,
...(dateRange?.from && { date_from: format(dateRange.from, 'yyyy-MM-dd HH:mm:ss') }),
...(dateRange?.to && { date_to: format(dateRange.to, 'yyyy-MM-dd 23:59:59') })
}

try {
const [] = await Promise.all([
setIsLoading(true)
const [
metricsResult,
deploymentsResult,
durationResult,
projectsResult,
gitAnalyticsResult,
gitDistributionResult,
branchResult,
] = await Promise.all([
pipe(token, 'vercel_deployment_metrics', params),
pipe(token, 'vercel_deployments_over_time', params),
pipe(token, 'vercel_deployment_duration', params),
pipe(token, 'vercel_project_stats', params),
pipe(token, 'vercel_git_analytics', params),
pipe(token, 'vercel_git_distribution', params),
pipe(token, 'vercel_branch_distribution', params),
])

setMetrics(metricsResult?.data?.[0])
setDeploymentsData(deploymentsResult?.data ?? [])
setDurationData(durationResult?.data ?? [])
setProjectsData(projectsResult?.data ?? [])
setGitData({
analytics: gitAnalyticsResult?.data ?? [],
distribution: gitDistributionResult?.data ?? [],
branches: branchResult?.data ?? []
})
} catch (error) {
console.error('Failed to fetch metrics:', error)
console.error('Failed to fetch data:', error)
} finally {
setIsLoading(false)
}
}

fetchMetrics()
}, [token])
fetchData()
}, [token, timeRange, dateRange])

return (
<div className="space-y-8">
<TimeRange
timeRange={timeRange}
onTimeRangeChange={setTimeRange}
dateRange={dateRange}
onDateRangeChange={(range) => setDateRange(range || { from: addDays(new Date(), -7), to: new Date() })}
className="mb-8"
/>

{/* Metrics Row */}
<div className="grid gap-4 md:grid-cols-3">
<div className="grid gap-4 md:grid-cols-4">
<MetricCard
title="Total Deployments"
value={metrics?.total_deployments ?? 'N/A'}
/>
<MetricCard
title="Success Rate"
value={metrics?.success_rate ? `${metrics.success_rate}%` : 'N/A'}
/>
<MetricCard
title="Average Deploy Time"
value={durationData.length > 0 ? `${Math.round(durationData.reduce((acc, curr) => acc + curr.avg_duration, 0) / durationData.length)}s` : 'N/A'}
/>
<MetricCard
title="Error Rate"
value={metrics?.error_rate ? `${metrics.error_rate}%` : 'N/A'}
/>
</div>

{/* Charts Grid */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Deployments Over Time</CardTitle>
</CardHeader>
<CardContent>
<DeploymentsChart
data={deploymentsData}
isLoading={isLoading}
/>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>Deploy Duration</CardTitle>
</CardHeader>
<CardContent>
<DurationChart data={durationData} />
</CardContent>
</Card>
</div>

{/* Tables and Additional Charts */}
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Top Projects</CardTitle>
</CardHeader>
<CardContent>
<ProjectsChart
data={projectsData}
isLoading={isLoading}
/>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>Git Analytics</CardTitle>
</CardHeader>
<CardContent>
<GitAnalyticsChart
data={gitData.analytics}
isLoading={isLoading}
/>
</CardContent>
</Card>
</div>
</div>
)
Expand Down
95 changes: 95 additions & 0 deletions apps/web/src/components/tools/vercel/deployments-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { BarChart, Bar, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartConfig } from '@/components/ui/chart'
import { format } from 'date-fns'

interface DeploymentsData {
period: string
event_type: string
count: number
}

const chartConfig = {
'deployment.succeeded': {
color: "hsl(var(--primary))",
label: "Successful Deployments",
},
'deployment.error': {
color: "hsl(var(--secondary))",
label: "Failed Deployments",
},
} satisfies ChartConfig

export function DeploymentsChart({ data, isLoading, className }: {
data: DeploymentsData[]
isLoading: boolean
className?: string
}) {
if (isLoading) return <div className={`flex items-center justify-center ${className}`}>Loading...</div>
if (!data.length) return <div className={`flex items-center justify-center ${className}`}>No data available</div>

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const chartData = data.reduce((acc: any[], curr: DeploymentsData) => {
const existing = acc.find(d => d.period === curr.period)
if (existing) {
existing[curr.event_type] = curr.count
} else {
acc.push({
period: curr.period,
[curr.event_type]: curr.count
})
}
return acc
}, [])

return (
<ChartContainer config={chartConfig} className={`w-full ${className}`}>
<BarChart
data={chartData}
margin={{ left: 24, right: 24, top: 24, bottom: 24 }}
>
<XAxis
dataKey="period"
tickLine={false}
axisLine={false}
tickFormatter={(value) => format(new Date(value), 'd HH:mm')}
/>
<YAxis
tickLine={false}
axisLine={false}
/>
<ChartTooltip
content={({ active, payload }) => {
if (!active || !payload?.length) return null
const data = payload[0].payload
return (
<div className="rounded-lg border bg-background p-2 shadow-sm">
<div className="text-xs text-muted-foreground">
{format(new Date(data.period), 'd HH:mm')}
</div>
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload.map((p: any) => (
<div key={p.dataKey} className="font-bold">
{chartConfig[p.dataKey as keyof typeof chartConfig].label}: {p.value}
</div>
))}
</div>
)
}}
/>
<Bar
dataKey="deployment.succeeded"
fill={chartConfig['deployment.succeeded'].color}
radius={[4, 4, 0, 0]}
stackId="stack"
/>
<Bar
dataKey="deployment.error"
fill={chartConfig['deployment.error'].color}
radius={[4, 4, 0, 0]}
stackId="stack"
/>
</BarChart>
</ChartContainer>
)
}
Loading
Loading