From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001 From: wwf <yearningwang@iqtogether.com> Date: 星期三, 04 六月 2025 15:17:49 +0800 Subject: [PATCH] 初始化 --- app/components/workflow/run/index.tsx | 181 +++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 172 insertions(+), 9 deletions(-) diff --git a/app/components/workflow/run/index.tsx b/app/components/workflow/run/index.tsx index 8b99603..8b0319c 100644 --- a/app/components/workflow/run/index.tsx +++ b/app/components/workflow/run/index.tsx @@ -3,16 +3,21 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import { BlockEnum } from '../types' import OutputPanel from './output-panel' import ResultPanel from './result-panel' import TracingPanel from './tracing-panel' +import IterationResultPanel from './iteration-result-panel' +import RetryResultPanel from './retry-result-panel' import cn from '@/utils/classnames' import { ToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' import { fetchRunDetail, fetchTracingList } from '@/service/log' -import type { NodeTracing } from '@/types/workflow' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' import type { WorkflowRunDetailResponse } from '@/models/log' import { useStore as useAppStore } from '@/app/components/app/store' + export type RunProps = { hideResult?: boolean activeTab?: 'RESULT' | 'DETAIL' | 'TRACING' @@ -55,12 +60,124 @@ } }, [notify, getResultCallback]) + const formatNodeList = useCallback((list: NodeTracing[]) => { + const allItems = [...list].reverse() + const result: NodeTracing[] = [] + const nodeGroupMap = new Map<string, Map<string, NodeTracing[]>>() + + const processIterationNode = (item: NodeTracing) => { + result.push({ + ...item, + details: [], + }) + } + + const updateParallelModeGroup = (runId: string, item: NodeTracing, iterationNode: NodeTracing) => { + if (!nodeGroupMap.has(iterationNode.node_id)) + nodeGroupMap.set(iterationNode.node_id, new Map()) + + const groupMap = nodeGroupMap.get(iterationNode.node_id)! + + if (!groupMap.has(runId)) { + groupMap.set(runId, [item]) + } + else { + if (item.status === 'retry') { + const retryNode = groupMap.get(runId)!.find(node => node.node_id === item.node_id) + + if (retryNode) { + if (retryNode?.retryDetail) + retryNode.retryDetail.push(item) + else + retryNode.retryDetail = [item] + } + } + else { + groupMap.get(runId)!.push(item) + } + } + + if (item.status === 'failed') { + iterationNode.status = 'failed' + iterationNode.error = item.error + } + + iterationNode.details = Array.from(groupMap.values()) + } + const updateSequentialModeGroup = (index: number, item: NodeTracing, iterationNode: NodeTracing) => { + const { details } = iterationNode + if (details) { + if (!details[index]) { + details[index] = [item] + } + else { + if (item.status === 'retry') { + const retryNode = details[index].find(node => node.node_id === item.node_id) + + if (retryNode) { + if (retryNode?.retryDetail) + retryNode.retryDetail.push(item) + else + retryNode.retryDetail = [item] + } + } + else { + details[index].push(item) + } + } + } + + if (item.status === 'failed') { + iterationNode.status = 'failed' + iterationNode.error = item.error + } + } + const processNonIterationNode = (item: NodeTracing) => { + const { execution_metadata } = item + if (!execution_metadata?.iteration_id) { + if (item.status === 'retry') { + const retryNode = result.find(node => node.node_id === item.node_id) + + if (retryNode) { + if (retryNode?.retryDetail) + retryNode.retryDetail.push(item) + else + retryNode.retryDetail = [item] + } + + return + } + result.push(item) + return + } + + const iterationNode = result.find(node => node.node_id === execution_metadata.iteration_id) + if (!iterationNode || !Array.isArray(iterationNode.details)) + return + + const { parallel_mode_run_id, iteration_index = 0 } = execution_metadata + + if (parallel_mode_run_id) + updateParallelModeGroup(parallel_mode_run_id, item, iterationNode) + else + updateSequentialModeGroup(iteration_index, item, iterationNode) + } + + allItems.forEach((item) => { + item.node_type === BlockEnum.Iteration + ? processIterationNode(item) + : processNonIterationNode(item) + }) + + return result + }, []) + const getTracingList = useCallback(async (appID: string, runID: string) => { try { const { data: nodeList } = await fetchTracingList({ url: `/apps/${appID}/workflow-runs/${runID}/node-executions`, }) - setList(nodeList) + setList(formatNodeList(nodeList)) } catch (err) { notify({ @@ -102,14 +219,50 @@ adjustResultHeight() }, [loading]) + const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([]) + const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({}) + const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([]) + const [isShowIterationDetail, { + setTrue: doShowIterationDetail, + setFalse: doHideIterationDetail, + }] = useBoolean(false) + const [isShowRetryDetail, { + setTrue: doShowRetryDetail, + setFalse: doHideRetryDetail, + }] = useBoolean(false) + + const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => { + setIterationRunResult(detail) + doShowIterationDetail() + setIterDurationMap(iterDurationMap) + }, [doShowIterationDetail, setIterationRunResult, setIterDurationMap]) + + const handleShowRetryDetail = useCallback((detail: NodeTracing[]) => { + setRetryRunResult(detail) + doShowRetryDetail() + }, [doShowRetryDetail, setRetryRunResult]) + + if (isShowIterationDetail) { + return ( + <div className='grow relative flex flex-col'> + <IterationResultPanel + list={iterationRunResult} + onHide={doHideIterationDetail} + onBack={doHideIterationDetail} + iterDurationMap={iterDurationMap} + /> + </div> + ) + } + return ( - <div className='relative flex grow flex-col'> + <div className='grow relative flex flex-col'> {/* tab */} - <div className='flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4'> + <div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-divider-subtle'> {!hideResult && ( <div className={cn( - 'system-sm-semibold-uppercase mr-6 cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', + 'mr-6 py-3 border-b-2 border-transparent system-sm-semibold-uppercase text-text-tertiary cursor-pointer', currentTab === 'RESULT' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('RESULT')} @@ -117,21 +270,21 @@ )} <div className={cn( - 'system-sm-semibold-uppercase mr-6 cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', + 'mr-6 py-3 border-b-2 border-transparent system-sm-semibold-uppercase text-text-tertiary cursor-pointer', currentTab === 'DETAIL' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('DETAIL')} >{t('runLog.detail')}</div> <div className={cn( - 'system-sm-semibold-uppercase mr-6 cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', + 'mr-6 py-3 border-b-2 border-transparent system-sm-semibold-uppercase text-text-tertiary cursor-pointer', currentTab === 'TRACING' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('TRACING')} >{t('runLog.tracing')}</div> </div> {/* panel detail */} - <div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg')}> + <div ref={ref} className={cn('grow bg-components-panel-bg h-0 overflow-y-auto rounded-b-2xl', currentTab !== 'DETAIL' && '!bg-background-section-burn')}> {loading && ( <div className='flex h-full items-center justify-center bg-components-panel-bg'> <Loading /> @@ -158,12 +311,22 @@ exceptionCounts={runDetail.exceptions_count} /> )} - {!loading && currentTab === 'TRACING' && ( + {!loading && currentTab === 'TRACING' && !isShowRetryDetail && ( <TracingPanel className='bg-background-section-burn' list={list} + onShowIterationDetail={handleShowIterationDetail} + onShowRetryDetail={handleShowRetryDetail} /> )} + { + !loading && currentTab === 'TRACING' && isShowRetryDetail && ( + <RetryResultPanel + list={retryRunResult} + onBack={doHideRetryDetail} + /> + ) + } </div> </div> ) -- Gitblit v1.8.0