diff --git a/src/components/molecules/GitHubBuildStatus.tsx b/src/components/molecules/GitHubBuildStatus.tsx index 05d72e2..c3fa04b 100644 --- a/src/components/molecules/GitHubBuildStatus.tsx +++ b/src/components/molecules/GitHubBuildStatus.tsx @@ -3,18 +3,19 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com 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, +import { + GitBranch, + CheckCircle, + XCircle, + Clock, ArrowSquareOut, Warning, Copy, - CheckSquare + CheckSquare, } from '@phosphor-icons/react' import { Skeleton } from '@/components/ui/skeleton' import { toast } from 'sonner' +import copy from '@/data/github-build-status.json' interface WorkflowRun { id: number @@ -45,6 +46,219 @@ interface GitHubBuildStatusProps { defaultBranch?: string } +interface WorkflowRunStatusProps { + status: string + conclusion: string | null +} + +interface WorkflowRunDetailsProps { + branch: string + updatedAt: string + event: string +} + +interface WorkflowRunItemProps { + workflow: WorkflowRun + renderStatus: (status: string, conclusion: string | null) => React.ReactNode + renderBadge: (status: string, conclusion: string | null) => React.ReactNode + formatTime: (dateString: string) => string +} + +interface WorkflowBadgeListProps { + workflows: Workflow[] + copiedBadge: string | null + onCopyBadge: (workflowPath: string, workflowName: string, branch?: string) => void + getBadgeUrl: (workflowPath: string, branch?: string) => string + getBadgeMarkdown: (workflowPath: string, workflowName: string, branch?: string) => string +} + +interface BranchBadgeListProps { + branches: string[] + workflows: Workflow[] + copiedBadge: string | null + onCopyBadge: (workflowPath: string, workflowName: string, branch: string) => void + getBadgeUrl: (workflowPath: string, branch?: string) => string +} + +const formatWithCount = (template: string, count: number) => + template.replace('{count}', count.toString()) + +const WorkflowRunStatus = ({ status, conclusion }: WorkflowRunStatusProps) => { + const getStatusIcon = () => { + if (status === 'completed') { + if (conclusion === 'success') { + return + } + if (conclusion === 'failure') { + return + } + if (conclusion === 'cancelled') { + return + } + } + return + } + + return
{getStatusIcon()}
+} + +const WorkflowRunBadge = ({ status, conclusion }: WorkflowRunStatusProps) => { + if (status === 'completed') { + if (conclusion === 'success') { + return ( + + {copy.status.success} + + ) + } + if (conclusion === 'failure') { + return {copy.status.failed} + } + if (conclusion === 'cancelled') { + return {copy.status.cancelled} + } + } + return ( + + {copy.status.running} + + ) +} + +const WorkflowRunDetails = ({ branch, updatedAt, event }: WorkflowRunDetailsProps) => ( +
+ {branch} + + {updatedAt} + + {event} +
+) + +const WorkflowRunItem = ({ workflow, renderStatus, renderBadge, formatTime }: WorkflowRunItemProps) => ( +
+
+ {renderStatus(workflow.status, workflow.conclusion)} +
+
+

{workflow.name}

+ {renderBadge(workflow.status, workflow.conclusion)} +
+ +
+
+ +
+) + +const WorkflowBadgeList = ({ + workflows, + copiedBadge, + onCopyBadge, + getBadgeUrl, + getBadgeMarkdown, +}: WorkflowBadgeListProps) => ( +
+

{copy.sections.workflowBadges}

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

{workflow.name}

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

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

+
+ ))} +
+
+) + +const BranchBadgeList = ({ + branches, + workflows, + copiedBadge, + onCopyBadge, + getBadgeUrl, +}: BranchBadgeListProps) => ( +
+

{copy.sections.branchBadges}

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

{branch}

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

{workflow.name}

+ +
+ {`${workflow.name} +
+ ))} +
+ ))} +
+
+) + export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHubBuildStatusProps) { const [workflows, setWorkflows] = useState([]) const [allWorkflows, setAllWorkflows] = useState([]) @@ -60,14 +274,14 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu try { setLoading(true) setError(null) - + 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' }, + headers: { Accept: 'application/vnd.github.v3+json' }, }), fetch(`https://api.github.com/repos/${owner}/${repo}/actions/workflows`, { - headers: { 'Accept': 'application/vnd.github.v3+json' }, - }) + headers: { Accept: 'application/vnd.github.v3+json' }, + }), ]) if (!runsResponse.ok || !workflowsResponse.ok) { @@ -76,7 +290,7 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu const runsData = await runsResponse.json() const workflowsData = await workflowsResponse.json() - + setWorkflows(runsData.workflow_runs || []) setAllWorkflows(workflowsData.workflows || []) } catch (err) { @@ -105,40 +319,10 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu navigator.clipboard.writeText(markdown) const key = `${workflowPath}-${branch || 'default'}` setCopiedBadge(key) - toast.success('Badge markdown copied to clipboard') + toast.success(copy.toast.badgeCopied) setTimeout(() => setCopiedBadge(null), 2000) } - const getStatusIcon = (status: string, conclusion: string | null) => { - if (status === 'completed') { - if (conclusion === 'success') { - return - } - if (conclusion === 'failure') { - return - } - if (conclusion === 'cancelled') { - return - } - } - return - } - - const getStatusBadge = (status: string, conclusion: string | null) => { - if (status === 'completed') { - if (conclusion === 'success') { - return Success - } - if (conclusion === 'failure') { - return Failed - } - if (conclusion === 'cancelled') { - return Cancelled - } - } - return Running - } - const formatTime = (dateString: string) => { const date = new Date(dateString) const now = new Date() @@ -147,10 +331,10 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu const hours = Math.floor(minutes / 60) const days = Math.floor(hours / 24) - if (days > 0) return `${days}d ago` - if (hours > 0) return `${hours}h ago` - if (minutes > 0) return `${minutes}m ago` - return 'just now' + if (days > 0) return formatWithCount(copy.time.daysAgo, days) + if (hours > 0) return formatWithCount(copy.time.hoursAgo, hours) + if (minutes > 0) return formatWithCount(copy.time.minutesAgo, minutes) + return copy.time.justNow } if (loading) { @@ -159,9 +343,9 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu - GitHub Actions + {copy.title} - Recent workflow runs + {copy.loading.description} {[...Array(3)].map((_, i) => ( @@ -187,22 +371,17 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu - GitHub Actions + {copy.title} - Unable to fetch workflow status + {copy.error.description}

{error}

-
@@ -217,20 +396,18 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu - GitHub Actions + {copy.title} - No workflow runs found + {copy.empty.description} -

- No GitHub Actions workflows have been run yet. -

+

{copy.empty.body}

) } - const uniqueBranches = Array.from(new Set(workflows.map(w => w.head_branch))) + const uniqueBranches = Array.from(new Set(workflows.map((workflow) => workflow.head_branch))) return ( @@ -239,164 +416,66 @@ export function GitHubBuildStatus({ owner, repo, defaultBranch = 'main' }: GitHu
- GitHub Actions + {copy.title} - Build status badges and recent workflow runs + {copy.header.description}
- - Status Badges - Recent Runs + {copy.tabs.badges} + {copy.tabs.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} -
-
-
- -
+ workflow={workflow} + renderStatus={(status, conclusion) => ( + + )} + renderBadge={(status, conclusion) => ( + + )} + formatTime={formatTime} + /> ))} - diff --git a/src/data/github-build-status.json b/src/data/github-build-status.json new file mode 100644 index 0000000..9386859 --- /dev/null +++ b/src/data/github-build-status.json @@ -0,0 +1,44 @@ +{ + "title": "GitHub Actions", + "loading": { + "description": "Recent workflow runs" + }, + "error": { + "description": "Unable to fetch workflow status", + "retry": "Try Again" + }, + "empty": { + "description": "No workflow runs found", + "body": "No GitHub Actions workflows have been run yet." + }, + "header": { + "description": "Build status badges and recent workflow runs", + "refresh": "Refresh" + }, + "tabs": { + "badges": "Status Badges", + "runs": "Recent Runs" + }, + "sections": { + "workflowBadges": "Workflow Badges", + "branchBadges": "Branch-Specific Badges" + }, + "actions": { + "viewAllWorkflows": "View All Workflows" + }, + "toast": { + "badgeCopied": "Badge markdown copied to clipboard" + }, + "status": { + "success": "Success", + "failed": "Failed", + "cancelled": "Cancelled", + "running": "Running" + }, + "time": { + "daysAgo": "{count}d ago", + "hoursAgo": "{count}h ago", + "minutesAgo": "{count}m ago", + "justNow": "just now" + } +}