From 84b9a74c6ea3e2f983c988ccad203796370e77fe Mon Sep 17 00:00:00 2001
From: YannC <37600690+Skraye@users.noreply.github.com>
Date: Tue, 11 Apr 2023 10:31:45 +0200
Subject: [PATCH] feat(ui): add validation on lowcode everywhere (#1139)
- Added task validation at the creation
- Added trigger validation
- Added validation in "New Error" section
---
ui/src/components/flows/TaskEditor.vue | 2 +-
ui/src/components/graph/Topology.vue | 23 +++++++++++++++++--
ui/src/components/graph/nodes/Edge.vue | 16 +++++++++++--
ui/src/stores/flow.js | 2 +-
.../webserver/controllers/FlowController.java | 15 +++++++++---
5 files changed, 49 insertions(+), 9 deletions(-)
diff --git a/ui/src/components/flows/TaskEditor.vue b/ui/src/components/flows/TaskEditor.vue
index 569a9c6ae11..e52a6cac655 100644
--- a/ui/src/components/flows/TaskEditor.vue
+++ b/ui/src/components/flows/TaskEditor.vue
@@ -43,7 +43,7 @@
if (this.modelValue) {
this.taskObject = YamlUtils.parse(this.modelValue);
this.selectedTaskType = this.taskObject.type;
- this.$store.dispatch("flow/validateTask", {task: this.modelValue})
+ this.$store.dispatch("flow/validateTask", {task: this.modelValue, section: this.section})
this.load();
}
diff --git a/ui/src/components/graph/Topology.vue b/ui/src/components/graph/Topology.vue
index 34bd9f907bb..e132306ccc2 100644
--- a/ui/src/components/graph/Topology.vue
+++ b/ui/src/components/graph/Topology.vue
@@ -13,6 +13,7 @@
import BottomLine from "../../components/layout/BottomLine.vue";
import TriggerFlow from "../../components/flows/TriggerFlow.vue";
+ import ValidationError from "../../components/flows/ValidationError.vue";
import SwitchView from "./SwitchView.vue";
import {cssVariable} from "../../utils/global"
import Cluster from "./nodes/Cluster.vue";
@@ -121,6 +122,11 @@
const updatedFromEditor = ref(false);
const timer = ref(null);
const dragging = ref(false);
+ const taskError = ref(store.getters["flow/taskError"])
+
+ watch(() => store.getters["flow/taskError"], async () => {
+ taskError.value = store.getters["flow/taskError"];
+ });
const flowables = () => {
@@ -562,6 +568,11 @@
}
const onUpdateNewTrigger = (event) => {
+ clearTimeout(timer.value);
+ timer.value = setTimeout(() => store.dispatch("flow/validateTask", {
+ task: event,
+ section: "trigger"
+ }), 500);
newTrigger.value = event;
}
@@ -583,6 +594,12 @@
}
const onUpdateNewError = (event) => {
+ clearTimeout(timer.value);
+ timer.value = setTimeout(() => store.dispatch("flow/validateTask", {
+ task: event,
+ section: "task"
+ }), 500);
+
newError.value = event;
}
@@ -869,7 +886,8 @@
/>
-
+
+
{{ $t("save") }}
@@ -889,7 +907,8 @@
/>
-
+
+
{{ $t("save") }}
diff --git a/ui/src/components/graph/nodes/Edge.vue b/ui/src/components/graph/nodes/Edge.vue
index 66107aabbd0..9be4a55ac2c 100644
--- a/ui/src/components/graph/nodes/Edge.vue
+++ b/ui/src/components/graph/nodes/Edge.vue
@@ -2,7 +2,7 @@
import type {EdgeProps, Position} from '@vue-flow/core'
import {EdgeLabelRenderer, getSmoothStepPath, useEdge} from '@vue-flow/core'
import type {CSSProperties} from 'vue'
- import {computed, getCurrentInstance, ref} from 'vue'
+ import {computed, getCurrentInstance, ref, watch} from 'vue'
import TaskEditor from "../../flows/TaskEditor.vue"
import Help from "vue-material-design-icons/Help.vue";
import HelpCircle from "vue-material-design-icons/HelpCircle.vue";
@@ -15,6 +15,7 @@
import yamlUtils from "../../../utils/yamlUtils.js";
import YamlUtils from "../../../utils/yamlUtils.js";
import {useStore} from "vuex";
+ import ValidationError from "../../flows/ValidationError.vue";
const store = useStore();
const t = getCurrentInstance().appContext.config.globalProperties.$t;
@@ -41,6 +42,12 @@
const emit = defineEmits(["edit"])
const taskYaml = ref("");
const execution = store.getters["execution/execution"];
+ const timer = ref(undefined);
+ const taskError = ref(store.getters["flow/taskError"])
+
+ watch(() => store.getters["flow/taskError"], async () => {
+ taskError.value = store.getters["flow/taskError"];
+ });
const isBorderEdge = () => {
if (!props.data.haveAdd && props.data.isFlowable) {
@@ -104,6 +111,10 @@
const updateTask = (task) => {
taskYaml.value = task;
+ clearTimeout(timer.value);
+ timer.value = setTimeout(() => {
+ store.dispatch("flow/validateTask", {task: task})
+ }, 500);
}
const getAddTaskInformation = () => {
@@ -237,7 +248,8 @@
/>
-
+
+
{{ $t("save") }}
diff --git a/ui/src/stores/flow.js b/ui/src/stores/flow.js
index 5b6fc65ca1f..a5b7e646f7d 100644
--- a/ui/src/stores/flow.js
+++ b/ui/src/stores/flow.js
@@ -212,7 +212,7 @@ export default {
})
},
validateTask({commit}, options) {
- return axios.post(`${apiRoot}flows/validate/task`, options.task, textYamlHeader)
+ return axios.post(`${apiRoot}flows/validate/task`, options.task, {...textYamlHeader, params: {section: options.section ? options.section : "task"}})
.then(response => {
commit("setTaskError", response.data.constraints)
return response.data
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java b/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
index 21cdfacd476..eb4514ead5f 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
@@ -9,6 +9,7 @@
import io.kestra.core.models.tasks.Task;
import io.kestra.core.models.topologies.FlowTopology;
import io.kestra.core.models.topologies.FlowTopologyGraph;
+import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.validations.ManualConstraintViolation;
import io.kestra.core.models.validations.ModelValidator;
import io.kestra.core.models.validations.ValidateConstraintViolation;
@@ -470,13 +471,21 @@ public List validateFlows(
@Post(uri = "/validate/task", produces = MediaType.TEXT_JSON, consumes = MediaType.APPLICATION_YAML)
@Operation(tags = {"Flows"}, summary = "Validate a list of flows")
public ValidateConstraintViolation validateTask(
- @Parameter(description = "A list of flows") @Body String task
+ @Parameter(description = "A list of flows") @Body String task,
+ @Parameter(description = "Type of task") @QueryValue(defaultValue = "task") String section
) {
ValidateConstraintViolation.ValidateConstraintViolationBuilder, ?> validateConstraintViolationBuilder = ValidateConstraintViolation.builder();
try {
- Task taskParse = yamlFlowParser.parse(task, Task.class);
- modelValidator.validate(taskParse);
+ if (section.equals("task")) {
+ Task taskParse = yamlFlowParser.parse(task, Task.class);
+ modelValidator.validate(taskParse);
+ } else if (section.equals("trigger")) {
+ AbstractTrigger triggerParse = yamlFlowParser.parse(task, AbstractTrigger.class);
+ modelValidator.validate(triggerParse);
+ } else {
+ throw new IllegalArgumentException("Invalid section, must be 'task' or 'trigger'");
+ }
} catch (ConstraintViolationException e) {
validateConstraintViolationBuilder.constraints(e.getMessage());
}