Feat/workflow retry (#11885)
parent
dacd457478
commit
0c0120ef27
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import type { WorkflowRetryConfig } from './types'
|
||||||
|
import {
|
||||||
|
useNodeDataUpdate,
|
||||||
|
} from '@/app/components/workflow/hooks'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
|
export const useRetryConfig = (
|
||||||
|
id: string,
|
||||||
|
) => {
|
||||||
|
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||||
|
|
||||||
|
const handleRetryConfigChange = useCallback((value?: WorkflowRetryConfig) => {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data: {
|
||||||
|
retry_config: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleRetryConfigChange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRetryDetailShowInSingleRun = () => {
|
||||||
|
const [retryDetails, setRetryDetails] = useState<NodeTracing[] | undefined>()
|
||||||
|
|
||||||
|
const handleRetryDetailsChange = useCallback((details: NodeTracing[] | undefined) => {
|
||||||
|
setRetryDetails(details)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
retryDetails,
|
||||||
|
handleRetryDetailsChange,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiAlertFill,
|
||||||
|
RiCheckboxCircleFill,
|
||||||
|
RiLoader2Line,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type RetryOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const RetryOnNode = ({
|
||||||
|
data,
|
||||||
|
}: RetryOnNodeProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { retry_config } = data
|
||||||
|
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||||
|
const {
|
||||||
|
isRunning,
|
||||||
|
isSuccessful,
|
||||||
|
isException,
|
||||||
|
isFailed,
|
||||||
|
} = useMemo(() => {
|
||||||
|
return {
|
||||||
|
isRunning: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||||
|
isSuccessful: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||||
|
isFailed: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||||
|
isException: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||||
|
}
|
||||||
|
}, [data._runningStatus, showSelectedBorder])
|
||||||
|
const showDefault = !isRunning && !isSuccessful && !isException && !isFailed
|
||||||
|
|
||||||
|
if (!retry_config)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='px-3'>
|
||||||
|
<div className={cn(
|
||||||
|
'flex items-center justify-between px-[5px] py-1 bg-workflow-block-parma-bg border-[0.5px] border-transparent rounded-md system-xs-medium-uppercase text-text-tertiary',
|
||||||
|
isRunning && 'bg-state-accent-hover border-state-accent-active text-text-accent',
|
||||||
|
isSuccessful && 'bg-state-success-hover border-state-success-active text-text-success',
|
||||||
|
(isException || isFailed) && 'bg-state-warning-hover border-state-warning-active text-text-warning',
|
||||||
|
)}>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
{
|
||||||
|
showDefault && (
|
||||||
|
t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isRunning && (
|
||||||
|
<>
|
||||||
|
<RiLoader2Line className='animate-spin mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retrying')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
isSuccessful && (
|
||||||
|
<>
|
||||||
|
<RiCheckboxCircleFill className='mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retrySuccessful')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(isFailed || isException) && (
|
||||||
|
<>
|
||||||
|
<RiAlertFill className='mr-1 w-3.5 h-3.5' />
|
||||||
|
{t('workflow.nodes.common.retry.retryFailed')}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!showDefault && (
|
||||||
|
<div>
|
||||||
|
{data._retryIndex}/{data.retry_config?.max_retries}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RetryOnNode
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useRetryConfig } from './hooks'
|
||||||
|
import s from './style.module.css'
|
||||||
|
import Switch from '@/app/components/base/switch'
|
||||||
|
import Slider from '@/app/components/base/slider'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import type {
|
||||||
|
Node,
|
||||||
|
} from '@/app/components/workflow/types'
|
||||||
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
|
|
||||||
|
type RetryOnPanelProps = Pick<Node, 'id' | 'data'>
|
||||||
|
const RetryOnPanel = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: RetryOnPanelProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleRetryConfigChange } = useRetryConfig(id)
|
||||||
|
const { retry_config } = data
|
||||||
|
|
||||||
|
const handleRetryEnabledChange = (value: boolean) => {
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: value,
|
||||||
|
max_retries: retry_config?.max_retries || 3,
|
||||||
|
retry_interval: retry_config?.retry_interval || 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMaxRetriesChange = (value: number) => {
|
||||||
|
if (value > 10)
|
||||||
|
value = 10
|
||||||
|
else if (value < 1)
|
||||||
|
value = 1
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: value,
|
||||||
|
retry_interval: retry_config?.retry_interval || 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRetryIntervalChange = (value: number) => {
|
||||||
|
if (value > 5000)
|
||||||
|
value = 5000
|
||||||
|
else if (value < 100)
|
||||||
|
value = 100
|
||||||
|
handleRetryConfigChange({
|
||||||
|
retry_enabled: true,
|
||||||
|
max_retries: retry_config?.max_retries || 3,
|
||||||
|
retry_interval: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='pt-2'>
|
||||||
|
<div className='flex items-center justify-between px-4 py-2 h-10'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
defaultValue={retry_config?.retry_enabled}
|
||||||
|
onChange={v => handleRetryEnabledChange(v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
retry_config?.retry_enabled && (
|
||||||
|
<div className='px-4 pb-2'>
|
||||||
|
<div className='flex items-center mb-1 w-full'>
|
||||||
|
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.maxRetries')}</div>
|
||||||
|
<Slider
|
||||||
|
className='mr-3 w-[108px]'
|
||||||
|
value={retry_config?.max_retries || 3}
|
||||||
|
onChange={handleMaxRetriesChange}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
wrapperClassName='w-[80px]'
|
||||||
|
value={retry_config?.max_retries || 3}
|
||||||
|
onChange={e => handleMaxRetriesChange(e.target.value as any)}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
unit={t('workflow.nodes.common.retry.times') || ''}
|
||||||
|
className={s.input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.retryInterval')}</div>
|
||||||
|
<Slider
|
||||||
|
className='mr-3 w-[108px]'
|
||||||
|
value={retry_config?.retry_interval || 1000}
|
||||||
|
onChange={handleRetryIntervalChange}
|
||||||
|
min={100}
|
||||||
|
max={5000}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
wrapperClassName='w-[80px]'
|
||||||
|
value={retry_config?.retry_interval || 1000}
|
||||||
|
onChange={e => handleRetryIntervalChange(e.target.value as any)}
|
||||||
|
min={100}
|
||||||
|
max={5000}
|
||||||
|
unit={t('workflow.nodes.common.retry.ms') || ''}
|
||||||
|
className={s.input}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Split className='mx-4 mt-2' />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RetryOnPanel
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
.input::-webkit-inner-spin-button,
|
||||||
|
.input::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export type WorkflowRetryConfig = {
|
||||||
|
max_retries: number
|
||||||
|
retry_interval: number
|
||||||
|
retry_enabled: boolean
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
RiArrowLeftLine,
|
||||||
|
} from '@remixicon/react'
|
||||||
|
import TracingPanel from './tracing-panel'
|
||||||
|
import type { NodeTracing } from '@/types/workflow'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
list: NodeTracing[]
|
||||||
|
onBack: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const RetryResultPanel: FC<Props> = ({
|
||||||
|
list,
|
||||||
|
onBack,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className='flex items-center px-4 h-8 text-text-accent-secondary bg-components-panel-bg system-sm-medium cursor-pointer'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.nativeEvent.stopImmediatePropagation()
|
||||||
|
onBack()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiArrowLeftLine className='mr-1 w-4 h-4' />
|
||||||
|
{t('workflow.singleRun.back')}
|
||||||
|
</div>
|
||||||
|
<TracingPanel
|
||||||
|
list={list.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||||
|
}))}
|
||||||
|
className='bg-background-section-burn'
|
||||||
|
/>
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default memo(RetryResultPanel)
|
||||||
Loading…
Reference in New Issue