mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
19 Commits
docs/purge
...
v0.13.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2888d929aa | ||
|
|
8a75fd3fe5 | ||
|
|
3b1bed8bf4 | ||
|
|
01128f0927 | ||
|
|
bface3755a | ||
|
|
8fe518ceae | ||
|
|
28e39ab756 | ||
|
|
24843cfc17 | ||
|
|
2e1c3733a4 | ||
|
|
61db3ccedb | ||
|
|
6a03dc9e41 | ||
|
|
b2a107bc55 | ||
|
|
97a6afbe2e | ||
|
|
cc800b4528 | ||
|
|
da50cf4287 | ||
|
|
1ecfed1559 | ||
|
|
4a3e250019 | ||
|
|
2917c178a5 | ||
|
|
0ceaeb4a97 |
@@ -22,6 +22,7 @@ dependencies {
|
||||
implementation group: 'net.jodah', name: 'failsafe', version: '2.4.4'
|
||||
implementation 'com.github.oshi:oshi-core:6.4.6'
|
||||
implementation 'io.pebbletemplates:pebble:3.2.1'
|
||||
implementation group: 'co.elastic.logging', name: 'logback-ecs-encoder', version: '1.5.0'
|
||||
|
||||
// scheduler
|
||||
implementation group: 'com.cronutils', name: 'cron-utils', version: '9.2.1'
|
||||
|
||||
@@ -696,7 +696,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
* @return List of parent {@link TaskRun}
|
||||
*/
|
||||
public List<TaskRun> findChilds(TaskRun taskRun) {
|
||||
if (taskRun.getParentTaskRunId() == null) {
|
||||
if (taskRun.getParentTaskRunId() == null || this.taskRunList == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class ExecutableUtils {
|
||||
|
||||
return WorkerTaskExecution.builder()
|
||||
.task(currentTask)
|
||||
.taskRun(currentTaskRun)
|
||||
.taskRun(currentTaskRun.withState(State.Type.RUNNING))
|
||||
.execution(execution)
|
||||
.iteration(iteration)
|
||||
.build();
|
||||
@@ -117,11 +117,11 @@ public final class ExecutableUtils {
|
||||
State.Type currentState = taskRun.getState().getCurrent();
|
||||
Optional<State.Type> previousState = taskRun.getState().getHistories().size() > 1 ? Optional.of(taskRun.getState().getHistories().get(taskRun.getState().getHistories().size() - 2).getState()) : Optional.empty();
|
||||
|
||||
int currentStateIteration = getIterationCounter(iterations, currentState, maxIterations) + 1;
|
||||
iterations.put(currentState.toString(), currentStateIteration);
|
||||
int currentStateIteration = iterations.getOrDefault(currentState.toString(), 0);
|
||||
iterations.put(currentState.toString(), currentStateIteration + 1);
|
||||
if (previousState.isPresent() && previousState.get() != currentState) {
|
||||
int previousStateIterations = getIterationCounter(iterations, previousState.get(), maxIterations) - 1;
|
||||
iterations.put(previousState.get().toString(), previousStateIterations);
|
||||
int previousStateIterations = iterations.getOrDefault(previousState.get() .toString(), maxIterations);
|
||||
iterations.put(previousState.get().toString(), previousStateIterations - 1);
|
||||
}
|
||||
|
||||
// update the state to success if current == max
|
||||
|
||||
@@ -107,7 +107,6 @@ public class NamespaceFilesService {
|
||||
private void copy(String tenantId, String namespace, Path basePath, List<URI> files) throws IOException {
|
||||
files
|
||||
.forEach(throwConsumer(f -> {
|
||||
InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f));
|
||||
Path destination = Paths.get(basePath.toString(), f.getPath());
|
||||
|
||||
if (!destination.getParent().toFile().exists()) {
|
||||
@@ -115,7 +114,9 @@ public class NamespaceFilesService {
|
||||
destination.getParent().toFile().mkdirs();
|
||||
}
|
||||
|
||||
Files.copy(inputStream, destination);
|
||||
try (InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f))) {
|
||||
Files.copy(inputStream, destination);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ public class RunContext {
|
||||
URI uri = URI.create(this.taskStateFilePathPrefix(state, isNamespace, useTaskRun));
|
||||
URI resolve = uri.resolve(uri.getPath() + "/" + name);
|
||||
|
||||
return this.storageInterface.get(getTenantId(), resolve);
|
||||
return this.storageInterface.get(getTenantId(), resolve);
|
||||
}
|
||||
|
||||
public URI putTaskStateFile(byte[] content, String state, String name) throws IOException {
|
||||
|
||||
@@ -5,11 +5,6 @@ import com.github.jknack.handlebars.Handlebars;
|
||||
import com.github.jknack.handlebars.HandlebarsException;
|
||||
import com.github.jknack.handlebars.Template;
|
||||
import com.github.jknack.handlebars.helper.*;
|
||||
import io.pebbletemplates.pebble.PebbleEngine;
|
||||
import io.pebbletemplates.pebble.error.AttributeNotFoundException;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.AbstractExtension;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.runners.handlebars.VariableRendererPlugins;
|
||||
import io.kestra.core.runners.handlebars.helpers.*;
|
||||
@@ -18,6 +13,15 @@ import io.kestra.core.runners.pebble.JsonWriter;
|
||||
import io.kestra.core.runners.pebble.PebbleLruCache;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.ConfigurationProperties;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.pebbletemplates.pebble.PebbleEngine;
|
||||
import io.pebbletemplates.pebble.error.AttributeNotFoundException;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.AbstractExtension;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
@@ -26,11 +30,6 @@ import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.Getter;
|
||||
|
||||
@Singleton
|
||||
public class VariableRenderer {
|
||||
private static final Pattern RAW_PATTERN = Pattern.compile("\\{%[-]*\\s*raw\\s*[-]*%\\}(.*?)\\{%[-]*\\s*endraw\\s*[-]*%\\}");
|
||||
@@ -93,6 +92,10 @@ public class VariableRenderer {
|
||||
}
|
||||
|
||||
public String recursiveRender(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return recursiveRender(inline, variables, true);
|
||||
}
|
||||
|
||||
public String recursiveRender(String inline, Map<String, Object> variables, Boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||
if (inline == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -111,6 +114,11 @@ public class VariableRenderer {
|
||||
return uuid;
|
||||
});
|
||||
|
||||
// pre-process variables
|
||||
if (preprocessVariables) {
|
||||
variables = this.preprocessVariables(variables);
|
||||
}
|
||||
|
||||
boolean isSame = false;
|
||||
String current = "";
|
||||
PebbleTemplate compiledTemplate;
|
||||
@@ -131,28 +139,43 @@ public class VariableRenderer {
|
||||
}
|
||||
|
||||
try {
|
||||
Template template = handlebars.compileInline(currentTemplate);
|
||||
Template template = handlebars.compileInline(currentTemplate);
|
||||
current = template.apply(variables);
|
||||
} catch (HandlebarsException | IOException hbE) {
|
||||
throw new IllegalVariableEvaluationException(
|
||||
"Pebble evaluation failed with '" + e.getMessage() + "' " +
|
||||
"and Handlebars fallback failed also with '" + hbE.getMessage() + "'" ,
|
||||
"Pebble evaluation failed with '" + e.getMessage() + "' " +
|
||||
"and Handlebars fallback failed also with '" + hbE.getMessage() + "'",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isSame = currentTemplate.equals(current);
|
||||
currentTemplate = current;
|
||||
}
|
||||
|
||||
// post-process raw tags
|
||||
for(var entry: replacers.entrySet()) {
|
||||
for (var entry : replacers.entrySet()) {
|
||||
current = current.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public Map<String, Object> preprocessVariables(Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
Map<String, Object> currentVariables = variables;
|
||||
Map<String, Object> previousVariables;
|
||||
boolean isSame = false;
|
||||
|
||||
while (!isSame) {
|
||||
previousVariables = currentVariables;
|
||||
currentVariables = this.render(currentVariables, variables, false);
|
||||
isSame = previousVariables.equals(currentVariables);
|
||||
}
|
||||
|
||||
return currentVariables;
|
||||
}
|
||||
|
||||
public IllegalVariableEvaluationException properPebbleException(PebbleException e) {
|
||||
if (e instanceof AttributeNotFoundException current) {
|
||||
return new IllegalVariableEvaluationException(
|
||||
@@ -166,17 +189,27 @@ public class VariableRenderer {
|
||||
return new IllegalVariableEvaluationException(e);
|
||||
}
|
||||
|
||||
// By default, render() will render variables
|
||||
// so we keep the previous behavior
|
||||
public String render(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return this.recursiveRender(inline, variables);
|
||||
return this.recursiveRender(inline, variables, true);
|
||||
}
|
||||
|
||||
public String render(String inline, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||
return this.recursiveRender(inline, variables, preprocessVariables);
|
||||
}
|
||||
|
||||
// Default behavior
|
||||
public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return render(in, variables, true);
|
||||
}
|
||||
|
||||
public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> r : in.entrySet()) {
|
||||
String key = this.render(r.getKey(), variables);
|
||||
Object value = renderObject(r.getValue(), variables).orElse(r.getValue());
|
||||
String key = this.render(r.getKey(), variables, preprocessVariables);
|
||||
Object value = renderObject(r.getValue(), variables, preprocessVariables).orElse(r.getValue());
|
||||
|
||||
map.putIfAbsent(
|
||||
key,
|
||||
@@ -188,23 +221,25 @@ public class VariableRenderer {
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private Optional<Object> renderObject(Object object, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
private Optional<Object> renderObject(Object object, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||
if (object instanceof Map) {
|
||||
return Optional.of(this.render((Map) object, variables));
|
||||
return Optional.of(this.render((Map) object, variables, preprocessVariables));
|
||||
} else if (object instanceof Collection) {
|
||||
return Optional.of(this.renderList((List) object, variables));
|
||||
return Optional.of(this.renderList((List) object, variables, preprocessVariables));
|
||||
} else if (object instanceof String) {
|
||||
return Optional.of(this.render((String) object, variables));
|
||||
return Optional.of(this.render((String) object, variables, preprocessVariables));
|
||||
} else if (object == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
return Optional.of(object);
|
||||
}
|
||||
|
||||
public List<Object> renderList(List<Object> list, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
private List<Object> renderList(List<Object> list, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||
List<Object> result = new ArrayList<>();
|
||||
|
||||
for (Object inline : list) {
|
||||
this.renderObject(inline, variables)
|
||||
this.renderObject(inline, variables, preprocessVariables)
|
||||
.ifPresent(result::add);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.tenant.TenantService;
|
||||
import io.kestra.core.utils.Slugify;
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.Function;
|
||||
@@ -11,6 +10,7 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
@@ -47,7 +47,9 @@ public class ReadFileFunction implements Function {
|
||||
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
URI namespaceFile = URI.create(storageInterface.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
|
||||
return new String(storageInterface.get(flow.get("tenantId"), namespaceFile).readAllBytes(), StandardCharsets.UTF_8);
|
||||
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), namespaceFile)) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private String readFromInternalStorageUri(EvaluationContext context, String path) throws IOException {
|
||||
@@ -69,7 +71,9 @@ public class ReadFileFunction implements Function {
|
||||
}
|
||||
}
|
||||
URI internalStorageFile = URI.create(path);
|
||||
return new String(storageInterface.get(flow.get("tenantId"), internalStorageFile).readAllBytes(), StandardCharsets.UTF_8);
|
||||
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), internalStorageFile)) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateFileUri(String namespace, String flowId, String executionId, String path) {
|
||||
|
||||
@@ -4,10 +4,7 @@ import io.kestra.core.runners.RunContext;
|
||||
import io.kestra.core.storages.StorageSplitInterface;
|
||||
import io.micronaut.core.convert.format.ReadableBytesTypeConverter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -42,8 +42,7 @@ public interface StorageInterface {
|
||||
* @return true if the uri points to a file/object that exist in the internal storage.
|
||||
*/
|
||||
default boolean exists(String tenantId, URI uri) {
|
||||
try {
|
||||
get(tenantId, uri);
|
||||
try (InputStream ignored = get(tenantId, uri)){
|
||||
return true;
|
||||
} catch (IOException ieo) {
|
||||
return false;
|
||||
|
||||
@@ -175,7 +175,10 @@ public class Flow extends Task implements ExecutableTask<Flow.Output> {
|
||||
|
||||
taskRun = taskRun.withOutputs(builder.build().toMap());
|
||||
|
||||
taskRun = taskRun.withState(ExecutableUtils.guessState(execution, this.transmitFailed, State.Type.SUCCESS));
|
||||
State.Type finalState = ExecutableUtils.guessState(execution, this.transmitFailed, State.Type.SUCCESS);
|
||||
if (taskRun.getState().getCurrent() != finalState) {
|
||||
taskRun = taskRun.withState(finalState);
|
||||
}
|
||||
|
||||
return Optional.of(ExecutableUtils.workerTaskResult(taskRun));
|
||||
}
|
||||
|
||||
@@ -54,9 +54,9 @@ public abstract class AbstractState extends Task {
|
||||
|
||||
|
||||
protected Map<String, Object> get(RunContext runContext) throws IllegalVariableEvaluationException, IOException {
|
||||
InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue);
|
||||
|
||||
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
|
||||
try (InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue)) {
|
||||
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
|
||||
}
|
||||
}
|
||||
|
||||
protected Pair<URI, Map<String, Object>> merge(RunContext runContext, Map<String, Object> map) throws IllegalVariableEvaluationException, IOException {
|
||||
|
||||
@@ -16,6 +16,7 @@ import io.kestra.core.runners.RunContext;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
@@ -128,7 +129,9 @@ public class Concat extends Task implements RunnableTask<Concat.Output> {
|
||||
|
||||
finalFiles.forEach(throwConsumer(s -> {
|
||||
URI from = new URI(runContext.render(s));
|
||||
IOUtils.copyLarge(runContext.uriToInputStream(from), fileOutputStream);
|
||||
try (InputStream inputStream = runContext.uriToInputStream(from)) {
|
||||
IOUtils.copyLarge(inputStream, fileOutputStream);
|
||||
}
|
||||
|
||||
if (separator != null) {
|
||||
IOUtils.copy(new ByteArrayInputStream(this.separator.getBytes()), fileOutputStream);
|
||||
|
||||
@@ -165,7 +165,7 @@ public class FlowTopologyService {
|
||||
.filter(t -> t instanceof ExecutableTask)
|
||||
.map(t -> (ExecutableTask<?>) t)
|
||||
.anyMatch(t ->
|
||||
t.subflowId().namespace().equals(child.getNamespace()) && t.subflowId().flowId().equals(child.getId())
|
||||
t.subflowId() != null && t.subflowId().namespace().equals(child.getNamespace()) && t.subflowId().flowId().equals(child.getId())
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to detect flow task on namespace:'" + parent.getNamespace() + "', flowId:'" + parent.getId() + "'", e);
|
||||
|
||||
25
core/src/main/resources/logback/ecs.xml
Normal file
25
core/src/main/resources/logback/ecs.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<appender name="CONSOLE_ECS_OUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<immediateFlush>true</immediateFlush>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
</filter>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>WARN</level>
|
||||
<onMatch>DENY</onMatch>
|
||||
</filter>
|
||||
<encoder class="co.elastic.logging.logback.EcsEncoder" />
|
||||
</appender>
|
||||
|
||||
<appender name="CONSOLE_ECS_ERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<immediateFlush>true</immediateFlush>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
<encoder class="co.elastic.logging.logback.EcsEncoder" />
|
||||
</appender>
|
||||
</included>
|
||||
@@ -35,8 +35,8 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
Flow flow = this.parse("flows/valids/return.yaml");
|
||||
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
||||
|
||||
assertThat(flowGraph.getNodes().size(), is(5));
|
||||
assertThat(flowGraph.getEdges().size(), is(4));
|
||||
assertThat(flowGraph.getNodes().size(), is(6));
|
||||
assertThat(flowGraph.getEdges().size(), is(5));
|
||||
assertThat(flowGraph.getClusters().size(), is(0));
|
||||
|
||||
assertThat(((AbstractGraphTask) flowGraph.getNodes().get(2)).getTask().getId(), is("date"));
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
@Property(name = "kestra.tasks.tmp-dir.path", value = "/tmp/sub/dir/tmp/")
|
||||
@@ -112,7 +113,7 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
void variables() throws TimeoutException {
|
||||
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "return");
|
||||
|
||||
assertThat(execution.getTaskRunList(), hasSize(3));
|
||||
assertThat(execution.getTaskRunList(), hasSize(4));
|
||||
|
||||
assertThat(
|
||||
ZonedDateTime.from(ZonedDateTime.parse((String) execution.getTaskRunList().get(0).getOutputs().get("value"))),
|
||||
@@ -120,6 +121,7 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
);
|
||||
assertThat(execution.getTaskRunList().get(1).getOutputs().get("value"), is("task-id"));
|
||||
assertThat(execution.getTaskRunList().get(2).getOutputs().get("value"), is("return"));
|
||||
assertThat((String) execution.getTaskRunList().get(3).getOutputs().get("value"), containsString("toto"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,13 +5,13 @@ import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.runners.VariableRenderer;
|
||||
import io.kestra.core.utils.Rethrow;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
@@ -105,8 +105,8 @@ public class ForEachItemCaseTest {
|
||||
assertThat(outputs.get("iterations"), notNullValue());
|
||||
Map<String, Integer> iterations = (Map<String, Integer>) outputs.get("iterations");
|
||||
assertThat(iterations.get("max"), is(3));
|
||||
assertThat(iterations.get("CREATED"), is(0));
|
||||
assertThat(iterations.get("RUNNING"), nullValue()); // if we didn't wait we will only observe CREATED and SUCCESS
|
||||
assertThat(iterations.get("CREATED"), nullValue());// if we didn't wait we will only observe RUNNING and SUCCESS
|
||||
assertThat(iterations.get("RUNNING"), is(0));
|
||||
assertThat(iterations.get("SUCCESS"), is(3));
|
||||
|
||||
// assert that not all subflows ran (depending on the speed of execution there can be some)
|
||||
|
||||
@@ -10,7 +10,7 @@ inputs:
|
||||
|
||||
variables:
|
||||
var-1: var-1
|
||||
var-2: '{{ var-1 }}'
|
||||
var-2: "{{ 'var-1' }}"
|
||||
|
||||
taskDefaults:
|
||||
- type: io.kestra.core.tasks.log.Log
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
id: return
|
||||
namespace: io.kestra.tests
|
||||
|
||||
variables:
|
||||
period: "{{ schedule.date ?? execution.startDate }}"
|
||||
|
||||
tasks:
|
||||
- id: date
|
||||
type: io.kestra.core.tasks.debugs.Return
|
||||
@@ -10,4 +13,7 @@ tasks:
|
||||
format: "{{task.id}}"
|
||||
- id: flow-id
|
||||
type: io.kestra.core.tasks.debugs.Return
|
||||
format: "{{flow.id}}"
|
||||
format: "{{flow.id}}"
|
||||
- id: variables
|
||||
type: io.kestra.core.tasks.debugs.Return
|
||||
format: "{{ vars.period | replace({':': 'toto'}) }}"
|
||||
@@ -1,4 +1,4 @@
|
||||
version=0.13.0
|
||||
version=0.13.4
|
||||
|
||||
jacksonVersion=2.15.2
|
||||
micronautVersion=3.10.1
|
||||
|
||||
@@ -406,7 +406,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
||||
|
||||
// send a running worker task result to track running vs created status
|
||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING));
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -426,7 +426,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
||||
.ifPresent(workerTaskExecution -> {
|
||||
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING).withState(execution.getState().getCurrent()));
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(execution.getState().getCurrent()));
|
||||
}
|
||||
|
||||
workerTaskExecutionStorage.delete(workerTaskExecution);
|
||||
|
||||
@@ -237,7 +237,7 @@ public class MemoryExecutor implements ExecutorInterface {
|
||||
|
||||
// send a running worker task result to track running vs created status
|
||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING));
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -259,7 +259,7 @@ public class MemoryExecutor implements ExecutorInterface {
|
||||
|
||||
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING).withState(execution.getState().getCurrent()));
|
||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(execution.getState().getCurrent()));
|
||||
}
|
||||
|
||||
WORKERTASKEXECUTIONS_WATCHER.remove(execution.getId());
|
||||
|
||||
@@ -30,6 +30,31 @@ const extensionsToFetch = Object.entries(versionByExtensionIdToFetch).map(([exte
|
||||
}));
|
||||
|
||||
// used to configure VSCode startup
|
||||
const sidebarTabs = [
|
||||
{"id": "workbench.view.explorer", "pinned": true, "visible": true, "order": 0},
|
||||
{"id": "workbench.view.search", "pinned": true, "visible": true, "order": 1},
|
||||
{"id": "workbench.view.scm", "pinned": false, "visible": false, "order": 2},
|
||||
{"id": "workbench.view.debug", "pinned": false,"visible": false,"order": 3},
|
||||
{"id": "workbench.view.extensions", "pinned": true,"visible": true,"order": 4},
|
||||
{"id": "workbench.view.remote", "pinned": false,"visible": false,"order": 4},
|
||||
{"id": "workbench.view.extension.test", "pinned": false,"visible": false,"order": 6},
|
||||
{"id": "workbench.view.extension.references-view", "pinned": false,"visible": false,"order": 7},
|
||||
{"id": "workbench.panel.chatSidebar", "pinned": false,"visible": false,"order": 100},
|
||||
{"id": "userDataProfiles", "pinned": false, "visible": false},
|
||||
{"id": "workbench.view.sync", "pinned": false,"visible": false},
|
||||
{"id": "workbench.view.editSessions", "pinned": false, "visible": false}
|
||||
];
|
||||
|
||||
const bottomBarTabs = [
|
||||
{"id":"workbench.panel.markers", "pinned": false,"visible": false,"order": 0},
|
||||
{"id":"workbench.panel.output", "pinned": false,"visible": false,"order": 1},
|
||||
{"id":"workbench.panel.repl", "pinned": false,"visible": false,"order": 2},
|
||||
{"id":"terminal", "pinned": false,"visible": false,"order": 3},
|
||||
{"id":"workbench.panel.testResults", "pinned": false,"visible": false,"order": 3},
|
||||
{"id":"~remote.forwardedPortsContainer", "pinned": false,"visible": false,"order": 5},
|
||||
{"id":"refactorPreview", "pinned": false,"visible": false}
|
||||
];
|
||||
|
||||
window.product = {
|
||||
productConfiguration: {
|
||||
nameShort: "Kestra VSCode",
|
||||
@@ -77,5 +102,17 @@ window.product = {
|
||||
"workbench.colorTheme": THEME === "dark" ? "Sweet Dracula" : "Default Light Modern",
|
||||
// provide the Kestra root URL to extension
|
||||
"kestra.api.url": KESTRA_API_URL
|
||||
},
|
||||
profile: {
|
||||
name: "Kestra VSCode",
|
||||
contents: JSON.stringify({
|
||||
globalState: JSON.stringify({
|
||||
"storage": {
|
||||
"workbench.activity.pinnedViewlets2": sidebarTabs,
|
||||
"workbench.activity.showAccounts": "false",
|
||||
"workbench.panel.pinnedPanels": bottomBarTabs
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -447,7 +447,7 @@
|
||||
},
|
||||
methods: {
|
||||
onDisplayColumnsChange(event) {
|
||||
localStorage.setItem("displayExecutionsColumns", event);
|
||||
localStorage.setItem(this.storageKey, event);
|
||||
this.displayColumns = event;
|
||||
},
|
||||
displayColumn(column) {
|
||||
@@ -608,4 +608,4 @@
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
executionId: this.executionId,
|
||||
path: this.value,
|
||||
maxRows: this.maxPreview,
|
||||
encoding: this.encoding
|
||||
encoding: this.encoding.value
|
||||
})
|
||||
.then(() => {
|
||||
this.isPreviewOpen = true;
|
||||
|
||||
@@ -456,15 +456,15 @@
|
||||
}
|
||||
|
||||
.bottom-right {
|
||||
bottom: var(--spacer);
|
||||
right: var(--spacer);
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: calc(var(--spacer) / 2);
|
||||
//gap: calc(var(--spacer) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
121
ui/src/components/inputs/EditorButtons.vue
Normal file
121
ui/src/components/inputs/EditorButtons.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="to-action-button">
|
||||
<div v-if="isAllowedEdit || canDelete" class="mx-2">
|
||||
<el-dropdown>
|
||||
<el-button type="default" :disabled="isReadOnly">
|
||||
<DotsVertical title=""/>
|
||||
{{ $t("actions") }}
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="m-dropdown-menu">
|
||||
<el-dropdown-item
|
||||
v-if="!isCreating && canDelete"
|
||||
:icon="Delete"
|
||||
size="large"
|
||||
@click="forwardEvent('delete-flow', $event)"
|
||||
>
|
||||
{{ $t("delete") }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
v-if="!isCreating"
|
||||
:icon="ContentCopy"
|
||||
size="large"
|
||||
@click="forwardEvent('copy', $event)"
|
||||
>
|
||||
{{ $t("copy") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="Exclamation"
|
||||
size="large"
|
||||
@click="forwardEvent('open-new-error', null)"
|
||||
:disabled="!flowHaveTasks"
|
||||
>
|
||||
{{ $t("add global error handler") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="LightningBolt"
|
||||
size="large"
|
||||
@click="forwardEvent('open-new-trigger', null)"
|
||||
:disabled="!flowHaveTasks"
|
||||
>
|
||||
{{ $t("add trigger") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="FileEdit"
|
||||
size="large"
|
||||
@click="forwardEvent('open-edit-metadata', null)"
|
||||
>
|
||||
{{ $t("edit metadata") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
:icon="ContentSave"
|
||||
@click="forwardEvent('save', $event)"
|
||||
v-if="isAllowedEdit"
|
||||
:type="flowError ? 'danger' : 'primary'"
|
||||
:disabled="!haveChange && !isCreating"
|
||||
class="edit-flow-save-button"
|
||||
>
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import DotsVertical from "vue-material-design-icons/DotsVertical.vue";
|
||||
import Delete from "vue-material-design-icons/Delete.vue";
|
||||
import ContentCopy from "vue-material-design-icons/ContentCopy.vue";
|
||||
import Exclamation from "vue-material-design-icons/Exclamation.vue";
|
||||
import LightningBolt from "vue-material-design-icons/LightningBolt.vue";
|
||||
import FileEdit from "vue-material-design-icons/FileEdit.vue";
|
||||
import ContentSave from "vue-material-design-icons/ContentSave.vue";
|
||||
</script>
|
||||
<script>
|
||||
import {defineComponent} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isCreating: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canDelete: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isAllowedEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
haveChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
flowHaveTasks: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
flowError: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
forwardEvent(type, event) {
|
||||
this.$emit(type, event);
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -28,6 +28,7 @@
|
||||
import {editorViewTypes} from "../../utils/constants";
|
||||
import Utils from "@kestra-io/ui-libs/src/utils/Utils";
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import EditorButtons from "./EditorButtons.vue";
|
||||
|
||||
const store = useStore();
|
||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||
@@ -675,75 +676,22 @@
|
||||
<ValidationError ref="validationDomElement" tooltip-placement="bottom-start" size="small" class="ms-2" :error="flowError" :warnings="flowWarnings" />
|
||||
</template>
|
||||
<template #buttons>
|
||||
<ul>
|
||||
<li v-if="isAllowedEdit || canDelete">
|
||||
<el-dropdown>
|
||||
<el-button type="default" :disabled="isReadOnly">
|
||||
<DotsVertical title="" />
|
||||
{{ $t("actions") }}
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="m-dropdown-menu">
|
||||
<el-dropdown-item
|
||||
v-if="!props.isCreating && canDelete"
|
||||
:icon="Delete"
|
||||
size="large"
|
||||
@click="deleteFlow"
|
||||
>
|
||||
{{ $t("delete") }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
v-if="!props.isCreating"
|
||||
:icon="ContentCopy"
|
||||
size="large"
|
||||
@click="() => router.push({name: 'flows/create', query: {copy: true}})"
|
||||
>
|
||||
{{ $t("copy") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="Exclamation"
|
||||
size="large"
|
||||
@click="isNewErrorOpen = true;"
|
||||
:disabled="!flowHaveTasks()"
|
||||
>
|
||||
{{ $t("add global error handler") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="LightningBolt"
|
||||
size="large"
|
||||
@click="isNewTriggerOpen = true;"
|
||||
:disabled="!flowHaveTasks()"
|
||||
>
|
||||
{{ $t("add trigger") }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="isAllowedEdit"
|
||||
:icon="FileEdit"
|
||||
size="large"
|
||||
@click="isEditMetadataOpen = true;"
|
||||
>
|
||||
{{ $t("edit metadata") }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
<li>
|
||||
<el-button
|
||||
:icon="ContentSave"
|
||||
@click="save"
|
||||
v-if="isAllowedEdit"
|
||||
:type="flowError ? 'danger' : 'primary'"
|
||||
:disabled="!haveChange && !isCreating"
|
||||
class="edit-flow-save-button"
|
||||
>
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</li>
|
||||
</ul>
|
||||
<EditorButtons
|
||||
v-if="![editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
|
||||
:is-creating="props.isCreating"
|
||||
:is-read-only="props.isReadOnly"
|
||||
:can-delete="canDelete"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
:have-change="haveChange"
|
||||
:flow-have-tasks="flowHaveTasks()"
|
||||
:flow-error="flowError"
|
||||
@delete-flow="deleteFlow"
|
||||
@save="save"
|
||||
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
|
||||
@open-new-error="isNewErrorOpen = true;"
|
||||
@open-new-trigger="isNewTriggerOpen = true;"
|
||||
@open-edit-metadata="isEditMetadataOpen = true;"
|
||||
/>
|
||||
</template>
|
||||
</editor>
|
||||
<div class="slider" @mousedown="dragEditor" v-if="combinedEditor" />
|
||||
@@ -859,6 +807,22 @@
|
||||
class="to-topology-button"
|
||||
@switch-view="switchViewType"
|
||||
/>
|
||||
<EditorButtons
|
||||
v-if="[editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
|
||||
:is-creating="props.isCreating"
|
||||
:is-read-only="props.isReadOnly"
|
||||
:can-delete="canDelete"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
:have-change="haveChange"
|
||||
:flow-have-tasks="flowHaveTasks()"
|
||||
:flow-error="flowError"
|
||||
@delete-flow="deleteFlow"
|
||||
@save="save"
|
||||
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
|
||||
@open-new-error="isNewErrorOpen = true;"
|
||||
@open-new-trigger="isNewTriggerOpen = true;"
|
||||
@open-edit-metadata="isEditMetadataOpen = true;"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@@ -879,6 +843,13 @@
|
||||
right: 45px;
|
||||
}
|
||||
|
||||
.to-action-button {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 45px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.editor-combined {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
:allow-auto-expand-subflows="false"
|
||||
:target-execution-id="currentTaskRun.outputs.executionId"
|
||||
:class="$el.classList.contains('even') ? '' : 'even'"
|
||||
:show-progress-bar="showProgressBar"
|
||||
:show-logs="showLogs"
|
||||
/>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
@@ -315,6 +317,16 @@
|
||||
this.logsWithIndexByAttemptUid[this.attemptUid(taskRun.id, this.selectedAttemptNumberByTaskRunId[taskRun.id])])) &&
|
||||
this.showLogs
|
||||
},
|
||||
followExecution(executionId) {
|
||||
this.$store
|
||||
.dispatch("execution/followExecution", {id: executionId})
|
||||
.then(sse => {
|
||||
this.executionSSE = sse;
|
||||
this.executionSSE.onmessage = async (event) => {
|
||||
this.followedExecution = JSON.parse(event.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
followLogs(executionId) {
|
||||
this.$store
|
||||
.dispatch("execution/followLogs", {id: executionId})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<sidebar-menu
|
||||
id="side-menu"
|
||||
:menu="menu"
|
||||
:menu="localMenu"
|
||||
@update:collapsed="onToggleCollapse"
|
||||
width="268px"
|
||||
:collapsed="collapsed"
|
||||
@@ -44,6 +44,7 @@
|
||||
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
|
||||
import {mapState} from "vuex";
|
||||
import AccountHardHatOutline from "vue-material-design-icons/AccountHardHatOutline.vue";
|
||||
import {shallowRef} from "vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -54,6 +55,16 @@
|
||||
},
|
||||
emits: ["menu-collapse"],
|
||||
methods: {
|
||||
flattenMenu(menu) {
|
||||
return menu.reduce((acc, item) => {
|
||||
if (item.child) {
|
||||
acc.push(...this.flattenMenu(item.child));
|
||||
}
|
||||
|
||||
acc.push(item);
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
onToggleCollapse(folded) {
|
||||
this.collapsed = folded;
|
||||
localStorage.setItem("menuCollapsed", folded ? "true" : "false");
|
||||
@@ -71,7 +82,7 @@
|
||||
r.class = "vsm--link_active";
|
||||
}
|
||||
|
||||
if (r.child && r.child.some(c => this.$route.path.startsWith(c.href))) {
|
||||
if (r.child && r.child.some(c => this.$route.path.startsWith(c.href) || c.routes?.includes(this.$route.name))) {
|
||||
r.class = "vsm--link_active";
|
||||
r.child = this.disabledCurrentRoute(r.child);
|
||||
}
|
||||
@@ -88,7 +99,7 @@
|
||||
href: {name: "home"},
|
||||
title: this.$t("homeDashboard.title"),
|
||||
icon: {
|
||||
element: ViewDashboardVariantOutline,
|
||||
element: shallowRef(ViewDashboardVariantOutline),
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -96,7 +107,7 @@
|
||||
href: {name: "editor"},
|
||||
title: this.$t("editor"),
|
||||
icon: {
|
||||
element: FolderEditOutline,
|
||||
element: shallowRef(FolderEditOutline),
|
||||
class: "menu-icon",
|
||||
},
|
||||
},
|
||||
@@ -105,7 +116,7 @@
|
||||
routes: this.routeStartWith("flows"),
|
||||
title: this.$t("flows"),
|
||||
icon: {
|
||||
element: FileTreeOutline,
|
||||
element: shallowRef(FileTreeOutline),
|
||||
class: "menu-icon",
|
||||
},
|
||||
exact: false,
|
||||
@@ -115,7 +126,7 @@
|
||||
routes: this.routeStartWith("templates"),
|
||||
title: this.$t("templates"),
|
||||
icon: {
|
||||
element: ContentCopy,
|
||||
element: shallowRef(ContentCopy),
|
||||
class: "menu-icon",
|
||||
},
|
||||
hidden: !this.configs.isTemplateEnabled
|
||||
@@ -125,7 +136,7 @@
|
||||
routes: this.routeStartWith("executions"),
|
||||
title: this.$t("executions"),
|
||||
icon: {
|
||||
element: TimelineClockOutline,
|
||||
element: shallowRef(TimelineClockOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -134,7 +145,7 @@
|
||||
routes: this.routeStartWith("taskruns"),
|
||||
title: this.$t("taskruns"),
|
||||
icon: {
|
||||
element: TimelineTextOutline,
|
||||
element: shallowRef(TimelineTextOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
hidden: !this.configs.isTaskRunEnabled
|
||||
@@ -144,7 +155,7 @@
|
||||
routes: this.routeStartWith("logs"),
|
||||
title: this.$t("logs"),
|
||||
icon: {
|
||||
element: NotebookOutline,
|
||||
element: shallowRef(NotebookOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -153,7 +164,7 @@
|
||||
routes: this.routeStartWith("blueprints"),
|
||||
title: this.$t("blueprints.title"),
|
||||
icon: {
|
||||
element: Ballot,
|
||||
element: shallowRef(Ballot),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -161,7 +172,7 @@
|
||||
title: this.$t("administration"),
|
||||
routes: this.routeStartWith("admin"),
|
||||
icon: {
|
||||
element: AccountSupervisorOutline,
|
||||
element: shallowRef(AccountSupervisorOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
child: [
|
||||
@@ -170,7 +181,7 @@
|
||||
routes: this.routeStartWith("admin/triggers"),
|
||||
title: this.$t("triggers"),
|
||||
icon: {
|
||||
element: TimerCogOutline,
|
||||
element: shallowRef(TimerCogOutline),
|
||||
class: "menu-icon"
|
||||
}
|
||||
},
|
||||
@@ -179,7 +190,7 @@
|
||||
routes: this.routeStartWith("admin/workers"),
|
||||
title: this.$t("workers"),
|
||||
icon: {
|
||||
element: AccountHardHatOutline,
|
||||
element: shallowRef(AccountHardHatOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
}
|
||||
@@ -190,41 +201,54 @@
|
||||
routes: this.routeStartWith("admin/settings"),
|
||||
title: this.$t("settings"),
|
||||
icon: {
|
||||
element: CogOutline,
|
||||
element: shallowRef(CogOutline),
|
||||
class: "menu-icon"
|
||||
}
|
||||
}
|
||||
];
|
||||
},
|
||||
expandParentIfNeeded() {
|
||||
document.querySelectorAll(".vsm--link_level-1.vsm--link_active:not(.vsm--link_open)[aria-haspopup]").forEach(e => e.click());
|
||||
document.querySelectorAll(".vsm--link.vsm--link_level-1.vsm--link_active:not(.vsm--link_open)[aria-haspopup]").forEach(e => {
|
||||
e.click()
|
||||
});
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
// Required here because in mounted() the menu is not yet rendered
|
||||
this.expandParentIfNeeded();
|
||||
},
|
||||
watch: {
|
||||
menu: {
|
||||
handler() {
|
||||
this.$el.querySelectorAll(".vsm--item span").forEach(e => {
|
||||
//empty icon name on mouseover
|
||||
e.setAttribute("title", "")
|
||||
});
|
||||
this.expandParentIfNeeded();
|
||||
handler(newVal, oldVal) {
|
||||
// Check if the active menu item has changed, if yes then update the menu
|
||||
if (JSON.stringify(this.flattenMenu(newVal).map(e => e.class?.includes("vsm--link_active") ?? false)) !==
|
||||
JSON.stringify(this.flattenMenu(oldVal).map(e => e.class?.includes("vsm--link_active") ?? false))) {
|
||||
this.localMenu = newVal;
|
||||
this.$el.querySelectorAll(".vsm--item span").forEach(e => {
|
||||
//empty icon name on mouseover
|
||||
e.setAttribute("title", "")
|
||||
});
|
||||
}
|
||||
},
|
||||
flush: 'post'
|
||||
}
|
||||
flush: "post",
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
collapsed: localStorage.getItem("menuCollapsed") === "true",
|
||||
localMenu: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("misc", ["configs"]),
|
||||
...
|
||||
mapState("misc", ["configs"]),
|
||||
menu() {
|
||||
return this.disabledCurrentRoute(this.generateMenu());
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.expandParentIfNeeded();
|
||||
this.localMenu = this.menu;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import {apiUrlWithoutTenants} from "override/utils/route";
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
|
||||
actions: {
|
||||
findAll(_, __) {
|
||||
return this.$http.get(`${apiUrl(this)}/workers`).then(response => {
|
||||
return this.$http.get(`${apiUrlWithoutTenants(this)}/workers`).then(response => {
|
||||
return response.data;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -413,11 +413,6 @@ form.ks-horizontal {
|
||||
|
||||
&.is-dark {
|
||||
color: var(--bs-gray-100);
|
||||
html:not(.dark) & {
|
||||
* {
|
||||
color: var(--bs-gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
background: var(--bs-gray-900);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
||||
@@ -1009,12 +1009,11 @@ public class ExecutionController {
|
||||
@Parameter(description = "The execution id") @PathVariable String executionId,
|
||||
@Parameter(description = "The internal storage uri") @QueryValue URI path,
|
||||
@Parameter(description = "The max row returns") @QueryValue @Nullable Integer maxRows,
|
||||
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue @Nullable String encoding
|
||||
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue(defaultValue = "UTF-8") String encoding
|
||||
) throws IOException {
|
||||
this.validateFile(executionId, path, "/api/v1/executions/{executionId}/file?path=" + path);
|
||||
|
||||
String extension = FilenameUtils.getExtension(path.toString());
|
||||
InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path);
|
||||
Optional<Charset> charset;
|
||||
|
||||
try {
|
||||
@@ -1023,13 +1022,15 @@ public class ExecutionController {
|
||||
throw new IllegalArgumentException("Unable to preview using encoding '" + encoding + "'");
|
||||
}
|
||||
|
||||
FileRender fileRender = FileRenderBuilder.of(
|
||||
extension,
|
||||
fileStream,
|
||||
charset,
|
||||
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
|
||||
);
|
||||
try (InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path)){
|
||||
FileRender fileRender = FileRenderBuilder.of(
|
||||
extension,
|
||||
fileStream,
|
||||
charset,
|
||||
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
|
||||
);
|
||||
|
||||
return HttpResponse.ok(fileRender);
|
||||
return HttpResponse.ok(fileRender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,9 @@ public class NamespaceFileController {
|
||||
) throws IOException, URISyntaxException {
|
||||
ensureWritableFile(path);
|
||||
|
||||
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), new BufferedInputStream(fileContent.getInputStream()));
|
||||
try(BufferedInputStream inputStream = new BufferedInputStream(fileContent.getInputStream())) {
|
||||
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
|
||||
Reference in New Issue
Block a user