You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
12 KiB

import React, { useState } from 'react';
import { TestScenarioTaskTreeDto, TaskExecutionTreeDto, TaskExecutionCaseTreeDto } from '@/services/testScenarioTaskExecutionService';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { ChevronDown, ChevronRight, Play, CheckCircle, XCircle, Clock, User, Calendar, Search } from 'lucide-react';
// 状态颜色映射
const getStatusColor = (status: string) => {
switch (status) {
case 'Success':
return 'bg-green-100 text-green-800 border-green-200';
case 'Failed':
return 'bg-red-100 text-red-800 border-red-200';
case 'Running':
return 'bg-blue-100 text-blue-800 border-blue-200';
case 'Pending':
return 'bg-yellow-100 text-yellow-800 border-yellow-200';
default:
return 'bg-gray-100 text-gray-800 border-gray-200';
}
};
// 状态图标映射
const getStatusIcon = (status: string) => {
switch (status) {
case 'Success':
return <CheckCircle className="w-4 h-4" />;
case 'Failed':
return <XCircle className="w-4 h-4" />;
case 'Running':
return <Play className="w-4 h-4" />;
case 'Pending':
return <Clock className="w-4 h-4" />;
default:
return <Clock className="w-4 h-4" />;
}
};
// 格式化时间
const formatTime = (time?: string) => {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN');
};
// 格式化时长
const formatDuration = (duration: number) => {
if (duration < 60) {
return `${duration.toFixed(2)}`;
} else if (duration < 3600) {
return `${(duration / 60).toFixed(2)}分钟`;
} else {
return `${(duration / 3600).toFixed(2)}小时`;
}
};
// 表格数据类型
type TableDataType = 'executions' | 'cases';
// 执行记录行组件
const ExecutionRow: React.FC<{
execution: TaskExecutionTreeDto;
taskName: string;
taskCode: string;
}> = ({ execution, taskName, taskCode }) => {
return (
<TableRow>
<TableCell className="font-medium">{execution.executionId}</TableCell>
<TableCell>{taskName}</TableCell>
<TableCell>{taskCode}</TableCell>
<TableCell>
<Badge className={getStatusColor(execution.status)}>
{getStatusIcon(execution.status)}
<span className="ml-1">{execution.status}</span>
</Badge>
</TableCell>
<TableCell>{execution.executorId}</TableCell>
<TableCell>{execution.progress}%</TableCell>
<TableCell>{execution.loop}</TableCell>
<TableCell>{execution.caseCount}</TableCell>
<TableCell>{formatTime(execution.startTime)}</TableCell>
<TableCell>{formatTime(execution.endTime)}</TableCell>
<TableCell>{formatDuration(execution.duration)}</TableCell>
<TableCell>{execution.runtimeCode || '-'}</TableCell>
</TableRow>
);
};
// 用例记录行组件
const CaseRow: React.FC<{
caseDetail: TaskExecutionCaseTreeDto;
taskName: string;
taskCode: string;
executionId: string;
}> = ({ caseDetail, taskName, taskCode, executionId }) => {
return (
<TableRow>
<TableCell className="font-medium">{caseDetail.caseDetailId}</TableCell>
<TableCell>{taskName}</TableCell>
<TableCell>{taskCode}</TableCell>
<TableCell>{executionId}</TableCell>
<TableCell>{caseDetail.testCaseFlowId}</TableCell>
<TableCell>
<Badge className={getStatusColor(caseDetail.status)}>
{getStatusIcon(caseDetail.status)}
<span className="ml-1">{caseDetail.status}</span>
</Badge>
</TableCell>
<TableCell>{caseDetail.executorId}</TableCell>
<TableCell>{caseDetail.loop}</TableCell>
<TableCell>{formatTime(caseDetail.startTime)}</TableCell>
<TableCell>{formatTime(caseDetail.endTime)}</TableCell>
<TableCell>{formatDuration(caseDetail.duration)}</TableCell>
</TableRow>
);
};
// 表格组件主组件
interface TaskExecutionTableProps {
selectedTask: TestScenarioTaskTreeDto | null;
loading: boolean;
}
const TaskExecutionTable: React.FC<TaskExecutionTableProps> = ({
selectedTask,
loading
}) => {
const [tableType, setTableType] = useState<TableDataType>('executions');
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
// 获取执行记录数据
const getExecutionData = () => {
if (!selectedTask) return [];
let executions = selectedTask.executions;
// 状态过滤
if (statusFilter !== 'all') {
executions = executions.filter(exec => exec.status === statusFilter);
}
// 搜索过滤
if (searchTerm) {
executions = executions.filter(exec =>
exec.executionId.toLowerCase().includes(searchTerm.toLowerCase()) ||
exec.executorId.toLowerCase().includes(searchTerm.toLowerCase()) ||
(exec.runtimeCode && exec.runtimeCode.toLowerCase().includes(searchTerm.toLowerCase()))
);
}
return executions;
};
// 获取用例记录数据
const getCaseData = () => {
if (!selectedTask) return [];
let cases: Array<{ caseDetail: TaskExecutionCaseTreeDto; executionId: string }> = [];
selectedTask.executions.forEach(execution => {
execution.caseDetails.forEach(caseDetail => {
cases.push({ caseDetail, executionId: execution.executionId });
});
});
// 状态过滤
if (statusFilter !== 'all') {
cases = cases.filter(item => item.caseDetail.status === statusFilter);
}
// 搜索过滤
if (searchTerm) {
cases = cases.filter(item =>
item.caseDetail.caseDetailId.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.caseDetail.testCaseFlowId.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.caseDetail.executorId.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.executionId.toLowerCase().includes(searchTerm.toLowerCase())
);
}
return cases;
};
const executionData = getExecutionData();
const caseData = getCaseData();
return (
<div className="h-full flex flex-col">
<div className="p-4 border-b">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold"></h3>
<p className="text-sm text-gray-600">
{selectedTask ? `当前任务: ${selectedTask.taskName}` : '请选择任务查看详细信息'}
</p>
</div>
<div className="flex items-center gap-2">
<Select value={tableType} onValueChange={(value: TableDataType) => setTableType(value)}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="executions"></SelectItem>
<SelectItem value="cases"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 搜索和过滤 */}
<div className="flex items-center gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="搜索..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="Success"></SelectItem>
<SelectItem value="Failed"></SelectItem>
<SelectItem value="Running"></SelectItem>
<SelectItem value="Pending"></SelectItem>
</SelectContent>
</Select>
<Button
variant="outline"
size="sm"
onClick={() => {
setSearchTerm('');
setStatusFilter('all');
}}
>
</Button>
</div>
</div>
<div className="flex-1 overflow-auto">
{loading ? (
<div className="flex items-center justify-center h-32">
<div className="text-gray-500">...</div>
</div>
) : !selectedTask ? (
<div className="flex items-center justify-center h-32">
<div className="text-gray-500"></div>
</div>
) : tableType === 'executions' ? (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{executionData.length > 0 ? (
executionData.map((execution) => (
<ExecutionRow
key={execution.executionId}
execution={execution}
taskName={selectedTask.taskName}
taskCode={selectedTask.taskCode}
/>
))
) : (
<TableRow>
<TableCell colSpan={12} className="text-center text-gray-500">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead>ID</TableHead>
<TableHead>ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{caseData.length > 0 ? (
caseData.map((item) => (
<CaseRow
key={item.caseDetail.caseDetailId}
caseDetail={item.caseDetail}
taskName={selectedTask.taskName}
taskCode={selectedTask.taskCode}
executionId={item.executionId}
/>
))
) : (
<TableRow>
<TableCell colSpan={11} className="text-center text-gray-500">
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</div>
</div>
);
};
export default TaskExecutionTable;