diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 185418677d..7a374e3dd7 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -32,7 +32,6 @@ updates:
timezone: "Europe/Paris"
open-pull-requests-limit: 50
labels: ["dependency-upgrade"]
- reviewers: ["MilosPaunovic"]
ignore:
# Ignore major versions greater than 8, as it's still known to be flaky
- dependency-name: "eslint"
diff --git a/.github/workflows/generate_translations.yml b/.github/workflows/generate_translations.yml
index a86f53f105..0d37c9edaa 100644
--- a/.github/workflows/generate_translations.yml
+++ b/.github/workflows/generate_translations.yml
@@ -108,4 +108,4 @@ jobs:
exit 0
fi
git commit -m "chore(translations): auto generate values for languages other than english"
- git push origin $BRANCH_NAME
+ git push origin $BRANCH_NAME
\ No newline at end of file
diff --git a/README.md b/README.md
index 6bb58967fb..1d0a86c771 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
-
+
@@ -167,7 +167,7 @@ Create custom plugins to extend Kestra's capabilities. Check out our [Plugin Dev
Stay connected and get support:
- **Slack:** Join our [Slack community](https://kestra.io/slack) to ask questions and share ideas.
-- **Twitter:** Follow us on [Twitter](https://twitter.com/kestra_io) for the latest updates.
+- **X(formerly Twitter):** Follow us on [X(formerly Twitter)](https://x.com/kestra_io) for the latest updates.
- **YouTube:** Subscribe to our [YouTube channel](https://www.youtube.com/@kestra-io) for tutorials and webinars.
- **LinkedIn:** Connect with us on [LinkedIn](https://www.linkedin.com/company/kestra/).
diff --git a/build.gradle b/build.gradle
index 9c943ff6df..577f7e1596 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,7 +27,7 @@ plugins {
id "com.github.ben-manes.versions" version "0.51.0"
// front
- id 'org.siouan.frontend-jdk17' version '8.1.0' apply false
+ id 'org.siouan.frontend-jdk21' version '9.0.0' apply false
// release
id "io.github.gradle-nexus.publish-plugin" version "2.0.0"
diff --git a/cli/src/main/java/io/kestra/cli/AbstractValidateCommand.java b/cli/src/main/java/io/kestra/cli/AbstractValidateCommand.java
index d550e84c02..8d5fce6ee0 100644
--- a/cli/src/main/java/io/kestra/cli/AbstractValidateCommand.java
+++ b/cli/src/main/java/io/kestra/cli/AbstractValidateCommand.java
@@ -1,6 +1,5 @@
package io.kestra.cli;
-import io.kestra.cli.commands.flows.FlowValidateCommand;
import io.kestra.core.models.validations.ModelValidator;
import io.kestra.core.models.validations.ValidateConstraintViolation;
import io.kestra.core.serializers.YamlFlowParser;
@@ -75,7 +74,8 @@ public Integer call(
YamlFlowParser yamlFlowParser,
ModelValidator modelValidator,
Function identity,
- Function> warningsFunction
+ Function> warningsFunction,
+ Function> infosFunction
) throws Exception {
super.call();
@@ -93,6 +93,8 @@ public Integer call(
stdOut("@|green \u2713|@ - " + identity.apply(parse));
List warnings = warningsFunction.apply(parse);
warnings.forEach(warning -> stdOut("@|bold,yellow \u26A0|@ - " + warning));
+ List infos = infosFunction.apply(parse);
+ infos.forEach(info -> stdOut("@|bold,blue \u2139|@ - " + info));
} catch (ConstraintViolationException e) {
stdErr("@|red \u2718|@ - " + path);
AbstractValidateCommand.handleException(e, clsName);
diff --git a/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java b/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
index 7783b904e4..2b3abb5215 100644
--- a/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
+++ b/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
@@ -39,9 +39,12 @@ public Integer call() throws Exception {
Flow flow = (Flow) object;
List warnings = new ArrayList<>();
warnings.addAll(flowService.deprecationPaths(flow).stream().map(deprecation -> deprecation + " is deprecated").toList());
- warnings.addAll(flowService.relocations(flow.generateSource()).stream().map(relocation -> relocation.from() + " is replaced by " + relocation.to()).toList());
warnings.addAll(flowService.warnings(flow));
return warnings;
+ },
+ (Object object) -> {
+ Flow flow = (Flow) object;
+ return flowService.relocations(flow.generateSource()).stream().map(relocation -> relocation.from() + " is replaced by " + relocation.to()).toList();
}
);
}
diff --git a/cli/src/main/java/io/kestra/cli/commands/templates/TemplateValidateCommand.java b/cli/src/main/java/io/kestra/cli/commands/templates/TemplateValidateCommand.java
index 74e3e29868..fec68750c6 100644
--- a/cli/src/main/java/io/kestra/cli/commands/templates/TemplateValidateCommand.java
+++ b/cli/src/main/java/io/kestra/cli/commands/templates/TemplateValidateCommand.java
@@ -32,6 +32,7 @@ public Integer call() throws Exception {
Template template = (Template) object;
return template.getNamespace() + " / " + template.getId();
},
+ (Object object) -> Collections.emptyList(),
(Object object) -> Collections.emptyList()
);
}
diff --git a/cli/src/main/java/io/kestra/cli/services/FileChangedEventListener.java b/cli/src/main/java/io/kestra/cli/services/FileChangedEventListener.java
index e56a34f651..622f02a6bd 100644
--- a/cli/src/main/java/io/kestra/cli/services/FileChangedEventListener.java
+++ b/cli/src/main/java/io/kestra/cli/services/FileChangedEventListener.java
@@ -121,7 +121,7 @@ public void startListening(List paths) throws IOException, InterruptedExce
WatchEvent.Kind> kind = watchEvent.kind();
Path entry = (Path) watchEvent.context();
- if (entry.endsWith(".yml") || entry.endsWith(".yaml")) {
+ if (entry.toString().endsWith(".yml") || entry.toString().endsWith(".yaml")) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) {
diff --git a/cli/src/main/java/io/kestra/cli/services/LocalFlowFileWatcher.java b/cli/src/main/java/io/kestra/cli/services/LocalFlowFileWatcher.java
index d151204b01..2cf8ee53ce 100644
--- a/cli/src/main/java/io/kestra/cli/services/LocalFlowFileWatcher.java
+++ b/cli/src/main/java/io/kestra/cli/services/LocalFlowFileWatcher.java
@@ -23,12 +23,12 @@ public FlowWithSource createOrUpdateFlow(Flow flow, String content) {
public void deleteFlow(FlowWithSource toDelete) {
flowRepositoryInterface.findByIdWithSource(toDelete.getTenantId(), toDelete.getNamespace(), toDelete.getId()).ifPresent(flowRepositoryInterface::delete);
- log.debug("Flow {} has been deleted", toDelete.getId());
+ log.error("Flow {} has been deleted", toDelete.getId());
}
@Override
public void deleteFlow(String tenantId, String namespace, String id) {
flowRepositoryInterface.findByIdWithSource(tenantId, namespace, id).ifPresent(flowRepositoryInterface::delete);
- log.debug("Flow {} has been deleted", id);
+ log.error("Flow {} has been deleted", id);
}
}
diff --git a/cli/src/main/resources/application.yml b/cli/src/main/resources/application.yml
index ac1e695eeb..1576eb9083 100644
--- a/cli/src/main/resources/application.yml
+++ b/cli/src/main/resources/application.yml
@@ -188,3 +188,8 @@ kestra:
uri: https://api.kestra.io/v1/reports/usages
initial-delay: 5m
fixed-delay: 1h
+
+ hidden-labels:
+ prefixes:
+ - system_
+ - internal_
diff --git a/cli/src/test/java/io/kestra/cli/commands/flows/FlowValidateCommandTest.java b/cli/src/test/java/io/kestra/cli/commands/flows/FlowValidateCommandTest.java
index a552e20ae1..f760e2c590 100644
--- a/cli/src/test/java/io/kestra/cli/commands/flows/FlowValidateCommandTest.java
+++ b/cli/src/test/java/io/kestra/cli/commands/flows/FlowValidateCommandTest.java
@@ -42,7 +42,9 @@ void warning() {
Integer call = PicocliRunner.call(FlowValidateCommand.class, ctx, args);
assertThat(call, is(0));
- assertThat(out.toString(), containsString("tasks[0] is deprecated"));
+ assertThat(out.toString(), containsString("✓ - system / warning"));
+ assertThat(out.toString(), containsString("⚠ - tasks[0] is deprecated"));
+ assertThat(out.toString(), containsString("ℹ - io.kestra.core.tasks.log.Log is replaced by io.kestra.plugin.core.log.Log"));
}
}
}
\ No newline at end of file
diff --git a/cli/src/test/resources/warning/flow-with-warning.yaml b/cli/src/test/resources/warning/flow-with-warning.yaml
index e57b43df4b..a84ef569e3 100644
--- a/cli/src/test/resources/warning/flow-with-warning.yaml
+++ b/cli/src/test/resources/warning/flow-with-warning.yaml
@@ -4,4 +4,7 @@ namespace: system
tasks:
- id: deprecated
type: io.kestra.plugin.core.debug.Echo
- format: Hello World
\ No newline at end of file
+ format: Hello World
+ - id: alias
+ type: io.kestra.core.tasks.log.Log
+ message: I'm an alias
\ No newline at end of file
diff --git a/core/src/main/java/io/kestra/core/metrics/MetricRegistry.java b/core/src/main/java/io/kestra/core/metrics/MetricRegistry.java
index cc563b05be..36d7ae35f5 100644
--- a/core/src/main/java/io/kestra/core/metrics/MetricRegistry.java
+++ b/core/src/main/java/io/kestra/core/metrics/MetricRegistry.java
@@ -19,58 +19,60 @@
@Singleton
@Slf4j
public class MetricRegistry {
- public final static String METRIC_WORKER_JOB_PENDING_COUNT = "worker.job.pending";
- public final static String METRIC_WORKER_JOB_RUNNING_COUNT = "worker.job.running";
- public final static String METRIC_WORKER_JOB_THREAD_COUNT = "worker.job.thread";
- public final static String METRIC_WORKER_RUNNING_COUNT = "worker.running.count";
- public final static String METRIC_WORKER_QUEUED_DURATION = "worker.queued.duration";
- public final static String METRIC_WORKER_STARTED_COUNT = "worker.started.count";
- public final static String METRIC_WORKER_TIMEOUT_COUNT = "worker.timeout.count";
- public final static String METRIC_WORKER_ENDED_COUNT = "worker.ended.count";
- public final static String METRIC_WORKER_ENDED_DURATION = "worker.ended.duration";
- public final static String METRIC_WORKER_TRIGGER_DURATION = "worker.trigger.duration";
- public final static String METRIC_WORKER_TRIGGER_RUNNING_COUNT = "worker.trigger.running.count";
- public final static String METRIC_WORKER_TRIGGER_STARTED_COUNT = "worker.trigger.started.count";
- public final static String METRIC_WORKER_TRIGGER_ENDED_COUNT = "worker.trigger.ended.count";
- public final static String METRIC_WORKER_TRIGGER_ERROR_COUNT = "worker.trigger.error.count";
- public final static String METRIC_WORKER_TRIGGER_EXECUTION_COUNT = "worker.trigger.execution.count";
-
- public final static String EXECUTOR_TASKRUN_NEXT_COUNT = "executor.taskrun.next.count";
- public final static String EXECUTOR_TASKRUN_ENDED_COUNT = "executor.taskrun.ended.count";
- public final static String EXECUTOR_TASKRUN_ENDED_DURATION = "executor.taskrun.ended.duration";
- public final static String EXECUTOR_WORKERTASKRESULT_COUNT = "executor.workertaskresult.count";
- public final static String EXECUTOR_EXECUTION_STARTED_COUNT = "executor.execution.started.count";
- public final static String EXECUTOR_EXECUTION_END_COUNT = "executor.execution.end.count";
- public final static String EXECUTOR_EXECUTION_DURATION = "executor.execution.duration";
-
- public final static String METRIC_INDEXER_REQUEST_COUNT = "indexer.request.count";
- public final static String METRIC_INDEXER_REQUEST_DURATION = "indexer.request.duration";
- public final static String METRIC_INDEXER_REQUEST_RETRY_COUNT = "indexer.request.retry.count";
- public final static String METRIC_INDEXER_SERVER_DURATION = "indexer.server.duration";
- public final static String METRIC_INDEXER_MESSAGE_FAILED_COUNT = "indexer.message.failed.count";
- public final static String METRIC_INDEXER_MESSAGE_IN_COUNT = "indexer.message.in.count";
- public final static String METRIC_INDEXER_MESSAGE_OUT_COUNT = "indexer.message.out.count";
-
- public final static String SCHEDULER_LOOP_COUNT = "scheduler.loop.count";
- public final static String SCHEDULER_TRIGGER_COUNT = "scheduler.trigger.count";
- public final static String SCHEDULER_TRIGGER_DELAY_DURATION = "scheduler.trigger.delay.duration";
- public final static String SCHEDULER_EVALUATE_COUNT = "scheduler.evaluate.count";
- public final static String SCHEDULER_EXECUTION_RUNNING_DURATION = "scheduler.execution.running.duration";
- public final static String SCHEDULER_EXECUTION_MISSING_DURATION = "scheduler.execution.missing.duration";
-
- public final static String STREAMS_STATE_COUNT = "stream.state.count";
-
-
- public final static String JDBC_QUERY_DURATION = "jdbc.query.duration";
-
- public final static String TAG_TASK_TYPE = "task_type";
- public final static String TAG_TRIGGER_TYPE = "trigger_type";
- public final static String TAG_FLOW_ID = "flow_id";
- public final static String TAG_NAMESPACE_ID = "namespace_id";
- public final static String TAG_STATE = "state";
- public final static String TAG_ATTEMPT_COUNT = "attempt_count";
- public final static String TAG_WORKER_GROUP = "worker_group";
- public final static String TAG_TENANT_ID = "tenant_id";
+ public static final String METRIC_WORKER_JOB_PENDING_COUNT = "worker.job.pending";
+ public static final String METRIC_WORKER_JOB_RUNNING_COUNT = "worker.job.running";
+ public static final String METRIC_WORKER_JOB_THREAD_COUNT = "worker.job.thread";
+ public static final String METRIC_WORKER_RUNNING_COUNT = "worker.running.count";
+ public static final String METRIC_WORKER_QUEUED_DURATION = "worker.queued.duration";
+ public static final String METRIC_WORKER_STARTED_COUNT = "worker.started.count";
+ public static final String METRIC_WORKER_TIMEOUT_COUNT = "worker.timeout.count";
+ public static final String METRIC_WORKER_ENDED_COUNT = "worker.ended.count";
+ public static final String METRIC_WORKER_ENDED_DURATION = "worker.ended.duration";
+ public static final String METRIC_WORKER_TRIGGER_DURATION = "worker.trigger.duration";
+ public static final String METRIC_WORKER_TRIGGER_RUNNING_COUNT = "worker.trigger.running.count";
+ public static final String METRIC_WORKER_TRIGGER_STARTED_COUNT = "worker.trigger.started.count";
+ public static final String METRIC_WORKER_TRIGGER_ENDED_COUNT = "worker.trigger.ended.count";
+ public static final String METRIC_WORKER_TRIGGER_ERROR_COUNT = "worker.trigger.error.count";
+ public static final String METRIC_WORKER_TRIGGER_EXECUTION_COUNT = "worker.trigger.execution.count";
+
+ public static final String EXECUTOR_TASKRUN_NEXT_COUNT = "executor.taskrun.next.count";
+ public static final String EXECUTOR_TASKRUN_ENDED_COUNT = "executor.taskrun.ended.count";
+ public static final String EXECUTOR_TASKRUN_ENDED_DURATION = "executor.taskrun.ended.duration";
+ public static final String EXECUTOR_WORKERTASKRESULT_COUNT = "executor.workertaskresult.count";
+ public static final String EXECUTOR_EXECUTION_STARTED_COUNT = "executor.execution.started.count";
+ public static final String EXECUTOR_EXECUTION_END_COUNT = "executor.execution.end.count";
+ public static final String EXECUTOR_EXECUTION_DURATION = "executor.execution.duration";
+
+ public static final String METRIC_INDEXER_REQUEST_COUNT = "indexer.request.count";
+ public static final String METRIC_INDEXER_REQUEST_DURATION = "indexer.request.duration";
+ public static final String METRIC_INDEXER_REQUEST_RETRY_COUNT = "indexer.request.retry.count";
+ public static final String METRIC_INDEXER_SERVER_DURATION = "indexer.server.duration";
+ public static final String METRIC_INDEXER_MESSAGE_FAILED_COUNT = "indexer.message.failed.count";
+ public static final String METRIC_INDEXER_MESSAGE_IN_COUNT = "indexer.message.in.count";
+ public static final String METRIC_INDEXER_MESSAGE_OUT_COUNT = "indexer.message.out.count";
+
+ public static final String SCHEDULER_LOOP_COUNT = "scheduler.loop.count";
+ public static final String SCHEDULER_TRIGGER_COUNT = "scheduler.trigger.count";
+ public static final String SCHEDULER_TRIGGER_DELAY_DURATION = "scheduler.trigger.delay.duration";
+ public static final String SCHEDULER_EVALUATE_COUNT = "scheduler.evaluate.count";
+ public static final String SCHEDULER_EXECUTION_RUNNING_DURATION = "scheduler.execution.running.duration";
+ public static final String SCHEDULER_EXECUTION_MISSING_DURATION = "scheduler.execution.missing.duration";
+
+ public static final String STREAMS_STATE_COUNT = "stream.state.count";
+
+ public static final String JDBC_QUERY_DURATION = "jdbc.query.duration";
+
+ public static final String QUEUE_BIG_MESSAGE_COUNT = "queue.big_message.count";
+
+ public static final String TAG_TASK_TYPE = "task_type";
+ public static final String TAG_TRIGGER_TYPE = "trigger_type";
+ public static final String TAG_FLOW_ID = "flow_id";
+ public static final String TAG_NAMESPACE_ID = "namespace_id";
+ public static final String TAG_STATE = "state";
+ public static final String TAG_ATTEMPT_COUNT = "attempt_count";
+ public static final String TAG_WORKER_GROUP = "worker_group";
+ public static final String TAG_TENANT_ID = "tenant_id";
+ public static final String TAG_CLASS_NAME = "class_name";
@Inject
private MeterRegistry meterRegistry;
diff --git a/core/src/main/java/io/kestra/core/models/Label.java b/core/src/main/java/io/kestra/core/models/Label.java
index 5879703085..bd2fbd8359 100644
--- a/core/src/main/java/io/kestra/core/models/Label.java
+++ b/core/src/main/java/io/kestra/core/models/Label.java
@@ -2,4 +2,11 @@
import jakarta.validation.constraints.NotNull;
-public record Label(@NotNull String key, @NotNull String value) {}
+public record Label(@NotNull String key, @NotNull String value) {
+ public static final String SYSTEM_PREFIX = "system_";
+
+ // system labels
+ public static final String CORRELATION_ID = SYSTEM_PREFIX + "correlationId";
+ public static final String USERNAME = SYSTEM_PREFIX + "username";
+ public static final String APP = SYSTEM_PREFIX + "app";
+}
diff --git a/core/src/main/java/io/kestra/core/models/executions/Execution.java b/core/src/main/java/io/kestra/core/models/executions/Execution.java
index 890c629765..89407f5933 100644
--- a/core/src/main/java/io/kestra/core/models/executions/Execution.java
+++ b/core/src/main/java/io/kestra/core/models/executions/Execution.java
@@ -39,6 +39,8 @@
import java.util.stream.Stream;
import java.util.zip.CRC32;
+import static io.kestra.core.models.Label.SYSTEM_PREFIX;
+
@Builder(toBuilder = true)
@Slf4j
@Getter
@@ -76,7 +78,6 @@ public class Execution implements DeletedInterface, TenantInterface {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Map outputs;
- @With
@JsonSerialize(using = ListOrMapOfLabelSerializer.class)
@JsonDeserialize(using = ListOrMapOfLabelDeserializer.class)
List labels;
@@ -180,6 +181,12 @@ public Execution build() {
this.prebuild();
return super.build();
}
+
+ @Override
+ public ExecutionBuilder labels(List labels) {
+ checkForSystemLabels(labels);
+ return super.labels(labels);
+ }
}
public Execution withState(State.Type state) {
@@ -205,6 +212,75 @@ public Execution withState(State.Type state) {
);
}
+ /**
+ * This method replaces labels with new ones.
+ * It refuses system labels as they must be passed via the withSystemLabels method.
+ */
+ public Execution withLabels(List labels) {
+ checkForSystemLabels(labels);
+
+ return new Execution(
+ this.tenantId,
+ this.id,
+ this.namespace,
+ this.flowId,
+ this.flowRevision,
+ this.taskRunList,
+ this.inputs,
+ this.outputs,
+ labels,
+ this.variables,
+ this.state,
+ this.parentId,
+ this.originalId,
+ this.trigger,
+ this.deleted,
+ this.metadata,
+ this.scheduleDate,
+ this.error
+ );
+ }
+
+ private static void checkForSystemLabels(List labels) {
+ if (labels != null) {
+ Optional first = labels.stream().filter(label -> label.key() != null && label.key().startsWith(SYSTEM_PREFIX)).findFirst();
+ if (first.isPresent()) {
+ throw new IllegalArgumentException("System labels can only be set by Kestra itself, offending label: " + first.get().key() + "=" + first.get().value());
+ }
+ }
+ }
+
+ /**
+ * This method in only to be used to add system labels to an execution.
+ * It will not replace exisiting labels but add new one (possibly duplicating).
+ */
+ public Execution withSystemLabels(List labels) {
+ List newLabels = this.labels == null ? new ArrayList<>() : this.labels;
+ if (labels != null) {
+ newLabels.addAll(labels);
+ }
+ return new Execution(
+ this.tenantId,
+ this.id,
+ this.namespace,
+ this.flowId,
+ this.flowRevision,
+ this.taskRunList,
+ this.inputs,
+ this.outputs,
+ newLabels,
+ this.variables,
+ this.state,
+ this.parentId,
+ this.originalId,
+ this.trigger,
+ this.deleted,
+ this.metadata,
+ this.scheduleDate,
+ this.error
+ );
+ }
+
public Execution withTaskRun(TaskRun taskRun) throws InternalException {
ArrayList newTaskRunList = new ArrayList<>(this.taskRunList);
diff --git a/core/src/main/java/io/kestra/core/models/tasks/retrys/Constant.java b/core/src/main/java/io/kestra/core/models/tasks/retrys/Constant.java
index 362d9e8d87..e339feb32b 100644
--- a/core/src/main/java/io/kestra/core/models/tasks/retrys/Constant.java
+++ b/core/src/main/java/io/kestra/core/models/tasks/retrys/Constant.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.failsafe.RetryPolicyBuilder;
+import io.kestra.core.validations.ConstantRetryValidation;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
@@ -14,6 +15,7 @@
@SuperBuilder
@Getter
@NoArgsConstructor
+@ConstantRetryValidation
public class Constant extends AbstractRetry {
@NotNull
@JsonInclude
diff --git a/core/src/main/java/io/kestra/core/models/tasks/retrys/Exponential.java b/core/src/main/java/io/kestra/core/models/tasks/retrys/Exponential.java
index 603c30e0e2..e6f4427826 100644
--- a/core/src/main/java/io/kestra/core/models/tasks/retrys/Exponential.java
+++ b/core/src/main/java/io/kestra/core/models/tasks/retrys/Exponential.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.failsafe.RetryPolicyBuilder;
+import io.kestra.core.validations.ExponentialRetryValidation;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
@@ -15,6 +16,7 @@
@SuperBuilder
@Getter
@NoArgsConstructor
+@ExponentialRetryValidation
public class Exponential extends AbstractRetry {
@NotNull
@JsonInclude
diff --git a/core/src/main/java/io/kestra/core/models/tasks/retrys/Random.java b/core/src/main/java/io/kestra/core/models/tasks/retrys/Random.java
index cfe2dd7153..184cd1f0d3 100644
--- a/core/src/main/java/io/kestra/core/models/tasks/retrys/Random.java
+++ b/core/src/main/java/io/kestra/core/models/tasks/retrys/Random.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import dev.failsafe.RetryPolicyBuilder;
+import io.kestra.core.validations.RandomRetryValidation;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
@@ -15,6 +16,7 @@
@SuperBuilder
@Getter
@NoArgsConstructor
+@RandomRetryValidation
public class Random extends AbstractRetry {
@NotNull
@JsonInclude
diff --git a/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java b/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
index bc8891b676..1307d366a0 100644
--- a/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
+++ b/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
@@ -34,6 +34,7 @@ public class ValidateConstraintViolation {
private boolean outdated;
private List deprecationPaths;
private List warnings;
+ private List infos;
@JsonIgnore
public String getIdentity(){
diff --git a/core/src/main/java/io/kestra/core/runners/ExecutableUtils.java b/core/src/main/java/io/kestra/core/runners/ExecutableUtils.java
index 61acf59fa5..af5aaac76c 100644
--- a/core/src/main/java/io/kestra/core/runners/ExecutableUtils.java
+++ b/core/src/main/java/io/kestra/core/runners/ExecutableUtils.java
@@ -13,10 +13,12 @@
import io.kestra.core.models.tasks.Task;
import io.kestra.core.storages.Storage;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.stream.Streams;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.*;
+import java.util.stream.Collectors;
@Slf4j
public final class ExecutableUtils {
@@ -91,6 +93,14 @@ public static > SubflowExecution> subflowEx
"flowRevision", currentFlow.getRevision()
);
+ // propagate system labels and compute correlation ID if not already existing
+ List systemLabels = Streams.of(currentExecution.getLabels())
+ .filter(label -> label.key().startsWith(Label.SYSTEM_PREFIX))
+ .collect(Collectors.toList());
+ if (systemLabels.stream().noneMatch(label -> label.key().equals(Label.CORRELATION_ID))) {
+ systemLabels.add(new Label(Label.CORRELATION_ID, currentExecution.getId()));
+ }
+
FlowInputOutput flowInputOutput = ((DefaultRunContext)runContext).getApplicationContext().getBean(FlowInputOutput.class);
Instant scheduleOnDate = scheduleDate != null ? scheduleDate.as(runContext, ZonedDateTime.class).toInstant() : null;
Execution execution = Execution
@@ -105,7 +115,8 @@ public static > SubflowExecution> subflowEx
.variables(variables)
.build()
)
- .withScheduleDate(scheduleOnDate);
+ .withScheduleDate(scheduleOnDate)
+ .withSystemLabels(systemLabels);
return SubflowExecution.builder()
.parentTask(currentTask)
.parentTaskRun(currentTaskRun.withState(State.Type.RUNNING))
diff --git a/core/src/main/java/io/kestra/core/validations/ConstantRetryValidation.java b/core/src/main/java/io/kestra/core/validations/ConstantRetryValidation.java
new file mode 100644
index 0000000000..9228c7dc5f
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/ConstantRetryValidation.java
@@ -0,0 +1,16 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.validations.validator.ConstantRetryValidator;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = ConstantRetryValidator.class)
+public @interface ConstantRetryValidation {
+ String message() default "invalid constant retry";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/core/src/main/java/io/kestra/core/validations/ExponentialRetryValidation.java b/core/src/main/java/io/kestra/core/validations/ExponentialRetryValidation.java
new file mode 100644
index 0000000000..44253464a3
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/ExponentialRetryValidation.java
@@ -0,0 +1,16 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.validations.validator.ExponentialRetryValidator;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = ExponentialRetryValidator.class)
+public @interface ExponentialRetryValidation {
+ String message() default "invalid exponential retry";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/core/src/main/java/io/kestra/core/validations/RandomRetryValidation.java b/core/src/main/java/io/kestra/core/validations/RandomRetryValidation.java
new file mode 100644
index 0000000000..fcf06d1b36
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/RandomRetryValidation.java
@@ -0,0 +1,16 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.validations.validator.RandomRetryValidator;
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = RandomRetryValidator.class)
+public @interface RandomRetryValidation {
+ String message() default "invalid random retry";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
diff --git a/core/src/main/java/io/kestra/core/validations/validator/ConstantRetryValidator.java b/core/src/main/java/io/kestra/core/validations/validator/ConstantRetryValidator.java
new file mode 100644
index 0000000000..dc29f98dde
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/validator/ConstantRetryValidator.java
@@ -0,0 +1,28 @@
+package io.kestra.core.validations.validator;
+
+import io.kestra.core.models.tasks.retrys.Constant;
+import io.kestra.core.validations.ConstantRetryValidation;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.validation.validator.constraints.ConstraintValidator;
+import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class ConstantRetryValidator implements ConstraintValidator {
+ @Override
+ public boolean isValid(@Nullable Constant value, @NonNull AnnotationValue annotationMetadata, @NonNull ConstraintValidatorContext context) {
+ if (value == null) {
+ return true;
+ }
+
+ if (value.getMaxDuration() != null && value.getInterval() != null && value.getMaxDuration().compareTo(value.getInterval()) <= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'interval' must be less than 'maxDuration' but is " + value.getInterval())
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/core/src/main/java/io/kestra/core/validations/validator/ExponentialRetryValidator.java b/core/src/main/java/io/kestra/core/validations/validator/ExponentialRetryValidator.java
new file mode 100644
index 0000000000..d68dc8ed53
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/validator/ExponentialRetryValidator.java
@@ -0,0 +1,36 @@
+package io.kestra.core.validations.validator;
+
+import io.kestra.core.models.tasks.retrys.Exponential;
+import io.kestra.core.validations.ExponentialRetryValidation;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.validation.validator.constraints.ConstraintValidator;
+import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class ExponentialRetryValidator implements ConstraintValidator {
+ @Override
+ public boolean isValid(@Nullable Exponential value, @NonNull AnnotationValue annotationMetadata, @NonNull ConstraintValidatorContext context) {
+ if (value == null) {
+ return true;
+ }
+
+ if (value.getMaxDuration() != null && value.getInterval() != null && value.getMaxDuration().compareTo(value.getInterval()) <= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'interval' must be less than 'maxDuration' but is " + value.getInterval())
+ .addConstraintViolation();
+ return false;
+ }
+
+ if (value.getInterval() != null && value.getMaxInterval() != null && value.getInterval().compareTo(value.getMaxInterval()) >= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'interval' must be less than 'maxInterval' but is " + value.getInterval())
+ .addConstraintViolation();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/src/main/java/io/kestra/core/validations/validator/FlowValidator.java b/core/src/main/java/io/kestra/core/validations/validator/FlowValidator.java
index 3dbfb6394a..c6fd1964a6 100644
--- a/core/src/main/java/io/kestra/core/validations/validator/FlowValidator.java
+++ b/core/src/main/java/io/kestra/core/validations/validator/FlowValidator.java
@@ -24,6 +24,8 @@
import java.util.Optional;
import java.util.Set;
+import static io.kestra.core.models.Label.SYSTEM_PREFIX;
+
@Singleton
@Introspected
public class FlowValidator implements ConstraintValidator {
@@ -57,32 +59,29 @@ public boolean isValid(
}
value.allTasksWithChilds()
- .forEach(
- task -> {
- if (task instanceof ExecutableTask> executableTask
- && value.getId().equals(executableTask.subflowId().flowId())
- && value.getNamespace().equals(executableTask.subflowId().namespace())) {
- violations.add("Recursive call to flow [" + value.getNamespace() + "." + value.getId() + "]");
- }
- }
- );
+ .stream().filter(task -> task instanceof ExecutableTask> executableTask
+ && value.getId().equals(executableTask.subflowId().flowId())
+ && value.getNamespace().equals(executableTask.subflowId().namespace()))
+ .forEach(task -> violations.add("Recursive call to flow [" + value.getNamespace() + "." + value.getId() + "]"));
// input unique name
- if (value.getInputs() != null) {
- List duplicates = getDuplicates(value.getInputs().stream().map(Data::getId).toList());
- if (!duplicates.isEmpty()) {
- violations.add("Duplicate input with name [" + String.join(", ", duplicates) + "]");
- }
- checkFlowInputsDependencyGraph(value, violations);
+ duplicateIds = getDuplicates(ListUtils.emptyOnNull(value.getInputs()).stream().map(Data::getId).toList());
+ if (!duplicateIds.isEmpty()) {
+ violations.add("Duplicate input with name [" + String.join(", ", duplicateIds) + "]");
}
+ checkFlowInputsDependencyGraph(value, violations);
+
// output unique name
- if (value.getOutputs() != null) {
- List duplicates = getDuplicates(value.getOutputs().stream().map(Data::getId).toList());
- if (!duplicates.isEmpty()) {
- violations.add("Duplicate output with name [" + String.join(", ", duplicates) + "]");
- }
+ duplicateIds = getDuplicates(ListUtils.emptyOnNull(value.getOutputs()).stream().map(Data::getId).toList());
+ if (!duplicateIds.isEmpty()) {
+ violations.add("Duplicate output with name [" + String.join(", ", duplicateIds) + "]");
}
+ // system labels
+ ListUtils.emptyOnNull(value.getLabels()).stream()
+ .filter(label -> label.key() != null && label.key().startsWith(SYSTEM_PREFIX))
+ .forEach(label -> violations.add("System labels can only be set by Kestra itself, offending label: " + label.key() + "=" + label.value()));
+
if (!violations.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Invalid Flow: " + String.join(", ", violations))
diff --git a/core/src/main/java/io/kestra/core/validations/validator/RandomRetryValidator.java b/core/src/main/java/io/kestra/core/validations/validator/RandomRetryValidator.java
new file mode 100644
index 0000000000..534f242e12
--- /dev/null
+++ b/core/src/main/java/io/kestra/core/validations/validator/RandomRetryValidator.java
@@ -0,0 +1,43 @@
+package io.kestra.core.validations.validator;
+
+import io.kestra.core.models.tasks.retrys.Random;
+import io.kestra.core.validations.RandomRetryValidation;
+import io.micronaut.core.annotation.AnnotationValue;
+import io.micronaut.core.annotation.NonNull;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.validation.validator.constraints.ConstraintValidator;
+import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class RandomRetryValidator implements ConstraintValidator {
+ @Override
+ public boolean isValid(@Nullable Random value, @NonNull AnnotationValue annotationMetadata, @NonNull ConstraintValidatorContext context) {
+ if (value == null) {
+ return true;
+ }
+
+ if (value.getMaxDuration() != null && value.getMaxInterval() != null && value.getMaxDuration().compareTo(value.getMinInterval()) <= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'minInterval' must be less than 'maxDuration' but is " + value.getMinInterval())
+ .addConstraintViolation();
+ return false;
+ }
+
+ if (value.getMaxDuration() != null && value.getMaxInterval() != null && value.getMaxDuration().compareTo(value.getMaxInterval()) <= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'maxInterval' must be less than 'maxDuration' but is " + value.getMaxInterval())
+ .addConstraintViolation();
+ return false;
+ }
+
+ if (value.getMaxInterval() != null && value.getMinInterval() != null && value.getMaxInterval().compareTo(value.getMinInterval()) <= 0) {
+ context.disableDefaultConstraintViolation();
+ context.buildConstraintViolationWithTemplate( "'minInterval' must be less than 'maxInterval' but is " + value.getMinInterval())
+ .addConstraintViolation();
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/src/main/java/io/kestra/plugin/core/execution/Labels.java b/core/src/main/java/io/kestra/plugin/core/execution/Labels.java
index 9b0ffe6a38..aa0b1d50e7 100644
--- a/core/src/main/java/io/kestra/plugin/core/execution/Labels.java
+++ b/core/src/main/java/io/kestra/plugin/core/execution/Labels.java
@@ -131,14 +131,12 @@ public Execution update(Execution execution, RunContext runContext) throws Excep
);
}));
- return execution.toBuilder()
- .labels(newLabels.entrySet().stream()
+ return execution.withLabels(newLabels.entrySet().stream()
.map(throwFunction(entry -> new Label(
entry.getKey(),
entry.getValue()
)))
- .toList())
- .build();
+ .toList());
} else {
throw new IllegalVariableEvaluationException("Unknown value type: " + labels.getClass());
}
diff --git a/core/src/main/java/io/kestra/plugin/core/flow/Sleep.java b/core/src/main/java/io/kestra/plugin/core/flow/Sleep.java
new file mode 100644
index 0000000000..38d4feac82
--- /dev/null
+++ b/core/src/main/java/io/kestra/plugin/core/flow/Sleep.java
@@ -0,0 +1,57 @@
+package io.kestra.plugin.core.flow;
+
+import io.kestra.core.models.annotations.Example;
+import io.kestra.core.models.annotations.Plugin;
+import io.kestra.core.models.annotations.PluginProperty;
+import io.kestra.core.models.tasks.RunnableTask;
+import io.kestra.core.models.tasks.Task;
+import io.kestra.core.models.tasks.VoidOutput;
+import io.kestra.core.runners.RunContext;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import lombok.experimental.SuperBuilder;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+@SuperBuilder
+@ToString
+@EqualsAndHashCode
+@Getter
+@NoArgsConstructor
+@Schema(
+ title = "A task that sleep for a specified duration before proceeding."
+)
+@Plugin(
+ examples = {
+ @Example(
+ code = """
+ id: sleep
+ type: io.kestra.plugin.core.flow.Sleep
+ duration: "PT5S"
+ """
+ )
+ }
+)
+public class Sleep extends Task implements RunnableTask {
+ @Schema(
+ title = "Duration to sleep",
+ description = "The time duration in ISO-8601 format (e.g., `PT5S` for 5 seconds)."
+ )
+ @PluginProperty
+ @NotNull
+ private Duration duration;
+
+ public VoidOutput run(RunContext runContext) throws Exception {
+ runContext.logger().info("Waiting for {}", duration);
+
+ // Wait for the specified duration
+ TimeUnit.MILLISECONDS.sleep(duration.toMillis());
+
+ return null;
+ }
+}
diff --git a/core/src/main/java/io/kestra/plugin/core/http/Download.java b/core/src/main/java/io/kestra/plugin/core/http/Download.java
index 308efa9996..6c1495101d 100644
--- a/core/src/main/java/io/kestra/plugin/core/http/Download.java
+++ b/core/src/main/java/io/kestra/plugin/core/http/Download.java
@@ -21,6 +21,8 @@
import java.io.File;
import java.io.FileOutputStream;
import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
@@ -128,6 +130,9 @@ public Output run(RunContext runContext) throws Exception {
String contentDisposition = builder.headers.get("Content-Disposition").getFirst();
filename = filenameFromHeader(runContext, contentDisposition);
}
+ if (filename != null) {
+ filename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
+ }
builder.uri(runContext.storage().putFile(tempFile, filename));
@@ -145,7 +150,7 @@ private String filenameFromHeader(RunContext runContext, String contentDispositi
String filename = null;
for (String part : parts) {
if (part.startsWith("filename")) {
- filename = part.substring(part.lastIndexOf('=') + 2, part.length() - 1);
+ filename = part.substring(part.lastIndexOf('=') + 1);
}
if (part.startsWith("filename*")) {
// following https://datatracker.ietf.org/doc/html/rfc5987 the filename* should be '(lang)'
diff --git a/core/src/main/java/io/kestra/plugin/core/trigger/Flow.java b/core/src/main/java/io/kestra/plugin/core/trigger/Flow.java
index 805aed820b..13c1e37ad4 100644
--- a/core/src/main/java/io/kestra/plugin/core/trigger/Flow.java
+++ b/core/src/main/java/io/kestra/plugin/core/trigger/Flow.java
@@ -41,37 +41,65 @@
::"""
)
@Plugin(
- examples = @Example(
- title = "This flow will be triggered after each successful execution of flow `company.team.trigger_flow` " +
- "and forward the `uri` of `my_task` taskId outputs.",
- full = true,
- code = """
- id: trigger_flow_listener
- namespace: company.team
-
- inputs:
- - id: from_parent
- type: STRING
-
- tasks:
- - id: only_no_input
- type: io.kestra.plugin.core.debug.Return
- format: "v1: {{ trigger.executionId }}"
-
- triggers:
- - id: listen_flow
- type: io.kestra.plugin.core.trigger.Flow
+ examples = {
+ @Example(
+ full = true,
+ title = """
+ Trigger the `transform` flow after the `extract` flow finishes successfully. \
+ The `extract` flow generates a `last_ingested_date` output that is passed to the \
+ `transform` flow as an input. Here is the `extract` flow:
+ ```yaml
+ id: extract
+ namespace: company.team
+
+ tasks:
+ - id: final_date
+ type: io.kestra.plugin.core.debug.Return
+ format: "{{ execution.startDate | dateAdd(-2, 'DAYS') | date('yyyy-MM-dd') }}"
+
+ outputs:
+ - id: last_ingested_date
+ type: STRING
+ value: "{{ outputs.final_date.value }}"
+ ```
+ Below is the `transform` flow triggered in response to the `extract` flow's successful completion.""",
+ code = """
+ id: transform
+ namespace: company.team
+
inputs:
- from-parent: '{{ outputs.my_task.uri }}'
- conditions:
- - type: io.kestra.plugin.core.condition.ExecutionFlowCondition
- namespace: company.team
- flowId: trigger_flow
- - type: io.kestra.plugin.core.condition.ExecutionStatusCondition
- in:
- - SUCCESS
- """
- ),
+ - id: last_ingested_date
+ type: STRING
+ defaults: "2025-01-01"
+
+ variables:
+ result: |
+ Ingestion done in {{ trigger.executionId }}.
+ Now transforming data up to {{ inputs.last_ingested_date }}
+
+ tasks:
+ - id: run_transform
+ type: io.kestra.plugin.core.debug.Return
+ format: "{{ render(vars.result) }}"
+
+ - id: log
+ type: io.kestra.plugin.core.log.Log
+ message: "{{ render(vars.result) }}"
+
+ triggers:
+ - id: run_after_extract
+ type: io.kestra.plugin.core.trigger.Flow
+ inputs:
+ last_ingested_date: "{{ trigger.outputs.last_ingested_date }}"
+ conditions:
+ - type: io.kestra.plugin.core.condition.ExecutionFlowCondition
+ namespace: company.team
+ flowId: extract
+ - type: io.kestra.plugin.core.condition.ExecutionStatusCondition
+ in:
+ - SUCCESS"""
+ ),
+ },
aliases = "io.kestra.core.models.triggers.types.Flow"
)
public class Flow extends AbstractTrigger implements TriggerOutput {
@@ -81,12 +109,11 @@ public class Flow extends AbstractTrigger implements TriggerOutput
@Nullable
@Schema(
- title = "Fill input of this flow based on output of current flow.",
+ title = "Pass upstream flow's outputs to inputs of the current flow.",
description = """
- Fill input of this flow based on output of current flow, allowing to pass data or file to the triggered flow
+ The inputs allow you to pass data object or a file to the downstream flow as long as those outputs are defined on the flow-level in the upstream flow.
::alert{type="warning"}
- If you provide invalid input, the flow will not be created! Since there is no task started, you can't log any reason that's visible on the Execution UI.
- So you will need to go to the Logs tabs on the UI to understand the error.
+ Make sure that the inputs and task outputs defined in this Flow trigger match the outputs of the upstream flow. Otherwise, the downstream flow execution will not to be created. If that happens, go to the Logs tab on the Flow page to understand the error.
::"""
)
@PluginProperty
@@ -97,13 +124,12 @@ public class Flow extends AbstractTrigger implements TriggerOutput
title = "List of execution states that will be evaluated by the trigger",
description = """
By default, only executions in a terminal state will be evaluated.
- If you use a condition of type `ExecutionStatusCondition` it will be evaluated after this list.
+ Any `ExecutionStatusCondition`-type condition will be evaluated after the list of `states`.
::alert{type="info"}
- The trigger will be evaluated on each execution state change, this means that, for non-terminal state, they can be observed multiple times.
- For example, if a flow has two `Pause` tasks, the execution will transition two times from PAUSED to RUNNING so theses states will be observed two times.
+ The trigger will be evaluated for each state change of matching executions. Keep in mind that if a flow has two `Pause` tasks, the execution will transition from PAUSED to a RUNNING state twice — one for each Pause task. The Flow trigger listening to a `PAUSED` state will be evaluated twice in this case.
::
::alert{type="warning"}
- You cannot evaluate on the CREATED state.
+ Note that a Flow trigger cannot react to the `CREATED` state.
::"""
)
@Builder.Default
diff --git a/core/src/main/resources/docs/task.peb b/core/src/main/resources/docs/task.peb
index 9c323d3fc1..ef1aff4bd4 100644
--- a/core/src/main/resources/docs/task.peb
+++ b/core/src/main/resources/docs/task.peb
@@ -18,10 +18,15 @@ icon: {{ icon }}
{% if deprecated %}
+ {%- if replacement -%}
+::alert{type="info"}
+🛈 This feature has been moved to a new location, please use `{{ replacement }}` instead.
+::
+ {%- else -%}
::alert{type="warning"}
⚠ This feature is deprecated and will be removed in the future. We encourage you to migrate to the new plugin, but you can still use the deprecated version.
-{% if replacement %}Please use `{{ replacement }}` instead. {%- endif %}
::
+ {%- endif -%}
{%- endif %}
{% if beta %}
diff --git a/core/src/test/java/io/kestra/core/validations/ConstantRetryValidationTest.java b/core/src/test/java/io/kestra/core/validations/ConstantRetryValidationTest.java
new file mode 100644
index 0000000000..794ad29832
--- /dev/null
+++ b/core/src/test/java/io/kestra/core/validations/ConstantRetryValidationTest.java
@@ -0,0 +1,47 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.junit.annotations.KestraTest;
+import io.kestra.core.models.tasks.retrys.Constant;
+import io.kestra.core.models.validations.ModelValidator;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+@KestraTest
+public class ConstantRetryValidationTest {
+ @Inject
+ private ModelValidator modelValidator;
+
+ @Test
+ void shouldValidateValidRetry() throws Exception {
+ var retry = Constant.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(10))
+ .interval(Duration.ofSeconds(1))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(true));
+ }
+
+ @Test
+ void shouldNotValidateInvalidRetry() throws Exception {
+ var retry = Constant.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(1))
+ .interval(Duration.ofSeconds(10))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(false));
+ assertThat(valid.get().getConstraintViolations(), hasSize(1));
+ assertThat(valid.get().getMessage(), is(": 'interval' must be less than 'maxDuration' but is PT10S\n"));
+ }
+}
diff --git a/core/src/test/java/io/kestra/core/validations/ExponentialRetryValidationTest.java b/core/src/test/java/io/kestra/core/validations/ExponentialRetryValidationTest.java
new file mode 100644
index 0000000000..860678cba1
--- /dev/null
+++ b/core/src/test/java/io/kestra/core/validations/ExponentialRetryValidationTest.java
@@ -0,0 +1,61 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.junit.annotations.KestraTest;
+import io.kestra.core.models.tasks.retrys.Exponential;
+import io.kestra.core.models.validations.ModelValidator;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+@KestraTest
+public class ExponentialRetryValidationTest {
+ @Inject
+ private ModelValidator modelValidator;
+
+ @Test
+ void shouldValidateValidRetry() throws Exception {
+ var retry = Exponential.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(10))
+ .interval(Duration.ofSeconds(1))
+ .maxInterval(Duration.ofSeconds(3))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(true));
+ }
+
+ @Test
+ void shouldNotValidateInvalidRetry() throws Exception {
+ var retry = Exponential.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(1))
+ .interval(Duration.ofSeconds(2))
+ .maxInterval(Duration.ofSeconds(3))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(false));
+ assertThat(valid.get().getConstraintViolations(), hasSize(1));
+ assertThat(valid.get().getMessage(), is(": 'interval' must be less than 'maxDuration' but is PT2S\n"));
+
+ retry = Exponential.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(12))
+ .interval(Duration.ofSeconds(3))
+ .maxInterval(Duration.ofSeconds(2))
+ .build();
+
+ valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(false));
+ assertThat(valid.get().getConstraintViolations(), hasSize(1));
+ assertThat(valid.get().getMessage(), is(": 'interval' must be less than 'maxInterval' but is PT3S\n"));
+ }
+}
diff --git a/core/src/test/java/io/kestra/core/validations/FlowValidationTest.java b/core/src/test/java/io/kestra/core/validations/FlowValidationTest.java
index 41b6be17b4..316898cfd5 100644
--- a/core/src/test/java/io/kestra/core/validations/FlowValidationTest.java
+++ b/core/src/test/java/io/kestra/core/validations/FlowValidationTest.java
@@ -22,7 +22,7 @@ class FlowValidationTest {
@Inject
private ModelValidator modelValidator;
@Inject
- YamlFlowParser yamlFlowParser = new YamlFlowParser();
+ private YamlFlowParser yamlFlowParser = new YamlFlowParser();
@Test
void invalidRecursiveFlow() {
@@ -33,6 +33,16 @@ void invalidRecursiveFlow() {
assertThat(validate.get().getMessage(), containsString(": Invalid Flow: Recursive call to flow [io.kestra.tests.recursive-flow]"));
}
+ @Test
+ void systemLabelShouldFailValidation() {
+ Flow flow = this.parse("flows/invalids/system-labels.yaml");
+ Optional validate = modelValidator.isValid(flow);
+
+ assertThat(validate.isPresent(), is(true));
+ assertThat(validate.get().getMessage(), containsString("System labels can only be set by Kestra itself, offending label: system_label=system_key"));
+ assertThat(validate.get().getMessage(), containsString("System labels can only be set by Kestra itself, offending label: system_id=id"));
+ }
+
private Flow parse(String path) {
URL resource = TestsUtils.class.getClassLoader().getResource(path);
assert resource != null;
diff --git a/core/src/test/java/io/kestra/core/validations/RandomRetryValidationTest.java b/core/src/test/java/io/kestra/core/validations/RandomRetryValidationTest.java
new file mode 100644
index 0000000000..1b79cf8bbe
--- /dev/null
+++ b/core/src/test/java/io/kestra/core/validations/RandomRetryValidationTest.java
@@ -0,0 +1,61 @@
+package io.kestra.core.validations;
+
+import io.kestra.core.junit.annotations.KestraTest;
+import io.kestra.core.models.tasks.retrys.Random;
+import io.kestra.core.models.validations.ModelValidator;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolationException;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+@KestraTest
+public class RandomRetryValidationTest {
+ @Inject
+ private ModelValidator modelValidator;
+
+ @Test
+ void shouldValidateValidRetry() throws Exception {
+ var retry = Random.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(10))
+ .minInterval(Duration.ofSeconds(1))
+ .maxInterval(Duration.ofSeconds(3))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(true));
+ }
+
+ @Test
+ void shouldNotValidateInvalidRetry() throws Exception {
+ var retry = Random.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(1))
+ .minInterval(Duration.ofSeconds(2))
+ .maxInterval(Duration.ofSeconds(3))
+ .build();
+
+ Optional valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(false));
+ assertThat(valid.get().getConstraintViolations(), hasSize(1));
+ assertThat(valid.get().getMessage(), is(": 'minInterval' must be less than 'maxDuration' but is PT2S\n"));
+
+ retry = Random.builder()
+ .maxAttempt(3)
+ .maxDuration(Duration.ofSeconds(12))
+ .minInterval(Duration.ofSeconds(3))
+ .maxInterval(Duration.ofSeconds(2))
+ .build();
+
+ valid = modelValidator.isValid(retry);
+ assertThat(valid.isEmpty(), is(false));
+ assertThat(valid.get().getConstraintViolations(), hasSize(1));
+ assertThat(valid.get().getMessage(), is(": 'minInterval' must be less than 'maxInterval' but is PT3S\n"));
+ }
+}
diff --git a/core/src/test/java/io/kestra/plugin/core/flow/CorrelationIdTest.java b/core/src/test/java/io/kestra/plugin/core/flow/CorrelationIdTest.java
new file mode 100644
index 0000000000..243296b614
--- /dev/null
+++ b/core/src/test/java/io/kestra/plugin/core/flow/CorrelationIdTest.java
@@ -0,0 +1,68 @@
+package io.kestra.plugin.core.flow;
+
+import io.kestra.core.models.Label;
+import io.kestra.core.models.executions.Execution;
+import io.kestra.core.models.flows.State;
+import io.kestra.core.queues.QueueException;
+import io.kestra.core.queues.QueueFactoryInterface;
+import io.kestra.core.queues.QueueInterface;
+import io.kestra.core.runners.AbstractMemoryRunnerTest;
+import io.kestra.core.utils.TestsUtils;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Flux;
+
+import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CorrelationIdTest extends AbstractMemoryRunnerTest {
+ @Inject
+ @Named(QueueFactoryInterface.EXECUTION_NAMED)
+ private QueueInterface executionQueue;
+
+ @Test
+ void shouldHaveCorrelationId() throws QueueException, TimeoutException, InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(2);
+ AtomicReference child = new AtomicReference<>();
+ AtomicReference grandChild = new AtomicReference<>();
+
+ Flux receive = TestsUtils.receive(executionQueue, either -> {
+ Execution execution = either.getLeft();
+ if (execution.getFlowId().equals("subflow-child") && execution.getState().getCurrent().isTerminated()) {
+ countDownLatch.countDown();
+ child.set(execution);
+ }
+ if (execution.getFlowId().equals("subflow-grand-child") && execution.getState().getCurrent().isTerminated()) {
+ countDownLatch.countDown();
+ grandChild.set(execution);
+ }
+ });
+
+ Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "subflow-parent");
+ assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
+
+ assertTrue(countDownLatch.await(1, TimeUnit.MINUTES));
+ receive.blockLast();
+
+ assertThat(child.get(), notNullValue());
+ assertThat(child.get().getState().getCurrent(), is(State.Type.SUCCESS));
+ Optional correlationId = child.get().getLabels().stream().filter(label -> label.key().equals(Label.CORRELATION_ID)).findAny();
+ assertThat(correlationId.isPresent(), is(true));
+ assertThat(correlationId.get().value(), is(execution.getId()));
+
+ assertThat(grandChild.get(), notNullValue());
+ assertThat(grandChild.get().getState().getCurrent(), is(State.Type.SUCCESS));
+ correlationId = grandChild.get().getLabels().stream().filter(label -> label.key().equals(Label.CORRELATION_ID)).findAny();
+ assertThat(correlationId.isPresent(), is(true));
+ assertThat(correlationId.get().value(), is(execution.getId()));
+ }
+}
diff --git a/core/src/test/java/io/kestra/plugin/core/flow/FlowCaseTest.java b/core/src/test/java/io/kestra/plugin/core/flow/FlowCaseTest.java
index 6bd6a1b759..97be642d16 100644
--- a/core/src/test/java/io/kestra/plugin/core/flow/FlowCaseTest.java
+++ b/core/src/test/java/io/kestra/plugin/core/flow/FlowCaseTest.java
@@ -100,14 +100,16 @@ void run(String input, State.Type fromState, State.Type triggerState, int count,
if (testInherited) {
assertThat(triggered.get().getLabels(), hasItems(
+ new Label(Label.CORRELATION_ID, execution.getId()),
new Label("mainFlowExecutionLabel", "execFoo"),
new Label("mainFlowLabel", "flowFoo"),
new Label("launchTaskLabel", "launchFoo"),
new Label("switchFlowLabel", "switchFoo")
));
} else {
- assertThat(triggered.get().getLabels().size(), is(2));
+ assertThat(triggered.get().getLabels().size(), is(3));
assertThat(triggered.get().getLabels(), hasItems(
+ new Label(Label.CORRELATION_ID, execution.getId()),
new Label("launchTaskLabel", "launchFoo"),
new Label("switchFlowLabel", "switchFoo")
));
diff --git a/core/src/test/java/io/kestra/plugin/core/flow/ForEachItemCaseTest.java b/core/src/test/java/io/kestra/plugin/core/flow/ForEachItemCaseTest.java
index 6aa6a9dc34..8990f6095d 100644
--- a/core/src/test/java/io/kestra/plugin/core/flow/ForEachItemCaseTest.java
+++ b/core/src/test/java/io/kestra/plugin/core/flow/ForEachItemCaseTest.java
@@ -1,5 +1,6 @@
package io.kestra.plugin.core.flow;
+import io.kestra.core.models.Label;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.State;
import io.kestra.core.queues.QueueException;
@@ -28,6 +29,7 @@
import java.time.Duration;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -101,6 +103,9 @@ public void forEachItem() throws TimeoutException, InterruptedException, URISynt
assertThat(triggered.get().getFlowId(), is("for-each-item-subflow"));
assertThat((String) triggered.get().getInputs().get("items"), matchesRegex("kestra:///io/kestra/tests/for-each-item/executions/.*/tasks/each-split/.*\\.txt"));
assertThat(triggered.get().getTaskRunList(), hasSize(1));
+ Optional correlationId = triggered.get().getLabels().stream().filter(label -> label.key().equals(Label.CORRELATION_ID)).findAny();
+ assertThat(correlationId.isPresent(), is(true));
+ assertThat(correlationId.get().value(), is(execution.getId()));
}
public void forEachItemEmptyItems() throws TimeoutException, URISyntaxException, IOException, QueueException {
diff --git a/core/src/test/java/io/kestra/plugin/core/flow/SleepTest.java b/core/src/test/java/io/kestra/plugin/core/flow/SleepTest.java
new file mode 100644
index 0000000000..50e31a8286
--- /dev/null
+++ b/core/src/test/java/io/kestra/plugin/core/flow/SleepTest.java
@@ -0,0 +1,23 @@
+package io.kestra.plugin.core.flow;
+
+import io.kestra.core.models.executions.Execution;
+import io.kestra.core.models.flows.State;
+import io.kestra.core.queues.QueueException;
+import io.kestra.core.runners.AbstractMemoryRunnerTest;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeoutException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class SleepTest extends AbstractMemoryRunnerTest {
+ @Test
+ void sleepTaskTest() throws TimeoutException, QueueException {
+ Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "sleep-task-flow");
+
+ assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
+ }
+
+
+}
diff --git a/core/src/test/java/io/kestra/plugin/core/http/DownloadTest.java b/core/src/test/java/io/kestra/plugin/core/http/DownloadTest.java
index 5442df59b1..d69d91f0ac 100644
--- a/core/src/test/java/io/kestra/plugin/core/http/DownloadTest.java
+++ b/core/src/test/java/io/kestra/plugin/core/http/DownloadTest.java
@@ -1,6 +1,7 @@
package io.kestra.plugin.core.http;
import com.google.common.collect.ImmutableMap;
+import io.kestra.core.junit.annotations.KestraTest;
import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.kestra.core.storages.StorageInterface;
@@ -12,19 +13,16 @@
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.runtime.server.EmbeddedServer;
-import io.kestra.core.junit.annotations.KestraTest;
import jakarta.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URI;
-import java.net.URL;
import java.nio.charset.StandardCharsets;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.endsWith;
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -135,7 +133,7 @@ void contentDisposition() throws Exception {
Download.Output output = task.run(runContext);
- assertThat(output.getUri().toString(), endsWith("filename.jpg"));
+ assertThat(output.getUri().toString(), containsString("filename.jpg"));
}
@Controller()
diff --git a/core/src/test/resources/flows/invalids/system-labels.yaml b/core/src/test/resources/flows/invalids/system-labels.yaml
new file mode 100644
index 0000000000..8f8ff62924
--- /dev/null
+++ b/core/src/test/resources/flows/invalids/system-labels.yaml
@@ -0,0 +1,11 @@
+id: system-labels
+namespace: io.kestra.tests
+
+labels:
+ system_label: system_key
+ system_id: id
+ another: label
+
+tasks:
+ - type: io.kestra.plugin.core.log.Log
+ message: "bla"
\ No newline at end of file
diff --git a/core/src/test/resources/flows/valids/retry-invalid.yml b/core/src/test/resources/flows/valids/retry-invalid.yml
deleted file mode 100644
index 2ab16ababa..0000000000
--- a/core/src/test/resources/flows/valids/retry-invalid.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: retry-invalid
-namespace: io.kestra.tests
-
-tasks:
- - id: log
- type: io.kestra.plugin.core.log.Log
- message: Hello World!
- retry:
- type: constant
- interval: PT0.250S
- maxAttempt: 5
- maxDuration: PT0.250S
\ No newline at end of file
diff --git a/core/src/test/resources/flows/valids/sleep-task-flow.yaml b/core/src/test/resources/flows/valids/sleep-task-flow.yaml
new file mode 100644
index 0000000000..1cff3b3663
--- /dev/null
+++ b/core/src/test/resources/flows/valids/sleep-task-flow.yaml
@@ -0,0 +1,9 @@
+id: sleep-task-flow
+namespace: io.kestra.tests
+
+tasks:
+ - id: sleep
+ type: io.kestra.plugin.core.flow.Sleep
+ duration: PT2S
+
+
diff --git a/core/src/test/resources/flows/valids/subflow-child.yaml b/core/src/test/resources/flows/valids/subflow-child.yaml
new file mode 100644
index 0000000000..bd3196e409
--- /dev/null
+++ b/core/src/test/resources/flows/valids/subflow-child.yaml
@@ -0,0 +1,8 @@
+id: subflow-child
+namespace: io.kestra.tests
+
+tasks:
+ - id: subflow
+ type: io.kestra.plugin.core.flow.Subflow
+ namespace: io.kestra.tests
+ flowId: subflow-grand-child
\ No newline at end of file
diff --git a/core/src/test/resources/flows/valids/subflow-grand-child.yaml b/core/src/test/resources/flows/valids/subflow-grand-child.yaml
new file mode 100644
index 0000000000..09e1ca5a89
--- /dev/null
+++ b/core/src/test/resources/flows/valids/subflow-grand-child.yaml
@@ -0,0 +1,7 @@
+id: subflow-grand-child
+namespace: io.kestra.tests
+
+tasks:
+ - id: firstLevel
+ type: io.kestra.plugin.core.log.Log
+ message: "My grandparent is {{labels.system_correlationId}}"
\ No newline at end of file
diff --git a/core/src/test/resources/flows/valids/subflow-parent.yaml b/core/src/test/resources/flows/valids/subflow-parent.yaml
new file mode 100644
index 0000000000..a3489e824b
--- /dev/null
+++ b/core/src/test/resources/flows/valids/subflow-parent.yaml
@@ -0,0 +1,8 @@
+id: subflow-parent
+namespace: io.kestra.tests
+
+tasks:
+ - id: subflow
+ type: io.kestra.plugin.core.flow.Subflow
+ namespace: io.kestra.tests
+ flowId: subflow-child
\ No newline at end of file
diff --git a/e2e-tests/build.gradle b/e2e-tests/build.gradle
index 5c788191f1..4690f26329 100644
--- a/e2e-tests/build.gradle
+++ b/e2e-tests/build.gradle
@@ -4,7 +4,7 @@ configurations {
dependencies {
testImplementation project(':tests')
- testImplementation("com.microsoft.playwright:playwright:1.47.0")
+ testImplementation("com.microsoft.playwright:playwright:1.48.0")
}
/**********************************************************************************************************************\
diff --git a/jdbc/src/main/java/io/kestra/jdbc/runner/JdbcQueue.java b/jdbc/src/main/java/io/kestra/jdbc/runner/JdbcQueue.java
index e28b4b710f..f79c66f7ae 100644
--- a/jdbc/src/main/java/io/kestra/jdbc/runner/JdbcQueue.java
+++ b/jdbc/src/main/java/io/kestra/jdbc/runner/JdbcQueue.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.CaseFormat;
import io.kestra.core.exceptions.DeserializationException;
+import io.kestra.core.metrics.MetricRegistry;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.queues.QueueException;
import io.kestra.core.queues.QueueInterface;
@@ -64,6 +65,8 @@ public abstract class JdbcQueue implements QueueInterface {
protected final MessageProtectionConfiguration messageProtectionConfiguration;
+ private final MetricRegistry metricRegistry;
+
protected final Table table;
protected final JdbcQueueIndexer jdbcQueueIndexer;
@@ -80,6 +83,7 @@ public JdbcQueue(Class cls, ApplicationContext applicationContext) {
this.dslContextWrapper = applicationContext.getBean(JooqDSLContextWrapper.class);
this.configuration = applicationContext.getBean(Configuration.class);
this.messageProtectionConfiguration = applicationContext.getBean(MessageProtectionConfiguration.class);
+ this.metricRegistry = applicationContext.getBean(MetricRegistry.class);
JdbcTableConfigs jdbcTableConfigs = applicationContext.getBean(JdbcTableConfigs.class);
@@ -97,6 +101,10 @@ protected Map, Object> produceFields(String consumerGroup, String
}
if (messageProtectionConfiguration.enabled && bytes.length >= messageProtectionConfiguration.limit) {
+ metricRegistry
+ .counter(MetricRegistry.QUEUE_BIG_MESSAGE_COUNT, MetricRegistry.TAG_CLASS_NAME, cls.getName())
+ .increment();
+
// we let terminated execution messages to go through anyway
if (!(message instanceof Execution execution) || !execution.getState().isTerminated()) {
throw new MessageTooBigException("Message of size " + bytes.length + " has exceeded the configured limit of " + messageProtectionConfiguration.limit);
diff --git a/platform/build.gradle b/platform/build.gradle
index dada2561c1..9d23b678f6 100644
--- a/platform/build.gradle
+++ b/platform/build.gradle
@@ -19,7 +19,7 @@ dependencies {
def jollydayVersion = "0.29.0"
def jsonschemaVersion = "4.35.0"
def kafkaVersion = "3.7.1"
- def opensearchVersion = "2.14.0"
+ def opensearchVersion = "2.16.0"
def opensearchRestVersion = "2.17.1"
def flyingSaucerVersion = "9.9.4"
@@ -113,7 +113,7 @@ dependencies {
api 'org.codehaus.plexus:plexus-utils:3.0.24' // https://nvd.nist.gov/vuln/detail/CVE-2022-4244
// for jOOQ to the same version as we use in EE
- api ("org.jooq:jooq:3.19.13")
+ api ("org.jooq:jooq:3.19.14")
// Tests
api "org.junit-pioneer:junit-pioneer:2.3.0"
@@ -122,7 +122,7 @@ dependencies {
api group: 'org.exparity', name: 'hamcrest-date', version: '2.0.8'
api 'com.github.tomakehurst:wiremock-jre8:3.0.1'
api "org.apache.kafka:kafka-streams-test-utils:$kafkaVersion"
- api "com.microsoft.playwright:playwright:1.47.0"
+ api "com.microsoft.playwright:playwright:1.48.0"
// Kestra components
api "io.kestra:core:$version"
diff --git a/storage-local/src/main/java/io/kestra/storage/local/LocalStorage.java b/storage-local/src/main/java/io/kestra/storage/local/LocalStorage.java
index 9669338c47..90946dd599 100644
--- a/storage-local/src/main/java/io/kestra/storage/local/LocalStorage.java
+++ b/storage-local/src/main/java/io/kestra/storage/local/LocalStorage.java
@@ -161,7 +161,7 @@ public URI put(String tenantId, URI uri, StorageObject storageObject) throws IOE
}
}
- return URI.create("kestra://" + uri.getPath());
+ return URI.create("kestra://" + uri.getRawPath());
}
@Override
diff --git a/ui/build.gradle b/ui/build.gradle
index 94b70cefd3..2993d6000b 100644
--- a/ui/build.gradle
+++ b/ui/build.gradle
@@ -1,7 +1,7 @@
-import org.siouan.frontendgradleplugin.infrastructure.gradle.RunNpm
+import org.siouan.frontendgradleplugin.infrastructure.gradle.RunNpmTaskType
plugins {
- id 'org.siouan.frontend-jdk17'
+ id 'org.siouan.frontend-jdk21'
}
publishSonatypePublicationPublicationToSonatypeRepository.enabled = false
@@ -28,12 +28,12 @@ tasks.named('assembleFrontend') {
outputs.dir('../webserver/src/main/resources/ui')
}
-final linterTask = tasks.register('lintFrontend', RunNpm) {
+final linterTask = tasks.register('lintFrontend', RunNpmTaskType) {
dependsOn tasks.named('installFrontend')
group 'Frontend'
description 'Lints the frontend source code.'
- script = 'run test:lint'
+ args = 'run test:lint'
}
check.dependsOn linterTask
\ No newline at end of file
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 8173d45d19..63b8f7810b 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kestra",
- "version": "0.19.3",
+ "version": "0.19.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kestra",
- "version": "0.19.3",
+ "version": "0.19.5",
"dependencies": {
"@js-joda/core": "^5.6.3",
"@kestra-io/ui-libs": "^0.0.69",
@@ -22,7 +22,7 @@
"core-js": "^3.38.1",
"cronstrue": "^2.50.0",
"dagre": "^0.8.5",
- "element-plus": "^2.8.5",
+ "element-plus": "^2.8.6",
"humanize-duration": "^3.32.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
@@ -39,7 +39,7 @@
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"pdfjs-dist": "^4.7.76",
- "posthog-js": "^1.169.0",
+ "posthog-js": "^1.174.3",
"rapidoc": "^9.3.8",
"shiki": "^1.22.0",
"throttle-debounce": "^5.0.2",
@@ -63,27 +63,27 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@rushstack/eslint-patch": "^1.10.4",
"@shikijs/markdown-it": "^1.22.0",
- "@typescript-eslint/parser": "^8.9.0",
+ "@typescript-eslint/parser": "^8.11.0",
"@vitejs/plugin-vue": "^5.1.4",
- "@vue/eslint-config-prettier": "^10.0.0",
+ "@vue/eslint-config-prettier": "^10.1.0",
"@vue/test-utils": "^2.4.6",
"decompress": "^4.2.1",
"eslint": "^8.57.1",
- "eslint-plugin-vue": "^9.29.0",
+ "eslint-plugin-vue": "^9.29.1",
"jsdom": "^25.0.1",
"monaco-editor": "^0.52.0",
"monaco-yaml": "^5.2.2",
"prettier": "^3.3.3",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "^5.12.0",
- "sass": "^1.79.5",
- "typescript": "5.5.4",
- "vite": "^5.4.9",
+ "sass": "^1.80.3",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.10",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^2.1.3"
},
"optionalDependencies": {
- "@rollup/rollup-darwin-arm64": "^4.12.1",
+ "@rollup/rollup-darwin-arm64": "^4.24.0",
"@rollup/rollup-linux-x64-gnu": "^4.24.0"
}
},
@@ -2173,16 +2173,15 @@
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.9.0.tgz",
- "integrity": "sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz",
+ "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==",
"dev": true,
- "license": "BSD-2-Clause",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.9.0",
- "@typescript-eslint/types": "8.9.0",
- "@typescript-eslint/typescript-estree": "8.9.0",
- "@typescript-eslint/visitor-keys": "8.9.0",
+ "@typescript-eslint/scope-manager": "8.11.0",
+ "@typescript-eslint/types": "8.11.0",
+ "@typescript-eslint/typescript-estree": "8.11.0",
+ "@typescript-eslint/visitor-keys": "8.11.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2202,14 +2201,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz",
- "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz",
+ "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.9.0",
- "@typescript-eslint/visitor-keys": "8.9.0"
+ "@typescript-eslint/types": "8.11.0",
+ "@typescript-eslint/visitor-keys": "8.11.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2220,11 +2218,10 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz",
- "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz",
+ "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -2234,14 +2231,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz",
- "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz",
+ "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==",
"dev": true,
- "license": "BSD-2-Clause",
"dependencies": {
- "@typescript-eslint/types": "8.9.0",
- "@typescript-eslint/visitor-keys": "8.9.0",
+ "@typescript-eslint/types": "8.11.0",
+ "@typescript-eslint/visitor-keys": "8.11.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2263,13 +2259,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz",
- "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz",
+ "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.9.0",
+ "@typescript-eslint/types": "8.11.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -2539,9 +2534,9 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/eslint-config-prettier": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.0.0.tgz",
- "integrity": "sha512-iDEjsfT+UXQTJfe+4mstb/B5BSZ5RpL6FO3F97XxElIXdD04gkH+F7PR4fBMEVyJi4892G4LQVPQ8oXxVyp8Dw==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.1.0.tgz",
+ "integrity": "sha512-J6wV91y2pXc0Phha01k0WOHBTPsoSTf4xlmMjoKaeSxBpAdsgTppGF5RZRdOHM7OA74zAXD+VLANrtYXpiPKkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4488,10 +4483,9 @@
"integrity": "sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA=="
},
"node_modules/element-plus": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.5.tgz",
- "integrity": "sha512-Px+kPbRTVvn5oa5+9saa7QEOnUweKXm0JVI7yJHzKF/doQGixwcFMsQEF2+3Fy62EA/7dRRKVuhsNGGZYNk3cw==",
- "license": "MIT",
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.6.tgz",
+ "integrity": "sha512-fk5jB8V3efM02/4roZ5SWOLArgaYXbxEydZLlXSr+KPAwjNyHBlk2+HO5em8YKo5+RLBoHnn6BaThj6IE4nXoQ==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.1",
@@ -4841,11 +4835,10 @@
}
},
"node_modules/eslint-plugin-vue": {
- "version": "9.29.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.0.tgz",
- "integrity": "sha512-hamyjrBhNH6Li6R1h1VF9KHfshJlKgKEg3ARbGTn72CMNDSMhWbgC7NdkRDEh25AFW+4SDATzyNM+3gWuZii8g==",
+ "version": "9.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.1.tgz",
+ "integrity": "sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"globals": "^13.24.0",
@@ -8319,14 +8312,14 @@
}
},
"node_modules/posthog-js": {
- "version": "1.169.0",
- "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.169.0.tgz",
- "integrity": "sha512-C0TiNv6ehbiy78F9gKZIqy3RbCRsWDSQDbQMi1YW2iuO4kDQUQwacmx2DKyaCwsH0/oN69FdBl99WoEJdjmxXg==",
- "license": "MIT",
+ "version": "1.174.3",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.174.3.tgz",
+ "integrity": "sha512-fRLncd3jkT9Y7gLiyQe8v8sJ9yuTIiQBBWcYQ8l+vv+m504LWFtxl+/JZtHXPhaG3Eyf7AzZ/Kafkw8jorWV9w==",
"dependencies": {
+ "core-js": "^3.38.1",
"fflate": "^0.4.8",
"preact": "^10.19.3",
- "web-vitals": "^4.0.1"
+ "web-vitals": "^4.2.0"
}
},
"node_modules/preact": {
@@ -9133,9 +9126,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
- "version": "1.79.5",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz",
- "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==",
+ "version": "1.80.3",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz",
+ "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9833,7 +9826,6 @@
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=16"
},
@@ -9903,9 +9895,9 @@
}
},
"node_modules/typescript": {
- "version": "5.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
- "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@@ -10333,11 +10325,10 @@
}
},
"node_modules/vite": {
- "version": "5.4.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
- "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
+ "version": "5.4.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
+ "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
diff --git a/ui/package.json b/ui/package.json
index 9821f25765..bdbeda773a 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "kestra",
- "version": "0.19.3",
+ "version": "0.19.5",
"private": true,
"type": "module",
"packageManager": "npm@9.9.3",
@@ -28,7 +28,7 @@
"core-js": "^3.38.1",
"cronstrue": "^2.50.0",
"dagre": "^0.8.5",
- "element-plus": "^2.8.5",
+ "element-plus": "^2.8.6",
"humanize-duration": "^3.32.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
@@ -45,7 +45,7 @@
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"pdfjs-dist": "^4.7.76",
- "posthog-js": "^1.169.0",
+ "posthog-js": "^1.174.3",
"rapidoc": "^9.3.8",
"shiki": "^1.22.0",
"throttle-debounce": "^5.0.2",
@@ -69,27 +69,27 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@rushstack/eslint-patch": "^1.10.4",
"@shikijs/markdown-it": "^1.22.0",
- "@typescript-eslint/parser": "^8.9.0",
+ "@typescript-eslint/parser": "^8.11.0",
"@vitejs/plugin-vue": "^5.1.4",
- "@vue/eslint-config-prettier": "^10.0.0",
+ "@vue/eslint-config-prettier": "^10.1.0",
"@vue/test-utils": "^2.4.6",
"decompress": "^4.2.1",
"eslint": "^8.57.1",
- "eslint-plugin-vue": "^9.29.0",
+ "eslint-plugin-vue": "^9.29.1",
"jsdom": "^25.0.1",
"monaco-editor": "^0.52.0",
"monaco-yaml": "^5.2.2",
"prettier": "^3.3.3",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-visualizer": "^5.12.0",
- "sass": "^1.79.5",
- "typescript": "5.5.4",
- "vite": "^5.4.9",
+ "sass": "^1.80.3",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.10",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^2.1.3"
},
"optionalDependencies": {
- "@rollup/rollup-darwin-arm64": "^4.12.1",
+ "@rollup/rollup-darwin-arm64": "^4.24.0",
"@rollup/rollup-linux-x64-gnu": "^4.24.0"
},
"overrides": {
@@ -97,4 +97,4 @@
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7"
}
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/assets/documentations/basic.md b/ui/src/assets/documentations/basic.md
index bba03e2496..26b4d0e16a 100644
--- a/ui/src/assets/documentations/basic.md
+++ b/ui/src/assets/documentations/basic.md
@@ -146,7 +146,9 @@ The table below lists common Pebble expressions and functions.
| `{{ secret('MY_SECRET') }}` | Retrieves secret `MY_SECRET`. |
| `{{ namespace.myproject.myvariable }}` | Accesses namespace variable `myproject.myvariable`. |
| `{{ outputs.taskId.outputAttribute }}` | Accesses task output attribute. |
-| `{{ error.taskId }}` | In case of failure, accesses the task identifier of the task that fail. |
+| `{{ error.taskId }}` | In case of failure, accesses the task identifier of the last task that fail. |
+| `{{ error.message }}` | In case of failure, accesses the last error message. |
+| `{{ error.stackTrace }}` | In case of failure, accesses the last error stack trace. |
| `{{ range(0, 3) }}` | Generates a list from 0 to 3. |
| `{{ block("post") }}` | Renders the contents of the ["post" block](https://kestra.io/docs/concepts/expression/function#block). |
| `{{ currentEachOutput(outputs.first) }}` | Retrieves the current output of a sibling task. |
diff --git a/ui/src/components/admin/Triggers.vue b/ui/src/components/admin/Triggers.vue
index 62a4d8439c..090cb714d1 100644
--- a/ui/src/components/admin/Triggers.vue
+++ b/ui/src/components/admin/Triggers.vue
@@ -80,12 +80,14 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -475,6 +478,25 @@
const disabled = this.state === "DISABLED" ? true : false;
return all.filter(trigger => trigger.disabled === disabled);
+ },
+ visibleColumns() {
+ const columns = [
+ {prop: "triggerId", label: this.$t("id")},
+ {prop: "flowId", label: this.$t("flow")},
+ {prop: "namespace", label: this.$t("namespace")},
+ {prop: "executionId", label: this.$t("current execution")},
+ {prop: "executionCurrentState", label: this.$t("state")},
+ {prop: "workerId", label: this.$t("workerId")},
+ {prop: "date", label: this.$t("date")},
+ {prop: "updatedDate", label: this.$t("updated date")},
+ {prop: "nextExecutionDate", label: this.$t("next execution date")},
+ {prop: "evaluateRunningDate", label: this.$t("evaluation lock date")},
+ ];
+
+ return columns.reduce((acc, column) => {
+ acc[column.prop] = this.triggersMerged.some(trigger => trigger[column.prop]);
+ return acc;
+ }, {});
}
}
};
diff --git a/ui/src/components/dashboard/components/charts/executions/Bar.vue b/ui/src/components/dashboard/components/charts/executions/Bar.vue
index 0307a7dac4..a4aef19d4a 100644
--- a/ui/src/components/dashboard/components/charts/executions/Bar.vue
+++ b/ui/src/components/dashboard/components/charts/executions/Bar.vue
@@ -25,15 +25,18 @@
+
+
+
-