diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java
new file mode 100644
index 000000000000..e59aacfea15e
--- /dev/null
+++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.validator;
+
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Validator for the list of start parameters (Property list).
+ *
If startParamList is not valid, an {@link IllegalArgumentException} will be thrown.
+ */
+@Slf4j
+@Component
+public class StartParamListValidator implements IValidator> {
+
+ @Override
+ public void validate(List startParamList) {
+ if (CollectionUtils.isEmpty(startParamList)) {
+ return;
+ }
+
+ Set keys = new HashSet<>();
+ for (Property param : startParamList) {
+ if (StringUtils.isBlank(param.getProp())) {
+ throw new IllegalArgumentException("Parameter key cannot be empty");
+ }
+
+ String key = param.getProp().trim();
+ if (keys.contains(key)) {
+ throw new IllegalArgumentException("Duplicate parameter key: " + key);
+ }
+ keys.add(key);
+ }
+ }
+}
diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
index 0b5da7e85a3f..08fa5c2b2b8a 100644
--- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
+++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
@@ -18,6 +18,7 @@
package org.apache.dolphinscheduler.api.validator.workflow;
import org.apache.dolphinscheduler.api.validator.IValidator;
+import org.apache.dolphinscheduler.api.validator.StartParamListValidator;
import org.apache.dolphinscheduler.api.validator.TenantExistValidator;
import org.apache.dolphinscheduler.common.enums.CommandType;
import org.apache.dolphinscheduler.common.enums.ReleaseState;
@@ -34,8 +35,12 @@ public class BackfillWorkflowDTOValidator implements IValidator startParamListValidator.validate(null))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testValidate_emptyList() {
+ assertThatCode(() -> startParamListValidator.validate(Collections.emptyList()))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testValidate_validParameters() {
+ List params = Collections.singletonList(
+ new Property("workflow_id", Direct.IN, DataType.VARCHAR, "1001"));
+ assertThatCode(() -> startParamListValidator.validate(params))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testValidate_rejectsBlankOrEmptyKeys() {
+ assertThrowsIllegalArgument("");
+
+ assertThrowsIllegalArgument(" ");
+
+ assertThrowsIllegalArgument("\t");
+
+ assertThrowsIllegalArgument("\n");
+
+ assertThrowsIllegalArgument(" \t\n ");
+ }
+
+ private void assertThrowsIllegalArgument(String propValue) {
+ List params = Collections.singletonList(
+ new Property(propValue, Direct.IN, DataType.VARCHAR, "dummyValue"));
+
+ assertThatThrownBy(() -> startParamListValidator.validate(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Parameter key cannot be empty");
+ }
+
+ @Test
+ void testValidate_duplicateKeys() {
+ List params = new ArrayList<>();
+ params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "A"));
+ params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "B"));
+
+ assertThatThrownBy(() -> startParamListValidator.validate(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Duplicate parameter key: app_name");
+ }
+
+ @Test
+ void testValidate_duplicateKeysAfterTrim() {
+ List params = new ArrayList<>();
+ params.add(new Property(" app_name ", Direct.IN, DataType.VARCHAR, "A"));
+ params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "B"));
+
+ assertThatThrownBy(() -> startParamListValidator.validate(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Duplicate parameter key: app_name");
+ }
+
+ @Test
+ void testValidate_inTypeEmptyValueAllowed() {
+ List params = Collections.singletonList(
+ new Property("input_var", Direct.IN, DataType.VARCHAR, ""));
+
+ assertThatCode(() -> startParamListValidator.validate(params))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testValidate_outTypeEmptyValueAllowed() {
+ List params = Collections.singletonList(
+ new Property("output_var", Direct.OUT, DataType.VARCHAR, ""));
+
+ assertThatCode(() -> startParamListValidator.validate(params))
+ .doesNotThrowAnyException();
+ }
+}
diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts
index 30ea28d8f421..59edc7dfeaed 100644
--- a/dolphinscheduler-ui/src/locales/en_US/project.ts
+++ b/dolphinscheduler-ui/src/locales/en_US/project.ts
@@ -353,8 +353,8 @@ export default {
update_directly: 'Whether to update the workflow definition',
dag_name_empty: 'DAG graph name cannot be empty',
positive_integer: 'Please enter a positive integer greater than 0',
- prop_empty: 'prop is empty',
- prop_repeat: 'prop is repeat',
+ prop_key_repeat: 'prop key is repeat',
+ prop_key_empty: 'prop key is empty',
node_not_created: 'Failed to save node not created',
copy_name: 'Copy Name',
view_variables: 'View Variables',
diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts
index 82255264bdd4..e6b08093c568 100644
--- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts
+++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts
@@ -347,8 +347,8 @@ export default {
update_directly: '是否更新工作流定义',
dag_name_empty: 'DAG图名称不能为空',
positive_integer: '请输入大于 0 的正整数',
- prop_empty: '自定义参数prop不能为空',
- prop_repeat: 'prop中有重复',
+ prop_key_repeat: '自定义参数prop中key有重复',
+ prop_key_empty: '自定义参数prop中key不能为空',
node_not_created: '未创建节点保存失败',
copy_name: '复制名称',
view_variables: '查看变量',
diff --git a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
index a6b78cda384c..1e39a6d37e4e 100644
--- a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
+++ b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
@@ -98,23 +98,23 @@ export default defineComponent({
},
globalParams: {
validator() {
- const props = new Set()
+ const globalParams = formValue.value.globalParams || []
- const keys = formValue.value.globalParams.map((item) => item.key)
- const keysSet = new Set(keys)
- if (keysSet.size !== keys.length) {
- return new Error(t('project.dag.prop_repeat'))
- }
+ if (!globalParams || globalParams.length === 0) return true
- for (const param of formValue.value.globalParams) {
- const prop = param.value
- const direct = param.direct
- if (direct === 'IN' && !prop) {
- return new Error(t('project.dag.prop_empty'))
+ for (const param of globalParams) {
+ if (!param.key || param.key.trim() === '') {
+ return new Error(t('project.dag.prop_key_empty'))
}
+ }
- props.add(prop)
+ const keys = globalParams.map((item) => (item.key || '').trim())
+ const uniqueKeys = new Set(keys)
+ if (uniqueKeys.size !== keys.length) {
+ return new Error(t('project.dag.prop_key_repeat'))
}
+
+ return true
}
}
}
diff --git a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
index bb574d2e5045..eea89ad0d4e2 100644
--- a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
+++ b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
@@ -290,6 +290,11 @@ export default defineComponent({
}
)
+ const formModel = computed(() => ({
+ ...startState.startForm,
+ startParamsList: variables.startParamsList
+ }))
+
return {
t,
showTaskDependType,
@@ -302,6 +307,7 @@ export default defineComponent({
removeStartParams,
addStartParams,
updateParamsList,
+ formModel,
...toRefs(variables),
...toRefs(startState),
...toRefs(props),
@@ -319,7 +325,7 @@ export default defineComponent({
onConfirm={this.handleStart}
confirmLoading={this.saving}
>
-
+
{
return {
- key: '',
+ prop: '',
direct: 'IN',
type: 'VARCHAR',
value: ''
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 357531129535..f4955a9d990d 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
@@ -101,6 +101,28 @@ export const useForm = () => {
return new Error(t('project.workflow.warning_group_tip'))
}
}
+ },
+ startParamsList: {
+ trigger: ['input', 'blur'],
+ validator: (rule: any, value: Array) => {
+ const params = value || []
+
+ if (!params || params.length === 0) return true
+
+ for (const param of params) {
+ if (!param.prop || param.prop.trim() === '') {
+ return new Error(t('project.dag.prop_key_empty'))
+ }
+ }
+
+ const keys = params.map((item) => (item.prop || '').trim())
+ const uniqueKeys = new Set(keys)
+ if (uniqueKeys.size !== keys.length) {
+ return new Error(t('project.dag.prop_key_repeat'))
+ }
+
+ return true
+ }
}
}
})