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
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;
|
|
|