Files
dify/web/app/components/workflow/nodes/trigger-schedule/default.ts
FFXN 0e320290e1 feat: evaluation (#35353)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: jyong <718720800@qq.com>
Co-authored-by: Yansong Zhang <916125788@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: hj24 <mambahj24@gmail.com>
Co-authored-by: hj24 <huangjian@dify.ai>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 非法操作 <hjlarry@163.com>
Co-authored-by: Ayush Baluni <73417844+aayushbaluni@users.noreply.github.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: jimcody1995 <jjimcody@gmail.com>
Co-authored-by: James <63717587+jamesrayammons@users.noreply.github.com>
Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai>
Co-authored-by: Stephen Zhou <hi@hyoban.cc>
Co-authored-by: Coding On Star <447357187@qq.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: jerryzai <jerryzh8710@protonmail.com>
Co-authored-by: NVIDIAN <speedy.hpc@hotmail.com>
Co-authored-by: ai-hpc <ai-hpc@users.noreply.github.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: Junghwan <70629228+shaun0927@users.noreply.github.com>
Co-authored-by: HeYinKazune <70251095+HeYin-OS@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Jingyi <jingyi.qi@dify.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: sxxtony <166789813+sxxtony@users.noreply.github.com>
2026-04-17 16:37:21 +08:00

171 lines
5.7 KiB
TypeScript

import type { NodeDefault } from '../../types'
import type { ScheduleTriggerNodeType } from './types'
import { BlockEnum } from '../../types'
import { genNodeMetaData } from '../../utils'
import { getDefaultScheduleConfig } from './constants'
import { isValidCronExpression } from './utils/cron-parser'
import { getNextExecutionTimes } from './utils/execution-time-calculator'
const isValidTimeFormat = (time: string): boolean => {
const timeRegex = /^(0?\d|1[0-2]):[0-5]\d (AM|PM)$/
if (!timeRegex.test(time))
return false
const [timePart, period] = time.split(' ')
const [hour, minute] = timePart!.split(':')
const hourNum = Number.parseInt(hour!, 10)
const minuteNum = Number.parseInt(minute!, 10)
return hourNum >= 1 && hourNum <= 12
&& minuteNum >= 0 && minuteNum <= 59
&& ['AM', 'PM'].includes(period!)
}
const validateHourlyConfig = (config: any, t: any): string => {
if (config.on_minute === undefined || config.on_minute < 0 || config.on_minute > 59)
return t('nodes.triggerSchedule.invalidOnMinute', { ns: 'workflow' })
return ''
}
const validateDailyConfig = (config: any, t: any): string => {
const i18nPrefix = 'workflow.errorMsg'
if (!config.time)
return t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.time', { ns: 'workflow' }) })
if (!isValidTimeFormat(config.time))
return t('nodes.triggerSchedule.invalidTimeFormat', { ns: 'workflow' })
return ''
}
const validateWeeklyConfig = (config: any, t: any): string => {
const dailyError = validateDailyConfig(config, t)
if (dailyError)
return dailyError
const i18nPrefix = 'workflow.errorMsg'
if (!config.weekdays || config.weekdays.length === 0)
return t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.weekdays', { ns: 'workflow' }) })
const validWeekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
for (const day of config.weekdays) {
if (!validWeekdays.includes(day))
return t('nodes.triggerSchedule.invalidWeekday', { ns: 'workflow', weekday: day })
}
return ''
}
const validateMonthlyConfig = (config: any, t: any): string => {
const dailyError = validateDailyConfig(config, t)
if (dailyError)
return dailyError
const i18nPrefix = 'workflow.errorMsg'
const getMonthlyDays = (): (number | 'last')[] => {
if (Array.isArray(config.monthly_days) && config.monthly_days.length > 0)
return config.monthly_days
return []
}
const monthlyDays = getMonthlyDays()
if (monthlyDays.length === 0)
return t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.monthlyDay', { ns: 'workflow' }) })
for (const day of monthlyDays) {
if (day !== 'last' && (typeof day !== 'number' || day < 1 || day > 31))
return t('nodes.triggerSchedule.invalidMonthlyDay', { ns: 'workflow' })
}
return ''
}
const validateVisualConfig = (payload: ScheduleTriggerNodeType, t: any): string => {
const i18nPrefix = 'workflow.errorMsg'
const { visual_config } = payload
if (!visual_config)
return t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.visualConfig', { ns: 'workflow' }) })
switch (payload.frequency) {
case 'hourly':
return validateHourlyConfig(visual_config, t)
case 'daily':
return validateDailyConfig(visual_config, t)
case 'weekly':
return validateWeeklyConfig(visual_config, t)
case 'monthly':
return validateMonthlyConfig(visual_config, t)
default:
return t('nodes.triggerSchedule.invalidFrequency', { ns: 'workflow' })
}
}
const metaData = genNodeMetaData({
sort: 2,
type: BlockEnum.TriggerSchedule,
helpLinkUri: 'trigger/schedule-trigger',
isStart: true,
})
const nodeDefault: NodeDefault<ScheduleTriggerNodeType> = {
metaData,
defaultValue: {
...getDefaultScheduleConfig(),
cron_expression: '',
} as ScheduleTriggerNodeType,
checkValid(payload: ScheduleTriggerNodeType, t: any) {
const i18nPrefix = 'errorMsg'
let errorMessages = ''
if (!errorMessages && !payload.mode)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.mode', { ns: 'workflow' }) })
// Validate timezone format if provided (timezone will be auto-filled by use-config.ts if undefined)
if (!errorMessages && payload.timezone) {
try {
Intl.DateTimeFormat(undefined, { timeZone: payload.timezone })
}
catch {
errorMessages = t('nodes.triggerSchedule.invalidTimezone', { ns: 'workflow' })
}
}
if (!errorMessages) {
if (payload.mode === 'cron') {
if (!payload.cron_expression || payload.cron_expression.trim() === '')
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.cronExpression', { ns: 'workflow' }) })
else if (!isValidCronExpression(payload.cron_expression))
errorMessages = t('nodes.triggerSchedule.invalidCronExpression', { ns: 'workflow' })
}
else if (payload.mode === 'visual') {
if (!payload.frequency)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t('nodes.triggerSchedule.frequency', { ns: 'workflow' }) })
else
errorMessages = validateVisualConfig(payload, t)
}
}
if (!errorMessages) {
try {
const nextTimes = getNextExecutionTimes(payload, 1)
if (nextTimes.length === 0)
errorMessages = t('nodes.triggerSchedule.noValidExecutionTime', { ns: 'workflow' })
}
catch {
errorMessages = t('nodes.triggerSchedule.executionTimeCalculationError', { ns: 'workflow' })
}
}
return {
isValid: !errorMessages,
errorMessage: errorMessages,
}
},
}
export default nodeDefault