diff --git a/core/src/main/java/io/kestra/core/models/flows/Input.java b/core/src/main/java/io/kestra/core/models/flows/Input.java index e349690dd29..e5f49ca7f62 100644 --- a/core/src/main/java/io/kestra/core/models/flows/Input.java +++ b/core/src/main/java/io/kestra/core/models/flows/Input.java @@ -5,18 +5,16 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.kestra.core.models.flows.input.*; -import io.kestra.core.models.validations.ManualConstraintViolation; import io.micronaut.core.annotation.Introspected; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @SuperBuilder @Getter @@ -37,7 +35,8 @@ @JsonSubTypes.Type(value = StringInput.class, name = "STRING"), @JsonSubTypes.Type(value = EnumInput.class, name = "ENUM"), @JsonSubTypes.Type(value = TimeInput.class, name = "TIME"), - @JsonSubTypes.Type(value = URIInput.class, name = "URI") + @JsonSubTypes.Type(value = URIInput.class, name = "URI"), + @JsonSubTypes.Type(value = MultiselectInput.class, name = "MULTISELECT") }) @JsonInclude(JsonInclude.Include.NON_DEFAULT) public abstract class Input implements Data { diff --git a/core/src/main/java/io/kestra/core/models/flows/Type.java b/core/src/main/java/io/kestra/core/models/flows/Type.java index 44f8b538420..55facddd168 100644 --- a/core/src/main/java/io/kestra/core/models/flows/Type.java +++ b/core/src/main/java/io/kestra/core/models/flows/Type.java @@ -21,7 +21,8 @@ public enum Type { JSON(JsonInput.class.getName()), URI(URIInput.class.getName()), SECRET(SecretInput.class.getName()), - ARRAY(ArrayInput.class.getName()); + ARRAY(ArrayInput.class.getName()), + MULTISELECT(MultiselectInput.class.getName()); private final String clsName; diff --git a/core/src/main/java/io/kestra/core/models/flows/input/ArrayInput.java b/core/src/main/java/io/kestra/core/models/flows/input/ArrayInput.java index f735361eb1b..2f04bef1800 100644 --- a/core/src/main/java/io/kestra/core/models/flows/input/ArrayInput.java +++ b/core/src/main/java/io/kestra/core/models/flows/input/ArrayInput.java @@ -16,7 +16,7 @@ @Getter @NoArgsConstructor @ArrayInputValidation -public class ArrayInput extends Input> { +public class ArrayInput extends Input> implements ItemTypeInterface { @Schema( title = "Type of the array items.", description = "Cannot be of type `ARRAY`." diff --git a/core/src/main/java/io/kestra/core/models/flows/input/ItemTypeInterface.java b/core/src/main/java/io/kestra/core/models/flows/input/ItemTypeInterface.java new file mode 100644 index 00000000000..452b815435a --- /dev/null +++ b/core/src/main/java/io/kestra/core/models/flows/input/ItemTypeInterface.java @@ -0,0 +1,7 @@ +package io.kestra.core.models.flows.input; + +import io.kestra.core.models.flows.Type; + +public interface ItemTypeInterface { + Type getItemType(); +} diff --git a/core/src/main/java/io/kestra/core/models/flows/input/MultiselectInput.java b/core/src/main/java/io/kestra/core/models/flows/input/MultiselectInput.java new file mode 100644 index 00000000000..de83d4eb181 --- /dev/null +++ b/core/src/main/java/io/kestra/core/models/flows/input/MultiselectInput.java @@ -0,0 +1,48 @@ +package io.kestra.core.models.flows.input; + +import io.kestra.core.models.flows.Input; +import io.kestra.core.models.flows.Type; +import io.kestra.core.models.validations.ManualConstraintViolation; +import io.kestra.core.validations.Regex; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +@SuperBuilder +@Getter +@NoArgsConstructor +public class MultiselectInput extends Input> implements ItemTypeInterface { + @Schema( + title = "List of values available." + ) + @NotNull + List<@Regex String> options; + + @Schema( + title = "Type of the different values available.", + description = "Cannot be of type `ARRAY` nor 'MULTISELECT'." + ) + @Builder.Default + private Type itemType = Type.STRING; + + @Override + public void validate(List inputs) throws ConstraintViolationException { + for(String input : inputs){ + if (!options.contains(input)) { + throw ManualConstraintViolation.toConstraintViolationException( + "it must match the values `" + options + "`", + this, + MultiselectInput.class, + getId(), + input + ); + } + } + } +} diff --git a/core/src/main/java/io/kestra/core/runners/FlowInputOutput.java b/core/src/main/java/io/kestra/core/runners/FlowInputOutput.java index 207e158b5db..51110d39c8e 100644 --- a/core/src/main/java/io/kestra/core/runners/FlowInputOutput.java +++ b/core/src/main/java/io/kestra/core/runners/FlowInputOutput.java @@ -7,8 +7,8 @@ import io.kestra.core.models.flows.Flow; import io.kestra.core.models.flows.Input; import io.kestra.core.models.flows.Type; -import io.kestra.core.models.flows.input.ArrayInput; import io.kestra.core.models.flows.input.FileInput; +import io.kestra.core.models.flows.input.ItemTypeInterface; import io.kestra.core.models.tasks.common.EncryptedString; import io.kestra.core.serializers.JacksonMapper; import io.kestra.core.storages.StorageInterface; @@ -246,7 +246,7 @@ private Optional> parseData( return Optional.of(new AbstractMap.SimpleEntry<>(data.getId(), current)); } - final Type elementType = data instanceof ArrayInput arrayInput ? arrayInput.getItemType() : null; + final Type elementType = data instanceof ItemTypeInterface itemTypeInterface ? itemTypeInterface.getItemType() : null; return Optional.of(new AbstractMap.SimpleEntry<>( data.getId(), @@ -289,7 +289,7 @@ private Object parseType(Execution execution, Type type, String id, Type element throw new IllegalArgumentException("Expected `URI` but received `" + current + "`"); } } - case ARRAY -> { + case ARRAY, MULTISELECT -> { if (elementType != null) { // recursively parse the elements only once yield JacksonMapper.toList(((String) current)) diff --git a/ui/src/components/inputs/InputsForm.vue b/ui/src/components/inputs/InputsForm.vue index 75bd411c93d..f8554f51fb2 100644 --- a/ui/src/components/inputs/InputsForm.vue +++ b/ui/src/components/inputs/InputsForm.vue @@ -32,6 +32,24 @@ {{ item }} + + + {{ item }} + + 0) { + return JSON.parse(input) + } + return input + }, updateDefaults() { for (const input of this.inputsList || []) { + if (input.type === "MULTISELECT") { + this.multiSelectInputs[input.id] = input.defaults; + } this.inputs[input.id] = Inputs.normalize(input.type, input.defaults); this.onChange(); } @@ -178,6 +206,10 @@ onChange() { this.$emit("update:modelValue", this.inputs); }, + onMultiSelectChange(input, e) { + this.inputs[input] = JSON.stringify(e).toString(); + this.onChange(); + }, onFileChange(input, e) { if (!e.target) { return; diff --git a/ui/src/utils/inputs.js b/ui/src/utils/inputs.js index 5199fa2b260..fdd6663ce39 100644 --- a/ui/src/utils/inputs.js +++ b/ui/src/utils/inputs.js @@ -4,6 +4,25 @@ export default class Inputs { static normalize(type, value) { let res = value; + if (value === null || value === undefined) { + res = undefined; + } else if (type === "DATE" || type === "DATETIME") { + res = moment(res).toISOString() + } else if (type === "DURATION" || type === "TIME") { + res = moment().startOf("day").add(res, "seconds").toString() + } else if (type === "ARRAY" || type === "MULTISELECT") { + res = JSON.stringify(res).toString(); + } else if (type === "BOOLEAN" && type === undefined){ + res = "undefined"; + } else if (type === "STRING" && Array.isArray(res)){ + res = res.toString(); + } + return res; + } + + static normalizeForComponents(type, value) { + let res = value; + if (value === null) { res = undefined; } else if (type === "DATE" || type === "DATETIME") { diff --git a/webserver/src/test/java/io/kestra/webserver/controllers/api/PluginControllerTest.java b/webserver/src/test/java/io/kestra/webserver/controllers/api/PluginControllerTest.java index 9bcb7bb7451..16da8a3dd8d 100644 --- a/webserver/src/test/java/io/kestra/webserver/controllers/api/PluginControllerTest.java +++ b/webserver/src/test/java/io/kestra/webserver/controllers/api/PluginControllerTest.java @@ -213,7 +213,7 @@ void inputs() throws URISyntaxException { Argument.listOf(InputType.class) ); - assertThat(doc.size(), is(14)); + assertThat(doc.size(), is(15)); }); }