Skip to content

Commit

Permalink
feat(ui): handle triggers as form
Browse files Browse the repository at this point in the history
  • Loading branch information
tchiotludo committed Feb 24, 2023
1 parent d7fc07f commit d678210
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.kestra.core.docs;

import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import io.kestra.core.Helpers;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.triggers.types.Schedule;
import io.kestra.core.plugins.PluginScanner;
import io.kestra.core.plugins.RegisteredPlugin;
import org.junit.jupiter.api.Test;

import java.net.URISyntaxException;
import java.nio.file.Path;
Expand All @@ -13,72 +15,89 @@
import java.util.Map;
import java.util.Objects;

import jakarta.inject.Inject;

import static io.kestra.core.utils.Rethrow.throwConsumer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

@MicronautTest
class ClassPluginDocumentationTest {
@Inject
JsonSchemaGenerator jsonSchemaGenerator;

@SuppressWarnings("unchecked")
@Test
void tasks() throws URISyntaxException {
Path plugins = Paths.get(Objects.requireNonNull(ClassPluginDocumentationTest.class.getClassLoader().getResource("plugins")).toURI());

PluginScanner pluginScanner = new PluginScanner(ClassPluginDocumentationTest.class.getClassLoader());
List<RegisteredPlugin> scan = pluginScanner.scan(plugins);

assertThat(scan.size(), is(1));
assertThat(scan.get(0).getTasks().size(), is(1));

ClassPluginDocumentation<? extends Task> doc = ClassPluginDocumentation.of(jsonSchemaGenerator, scan.get(0), scan.get(0).getTasks().get(0), Task.class);

assertThat(doc.getDocExamples().size(), is(2));
assertThat(doc.getIcon(), is(notNullValue()));
assertThat(doc.getInputs().size(), is(2));

// simple
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("type"), is("string"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("default"), is("{}"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("pattern"), is(".*"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("description"), containsString("of this input"));

// enum
Map<String, Object> enumProperties = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput")).get("properties")).get("childEnum");
assertThat(((List<String>) enumProperties.get("enum")).size(), is(2));
assertThat(((List<String>) enumProperties.get("enum")), containsInAnyOrder("VALUE_1", "VALUE_2"));

Map<String, Object> childInput = (Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput")).get("properties");

// array
Map<String, Object> childInputList = (Map<String, Object>) childInput.get("list");
assertThat((String) (childInputList).get("type"), is("array"));
assertThat((String) (childInputList).get("title"), is("List of string"));
assertThat((Integer) (childInputList).get("minItems"), is(1));
assertThat(((Map<String, String>) (childInputList).get("items")).get("type"), is("string"));

// map
Map<String, Object> childInputMap = (Map<String, Object>) childInput.get("map");
assertThat((String) (childInputMap).get("type"), is("object"));
assertThat((Boolean) (childInputMap).get("$dynamic"), is(true));
assertThat(((Map<String, String>) (childInputMap).get("additionalProperties")).get("type"), is("number"));

// output
Map<String, Object> childOutput = (Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.AbstractTask-OutputChild")).get("properties");
assertThat(((Map<String, String>) childOutput.get("value")).get("type"), is("string"));
assertThat(((Map<String, Object>) childOutput.get("outputChildMap")).get("type"), is("object"));
assertThat(((Map<String, String>)((Map<String, Object>) childOutput.get("outputChildMap")).get("additionalProperties")).get("$ref"), containsString("OutputMap"));

// required
Map<String, Object> propertiesChild = (Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput");
assertThat(((List<String>) propertiesChild.get("required")).size(), is(3));

// output ref
Map<String, Object> outputMap = ((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.AbstractTask-OutputMap")).get("properties"));
assertThat(outputMap.size(), is(2));
assertThat(((Map<String, Object>) outputMap.get("code")).get("type"), is("integer"));
Helpers.runApplicationContext(throwConsumer((applicationContext) -> {
JsonSchemaGenerator jsonSchemaGenerator = applicationContext.getBean(JsonSchemaGenerator.class);

Path plugins = Paths.get(Objects.requireNonNull(ClassPluginDocumentationTest.class.getClassLoader().getResource("plugins")).toURI());

PluginScanner pluginScanner = new PluginScanner(ClassPluginDocumentationTest.class.getClassLoader());
List<RegisteredPlugin> scan = pluginScanner.scan(plugins);

assertThat(scan.size(), is(1));
assertThat(scan.get(0).getTasks().size(), is(1));

ClassPluginDocumentation<? extends Task> doc = ClassPluginDocumentation.of(jsonSchemaGenerator, scan.get(0), scan.get(0).getTasks().get(0), Task.class);

assertThat(doc.getDocExamples().size(), is(2));
assertThat(doc.getIcon(), is(notNullValue()));
assertThat(doc.getInputs().size(), is(2));

// simple
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("type"), is("string"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("default"), is("{}"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("pattern"), is(".*"));
assertThat(((Map<String, String>) doc.getInputs().get("format")).get("description"), containsString("of this input"));

// enum
Map<String, Object> enumProperties = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput")).get("properties")).get("childEnum");
assertThat(((List<String>) enumProperties.get("enum")).size(), is(2));
assertThat(((List<String>) enumProperties.get("enum")), containsInAnyOrder("VALUE_1", "VALUE_2"));

Map<String, Object> childInput = (Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput")).get("properties");

// array
Map<String, Object> childInputList = (Map<String, Object>) childInput.get("list");
assertThat((String) (childInputList).get("type"), is("array"));
assertThat((String) (childInputList).get("title"), is("List of string"));
assertThat((Integer) (childInputList).get("minItems"), is(1));
assertThat(((Map<String, String>) (childInputList).get("items")).get("type"), is("string"));

// map
Map<String, Object> childInputMap = (Map<String, Object>) childInput.get("map");
assertThat((String) (childInputMap).get("type"), is("object"));
assertThat((Boolean) (childInputMap).get("$dynamic"), is(true));
assertThat(((Map<String, String>) (childInputMap).get("additionalProperties")).get("type"), is("number"));

// output
Map<String, Object> childOutput = (Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.AbstractTask-OutputChild")).get("properties");
assertThat(((Map<String, String>) childOutput.get("value")).get("type"), is("string"));
assertThat(((Map<String, Object>) childOutput.get("outputChildMap")).get("type"), is("object"));
assertThat(((Map<String, String>)((Map<String, Object>) childOutput.get("outputChildMap")).get("additionalProperties")).get("$ref"), containsString("OutputMap"));

// required
Map<String, Object> propertiesChild = (Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.ExampleTask-PropertyChildInput");
assertThat(((List<String>) propertiesChild.get("required")).size(), is(3));

// output ref
Map<String, Object> outputMap = ((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.plugin.templates.AbstractTask-OutputMap")).get("properties"));
assertThat(outputMap.size(), is(2));
assertThat(((Map<String, Object>) outputMap.get("code")).get("type"), is("integer"));
}));
}

@SuppressWarnings("unchecked")
@Test
void trigger() throws URISyntaxException {
Helpers.runApplicationContext(throwConsumer((applicationContext) -> {
JsonSchemaGenerator jsonSchemaGenerator = applicationContext.getBean(JsonSchemaGenerator.class);

PluginScanner pluginScanner = new PluginScanner(ClassPluginDocumentationTest.class.getClassLoader());
RegisteredPlugin scan = pluginScanner.scan();

ClassPluginDocumentation<? extends AbstractTrigger> doc = ClassPluginDocumentation.of(jsonSchemaGenerator, scan, Schedule.class, null);

assertThat(doc.getDefs().size(), is(3));

assertThat(((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.conditions.ScheduleCondition")).get("type"), is("object"));
assertThat(((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.conditions.ScheduleCondition")).get("properties")).size(), is(0));
}));
}
}
5 changes: 4 additions & 1 deletion ui/src/components/flows/TaskEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
>
<span v-if="component !== 'el-button'">{{ $t('show task source') }}</span>
<el-drawer
:title="`Task ${taskId || task.id}`"
v-if="isModalOpen"
v-model="isModalOpen"
destroy-on-close
lock-scroll
size=""
:append-to-body="true"
>
<template #header>
<code>{{ taskId || task.id }}</code>
</template>
<template #footer>
<div v-loading="isLoading">
<el-button :icon="ContentSave" @click="saveTask" v-if="canSave && !isReadOnly" type="primary">
Expand Down Expand Up @@ -52,6 +54,7 @@
<task-editor
ref="editor"
v-model="taskYaml"
:section="section"
/>
</el-tab-pane>
</el-tabs>
Expand Down
28 changes: 15 additions & 13 deletions ui/src/components/flows/TaskEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</template>
<plugin-select
v-model="selectedTaskType"
:section="section"
@update:model-value="onTaskTypeSelect"
/>
</el-form-item>
Expand Down Expand Up @@ -41,11 +42,16 @@
}
},
props: {
modelValue : {
modelValue: {
type: String,
required: false,
default: undefined,
},
section: {
type: String,
required: true,
default: undefined,
}
},
data() {
return {
Expand All @@ -55,15 +61,6 @@
plugin: undefined
};
},
computed: {
taskModels() {
const taskModels = [];
for (const plugin of this.plugins || []) {
taskModels.push.apply(taskModels, plugin.tasks);
}
return taskModels;
},
},
methods: {
load() {
this.isLoading = true;
Expand All @@ -84,10 +81,15 @@
onTaskTypeSelect() {
this.load();
this.onInput({
id: this.taskObject && this.taskObject.id ? this.taskObject.id : "",
const value = {
type: this.selectedTaskType
});
};
if (this.section !== "conditions") {
value["id"] = this.taskObject && this.taskObject.id ? this.taskObject.id : "";
}
this.onInput(value);
},
},
};
Expand Down
4 changes: 4 additions & 0 deletions ui/src/components/flows/tasks/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export default {
if (property.$ref.includes("Task")) {
return "task"
}

if (property.$ref.includes(".conditions.")) {
return "condition"
}
}

if (Object.prototype.hasOwnProperty.call(property, "additionalProperties")) {
Expand Down
1 change: 0 additions & 1 deletion ui/src/components/flows/tasks/TaskArray.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<template>
<div class="d-flex w-100 task-array" v-for="(item, index) in values" :key="'array-' + index">
<div class="flex-fill flex-grow-1 w-100 me-2">
<!--{{ `task-${getType(schema.items)}` + schema.items }}-->
<component
:is="`task-${getType(schema.items)}`"
:model-value="item"
Expand Down
5 changes: 4 additions & 1 deletion ui/src/components/flows/tasks/TaskComplex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@


<el-drawer
v-if="isOpen"
v-model="isOpen"
:title="root"
destroy-on-close
size=""
:append-to-body="true"
>
<template #header>
<code>{{ root }}</code>
</template>
<el-form label-position="top">
<task-object
v-if="schema"
Expand Down
69 changes: 69 additions & 0 deletions ui/src/components/flows/tasks/TaskCondition.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<el-input
:model-value="JSON.stringify(values)"
:disabled="true"
>
<template #append>
<el-button :icon="Eye" @click="this.isOpen = true" />
</template>
</el-input>

<el-drawer
v-if="isOpen"
v-model="isOpen"
:title="root"
destroy-on-close
size=""
:append-to-body="true"
>
<template #header>
<code>{{ root }}</code>
</template>
<el-form label-position="top">
<task-editor
ref="editor"
section="conditions"
:model-value="taskYaml"
@update:model-value="onInput"
/>
</el-form>

<template #footer>
<el-button :icon="ContentSave" @click="isOpen = false" type="primary">
{{ $t('save') }}
</el-button>
</template>
</el-drawer>
</template>

<script setup>
import Eye from "vue-material-design-icons/Eye.vue";
import ContentSave from "vue-material-design-icons/ContentSave.vue";
</script>

<script>
import Task from "./Task"
import YamlUtils from "../../../utils/yamlUtils";
import TaskEditor from "../TaskEditor.vue"
export default {
mixins: [Task],
components: {TaskEditor},
data() {
return {
isOpen: false,
};
},
computed: {
taskYaml() {
return YamlUtils.stringify(this.modelValue);
}
},
methods: {
onInput(value) {
this.$emit("update:modelValue", YamlUtils.parse(value));
},
},
};
</script>

6 changes: 2 additions & 4 deletions ui/src/components/flows/tasks/TaskDict.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@
computed: {
values() {
if (this.modelValue === undefined) {
return {key: "", data: undefined};
return {"": undefined};
}
return this.modelValue;
},
},
keyDebounce: undefined,
methods:{
onInput(key, value) {
const local = this.modelValue || {};
Expand Down Expand Up @@ -75,7 +74,6 @@
this.$emit("update:modelValue", local);
},
}
},
};
</script>
Loading

0 comments on commit d678210

Please sign in to comment.