Skip to content

Commit

Permalink
feat(): multiselect input (#4061)
Browse files Browse the repository at this point in the history
* feat(): multiselect input string

* feat(): multiple type for multiselect
  • Loading branch information
Skraye authored Jun 17, 2024
1 parent b9620a5 commit 7064bd2
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 13 deletions.
13 changes: 6 additions & 7 deletions core/src/main/java/io/kestra/core/models/flows/Input.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<T> implements Data {
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/io/kestra/core/models/flows/Type.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
@Getter
@NoArgsConstructor
@ArrayInputValidation
public class ArrayInput extends Input<List<?>> {
public class ArrayInput extends Input<List<?>> implements ItemTypeInterface {
@Schema(
title = "Type of the array items.",
description = "Cannot be of type `ARRAY`."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.kestra.core.models.flows.input;

import io.kestra.core.models.flows.Type;

public interface ItemTypeInterface {
Type getItemType();
}
Original file line number Diff line number Diff line change
@@ -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<List<String>> 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<String> 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
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -246,7 +246,7 @@ private Optional<AbstractMap.SimpleEntry<String, Object>> 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(),
Expand Down Expand Up @@ -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))
Expand Down
32 changes: 32 additions & 0 deletions ui/src/components/inputs/InputsForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@
{{ item }}
</el-option>
</el-select>
<el-select
:full-height="false"
:input="true"
:navbar="false"
v-if="input.type === 'MULTISELECT'"
v-model="multiSelectInputs[input.id]"
@update:model-value="onMultiSelectChange(input.id, $event)"
multiple
>
<el-option
v-for="item in input.options"
:key="item"
:label="item"
:value="item"
>
{{ item }}
</el-option>
</el-select>
<el-input
type="password"
v-if="input.type === 'SECRET'"
Expand Down Expand Up @@ -139,6 +157,7 @@
data() {
return {
inputs: {},
multiSelectInputs: {}
};
},
emits: ["update:modelValue"],
Expand Down Expand Up @@ -169,15 +188,28 @@
},
methods: {
parseInput(input) {
if (input && input.length > 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();
}
},
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;
Expand Down
19 changes: 19 additions & 0 deletions ui/src/utils/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ void inputs() throws URISyntaxException {
Argument.listOf(InputType.class)
);

assertThat(doc.size(), is(14));
assertThat(doc.size(), is(15));
});
}

Expand Down

0 comments on commit 7064bd2

Please sign in to comment.