wwf
2 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
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>
  )