Initial commit
This commit is contained in:
194
src/components/WorkflowRunsModal.tsx
Normal file
194
src/components/WorkflowRunsModal.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { X, CheckCircle, XCircle, Clock, AlertCircle, Play, Pause, HelpCircle, Ban, Timer, GitBranch, User, ExternalLink } from 'lucide-react';
|
||||
import { WorkflowRun } from '../types/github';
|
||||
import { ApiService } from '../services/api';
|
||||
|
||||
interface WorkflowRunsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
repository: {
|
||||
owner: string;
|
||||
name: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
const getStatusIcon = (status: string, conclusion: string | null) => {
|
||||
switch (status) {
|
||||
case 'in_progress':
|
||||
return <Play className="w-4 h-4 text-ctp-blue" />;
|
||||
case 'queued':
|
||||
case 'requested':
|
||||
case 'pending':
|
||||
return <Clock className="w-4 h-4 text-ctp-overlay1" />;
|
||||
case 'waiting':
|
||||
return <Pause className="w-4 h-4 text-ctp-rosewater" />;
|
||||
case 'completed':
|
||||
switch (conclusion) {
|
||||
case 'success':
|
||||
return <CheckCircle className="w-4 h-4 text-ctp-green" />;
|
||||
case 'failure':
|
||||
return <XCircle className="w-4 h-4 text-ctp-red" />;
|
||||
case 'cancelled':
|
||||
return <Ban className="w-4 h-4 text-ctp-overlay1" />;
|
||||
case 'action_required':
|
||||
return <AlertCircle className="w-4 h-4 text-ctp-yellow" />;
|
||||
case 'neutral':
|
||||
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
|
||||
case 'skipped':
|
||||
return <AlertCircle className="w-4 h-4 text-ctp-overlay0" />;
|
||||
case 'stale':
|
||||
return <Clock className="w-4 h-4 text-ctp-overlay1" />;
|
||||
case 'timed_out':
|
||||
return <Timer className="w-4 h-4 text-ctp-yellow" />;
|
||||
default:
|
||||
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
|
||||
}
|
||||
default:
|
||||
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: string, conclusion: string | null) => {
|
||||
if (status === 'completed' && conclusion) {
|
||||
return conclusion.charAt(0).toUpperCase() + conclusion.slice(1);
|
||||
}
|
||||
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||
};
|
||||
|
||||
export const WorkflowRunsModal: React.FC<WorkflowRunsModalProps> = ({ isOpen, onClose, repository }) => {
|
||||
const [runs, setRuns] = useState<WorkflowRun[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const apiService = useMemo(() => new ApiService(), []);
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const fetchWorkflowRuns = useCallback(async () => {
|
||||
if (!repository) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await apiService.getRepositoryWorkflowRuns(repository.owner, repository.name);
|
||||
setRuns(response);
|
||||
} catch (error) {
|
||||
console.error('Error fetching workflow runs:', error);
|
||||
setError('Failed to fetch workflow runs');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [repository, apiService]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && repository) {
|
||||
fetchWorkflowRuns();
|
||||
}
|
||||
}, [isOpen, repository, fetchWorkflowRuns]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
|
||||
onClick={handleBackdropClick}
|
||||
>
|
||||
<div className="bg-ctp-base rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between p-6 border-b border-ctp-surface0">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-ctp-text">
|
||||
{repository?.owner}/{repository?.name}
|
||||
</h2>
|
||||
<p className="text-sm text-ctp-subtext0">Recent workflow runs</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-ctp-subtext0 hover:text-ctp-text transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
{loading ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<Clock className="w-8 h-8 animate-spin text-ctp-blue" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-ctp-red">{error}</p>
|
||||
</div>
|
||||
) : runs.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-ctp-subtext0">No workflow runs found</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{runs.map((run) => (
|
||||
<div
|
||||
key={run.id}
|
||||
className="bg-ctp-mantle border border-ctp-surface0 rounded-lg p-4 hover:shadow-sm transition-shadow"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
{getStatusIcon(run.status, run.conclusion)}
|
||||
<div>
|
||||
<h3 className="font-medium text-ctp-text">{run.display_title}</h3>
|
||||
<p className="text-sm text-ctp-subtext0">
|
||||
{getStatusText(run.status, run.conclusion)} • #{run.run_number}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={run.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-ctp-blue hover:text-ctp-lavender transition-colors"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
<GitBranch className="w-4 h-4 text-ctp-overlay1" />
|
||||
<span className="text-ctp-subtext0">Branch:</span>
|
||||
<span className="text-ctp-text font-mono">{run.head_branch}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="w-4 h-4 text-ctp-overlay1" />
|
||||
<span className="text-ctp-subtext0">Author:</span>
|
||||
<span className="text-ctp-text">{run.actor.login}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-sm">
|
||||
<p className="text-ctp-subtext0 mb-1">Commit:</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
{run.head_commit && (
|
||||
<>
|
||||
<span className="font-mono text-xs bg-ctp-surface0 text-ctp-text px-2 py-1 rounded">
|
||||
{run.head_commit.id.substring(0, 7)}
|
||||
</span>
|
||||
<span className="text-ctp-text truncate">{run.head_commit.message}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-xs text-ctp-subtext1">
|
||||
{formatDistanceToNow(new Date(run.created_at), { addSuffix: true })}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user