From fef2c6f397b5ce8067656bca8264b495026fd78e Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 15 Jan 2025 11:50:25 +0000 Subject: [PATCH 1/6] rebuild charts with tb charts --- .gitignore | 1 + apps/web/package.json | 1 + apps/web/pnpm-lock.yaml | 36 +++++++ .../src/components/tools/vercel/dashboard.tsx | 78 ++++---------- .../tools/vercel/deployment-duration-ts.tsx | 23 ++++ .../tools/vercel/deployments-chart.tsx | 102 ------------------ .../tools/vercel/deployments-over-time.tsx | 24 +++++ .../tools/vercel/duration-chart.tsx | 79 -------------- .../tools/vercel/git-analytics-chart.tsx | 67 ------------ .../tools/vercel/projects-chart.tsx | 69 ------------ 10 files changed, 104 insertions(+), 376 deletions(-) create mode 100644 apps/web/src/components/tools/vercel/deployment-duration-ts.tsx delete mode 100644 apps/web/src/components/tools/vercel/deployments-chart.tsx create mode 100644 apps/web/src/components/tools/vercel/deployments-over-time.tsx delete mode 100644 apps/web/src/components/tools/vercel/duration-chart.tsx delete mode 100644 apps/web/src/components/tools/vercel/git-analytics-chart.tsx delete mode 100644 apps/web/src/components/tools/vercel/projects-chart.tsx diff --git a/.gitignore b/.gitignore index a524483..bc34943 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store .venv .tinyb +.tb_error.txt .tmp diff --git a/apps/web/package.json b/apps/web/package.json index 8095457..f0aae56 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", + "@tinybirdco/charts": "^0.2.4", "ai": "^4.0.22", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 1538433..1f83d91 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.1.6 version: 1.1.6(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tinybirdco/charts': + specifier: ^0.2.4 + version: 0.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) ai: specifier: ^4.0.22 version: 4.0.22(react@19.0.0)(zod@3.24.1) @@ -837,6 +840,12 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' + '@tinybirdco/charts@0.2.4': + resolution: {integrity: sha512-0N8fYPY8uxO5KcBkW5Dgmapj4AqcWgyCmWnZBYsugXnnbx4/yg//f3/9oMWnM3RZ6ltt9moltYIzF/95NO2zNg==} + peerDependencies: + react: ^18.0.0 + react-dom: '>=16.6.0' + '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -1330,6 +1339,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2583,6 +2595,9 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2726,6 +2741,9 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3376,6 +3394,13 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.17 + '@tinybirdco/charts@0.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + echarts: 5.6.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + swr: 2.3.0(react@19.0.0) + '@types/d3-array@3.2.1': {} '@types/d3-color@3.1.3': {} @@ -3901,6 +3926,11 @@ snapshots: eastasianwidth@0.2.0: {} + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5545,6 +5575,8 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@2.3.0: {} + tslib@2.8.1: {} type-check@0.4.0: @@ -5748,4 +5780,8 @@ snapshots: zod@3.24.1: {} + zrender@5.6.1: + dependencies: + tslib: 2.3.0 + zwitch@2.0.4: {} diff --git a/apps/web/src/components/tools/vercel/dashboard.tsx b/apps/web/src/components/tools/vercel/dashboard.tsx index d99bbef..adc8a63 100644 --- a/apps/web/src/components/tools/vercel/dashboard.tsx +++ b/apps/web/src/components/tools/vercel/dashboard.tsx @@ -4,20 +4,11 @@ import { useQueryState } from 'nuqs' 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, type TimeRange as TR } from '@/components/time-range' import MetricCard from '@/components/metric-card' -import { DeploymentsChart, DeploymentsData } from './deployments-chart' -import { DurationChart, DurationData } from './duration-chart' -import { ProjectsChart, ProjectData } from './projects-chart' -import { GitAnalyticsChart, GitData } from './git-analytics-chart' - -interface VercelMetrics { - total_deployments: number - success_rate: number - error_rate: number -} +import { VercelDeploymentDuration } from './deployment-duration-ts' +import { VercelDeploymentsOverTime } from './deployments-over-time' export default function VercelDashboard() { const [token] = useQueryState('token') @@ -28,44 +19,13 @@ export default function VercelDashboard() { }) const [, setIsLoading] = useState(true) - const [metrics, setMetrics] = useState() - const [deploymentsData, setDeploymentsData] = useState([]) - const [durationData, setDurationData] = useState([]) - const [projectsData, setProjectsData] = useState([]) - const [gitData, setGitData] = useState([]) useEffect(() => { 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 { setIsLoading(true) - const [ - metricsResult, - deploymentsResult, - durationResult, - projectsResult, - gitAnalyticsResult, - - ] = await Promise.all([ - pipe<{ data: VercelMetrics[] }>(token, 'vercel_deployment_metrics', params), - pipe<{ data: DeploymentsData[] }>(token, 'vercel_deployments_over_time', params), - pipe<{ data: DurationData[] }>(token, 'vercel_deployment_duration', params), - pipe<{ data: ProjectData[] }>(token, 'vercel_project_stats', params), - pipe<{ data: GitData[] }>(token, 'vercel_git_analytics', params) - ]) - - setMetrics(metricsResult?.data?.[0]) - setDeploymentsData(deploymentsResult?.data ?? []) - setDurationData(durationResult?.data ?? []) - setProjectsData(projectsResult?.data ?? []) - setGitData(gitAnalyticsResult?.data ?? []) } catch (error) { console.error('Failed to fetch data:', error) } finally { @@ -90,19 +50,19 @@ export default function VercelDashboard() {
0 ? `${Math.round(durationData.reduce((acc, curr) => acc + curr.avg_duration, 0) / durationData.length)}s` : 'N/A'} + value={'N/A'} />
@@ -113,9 +73,11 @@ export default function VercelDashboard() { Deployments Over Time - @@ -125,23 +87,23 @@ export default function VercelDashboard() { Deploy Duration - + {/* Deploy Duration chart */} + - {/* Tables and Additional Charts */}
Top Projects - + {/* Top Projects table */} @@ -150,9 +112,7 @@ export default function VercelDashboard() { Git Analytics - + {/* Git Analytics chart */}
diff --git a/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx new file mode 100644 index 0000000..feb06e0 --- /dev/null +++ b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx @@ -0,0 +1,23 @@ +'use client' + +import { LineChart } from '@tinybirdco/charts' + +export function VercelDeploymentDuration(params: { + date_from?: string + date_to?: string + time_range?: string +}) { + return ( + + ) +} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/deployments-chart.tsx b/apps/web/src/components/tools/vercel/deployments-chart.tsx deleted file mode 100644 index 77ea1c7..0000000 --- a/apps/web/src/components/tools/vercel/deployments-chart.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { BarChart, Bar, XAxis, YAxis } from 'recharts' -import { ChartContainer, ChartTooltip, ChartConfig } from '@/components/ui/chart' -import { format } from 'date-fns' -import { type TimeRange } from '@/components/time-range' - -export interface DeploymentsData { - period: string - event_type: string - count: number -} - -const chartConfig = { - 'deployment.succeeded': { - color: "hsl(var(--chart-2))", - label: "Successful Deployments", - }, - 'deployment.error': { - color: "hsl(var(--chart-1))", - label: "Failed Deployments", - }, -} satisfies ChartConfig - -export function DeploymentsChart({ data, timeRange }: { data: DeploymentsData[]; timeRange: TimeRange }) { - if (!data.length) return
No data available
- - let dateFormat = 'yyyy-MM-dd HH:mm' - if (timeRange === 'hourly') { - dateFormat = 'yyyy-MM-dd HH:mm' - } else if (timeRange === 'daily') { - dateFormat = 'd MMMM' - } else if (timeRange === 'weekly') { - dateFormat = 'd MMMM' - } else if (timeRange === 'monthly') { - dateFormat = 'MMMM' - } - - // 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 ( - - - format(new Date(value), dateFormat)} - /> - - { - if (!active || !payload?.length) return null - const data = payload[0].payload - return ( -
-
- {format(new Date(data.period), dateFormat)} -
- { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - payload.map((p: any) => ( -
- {chartConfig[p.dataKey as keyof typeof chartConfig].label}: {p.value} -
- ))} -
- ) - }} - /> - - -
-
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/deployments-over-time.tsx b/apps/web/src/components/tools/vercel/deployments-over-time.tsx new file mode 100644 index 0000000..1a8e9ae --- /dev/null +++ b/apps/web/src/components/tools/vercel/deployments-over-time.tsx @@ -0,0 +1,24 @@ +'use client' + +import { BarChart } from '@tinybirdco/charts' + +export function VercelDeploymentsOverTime(params: { + date_from?: string + date_to?: string + time_range?: string +}) { + return ( + + ) +} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/duration-chart.tsx b/apps/web/src/components/tools/vercel/duration-chart.tsx deleted file mode 100644 index 831d189..0000000 --- a/apps/web/src/components/tools/vercel/duration-chart.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { format } from 'date-fns' -import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' -import { Card } from '@/components/ui/card' -import { type TimeRange } from '@/components/time-range' - -export interface DurationData { - period: string - avg_duration: number - p95_duration: number -} - -interface DurationChartProps { - data: DurationData[] - timeRange: TimeRange -} - -export function DurationChart({ data, timeRange }: DurationChartProps) { - - let dateFormat = 'yyyy-MM-dd HH:mm' - if (timeRange === 'hourly') { - dateFormat = 'yyyy-MM-dd HH:mm' - } else if (timeRange === 'daily') { - dateFormat = 'd MMMM' - } else if (timeRange === 'weekly') { - dateFormat = 'd MMMM' - } else if (timeRange === 'monthly') { - dateFormat = 'MMMM' - } - - return ( - - - format(new Date(value), dateFormat)} - /> - `${value}s`} - /> - { - if (!active || !payload?.length) return null - return ( - -
- {format(new Date(payload[0].payload.period), dateFormat)} -
-
- Avg: {payload[0].payload.avg_duration}s -
-
- P95: {payload[0].payload.p95_duration}s -
-
- ) - }} - /> - - -
-
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/git-analytics-chart.tsx b/apps/web/src/components/tools/vercel/git-analytics-chart.tsx deleted file mode 100644 index 7c490c8..0000000 --- a/apps/web/src/components/tools/vercel/git-analytics-chart.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { BarChart, Bar, XAxis, YAxis } from 'recharts' -import { ChartContainer, ChartTooltip, ChartConfig } from '@/components/ui/chart' - -export interface GitData { - author: string - commits: number - github_repo?: string - gitlab_repo?: string - branch: string -} - -const chartConfig = { - commits: { - color: "hsl(var(--primary))", - label: "Commits", - }, -} satisfies ChartConfig - -function transformData(data: GitData[]): (GitData & { fill: string })[] { - return data.map((item, index) => ({ - ...item, - fill: `hsl(var(--chart-${(index % 12) + 1}))` - })); -} - -export function GitAnalyticsChart({ data }: { data: GitData[] }) { - console.log(data) - data = transformData(data) - - return ( - - - - - { - if (!active || !payload?.length) return null - const data = payload[0].payload - return ( -
-
{data.author}
-
Commits: {data.commits}
- {data.github_repo &&
Repo: {data.github_repo}
} - {data.gitlab_repo &&
Repo: {data.gitlab_repo}
} -
- ) - }} - /> - -
-
- ) -} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/projects-chart.tsx b/apps/web/src/components/tools/vercel/projects-chart.tsx deleted file mode 100644 index 5501a73..0000000 --- a/apps/web/src/components/tools/vercel/projects-chart.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { BarChart, Bar, XAxis, YAxis } from 'recharts' -import { ChartContainer, ChartTooltip, ChartConfig } from '@/components/ui/chart' - -export interface ProjectData { - project_name: string - total_deployments: number - error_rate: number -} - -const chartConfig = { - total_deployments: { - color: "hsl(var(--chart-1))", - label: "Total Deployments", - }, - error_rate: { - color: "hsl(var(--chart-2))", - label: "Error Rate %", - }, -} satisfies ChartConfig - -function transformData(data: ProjectData[]): (ProjectData & { fill: string })[] { - return data.map((item, index) => ({ - ...item, - fill: `hsl(var(--chart-${(index % 12) + 1}))` - })); -} - -export function ProjectsChart({ data }: { data: ProjectData[] }) { - if (!data.length) return
No data available
- - data = transformData(data) - - return ( - - - - - { - if (!active || !payload?.length) return null - const data = payload[0].payload - return ( -
-
{data.project_name}
-
Deployments: {data.total_deployments}
-
Error Rate: {data.error_rate}%
-
- ) - }} - /> - -
-
- ) -} \ No newline at end of file From aeb10b74577997ed698a8709ea884181346567b0 Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 15 Jan 2025 12:17:41 +0000 Subject: [PATCH 2/6] api host --- apps/web/.env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/.env.example b/apps/web/.env.example index 3aafeac..d9a8089 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,2 +1,3 @@ ANTHROPIC_API_KEY= -NEXT_PUBLIC_VERCEL_URL= \ No newline at end of file +NEXT_PUBLIC_VERCEL_URL= +NEXT_PUBLIC_TINYBIRD_API_HOST= \ No newline at end of file From 9e553e43be4ad8478d497ed23b88bd9d854bce16 Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 22 Jan 2025 13:19:52 +0000 Subject: [PATCH 3/6] charts --- apps/web/package.json | 1 + apps/web/pnpm-lock.yaml | 100 +++++++++ .../src/components/tools/vercel/dashboard.tsx | 26 ++- .../components/tools/vercel/git-analytics.tsx | 21 ++ .../components/tools/vercel/project-stats.tsx | 21 ++ apps/web/src/components/ui/dropdown-menu.tsx | 201 ++++++++++++++++++ tinybird/pipes/vercel_git_analytics.pipe | 25 ++- tinybird/pipes/vercel_project_stats.pipe | 5 +- 8 files changed, 381 insertions(+), 19 deletions(-) create mode 100644 apps/web/src/components/tools/vercel/git-analytics.tsx create mode 100644 apps/web/src/components/tools/vercel/project-stats.tsx create mode 100644 apps/web/src/components/ui/dropdown-menu.tsx diff --git a/apps/web/package.json b/apps/web/package.json index f0aae56..fa9aa0d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-scroll-area": "^1.2.2", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 1f83d91..519a35e 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@radix-ui/react-dialog': specifier: ^1.1.4 version: 1.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.4 + version: 2.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-label': specifier: ^2.1.1 version: 2.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -574,6 +577,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.4': + resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -618,6 +634,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.4': + resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.4': resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} peerDependencies: @@ -683,6 +712,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-scroll-area@1.2.2': resolution: {integrity: sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==} peerDependencies: @@ -3134,6 +3176,21 @@ snapshots: '@types/react': 19.0.2 '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-menu': 2.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.2)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.2)(react@19.0.0)': dependencies: react: 19.0.0 @@ -3167,6 +3224,32 @@ snapshots: '@types/react': 19.0.2 '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-menu@2.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.2)(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@19.0.2)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-popover@1.1.4(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -3237,6 +3320,23 @@ snapshots: '@types/react': 19.0.2 '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.2)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.2)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.2 + '@types/react-dom': 19.0.2(@types/react@19.0.2) + '@radix-ui/react-scroll-area@1.2.2(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/number': 1.1.0 diff --git a/apps/web/src/components/tools/vercel/dashboard.tsx b/apps/web/src/components/tools/vercel/dashboard.tsx index adc8a63..cbd79ef 100644 --- a/apps/web/src/components/tools/vercel/dashboard.tsx +++ b/apps/web/src/components/tools/vercel/dashboard.tsx @@ -9,6 +9,8 @@ import { TimeRange, type TimeRange as TR } from '@/components/time-range' import MetricCard from '@/components/metric-card' import { VercelDeploymentDuration } from './deployment-duration-ts' import { VercelDeploymentsOverTime } from './deployments-over-time' +import { GitAnalytics } from './git-analytics' +import { VercelProjects } from './project-stats' export default function VercelDashboard() { const [token] = useQueryState('token') @@ -38,13 +40,15 @@ export default function VercelDashboard() { return (
- setDateRange(range || { from: addDays(new Date(), -7), to: new Date() })} - className="mb-8" - /> +
+ setDateRange(range || { from: addDays(new Date(), -7), to: new Date() })} + className="mb-8" + /> +
{/* Metrics Row */}
@@ -104,6 +108,10 @@ export default function VercelDashboard() { {/* Top Projects table */} + @@ -113,6 +121,10 @@ export default function VercelDashboard() { {/* Git Analytics chart */} +
diff --git a/apps/web/src/components/tools/vercel/git-analytics.tsx b/apps/web/src/components/tools/vercel/git-analytics.tsx new file mode 100644 index 0000000..42259a2 --- /dev/null +++ b/apps/web/src/components/tools/vercel/git-analytics.tsx @@ -0,0 +1,21 @@ +'use client' + +import { BarList } from '@tinybirdco/charts' + +export function GitAnalytics(params: { + date_from?: string + date_to?: string +}) { + return ( + + ) +} \ No newline at end of file diff --git a/apps/web/src/components/tools/vercel/project-stats.tsx b/apps/web/src/components/tools/vercel/project-stats.tsx new file mode 100644 index 0000000..1657900 --- /dev/null +++ b/apps/web/src/components/tools/vercel/project-stats.tsx @@ -0,0 +1,21 @@ +'use client' + +import { BarList } from '@tinybirdco/charts' + +export function VercelProjects(params: { + date_from?: string + date_to?: string +}) { + return ( + + ) +} \ No newline at end of file diff --git a/apps/web/src/components/ui/dropdown-menu.tsx b/apps/web/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..082639f --- /dev/null +++ b/apps/web/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,201 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/tinybird/pipes/vercel_git_analytics.pipe b/tinybird/pipes/vercel_git_analytics.pipe index 9ae28d0..eefb300 100644 --- a/tinybird/pipes/vercel_git_analytics.pipe +++ b/tinybird/pipes/vercel_git_analytics.pipe @@ -4,18 +4,23 @@ TAGS "vercel" NODE git_analytics SQL > % - SELECT - coalesce(event.payload.deployment.meta.githubCommitAuthorName::String, event.payload.deployment.meta.gitlabCommitAuthorName::String) as author, - count() as commits, - any(event.payload.deployment.meta.githubRepo::String) as github_repo, - any(event.payload.deployment.meta.gitlabProjectRepo::String) as gitlab_repo, - any(event.payload.deployment.meta.githubCommitRef::String) as branch + SELECT + coalesce( + nullIf(event.payload.deployment.meta.githubCommitAuthorName::String, ''), + coalesce( + nullIf(event.payload.deployment.meta.gitlabCommitAuthorLogin::String, ''), + nullIf(event.payload.deployment.meta.gitlabCommitAuthorName::String, '') + ), + 'user_not_found' + ) as author, + count() as commits FROM vercel - WHERE event_type = 'deployment.created' - AND event_time >= {{DateTime(date_from, '2024-01-01 00:00:00')}} - AND event_time <= {{DateTime(date_to, '2024-12-31 23:59:59')}} - AND author != '' + WHERE + event_type = 'deployment.created' + AND event_time >= {{ DateTime(date_from, '2024-01-01 00:00:00') }} + AND event_time <= {{ DateTime(date_to, '2024-12-31 23:59:59') }} GROUP BY author ORDER BY commits DESC + LIMIT 10 TYPE ENDPOINT diff --git a/tinybird/pipes/vercel_project_stats.pipe b/tinybird/pipes/vercel_project_stats.pipe index 503df61..35c17ac 100644 --- a/tinybird/pipes/vercel_project_stats.pipe +++ b/tinybird/pipes/vercel_project_stats.pipe @@ -5,8 +5,8 @@ NODE project_stats SQL > % SELECT - event.payload.project.id as project_id, - event.payload.name as project_name, + event.payload.project.id::String as project_id, + event.payload.name::String as project_name, count() as total_deployments, countIf(event_type = 'deployment.error') as errors, round(countIf(event_type = 'deployment.error') * 100.0 / count(), 2) as error_rate @@ -16,5 +16,6 @@ SQL > AND event_time <= {{DateTime(date_to, '2024-12-31 23:59:59')}} GROUP BY project_id, project_name ORDER BY total_deployments DESC + LIMIT 10 TYPE ENDPOINT From e57545ff817d6e3f84e6ed1513214c8e4a88499f Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 22 Jan 2025 13:24:39 +0000 Subject: [PATCH 4/6] metrics --- .../src/components/tools/vercel/dashboard.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/tools/vercel/dashboard.tsx b/apps/web/src/components/tools/vercel/dashboard.tsx index cbd79ef..7ea35b0 100644 --- a/apps/web/src/components/tools/vercel/dashboard.tsx +++ b/apps/web/src/components/tools/vercel/dashboard.tsx @@ -11,6 +11,13 @@ import { VercelDeploymentDuration } from './deployment-duration-ts' import { VercelDeploymentsOverTime } from './deployments-over-time' import { GitAnalytics } from './git-analytics' import { VercelProjects } from './project-stats' +import { pipe } from '@/lib/tinybird' + +interface VercelMetrics { + total_deployments: number + success_rate: number + error_rate: number +} export default function VercelDashboard() { const [token] = useQueryState('token') @@ -19,6 +26,7 @@ export default function VercelDashboard() { from: addDays(new Date(), -7), to: new Date() }) + const [metrics, setMetrics] = useState() const [, setIsLoading] = useState(true) @@ -26,8 +34,20 @@ export default function VercelDashboard() { 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 { setIsLoading(true) + const [ + metricsResult, + ] = await Promise.all([ + pipe<{ data: VercelMetrics[] }>(token, 'vercel_deployment_metrics', params), + ]) + setMetrics(metricsResult?.data?.[0]) } catch (error) { console.error('Failed to fetch data:', error) } finally { @@ -51,22 +71,18 @@ export default function VercelDashboard() {
{/* Metrics Row */} -
+
-
From da6a25b84a8d46394c753c924965eecc647e9eed Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 22 Jan 2025 13:25:56 +0000 Subject: [PATCH 5/6] rm title --- apps/web/src/components/tools/vercel/deployment-duration-ts.tsx | 1 - apps/web/src/components/tools/vercel/deployments-over-time.tsx | 1 - apps/web/src/components/tools/vercel/git-analytics.tsx | 1 - apps/web/src/components/tools/vercel/project-stats.tsx | 1 - 4 files changed, 4 deletions(-) diff --git a/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx index feb06e0..d3f4e3b 100644 --- a/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx +++ b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx @@ -15,7 +15,6 @@ export function VercelDeploymentDuration(params: { categories={['avg_duration', 'p95_duration']} colorPalette={['#27F795', '#008060', '#0EB1B9', '#9263AF', '#5A6FC0', '#86BFDB', '#FFC145', '#FF6B6C', '#DC82C8', '#FFC0F1']} stacked={true} - title="Vercel Deployment Duration" height="500px" params={params} /> diff --git a/apps/web/src/components/tools/vercel/deployments-over-time.tsx b/apps/web/src/components/tools/vercel/deployments-over-time.tsx index 1a8e9ae..ef426f3 100644 --- a/apps/web/src/components/tools/vercel/deployments-over-time.tsx +++ b/apps/web/src/components/tools/vercel/deployments-over-time.tsx @@ -16,7 +16,6 @@ export function VercelDeploymentsOverTime(params: { colorPalette={['#27F795', '#008060', '#0EB1B9', '#9263AF', '#5A6FC0', '#86BFDB', '#FFC145', '#FF6B6C', '#DC82C8', '#FFC0F1']} stacked={true} groupBy="event_type" - title="Vercel Deployments Over Time" height="500px" params={params} /> diff --git a/apps/web/src/components/tools/vercel/git-analytics.tsx b/apps/web/src/components/tools/vercel/git-analytics.tsx index 42259a2..5004fb1 100644 --- a/apps/web/src/components/tools/vercel/git-analytics.tsx +++ b/apps/web/src/components/tools/vercel/git-analytics.tsx @@ -13,7 +13,6 @@ export function GitAnalytics(params: { index="author" categories={['commits']} colorPalette={['#27F795', '#008060', '#0EB1B9', '#9263AF', '#5A6FC0', '#86BFDB', '#FFC145', '#FF6B6C', '#DC82C8', '#FFC0F1']} - title="Git Analytics" height="500px" params={params} /> diff --git a/apps/web/src/components/tools/vercel/project-stats.tsx b/apps/web/src/components/tools/vercel/project-stats.tsx index 1657900..d127238 100644 --- a/apps/web/src/components/tools/vercel/project-stats.tsx +++ b/apps/web/src/components/tools/vercel/project-stats.tsx @@ -13,7 +13,6 @@ export function VercelProjects(params: { index="project_name" categories={['total_deployments']} colorPalette={['#27F795', '#008060', '#0EB1B9', '#9263AF', '#5A6FC0', '#86BFDB', '#FFC145', '#FF6B6C', '#DC82C8', '#FFC0F1']} - title="Vercel Projects" height="500px" params={params} /> From 5c17f32a5080f3cfa92f5ebb34a6e452d37479fc Mon Sep 17 00:00:00 2001 From: sdairs Date: Wed, 22 Jan 2025 13:31:53 +0000 Subject: [PATCH 6/6] token --- .../src/components/tools/vercel/deployment-duration-ts.tsx | 4 +++- .../web/src/components/tools/vercel/deployments-over-time.tsx | 4 +++- apps/web/src/components/tools/vercel/git-analytics.tsx | 4 +++- apps/web/src/components/tools/vercel/project-stats.tsx | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx index d3f4e3b..53c01c1 100644 --- a/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx +++ b/apps/web/src/components/tools/vercel/deployment-duration-ts.tsx @@ -1,16 +1,18 @@ 'use client' import { LineChart } from '@tinybirdco/charts' +import { useQueryState } from 'nuqs' export function VercelDeploymentDuration(params: { date_from?: string date_to?: string time_range?: string }) { + const [token] = useQueryState('token') return (