diff --git a/src/components/molecules/GitHubBuildStatus.tsx b/src/components/molecules/GitHubBuildStatus.tsx index 583f749..05d72e2 100644 --- a/src/components/molecules/GitHubBuildStatus.tsx +++ b/src/components/molecules/GitHubBuildStatus.tsx @@ -2,15 +2,19 @@ import { useState, useEffect } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { GitBranch, CheckCircle, XCircle, Clock, ArrowSquareOut, - Warning + Warning, + Copy, + CheckSquare } from '@phosphor-icons/react' import { Skeleton } from '@/components/ui/skeleton' +import { toast } from 'sonner' interface WorkflowRun { id: number @@ -23,42 +27,58 @@ interface WorkflowRun { head_branch: string head_sha: string event: string + workflow_id: number + path: string +} + +interface Workflow { + id: number + name: string + path: string + state: string + badge_url: string } interface GitHubBuildStatusProps { owner: string repo: string + defaultBranch?: string } -export function GitHubBuildStatus({ owner, repo }: GitHubBuildStatusProps) { +export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHubBuildStatusProps) { const [workflows, setWorkflows] = useState([]) + const [allWorkflows, setAllWorkflows] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [copiedBadge, setCopiedBadge] = useState(null) useEffect(() => { - fetchWorkflowRuns() + fetchData() }, [owner, repo]) - const fetchWorkflowRuns = async () => { + const fetchData = async () => { try { setLoading(true) setError(null) - const response = await fetch( - `https://api.github.com/repos/${owner}/${repo}/actions/runs?per_page=5`, - { - headers: { - 'Accept': 'application/vnd.github.v3+json', - }, - } - ) + const [runsResponse, workflowsResponse] = await Promise.all([ + fetch(`https://api.github.com/repos/${owner}/${repo}/actions/runs?per_page=5`, { + headers: { 'Accept': 'application/vnd.github.v3+json' }, + }), + fetch(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, { + headers: { 'Accept': 'application/vnd.github.v3+json' }, + }) + ]) - if (!response.ok) { - throw new Error(`GitHub API error: ${response.status}`) + if (!runsResponse.ok || !workflowsResponse.ok) { + throw new Error(`GitHub API error: ${runsResponse.status}`) } - const data = await response.json() - setWorkflows(data.workflow_runs || []) + const runsData = await runsResponse.json() + const workflowsData = await workflowsResponse.json() + + setWorkflows(runsData.workflow_runs || []) + setAllWorkflows(workflowsData.workflows || []) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch workflows') } finally { @@ -66,6 +86,29 @@ export function GitHubBuildStatus({ owner, repo }: GitHubBuildStatusProps) { } } + const getBadgeUrl = (workflowPath: string, branch?: string) => { + const workflowFile = workflowPath.split('/').pop() + if (branch) { + return `https://github.com/${owner}/${repo}/actions/workflows/${workflowFile}/badge.svg?branch=${branch}` + } + return `https://github.com/${owner}/${repo}/actions/workflows/${workflowFile}/badge.svg` + } + + const getBadgeMarkdown = (workflowPath: string, workflowName: string, branch?: string) => { + const badgeUrl = getBadgeUrl(workflowPath, branch) + const actionUrl = `https://github.com/${owner}/${repo}/actions/workflows/${workflowPath.split('/').pop()}` + return `[![${workflowName}](${badgeUrl})](${actionUrl})` + } + + const copyBadgeMarkdown = (workflowPath: string, workflowName: string, branch?: string) => { + const markdown = getBadgeMarkdown(workflowPath, workflowName, branch) + navigator.clipboard.writeText(markdown) + const key = `${workflowPath}-${branch || 'default'}` + setCopiedBadge(key) + toast.success('Badge markdown copied to clipboard') + setTimeout(() => setCopiedBadge(null), 2000) + } + const getStatusIcon = (status: string, conclusion: string | null) => { if (status === 'completed') { if (conclusion === 'success') { @@ -156,7 +199,7 @@ export function GitHubBuildStatus({ owner, repo }: GitHubBuildStatusProps) { - - {workflows.map((workflow) => ( -
-
- {getStatusIcon(workflow.status, workflow.conclusion)} -
-
-

- {workflow.name} -

- {getStatusBadge(workflow.status, workflow.conclusion)} -
-
- {workflow.head_branch} - - {formatTime(workflow.updated_at)} - - {workflow.event} + + + + Status Badges + Recent Runs + + + +
+
+

Workflow Badges

+
+ {allWorkflows.map((workflow) => ( +
+
+

{workflow.name}

+ +
+ {`${workflow.name} +

+ {getBadgeMarkdown(workflow.path, workflow.name)} +

+
+ ))}
+ + {uniqueBranches.length > 0 && ( +
+

Branch-Specific Badges

+
+ {uniqueBranches.slice(0, 3).map((branch) => ( +
+
+ +

{branch}

+
+ {allWorkflows.slice(0, 2).map((workflow) => ( +
+
+

{workflow.name}

+ +
+ {`${workflow.name} +
+ ))} +
+ ))} +
+
+ )}
+
+ + + {workflows.map((workflow) => ( +
+
+ {getStatusIcon(workflow.status, workflow.conclusion)} +
+
+

+ {workflow.name} +

+ {getStatusBadge(workflow.status, workflow.conclusion)} +
+
+ {workflow.head_branch} + + {formatTime(workflow.updated_at)} + + {workflow.event} +
+
+
+ +
+ ))} -
- ))} - + + )