From f273de2cab7c2b95c47a3feeb1aa3b09d511a51b Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 27 Dec 2025 18:11:52 +0000 Subject: [PATCH] refactor: extract run list components --- .../components/misc/github/views/RunList.tsx | 357 ++---------------- .../github/views/run-list/RefreshControls.tsx | 73 ++++ .../github/views/run-list/RunItemCard.tsx | 109 ++++++ .../github/views/run-list/RunListAlerts.tsx | 171 +++++++++ .../views/run-list/RunListEmptyState.tsx | 11 + .../github/views/run-list/run-list.types.ts | 48 +++ 6 files changed, 446 insertions(+), 323 deletions(-) create mode 100644 frontends/nextjs/src/components/misc/github/views/run-list/RefreshControls.tsx create mode 100644 frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx create mode 100644 frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx create mode 100644 frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx create mode 100644 frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts diff --git a/frontends/nextjs/src/components/misc/github/views/RunList.tsx b/frontends/nextjs/src/components/misc/github/views/RunList.tsx index d508ed6da..25b511d03 100644 --- a/frontends/nextjs/src/components/misc/github/views/RunList.tsx +++ b/frontends/nextjs/src/components/misc/github/views/RunList.tsx @@ -1,60 +1,15 @@ import { Box, Stack, Typography } from '@mui/material' -import { alpha } from '@mui/material/styles' -import { - Autorenew as RunningIcon, - Cancel as FailureIcon, - CheckCircle as SuccessIcon, - Download as DownloadIcon, - OpenInNew as OpenInNewIcon, - Refresh as RefreshIcon, -} from '@mui/icons-material' -import { Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton } from '@/components/ui' +import { CheckCircle as SuccessIcon } from '@mui/icons-material' -import { WorkflowRun } from '../types' +import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton } from '@/components/ui' -const spinSx = { - animation: 'spin 1s linear infinite', - '@keyframes spin': { - from: { transform: 'rotate(0deg)' }, - to: { transform: 'rotate(360deg)' }, - }, -} - -interface PipelineSummary { - cancelled: number - completed: number - failed: number - health: 'healthy' | 'warning' | 'critical' - inProgress: number - mostRecentFailed: boolean - mostRecentPassed: boolean - mostRecentRunning: boolean - recentWorkflows: WorkflowRun[] - successRate: number - successful: number - total: number -} - -interface RunListProps { - runs: WorkflowRun[] | null - isLoading: boolean - error: string | null - needsAuth: boolean - repoLabel: string - lastFetched: Date | null - autoRefreshEnabled: boolean - secondsUntilRefresh: number - onToggleAutoRefresh: () => void - onRefresh: () => void - getStatusColor: (status: string, conclusion: string | null) => string - onDownloadLogs: (runId: number, runName: string) => void - onDownloadJson: () => void - isLoadingLogs: boolean - conclusion: PipelineSummary | null - summaryTone: 'success' | 'error' | 'warning' - selectedRunId: number | null -} +import type { WorkflowRun } from '../types' +import { RefreshControls } from './run-list/RefreshControls' +import { RunItemCard } from './run-list/RunItemCard' +import { RunListAlerts } from './run-list/RunListAlerts' +import { RunListEmptyState } from './run-list/RunListEmptyState' +import type { RunListProps } from './run-list/run-list.types' export function RunList({ runs, @@ -111,191 +66,25 @@ export function RunList({ )} - - - - - Auto-refresh {autoRefreshEnabled ? 'ON' : 'OFF'} - - {autoRefreshEnabled && ( - - Next refresh: {secondsUntilRefresh}s - - )} - - - - - - - - + - {error && ( - - Error - {error} - - )} - - {needsAuth && ( - - Authentication Required - - GitHub API requires authentication for this request. Please configure credentials and retry. - - - )} - - {conclusion && ( - ({ - borderWidth: 2, - borderColor: theme.palette[summaryTone].main, - bgcolor: alpha(theme.palette[summaryTone].main, 0.08), - alignItems: 'flex-start', - mb: 2, - })} - > - - {summaryTone === 'success' && ( - - )} - {summaryTone === 'error' && ( - - )} - {summaryTone === 'warning' && ( - - )} - - - - {conclusion.mostRecentPassed && 'Most Recent Builds: ALL PASSED'} - {conclusion.mostRecentFailed && 'Most Recent Builds: FAILURES DETECTED'} - {conclusion.mostRecentRunning && 'Most Recent Builds: RUNNING'} - - - - - - {conclusion.recentWorkflows.length > 1 - ? `Showing ${conclusion.recentWorkflows.length} workflows from the most recent run:` - : 'Most recent workflow:'} - - - {conclusion.recentWorkflows.map((workflow: WorkflowRun) => { - const statusLabel = workflow.status === 'completed' - ? workflow.conclusion - : workflow.status - const badgeVariant = workflow.conclusion === 'success' - ? 'default' - : workflow.conclusion === 'failure' - ? 'destructive' - : 'outline' - - return ( - - - - {workflow.status === 'completed' && workflow.conclusion === 'success' && ( - - )} - {workflow.status === 'completed' && workflow.conclusion === 'failure' && ( - - )} - {workflow.status !== 'completed' && ( - - )} - {workflow.name} - - {statusLabel} - - - - - Branch: - - {workflow.head_branch} - - - - Updated: - {new Date(workflow.updated_at).toLocaleString()} - - - - - ) - })} - - - - - - - - - - )} + @@ -320,92 +109,16 @@ export function RunList({ {runs && runs.length > 0 ? ( - {runs.map((run) => { - const statusIcon = getStatusColor(run.status, run.conclusion) - return ( - - - - - - - {run.name} - - {run.event} - - - - - - Branch: - - {run.head_branch} - - - - Event: - {run.event} - - - Status: - - {run.status === 'completed' ? run.conclusion : run.status} - - - - - Updated: {new Date(run.updated_at).toLocaleString()} - - - - - - - - - - - ) - })} + {runs.map((run: WorkflowRun) => ( + + ))} + + + + + + +) diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx new file mode 100644 index 000000000..798877a59 --- /dev/null +++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx @@ -0,0 +1,109 @@ +import { Box, Stack, Typography } from '@mui/material' +import { Download as DownloadIcon, OpenInNew as OpenInNewIcon, Autorenew as RunningIcon } from '@mui/icons-material' + +import { Badge, Button, Card, CardContent } from '@/components/ui' + +import type { WorkflowRun } from '../types' +import type { RunListProps } from './run-list.types' +import { spinSx } from './run-list.types' + +type RunItemCardProps = Pick< + RunListProps, + 'getStatusColor' | 'onDownloadLogs' | 'isLoadingLogs' | 'selectedRunId' +> & { + run: WorkflowRun +} + +export const RunItemCard = ({ + run, + getStatusColor, + onDownloadLogs, + isLoadingLogs, + selectedRunId, +}: RunItemCardProps) => { + const statusIcon = getStatusColor(run.status, run.conclusion) + + return ( + + + + + + + {run.name} + + {run.event} + + + + + + Branch: + + {run.head_branch} + + + + Event: + {run.event} + + + Status: + + {run.status === 'completed' ? run.conclusion : run.status} + + + + + Updated: {new Date(run.updated_at).toLocaleString()} + + + + + + + + + + + ) +} diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx new file mode 100644 index 000000000..b7419f2c9 --- /dev/null +++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx @@ -0,0 +1,171 @@ +import { Box, Stack, Typography } from '@mui/material' +import { alpha } from '@mui/material/styles' +import { + Autorenew as RunningIcon, + Cancel as FailureIcon, + CheckCircle as SuccessIcon, + OpenInNew as OpenInNewIcon, +} from '@mui/icons-material' + +import { Alert, AlertDescription, AlertTitle, Badge, Button } from '@/components/ui' + +import type { WorkflowRun } from '../types' +import type { PipelineSummary, RunListProps } from './run-list.types' +import { spinSx } from './run-list.types' + +type RunListAlertsProps = Pick< + RunListProps, + 'error' | 'needsAuth' | 'conclusion' | 'summaryTone' +> + +type SummaryAlertProps = { + conclusion: PipelineSummary + summaryTone: RunListProps['summaryTone'] +} + +const SummaryAlert = ({ conclusion, summaryTone }: SummaryAlertProps) => ( + ({ + borderWidth: 2, + borderColor: theme.palette[summaryTone].main, + bgcolor: alpha(theme.palette[summaryTone].main, 0.08), + alignItems: 'flex-start', + mb: 2, + })} + > + + {summaryTone === 'success' && ( + + )} + {summaryTone === 'error' && ( + + )} + {summaryTone === 'warning' && ( + + )} + + + + {conclusion.mostRecentPassed && 'Most Recent Builds: ALL PASSED'} + {conclusion.mostRecentFailed && 'Most Recent Builds: FAILURES DETECTED'} + {conclusion.mostRecentRunning && 'Most Recent Builds: RUNNING'} + + + + + + {conclusion.recentWorkflows.length > 1 + ? `Showing ${conclusion.recentWorkflows.length} workflows from the most recent run:` + : 'Most recent workflow:'} + + + {conclusion.recentWorkflows.map((workflow: WorkflowRun) => { + const statusLabel = workflow.status === 'completed' + ? workflow.conclusion + : workflow.status + const badgeVariant = workflow.conclusion === 'success' + ? 'default' + : workflow.conclusion === 'failure' + ? 'destructive' + : 'outline' + + return ( + + + + {workflow.status === 'completed' && workflow.conclusion === 'success' && ( + + )} + {workflow.status === 'completed' && workflow.conclusion === 'failure' && ( + + )} + {workflow.status !== 'completed' && ( + + )} + {workflow.name} + + {statusLabel} + + + + + Branch: + + {workflow.head_branch} + + + + Updated: + {new Date(workflow.updated_at).toLocaleString()} + + + + + ) + })} + + + + + + + + + +) + +export const RunListAlerts = ({ error, needsAuth, conclusion, summaryTone }: RunListAlertsProps) => ( + <> + {error && ( + + Error + {error} + + )} + + {needsAuth && ( + + Authentication Required + + GitHub API requires authentication for this request. Please configure credentials and retry. + + + )} + + {conclusion && ( + + )} + +) diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx new file mode 100644 index 000000000..aae0930d0 --- /dev/null +++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx @@ -0,0 +1,11 @@ +import { Box } from '@mui/material' + +import type { RunListProps } from './run-list.types' + +type RunListEmptyStateProps = Pick + +export const RunListEmptyState = ({ isLoading }: RunListEmptyStateProps) => ( + + {isLoading ? 'Loading workflow runs...' : 'No workflow runs found. Click refresh to fetch data.'} + +) diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts b/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts new file mode 100644 index 000000000..f0775def7 --- /dev/null +++ b/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts @@ -0,0 +1,48 @@ +import { SxProps, Theme } from '@mui/material/styles' + +import type { WorkflowRun } from '../types' + +type SummaryTone = 'success' | 'error' | 'warning' + +export interface PipelineSummary { + cancelled: number + completed: number + failed: number + health: 'healthy' | 'warning' | 'critical' + inProgress: number + mostRecentFailed: boolean + mostRecentPassed: boolean + mostRecentRunning: boolean + recentWorkflows: WorkflowRun[] + successRate: number + successful: number + total: number +} + +export interface RunListProps { + runs: WorkflowRun[] | null + isLoading: boolean + error: string | null + needsAuth: boolean + repoLabel: string + lastFetched: Date | null + autoRefreshEnabled: boolean + secondsUntilRefresh: number + onToggleAutoRefresh: () => void + onRefresh: () => void + getStatusColor: (status: string, conclusion: string | null) => string + onDownloadLogs: (runId: number, runName: string) => void + onDownloadJson: () => void + isLoadingLogs: boolean + conclusion: PipelineSummary | null + summaryTone: SummaryTone + selectedRunId: number | null +} + +export const spinSx: SxProps = { + animation: 'spin 1s linear infinite', + '@keyframes spin': { + from: { transform: 'rotate(0deg)' }, + to: { transform: 'rotate(360deg)' }, + }, +}