You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@seatunnel.apache.org by ga...@apache.org on 2023/05/10 20:22:20 UTC
[incubator-seatunnel-web] branch add_canvas_job_define updated: [Feature][DynamicForm] Add seatunnel dynamicform module (#49)
This is an automated email from the ASF dual-hosted git repository.
gaojun2048 pushed a commit to branch add_canvas_job_define
in repository https://gitbox.apache.org/repos/asf/incubator-seatunnel-web.git
The following commit(s) were added to refs/heads/add_canvas_job_define by this push:
new fa2e3333 [Feature][DynamicForm] Add seatunnel dynamicform module (#49)
fa2e3333 is described below
commit fa2e3333d4fe338204a58d2d8b565151e8f126dc
Author: Eric <ga...@gmail.com>
AuthorDate: Thu May 11 04:22:15 2023 +0800
[Feature][DynamicForm] Add seatunnel dynamicform module (#49)
---
.github/workflows/backend.yml | 2 +-
pom.xml | 148 ++++++++-
seatunnel-server/pom.xml | 1 +
seatunnel-server/seatunnel-app/pom.xml | 4 +
.../src/main/resources/application.yml | 2 +-
.../app/controller/UserControllerTest.java | 9 +-
.../pom.xml | 42 ++-
.../app/dynamicforms/AbstractFormOption.java | 118 ++++++++
.../app/dynamicforms/AbstractFormSelectOption.java | 49 +++
.../seatunnel/app/dynamicforms/Constants.java | 25 ++
.../app/dynamicforms/DynamicSelectOption.java | 33 +++
.../app/dynamicforms/FormInputOption.java | 56 ++++
.../seatunnel/app/dynamicforms/FormLocale.java | 51 ++++
.../app/dynamicforms/FormOptionBuilder.java | 165 +++++++++++
.../seatunnel/app/dynamicforms/FormStructure.java | 65 ++++
.../app/dynamicforms/FormStructureBuilder.java | 74 +++++
.../app/dynamicforms/FormStructureValidate.java | 273 +++++++++++++++++
.../app/dynamicforms/StaticSelectOption.java | 38 +++
.../exception/FormStructureValidateException.java | 35 +++
.../dynamicforms/validate/AbstractValidate.java | 65 ++++
.../validate/MutuallyExclusiveValidate.java | 42 +++
.../dynamicforms/validate/NonEmptyValidate.java | 30 ++
.../validate/UnionNonEmptyValidate.java | 45 +++
.../app/dynamicforms/validate/ValidateBuilder.java | 78 +++++
.../app/dynamicforms/FormStructureBuilderTest.java | 329 +++++++++++++++++++++
.../seatunnel/app/dynamicforms/TestUtils.java | 34 +++
.../src/test/resources/test_form.json | 1 +
seatunnel-server/seatunnel-server-common/pom.xml | 7 +-
seatunnel-ui/src/router/routes.ts | 2 +-
tools/dependencies/known-dependencies.txt | 7 +-
30 files changed, 1805 insertions(+), 25 deletions(-)
diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index efc139b1..eea3507e 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -125,7 +125,7 @@ jobs:
cache: 'maven'
- name: Install
run: >-
- ./mvnw -B -q install -DskipTests
+ ./mvnw -B -q install -DskipTests -P release
-D"maven.test.skip"=true
-D"maven.javadoc.skip"=true
-D"checkstyle.skip"=true
diff --git a/pom.xml b/pom.xml
index e5195ce0..070562e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -100,7 +100,6 @@
<skipUT>false</skipUT>
<!-- dependency -->
- <seatunnel-common.version>2.1.3</seatunnel-common.version>
<commons.logging.version>1.2</commons.logging.version>
<slf4j.version>1.7.25</slf4j.version>
<jackson.version>2.12.7.1</jackson.version>
@@ -113,14 +112,157 @@
<checker.qual.version>3.10.0</checker.qual.version>
<log4j-core.version>2.17.1</log4j-core.version>
<awaitility.version>4.2.0</awaitility.version>
+ <seatunnel-framework.version>2.3.1</seatunnel-framework.version>
</properties>
<dependencyManagement>
<dependencies>
+ <!-- seatunnel main repository dependency -->
<dependency>
<groupId>org.apache.seatunnel</groupId>
<artifactId>seatunnel-common</artifactId>
- <version>${seatunnel-common.version}</version>
+ <version>${seatunnel-framework.version}</version>
+ <exclusions>
+ <exclusion>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <groupId>org.slf4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-1.2-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-core</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-jackson</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <classifier>optional</classifier>
+ <exclusions>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-properties</artifactId>
+ </exclusion>
+ <exclusion>
+ <artifactId>slf4j-log4j12</artifactId>
+ <groupId>org.slf4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-1.2-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <groupId>org.slf4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-core</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-api</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <exclusions>
+ <exclusion>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <groupId>org.slf4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-1.2-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-api</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-core</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-engine-client</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.auto.service</groupId>
+ <artifactId>auto-service</artifactId>
+ <version>${auto-service.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-plugin-discovery</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-transforms-v2</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ </dependency>
+
+ <!-- seatunnel connector test -->
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>connector-common</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>connector-console</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>connector-fake</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>connector-jdbc</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <scope>test</scope>
</dependency>
<dependency>
@@ -153,7 +295,7 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
-
+
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
diff --git a/seatunnel-server/pom.xml b/seatunnel-server/pom.xml
index 79ef3a31..d6bd26df 100644
--- a/seatunnel-server/pom.xml
+++ b/seatunnel-server/pom.xml
@@ -27,6 +27,7 @@
<packaging>pom</packaging>
<modules>
<module>seatunnel-app</module>
+ <module>seatunnel-dynamicform</module>
<module>seatunnel-spi</module>
<module>seatunnel-scheduler</module>
<module>seatunnel-server-common</module>
diff --git a/seatunnel-server/seatunnel-app/pom.xml b/seatunnel-server/seatunnel-app/pom.xml
index aa41a158..0e94b654 100644
--- a/seatunnel-server/seatunnel-app/pom.xml
+++ b/seatunnel-server/seatunnel-app/pom.xml
@@ -93,6 +93,10 @@
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </exclusion>
</exclusions>
</dependency>
diff --git a/seatunnel-server/seatunnel-app/src/main/resources/application.yml b/seatunnel-server/seatunnel-app/src/main/resources/application.yml
index 7d9f51af..a873c088 100644
--- a/seatunnel-server/seatunnel-app/src/main/resources/application.yml
+++ b/seatunnel-server/seatunnel-app/src/main/resources/application.yml
@@ -40,7 +40,7 @@ ds:
default: gaojun
api:
token: 296fecc6e5f78e6a5aa39106707476fe
- prefix: http://127.0.0.1:12345/dolphinscheduler
+ prefix: http://localhost:12345/dolphinscheduler
jwt:
expireTime: 86400
diff --git a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/controller/UserControllerTest.java b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/controller/UserControllerTest.java
index 4b1b9116..20b706c9 100644
--- a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/controller/UserControllerTest.java
+++ b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/controller/UserControllerTest.java
@@ -28,7 +28,8 @@ import org.apache.seatunnel.app.domain.request.user.AddUserReq;
import org.apache.seatunnel.app.domain.response.user.AddUserRes;
import org.apache.seatunnel.common.utils.JsonUtils;
-import com.fasterxml.jackson.core.type.TypeReference;
+import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference;
+
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -38,6 +39,8 @@ import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import java.lang.reflect.Type;
+
@Disabled("todo:this test is not working, waiting fix")
public class UserControllerTest extends WebMvcApplicationTest {
@@ -59,6 +62,10 @@ public class UserControllerTest extends WebMvcApplicationTest {
.andReturn();
String result = mvcResult.getResponse().getContentAsString();
Result<AddUserRes> data = JsonUtils.parseObject(result, new TypeReference<Result<AddUserRes>>() {
+ @Override
+ public Type getType() {
+ return super.getType();
+ }
});
Assertions.assertTrue(data.isSuccess());
Assertions.assertNotNull(data.getData());
diff --git a/seatunnel-server/seatunnel-server-common/pom.xml b/seatunnel-server/seatunnel-dynamicform/pom.xml
similarity index 52%
copy from seatunnel-server/seatunnel-server-common/pom.xml
copy to seatunnel-server/seatunnel-dynamicform/pom.xml
index 6eb22aac..36376f75 100644
--- a/seatunnel-server/seatunnel-server-common/pom.xml
+++ b/seatunnel-server/seatunnel-dynamicform/pom.xml
@@ -13,27 +13,45 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>seatunnel-server</artifactId>
<groupId>org.apache.seatunnel</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>seatunnel-server-common</artifactId>
-
- <properties>
- <java.version>1.8</java.version>
- </properties>
+ <artifactId>seatunnel-dynamicform</artifactId>
<dependencies>
<dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <scope>provided</scope>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-jackson</artifactId>
+ <classifier>optional</classifier>
</dependency>
</dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormOption.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormOption.java
new file mode 100644
index 00000000..4630bbb3
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormOption.java
@@ -0,0 +1,118 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.app.dynamicforms.validate.AbstractValidate;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonInclude;
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NonNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public abstract class AbstractFormOption<T extends AbstractFormOption, V extends AbstractValidate> {
+
+ // support i18n
+ private final String label;
+ private final String field;
+ private Object defaultValue;
+
+ // support i18n
+ private String description = "";
+ private boolean clearable;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private Map<String, Object> show;
+
+ // support i18n
+ private String placeholder = "";
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private V validate;
+
+ public AbstractFormOption(@NonNull String label, @NonNull String field) {
+ this.label = label;
+ this.field = field;
+ }
+
+ public enum FormType {
+ @JsonProperty("input")
+ INPUT("input"),
+
+ @JsonProperty("select")
+ SELECT("select");
+
+ @Getter
+ private String formType;
+
+ FormType(String formType) {
+ this.formType = formType;
+ }
+ }
+
+ public T withShow(@NonNull String field, @NonNull List<Object> values) {
+ if (this.show == null) {
+ this.show = new HashMap<>();
+ }
+
+ this.show.put(Constants.SHOW_FIELD, field);
+ this.show.put(Constants.SHOW_VALUE, values);
+ return (T) this;
+ }
+
+ public T withValidate(@NonNull V validate) {
+ this.validate = validate;
+ return (T) this;
+ }
+
+ public T withDefaultValue(Object defaultValue) {
+ this.defaultValue = defaultValue;
+ return (T) this;
+ }
+
+ public T withDescription(@NonNull String description) {
+ this.description = description;
+ return (T) this;
+ }
+
+ public T withI18nDescription(@NonNull String description) {
+ this.description = FormLocale.I18N_PREFIX + description;
+ return (T) this;
+ }
+
+ public T withClearable() {
+ this.clearable = true;
+ return (T) this;
+ }
+
+ public T withPlaceholder(@NonNull String placeholder) {
+ this.placeholder = placeholder;
+ return (T) this;
+ }
+
+ public T withI18nPlaceholder(@NonNull String placeholder) {
+ this.placeholder = FormLocale.I18N_PREFIX + placeholder;
+ return (T) this;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormSelectOption.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormSelectOption.java
new file mode 100644
index 00000000..52b0c534
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/AbstractFormSelectOption.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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.NonNull;
+
+public abstract class AbstractFormSelectOption extends AbstractFormOption {
+
+ @JsonProperty("type")
+ @Getter
+ private final FormType formType = FormType.SELECT;
+
+ public AbstractFormSelectOption(@NonNull String label, @NonNull String field) {
+ super(label, field);
+ }
+
+ public static class SelectOption {
+ @JsonProperty
+ @Getter
+ private String label;
+
+ @JsonProperty
+ @Getter
+ private Object value;
+
+ public SelectOption(@NonNull String label, @NonNull Object value) {
+ this.label = label;
+ this.value = value;
+ }
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/Constants.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/Constants.java
new file mode 100644
index 00000000..df4ac2dc
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/Constants.java
@@ -0,0 +1,25 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+public final class Constants {
+
+ public static final String SHOW_FIELD = "field";
+
+ public static final String SHOW_VALUE = "value";
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/DynamicSelectOption.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/DynamicSelectOption.java
new file mode 100644
index 00000000..d1365cd6
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/DynamicSelectOption.java
@@ -0,0 +1,33 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+
+public class DynamicSelectOption extends AbstractFormSelectOption {
+ @Getter
+ @Setter
+ private String api;
+
+ public DynamicSelectOption(@NonNull String api, @NonNull String label, @NonNull String field) {
+ super(label, field);
+ this.api = api;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormInputOption.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormInputOption.java
new file mode 100644
index 00000000..f71d64ac
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormInputOption.java
@@ -0,0 +1,56 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Getter;
+import lombok.NonNull;
+
+public class FormInputOption extends AbstractFormOption {
+ @JsonProperty("type")
+ @Getter
+ private final FormType formType = FormType.INPUT;
+
+ @Getter
+ private final InputType inputType;
+
+ public FormInputOption(
+ @NonNull InputType inputType, @NonNull String label, @NonNull String field) {
+ super(label, field);
+ this.inputType = inputType;
+ }
+
+ public enum InputType {
+ @JsonProperty("text")
+ TEXT("text"),
+
+ @JsonProperty("password")
+ PASSWORD("password"),
+
+ @JsonProperty("textarea")
+ TEXTAREA("textarea");
+
+ @Getter
+ private String inputType;
+
+ InputType(String inputType) {
+ this.inputType = inputType;
+ }
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormLocale.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormLocale.java
new file mode 100644
index 00000000..466b03c6
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormLocale.java
@@ -0,0 +1,51 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+import lombok.NonNull;
+
+import java.util.LinkedHashMap;
+
+/**
+ * Multi language support
+ */
+@Data
+public class FormLocale {
+ @JsonIgnore
+ public static final String I18N_PREFIX = "i18n.";
+
+ @JsonProperty("zh_CN")
+ private LinkedHashMap<String, String> zhCN = new LinkedHashMap<>();
+
+ @JsonProperty("en_US")
+ private LinkedHashMap<String, String> enUS = new LinkedHashMap<>();
+
+ public FormLocale addZhCN(@NonNull String key, @NonNull String value) {
+ zhCN.put(key, value);
+ return this;
+ }
+
+ public FormLocale addEnUS(@NonNull String key, @NonNull String value) {
+ enUS.put(key, value);
+ return this;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormOptionBuilder.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormOptionBuilder.java
new file mode 100644
index 00000000..8c645abf
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormOptionBuilder.java
@@ -0,0 +1,165 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import lombok.NonNull;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FormOptionBuilder {
+
+ private String label;
+
+ private String field;
+
+ public static FormOptionBuilder builder() {
+ return new FormOptionBuilder();
+ }
+
+ public FormOptionBuilder withLabel(@NonNull String label) {
+ this.label = label;
+ return this;
+ }
+
+ public FormOptionBuilder withI18nLabel(@NonNull String label) {
+ this.label = FormLocale.I18N_PREFIX + label;
+ return this;
+ }
+
+ public FormOptionBuilder withField(@NonNull String field) {
+ this.field = field;
+ return this;
+ }
+
+ public InputOptionBuilder inputOptionBuilder() {
+ return new InputOptionBuilder(label, field);
+ }
+
+ public DynamicSelectOptionBuilder dynamicSelectOptionBuilder() {
+ return new DynamicSelectOptionBuilder(label, field);
+ }
+
+ public StaticSelectOptionBuilder staticSelectOptionBuilder() {
+ return new StaticSelectOptionBuilder(label, field);
+ }
+
+ public static class InputOptionBuilder {
+ private String label;
+
+ private String field;
+
+ public InputOptionBuilder(@NonNull String label, @NonNull String field) {
+ this.label = label;
+ this.field = field;
+ }
+
+ public FormInputOption formTextInputOption() {
+ return new FormInputOption(FormInputOption.InputType.TEXT, label, field);
+ }
+
+ public FormInputOption formPasswordInputOption() {
+ return new FormInputOption(FormInputOption.InputType.PASSWORD, label, field);
+ }
+
+ public FormInputOption formTextareaInputOption() {
+ return new FormInputOption(FormInputOption.InputType.TEXTAREA, label, field);
+ }
+ }
+
+ public static class DynamicSelectOptionBuilder {
+ private String label;
+
+ private String field;
+
+ private String selectApi;
+
+ public DynamicSelectOptionBuilder(@NonNull String label, @NonNull String field) {
+ this.label = label;
+ this.field = field;
+ }
+
+ public DynamicSelectOptionBuilder withSelectApi(@NonNull String selectApi) {
+ this.selectApi = selectApi;
+ return this;
+ }
+
+ public DynamicSelectOption formDynamicSelectOption() {
+ return new DynamicSelectOption(selectApi, label, field);
+ }
+ }
+
+ public static class StaticSelectOptionBuilder {
+ private String label;
+
+ private String field;
+
+ private List<AbstractFormSelectOption.SelectOption> options = new ArrayList<>();
+
+ public StaticSelectOptionBuilder(@NonNull String label, @NonNull String field) {
+ this.label = label;
+ this.field = field;
+ }
+
+ public StaticSelectOptionBuilder addSelectOptions(
+ @NonNull List<ImmutablePair> selectOptions) {
+ for (ImmutablePair option : selectOptions) {
+ options.add(
+ new AbstractFormSelectOption.SelectOption(
+ option.left.toString(), option.right.toString()));
+ }
+ return this;
+ }
+
+ public StaticSelectOptionBuilder addI18nSelectOptions(
+ @NonNull List<ImmutablePair> selectOptions) {
+ for (ImmutablePair option : selectOptions) {
+ options.add(
+ new AbstractFormSelectOption.SelectOption(
+ FormLocale.I18N_PREFIX + option.left.toString(),
+ option.right.toString()));
+ }
+ return this;
+ }
+
+ public StaticSelectOptionBuilder addSelectOptions(@NonNull ImmutablePair... selectOptions) {
+ for (ImmutablePair option : selectOptions) {
+ options.add(
+ new AbstractFormSelectOption.SelectOption(
+ option.left.toString(), option.right.toString()));
+ }
+ return this;
+ }
+
+ public StaticSelectOptionBuilder addI18nSelectOptions(
+ @NonNull ImmutablePair... selectOptions) {
+ for (ImmutablePair option : selectOptions) {
+ options.add(
+ new AbstractFormSelectOption.SelectOption(
+ FormLocale.I18N_PREFIX + option.left.toString(),
+ option.right.toString()));
+ }
+ return this;
+ }
+
+ public StaticSelectOption formStaticSelectOption() {
+ return new StaticSelectOption(options, label, field);
+ }
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructure.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructure.java
new file mode 100644
index 00000000..6096b596
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructure.java
@@ -0,0 +1,65 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonIgnoreType;
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonInclude;
+
+import lombok.Data;
+import lombok.NonNull;
+
+import java.util.List;
+import java.util.Map;
+
+/** SeaTunnel Web UI will use this json data to automatically create page form elements */
+@Data
+public class FormStructure {
+ private String name;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private FormLocale locales;
+
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private Map<String, Map<String, String>> apis;
+
+ private List<AbstractFormOption> forms;
+
+ public FormStructure() {}
+
+ public FormStructure(
+ @NonNull String name,
+ @NonNull List<AbstractFormOption> formOptionList,
+ FormLocale locale,
+ Map<String, Map<String, String>> apis) {
+ this.name = name;
+ this.forms = formOptionList;
+ this.locales = locale;
+ this.apis = apis;
+ }
+
+ @JsonIgnoreType
+ public enum HttpMethod {
+ GET,
+
+ POST
+ }
+
+ public static FormStructureBuilder builder() {
+ return new FormStructureBuilder();
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilder.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilder.java
new file mode 100644
index 00000000..ba863680
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.app.dynamicforms.exception.FormStructureValidateException;
+
+import lombok.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FormStructureBuilder {
+ private String name;
+
+ private List<AbstractFormOption> forms = new ArrayList<>();
+
+ private FormLocale locales;
+
+ private Map<String, Map<String, String>> apis;
+
+ public FormStructureBuilder name(@NonNull String name) {
+ this.name = name;
+ return this;
+ }
+
+ public FormStructureBuilder addFormOption(@NonNull AbstractFormOption... formOptions) {
+ for (AbstractFormOption formOption : formOptions) {
+ forms.add(formOption);
+ }
+ return this;
+ }
+
+ public FormStructureBuilder withLocale(FormLocale locale) {
+ this.locales = locale;
+ return this;
+ }
+
+ public FormStructureBuilder addApi(
+ @NonNull String apiName,
+ @NonNull String url,
+ @NonNull FormStructure.HttpMethod method) {
+ if (apis == null) {
+ apis = new HashMap<>();
+ }
+ apis.putIfAbsent(apiName, new HashMap<>());
+ apis.get(apiName).put("url", url);
+ apis.get(apiName).put("method", method.name().toLowerCase(java.util.Locale.ROOT));
+
+ return this;
+ }
+
+ public FormStructure build() throws FormStructureValidateException {
+ FormStructure formStructure = new FormStructure(name, forms, locales, apis);
+ FormStructureValidate.validateFormStructure(formStructure);
+ return formStructure;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureValidate.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureValidate.java
new file mode 100644
index 00000000..025fe313
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/FormStructureValidate.java
@@ -0,0 +1,273 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.app.dynamicforms.exception.FormStructureValidateException;
+import org.apache.seatunnel.app.dynamicforms.validate.AbstractValidate;
+import org.apache.seatunnel.app.dynamicforms.validate.MutuallyExclusiveValidate;
+import org.apache.seatunnel.app.dynamicforms.validate.UnionNonEmptyValidate;
+
+import lombok.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Check whether the form structure is correct
+ */
+public class FormStructureValidate {
+
+ /**
+ * validate rules
+ */
+ public static void validateFormStructure(@NonNull FormStructure formStructure)
+ throws FormStructureValidateException {
+
+ List<String> apiErrorList = validateApiOption(formStructure);
+ List<String> localeErrorList = validateLocaleOption(formStructure);
+ List<String> showErrorList = validateShow(formStructure);
+ List<String> unionNonErrorList = validateUnionNonEmpty(formStructure);
+ List<String> exclusiveErrorList = validateMutuallyExclusive(formStructure);
+
+ apiErrorList.addAll(localeErrorList);
+ apiErrorList.addAll(showErrorList);
+ apiErrorList.addAll(unionNonErrorList);
+ apiErrorList.addAll(exclusiveErrorList);
+
+ if (apiErrorList.size() > 0) {
+ throw new FormStructureValidateException(formStructure.getName(), apiErrorList);
+ }
+ }
+
+ private static List<String> validateApiOption(@NonNull FormStructure formStructure) {
+ List<String> errorMessageList = new ArrayList();
+ Map<String, Map<String, String>> apis = formStructure.getApis();
+ formStructure
+ .getForms()
+ .forEach(
+ formOption -> {
+ if (formOption instanceof DynamicSelectOption) {
+ String api = ((DynamicSelectOption) formOption).getApi();
+ if (apis == null || !apis.keySet().contains(api)) {
+ errorMessageList.add(
+ String.format(
+ "DynamicSelectOption[%s] used api[%s] can not found in FormStructure.apis",
+ ((DynamicSelectOption) formOption).getLabel(),
+ api));
+ }
+ }
+ });
+ return errorMessageList;
+ }
+
+ private static List<String> validateLocaleOption(@NonNull FormStructure formStructure) {
+ List<String> errorMessageList = new ArrayList();
+ FormLocale locales = formStructure.getLocales();
+ formStructure
+ .getForms()
+ .forEach(
+ formOption -> {
+ if (formOption.getLabel().startsWith(FormLocale.I18N_PREFIX)) {
+ String labelName =
+ formOption.getLabel().replace(FormLocale.I18N_PREFIX, "");
+ validateOneI18nOption(
+ locales,
+ formOption.getLabel(),
+ "label",
+ labelName,
+ errorMessageList);
+ }
+
+ if (formOption.getDescription().startsWith(FormLocale.I18N_PREFIX)) {
+ String description =
+ formOption
+ .getDescription()
+ .replace(FormLocale.I18N_PREFIX, "");
+ validateOneI18nOption(
+ locales,
+ formOption.getLabel(),
+ "description",
+ description,
+ errorMessageList);
+ }
+
+ if (formOption.getPlaceholder().startsWith(FormLocale.I18N_PREFIX)) {
+ String placeholder =
+ formOption
+ .getPlaceholder()
+ .replace(FormLocale.I18N_PREFIX, "");
+ validateOneI18nOption(
+ locales,
+ formOption.getLabel(),
+ "placeholder",
+ placeholder,
+ errorMessageList);
+ }
+
+ AbstractValidate validate = formOption.getValidate();
+ if (validate != null
+ && validate.getMessage().startsWith(FormLocale.I18N_PREFIX)) {
+ String message =
+ validate.getMessage().replace(FormLocale.I18N_PREFIX, "");
+ validateOneI18nOption(
+ locales,
+ formOption.getLabel(),
+ "validateMessage",
+ message,
+ errorMessageList);
+ }
+ });
+ return errorMessageList;
+ }
+
+ private static void validateOneI18nOption(
+ FormLocale locale,
+ @NonNull String formOptionLabel,
+ @NonNull String formOptionName,
+ @NonNull String key,
+ @NonNull List<String> errorMessageList) {
+ if (locale == null || !locale.getEnUS().containsKey(key)) {
+ errorMessageList.add(
+ String.format(
+ "FormOption[%s] used i18n %s[%s] can not found in FormStructure.locales en_US",
+ formOptionLabel, formOptionName, key));
+ }
+
+ if (locale == null || !locale.getZhCN().containsKey(key)) {
+ errorMessageList.add(
+ String.format(
+ "FormOption[%s] used i18n %s[%s] can not found in FormStructure.locales zh_CN",
+ formOptionLabel, formOptionName, key));
+ }
+ }
+
+ private static List<String> validateShow(@NonNull FormStructure formStructure) {
+ List<String> errorMessageList = new ArrayList();
+ // Find all select options
+ List<String> allFields =
+ formStructure.getForms().stream()
+ .map(formOption -> formOption.getField())
+ .collect(Collectors.toList());
+ formStructure
+ .getForms()
+ .forEach(
+ formOption -> {
+ Map show = formOption.getShow();
+ if (show == null) {
+ return;
+ }
+
+ String field = show.get("field").toString();
+ if (allFields == null || !allFields.contains(field)) {
+ errorMessageList.add(
+ String.format(
+ "FormOption[%s] used show field[%s] can not found in form options",
+ formOption.getLabel(), field));
+ }
+ });
+
+ return errorMessageList;
+ }
+
+ private static List<String> validateUnionNonEmpty(@NonNull FormStructure formStructure) {
+ List<String> errorMessageList = new ArrayList();
+ Map<String, List<String>> unionMap = new HashMap<>();
+ // find all union-non-empty options
+ formStructure
+ .getForms()
+ .forEach(
+ formOption -> {
+ if (formOption.getValidate() != null
+ && formOption.getValidate() instanceof UnionNonEmptyValidate) {
+ unionMap.put(
+ formOption.getField(),
+ ((UnionNonEmptyValidate) formOption.getValidate())
+ .getFields());
+ }
+ });
+
+ unionMap.forEach(
+ (k, v) -> {
+ if (v == null || !v.contains(k)) {
+ errorMessageList.add(
+ String.format(
+ "UnionNonEmptyValidate Option field[%s] must in validate union field list",
+ k));
+ }
+
+ if (v != null) {
+ v.forEach(
+ field -> {
+ if (!unionMap.keySet().contains(field)) {
+ errorMessageList.add(
+ String.format(
+ "UnionNonEmptyValidate Option field[%s] , validate union field[%s] can not found in form options",
+ k, field));
+ }
+ });
+ }
+ });
+
+ return errorMessageList;
+ }
+
+ private static List<String> validateMutuallyExclusive(@NonNull FormStructure formStructure) {
+ List<String> errorMessageList = new ArrayList();
+ Map<String, List<String>> exclusiveMap = new HashMap<>();
+ // find all mutually-exclusive options
+ formStructure
+ .getForms()
+ .forEach(
+ formOption -> {
+ if (formOption.getValidate() != null
+ && formOption.getValidate() instanceof MutuallyExclusiveValidate) {
+ exclusiveMap.put(
+ formOption.getField(),
+ ((MutuallyExclusiveValidate) formOption.getValidate())
+ .getFields());
+ }
+ });
+
+ exclusiveMap.forEach(
+ (k, v) -> {
+ if (v == null || !v.contains(k)) {
+ errorMessageList.add(
+ String.format(
+ "MutuallyExclusiveValidate Option field[%s] must in validate field list",
+ k));
+ }
+
+ if (v != null) {
+ v.forEach(
+ field -> {
+ if (!exclusiveMap.keySet().contains(field)) {
+ errorMessageList.add(
+ String.format(
+ "MutuallyExclusiveValidate Option field[%s] , validate field[%s] can not found in form options",
+ k, field));
+ }
+ });
+ }
+ });
+
+ return errorMessageList;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/StaticSelectOption.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/StaticSelectOption.java
new file mode 100644
index 00000000..e70f4a24
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/StaticSelectOption.java
@@ -0,0 +1,38 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StaticSelectOption extends AbstractFormSelectOption {
+
+ @Getter
+ @Setter
+ private List<SelectOption> options = new ArrayList<>();
+
+ public StaticSelectOption(
+ @NonNull List<SelectOption> options, @NonNull String label, @NonNull String field) {
+ super(label, field);
+ this.options = options;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/exception/FormStructureValidateException.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/exception/FormStructureValidateException.java
new file mode 100644
index 00000000..02804e32
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/exception/FormStructureValidateException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.seatunnel.app.dynamicforms.exception;
+
+import lombok.NonNull;
+
+import java.util.List;
+
+public class FormStructureValidateException extends RuntimeException {
+
+ public FormStructureValidateException(
+ @NonNull String formName, @NonNull List<String> errorList, @NonNull Throwable e) {
+ super(String.format("Form: %s, validate error - %s", formName, errorList), e);
+ }
+
+ public FormStructureValidateException(
+ @NonNull String formName, @NonNull List<String> errorList) {
+ super(String.format("Form: %s, validate error - %s", formName, errorList));
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/AbstractValidate.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/AbstractValidate.java
new file mode 100644
index 00000000..f5576611
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/AbstractValidate.java
@@ -0,0 +1,65 @@
+/*
+ * 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.seatunnel.app.dynamicforms.validate;
+
+import org.apache.seatunnel.app.dynamicforms.FormLocale;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Data
+public class AbstractValidate<T extends AbstractValidate> {
+ private final List<String> trigger = Arrays.asList("input", "blur");
+
+ // support i18n
+ private String message = "required";
+
+ public enum RequiredType {
+ @JsonProperty("non-empty")
+ NON_EMPTY("non-empty"),
+
+ @JsonProperty("union-non-empty")
+ UNION_NON_EMPTY("union-non-empty"),
+
+ @JsonProperty("mutually-exclusive")
+ MUTUALLY_EXCLUSIVE("mutually-exclusive");
+
+ @Getter
+ private String type;
+
+ RequiredType(String type) {
+ this.type = type;
+ }
+ }
+
+ public T withMessage(@NonNull String message) {
+ this.message = message;
+ return (T) this;
+ }
+
+ public T withI18nMessage(@NonNull String message) {
+ this.message = FormLocale.I18N_PREFIX + message;
+ return (T) this;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/MutuallyExclusiveValidate.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/MutuallyExclusiveValidate.java
new file mode 100644
index 00000000..a223d9df
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/MutuallyExclusiveValidate.java
@@ -0,0 +1,42 @@
+/*
+ * 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.seatunnel.app.dynamicforms.validate;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+import lombok.NonNull;
+
+import java.util.List;
+
+@Data
+public class MutuallyExclusiveValidate extends AbstractValidate {
+ private final boolean required = false;
+ private List<String> fields;
+
+ @JsonProperty("type")
+ private final RequiredType requiredType = RequiredType.MUTUALLY_EXCLUSIVE;
+
+ public MutuallyExclusiveValidate(@NonNull List<String> fields) {
+ checkArgument(fields.size() > 0);
+ this.fields = fields;
+ this.withMessage("parameters:" + fields + ", only one can be set");
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/NonEmptyValidate.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/NonEmptyValidate.java
new file mode 100644
index 00000000..0ee27cbb
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/NonEmptyValidate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.seatunnel.app.dynamicforms.validate;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+
+@Data
+public class NonEmptyValidate extends AbstractValidate {
+ private final boolean required = true;
+
+ @JsonProperty("type")
+ private final RequiredType requiredType = RequiredType.NON_EMPTY;
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/UnionNonEmptyValidate.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/UnionNonEmptyValidate.java
new file mode 100644
index 00000000..8328459d
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/UnionNonEmptyValidate.java
@@ -0,0 +1,45 @@
+/*
+ * 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.seatunnel.app.dynamicforms.validate;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+import lombok.NonNull;
+
+import java.util.List;
+
+@Data
+public class UnionNonEmptyValidate extends AbstractValidate {
+ private final boolean required = false;
+ private List<String> fields;
+
+ @JsonProperty("type")
+ private final RequiredType requiredType = RequiredType.UNION_NON_EMPTY;
+
+ public UnionNonEmptyValidate(@NonNull List<String> fields) {
+ checkArgument(fields.size() > 0);
+ this.fields = fields;
+ this.withMessage(
+ "parameters:"
+ + fields
+ + " if any of these parameters is set, other parameters must also be set");
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/ValidateBuilder.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/ValidateBuilder.java
new file mode 100644
index 00000000..53a6e94f
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/validate/ValidateBuilder.java
@@ -0,0 +1,78 @@
+/*
+ * 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.seatunnel.app.dynamicforms.validate;
+
+import lombok.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ValidateBuilder {
+
+ public static ValidateBuilder builder() {
+ return new ValidateBuilder();
+ }
+
+ public NonEmptyValidateBuilder nonEmptyValidateBuilder() {
+ return new NonEmptyValidateBuilder();
+ }
+
+ public UnionNonEmptyValidateBuilder unionNonEmptyValidateBuilder() {
+ return new UnionNonEmptyValidateBuilder();
+ }
+
+ public MutuallyExclusiveValidateBuilder mutuallyExclusiveValidateBuilder() {
+ return new MutuallyExclusiveValidateBuilder();
+ }
+
+ public static class NonEmptyValidateBuilder {
+ public NonEmptyValidate nonEmptyValidate() {
+ return new NonEmptyValidate();
+ }
+ }
+
+ public static class UnionNonEmptyValidateBuilder {
+ private List<String> fields = new ArrayList<>();
+
+ public UnionNonEmptyValidateBuilder fields(@NonNull String... fields) {
+ for (String field : fields) {
+ this.fields.add(field);
+ }
+ return this;
+ }
+
+ public UnionNonEmptyValidate unionNonEmptyValidate() {
+ return new UnionNonEmptyValidate(fields);
+ }
+ }
+
+ public static class MutuallyExclusiveValidateBuilder {
+ private List<String> fields = new ArrayList<>();
+
+ public MutuallyExclusiveValidateBuilder fields(@NonNull String... fields) {
+ for (String field : fields) {
+ this.fields.add(field);
+ }
+ return this;
+ }
+
+ public MutuallyExclusiveValidate mutuallyExclusiveValidate() {
+ return new MutuallyExclusiveValidate(fields);
+ }
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilderTest.java b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilderTest.java
new file mode 100644
index 00000000..cbea72a8
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/FormStructureBuilderTest.java
@@ -0,0 +1,329 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import org.apache.seatunnel.app.dynamicforms.exception.FormStructureValidateException;
+import org.apache.seatunnel.app.dynamicforms.validate.ValidateBuilder;
+import org.apache.seatunnel.common.utils.FileUtils;
+import org.apache.seatunnel.common.utils.JsonUtils;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class FormStructureBuilderTest {
+
+ @Test
+ public void testFormStructureBuild() {
+ FormLocale locale = new FormLocale();
+ locale.addZhCN("name_password_union_required", "all name and password are required")
+ .addZhCN("username", "username")
+ .addEnUS("name_password_union_required", "all name and password are required")
+ .addEnUS("username", "username");
+
+ FormInputOption nameOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withI18nLabel("username")
+ .withField("username")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withDescription("username")
+ .withClearable()
+ .withPlaceholder("username")
+ .withShow("checkType", Arrays.asList("nameAndPassword"))
+ .withValidate(
+ ValidateBuilder.builder()
+ .unionNonEmptyValidateBuilder()
+ .fields("username", "password")
+ .unionNonEmptyValidate()
+ .withI18nMessage("name_password_union_required"));
+
+ FormInputOption passwordOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withLabel("password")
+ .withField("password")
+ .inputOptionBuilder()
+ .formPasswordInputOption()
+ .withDescription("password")
+ .withPlaceholder("password")
+ .withShow("checkType", Arrays.asList("nameAndPassword"))
+ .withValidate(
+ ValidateBuilder.builder()
+ .unionNonEmptyValidateBuilder()
+ .fields("username", "password")
+ .unionNonEmptyValidate()
+ .withI18nMessage("name_password_union_required"));
+
+ FormInputOption textAreaOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withLabel("content")
+ .withField("context")
+ .inputOptionBuilder()
+ .formTextareaInputOption()
+ .withClearable()
+ .withDescription("content");
+
+ StaticSelectOption checkTypeOption =
+ (StaticSelectOption)
+ FormOptionBuilder.builder()
+ .withLabel("checkType")
+ .withField("checkType")
+ .staticSelectOptionBuilder()
+ .addSelectOptions(
+ new ImmutablePair("no", "no"),
+ new ImmutablePair("nameAndPassword", "nameAndPassword"))
+ .formStaticSelectOption()
+ .withClearable()
+ .withDefaultValue("no")
+ .withDescription("check type")
+ .withValidate(
+ ValidateBuilder.builder()
+ .nonEmptyValidateBuilder()
+ .nonEmptyValidate());
+
+ DynamicSelectOption cityOption =
+ (DynamicSelectOption)
+ FormOptionBuilder.builder()
+ .withField("city")
+ .withLabel("city")
+ .dynamicSelectOptionBuilder()
+ .withSelectApi("getCity")
+ .formDynamicSelectOption()
+ .withDescription("city")
+ .withValidate(
+ ValidateBuilder.builder()
+ .nonEmptyValidateBuilder()
+ .nonEmptyValidate());
+
+ AbstractFormOption exclusive1 =
+ FormOptionBuilder.builder()
+ .withField("exclusive1")
+ .withLabel("exclusive1")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive2")
+ .mutuallyExclusiveValidate());
+
+ AbstractFormOption exclusive2 =
+ FormOptionBuilder.builder()
+ .withField("exclusive2")
+ .withLabel("exclusive2")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive2")
+ .mutuallyExclusiveValidate());
+
+ FormStructure testForm =
+ FormStructure.builder()
+ .name("testForm")
+ .addFormOption(
+ nameOption,
+ passwordOption,
+ textAreaOption,
+ checkTypeOption,
+ cityOption,
+ exclusive1,
+ exclusive2)
+ .withLocale(locale)
+ .addApi("getCity", "/api/get_city", FormStructure.HttpMethod.GET)
+ .build();
+
+ String s = JsonUtils.toJsonString(testForm);
+ String templateFilePath = TestUtils.getResource("test_form.json");
+ String result = FileUtils.readFileToStr(Paths.get(templateFilePath));
+ Assertions.assertEquals(result, s);
+ }
+
+ @Test
+ public void testFormStructureValidate() {
+ FormLocale locale = new FormLocale();
+ locale.addZhCN("name_password_union_required", "all name and password are required")
+ .addEnUS("name_password_union_required", "all name and password are required")
+ .addEnUS("username", "username");
+
+ FormInputOption nameOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withI18nLabel("username")
+ .withField("username")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withDescription("username")
+ .withClearable()
+ .withPlaceholder("username")
+ .withShow("checkType1", Arrays.asList("nameAndPassword"))
+ .withValidate(
+ ValidateBuilder.builder()
+ .unionNonEmptyValidateBuilder()
+ .fields("user", "password")
+ .unionNonEmptyValidate()
+ .withI18nMessage("name_password_union_required"));
+
+ FormInputOption passwordOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withLabel("password")
+ .withField("password")
+ .inputOptionBuilder()
+ .formPasswordInputOption()
+ .withDescription("password")
+ .withPlaceholder("password")
+ .withShow("checkType", Arrays.asList("nameAndPassword"))
+ .withValidate(
+ ValidateBuilder.builder()
+ .unionNonEmptyValidateBuilder()
+ .fields("username", "password")
+ .unionNonEmptyValidate()
+ .withI18nMessage("name_password_union_required"));
+
+ FormInputOption textAreaOption =
+ (FormInputOption)
+ FormOptionBuilder.builder()
+ .withLabel("content")
+ .withField("context")
+ .inputOptionBuilder()
+ .formTextareaInputOption()
+ .withClearable()
+ .withDescription("content");
+
+ StaticSelectOption checkTypeOption =
+ (StaticSelectOption)
+ FormOptionBuilder.builder()
+ .withLabel("checkType")
+ .withField("checkType")
+ .staticSelectOptionBuilder()
+ .addSelectOptions(
+ new ImmutablePair("no", "no"),
+ new ImmutablePair("nameAndPassword", "nameAndPassword"))
+ .formStaticSelectOption()
+ .withClearable()
+ .withDefaultValue("no")
+ .withDescription("check type")
+ .withValidate(
+ ValidateBuilder.builder()
+ .nonEmptyValidateBuilder()
+ .nonEmptyValidate());
+
+ DynamicSelectOption cityOption =
+ (DynamicSelectOption)
+ FormOptionBuilder.builder()
+ .withField("city")
+ .withLabel("city")
+ .dynamicSelectOptionBuilder()
+ .withSelectApi("getCity")
+ .formDynamicSelectOption()
+ .withDescription("city")
+ .withValidate(
+ ValidateBuilder.builder()
+ .nonEmptyValidateBuilder()
+ .nonEmptyValidate());
+
+ AbstractFormOption exclusive1 =
+ FormOptionBuilder.builder()
+ .withField("exclusive1")
+ .withLabel("exclusive1")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive2")
+ .mutuallyExclusiveValidate());
+
+ AbstractFormOption exclusive2 =
+ FormOptionBuilder.builder()
+ .withField("exclusive2")
+ .withLabel("exclusive2")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive2")
+ .mutuallyExclusiveValidate());
+
+ AbstractFormOption exclusive3 =
+ FormOptionBuilder.builder()
+ .withField("exclusive3")
+ .withLabel("exclusive3")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive2")
+ .mutuallyExclusiveValidate());
+
+ AbstractFormOption exclusive4 =
+ FormOptionBuilder.builder()
+ .withField("exclusive4")
+ .withLabel("exclusive4")
+ .inputOptionBuilder()
+ .formTextInputOption()
+ .withValidate(
+ ValidateBuilder.builder()
+ .mutuallyExclusiveValidateBuilder()
+ .fields("exclusive1", "exclusive4", "exclusive5")
+ .mutuallyExclusiveValidate());
+
+ String error = "";
+ try {
+ FormStructure testForm =
+ FormStructure.builder()
+ .name("testForm")
+ .addFormOption(
+ nameOption,
+ passwordOption,
+ textAreaOption,
+ checkTypeOption,
+ cityOption,
+ exclusive1,
+ exclusive3,
+ exclusive2,
+ exclusive4)
+ .withLocale(locale)
+ .addApi("getCity1", "/api/get_city", FormStructure.HttpMethod.GET)
+ .build();
+ } catch (FormStructureValidateException e) {
+ error = e.getMessage();
+ }
+
+ String result =
+ "Form: testForm, validate error - "
+ + "[DynamicSelectOption[city] used api[getCity] can not found in FormStructure.apis, "
+ + "FormOption[i18n.username] used i18n label[username] can not found in FormStructure.locales zh_CN, "
+ + "FormOption[i18n.username] used show field[checkType1] can not found in form options, "
+ + "UnionNonEmptyValidate Option field[username] must in validate union field list, "
+ + "UnionNonEmptyValidate Option field[username] , validate union field[user] can not found in form options, "
+ + "MutuallyExclusiveValidate Option field[exclusive3] must in validate field list, "
+ + "MutuallyExclusiveValidate Option field[exclusive4] , validate field[exclusive5] can not found in form options]";
+ Assertions.assertEquals(result, error);
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/TestUtils.java b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/TestUtils.java
new file mode 100644
index 00000000..e0f94182
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/TestUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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.seatunnel.app.dynamicforms;
+
+import java.io.File;
+
+public class TestUtils {
+ public static String getResource(String confFile) {
+ return System.getProperty("user.dir")
+ + File.separator
+ + "src"
+ + File.separator
+ + "test"
+ + File.separator
+ + "resources"
+ + File.separator
+ + confFile;
+ }
+}
diff --git a/seatunnel-server/seatunnel-dynamicform/src/test/resources/test_form.json b/seatunnel-server/seatunnel-dynamicform/src/test/resources/test_form.json
new file mode 100644
index 00000000..8044981b
--- /dev/null
+++ b/seatunnel-server/seatunnel-dynamicform/src/test/resources/test_form.json
@@ -0,0 +1 @@
+{"name":"testForm","locales":{"zh_CN":{"name_password_union_required":"all name and password are required","username":"username"},"en_US":{"name_password_union_required":"all name and password are required","username":"username"}},"apis":{"getCity":{"method":"get","url":"/api/get_city"}},"forms":[{"label":"i18n.username","field":"username","defaultValue":null,"description":"username","clearable":true,"show":{"field":"checkType","value":["nameAndPassword"]},"placeholder":"username","valid [...]
\ No newline at end of file
diff --git a/seatunnel-server/seatunnel-server-common/pom.xml b/seatunnel-server/seatunnel-server-common/pom.xml
index 6eb22aac..35bf91c8 100644
--- a/seatunnel-server/seatunnel-server-common/pom.xml
+++ b/seatunnel-server/seatunnel-server-common/pom.xml
@@ -31,9 +31,10 @@
<dependencies>
<dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <scope>provided</scope>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>seatunnel-jackson</artifactId>
+ <version>${seatunnel-framework.version}</version>
+ <classifier>optional</classifier>
</dependency>
</dependencies>
</project>
\ No newline at end of file
diff --git a/seatunnel-ui/src/router/routes.ts b/seatunnel-ui/src/router/routes.ts
index 64eac468..19fdd67e 100644
--- a/seatunnel-ui/src/router/routes.ts
+++ b/seatunnel-ui/src/router/routes.ts
@@ -28,7 +28,7 @@ const components: { [key: string]: Component } = utils.mapping(modules)
const basePage: RouteRecordRaw[] = [{
path: '/',
- redirect: { name: 'data-pipes' }
+ redirect: { name: 'login' }
},
dataPipes, jobs, tasks, userManage]
diff --git a/tools/dependencies/known-dependencies.txt b/tools/dependencies/known-dependencies.txt
index 055e9389..5f7dfbe3 100644
--- a/tools/dependencies/known-dependencies.txt
+++ b/tools/dependencies/known-dependencies.txt
@@ -50,9 +50,10 @@ mybatis-plus-boot-starter-3.5.3.1.jar
mybatis-plus-core-3.5.3.1.jar
mybatis-plus-extension-3.5.3.1.jar
scala-library-2.11.12.jar
-seatunnel-common-2.1.3.jar
-seatunnel-config-base-2.1.1.jar
-seatunnel-config-shade-2.1.1.jar
+seatunnel-common-2.3.1.jar
+seatunnel-config-base-2.3.1.jar
+seatunnel-config-shade-2.3.1.jar
+seatunnel-jackson-2.3.1-optional.jar
slf4j-api-1.7.25.jar
snakeyaml-1.29.jar
spring-aop-5.3.20.jar