From 89cd1ed7fe590845b26bbd48146648a6607fe887 Mon Sep 17 00:00:00 2001 From: wenjie liang <97488304+liang-wenjie@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:55:25 +0800 Subject: [PATCH 1/7] feat(schedule): add missed fire policy --- .../api/dto/ScheduleParam.java | 3 + .../service/impl/SchedulerServiceImpl.java | 7 ++ .../dolphinscheduler/api/vo/ScheduleVO.java | 4 ++ .../enums/ScheduleMissedFirePolicy.java | 37 +++++++++++ .../dolphinscheduler/dao/entity/Schedule.java | 3 + .../dao/mapper/ScheduleMapper.xml | 4 +- .../resources/sql/dolphinscheduler_h2.sql | 1 + .../resources/sql/dolphinscheduler_mysql.sql | 1 + .../sql/dolphinscheduler_postgresql.sql | 1 + .../mysql/dolphinscheduler_ddl_post.sql | 2 + .../postgresql/dolphinscheduler_ddl_post.sql | 2 + .../quartz/QuartzCornTriggerBuilder.java | 9 +-- ...QuartzScheduleMissedFirePolicyApplier.java | 49 ++++++++++++++ ...tzScheduleMissedFirePolicyApplierTest.java | 66 +++++++++++++++++++ .../src/locales/en_US/project.ts | 4 ++ .../src/locales/zh_CN/project.ts | 4 ++ .../definition/components/timing-modal.tsx | 24 +++++++ .../definition/components/use-form.ts | 1 + 18 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 88fd4ea9664b..4247cc3eedea 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -17,6 +17,8 @@ package org.apache.dolphinscheduler.api.dto; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + import java.util.Date; import lombok.Data; @@ -31,6 +33,7 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; + private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ONCE_NOW; public ScheduleParam() { } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 12df89e9fccf..3968ff994bce 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,6 +32,7 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.UserType; @@ -172,6 +173,7 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); + scheduleObj.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -401,6 +403,10 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } + private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + } + /** * update workflow definition schedule * @@ -557,6 +563,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); + schedule.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index fc4b1d966859..29475610aeb7 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,6 +18,7 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; import org.apache.dolphinscheduler.common.enums.WarningType; @@ -54,6 +55,8 @@ public class ScheduleVO { private String crontab; + private ScheduleMissedFirePolicy missedFirePolicy; + private FailureStrategy failureStrategy; private WarningType warningType; @@ -83,6 +86,7 @@ public class ScheduleVO { public ScheduleVO(Schedule schedule) { this.setId(schedule.getId()); this.setCrontab(schedule.getCrontab()); + this.setMissedFirePolicy(schedule.getMissedFirePolicy()); this.setProjectName(schedule.getProjectName()); this.setUserName(schedule.getUserName()); this.setWorkerGroup(schedule.getWorkerGroup()); diff --git a/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java new file mode 100644 index 000000000000..7959d636191e --- /dev/null +++ b/dolphinscheduler-common/src/main/java/org/apache/dolphinscheduler/common/enums/ScheduleMissedFirePolicy.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.common.enums; + +import lombok.Getter; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +@Getter +public enum ScheduleMissedFirePolicy { + + SKIP_MISSED(0), + FIRE_ONCE_NOW(1), + FIRE_ALL_MISSED(2); + + @EnumValue + private final int code; + + ScheduleMissedFirePolicy(int code) { + this.code = code; + } +} diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java index a55c8d1ad529..965a5c919477 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/entity/Schedule.java @@ -20,6 +20,7 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.WarningType; import java.util.Date; @@ -67,6 +68,8 @@ public class Schedule { private String crontab; + private ScheduleMissedFirePolicy missedFirePolicy; + private FailureStrategy failureStrategy; private WarningType warningType; diff --git a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml index 99c1a59ef6eb..78a401c9d1b2 100644 --- a/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml +++ b/dolphinscheduler-dao/src/main/resources/org/apache/dolphinscheduler/dao/mapper/ScheduleMapper.xml @@ -19,12 +19,12 @@ - id, workflow_definition_code, start_time, end_time, timezone_id, crontab, failure_strategy, user_id, release_state, + id, workflow_definition_code, start_time, end_time, timezone_id, crontab, missed_fire_policy, failure_strategy, user_id, release_state, warning_type, warning_group_id, workflow_instance_priority, worker_group, tenant_code, environment_code, create_time, update_time ${alias}.id, ${alias}.workflow_definition_code, ${alias}.start_time, ${alias}.end_time, ${alias}.timezone_id, - ${alias}.crontab, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, + ${alias}.crontab, ${alias}.missed_fire_policy, ${alias}.failure_strategy, ${alias}.user_id, ${alias}.release_state, ${alias}.warning_type, ${alias}.warning_group_id, ${alias}.workflow_instance_priority, ${alias}.worker_group, ${alias}.tenant_code, ${alias}.environment_code, ${alias}.create_time, ${alias}.update_time diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index 1725b5c2df36..a16f213ef4d7 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,6 +858,7 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, + missed_fire_policy tinyint NOT NULL DEFAULT 1, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index f6dfa61fc122..ac1d3abe531f 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,6 +859,7 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', + `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 698b35768d94..91843ffa413e 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,6 +785,7 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , + missed_fire_policy smallint NOT NULL DEFAULT 1, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..cd76951444a5 --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE `t_ds_schedules` + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql new file mode 100644 index 000000000000..2f84808b68cf --- /dev/null +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE t_ds_schedules + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 1; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index b7177e74db84..6028c9223906 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,14 +81,15 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); + QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, schedule.getMissedFirePolicy()); + return TriggerBuilder.newTrigger() .withIdentity(triggerKey) .startAt(startDate) .endAt(endDate) - .withSchedule( - CronScheduleBuilder.cronSchedule(schedule.getCrontab()) - .withMisfireHandlingInstructionIgnoreMisfires() - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId()))) + .withSchedule(scheduleBuilder) .build(); } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java new file mode 100644 index 000000000000..4f6e0d8d55fd --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.quartz.CronScheduleBuilder; + +final class QuartzScheduleMissedFirePolicyApplier { + + private QuartzScheduleMissedFirePolicyApplier() { + throw new IllegalStateException("Utility class"); + } + + static void apply(CronScheduleBuilder scheduleBuilder, ScheduleMissedFirePolicy missedFirePolicy) { + switch (effectivePolicy(missedFirePolicy)) { + case SKIP_MISSED: + scheduleBuilder.withMisfireHandlingInstructionDoNothing(); + return; + case FIRE_ONCE_NOW: + scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); + return; + case FIRE_ALL_MISSED: + scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); + return; + default: + throw new IllegalStateException("Unsupported schedule missed fire policy: " + missedFirePolicy); + } + } + + private static ScheduleMissedFirePolicy effectivePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java new file mode 100644 index 000000000000..9b962602cd3b --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.junit.jupiter.api.Test; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Trigger; + +class QuartzScheduleMissedFirePolicyApplierTest { + + private static final String CRON_EXPRESSION = "0 0 * * * ?"; + + @Test + void shouldSkipMissedExecutions() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, + buildMisfireInstruction(ScheduleMissedFirePolicy.SKIP_MISSED)); + } + + @Test + void shouldFireOnceNow() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, + buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ONCE_NOW)); + } + + @Test + void shouldFireAllMissedExecutions() { + assertEquals( + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, + buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ALL_MISSED)); + } + + @Test + void shouldFireOnceNowByDefault() { + assertEquals( + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, + buildMisfireInstruction(null)); + } + + private int buildMisfireInstruction(ScheduleMissedFirePolicy policy) { + CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_EXPRESSION); + QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, policy); + return scheduleBuilder.build().getMisfireInstruction(); + } +} diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 40e553d4dd1d..534c11915b84 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -149,6 +149,10 @@ export default { start_time: 'Start Time', end_time: 'End Time', crontab: 'Crontab', + missed_fire_policy: 'Missed Fire Policy', + skip_missed: 'Skip missed executions', + fire_once_now: 'Fire once immediately', + fire_all_missed: 'Fire all missed executions', delete_confirm: 'Delete?', delete_confirm_with_name: 'Delete "{name}"?', delete_irreversible: diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index abaa84a34b1a..1f99db99c032 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,6 +148,10 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', + missed_fire_policy: '错过触发策略', + skip_missed: '跳过错过的执行,等待下一次调度', + fire_once_now: '立即补触发一次,之后按正常节奏继续调度', + fire_all_missed: '补触发所有错过的执行,之后按正常节奏继续调度', delete_confirm: '确定删除吗?', delete_confirm_with_name: '确定删除“{name}”吗?', delete_irreversible: '此操作不可撤销。工作流及其关联数据将被永久删除。', diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index c30230581a65..fc8bb583bec1 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -295,6 +295,8 @@ export default defineComponent({ ] timingState.timingForm.crontab = props.row.crontab timingState.timingForm.timezoneId = props.row.timezoneId + timingState.timingForm.missedFirePolicy = + props.row.missedFirePolicy || 'FIRE_ONCE_NOW' timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType timingState.timingForm.workflowInstancePriority = @@ -410,6 +412,28 @@ export default defineComponent({ ) : null} + + + { ], crontab: '0 0 * * * ? *', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, + missedFirePolicy: 'FIRE_ONCE_NOW', failureStrategy: 'CONTINUE', warningType: 'NONE', workflowInstancePriority: 'MEDIUM', From c73327eb99d92dab4e0ad5b4a6c4e58ba306eb0f Mon Sep 17 00:00:00 2001 From: wenjie liang <97488304+liang-wenjie@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:21:38 +0800 Subject: [PATCH 2/7] refactor(schedule): apply review suggestions --- ...r.java => CronScheduleBuilderFactory.java} | 28 +++---- ...reAllMissedCronScheduleBuilderFactory.java | 29 +++++++ ...FireOnceNowCronScheduleBuilderFactory.java | 29 +++++++ .../quartz/QuartzCornTriggerBuilder.java | 4 +- .../SkipMissedCronScheduleBuilderFactory.java | 29 +++++++ .../CronScheduleBuilderFactoryTest.java | 75 +++++++++++++++++++ ...tzScheduleMissedFirePolicyApplierTest.java | 66 ---------------- .../src/locales/zh_CN/project.ts | 2 +- 8 files changed, 176 insertions(+), 86 deletions(-) rename dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/{QuartzScheduleMissedFirePolicyApplier.java => CronScheduleBuilderFactory.java} (53%) create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java create mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java delete mode 100644 dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java similarity index 53% rename from dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java rename to dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 4f6e0d8d55fd..214146968067 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplier.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -21,29 +21,23 @@ import org.quartz.CronScheduleBuilder; -final class QuartzScheduleMissedFirePolicyApplier { +interface CronScheduleBuilderFactory { - private QuartzScheduleMissedFirePolicyApplier() { - throw new IllegalStateException("Utility class"); - } + CronScheduleBuilder createCronScheduleBuilder(String cronExpression); - static void apply(CronScheduleBuilder scheduleBuilder, ScheduleMissedFirePolicy missedFirePolicy) { - switch (effectivePolicy(missedFirePolicy)) { + static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { + ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null + ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW + : missedFirePolicy; + switch (effectivePolicy) { case SKIP_MISSED: - scheduleBuilder.withMisfireHandlingInstructionDoNothing(); - return; + return new SkipMissedCronScheduleBuilderFactory(); case FIRE_ONCE_NOW: - scheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); - return; + return new FireOnceNowCronScheduleBuilderFactory(); case FIRE_ALL_MISSED: - scheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); - return; + return new FireAllMissedCronScheduleBuilderFactory(); default: - throw new IllegalStateException("Unsupported schedule missed fire policy: " + missedFirePolicy); + throw new IllegalArgumentException("Unsupported schedule missed fire policy: " + missedFirePolicy); } } - - private static ScheduleMissedFirePolicy effectivePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; - } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..3bc89ff25c94 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class FireAllMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionIgnoreMisfires(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..ea354cbd3e77 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class FireOnceNowCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionFireAndProceed(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index 6028c9223906..e28d6c77ede2 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -81,9 +81,9 @@ public CronTrigger build() { JobKey jobKey = QuartzJobKey.of(projectId, schedule.getId()).toJobKey(); TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + CronScheduleBuilder scheduleBuilder = CronScheduleBuilderFactory.getFactory(schedule.getMissedFirePolicy()) + .createCronScheduleBuilder(schedule.getCrontab()) .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); - QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, schedule.getMissedFirePolicy()); return TriggerBuilder.newTrigger() .withIdentity(triggerKey) diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java new file mode 100644 index 000000000000..7a7f87bbc613 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import org.quartz.CronScheduleBuilder; + +final class SkipMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { + + @Override + public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { + return CronScheduleBuilder.cronSchedule(cronExpression) + .withMisfireHandlingInstructionDoNothing(); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java new file mode 100644 index 000000000000..2f9823218ee8 --- /dev/null +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dolphinscheduler.scheduler.quartz; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; + +import org.junit.jupiter.api.Test; +import org.quartz.CronTrigger; +import org.quartz.Trigger; + +class CronScheduleBuilderFactoryTest { + + private static final String CRON_EXPRESSION = "0 0 * * * ?"; + + @Test + void shouldCreateSkipMissedCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.SKIP_MISSED, + SkipMissedCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); + } + + @Test + void shouldCreateFireOnceNowCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.FIRE_ONCE_NOW, + FireOnceNowCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + } + + @Test + void shouldCreateFireAllMissedCronScheduleBuilder() { + assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy.FIRE_ALL_MISSED, + FireAllMissedCronScheduleBuilderFactory.class, + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); + } + + @Test + void shouldCreateFireOnceNowCronScheduleBuilderByDefault() { + assertFactoryAndMisfireInstruction( + null, + FireOnceNowCronScheduleBuilderFactory.class, + CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + } + + private void assertFactoryAndMisfireInstruction( + ScheduleMissedFirePolicy policy, + Class expectedFactoryClass, + int expectedMisfireInstruction) { + CronScheduleBuilderFactory factory = CronScheduleBuilderFactory.getFactory(policy); + assertInstanceOf(expectedFactoryClass, factory); + assertEquals( + expectedMisfireInstruction, + factory.createCronScheduleBuilder(CRON_EXPRESSION).build().getMisfireInstruction()); + } +} diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java deleted file mode 100644 index 9b962602cd3b..000000000000 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzScheduleMissedFirePolicyApplierTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dolphinscheduler.scheduler.quartz; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; - -import org.junit.jupiter.api.Test; -import org.quartz.CronScheduleBuilder; -import org.quartz.CronTrigger; -import org.quartz.Trigger; - -class QuartzScheduleMissedFirePolicyApplierTest { - - private static final String CRON_EXPRESSION = "0 0 * * * ?"; - - @Test - void shouldSkipMissedExecutions() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING, - buildMisfireInstruction(ScheduleMissedFirePolicy.SKIP_MISSED)); - } - - @Test - void shouldFireOnceNow() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, - buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ONCE_NOW)); - } - - @Test - void shouldFireAllMissedExecutions() { - assertEquals( - Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, - buildMisfireInstruction(ScheduleMissedFirePolicy.FIRE_ALL_MISSED)); - } - - @Test - void shouldFireOnceNowByDefault() { - assertEquals( - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, - buildMisfireInstruction(null)); - } - - private int buildMisfireInstruction(ScheduleMissedFirePolicy policy) { - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_EXPRESSION); - QuartzScheduleMissedFirePolicyApplier.apply(scheduleBuilder, policy); - return scheduleBuilder.build().getMisfireInstruction(); - } -} diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index 1f99db99c032..429fd4990bf7 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -148,7 +148,7 @@ export default { start_time: '开始时间', end_time: '结束时间', crontab: 'Crontab', - missed_fire_policy: '错过触发策略', + missed_fire_policy: '定时错过策略', skip_missed: '跳过错过的执行,等待下一次调度', fire_once_now: '立即补触发一次,之后按正常节奏继续调度', fire_all_missed: '补触发所有错过的执行,之后按正常节奏继续调度', From 2868f0960da1da692b76c5482bc95f8003085a37 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Wed, 5 Aug 2026 11:19:50 +0800 Subject: [PATCH 3/7] fix(schedule): preserve existing misfire behavior --- .../api/dto/ScheduleParam.java | 2 +- .../service/impl/SchedulerServiceImpl.java | 4 ++-- .../dolphinscheduler/api/vo/ScheduleVO.java | 2 +- .../resources/sql/dolphinscheduler_h2.sql | 2 +- .../resources/sql/dolphinscheduler_mysql.sql | 2 +- .../sql/dolphinscheduler_postgresql.sql | 2 +- .../mysql/dolphinscheduler_ddl_post.sql | 19 ++++++++++++++++++- .../postgresql/dolphinscheduler_ddl_post.sql | 19 ++++++++++++++++++- .../quartz/CronScheduleBuilderFactory.java | 2 +- .../CronScheduleBuilderFactoryTest.java | 6 +++--- .../definition/components/timing-modal.tsx | 2 +- .../definition/components/use-form.ts | 2 +- 12 files changed, 49 insertions(+), 15 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 4247cc3eedea..6369f56e4881 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -33,7 +33,7 @@ public class ScheduleParam { private Date endTime; private String crontab; private String timezoneId; - private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ONCE_NOW; + private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ALL_MISSED; public ScheduleParam() { } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 3968ff994bce..a167f0f5772a 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -32,9 +32,9 @@ import org.apache.dolphinscheduler.api.vo.ScheduleVO; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -404,7 +404,7 @@ public List previewSchedule(User loginUser, String schedule) { } private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW : missedFirePolicy; + return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; } /** diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java index 29475610aeb7..8ddf9c3ade64 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/vo/ScheduleVO.java @@ -18,9 +18,9 @@ package org.apache.dolphinscheduler.api.vo; import org.apache.dolphinscheduler.common.enums.FailureStrategy; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; import org.apache.dolphinscheduler.dao.entity.Schedule; diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql index a16f213ef4d7..d69728be6ef3 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_h2.sql @@ -858,7 +858,7 @@ CREATE TABLE t_ds_schedules end_time datetime NOT NULL, timezone_id varchar(40) DEFAULT NULL, crontab varchar(255) NOT NULL, - missed_fire_policy tinyint NOT NULL DEFAULT 1, + missed_fire_policy tinyint NOT NULL DEFAULT 2, failure_strategy tinyint(4) NOT NULL, user_id int(11) NOT NULL, release_state tinyint(4) NOT NULL, diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql index ac1d3abe531f..361cdc3ff7fd 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql @@ -859,7 +859,7 @@ CREATE TABLE `t_ds_schedules` ( `end_time` datetime NOT NULL COMMENT 'end time', `timezone_id` varchar(40) DEFAULT NULL COMMENT 'schedule timezone id', `crontab` varchar(255) NOT NULL COMMENT 'crontab description', - `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', + `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed', `failure_strategy` tinyint(4) NOT NULL COMMENT 'failure strategy. 0:end,1:continue', `user_id` int(11) NOT NULL COMMENT 'user id', `release_state` tinyint(4) NOT NULL COMMENT 'release state. 0:offline,1:online ', diff --git a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql index 91843ffa413e..387716298493 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql @@ -785,7 +785,7 @@ CREATE TABLE t_ds_schedules ( end_time timestamp NOT NULL , timezone_id varchar(40) default NULL , crontab varchar(255) NOT NULL , - missed_fire_policy smallint NOT NULL DEFAULT 1, + missed_fire_policy smallint NOT NULL DEFAULT 2, failure_strategy int NOT NULL , user_id int NOT NULL , release_state int NOT NULL , diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql index cd76951444a5..fbf620b4c825 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql @@ -1,2 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + ALTER TABLE `t_ds_schedules` - ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '1' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql index 2f84808b68cf..0aaff6c8eea4 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql @@ -1,2 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + ALTER TABLE t_ds_schedules - ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 1; + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 214146968067..1784aeae044e 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -27,7 +27,7 @@ interface CronScheduleBuilderFactory { static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null - ? ScheduleMissedFirePolicy.FIRE_ONCE_NOW + ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; switch (effectivePolicy) { case SKIP_MISSED: diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java index 2f9823218ee8..9d0b135eb327 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -55,11 +55,11 @@ void shouldCreateFireAllMissedCronScheduleBuilder() { } @Test - void shouldCreateFireOnceNowCronScheduleBuilderByDefault() { + void shouldCreateFireAllMissedCronScheduleBuilderByDefault() { assertFactoryAndMisfireInstruction( null, - FireOnceNowCronScheduleBuilderFactory.class, - CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); + FireAllMissedCronScheduleBuilderFactory.class, + Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY); } private void assertFactoryAndMisfireInstruction( diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx index fc8bb583bec1..5d15f06962a2 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/timing-modal.tsx @@ -296,7 +296,7 @@ export default defineComponent({ timingState.timingForm.crontab = props.row.crontab timingState.timingForm.timezoneId = props.row.timezoneId timingState.timingForm.missedFirePolicy = - props.row.missedFirePolicy || 'FIRE_ONCE_NOW' + props.row.missedFirePolicy || 'FIRE_ALL_MISSED' timingState.timingForm.failureStrategy = props.row.failureStrategy timingState.timingForm.warningType = props.row.warningType timingState.timingForm.workflowInstancePriority = diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts index e9a6f9517ebc..9329c251737a 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts @@ -136,7 +136,7 @@ export const useForm = () => { ], crontab: '0 0 * * * ? *', timezoneId: Intl.DateTimeFormat().resolvedOptions().timeZone, - missedFirePolicy: 'FIRE_ONCE_NOW', + missedFirePolicy: 'FIRE_ALL_MISSED', failureStrategy: 'CONTINUE', warningType: 'NONE', workflowInstancePriority: 'MEDIUM', From 060743fd25e93f16c0d409f7b2a6470deb3f08af Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Thu, 6 Aug 2026 19:29:45 +0800 Subject: [PATCH 4/7] fix: address missed fire policy review feedback --- docs/docs/en/guide/upgrade/incompatible.md | 4 ++++ docs/docs/zh/guide/upgrade/incompatible.md | 4 ++++ .../service/impl/SchedulerServiceImpl.java | 9 ++------- .../mysql/dolphinscheduler_ddl_post.sql | 19 ------------------- .../postgresql/dolphinscheduler_ddl_post.sql | 19 ------------------- .../mysql/dolphinscheduler_ddl.sql | 2 ++ .../postgresql/dolphinscheduler_ddl.sql | 2 ++ .../quartz/CronScheduleBuilderFactory.java | 3 ++- ...reAllMissedCronScheduleBuilderFactory.java | 10 +++++++--- ...FireOnceNowCronScheduleBuilderFactory.java | 10 +++++++--- .../quartz/QuartzCornTriggerBuilder.java | 3 +-- .../SkipMissedCronScheduleBuilderFactory.java | 10 +++++++--- .../CronScheduleBuilderFactoryTest.java | 13 ++++++++++--- 13 files changed, 48 insertions(+), 60 deletions(-) delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql delete mode 100644 dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql diff --git a/docs/docs/en/guide/upgrade/incompatible.md b/docs/docs/en/guide/upgrade/incompatible.md index 0f60fcb9986a..026d9b88bbcb 100644 --- a/docs/docs/en/guide/upgrade/incompatible.md +++ b/docs/docs/en/guide/upgrade/incompatible.md @@ -44,3 +44,7 @@ This document records the incompatible updates between each version. You need to * Remove import and export of workflow definition. ([#17940])(https://github.com/apache/dolphinscheduler/issues/17940) +## 3.5.0 + +* Add the `missed_fire_policy` column to `t_ds_schedules`. Existing schedules default to `FIRE_ALL_MISSED` to preserve the previous Quartz `IgnoreMisfires` behavior. ([#18464](https://github.com/apache/dolphinscheduler/pull/18464)) + diff --git a/docs/docs/zh/guide/upgrade/incompatible.md b/docs/docs/zh/guide/upgrade/incompatible.md index 2f0c4c044ff8..301d4e66d30b 100644 --- a/docs/docs/zh/guide/upgrade/incompatible.md +++ b/docs/docs/zh/guide/upgrade/incompatible.md @@ -48,3 +48,7 @@ * 移除导入导出工作流([#17940])(https://github.com/apache/dolphinscheduler/issues/17940) +## 3.5.0 + +* 为 `t_ds_schedules` 表新增 `missed_fire_policy` 字段。现有定时默认使用 `FIRE_ALL_MISSED`,以保持原有 Quartz `IgnoreMisfires` 行为。([#18464](https://github.com/apache/dolphinscheduler/pull/18464)) + diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index a167f0f5772a..23bb799a9249 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,7 +34,6 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -173,7 +172,7 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - scheduleObj.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); + scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -403,10 +402,6 @@ public List previewSchedule(User loginUser, String schedule) { .collect(Collectors.toList()); } - private ScheduleMissedFirePolicy defaultMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { - return missedFirePolicy == null ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED : missedFirePolicy; - } - /** * update workflow definition schedule * @@ -563,7 +558,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); - schedule.setMissedFirePolicy(defaultMissedFirePolicy(scheduleParam.getMissedFirePolicy())); + schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index fbf620b4c825..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/mysql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -ALTER TABLE `t_ds_schedules` - ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql deleted file mode 100644 index 0aaff6c8eea4..000000000000 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.3.2_schema/postgresql/dolphinscheduler_ddl_post.sql +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -ALTER TABLE t_ds_schedules - ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql index 3f7d3175979f..afeb9917b86b 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/mysql/dolphinscheduler_ddl.sql @@ -17,4 +17,6 @@ ALTER TABLE `t_ds_task_instance` ADD INDEX idx_project_submit_time (project_code ASC, submit_time DESC); ALTER TABLE `t_ds_workflow_instance` ADD INDEX idx_project_start_time (project_code ASC, start_time DESC); +ALTER TABLE `t_ds_schedules` + ADD COLUMN `missed_fire_policy` tinyint NOT NULL DEFAULT '2' COMMENT 'missed fire policy: 0 skip missed, 1 fire once now, 2 fire all missed' AFTER `crontab`; diff --git a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql index 61a5ae809a13..1709fe57e737 100644 --- a/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql +++ b/dolphinscheduler-dao/src/main/resources/sql/upgrade/3.5.0_schema/postgresql/dolphinscheduler_ddl.sql @@ -17,3 +17,5 @@ CREATE INDEX idx_project_submit_time ON t_ds_task_instance (project_code ASC, submit_time DESC); CREATE INDEX idx_project_start_time ON t_ds_workflow_instance (project_code ASC, start_time DESC); +ALTER TABLE t_ds_schedules + ADD COLUMN missed_fire_policy smallint NOT NULL DEFAULT 2; diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java index 1784aeae044e..4702995a973a 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactory.java @@ -18,12 +18,13 @@ package org.apache.dolphinscheduler.scheduler.quartz; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.dao.entity.Schedule; import org.quartz.CronScheduleBuilder; interface CronScheduleBuilderFactory { - CronScheduleBuilder createCronScheduleBuilder(String cronExpression); + CronScheduleBuilder createCronScheduleBuilder(Schedule schedule); static CronScheduleBuilderFactory getFactory(ScheduleMissedFirePolicy missedFirePolicy) { ScheduleMissedFirePolicy effectivePolicy = missedFirePolicy == null diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java index 3bc89ff25c94..b3022d1cb569 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireAllMissedCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class FireAllMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionIgnoreMisfires(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionIgnoreMisfires() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java index ea354cbd3e77..9990f429fd63 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/FireOnceNowCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class FireOnceNowCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionFireAndProceed(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionFireAndProceed() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java index e28d6c77ede2..270f06eb970a 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/QuartzCornTriggerBuilder.java @@ -82,8 +82,7 @@ public CronTrigger build() { TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()); CronScheduleBuilder scheduleBuilder = CronScheduleBuilderFactory.getFactory(schedule.getMissedFirePolicy()) - .createCronScheduleBuilder(schedule.getCrontab()) - .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); + .createCronScheduleBuilder(schedule); return TriggerBuilder.newTrigger() .withIdentity(triggerKey) diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java index 7a7f87bbc613..2d948085acca 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/main/java/org/apache/dolphinscheduler/scheduler/quartz/SkipMissedCronScheduleBuilderFactory.java @@ -17,13 +17,17 @@ package org.apache.dolphinscheduler.scheduler.quartz; +import org.apache.dolphinscheduler.common.utils.DateUtils; +import org.apache.dolphinscheduler.dao.entity.Schedule; + import org.quartz.CronScheduleBuilder; final class SkipMissedCronScheduleBuilderFactory implements CronScheduleBuilderFactory { @Override - public CronScheduleBuilder createCronScheduleBuilder(String cronExpression) { - return CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionDoNothing(); + public CronScheduleBuilder createCronScheduleBuilder(Schedule schedule) { + return CronScheduleBuilder.cronSchedule(schedule.getCrontab()) + .withMisfireHandlingInstructionDoNothing() + .inTimeZone(DateUtils.getTimezone(schedule.getTimezoneId())); } } diff --git a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java index 9d0b135eb327..f006472188b0 100644 --- a/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java +++ b/dolphinscheduler-scheduler-plugin/dolphinscheduler-scheduler-quartz/src/test/java/org/apache/dolphinscheduler/scheduler/quartz/CronScheduleBuilderFactoryTest.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.dao.entity.Schedule; import org.junit.jupiter.api.Test; import org.quartz.CronTrigger; @@ -30,6 +31,8 @@ class CronScheduleBuilderFactoryTest { private static final String CRON_EXPRESSION = "0 0 * * * ?"; + private static final String TIMEZONE_ID = "Asia/Shanghai"; + @Test void shouldCreateSkipMissedCronScheduleBuilder() { assertFactoryAndMisfireInstruction( @@ -66,10 +69,14 @@ private void assertFactoryAndMisfireInstruction( ScheduleMissedFirePolicy policy, Class expectedFactoryClass, int expectedMisfireInstruction) { + Schedule schedule = new Schedule(); + schedule.setCrontab(CRON_EXPRESSION); + schedule.setTimezoneId(TIMEZONE_ID); CronScheduleBuilderFactory factory = CronScheduleBuilderFactory.getFactory(policy); assertInstanceOf(expectedFactoryClass, factory); - assertEquals( - expectedMisfireInstruction, - factory.createCronScheduleBuilder(CRON_EXPRESSION).build().getMisfireInstruction()); + Trigger trigger = factory.createCronScheduleBuilder(schedule).build(); + CronTrigger cronTrigger = assertInstanceOf(CronTrigger.class, trigger); + assertEquals(expectedMisfireInstruction, cronTrigger.getMisfireInstruction()); + assertEquals(TIMEZONE_ID, cronTrigger.getTimeZone().getID()); } } From dcf1d0d7d65b4634c4c69e85986b58f2d5ab707f Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Fri, 7 Aug 2026 14:53:01 +0800 Subject: [PATCH 5/7] fix(schedule): serialize missedFirePolicy and preserve existing policy on update Address review feedback (SbloodyS): - Frontend: include missedFirePolicy in the schedule create/update payload so the selected policy is actually persisted to the backend. - Backend: distinguish an omitted JSON field from an explicit value. A new missedFirePolicySet marker tracks field presence, because Jackson cannot tell omission from an explicit null. - create: omitted or explicit null falls back to FIRE_ALL_MISSED - update: when the client omits the field (e.g. an older client), the existing stored policy is preserved instead of being overwritten - Add unit tests covering create/update semantics and JSON presence detection. Co-Authored-By: WorkBuddy --- .../api/dto/ScheduleParam.java | 14 ++ .../service/impl/SchedulerServiceImpl.java | 10 +- .../api/service/SchedulerServiceTest.java | 120 ++++++++++++++++++ .../definition/components/use-modal.ts | 3 +- 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java index 6369f56e4881..7d70c77b9c3a 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/ScheduleParam.java @@ -23,6 +23,8 @@ import lombok.Data; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * schedule parameters */ @@ -35,6 +37,9 @@ public class ScheduleParam { private String timezoneId; private ScheduleMissedFirePolicy missedFirePolicy = ScheduleMissedFirePolicy.FIRE_ALL_MISSED; + @JsonIgnore + private boolean missedFirePolicySet; + public ScheduleParam() { } @@ -45,6 +50,15 @@ public ScheduleParam(Date startTime, Date endTime, String timezoneId, String cro this.crontab = crontab; } + public void setMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + this.missedFirePolicy = missedFirePolicy; + this.missedFirePolicySet = true; + } + + public boolean isMissedFirePolicySet() { + return missedFirePolicySet; + } + @Override public String toString() { return "ScheduleParam{" diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 23bb799a9249..219e80147434 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,6 +34,7 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -172,7 +173,10 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + ScheduleMissedFirePolicy missedFirePolicy = scheduleParam.getMissedFirePolicy(); + scheduleObj.setMissedFirePolicy(missedFirePolicy == null + ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED + : missedFirePolicy); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -558,7 +562,9 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); - schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { + schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); + } schedule.setTimezoneId(scheduleParam.getTimezoneId()); } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index 20ea9e3ea4a1..c3572a4b1368 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -17,10 +17,17 @@ package org.apache.dolphinscheduler.api.service; +import org.apache.dolphinscheduler.api.dto.ScheduleParam; import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.service.impl.SchedulerServiceImpl; +import org.apache.dolphinscheduler.api.validator.TenantExistValidator; +import org.apache.dolphinscheduler.common.enums.FailureStrategy; +import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; +import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; +import org.apache.dolphinscheduler.common.enums.WarningType; +import org.apache.dolphinscheduler.common.utils.JSONUtils; import org.apache.dolphinscheduler.dao.entity.Project; import org.apache.dolphinscheduler.dao.entity.Schedule; import org.apache.dolphinscheduler.dao.entity.User; @@ -28,6 +35,7 @@ import org.apache.dolphinscheduler.dao.repository.ProjectDao; import org.apache.dolphinscheduler.dao.repository.ScheduleDao; import org.apache.dolphinscheduler.dao.repository.WorkflowDefinitionDao; +import org.apache.dolphinscheduler.scheduler.api.SchedulerApi; import java.util.Optional; @@ -35,6 +43,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -61,6 +72,15 @@ public class SchedulerServiceTest extends BaseServiceTestTool { @Mock private ProjectService projectService; + @Mock + private ExecutorService executorService; + + @Mock + private TenantExistValidator tenantExistValidator; + + @Mock + private SchedulerApi schedulerApi; + protected static User user; protected Exception exception; private static final String userName = "userName"; @@ -80,6 +100,106 @@ public void setUp() { user.setId(userId); } + @Test + public void testScheduleParamMissedFirePolicyPresence() { + String scheduleWithoutPolicy = "{\"startTime\":\"2019-12-16 00:00:00\"," + + "\"endTime\":\"2019-12-17 00:00:00\",\"crontab\":\"0 0 6 * * ? *\"}"; + String scheduleWithPolicy = "{\"startTime\":\"2019-12-16 00:00:00\"," + + "\"endTime\":\"2019-12-17 00:00:00\",\"crontab\":\"0 0 6 * * ? *\"," + + "\"missedFirePolicy\":\"SKIP_MISSED\"}"; + + ScheduleParam withoutPolicy = JSONUtils.parseObject(scheduleWithoutPolicy, ScheduleParam.class); + ScheduleParam withPolicy = JSONUtils.parseObject(scheduleWithPolicy, ScheduleParam.class); + + Assertions.assertEquals(ScheduleMissedFirePolicy.FIRE_ALL_MISSED, withoutPolicy.getMissedFirePolicy()); + Assertions.assertFalse(withoutPolicy.isMissedFirePolicySet()); + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, withPolicy.getMissedFirePolicy()); + Assertions.assertTrue(withPolicy.isMissedFirePolicySet()); + } + + @ParameterizedTest + @EnumSource(ScheduleMissedFirePolicy.class) + public void testInsertScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + Project project = this.getProject(); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Schedule insertedSchedule = new Schedule(); + insertedSchedule.setId(scheduleId); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(project); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(insertedSchedule); + + Schedule result = schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + ArgumentCaptor scheduleCaptor = ArgumentCaptor.forClass(Schedule.class); + Mockito.verify(scheduleDao).insert(scheduleCaptor.capture()); + Assertions.assertEquals(missedFirePolicy, scheduleCaptor.getValue().getMissedFirePolicy()); + Assertions.assertSame(insertedSchedule, result); + } + + @Test + public void testInsertScheduleDefaultsMissedFirePolicy() { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(new Schedule()); + + schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + ArgumentCaptor scheduleCaptor = ArgumentCaptor.forClass(Schedule.class); + Mockito.verify(scheduleDao).insert(scheduleCaptor.capture()); + Assertions.assertEquals(ScheduleMissedFirePolicy.FIRE_ALL_MISSED, + scheduleCaptor.getValue().getMissedFirePolicy()); + } + + @ParameterizedTest + @EnumSource(ScheduleMissedFirePolicy.class) + public void testUpdateScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + Assertions.assertEquals(missedFirePolicy, schedule.getMissedFirePolicy()); + } + + @Test + public void testUpdateSchedulePreservesMissedFirePolicyWhenOmitted() { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setMissedFirePolicy(ScheduleMissedFirePolicy.SKIP_MISSED); + WorkflowDefinition workflowDefinition = this.getProcessDefinition(); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(workflowDefinition)); + + schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpression(null), WarningType.NONE, 0, + FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", environmentCode); + + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); + } + + private String scheduleExpression(ScheduleMissedFirePolicy missedFirePolicy) { + String policy = missedFirePolicy == null ? "" : ",\"missedFirePolicy\":\"" + missedFirePolicy.name() + "\""; + return "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + + "\"crontab\":\"0 0 6 * * ? *\",\"timezoneId\":\"Asia/Shanghai\"" + policy + "}"; + } + @Test public void testDeleteSchedules() { Schedule schedule = this.getSchedule(); diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts index 4baac0f1d1dc..203ae5bc447e 100644 --- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts +++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-modal.ts @@ -180,7 +180,8 @@ export function useModal( startTime: start, endTime: end, crontab: state.timingForm.crontab, - timezoneId: state.timingForm.timezoneId + timezoneId: state.timingForm.timezoneId, + missedFirePolicy: state.timingForm.missedFirePolicy }), failureStrategy: state.timingForm.failureStrategy, warningType: state.timingForm.warningType, From c804e50cf51a43a0753dd2f79766c4717de49c52 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Fri, 7 Aug 2026 16:36:49 +0800 Subject: [PATCH 6/7] test(api): fix schedule insert mock --- .../dolphinscheduler/api/service/SchedulerServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index c3572a4b1368..d6da88e731aa 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -128,7 +128,7 @@ public void testInsertScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy miss Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) .thenReturn(Optional.of(workflowDefinition)); - Mockito.when(scheduleDao.queryById(Mockito.anyInt())).thenReturn(insertedSchedule); + Mockito.when(scheduleDao.queryById(Mockito.any())).thenReturn(insertedSchedule); Schedule result = schedulerService.insertSchedule( user, projectCode, processDefinitionCode, scheduleExpression(missedFirePolicy), WarningType.NONE, 0, From 1831d55e5d6be9b00d1712de464e7da3411ead49 Mon Sep 17 00:00:00 2001 From: liangwenjie2021 Date: Mon, 10 Aug 2026 14:16:41 +0800 Subject: [PATCH 7/7] fix(api): reject invalid missed fire policy --- .../service/impl/SchedulerServiceImpl.java | 15 +++-- .../api/service/SchedulerServiceTest.java | 67 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java index 219e80147434..7b0ae201c947 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java @@ -34,7 +34,6 @@ import org.apache.dolphinscheduler.common.enums.FailureStrategy; import org.apache.dolphinscheduler.common.enums.Priority; import org.apache.dolphinscheduler.common.enums.ReleaseState; -import org.apache.dolphinscheduler.common.enums.ScheduleMissedFirePolicy; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.WarningType; import org.apache.dolphinscheduler.common.utils.DateUtils; @@ -173,10 +172,8 @@ public Schedule insertSchedule(User loginUser, throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, scheduleParam.getCrontab()); } scheduleObj.setCrontab(scheduleParam.getCrontab()); - ScheduleMissedFirePolicy missedFirePolicy = scheduleParam.getMissedFirePolicy(); - scheduleObj.setMissedFirePolicy(missedFirePolicy == null - ? ScheduleMissedFirePolicy.FIRE_ALL_MISSED - : missedFirePolicy); + validateMissedFirePolicy(scheduleParam); + scheduleObj.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); scheduleObj.setTimezoneId(scheduleParam.getTimezoneId()); scheduleObj.setWarningType(warningType); scheduleObj.setWarningGroupId(warningGroupId); @@ -562,6 +559,7 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe throw new ServiceException(Status.SCHEDULE_CRON_CHECK_FAILED, scheduleParam.getCrontab()); } schedule.setCrontab(scheduleParam.getCrontab()); + validateMissedFirePolicy(scheduleParam); if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() != null) { schedule.setMissedFirePolicy(scheduleParam.getMissedFirePolicy()); } @@ -593,4 +591,11 @@ private Schedule updateSchedule(Schedule schedule, WorkflowDefinition workflowDe return schedule; } + private void validateMissedFirePolicy(ScheduleParam scheduleParam) { + if (scheduleParam.isMissedFirePolicySet() && scheduleParam.getMissedFirePolicy() == null) { + log.warn("Schedule missed fire policy is invalid."); + throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, "missedFirePolicy"); + } + } + } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java index d6da88e731aa..3c9a9dedc696 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java @@ -158,6 +158,16 @@ user, projectCode, processDefinitionCode, scheduleExpression(null), WarningType. scheduleCaptor.getValue().getMissedFirePolicy()); } + @Test + public void testInsertScheduleRejectsExplicitNullMissedFirePolicy() { + assertInsertScheduleRejectsInvalidMissedFirePolicy("null"); + } + + @Test + public void testInsertScheduleRejectsUnknownMissedFirePolicy() { + assertInsertScheduleRejectsInvalidMissedFirePolicy("\"FIRE_ONCE_NWO\""); + } + @ParameterizedTest @EnumSource(ScheduleMissedFirePolicy.class) public void testUpdateScheduleWithMissedFirePolicy(ScheduleMissedFirePolicy missedFirePolicy) { @@ -194,8 +204,65 @@ user, projectCode, scheduleId, scheduleExpression(null), WarningType.NONE, 0, Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); } + @Test + public void testUpdateScheduleRejectsExplicitNullMissedFirePolicy() { + assertUpdateScheduleRejectsInvalidMissedFirePolicy("null"); + } + + @Test + public void testUpdateScheduleRejectsUnknownMissedFirePolicy() { + assertUpdateScheduleRejectsInvalidMissedFirePolicy("\"FIRE_ONCE_NWO\""); + } + + private void assertInsertScheduleRejectsInvalidMissedFirePolicy(String missedFirePolicyValue) { + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryByWorkflowDefinitionCode(processDefinitionCode)).thenReturn(null); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + + exception = Assertions.assertThrows(ServiceException.class, + () -> schedulerService.insertSchedule( + user, projectCode, processDefinitionCode, + scheduleExpressionWithPolicyValue(missedFirePolicyValue), + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode)); + + Assertions.assertEquals(Status.REQUEST_PARAMS_NOT_VALID_ERROR.getCode(), + ((ServiceException) exception).getCode()); + Mockito.verify(scheduleDao, Mockito.never()).insert(Mockito.any()); + } + + private void assertUpdateScheduleRejectsInvalidMissedFirePolicy(String missedFirePolicyValue) { + Schedule schedule = this.getSchedule(); + schedule.setReleaseState(ReleaseState.OFFLINE); + schedule.setMissedFirePolicy(ScheduleMissedFirePolicy.SKIP_MISSED); + Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject()); + Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule); + Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode)) + .thenReturn(Optional.of(this.getProcessDefinition())); + + exception = Assertions.assertThrows(ServiceException.class, + () -> schedulerService.updateSchedule( + user, projectCode, scheduleId, scheduleExpressionWithPolicyValue(missedFirePolicyValue), + WarningType.NONE, 0, FailureStrategy.CONTINUE, Priority.MEDIUM, "default", "tenantCode", + environmentCode)); + + Assertions.assertEquals(Status.REQUEST_PARAMS_NOT_VALID_ERROR.getCode(), + ((ServiceException) exception).getCode()); + Assertions.assertEquals(ScheduleMissedFirePolicy.SKIP_MISSED, schedule.getMissedFirePolicy()); + Mockito.verify(scheduleDao, Mockito.never()).updateById(Mockito.any()); + } + private String scheduleExpression(ScheduleMissedFirePolicy missedFirePolicy) { String policy = missedFirePolicy == null ? "" : ",\"missedFirePolicy\":\"" + missedFirePolicy.name() + "\""; + return scheduleExpressionWithPolicy(policy); + } + + private String scheduleExpressionWithPolicyValue(String missedFirePolicyValue) { + return scheduleExpressionWithPolicy(",\"missedFirePolicy\":" + missedFirePolicyValue); + } + + private String scheduleExpressionWithPolicy(String policy) { return "{\"startTime\":\"2019-12-16 00:00:00\",\"endTime\":\"2019-12-17 00:00:00\"," + "\"crontab\":\"0 0 6 * * ? *\",\"timezoneId\":\"Asia/Shanghai\"" + policy + "}"; }