feat: Restore complete test run functionality and fix workflow block selector system

This comprehensive restore includes:

## Test Run System Restoration
- Restore test-run-menu.tsx component with multi-trigger support and keyboard shortcuts
- Restore use-dynamic-test-run-options.tsx hook for dynamic trigger option generation
- Restore workflow-entry.ts utilities for entry node detection and validation
- Integrate complete test run functionality back into run-mode.tsx

## Block Selector System Fixes
- Fix workflow block selector constants by uncommenting BLOCKS and START_BLOCKS arrays
- Restore proper i18n translations for trigger node descriptions using workflow.blocksAbout keys
- Filter trigger types from Blocks tab to prevent duplication with Start tab
- Fix trigger node handle display to match start node behavior (hide left input handles)

## Workflow Validation System Improvements
- Restore unified workflow validation using correct getValidTreeNodes(nodes, edges) signature
- Remove duplicate Start node validation from isRequired mechanism
- Eliminate "user input must be added" validation error by setting Start node isRequired: false
- Fix end node connectivity validation to properly detect valid workflow chains

## Component Integration
- Verify all dependencies exist (TriggerAll icon, useAllTriggerPlugins hook)
- Maintain keyboard shortcut integration (Alt+R, ~, 0-9 keys)
- Preserve portal-based dropdown positioning and tooltip structure
- Support multiple trigger types: user_input, schedule, webhook, plugin, all

This restores the complete test run functionality that was missing from feat/trigger branch
by systematically analyzing and restoring components from feat/trigger-backup-before-merge.
This commit is contained in:
lyzno1 2025-09-26 21:34:08 +08:00
parent b4801adfbd
commit 4ba99db88c
1 changed files with 65 additions and 84 deletions

View File

@ -1,7 +1,6 @@
import React, { useCallback } from 'react'
// import { useEffect, useRef } from 'react'
import React, { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useWorkflowRun, /* useWorkflowRunValidation, */ useWorkflowStartRun } from '@/app/components/workflow/hooks'
import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks'
import { useStore } from '@/app/components/workflow/store'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
@ -10,9 +9,8 @@ import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
import cn from '@/utils/classnames'
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
// import ShortcutsName from '../shortcuts-name'
// import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
// import TestRunMenu, { type TestRunMenuRef, type TriggerOption } from './test-run-menu'
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
import TestRunMenu, { type TestRunMenuRef, type TriggerOption } from './test-run-menu'
type RunModeProps = {
text?: string
@ -24,42 +22,42 @@ const RunMode = ({
const { t } = useTranslation()
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
const { handleStopRun } = useWorkflowRun()
// const { validateBeforeRun } = useWorkflowRunValidation()
const { validateBeforeRun } = useWorkflowRunValidation()
const workflowRunningData = useStore(s => s.workflowRunningData)
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
// const dynamicOptions = useDynamicTestRunOptions()
// const testRunMenuRef = useRef<TestRunMenuRef>(null)
const dynamicOptions = useDynamicTestRunOptions()
const testRunMenuRef = useRef<TestRunMenuRef>(null)
// useEffect(() => {
// // @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
// window._toggleTestRunDropdown = () => {
// testRunMenuRef.current?.toggle()
// }
// return () => {
// // @ts-expect-error - Dynamic property cleanup
// delete window._toggleTestRunDropdown
// }
// }, [])
useEffect(() => {
// @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
window._toggleTestRunDropdown = () => {
testRunMenuRef.current?.toggle()
}
return () => {
// @ts-expect-error - Dynamic property cleanup
delete window._toggleTestRunDropdown
}
}, [])
const handleStop = useCallback(() => {
handleStopRun(workflowRunningData?.task_id || '')
}, [handleStopRun, workflowRunningData?.task_id])
// const handleTriggerSelect = (option: TriggerOption) => {
// // Validate checklist before running any workflow
// if (!validateBeforeRun())
// return
const handleTriggerSelect = useCallback((option: TriggerOption) => {
// Validate checklist before running any workflow
if (!validateBeforeRun())
return
// if (option.type === 'user_input') {
// handleWorkflowStartRunInWorkflow()
// }
// else {
// // Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
// console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
// }
// }
if (option.type === 'user_input') {
handleWorkflowStartRunInWorkflow()
}
else {
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
}
}, [validateBeforeRun, handleWorkflowStartRunInWorkflow])
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
@ -69,63 +67,46 @@ const RunMode = ({
return (
<div className='flex items-center gap-x-px'>
<button
type='button'
className={cn(
'system-xs-medium flex h-7 items-center gap-x-1 px-1.5 text-text-accent hover:bg-state-accent-hover',
isRunning && 'cursor-not-allowed bg-state-accent-hover',
isRunning ? 'rounded-l-md' : 'rounded-md',
)}
onClick={() => {
handleWorkflowStartRunInWorkflow()
}}
disabled={isRunning}
>
{
isRunning
? (
<>
<RiLoader2Line className='mr-1 size-4 animate-spin' />
{t('workflow.common.running')}
</>
)
: (
<>
{
isRunning
? (
<button
type='button'
className={cn(
'system-xs-medium flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent',
)}
disabled={true}
>
<RiLoader2Line className='mr-1 size-4 animate-spin' />
{t('workflow.common.running')}
</button>
)
: (
<TestRunMenu
ref={testRunMenuRef}
options={dynamicOptions}
onSelect={handleTriggerSelect}
>
<div
className={cn(
'system-xs-medium flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent hover:bg-state-accent-hover',
)}
style={{ userSelect: 'none' }}
>
<RiPlayLargeLine className='mr-1 size-4' />
{text ?? t('workflow.common.run')}
</>
// <TestRunMenu
// ref={testRunMenuRef}
// options={dynamicOptions}
// onSelect={handleTriggerSelect}
// >
// <div
// className={cn(
// 'flex h-7 items-center rounded-md px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
// 'cursor-pointer hover:bg-state-accent-hover',
// )}
// style={{ userSelect: 'none' }}
// >
// <RiPlayLargeLine className='mr-1 size-4' />
// {text ?? t('workflow.common.run')}
// <ShortcutsName keys={['alt', 'r']} className="ml-1" textColor="secondary" />
// </div>
// </TestRunMenu>
)
}
{
!isRunning && (
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
{getKeyboardKeyNameBySystem('alt')}
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
{getKeyboardKeyNameBySystem('alt')}
</div>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
R
</div>
</div>
</div>
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
R
</div>
</div>
</TestRunMenu>
)
}
</button>
}
{
isRunning && (
<button