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

View File

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