feat(web): run history of batch test

This commit is contained in:
JzoNg 2026-04-10 10:58:40 +08:00
parent a7ef8f9c12
commit 03325e9750
5 changed files with 186 additions and 38 deletions

View File

@ -1,47 +1,163 @@
import type { BatchTestRecord } from '../../types'
import type { EvaluationResourceProps } from '../../types'
import type { EvaluationLogFile } from '@/types/evaluation'
import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
import Pagination from '@/app/components/base/pagination'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/app/components/base/ui/dropdown-menu'
import { consoleClient, consoleQuery } from '@/service/client'
import { downloadUrl } from '@/utils/download'
type HistoryTabProps = {
batchRecords: BatchTestRecord[]
const PAGE_SIZE = 16
const LOADING_ROW_IDS = ['1', '2', '3', '4', '5', '6']
const formatCreatedAt = (createdAt: string) => {
if (!createdAt)
return '-'
return createdAt.includes('T') ? createdAt.slice(0, 10) : createdAt
}
const HistoryTab = ({ batchRecords }: HistoryTabProps) => {
const HistoryTab = ({
resourceType,
resourceId,
}: EvaluationResourceProps) => {
const { t } = useTranslation('evaluation')
const statusLabels = {
running: t('batch.status.running'),
success: t('batch.status.success'),
failed: t('batch.status.failed'),
}
const [page, setPage] = useState(0)
const logsQuery = useQuery({
...consoleQuery.evaluation.logs.queryOptions({
input: {
params: {
targetType: resourceType,
targetId: resourceId,
},
query: {
page: page + 1,
page_size: PAGE_SIZE,
},
},
refetchOnWindowFocus: false,
}),
placeholderData: keepPreviousData,
})
const fileDownloadMutation = useMutation({
mutationFn: async (file: EvaluationLogFile) => {
const fileInfo = await consoleClient.evaluation.file({
params: {
targetType: resourceType,
targetId: resourceId,
fileId: file.id,
},
})
downloadUrl({ url: fileInfo.download_url, fileName: file.name })
},
})
const records = logsQuery.data?.data ?? []
const total = logsQuery.data?.total ?? 0
const isInitialLoading = logsQuery.isLoading && !logsQuery.data
return (
<div className="space-y-3">
{batchRecords.length === 0 && (
<div className="rounded-2xl border border-dashed border-divider-subtle px-4 py-10 text-center system-sm-regular text-text-tertiary">
{t('batch.emptyHistory')}
</div>
)}
{batchRecords.map(record => (
<div key={record.id} className="rounded-2xl border border-divider-subtle bg-background-default-subtle p-4">
<div className="flex items-start justify-between gap-3">
<div>
<div className="system-sm-semibold text-text-primary">{record.summary}</div>
<div className="mt-1 system-xs-regular text-text-tertiary">{record.fileName}</div>
</div>
<Badge className={record.status === 'failed' ? 'badge-warning' : record.status === 'success' ? 'badge-accent' : ''}>
{record.status === 'running'
? (
<span className="flex items-center gap-1">
<span aria-hidden="true" className="i-ri-loader-4-line h-3 w-3 animate-spin" />
{statusLabels.running}
</span>
)
: statusLabels[record.status]}
</Badge>
<div className="flex min-h-full flex-col">
<div className="min-h-0 flex-1 overflow-hidden">
<table className="w-full table-fixed border-collapse overflow-hidden rounded-md">
<colgroup>
<col className="w-[120px]" />
<col className="w-[95px]" />
<col className="w-[80px]" />
<col className="w-[67px]" />
<col className="w-[40px]" />
</colgroup>
<thead>
<tr className="border-b border-divider-regular">
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">
<span className="inline-flex items-center gap-0.5">
{t('history.columns.time')}
<span aria-hidden="true" className="i-ri-arrow-down-line h-3.5 w-3.5" />
</span>
</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.creator')}</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.version')}</th>
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.status')}</th>
<th className="h-7 text-center text-text-tertiary">
<span aria-hidden="true" className="i-ri-download-2-line inline-block h-3.5 w-3.5" />
</th>
</tr>
</thead>
<tbody>
{isInitialLoading && LOADING_ROW_IDS.map(rowId => (
<tr key={rowId} className="border-b border-divider-subtle">
<td colSpan={5} className="h-10 px-3">
<div className="h-4 animate-pulse rounded bg-background-section" />
</td>
</tr>
))}
{!isInitialLoading && records.map(record => (
<tr key={`${record.created_at}-${record.test_file.id}`} className="border-b border-divider-subtle">
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{formatCreatedAt(record.created_at)}</td>
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.created_by}</td>
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.version || '-'}</td>
<td className="h-10 px-3 text-center">
{record.result_file
? <span aria-label={t('history.status.completed')} className="i-ri-checkbox-circle-fill inline-block h-4 w-4 text-util-colors-green-green-600" />
: <span aria-label={t('history.status.running')} className="i-ri-loader-4-line inline-block h-4 w-4 animate-spin text-text-accent" />}
</td>
<td className="h-10 text-center">
<DropdownMenu>
<DropdownMenuTrigger
render={(
<button
type="button"
aria-label={t('history.actions.open')}
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
/>
)}
>
<span aria-hidden="true" className="i-ri-more-2-fill h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent popupClassName="w-[180px] rounded-lg border-[0.5px] border-components-panel-border py-1 shadow-lg">
<DropdownMenuItem
className="gap-2"
onClick={() => fileDownloadMutation.mutate(record.test_file)}
>
<span aria-hidden="true" className="i-ri-file-download-line h-4 w-4" />
{t('history.actions.downloadTestFile')}
</DropdownMenuItem>
<DropdownMenuItem
className="gap-2"
disabled={!record.result_file}
onClick={() => record.result_file && fileDownloadMutation.mutate(record.result_file)}
>
<span aria-hidden="true" className="i-ri-download-2-line h-4 w-4" />
{t('history.actions.downloadResultFile')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
</tr>
))}
</tbody>
</table>
{!isInitialLoading && records.length === 0 && (
<div className="rounded-2xl border border-dashed border-divider-subtle px-4 py-10 text-center system-sm-regular text-text-tertiary">
{t('history.empty')}
</div>
<div className="mt-3 system-xs-regular text-text-tertiary">{record.startedAt}</div>
</div>
))}
)}
</div>
{total > PAGE_SIZE && (
<Pagination
className="px-0 py-3"
current={page}
limit={PAGE_SIZE}
total={total}
onChange={setPage}
/>
)}
</div>
)
}

View File

@ -76,7 +76,7 @@ const BatchTestPanel = ({
onUploadFileNameChange={uploadedFileName => setUploadedFileName(resourceType, resourceId, uploadedFileName)}
/>
)}
{resource.activeBatchTab === 'history' && <HistoryTab batchRecords={resource.batchRecords} />}
{resource.activeBatchTab === 'history' && <HistoryTab resourceType={resourceType} resourceId={resourceId} />}
</div>
</div>
)

View File

@ -47,6 +47,9 @@
"conditions.valueTypes.number": "Number",
"conditions.valueTypes.string": "String",
"description": "Configure automated testing to grade your application's performance.",
"history.actions.downloadResultFile": "Download result",
"history.actions.downloadTestFile": "Download test file",
"history.actions.open": "Open history actions",
"history.columns.creator": "Creator",
"history.columns.status": "Status",
"history.columns.time": "Time",
@ -55,6 +58,8 @@
"history.empty": "No test history yet",
"history.latestVersion": "Latest",
"history.searchPlaceholder": "Search",
"history.status.completed": "Completed",
"history.status.running": "Running",
"history.title": "Test History",
"judgeModel.description": "Choose the model used to score your evaluation results.",
"judgeModel.title": "Judge Model",

View File

@ -47,6 +47,20 @@
"conditions.valueTypes.number": "数值",
"conditions.valueTypes.string": "文本",
"description": "配置自动化测试,对应用表现进行评分。",
"history.actions.downloadResultFile": "下载结果文件",
"history.actions.downloadTestFile": "下载测试文件",
"history.actions.open": "打开历史记录操作",
"history.columns.creator": "创建人",
"history.columns.status": "状态",
"history.columns.time": "时间",
"history.columns.version": "版本",
"history.creatorYou": "你",
"history.empty": "还没有测试历史",
"history.latestVersion": "最新",
"history.searchPlaceholder": "搜索",
"history.status.completed": "已完成",
"history.status.running": "运行中",
"history.title": "测试历史",
"judgeModel.description": "选择用于打分和判定评测结果的模型。",
"judgeModel.title": "判定模型",
"metrics.add": "添加指标",

View File

@ -77,6 +77,19 @@ export type EvaluationRun = {
created_at: number
}
export type EvaluationLogFile = {
id: string
name: string
}
export type EvaluationLog = {
created_at: string
created_by: string
test_file: EvaluationLogFile
result_file: EvaluationLogFile | null
version: string
}
export type EvaluationRunMetric = {
name?: string
value?: unknown
@ -97,7 +110,7 @@ export type EvaluationRunItem = {
}
export type EvaluationLogsResponse = {
data: EvaluationRun[]
data: EvaluationLog[]
total: number
page: number
page_size: number