Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f9d12f6
add startParams null check in fronend
Feb 5, 2026
fcd2a9e
add formModel
Feb 5, 2026
eaa61c1
add startParam null check in backend
Feb 5, 2026
88e6c5b
Merge branch 'dev' into Improvement-17942
njnu-seafish Feb 5, 2026
eb90563
Merge branch 'dev' into Improvement-17942
SbloodyS Feb 10, 2026
074ef19
Merge branch 'dev' into Improvement-17942
njnu-seafish Feb 27, 2026
5208505
Merge branch 'apache:dev' into Improvement-17942
njnu-seafish Feb 27, 2026
6f0a5cd
remove comment
Feb 27, 2026
135b559
Merge branch 'dev' into Improvement-17942
njnu-seafish Feb 28, 2026
d6a2730
Merge branch 'dev' into Improvement-17942
SbloodyS Feb 28, 2026
cd8b48a
Merge branch 'dev' into Improvement-17942
SbloodyS Mar 2, 2026
7fd5808
Merge branch 'apache:dev' into Improvement-17942
njnu-seafish Mar 6, 2026
4a0a0d4
add StartParamListValidator
Mar 6, 2026
349cabd
Merge branch 'dev' into Improvement-17942
njnu-seafish Mar 6, 2026
8d28d1d
add test for StartParamListValidator
Mar 6, 2026
4f49fc4
Merge branch 'dev' into Improvement-17942
njnu-seafish Mar 9, 2026
aee758f
allow empty value for IN and OUT params
Mar 9, 2026
1802edb
use StringUtils.isBlank to handle empty
Mar 9, 2026
b7ac7e9
Merge branch 'dev' into Improvement-17942
ruanwenjun Mar 10, 2026
b11ee3f
Merge branch 'dev' into Improvement-17942
ruanwenjun Mar 11, 2026
55a1eb6
Merge branch 'dev' into Improvement-17942
njnu-seafish Mar 11, 2026
c30ce1b
Merge branch 'dev' into Improvement-17942
njnu-seafish Mar 12, 2026
afe2f0e
Merge branch 'dev' into Improvement-17942
SbloodyS Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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).
* <p> If startParamList is not valid, an {@link IllegalArgumentException} will be thrown. </p>
*/
@Slf4j
@Component
public class StartParamListValidator implements IValidator<List<Property>> {

@Override
public void validate(List<Property> startParamList) {
if (CollectionUtils.isEmpty(startParamList)) {
return;
}

Set<String> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,8 +35,12 @@ public class BackfillWorkflowDTOValidator implements IValidator<BackfillWorkflow

private final TenantExistValidator tenantExistValidator;

public BackfillWorkflowDTOValidator(TenantExistValidator tenantExistValidator) {
private final StartParamListValidator startParamListValidator;

public BackfillWorkflowDTOValidator(TenantExistValidator tenantExistValidator,
StartParamListValidator startParamListValidator) {
this.tenantExistValidator = tenantExistValidator;
this.startParamListValidator = startParamListValidator;
}

@Override
Expand All @@ -59,6 +64,9 @@ public void validate(final BackfillWorkflowDTO backfillWorkflowDTO) {
if (backfillWorkflowDTO.getWorkflowDefinition().getReleaseState() != ReleaseState.ONLINE) {
throw new IllegalStateException("The workflowDefinition should be online");
}

tenantExistValidator.validate(backfillWorkflowDTO.getTenantCode());

startParamListValidator.validate(backfillWorkflowDTO.getStartParamList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,8 +33,12 @@ public class TriggerWorkflowDTOValidator implements IValidator<TriggerWorkflowDT

private final TenantExistValidator tenantExistValidator;

public TriggerWorkflowDTOValidator(TenantExistValidator tenantExistValidator) {
private final StartParamListValidator startParamListValidator;

public TriggerWorkflowDTOValidator(TenantExistValidator tenantExistValidator,
StartParamListValidator startParamListValidator) {
this.tenantExistValidator = tenantExistValidator;
this.startParamListValidator = startParamListValidator;
}

@Override
Expand All @@ -47,6 +52,9 @@ public void validate(final TriggerWorkflowDTO triggerWorkflowDTO) {
if (triggerWorkflowDTO.getWorkflowDefinition().getReleaseState() != ReleaseState.ONLINE) {
throw new IllegalStateException("The workflowDefinition should be online");
}

tenantExistValidator.validate(triggerWorkflowDTO.getTenantCode());

startParamListValidator.validate(triggerWorkflowDTO.getStartParamList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.apache.dolphinscheduler.plugin.task.api.enums.DataType;
import org.apache.dolphinscheduler.plugin.task.api.enums.Direct;
import org.apache.dolphinscheduler.plugin.task.api.model.Property;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class StartParamListValidatorTest {

@InjectMocks
private StartParamListValidator startParamListValidator;

@Test
void testValidate_nullList() {
assertThatCode(() -> startParamListValidator.validate(null))
.doesNotThrowAnyException();
}

@Test
void testValidate_emptyList() {
assertThatCode(() -> startParamListValidator.validate(Collections.emptyList()))
.doesNotThrowAnyException();
}

@Test
void testValidate_validParameters() {
List<Property> 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<Property> 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<Property> 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<Property> 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<Property> params = Collections.singletonList(
new Property("input_var", Direct.IN, DataType.VARCHAR, ""));

assertThatCode(() -> startParamListValidator.validate(params))
.doesNotThrowAnyException();
}

@Test
void testValidate_outTypeEmptyValueAllowed() {
List<Property> params = Collections.singletonList(
new Property("output_var", Direct.OUT, DataType.VARCHAR, ""));

assertThatCode(() -> startParamListValidator.validate(params))
.doesNotThrowAnyException();
}
}
4 changes: 2 additions & 2 deletions dolphinscheduler-ui/src/locales/en_US/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions dolphinscheduler-ui/src/locales/zh_CN/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '查看变量',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ export default defineComponent({
}
)

const formModel = computed(() => ({
...startState.startForm,
startParamsList: variables.startParamsList
}))

return {
t,
showTaskDependType,
Expand All @@ -302,6 +307,7 @@ export default defineComponent({
removeStartParams,
addStartParams,
updateParamsList,
formModel,
...toRefs(variables),
...toRefs(startState),
...toRefs(props),
Expand All @@ -319,7 +325,7 @@ export default defineComponent({
onConfirm={this.handleStart}
confirmLoading={this.saving}
>
<NForm ref='startFormRef' model={this.startForm} rules={this.rules}>
<NForm ref='startFormRef' model={this.formModel} rules={this.rules}>
<NFormItem
label={t('project.workflow.workflow_name')}
path='workflow_name'
Expand Down Expand Up @@ -577,13 +583,13 @@ export default defineComponent({
)}
<NFormItem
label={t('project.workflow.startup_parameter')}
path='startup_parameter'
path='startParamsList'
>
<NDynamicInput
v-model:value={this.startParamsList}
onCreate={() => {
return {
key: '',
prop: '',
direct: 'IN',
type: 'VARCHAR',
value: ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>) => {
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
}
}
}
})
Expand Down
Loading