mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-26 05:00:31 -05:00
Compare commits
40 Commits
v1.0.7
...
fix/sdk-ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aafc4c326b | ||
|
|
461058c13e | ||
|
|
b7512c3124 | ||
|
|
2bd51ccdec | ||
|
|
ee9193c4d5 | ||
|
|
d3e5293ab7 | ||
|
|
68336c753d | ||
|
|
73f3471c0e | ||
|
|
4012f74e43 | ||
|
|
d79a0d3fb2 | ||
|
|
5720682d2c | ||
|
|
d9c5b274d3 | ||
|
|
a816dff4b0 | ||
|
|
0d31e140b5 | ||
|
|
e61d5568df | ||
|
|
e7216d9f6b | ||
|
|
adfe389c7b | ||
|
|
47ab4ce9d1 | ||
|
|
d10893ca00 | ||
|
|
c5ef356a1c | ||
|
|
0313e8e49b | ||
|
|
f4b6161f14 | ||
|
|
e69e82a35e | ||
|
|
e77378bcb7 | ||
|
|
3c9df90a35 | ||
|
|
6c86f0917c | ||
|
|
30b7346ee0 | ||
|
|
2f485c74ff | ||
|
|
3a5713bbd1 | ||
|
|
2eed738b83 | ||
|
|
5e2609ce5e | ||
|
|
86f909ce93 | ||
|
|
a8cb28a127 | ||
|
|
0fe9ba3e13 | ||
|
|
40f5aadd1a | ||
|
|
ceac25429a | ||
|
|
4144d9fbb1 | ||
|
|
9cc7d45f74 | ||
|
|
81ee330b9e | ||
|
|
5382655a2e |
11
.github/workflows/pre-release.yml
vendored
11
.github/workflows/pre-release.yml
vendored
@@ -5,6 +5,15 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip-test:
|
||||
description: 'Skip test'
|
||||
type: choice
|
||||
required: true
|
||||
default: 'false'
|
||||
options:
|
||||
- "true"
|
||||
- "false"
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
@@ -14,6 +23,7 @@ jobs:
|
||||
backend-tests:
|
||||
name: Backend tests
|
||||
uses: kestra-io/actions/.github/workflows/kestra-oss-backend-tests.yml@main
|
||||
if: ${{ github.event.inputs.skip-test == 'false' || github.event.inputs.skip-test == '' }}
|
||||
secrets:
|
||||
GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -23,6 +33,7 @@ jobs:
|
||||
frontend-tests:
|
||||
name: Frontend tests
|
||||
uses: kestra-io/actions/.github/workflows/kestra-oss-frontend-tests.yml@main
|
||||
if: ${{ github.event.inputs.skip-test == 'false' || github.event.inputs.skip-test == '' }}
|
||||
secrets:
|
||||
GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
12
.github/workflows/release-docker.yml
vendored
12
.github/workflows/release-docker.yml
vendored
@@ -13,11 +13,11 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
plugin-version:
|
||||
description: 'Plugin version'
|
||||
required: false
|
||||
type: string
|
||||
default: "LATEST"
|
||||
dry-run:
|
||||
description: 'Dry run mode that will not write or release anything'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
publish-docker:
|
||||
@@ -25,9 +25,9 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: kestra-io/actions/.github/workflows/kestra-oss-publish-docker.yml@main
|
||||
with:
|
||||
plugin-version: ${{ inputs.plugin-version }}
|
||||
retag-latest: ${{ inputs.retag-latest }}
|
||||
retag-lts: ${{ inputs.retag-lts }}
|
||||
dry-run: ${{ inputs.dry-run }}
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class KvUpdateCommand extends AbstractApiCommand {
|
||||
Duration ttl = expiration == null ? null : Duration.parse(expiration);
|
||||
MutableHttpRequest<String> request = HttpRequest
|
||||
.PUT(apiUri("/namespaces/", tenantService.getTenantId(tenantId)) + namespace + "/kv/" + key, value)
|
||||
.contentType(MediaType.APPLICATION_JSON_TYPE);
|
||||
.contentType(MediaType.TEXT_PLAIN);
|
||||
|
||||
if (ttl != null) {
|
||||
request.header("ttl", ttl.toString());
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.github.victools.jsonschema.generator.impl.DefinitionKey;
|
||||
import com.github.victools.jsonschema.generator.naming.DefaultSchemaDefinitionNamingStrategy;
|
||||
import com.github.victools.jsonschema.module.jackson.JacksonModule;
|
||||
import com.github.victools.jsonschema.module.jackson.JacksonOption;
|
||||
import com.github.victools.jsonschema.module.jackson.JsonUnwrappedDefinitionProvider;
|
||||
import com.github.victools.jsonschema.module.jakarta.validation.JakartaValidationModule;
|
||||
import com.github.victools.jsonschema.module.jakarta.validation.JakartaValidationOption;
|
||||
import com.github.victools.jsonschema.module.swagger2.Swagger2Module;
|
||||
@@ -45,6 +46,9 @@ import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.time.*;
|
||||
@@ -58,7 +62,9 @@ import static io.kestra.core.docs.AbstractClassDocumentation.required;
|
||||
import static io.kestra.core.serializers.JacksonMapper.MAP_TYPE_REFERENCE;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class JsonSchemaGenerator {
|
||||
|
||||
private static final List<Class<?>> TYPES_RESOLVED_AS_STRING = List.of(Duration.class, LocalTime.class, LocalDate.class, LocalDateTime.class, ZonedDateTime.class, OffsetDateTime.class, OffsetTime.class);
|
||||
private static final List<Class<?>> SUBTYPE_RESOLUTION_EXCLUSION_FOR_PLUGIN_SCHEMA = List.of(Task.class, AbstractTrigger.class);
|
||||
|
||||
@@ -270,8 +276,22 @@ public class JsonSchemaGenerator {
|
||||
.with(Option.DEFINITIONS_FOR_ALL_OBJECTS)
|
||||
.with(Option.DEFINITION_FOR_MAIN_SCHEMA)
|
||||
.with(Option.PLAIN_DEFINITION_KEYS)
|
||||
.with(Option.ALLOF_CLEANUP_AT_THE_END);;
|
||||
|
||||
.with(Option.ALLOF_CLEANUP_AT_THE_END);
|
||||
|
||||
// HACK: Registered a custom JsonUnwrappedDefinitionProvider prior to the JacksonModule
|
||||
// to be able to return an CustomDefinition with an empty node when the ResolvedType can't be found.
|
||||
builder.forTypesInGeneral().withCustomDefinitionProvider(new JsonUnwrappedDefinitionProvider(){
|
||||
@Override
|
||||
public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, SchemaGenerationContext context) {
|
||||
try {
|
||||
return super.provideCustomSchemaDefinition(javaType, context);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// This error happens when a non-supported plugin type exists in the classpath.
|
||||
log.debug("Cannot create schema definition for type '{}'. Cause: NoClassDefFoundError", javaType.getTypeName());
|
||||
return new CustomDefinition(context.getGeneratorConfig().createObjectNode(), true);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!draft7) {
|
||||
builder.with(new JacksonModule(JacksonOption.IGNORE_TYPE_INFO_TRANSFORM));
|
||||
} else {
|
||||
@@ -300,6 +320,7 @@ public class JsonSchemaGenerator {
|
||||
// inline some type
|
||||
builder.forTypesInGeneral()
|
||||
.withCustomDefinitionProvider(new CustomDefinitionProviderV2() {
|
||||
|
||||
@Override
|
||||
public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, SchemaGenerationContext context) {
|
||||
if (javaType.isInstanceOf(Map.class) || javaType.isInstanceOf(Enum.class)) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.ListUtils;
|
||||
import io.kestra.core.utils.MapUtils;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
@@ -77,10 +78,12 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
|
||||
@With
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Schema(implementation = Object.class)
|
||||
Map<String, Object> inputs;
|
||||
|
||||
@With
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Schema(implementation = Object.class)
|
||||
Map<String, Object> outputs;
|
||||
|
||||
@JsonSerialize(using = ListOrMapOfLabelSerializer.class)
|
||||
@@ -88,6 +91,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
List<Label> labels;
|
||||
|
||||
@With
|
||||
@Schema(implementation = Object.class)
|
||||
Map<String, Object> variables;
|
||||
|
||||
@NotNull
|
||||
@@ -936,7 +940,15 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
for (TaskRun current : taskRuns) {
|
||||
if (!MapUtils.isEmpty(current.getOutputs())) {
|
||||
if (current.getIteration() != null) {
|
||||
taskOutputs = MapUtils.merge(taskOutputs, outputs(current, byIds));
|
||||
Map<String, Object> merged = MapUtils.merge(taskOutputs, outputs(current, byIds));
|
||||
// If one of two of the map is null in the merge() method, we just return the other
|
||||
// And if the not null map is a Variables (= read only), we cast it back to a simple
|
||||
// hashmap to avoid taskOutputs becoming read-only
|
||||
// i.e this happen in nested loopUntil tasks
|
||||
if (merged instanceof Variables) {
|
||||
merged = new HashMap<>(merged);
|
||||
}
|
||||
taskOutputs = merged;
|
||||
} else {
|
||||
taskOutputs.putAll(outputs(current, byIds));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.kestra.core.models.tasks.ResolvedTask;
|
||||
import io.kestra.core.models.tasks.retrys.AbstractRetry;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
@@ -53,6 +54,7 @@ public class TaskRun implements TenantInterface {
|
||||
@With
|
||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||
@Nullable
|
||||
@Schema(implementation = Object.class)
|
||||
Variables outputs;
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -282,9 +282,10 @@ public class FlowInputOutput {
|
||||
Input<?> input = resolvable.get().input();
|
||||
|
||||
try {
|
||||
// resolve all input dependencies and check whether input is enabled
|
||||
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, flow, execution, inputs, decryptSecrets);
|
||||
final RunContext runContext = buildRunContextForExecutionAndInputs(flow, execution, dependencies, decryptSecrets);
|
||||
// Resolve all input dependencies and check whether input is enabled
|
||||
// Note: Secrets are always decrypted here because they can be part of expressions used to render inputs such as SELECT & MULTI_SELECT.
|
||||
final Map<String, InputAndValue> dependencies = resolveAllDependentInputs(input, flow, execution, inputs, true);
|
||||
final RunContext runContext = buildRunContextForExecutionAndInputs(flow, execution, dependencies, true);
|
||||
|
||||
boolean isInputEnabled = dependencies.isEmpty() || dependencies.values().stream().allMatch(InputAndValue::enabled);
|
||||
|
||||
@@ -324,7 +325,8 @@ public class FlowInputOutput {
|
||||
|
||||
// resolve default if needed
|
||||
if (value == null && input.getDefaults() != null) {
|
||||
value = resolveDefaultValue(input, runContext);
|
||||
RunContext runContextForDefault = decryptSecrets ? runContext : buildRunContextForExecutionAndInputs(flow, execution, dependencies, false);
|
||||
value = resolveDefaultValue(input, runContextForDefault);
|
||||
resolvable.isDefault(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import io.kestra.core.models.flows.FlowInterface;
|
||||
import io.kestra.core.models.flows.Input;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.flows.input.SecretInput;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.models.property.PropertyContext;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
@@ -100,7 +99,7 @@ public final class RunVariables {
|
||||
* @return a new immutable {@link Map}.
|
||||
*/
|
||||
static Map<String, Object> of(final AbstractTrigger trigger) {
|
||||
return ImmutableMap.of(
|
||||
return Map.of(
|
||||
"id", trigger.getId(),
|
||||
"type", trigger.getType()
|
||||
);
|
||||
@@ -282,12 +281,15 @@ public final class RunVariables {
|
||||
}
|
||||
|
||||
if (flow != null && flow.getInputs() != null) {
|
||||
// Create a new PropertyContext with 'flow' variables which are required by some pebble expressions.
|
||||
PropertyContextWithVariables context = new PropertyContextWithVariables(propertyContext, Map.of("flow", RunVariables.of(flow)));
|
||||
|
||||
// we add default inputs value from the flow if not already set, this will be useful for triggers
|
||||
flow.getInputs().stream()
|
||||
.filter(input -> input.getDefaults() != null && !inputs.containsKey(input.getId()))
|
||||
.forEach(input -> {
|
||||
try {
|
||||
inputs.put(input.getId(), FlowInputOutput.resolveDefaultValue(input, propertyContext));
|
||||
inputs.put(input.getId(), FlowInputOutput.resolveDefaultValue(input, context));
|
||||
} catch (IllegalVariableEvaluationException e) {
|
||||
// Silent catch, if an input depends on another input, or a variable that is populated at runtime / input filling time, we can't resolve it here.
|
||||
}
|
||||
@@ -391,4 +393,20 @@ public final class RunVariables {
|
||||
}
|
||||
|
||||
private RunVariables(){}
|
||||
|
||||
private record PropertyContextWithVariables(
|
||||
PropertyContext delegate,
|
||||
Map<String, Object> variables
|
||||
) implements PropertyContext {
|
||||
|
||||
@Override
|
||||
public String render(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return delegate.render(inline, variables.isEmpty() ? this.variables : variables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> render(Map<String, Object> inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return delegate.render(inline, variables.isEmpty() ? this.variables : variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package io.kestra.core.runners.pebble.filters;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.pebbletemplates.pebble.error.PebbleException;
|
||||
import io.pebbletemplates.pebble.extension.Filter;
|
||||
import io.pebbletemplates.pebble.template.EvaluationContext;
|
||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChunkFilter implements Filter {
|
||||
@Override
|
||||
public List<String> getArgumentNames() {
|
||||
@@ -30,6 +31,10 @@ public class ChunkFilter implements Filter {
|
||||
throw new PebbleException(null, "'chunk' filter can only be applied to List. Actual type was: " + input.getClass().getName(), lineNumber, self.getName());
|
||||
}
|
||||
|
||||
return Lists.partition((List) input, ((Long) args.get("size")).intValue());
|
||||
Object sizeObj = args.get("size");
|
||||
if (!(sizeObj instanceof Number)) {
|
||||
throw new PebbleException(null, "'chunk' filter argument 'size' must be a number. Actual type was: " + sizeObj.getClass().getName(), lineNumber, self.getName());
|
||||
}
|
||||
return Lists.partition((List) input, ((Number) sizeObj).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JqFilter implements Filter {
|
||||
private final Scope scope;
|
||||
// Load Scope once as static to avoid repeated initialization
|
||||
// This improves performance by loading builtin functions only once when the class loads
|
||||
private static final Scope SCOPE;
|
||||
private final List<String> argumentNames = new ArrayList<>();
|
||||
|
||||
static {
|
||||
SCOPE = Scope.newEmptyScope();
|
||||
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, SCOPE);
|
||||
}
|
||||
|
||||
public JqFilter() {
|
||||
scope = Scope.newEmptyScope();
|
||||
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, scope);
|
||||
this.argumentNames.add("expression");
|
||||
}
|
||||
|
||||
@@ -43,10 +48,7 @@ public class JqFilter implements Filter {
|
||||
|
||||
String pattern = (String) args.get("expression");
|
||||
|
||||
Scope rootScope = Scope.newEmptyScope();
|
||||
BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, rootScope);
|
||||
try {
|
||||
|
||||
JsonQuery q = JsonQuery.compile(pattern, Versions.JQ_1_6);
|
||||
|
||||
JsonNode in;
|
||||
@@ -59,7 +61,7 @@ public class JqFilter implements Filter {
|
||||
final List<Object> out = new ArrayList<>();
|
||||
|
||||
try {
|
||||
q.apply(scope, in, v -> {
|
||||
q.apply(Scope.newChildScope(SCOPE), in, v -> {
|
||||
if (v instanceof TextNode) {
|
||||
out.add(v.textValue());
|
||||
} else if (v instanceof NullNode) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class KvFunction implements Function {
|
||||
String key = getKey(args, self, lineNumber);
|
||||
String namespace = (String) args.get(NAMESPACE_ARG);
|
||||
|
||||
Boolean errorOnMissing = Optional.ofNullable((Boolean) args.get(ERROR_ON_MISSING_ARG)).orElse(true);
|
||||
boolean errorOnMissing = Optional.ofNullable((Boolean) args.get(ERROR_ON_MISSING_ARG)).orElse(true);
|
||||
|
||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||
String flowNamespace = flow.get(NAMESPACE_ARG);
|
||||
@@ -53,11 +53,16 @@ public class KvFunction implements Function {
|
||||
// we didn't check allowedNamespace here as it's checked in the kvStoreService itself
|
||||
value = kvStoreService.get(flowTenantId, namespace, flowNamespace).getValue(key);
|
||||
}
|
||||
} catch (ResourceExpiredException e) {
|
||||
if (errorOnMissing) {
|
||||
throw new PebbleException(e, e.getMessage(), lineNumber, self.getName());
|
||||
}
|
||||
value = Optional.empty();
|
||||
} catch (Exception e) {
|
||||
throw new PebbleException(e, e.getMessage(), lineNumber, self.getName());
|
||||
}
|
||||
|
||||
if (value.isEmpty() && errorOnMissing == Boolean.TRUE) {
|
||||
if (value.isEmpty() && errorOnMissing) {
|
||||
throw new PebbleException(null, "The key '" + key + "' does not exist in the namespace '" + namespace + "'.", lineNumber, self.getName());
|
||||
}
|
||||
|
||||
@@ -85,4 +90,4 @@ public class KvFunction implements Function {
|
||||
|
||||
return (String) args.get(KEY_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.flows.input.InputAndValue;
|
||||
import io.kestra.core.models.hierarchies.AbstractGraphTask;
|
||||
import io.kestra.core.models.hierarchies.GraphCluster;
|
||||
import io.kestra.core.models.tasks.FlowableTask;
|
||||
import io.kestra.core.models.tasks.ResolvedTask;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.tasks.retrys.AbstractRetry;
|
||||
@@ -121,21 +122,38 @@ public class ExecutionService {
|
||||
* Retry set the given taskRun in created state
|
||||
* and return the execution in running state
|
||||
**/
|
||||
public Execution retryTask(Execution execution, String taskRunId) {
|
||||
List<TaskRun> newTaskRuns = execution
|
||||
.getTaskRunList()
|
||||
.stream()
|
||||
.map(taskRun -> {
|
||||
if (taskRun.getId().equals(taskRunId)) {
|
||||
return taskRun
|
||||
.withState(State.Type.CREATED);
|
||||
public Execution retryTask(Execution execution, Flow flow, String taskRunId) throws InternalException {
|
||||
TaskRun taskRun = execution.findTaskRunByTaskRunId(taskRunId).withState(State.Type.CREATED);
|
||||
List<TaskRun> taskRunList = execution.getTaskRunList();
|
||||
|
||||
if (taskRun.getParentTaskRunId() != null) {
|
||||
// we need to find the parent to remove any errors or finally tasks already executed
|
||||
TaskRun parentTaskRun = execution.findTaskRunByTaskRunId(taskRun.getParentTaskRunId());
|
||||
Task parentTask = flow.findTaskByTaskId(parentTaskRun.getTaskId());
|
||||
if (parentTask instanceof FlowableTask<?> flowableTask) {
|
||||
if (flowableTask.getErrors() != null) {
|
||||
List<Task> allErrors = Stream.concat(flowableTask.getErrors().stream()
|
||||
.filter(task -> task.isFlowable() && ((FlowableTask<?>) task).getErrors() != null)
|
||||
.flatMap(task -> ((FlowableTask<?>) task).getErrors().stream()),
|
||||
flowableTask.getErrors().stream())
|
||||
.toList();
|
||||
allErrors.forEach(error -> taskRunList.removeIf(t -> t.getTaskId().equals(error.getId())));
|
||||
}
|
||||
|
||||
return taskRun;
|
||||
})
|
||||
.toList();
|
||||
if (flowableTask.getFinally() != null) {
|
||||
List<Task> allFinally = Stream.concat(flowableTask.getFinally().stream()
|
||||
.filter(task -> task.isFlowable() && ((FlowableTask<?>) task).getFinally() != null)
|
||||
.flatMap(task -> ((FlowableTask<?>) task).getFinally().stream()),
|
||||
flowableTask.getFinally().stream())
|
||||
.toList();
|
||||
allFinally.forEach(error -> taskRunList.removeIf(t -> t.getTaskId().equals(error.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
return execution.withTaskRunList(newTaskRuns).withState(State.Type.RUNNING);
|
||||
return execution.withTaskRunList(taskRunList).withTaskRun(taskRun).withState(State.Type.RUNNING);
|
||||
}
|
||||
|
||||
return execution.withTaskRun(taskRun).withState(State.Type.RUNNING);
|
||||
}
|
||||
|
||||
public Execution retryWaitFor(Execution execution, String flowableTaskRunId) {
|
||||
@@ -709,7 +727,7 @@ public class ExecutionService {
|
||||
// An edge case can exist where the execution is resumed automatically before we resume it with a killing.
|
||||
try {
|
||||
newExecution = this.resume(execution, flow, State.Type.KILLING, null);
|
||||
newExecution = newExecution.withState(afterKillState.orElse(newExecution.getState().getCurrent()));
|
||||
newExecution = newExecution.withState(killingOrAfterKillState);
|
||||
} catch (Exception e) {
|
||||
// if we cannot resume, we set it anyway to killing, so we don't throw
|
||||
log.warn("Unable to resume a paused execution before killing it", e);
|
||||
@@ -723,6 +741,7 @@ public class ExecutionService {
|
||||
// immediately without publishing a CrudEvent like it's done on pause/resume method.
|
||||
return newExecution;
|
||||
}
|
||||
|
||||
public Execution kill(Execution execution, FlowInterface flow) {
|
||||
return this.kill(execution, flow, Optional.empty());
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class Switch extends Task implements FlowableTask<Switch.Output> {
|
||||
@Schema(
|
||||
title = "The map of keys and a list of tasks to be executed if the conditional `value` matches the key"
|
||||
)
|
||||
@PluginProperty
|
||||
@PluginProperty(additionalProperties = Task[].class)
|
||||
private Map<String, List<Task>> cases;
|
||||
|
||||
@Valid
|
||||
|
||||
@@ -173,8 +173,8 @@ public class Download extends AbstractHttp implements RunnableTask<Download.Outp
|
||||
if (path.indexOf('/') != -1) {
|
||||
path = path.substring(path.lastIndexOf('/')); // keep the last segment
|
||||
}
|
||||
if (path.indexOf('.') != -1) {
|
||||
return path.substring(path.indexOf('.'));
|
||||
if (path.lastIndexOf('.') != -1) {
|
||||
return path.substring(path.lastIndexOf('.'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ class ExecutionServiceTest {
|
||||
assertThat(restart.getState().getHistories()).hasSize(4);
|
||||
assertThat(restart.getTaskRunList().stream().filter(taskRun -> taskRun.getState().getCurrent() == State.Type.RESTARTED).count()).isGreaterThan(1L);
|
||||
assertThat(restart.getTaskRunList().stream().filter(taskRun -> taskRun.getState().getCurrent() == State.Type.RUNNING).count()).isGreaterThan(1L);
|
||||
|
||||
assertThat(restart.getTaskRunList().getFirst().getId()).isEqualTo(restart.getTaskRunList().getFirst().getId());
|
||||
assertThat(restart.getLabels()).contains(new Label(Label.RESTARTED, "true"));
|
||||
}
|
||||
@@ -413,9 +414,9 @@ class ExecutionServiceTest {
|
||||
|
||||
Execution killed = executionService.kill(execution, flow);
|
||||
|
||||
assertThat(killed.getState().getCurrent()).isEqualTo(State.Type.RESTARTED);
|
||||
assertThat(killed.getState().getCurrent()).isEqualTo(State.Type.KILLING);
|
||||
assertThat(killed.findTaskRunsByTaskId("pause").getFirst().getState().getCurrent()).isEqualTo(State.Type.KILLED);
|
||||
assertThat(killed.getState().getHistories()).hasSize(4);
|
||||
assertThat(killed.getState().getHistories()).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -8,17 +8,23 @@ import io.kestra.core.models.flows.Type;
|
||||
import io.kestra.core.models.flows.input.FileInput;
|
||||
import io.kestra.core.models.flows.input.InputAndValue;
|
||||
import io.kestra.core.models.flows.input.IntInput;
|
||||
import io.kestra.core.models.flows.input.MultiselectInput;
|
||||
import io.kestra.core.models.flows.input.StringInput;
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.kestra.core.secret.SecretNotFoundException;
|
||||
import io.kestra.core.secret.SecretService;
|
||||
import io.kestra.core.services.KVStoreService;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.storages.kv.InternalKVStore;
|
||||
import io.kestra.core.storages.kv.KVStore;
|
||||
import io.kestra.core.storages.kv.KVValue;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.micronaut.http.MediaType;
|
||||
import io.micronaut.http.multipart.CompletedFileUpload;
|
||||
import io.micronaut.http.multipart.CompletedPart;
|
||||
import io.micronaut.test.annotation.MockBean;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -34,11 +40,13 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
@KestraTest
|
||||
class FlowInputOutputTest {
|
||||
|
||||
private static final String TEST_SECRET_VALUE = "test-secret-value";
|
||||
private static final String TEST_KV_VALUE = "test-kv-value";
|
||||
|
||||
static final Execution DEFAULT_TEST_EXECUTION = Execution.builder()
|
||||
.id(IdUtils.create())
|
||||
@@ -63,6 +71,21 @@ class FlowInputOutputTest {
|
||||
};
|
||||
}
|
||||
|
||||
@MockBean(KVStoreService.class)
|
||||
KVStoreService testKVStoreService() {
|
||||
return new KVStoreService() {
|
||||
@Override
|
||||
public KVStore get(String tenant, String namespace, @Nullable String fromNamespace) {
|
||||
return new InternalKVStore(tenant, namespace, storageInterface) {
|
||||
@Override
|
||||
public Optional<KVValue> getValue(String key) {
|
||||
return Optional.of(new KVValue(TEST_KV_VALUE));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveEnabledInputsGivenInputWithConditionalExpressionMatchingTrue() {
|
||||
// Given
|
||||
@@ -318,6 +341,24 @@ class FlowInputOutputTest {
|
||||
Assertions.assertEquals("******", results.getFirst().value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotObfuscateSecretsInSelectWhenValidatingInputs() {
|
||||
// Given
|
||||
MultiselectInput input = MultiselectInput.builder()
|
||||
.id("input")
|
||||
.type(Type.MULTISELECT)
|
||||
.expression("{{ [secret('???')] }}")
|
||||
.required(false)
|
||||
.build();
|
||||
|
||||
// When
|
||||
List<InputAndValue> results = flowInputOutput.validateExecutionInputs(List.of(input), null, DEFAULT_TEST_EXECUTION, Mono.empty()).block();
|
||||
|
||||
// Then
|
||||
Assertions.assertEquals(TEST_SECRET_VALUE, ((MultiselectInput)results.getFirst().input()).getValues().getFirst());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldNotObfuscateSecretsWhenReadingInputs() {
|
||||
// Given
|
||||
@@ -335,6 +376,23 @@ class FlowInputOutputTest {
|
||||
Assertions.assertEquals(TEST_SECRET_VALUE, results.get("input"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEvaluateExpressionOnDefaultsUsingKVFunction() {
|
||||
// Given
|
||||
StringInput input = StringInput.builder()
|
||||
.id("input")
|
||||
.type(Type.STRING)
|
||||
.defaults(Property.ofExpression("{{ kv('???') }}"))
|
||||
.required(false)
|
||||
.build();
|
||||
|
||||
// When
|
||||
Map<String, Object> results = flowInputOutput.readExecutionInputs(List.of(input), null, DEFAULT_TEST_EXECUTION, Mono.empty()).block();
|
||||
|
||||
// Then
|
||||
assertThat(results.get("input")).isEqualTo(TEST_KV_VALUE);
|
||||
}
|
||||
|
||||
private static class MemoryCompletedPart implements CompletedPart {
|
||||
|
||||
protected final String name;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package io.kestra.core.runners;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.DependsOn;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.FlowInterface;
|
||||
import io.kestra.core.models.flows.GenericFlow;
|
||||
import io.kestra.core.models.flows.Type;
|
||||
import io.kestra.core.models.flows.input.BoolInput;
|
||||
import io.kestra.core.models.property.Property;
|
||||
@@ -11,25 +13,55 @@ import io.kestra.core.models.property.PropertyContext;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.runners.pebble.PebbleEngineFactory;
|
||||
import io.kestra.core.services.KVStoreService;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.storages.kv.InternalKVStore;
|
||||
import io.kestra.core.storages.kv.KVStore;
|
||||
import io.kestra.core.storages.kv.KVValue;
|
||||
import io.kestra.core.tenant.TenantService;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.test.annotation.MockBean;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@KestraTest
|
||||
class RunVariablesTest {
|
||||
|
||||
private final PropertyContext propertyContext = Mockito.mock(PropertyContext.class);
|
||||
@Inject
|
||||
VariableRenderer renderer;
|
||||
|
||||
@Inject
|
||||
StorageInterface storageInterface;
|
||||
|
||||
@MockBean(KVStoreService.class)
|
||||
KVStoreService testKVStoreService() {
|
||||
return new KVStoreService() {
|
||||
@Override
|
||||
public KVStore get(String tenant, String namespace, @Nullable String fromNamespace) {
|
||||
return new InternalKVStore(tenant, namespace, storageInterface) {
|
||||
@Override
|
||||
public Optional<KVValue> getValue(String key) {
|
||||
return Optional.of(new KVValue("value"));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldGetEmptyVariables() {
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder().build(new RunContextLogger(), propertyContext);
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder().build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
assertThat(variables.size()).isEqualTo(3);
|
||||
assertThat((Map<String, Object>) variables.get("envs")).isEqualTo(Map.of());
|
||||
assertThat((Map<String, Object>) variables.get("globals")).isEqualTo(Map.of());
|
||||
@@ -46,7 +78,7 @@ class RunVariablesTest {
|
||||
.revision(42)
|
||||
.build()
|
||||
)
|
||||
.build(new RunContextLogger(), propertyContext);
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
Assertions.assertEquals(Map.of(
|
||||
"id", "id-value",
|
||||
"namespace", "namespace-value",
|
||||
@@ -65,7 +97,7 @@ class RunVariablesTest {
|
||||
.tenantId("tenant-value")
|
||||
.build()
|
||||
)
|
||||
.build(new RunContextLogger(), propertyContext);
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
Assertions.assertEquals(Map.of(
|
||||
"id", "id-value",
|
||||
"namespace", "namespace-value",
|
||||
@@ -88,7 +120,7 @@ class RunVariablesTest {
|
||||
return "type-value";
|
||||
}
|
||||
})
|
||||
.build(new RunContextLogger(), propertyContext);
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
Assertions.assertEquals(Map.of("id", "id-value", "type", "type-value"), variables.get("task"));
|
||||
}
|
||||
|
||||
@@ -106,7 +138,7 @@ class RunVariablesTest {
|
||||
return "type-value";
|
||||
}
|
||||
})
|
||||
.build(new RunContextLogger(), propertyContext);
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
Assertions.assertEquals(Map.of("id", "id-value", "type", "type-value"), variables.get("trigger"));
|
||||
}
|
||||
|
||||
@@ -115,7 +147,7 @@ class RunVariablesTest {
|
||||
void shouldGetKestraConfiguration() {
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder()
|
||||
.withKestraConfiguration(new RunVariables.KestraConfiguration("test", "http://localhost:8080"))
|
||||
.build(new RunContextLogger(), propertyContext);
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
assertThat(variables.size()).isEqualTo(4);
|
||||
Map<String, Object> kestra = (Map<String, Object>) variables.get("kestra");
|
||||
assertThat(kestra).hasSize(2);
|
||||
@@ -124,7 +156,7 @@ class RunVariablesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonResolvableDynamicInputsShouldBeSkipped() throws IllegalVariableEvaluationException {
|
||||
void nonResolvableDynamicInputsShouldBeSkipped() {
|
||||
VariableRenderer.VariableConfiguration mkVariableConfiguration = Mockito.mock(VariableRenderer.VariableConfiguration.class);
|
||||
ApplicationContext mkApplicationContext = Mockito.mock(ApplicationContext.class);
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder()
|
||||
@@ -145,4 +177,23 @@ class RunVariablesTest {
|
||||
"a", true
|
||||
), variables.get("inputs"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildVariablesGivenFlowWithInputHavingDefaultPebbleExpression() {
|
||||
FlowInterface flow = GenericFlow.fromYaml(TenantService.MAIN_TENANT, """
|
||||
id: id-value
|
||||
namespace: namespace-value
|
||||
inputs:
|
||||
- id: input
|
||||
type: STRING
|
||||
defaults: "{{ kv('???') }}"
|
||||
""");
|
||||
|
||||
Map<String, Object> variables = new RunVariables.DefaultBuilder()
|
||||
.withFlow(flow)
|
||||
.withExecution(Execution.builder().id(IdUtils.create()).build())
|
||||
.build(new RunContextLogger(), PropertyContext.create(renderer));
|
||||
|
||||
assertThat(variables.get("inputs")).isEqualTo(Map.of("input", "value"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,17 @@ class ChunkFilterTest {
|
||||
}).get();
|
||||
});
|
||||
}
|
||||
@Test
|
||||
void chunkWithIntegerVariable() throws IllegalVariableEvaluationException {
|
||||
// Reproducer for issue: Integer variable causing ClassCastException
|
||||
Map<String, Object> vars = Map.of(
|
||||
"max_items", Integer.valueOf(2),
|
||||
"list", Arrays.asList(1, 2, 3, 4, 5)
|
||||
);
|
||||
|
||||
String render = variableRenderer.render("{{ list | chunk(max_items) }}", vars);
|
||||
|
||||
assertThat(render).isEqualTo("[[1,2],[3,4],[5]]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,10 +14,14 @@ public class FunctionTestUtils {
|
||||
}
|
||||
|
||||
public static Map<String, Object> getVariables(String namespace) {
|
||||
return FunctionTestUtils.getVariables(MAIN_TENANT, namespace);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getVariables(String tenant, String namespace) {
|
||||
return Map.of(
|
||||
"flow", Map.of(
|
||||
"id", "kv",
|
||||
"tenantId", MAIN_TENANT,
|
||||
"tenantId", tenant,
|
||||
"namespace", namespace)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import static io.kestra.core.runners.pebble.functions.FunctionTestUtils.getVariables;
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.runners.VariableRenderer;
|
||||
import io.kestra.core.storages.StorageContext;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.storages.kv.InternalKVStore;
|
||||
import io.kestra.core.storages.kv.KVMetadata;
|
||||
import io.kestra.core.storages.kv.KVStore;
|
||||
import io.kestra.core.storages.kv.KVValueAndMetadata;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.kestra.core.runners.pebble.functions.FunctionTestUtils.getVariables;
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@KestraTest(startRunner = true)
|
||||
public class KvFunctionTest {
|
||||
|
||||
@@ -107,6 +112,25 @@ public class KvFunctionTest {
|
||||
assertThat(rendered).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowOrGetEmptyIfExpiredDependingOnErrorOnMissing() throws IOException, IllegalVariableEvaluationException {
|
||||
String tenant = TestsUtils.randomTenant();
|
||||
String namespace = TestsUtils.randomNamespace();
|
||||
Map<String, Object> variables = getVariables(tenant, namespace);
|
||||
|
||||
KVStore kv = new InternalKVStore(tenant, namespace, storageInterface);
|
||||
kv.put("my-expired-key", new KVValueAndMetadata(new KVMetadata(null, Instant.now().minus(1, ChronoUnit.HOURS)), "anyValue"));
|
||||
|
||||
String rendered = variableRenderer.render("{{ kv('my-expired-key', errorOnMissing=false) }}", variables);
|
||||
assertThat(rendered).isEqualTo("");
|
||||
|
||||
kv.put("another-expired-key", new KVValueAndMetadata(new KVMetadata(null, Instant.now().minus(1, ChronoUnit.HOURS)), "anyValue"));
|
||||
|
||||
IllegalVariableEvaluationException exception = Assertions.assertThrows(IllegalVariableEvaluationException.class, () -> variableRenderer.render("{{ kv('another-expired-key') }}", variables));
|
||||
|
||||
assertThat(exception.getMessage()).isEqualTo("io.pebbletemplates.pebble.error.PebbleException: The requested value has expired ({{ kv('another-expired-key') }}:1)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailGivenNonExistingKeyAndErrorOnMissingTrue() {
|
||||
// Given
|
||||
@@ -126,9 +150,7 @@ public class KvFunctionTest {
|
||||
// Given
|
||||
Map<String, Object> variables = getVariables("io.kestra.tests");
|
||||
// When
|
||||
IllegalVariableEvaluationException exception = Assertions.assertThrows(IllegalVariableEvaluationException.class, () -> {
|
||||
variableRenderer.render("{{ kv('my-key') }}", variables);
|
||||
});
|
||||
IllegalVariableEvaluationException exception = Assertions.assertThrows(IllegalVariableEvaluationException.class, () -> variableRenderer.render("{{ kv('my-key') }}", variables));
|
||||
|
||||
// Then
|
||||
assertThat(exception.getMessage()).isEqualTo("io.pebbletemplates.pebble.error.PebbleException: The key 'my-key' does not exist in the namespace 'io.kestra.tests'. ({{ kv('my-key') }}:1)");
|
||||
|
||||
@@ -12,5 +12,7 @@ class FileUtilsTest {
|
||||
assertThat(FileUtils.getExtension("")).isNull();
|
||||
assertThat(FileUtils.getExtension("/file/hello")).isNull();
|
||||
assertThat(FileUtils.getExtension("/file/hello.txt")).isEqualTo(".txt");
|
||||
assertThat(FileUtils.getExtension("/file/hello.file with spaces.txt")).isEqualTo(".txt");
|
||||
assertThat(FileUtils.getExtension("/file/hello.file.with.multiple.dots.txt")).isEqualTo(".txt");
|
||||
}
|
||||
}
|
||||
@@ -231,4 +231,9 @@ public class RetryCaseTest {
|
||||
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.FAILED);
|
||||
}
|
||||
|
||||
public void retryWithFlowableErrors(Execution execution) {
|
||||
assertThat(execution.getState().getCurrent()).isEqualTo(State.Type.SUCCESS);
|
||||
assertThat(execution.getTaskRunList()).hasSize(3);
|
||||
assertThat(execution.getTaskRunList().get(2).attemptNumber()).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +217,25 @@ class DownloadTest {
|
||||
assertThat(output.getUri().toString()).endsWith("filename..jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contentDispositionWithSpaceAfterDot() throws Exception {
|
||||
EmbeddedServer embeddedServer = applicationContext.getBean(EmbeddedServer.class);
|
||||
embeddedServer.start();
|
||||
|
||||
Download task = Download.builder()
|
||||
.id(DownloadTest.class.getSimpleName())
|
||||
.type(DownloadTest.class.getName())
|
||||
.uri(Property.ofValue(embeddedServer.getURI() + "/content-disposition-space-after-dot"))
|
||||
.build();
|
||||
|
||||
RunContext runContext = TestsUtils.mockRunContext(this.runContextFactory, task, ImmutableMap.of());
|
||||
|
||||
Download.Output output = task.run(runContext);
|
||||
|
||||
assertThat(output.getUri().toString()).doesNotContain("/secure-path/");
|
||||
assertThat(output.getUri().toString()).endsWith("file.with+spaces.txt");
|
||||
}
|
||||
|
||||
@Controller()
|
||||
public static class SlackWebController {
|
||||
@Get("500")
|
||||
@@ -257,5 +276,11 @@ class DownloadTest {
|
||||
return HttpResponse.ok("Hello World".getBytes())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"/secure-path/filename..jpg\"");
|
||||
}
|
||||
|
||||
@Get("content-disposition-space-after-dot")
|
||||
public HttpResponse<byte[]> contentDispositionWithSpaceAfterDot() {
|
||||
return HttpResponse.ok("Hello World".getBytes())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"file.with spaces.txt\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
id: retry-with-flowable-errors
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: set_kv
|
||||
type: io.kestra.plugin.core.kv.Set
|
||||
key: "retry_counter_1"
|
||||
value: "1"
|
||||
kvType: NUMBER
|
||||
overwrite: true
|
||||
|
||||
- id: retry_block
|
||||
type: io.kestra.plugin.core.flow.AllowFailure
|
||||
tasks:
|
||||
- id: run_script
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: "{{kv(namespace=flow.namespace, key='retry_counter_1') < 2 ? ko : 'It works'}}"
|
||||
errors:
|
||||
- id: incr_counter
|
||||
type: io.kestra.plugin.core.kv.Set
|
||||
key: retry_counter_1
|
||||
value: "{{ kv(namespace=flow.namespace, key='retry_counter_1') + 1 }}"
|
||||
overwrite: true
|
||||
retry:
|
||||
type: constant
|
||||
behavior: RETRY_FAILED_TASK
|
||||
maxAttempts: 3
|
||||
interval: PT0.5S
|
||||
warningOnRetry: true
|
||||
@@ -1,6 +1,6 @@
|
||||
version=1.0.7
|
||||
version=1.0.8
|
||||
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.priority=low
|
||||
org.gradle.priority=low
|
||||
|
||||
@@ -1204,6 +1204,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
||||
flowTriggerService.withFlowTriggersOnly(allFlows.stream())
|
||||
.filter(f -> ListUtils.emptyOnNull(f.getTrigger().getConditions()).stream().anyMatch(c -> c instanceof MultipleCondition) || f.getTrigger().getPreconditions() != null)
|
||||
.map(f -> new MultipleConditionEvent(f.getFlow(), execution))
|
||||
.distinct() // we can have multiple MultipleConditionEvent if a flow contains multiple triggers as it would lead to multiple FlowWithFlowTrigger
|
||||
.forEach(throwConsumer(multipleCondition -> multipleConditionEventQueue.emit(multipleCondition)));
|
||||
}
|
||||
|
||||
@@ -1270,6 +1271,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
||||
else if (executionDelay.getDelayType().equals(ExecutionDelay.DelayType.RESTART_FAILED_TASK)) {
|
||||
Execution newAttempt = executionService.retryTask(
|
||||
pair.getKey(),
|
||||
findFlow(pair.getKey()),
|
||||
executionDelay.getTaskRunId()
|
||||
);
|
||||
executor = executor.withExecution(newAttempt, "retryFailedTask");
|
||||
|
||||
@@ -137,4 +137,10 @@ public abstract class JdbcRunnerRetryTest {
|
||||
void retryDynamicTask(Execution execution){
|
||||
retryCaseTest.retryDynamicTask(execution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExecuteFlow("flows/valids/retry-with-flowable-errors.yaml")
|
||||
void retryWithFlowableErrors(Execution execution){
|
||||
retryCaseTest.retryWithFlowableErrors(execution);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ dependencies {
|
||||
api platform("dev.langchain4j:langchain4j-community-bom:$langchain4jCommunityVersion")
|
||||
|
||||
constraints {
|
||||
// downgrade to proto 1.3.2-alpha as 1.5.0 needs protobuf 4
|
||||
api("io.opentelemetry.proto:opentelemetry-proto:1.3.2-alpha")
|
||||
// need to force this dep as mysql-connector brings a version incompatible with the Google Cloud libs
|
||||
api("com.google.protobuf:protobuf-java:$protobufVersion")
|
||||
api("com.google.protobuf:protobuf-java-util:$protobufVersion")
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
@@ -37,6 +38,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
|
||||
@@ -50,6 +52,45 @@ abstract public class TestsUtils {
|
||||
queueConsumersCancellations.get().clear();
|
||||
}
|
||||
|
||||
public static String randomNamespace(String... prefix) {
|
||||
return TestsUtils.randomString(prefix);
|
||||
}
|
||||
|
||||
public static String randomTenant(String... prefix) {
|
||||
return TestsUtils.randomString(prefix);
|
||||
}
|
||||
|
||||
private static String[] stackTraceToParts() {
|
||||
// We take the stacktrace from the util caller to troubleshoot more easily
|
||||
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
|
||||
String[] packageSplit = stackTraceElement.getClassName().split("\\.");
|
||||
return new String[]{packageSplit[packageSplit.length - 1].toLowerCase(), stackTraceElement.getMethodName().toLowerCase()};
|
||||
}
|
||||
|
||||
/**
|
||||
* there is at least one bug in {@link io.kestra.cli.services.FileChangedEventListener#getTenantIdFromPath(Path)} forbidding use to use '_' character
|
||||
* @param prefix
|
||||
* @return
|
||||
*/
|
||||
private static String randomString(String... prefix) {
|
||||
if (prefix.length == 0) {
|
||||
prefix = new String[]{String.join("-", stackTraceToParts())};
|
||||
}
|
||||
var tenantRegex = "^[a-z0-9][a-z0-9_-]*";
|
||||
var validTenantPrefixes = Arrays.stream(prefix)
|
||||
.map(s -> s.replace(".", "-"))
|
||||
.map(String::toLowerCase)
|
||||
.peek(p -> {
|
||||
if (!p.matches(tenantRegex)) {
|
||||
throw new IllegalArgumentException("random tenant prefix %s should match tenant regex %s".formatted(p, tenantRegex));
|
||||
}
|
||||
}).toList();
|
||||
String[] parts = Stream
|
||||
.concat(validTenantPrefixes.stream(), Stream.of(IdUtils.create().toLowerCase()))
|
||||
.toArray(String[]::new);
|
||||
return IdUtils.fromPartsAndSeparator('-',parts);
|
||||
}
|
||||
|
||||
public static <T> T map(String path, Class<T> cls) throws IOException {
|
||||
URL resource = TestsUtils.class.getClassLoader().getResource(path);
|
||||
assert resource != null;
|
||||
|
||||
36
ui/package-lock.json
generated
36
ui/package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.255",
|
||||
"@kestra-io/ui-libs": "^0.0.260",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
"@vue-flow/core": "^1.46.2",
|
||||
@@ -3202,9 +3202,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs": {
|
||||
"version": "0.0.255",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.255.tgz",
|
||||
"integrity": "sha512-HzuAzQJOBSmXqKWbi+b0z3PwdRJphtKLBJ8LEbtBYRaIF4SfA9uDndVquOeTM1pORpLmO8Gw51Us4ZcUnABNZw==",
|
||||
"version": "0.0.260",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.260.tgz",
|
||||
"integrity": "sha512-m71BkH1n5PfSdywlg5Gaq9TnFiX67u6SH///JnsQyCTx7u2pLfVav5YDejVNFrPG9qyvJRnEuNSFxmBCMB8L2g==",
|
||||
"dependencies": {
|
||||
"@nuxtjs/mdc": "^0.17.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -4127,13 +4127,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
|
||||
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
|
||||
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.55.0"
|
||||
"playwright": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -17509,13 +17509,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
|
||||
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.55.0"
|
||||
"playwright-core": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -17528,9 +17528,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.55.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
|
||||
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -20783,9 +20783,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.255",
|
||||
"@kestra-io/ui-libs": "^0.0.260",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
"@vue-flow/core": "^1.46.2",
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<Logo class="logo" />
|
||||
</div>
|
||||
|
||||
<el-form @submit.prevent :model="credentials" ref="form">
|
||||
<el-form @submit.prevent :model="credentials" ref="form" :rules="rules" :show-message="false">
|
||||
<input type="hidden" name="from" :value="redirectPath">
|
||||
<el-form-item>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
name="username"
|
||||
size="large"
|
||||
@@ -14,14 +14,18 @@
|
||||
v-model="credentials.username"
|
||||
:placeholder="t('email')"
|
||||
required
|
||||
prop="username"
|
||||
>
|
||||
<template #prepend>
|
||||
<Account />
|
||||
</template>
|
||||
<template #suffix v-if="getFieldError('username')">
|
||||
<el-tooltip placement="top" :content="getFieldError('username')">
|
||||
<InformationOutline class="validation-icon error" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="credentials.password"
|
||||
size="large"
|
||||
@@ -31,11 +35,15 @@
|
||||
type="password"
|
||||
show-password
|
||||
required
|
||||
prop="password"
|
||||
>
|
||||
<template #prepend>
|
||||
<Lock />
|
||||
</template>
|
||||
<template #suffix v-if="getFieldError('password')">
|
||||
<el-tooltip placement="top" :content="getFieldError('password')">
|
||||
<InformationOutline class="validation-icon error" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -44,7 +52,7 @@
|
||||
class="w-100"
|
||||
size="large"
|
||||
native-type="submit"
|
||||
@click="handleSubmit"
|
||||
@click.prevent="handleSubmit"
|
||||
:disabled="isLoginDisabled"
|
||||
:loading="isLoading"
|
||||
>
|
||||
@@ -73,9 +81,11 @@
|
||||
import {ElMessage} from "element-plus"
|
||||
import type {FormInstance} from "element-plus"
|
||||
import axios from "axios"
|
||||
import MailChecker from "mailchecker"
|
||||
|
||||
import Account from "vue-material-design-icons/Account.vue"
|
||||
import Lock from "vue-material-design-icons/Lock.vue"
|
||||
import InformationOutline from "vue-material-design-icons/InformationOutline.vue"
|
||||
import Logo from "../home/Logo.vue"
|
||||
|
||||
import {useCoreStore} from "../../stores/core"
|
||||
@@ -104,11 +114,47 @@
|
||||
password: ""
|
||||
})
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const PASSWORD_REGEX = /^(?=.*[A-Z])(?=.*\d)\S{8,}$/
|
||||
|
||||
const validateEmail = (_rule: any, value: string, callback: (error?: Error) => void) => {
|
||||
if (!value?.trim()) {
|
||||
return callback(new Error(t("setup.validation.email_required")));
|
||||
} else if (!EMAIL_REGEX.test(value)) {
|
||||
return callback(new Error(t("setup.validation.email_invalid")));
|
||||
} else if (!MailChecker.isValid(value)) {
|
||||
return callback(new Error(t("setup.validation.email_temporary_not_allowed")));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const validatePassword = (_rule: any, value: string, callback: (error?: Error) => void) => {
|
||||
if (!value || !PASSWORD_REGEX.test(value)) {
|
||||
return callback(new Error(t("setup.validation.password_invalid")));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const rules = computed(() => ({
|
||||
username: [{required: true, validator: validateEmail, trigger: "blur"}],
|
||||
password: [{required: true, validator: validatePassword, trigger: "blur"}]
|
||||
}))
|
||||
|
||||
const getFieldError = (fieldName: string) => {
|
||||
if (!form.value) return null
|
||||
const field = form.value.fields?.find((f: any) => f.prop === fieldName)
|
||||
return field?.validateState === "error" ? field.validateMessage : null
|
||||
}
|
||||
|
||||
const redirectPath = computed(() => (route.query.from as string) ?? "/welcome")
|
||||
|
||||
const isLoginDisabled = computed(() =>
|
||||
!credentials.value.username?.trim() ||
|
||||
!credentials.value.password?.trim() ||
|
||||
!EMAIL_REGEX.test(credentials.value.username) ||
|
||||
!PASSWORD_REGEX.test(credentials.value.password) ||
|
||||
!MailChecker.isValid(credentials.value.username) ||
|
||||
isLoading.value
|
||||
)
|
||||
|
||||
@@ -136,13 +182,13 @@
|
||||
const handleNetworkError = (error: any) => {
|
||||
return error.code === "ERR_NETWORK" ||
|
||||
error.code === "ECONNREFUSED" ||
|
||||
(!error.response && error.message.includes("Network Error"))
|
||||
(!error.response && error.message?.includes("Network Error"))
|
||||
}
|
||||
|
||||
const loadAuthConfigErrors = async (showIncorrectCredsMessage = true) => {
|
||||
const loadAuthConfigErrors = async () => {
|
||||
try {
|
||||
const errors = await miscStore.loadBasicAuthValidationErrors()
|
||||
if (errors && errors.length > 0) {
|
||||
if (errors?.length) {
|
||||
errors.forEach((error: string) => {
|
||||
ElMessage.error({
|
||||
message: `${error}. ${t("setup.validation.config_message")}`,
|
||||
@@ -150,24 +196,23 @@
|
||||
showClose: false
|
||||
})
|
||||
})
|
||||
} else if (showIncorrectCredsMessage) {
|
||||
ElMessage.error(t("setup.validation.incorrect_creds"))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load auth config errors:", error)
|
||||
} catch {
|
||||
ElMessage.error({
|
||||
message: t("setup.validation.incorrect_creds")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
coreStore.error = undefined;
|
||||
event.preventDefault()
|
||||
if (!form.value || isLoading.value) return
|
||||
|
||||
if (!(await form.value.validate().catch(() => false))) return
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
coreStore.error = undefined;
|
||||
if (!form.value || isLoading.value) return
|
||||
|
||||
if (!(await form.value.validate().catch(() => false))) return
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
const {username, password} = credentials.value
|
||||
|
||||
if (!username?.trim() || !password?.trim()) {
|
||||
@@ -203,7 +248,7 @@
|
||||
}
|
||||
|
||||
if (error?.response?.status === 401) {
|
||||
await loadAuthConfigErrors(true)
|
||||
await loadAuthConfigErrors()
|
||||
} else if (error?.response?.status === 404) {
|
||||
router.push({name: "setup"})
|
||||
} else {
|
||||
@@ -255,6 +300,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.validation-icon {
|
||||
font-size: 1.25em;
|
||||
&.error {
|
||||
color: var(--ks-content-alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,37 +7,67 @@
|
||||
:closable="false"
|
||||
class="mb-2"
|
||||
/>
|
||||
<el-row v-for="(item, index) in currentValue" :key="index" :gutter="10" class="w-100" :data-testid="`task-dict-item-${item[0]}-${index}`">
|
||||
<el-col :span="6">
|
||||
<InputText
|
||||
:model-value="item[0]"
|
||||
@update:model-value="onKey(index, $event)"
|
||||
margin="m-0"
|
||||
placeholder="Key"
|
||||
:have-error="duplicatedKeys.includes(item[0])"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<component
|
||||
:is="schema.additionalProperties ? getTaskComponent(schema.additionalProperties) : TaskExpression"
|
||||
:model-value="item[1]"
|
||||
@update:model-value="onValueChange(index, $event)"
|
||||
:root="getKey(item[0])"
|
||||
:schema="schema.additionalProperties"
|
||||
:required="isRequired(item[0])"
|
||||
:definitions="definitions"
|
||||
:disabled
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="2" class="col align-self-center delete">
|
||||
<DeleteOutline @click="removeItem(index)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<Add v-if="!disabledAdding" @add="addItem()" />
|
||||
<template v-if="componentType">
|
||||
<Wrapper v-for="(item, index) in currentValue" :key="index" class="item-wrapper">
|
||||
<template #tasks>
|
||||
<InputText
|
||||
:model-value="item[0]"
|
||||
@update:model-value="onKey(index, $event)"
|
||||
margin="m-0"
|
||||
placeholder="Key"
|
||||
:have-error="duplicatedKeys.includes(item[0])"
|
||||
/>
|
||||
<hr>
|
||||
<component
|
||||
ref="valueComponent"
|
||||
:is="componentType"
|
||||
:model-value="item[1]"
|
||||
@update:model-value="onValueChange(index, $event)"
|
||||
:root="getKey(item[0])"
|
||||
:schema="schema.additionalProperties"
|
||||
:required="isRequired(item[0])"
|
||||
:disabled
|
||||
merge
|
||||
/>
|
||||
<div class="delete-container">
|
||||
<button @click="removeItem(index)" class="remove-entry">
|
||||
{{ te(`no_code.remove.${root}`) ? t(`no_code.remove.${root}`) : t('no_code.remove.default') }} <DeleteOutline />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Wrapper>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-row v-for="(item, index) in currentValue" :key="index" :gutter="10" class="w-100" :data-testid="`task-dict-item-${item[0]}-${index}`">
|
||||
<el-col :span="6">
|
||||
<InputText
|
||||
:model-value="item[0]"
|
||||
@update:model-value="onKey(index, $event)"
|
||||
margin="m-0"
|
||||
placeholder="Key"
|
||||
:have-error="duplicatedKeys.includes(item[0])"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<TaskExpression
|
||||
:model-value="item[1]"
|
||||
@update:model-value="onValueChange(index, $event)"
|
||||
:root="getKey(item[0])"
|
||||
:schema="schema.additionalProperties"
|
||||
:required="isRequired(item[0])"
|
||||
:disabled
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="2" class="col align-self-center delete">
|
||||
<DeleteOutline @click="removeItem(index)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<Add v-if="!props.disabled" :disabled="addButtonDisabled" @add="addItem()" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref, watch} from "vue";
|
||||
<script setup lang="ts">
|
||||
import {computed, ref, useTemplateRef, watch} from "vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {DeleteOutline} from "../../utils/icons";
|
||||
|
||||
@@ -46,35 +76,32 @@
|
||||
import Add from "../Add.vue";
|
||||
import getTaskComponent from "./getTaskComponent";
|
||||
import debounce from "lodash/debounce";
|
||||
import Wrapper from "./Wrapper.vue";
|
||||
|
||||
const {t} = useI18n();
|
||||
const {t, te} = useI18n();
|
||||
|
||||
defineOptions({
|
||||
name: "TaskDict",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
schema: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
definitions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
root: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
const valueComponent = useTemplateRef<any[]>("valueComponent");
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: Record<string, any>;
|
||||
schema?: any;
|
||||
root?: string;
|
||||
disabled?: boolean;
|
||||
definitions?: Record<string, any>;
|
||||
}>(), {
|
||||
disabled: false,
|
||||
modelValue: () => ({}),
|
||||
root: undefined,
|
||||
schema: () => ({type: "object"}),
|
||||
definitions: () => ({}),
|
||||
});
|
||||
|
||||
const componentType = computed(() => {
|
||||
return props.schema.additionalProperties ? getTaskComponent(props.schema.additionalProperties, props.root, props.definitions) : null;
|
||||
});
|
||||
|
||||
const currentValue = ref<[string, any][]>([])
|
||||
@@ -143,15 +170,50 @@
|
||||
}
|
||||
|
||||
function addItem() {
|
||||
if(addButtonDisabled.value) {
|
||||
return;
|
||||
}
|
||||
currentValue.value.push(["", undefined]);
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
const disabledAdding = computed(() => {
|
||||
return props.disabled || currentValue.value.at(-1)?.[0] === "" && currentValue.value.at(-1)?.[1] === undefined;
|
||||
const addButtonDisabled = computed(() => {
|
||||
return currentValue.value.at(-1)?.[0] === "" && currentValue.value.at(-1)?.[1] === undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../styles/code.scss";
|
||||
</style>
|
||||
|
||||
.task-container{
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.delete-container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 1rem;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.remove-entry{
|
||||
color: var(--ks-content-secondary);
|
||||
background-color: var(--ks-button-background-secondary);
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
opacity: 0.7;
|
||||
padding: 0;
|
||||
height: .75rem;
|
||||
&:hover {
|
||||
color: var(--ks-content-secondary);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
margin: .25rem 0;
|
||||
background-color: var(--ks-background-card);
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,81 @@
|
||||
<template>
|
||||
<div class="tasks-wrapper">
|
||||
<Collapse
|
||||
:title="root"
|
||||
:elements="items"
|
||||
:section
|
||||
:block-schema-path="[blockSchemaPath, 'properties', root, 'items'].join('/')"
|
||||
@remove="removeItem"
|
||||
@reorder="(yaml) => flowStore.flowYaml = yaml"
|
||||
/>
|
||||
<el-collapse v-model="expanded" class="collapse">
|
||||
<el-collapse-item
|
||||
:name="section"
|
||||
:title="`${section}${elements ? ` (${elements.length})` : ''}`"
|
||||
:disabled="merge"
|
||||
:class="{merge}"
|
||||
>
|
||||
<template #icon>
|
||||
<Creation
|
||||
:parent-path-complete
|
||||
:ref-path="elements?.length ? elements.length - 1 : undefined"
|
||||
:block-schema-path
|
||||
/>
|
||||
</template>
|
||||
|
||||
<Element
|
||||
v-for="(element, elementIndex) in filteredElements"
|
||||
:key="elementIndex"
|
||||
:section
|
||||
:parent-path-complete
|
||||
:element
|
||||
:element-index
|
||||
:moved="elementIndex == movedIndex"
|
||||
:block-schema-path
|
||||
:type-field-schema
|
||||
@remove-element="removeElement(elementIndex)"
|
||||
@move-element="
|
||||
(direction: 'up' | 'down') =>
|
||||
moveElement(
|
||||
elements,
|
||||
element.id,
|
||||
elementIndex,
|
||||
direction,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, inject, ref} from "vue";
|
||||
import Collapse from "../collapse/Collapse.vue";
|
||||
import {BLOCK_SCHEMA_PATH_INJECTION_KEY} from "../../injectionKeys";
|
||||
import {useFlowStore} from "../../../../stores/flow";
|
||||
import * as YAML_UTILS from "@kestra-io/ui-libs/flow-yaml-utils";
|
||||
import {CollapseItem} from "../../utils/types";
|
||||
import {
|
||||
CREATING_TASK_INJECTION_KEY, FULL_SCHEMA_INJECTION_KEY, FULL_SOURCE_INJECTION_KEY,
|
||||
PARENT_PATH_INJECTION_KEY, REF_PATH_INJECTION_KEY,
|
||||
} from "../../injectionKeys";
|
||||
import {SECTIONS_MAP} from "../../../../utils/constants";
|
||||
import {getValueAtJsonPath} from "../../../../utils/utils";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import Creation from "../collapse/buttons/Creation.vue";
|
||||
import Element from "../collapse/Element.vue";
|
||||
|
||||
const blockSchemaPath = inject(BLOCK_SCHEMA_PATH_INJECTION_KEY, ref(""))
|
||||
|
||||
const blockSchemaPathInjected = inject(BLOCK_SCHEMA_PATH_INJECTION_KEY, ref(""))
|
||||
|
||||
const schemaAtBlockPathInjected = computed(() => getValueAtJsonPath(fullSchema.value, blockSchemaPathInjected.value))
|
||||
|
||||
const blockSchemaPath = computed(() => {
|
||||
const rootParts = props.root ? props.root.split(".") : []
|
||||
if(rootParts.length > 1){
|
||||
// if second part is a property not defined in properties,
|
||||
// it can only be defined by additionalProperties
|
||||
const s = schemaAtBlockPathInjected.value?.properties?.[rootParts[0]]
|
||||
if(s && s.properties?.[rootParts[1]] === undefined && s.additionalProperties){
|
||||
rootParts[1] = "additionalProperties"
|
||||
} else {
|
||||
rootParts.splice(1, 0, "properties")
|
||||
}
|
||||
}
|
||||
return [blockSchemaPathInjected.value, "properties", ...rootParts, "items"].join("/");
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
@@ -34,36 +92,111 @@
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: Task[],
|
||||
root?: string;
|
||||
merge?: boolean;
|
||||
}>(), {
|
||||
modelValue: () => [],
|
||||
root: undefined
|
||||
root: undefined,
|
||||
merge: false,
|
||||
});
|
||||
|
||||
const items = computed(() =>
|
||||
const elements = computed(() =>
|
||||
!Array.isArray(props.modelValue) ? [props.modelValue] : props.modelValue,
|
||||
);
|
||||
|
||||
function removeItem(yaml: string, index: number){
|
||||
flowStore.flowYaml = yaml;
|
||||
if(items.value.length <= 1 && index === 0){
|
||||
function removeElement(index: number){
|
||||
if(elements.value.length <= 1){
|
||||
emits("update:modelValue", undefined);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let localItems = [...items.value]
|
||||
let localItems = [...elements.value]
|
||||
localItems.splice(index, 1)
|
||||
|
||||
emits("update:modelValue", localItems);
|
||||
};
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
const section = computed(() => {
|
||||
return props.root ?? "tasks";
|
||||
if(props.merge){
|
||||
return t("tasks");
|
||||
}
|
||||
return props.root ?? t("tasks");
|
||||
});
|
||||
|
||||
const flow = inject(FULL_SOURCE_INJECTION_KEY, ref(""));
|
||||
|
||||
const filteredElements = computed(() => elements.value?.filter(Boolean) ?? []);
|
||||
const expanded = props.merge ? computed(() => section.value) : ref<CollapseItem["title"]>(props.root ?? "tasks");
|
||||
|
||||
const parentPath = inject(PARENT_PATH_INJECTION_KEY, "");
|
||||
const refPath = inject(REF_PATH_INJECTION_KEY, undefined);
|
||||
const creatingTask = inject(CREATING_TASK_INJECTION_KEY, false);
|
||||
|
||||
const parentPathComplete = computed(() => {
|
||||
return `${[
|
||||
[
|
||||
parentPath,
|
||||
creatingTask && refPath !== undefined
|
||||
? `[${refPath + 1}]`
|
||||
: refPath !== undefined
|
||||
? `[${refPath}]`
|
||||
: undefined,
|
||||
].filter(Boolean).join(""),
|
||||
section.value
|
||||
].filter(p => p.length).join(".")}`;
|
||||
});
|
||||
|
||||
const movedIndex = ref(-1);
|
||||
|
||||
const moveElement = (
|
||||
items: Record<string, any>[] | undefined,
|
||||
elementID: string,
|
||||
index: number,
|
||||
direction: "up" | "down",
|
||||
) => {
|
||||
const keyName = section.value === "Plugin Defaults" ? "type" : "id";
|
||||
if (!items || !flow) return;
|
||||
if (
|
||||
(direction === "up" && index === 0) ||
|
||||
(direction === "down" && index === items.length - 1)
|
||||
)
|
||||
return;
|
||||
|
||||
const newIndex = direction === "up" ? index - 1 : index + 1;
|
||||
|
||||
movedIndex.value = newIndex;
|
||||
setTimeout(() => {
|
||||
movedIndex.value = -1;
|
||||
}, 200);
|
||||
|
||||
flowStore.flowYaml =
|
||||
YAML_UTILS.swapBlocks({
|
||||
source:flow.value,
|
||||
section: SECTIONS_MAP[section.value.toLowerCase() as keyof typeof SECTIONS_MAP],
|
||||
key1:elementID,
|
||||
key2:items[newIndex][keyName],
|
||||
keyName,
|
||||
})
|
||||
};
|
||||
|
||||
const fullSchema = inject(FULL_SCHEMA_INJECTION_KEY, ref<Record<string, any>>({}));
|
||||
|
||||
const blockSchema = computed(() => getValueAtJsonPath(fullSchema.value, blockSchemaPath.value) ?? {});
|
||||
|
||||
// resolve parentPathComplete field schema from pluginsStore
|
||||
const typeFieldSchema = computed(() => blockSchema.value?.type ? "type" : blockSchema.value?.on ? "on" : "type");
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../styles/code.scss";
|
||||
|
||||
.list-header{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
gap: 1rem;
|
||||
}
|
||||
.tasks-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -73,4 +206,8 @@
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
.merge :deep(.el-collapse-item__header){
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
import {pascalCase} from "change-case";
|
||||
import {resolve$ref} from "../../../../utils/utils";
|
||||
|
||||
const TasksComponents = import.meta.glob<{ default: any }>("./Task*.vue", {eager: true});
|
||||
|
||||
@@ -70,7 +71,8 @@ function getType(property: any, key?: string, schema?: any): string {
|
||||
}
|
||||
|
||||
if (property.type === "array") {
|
||||
if (property.items?.anyOf?.length === 0 || property.items?.anyOf?.length > 10 || key === "pluginDefaults" || key === "layout") {
|
||||
const items = schema ? resolve$ref({definitions: schema}, property.items) : property.items;
|
||||
if (items?.anyOf?.length === 0 || items?.anyOf?.length > 10 || key === "pluginDefaults" || key === "layout") {
|
||||
return "list";
|
||||
}
|
||||
|
||||
|
||||
@@ -475,7 +475,7 @@
|
||||
return this.namespacesStore
|
||||
.createKv({
|
||||
...this.kv,
|
||||
contentType: ["DATE", "DATETIME"].includes(type) ? "text/plain" : "application/json",
|
||||
contentType: "text/plain",
|
||||
value
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -36,7 +36,7 @@ function statsGlobalData(config: Config, uid: string): any {
|
||||
|
||||
export async function initPostHogForSetup(config: Config): Promise<void> {
|
||||
try {
|
||||
if (!config.isUiAnonymousUsageEnabled) return
|
||||
if (!config.isUiAnonymousUsageEnabled || import.meta.env.MODE === "development") return
|
||||
|
||||
const apiStore = useApiStore()
|
||||
const apiConfig = await apiStore.loadConfig()
|
||||
|
||||
@@ -1081,7 +1081,7 @@
|
||||
"cron": "Cron",
|
||||
"execution_failed": "Execution failed! Last error was",
|
||||
"execution restarted": "This execution has been restarted {nbRestart} time(s).",
|
||||
"execution replay": "This execution is a replay of <code>{originalId}</code>.",
|
||||
"execution replay": "This execution is a replay of <code>{originalId}</code>.",
|
||||
"execution replayed": "This execution has been replayed.",
|
||||
"task run id": "TaskRun ID",
|
||||
"active": "Active",
|
||||
@@ -1201,6 +1201,7 @@
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"default": "Select a type",
|
||||
"task": "Select a task",
|
||||
"tasks": "Select a task",
|
||||
"triggers": "Select a trigger",
|
||||
@@ -1212,6 +1213,7 @@
|
||||
"inputs": "Select an input field type"
|
||||
},
|
||||
"creation": {
|
||||
"default": "Add",
|
||||
"tasks": "Add a task",
|
||||
"triggers": "Add a trigger",
|
||||
"errors": "Add an error handler",
|
||||
@@ -1230,6 +1232,10 @@
|
||||
"input": "Close input",
|
||||
"pluginDefaults": "Close plugin default",
|
||||
"conditions": "Close condition"
|
||||
},
|
||||
"remove": {
|
||||
"default": "Remove this entry",
|
||||
"cases": "Remove this case"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -71,6 +71,9 @@ import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -121,7 +124,7 @@ public class ExecutionController {
|
||||
@Nullable
|
||||
@Value("${micronaut.server.context-path}")
|
||||
protected String basePath;
|
||||
|
||||
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepository;
|
||||
|
||||
@@ -168,7 +171,7 @@ public class ExecutionController {
|
||||
|
||||
@Inject
|
||||
private ApplicationEventPublisher<CrudEvent<Execution>> eventPublisher;
|
||||
|
||||
|
||||
@Inject
|
||||
private RunContextFactory runContextFactory;
|
||||
|
||||
@@ -186,7 +189,7 @@ public class ExecutionController {
|
||||
|
||||
@Inject
|
||||
private Optional<OpenTelemetry> openTelemetry;
|
||||
|
||||
|
||||
@Inject
|
||||
private ExecutionStreamingService executionStreamingService;
|
||||
|
||||
@@ -206,7 +209,7 @@ public class ExecutionController {
|
||||
@Parameter(description = "The current page") @QueryValue(defaultValue = "1") @Min(1) int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(defaultValue = "10") @Min(1) int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
//Deprecated params
|
||||
@Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@@ -355,9 +358,9 @@ public class ExecutionController {
|
||||
@ApiResponse(responseCode = "204", description = "On success")
|
||||
public HttpResponse<Void> deleteExecution(
|
||||
@Parameter(description = "The execution id") @PathVariable String executionId,
|
||||
@Parameter(description = "Whether to delete execution logs") @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics") @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage") @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
@Parameter(description = "Whether to delete execution logs", required = false) @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics", required = false) @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage", required = false) @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
) throws IOException {
|
||||
Optional<Execution> execution = executionRepository.findById(tenantService.resolveTenant(), executionId);
|
||||
if (execution.isPresent()) {
|
||||
@@ -376,9 +379,9 @@ public class ExecutionController {
|
||||
public MutableHttpResponse<?> deleteExecutionsByIds(
|
||||
@RequestBody(description = "The execution id") @Body List<String> executionsId,
|
||||
@Parameter(description = "Whether to delete non-terminated executions") @Nullable @QueryValue(defaultValue = "false") Boolean includeNonTerminated,
|
||||
@Parameter(description = "Whether to delete execution logs") @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics") @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage") @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
@Parameter(description = "Whether to delete execution logs", required = false) @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics", required = false) @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage", required = false) @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
) throws IOException {
|
||||
List<Execution> executions = new ArrayList<>();
|
||||
Set<ManualConstraintViolation<String>> invalids = new HashSet<>();
|
||||
@@ -417,27 +420,27 @@ public class ExecutionController {
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Operation(tags = {"Executions"}, summary = "Delete executions filter by query parameters")
|
||||
public HttpResponse<?> deleteExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
|
||||
@Parameter(description = "Whether to delete non-terminated executions") @Nullable @QueryValue(defaultValue = "false") Boolean includeNonTerminated,
|
||||
@Parameter(description = "Whether to delete execution logs") @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics") @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage") @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
@Parameter(description = "Whether to delete execution logs", required = false) @QueryValue(defaultValue = "true") Boolean deleteLogs,
|
||||
@Parameter(description = "Whether to delete execution metrics", required = false) @QueryValue(defaultValue = "true") Boolean deleteMetrics,
|
||||
@Parameter(description = "Whether to delete execution files in the internal storage", required = false) @QueryValue(defaultValue = "true") Boolean deleteStorage
|
||||
) throws IOException {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -666,7 +669,16 @@ public class ExecutionController {
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Post(uri = "/{namespace}/{id}", consumes = MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(tags = {"Executions"}, summary = "Create a new execution for a flow")
|
||||
@Operation(
|
||||
tags = {"Executions"},
|
||||
summary = "Create a new execution for a flow",
|
||||
extensions = @Extension(
|
||||
name = "x-sdk-customization",
|
||||
properties = {
|
||||
@ExtensionProperty(name = "x-multipart", value = "true")
|
||||
}
|
||||
)
|
||||
)
|
||||
@ApiResponse(responseCode = "409", description = "if the flow is disabled")
|
||||
@ApiResponse(responseCode = "200", description = "On execution created", content = {@Content(schema = @Schema(implementation = ExecutionResponse.class))})
|
||||
@SingleResult
|
||||
@@ -996,22 +1008,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/restart/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Restart executions filter by query parameters")
|
||||
public HttpResponse<?> restartExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
) throws Exception {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -1056,13 +1068,32 @@ public class ExecutionController {
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Post(uri = "/{executionId}/replay-with-inputs", consumes = MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(tags = {"Executions"}, summary = "Create a new execution from an old one and start it from a specified task run id")
|
||||
@Operation(
|
||||
tags = {"Executions"},
|
||||
summary = "Create a new execution from an old one and start it from a specified task run id",
|
||||
extensions = @Extension(
|
||||
name = "x-sdk-customization",
|
||||
properties = {
|
||||
@ExtensionProperty(name = "x-multipart", value = "true")
|
||||
}
|
||||
)
|
||||
)
|
||||
public Mono<Execution> replayExecutionWithinputs(
|
||||
@Parameter(description = "the original execution id to clone") @PathVariable String executionId,
|
||||
@Parameter(description = "The taskrun id") @Nullable @QueryValue String taskRunId,
|
||||
@Parameter(description = "The flow revision to use for new execution") @Nullable @QueryValue Integer revision,
|
||||
@Parameter(description = "Set a list of breakpoints at specific tasks 'id.value', separated by a coma.") @QueryValue Optional<String> breakpoints,
|
||||
@RequestBody(description = "The inputs") @Body MultipartBody inputs
|
||||
@RequestBody(
|
||||
description = "The inputs (multipart map)",
|
||||
content = @Content(
|
||||
mediaType = MediaType.MULTIPART_FORM_DATA,
|
||||
schema = @Schema(
|
||||
type = "object",
|
||||
additionalProperties = Schema.AdditionalPropertiesValue.TRUE,
|
||||
additionalPropertiesSchema = Object.class
|
||||
)
|
||||
)
|
||||
) @Body MultipartBody inputs
|
||||
) {
|
||||
Optional<Execution> execution = executionRepository.findById(tenantService.resolveTenant(), executionId);
|
||||
if (execution.isEmpty()) {
|
||||
@@ -1239,22 +1270,22 @@ public class ExecutionController {
|
||||
@ApiResponse(responseCode = "200", description = "On success", content = {@Content(schema = @Schema(implementation = BulkResponse.class))})
|
||||
@ApiResponse(responseCode = "422", description = "Changed state with errors", content = {@Content(schema = @Schema(implementation = BulkErrorResponse.class))})
|
||||
public HttpResponse<?> updateExecutionsStatusByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Parameter(description = "The new state of the executions") @NotNull @QueryValue State.Type newStatus
|
||||
) throws QueueException {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
@@ -1302,7 +1333,7 @@ public class ExecutionController {
|
||||
if (execution.getState().isTerminated() && !isOnKillCascade) {
|
||||
throw new IllegalStateException("Execution is already finished, can't kill it");
|
||||
}
|
||||
|
||||
|
||||
eventPublisher.publishEvent(CrudEvent.of(execution, execution.withState(State.Type.KILLING)));
|
||||
killQueue.emit(ExecutionKilledExecution
|
||||
.builder()
|
||||
@@ -1512,22 +1543,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/resume/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Resume executions filter by query parameters")
|
||||
public HttpResponse<?> resumeExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
) throws Exception {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -1621,22 +1652,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/pause/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Pause executions filter by query parameters")
|
||||
public HttpResponse<?> pauseExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
) throws Exception {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -1665,22 +1696,22 @@ public class ExecutionController {
|
||||
@Delete(uri = "/kill/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Kill executions filter by query parameters")
|
||||
public HttpResponse<?> killExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
) throws QueueException {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -1709,22 +1740,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/replay/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Create new executions from old ones filter by query parameters. Keep the flow revision")
|
||||
public HttpResponse<?> replayExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
|
||||
@Parameter(description = "If latest revision should be used") @Nullable @QueryValue(defaultValue = "false") Boolean latestRevision
|
||||
) throws Exception {
|
||||
@@ -1800,7 +1831,17 @@ public class ExecutionController {
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Get(uri = "/{executionId}/follow", produces = MediaType.TEXT_EVENT_STREAM)
|
||||
@Operation(tags = {"Executions"}, summary = "Follow an execution")
|
||||
@Operation(
|
||||
tags = {"Executions"},
|
||||
summary = "Follow an execution",
|
||||
extensions = @Extension(
|
||||
name = "x-sdk-customization",
|
||||
properties = {
|
||||
@ExtensionProperty(name = "x-replace-follow-execution", value = "true"),
|
||||
@ExtensionProperty(name = "x-skipped", value = "true")
|
||||
}
|
||||
)
|
||||
)
|
||||
public Flux<Event<Execution>> followExecution(
|
||||
@Parameter(description = "The execution id") @PathVariable String executionId
|
||||
) {
|
||||
@@ -2017,22 +2058,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/labels/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Set label on executions filter by query parameters")
|
||||
public HttpResponse<?> setLabelsOnTerminatedExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
|
||||
@RequestBody(description = "The labels to add to the execution") @Body @NotNull @Valid List<Label> setLabels
|
||||
) {
|
||||
@@ -2134,22 +2175,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/unqueue/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Unqueue executions filter by query parameters")
|
||||
public HttpResponse<?> unqueueExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter,
|
||||
@Parameter(description = "The new state of the unqueued executions") @Nullable @QueryValue State.Type newState
|
||||
) throws Exception {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
@@ -2248,22 +2289,22 @@ public class ExecutionController {
|
||||
@Post(uri = "/force-run/by-query")
|
||||
@Operation(tags = {"Executions"}, summary = "Force run executions filter by query parameters")
|
||||
public HttpResponse<?> forceRunExecutionsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include") @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter") @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime") @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", examples = {
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the executions to include", deprecated = true) @Nullable @QueryValue(value = "scope") List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A flow id filter", deprecated = true) @Nullable @QueryValue String flowId,
|
||||
@Deprecated @Parameter(description = "The start datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime startDate,
|
||||
@Deprecated @Parameter(description = "The end datetime", deprecated = true) @Nullable @Format("yyyy-MM-dd'T'HH:mm[:ss][.SSS][XXX]") @QueryValue ZonedDateTime endDate,
|
||||
@Deprecated @Parameter(description = "A time range filter relative to the current time", deprecated = true, examples = {
|
||||
@ExampleObject(name = "Filter last 5 minutes", value = "PT5M"),
|
||||
@ExampleObject(name = "Filter last 24 hours", value = "P1D")
|
||||
}) @Nullable @QueryValue Duration timeRange,
|
||||
@Deprecated @Parameter(description = "A state filter") @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id") @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter") @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
@Deprecated @Parameter(description = "A state filter", deprecated = true) @Nullable @QueryValue List<State.Type> state,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels,
|
||||
@Deprecated @Parameter(description = "The trigger execution id", deprecated = true) @Nullable @QueryValue String triggerExecutionId,
|
||||
@Deprecated @Parameter(description = "A execution child filter", deprecated = true) @Nullable @QueryValue ExecutionRepositoryInterface.ChildFilter childFilter
|
||||
) throws Exception {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -2338,7 +2379,17 @@ public class ExecutionController {
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Get(uri = "/{executionId}/follow-dependencies", produces = MediaType.TEXT_EVENT_STREAM)
|
||||
@Operation(tags = {"Executions"}, summary = "Follow all execution dependencies executions")
|
||||
@Operation(
|
||||
tags = {"Executions"},
|
||||
summary = "Follow all execution dependencies executions",
|
||||
extensions = @Extension(
|
||||
name = "x-sdk-customization",
|
||||
properties = {
|
||||
@ExtensionProperty(name = "x-replace-follow-dependencies-execution", value = "true"),
|
||||
@ExtensionProperty(name = "x-skipped", value = "true")
|
||||
}
|
||||
)
|
||||
)
|
||||
public Flux<Event<ExecutionStatusEvent>> followDependenciesExecutions(
|
||||
@Parameter(description = "The execution id") @PathVariable String executionId,
|
||||
@Parameter(description = "If true, list only destination dependencies, otherwise list also source dependencies") @QueryValue(defaultValue = "false") boolean destinationOnly,
|
||||
|
||||
@@ -46,6 +46,7 @@ import io.micronaut.validation.Validated;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
@@ -222,7 +223,7 @@ public class FlowController {
|
||||
@Parameter(description = "The current page") @QueryValue(defaultValue = "1") @Min(1) int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(defaultValue = "10") @Min(1) int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
|
||||
@Parameter(description = "Filters") @QueryFilterFormat() List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat() List<QueryFilter> filters,
|
||||
// Deprecated params
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include", deprecated = true) @Nullable @QueryValue List<FlowScope> scope,
|
||||
@@ -277,7 +278,7 @@ public class FlowController {
|
||||
*/
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Post(consumes = MediaType.ALL)
|
||||
@Operation(tags = {"Flows"}, summary = "Create a flow from json object", deprecated = true)
|
||||
@Operation(tags = {"Flows"}, summary = "Create a flow from json object", deprecated = true, hidden = true)
|
||||
@Deprecated(forRemoval = true, since = "0.18")
|
||||
@Hidden // we hide it otherwise this is the one that will be included in the OpenAPI spec instead of the YAML one.
|
||||
public HttpResponse<Flow> createFlowFromJson(
|
||||
@@ -334,7 +335,8 @@ public class FlowController {
|
||||
summary = "Update a complete namespace from json object",
|
||||
description = "All flow will be created / updated for this namespace.\n" +
|
||||
"Flow that already created but not in `flows` will be deleted if the query delete is `true`",
|
||||
deprecated = true
|
||||
deprecated = true,
|
||||
hidden = true
|
||||
)
|
||||
@Deprecated(forRemoval = true, since = "0.18")
|
||||
@Hidden // we hide it otherwise this is the one that will be included in the OpenAPI spec instead of the YAML one.
|
||||
@@ -437,7 +439,7 @@ public class FlowController {
|
||||
|
||||
@Put(uri = "{namespace}/{id}", consumes = MediaType.APPLICATION_YAML)
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Operation(tags = {"Flows"}, summary = "Update a flow")
|
||||
@Operation(tags = {"Flows"}, summary = "Update a flow")// force deprecated = false otherwise it is marked as deprecated, dont know why
|
||||
@ApiResponse(responseCode = "200", description = "On success", content = {@Content(schema = @Schema(implementation = FlowWithSource.class))})
|
||||
public HttpResponse<FlowWithSource> updateFlow(
|
||||
@Parameter(description = "The flow namespace") @PathVariable String namespace,
|
||||
@@ -476,9 +478,9 @@ public class FlowController {
|
||||
/**
|
||||
* @deprecated use {@link #updateFlow(String, String, String)} instead
|
||||
*/
|
||||
@Put(uri = "{namespace}/{id}", consumes = MediaType.ALL)
|
||||
@Put(uri = "{namespace}/{id}", consumes = MediaType.APPLICATION_JSON)
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Operation(tags = {"Flows"}, summary = "Update a flow", deprecated = true)
|
||||
@Operation(tags = {"Flows"}, operationId = "updateFlowFromJson", summary = "Update a flow", deprecated = true, hidden = true)
|
||||
@Deprecated(forRemoval = true, since = "0.18")
|
||||
@Hidden // we hide it otherwise this is the one that will be included in the OpenAPI spec instead of the JSON one.
|
||||
public HttpResponse<Flow> updateFlowFromJson(
|
||||
@@ -666,7 +668,7 @@ public class FlowController {
|
||||
@Post(uri = "/validate/task", consumes = MediaType.APPLICATION_YAML)
|
||||
@Operation(tags = {"Flows"}, summary = "Validate a task")
|
||||
public ValidateConstraintViolation validateTask(
|
||||
@RequestBody(description = "A task definition that can be from tasks or triggers") @Body String task,
|
||||
@RequestBody(description = "A task definition that can be from tasks or triggers") @Schema(implementation = Object.class) @Body String task,
|
||||
@Parameter(description = "The type of task") @QueryValue TaskValidationType section
|
||||
) {
|
||||
ValidateConstraintViolation.ValidateConstraintViolationBuilder<?, ?> validateConstraintViolationBuilder = ValidateConstraintViolation.builder();
|
||||
@@ -703,12 +705,12 @@ public class FlowController {
|
||||
summary = "Export flows as a ZIP archive of yaml sources."
|
||||
)
|
||||
public HttpResponse<byte[]> exportFlowsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat() List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat() List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include") @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include", deprecated = true) @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
) throws IOException {
|
||||
filters = mapLegacyQueryParamsToNewFilters(filters, query, scope, namespace, labels);
|
||||
|
||||
@@ -741,12 +743,12 @@ public class FlowController {
|
||||
summary = "Delete flows returned by the query parameters."
|
||||
)
|
||||
public HttpResponse<BulkResponse> deleteFlowsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat() List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include") @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include", deprecated = true) @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
) {
|
||||
filters = mapLegacyQueryParamsToNewFilters(filters, query, scope, namespace, labels);
|
||||
|
||||
@@ -784,12 +786,12 @@ public class FlowController {
|
||||
summary = "Disable flows returned by the query parameters."
|
||||
)
|
||||
public HttpResponse<BulkResponse> disableFlowsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat() List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat() List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include") @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include", deprecated = true) @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
) {
|
||||
filters = mapLegacyQueryParamsToNewFilters(filters, query, scope, namespace, labels);
|
||||
|
||||
@@ -816,12 +818,12 @@ public class FlowController {
|
||||
summary = "Enable flows returned by the query parameters."
|
||||
)
|
||||
public HttpResponse<BulkResponse> enableFlowsByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat() List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat() List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include") @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "The scope of the flows to include", deprecated = true) @Nullable @QueryValue List<FlowScope> scope,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A labels filter as a list of 'key:value'", deprecated = true) @Nullable @QueryValue @Format("MULTI") List<String> labels
|
||||
) {
|
||||
filters = mapLegacyQueryParamsToNewFilters(filters, query, scope, namespace, labels);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.*;
|
||||
import java.time.*;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
@Validated
|
||||
@@ -91,7 +91,7 @@ public class KVController {
|
||||
}
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Put(uri = "{key}", consumes = {MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||
@Put(uri = "{key}", consumes = {MediaType.TEXT_PLAIN})
|
||||
@Operation(tags = {"KV"}, summary = "Puts a key-value pair in store")
|
||||
public void setKeyValue(
|
||||
HttpHeaders httpHeaders,
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.micronaut.scheduling.annotation.ExecuteOn;
|
||||
import io.micronaut.validation.Validated;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.slf4j.event.Level;
|
||||
@@ -66,7 +67,7 @@ public class LogController {
|
||||
@Parameter(description = "The current page") @QueryValue(defaultValue = "1") @Min(1) int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(defaultValue = "10") @Min(1) int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
|
||||
@Parameter(description = "Filters") @Nullable @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @Nullable @QueryFilterFormat List<QueryFilter> filters,
|
||||
// Deprecated params
|
||||
@Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Parameter(description = "A namespace filter prefix",deprecated = true) @Nullable @QueryValue String namespace,
|
||||
|
||||
@@ -19,6 +19,7 @@ import io.micronaut.scheduling.annotation.ExecuteOn;
|
||||
import io.micronaut.validation.Validated;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -44,7 +45,7 @@ public class NamespaceSecretController {
|
||||
@Parameter(description = "The current page") @QueryValue(value = "page", defaultValue = "1") int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(value = "size", defaultValue = "10") int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue(value = "sort") List<String> sort,
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters
|
||||
) throws IllegalArgumentException, IOException {
|
||||
final String tenantId = this.tenantService.resolveTenant();
|
||||
List<String> items = secretService.inheritedSecrets(tenantId, namespace).get(namespace).stream().toList();
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.micronaut.scheduling.TaskExecutors;
|
||||
import io.micronaut.scheduling.annotation.ExecuteOn;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.constraints.Min;
|
||||
@@ -44,7 +45,7 @@ public class TaskRunController {
|
||||
@Parameter(description = "The current page") @QueryValue(defaultValue = "1") @Min(1) int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(defaultValue = "10") @Min(1) int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
// Deprecated params
|
||||
@Parameter(description = "A string filter",deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
|
||||
@@ -32,6 +32,7 @@ import io.micronaut.scheduling.annotation.ExecuteOn;
|
||||
import io.micronaut.validation.Validated;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
@@ -82,7 +83,7 @@ public class TriggerController {
|
||||
@Parameter(description = "The current page") @QueryValue(defaultValue = "1") @Min(1) int page,
|
||||
@Parameter(description = "The current page size") @QueryValue(defaultValue = "10") @Min(1) int size,
|
||||
@Parameter(description = "The sort of current page") @Nullable @QueryValue List<String> sort,
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
// Deprecated params
|
||||
@Parameter(description = "A string filter",deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
@@ -205,10 +206,10 @@ public class TriggerController {
|
||||
@Post(uri = "/unlock/by-query")
|
||||
@Operation(tags = {"Triggers"}, summary = "Unlock triggers by query parameters")
|
||||
public MutableHttpResponse<?> unlockTriggersByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace
|
||||
) {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -280,13 +281,13 @@ public class TriggerController {
|
||||
if (abstractTrigger == null) {
|
||||
throw new HttpStatusException(HttpStatus.NOT_FOUND, String.format("Flow %s has no trigger %s", newTrigger.getFlowId(), newTrigger.getTriggerId()));
|
||||
}
|
||||
|
||||
|
||||
if (abstractTrigger instanceof RealtimeTriggerInterface) {
|
||||
throw new IllegalArgumentException("Realtime triggers can not be updated through the API, please edit the trigger from the flow.");
|
||||
}
|
||||
|
||||
|
||||
Trigger updatedTrigger;
|
||||
|
||||
|
||||
if (newTrigger.getBackfill() != null) {
|
||||
try {
|
||||
updatedTrigger = setTriggerBackfill(newTrigger, maybeFlow.get(), abstractTrigger);
|
||||
@@ -296,13 +297,13 @@ public class TriggerController {
|
||||
} else {
|
||||
updatedTrigger = setTriggerDisabled(newTrigger.uid(), newTrigger.getDisabled(), abstractTrigger, maybeFlow.get());
|
||||
}
|
||||
|
||||
|
||||
if (updatedTrigger == null) {
|
||||
return HttpResponse.notFound();
|
||||
}
|
||||
return HttpResponse.ok(updatedTrigger);
|
||||
}
|
||||
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Post(uri = "/{namespace}/{flowId}/{triggerId}/restart")
|
||||
@Operation(tags = {"Triggers"}, summary = "Restart a trigger")
|
||||
@@ -369,10 +370,10 @@ public class TriggerController {
|
||||
@Post(uri = "/backfill/pause/by-query")
|
||||
@Operation(tags = {"Triggers"}, summary = "Pause backfill for given triggers")
|
||||
public MutableHttpResponse<?> pauseBackfillByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace
|
||||
) throws QueueException {
|
||||
// Updating the backfill within the flux does not works
|
||||
List<Trigger> triggers = triggerRepository
|
||||
@@ -408,10 +409,10 @@ public class TriggerController {
|
||||
@Post(uri = "/backfill/unpause/by-query")
|
||||
@Operation(tags = {"Triggers"}, summary = "Unpause backfill for given triggers")
|
||||
public MutableHttpResponse<?> unpauseBackfillByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace
|
||||
) throws QueueException {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -477,10 +478,10 @@ public class TriggerController {
|
||||
@Post(uri = "/backfill/delete/by-query")
|
||||
@Operation(tags = {"Triggers"}, summary = "Delete backfill for given triggers")
|
||||
public MutableHttpResponse<?> deleteBackfillByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace
|
||||
) throws QueueException {
|
||||
filters = RequestUtils.getFiltersOrDefaultToLegacyMapping(
|
||||
filters,
|
||||
@@ -521,10 +522,10 @@ public class TriggerController {
|
||||
@Post(uri = "/set-disabled/by-query")
|
||||
@Operation(tags = {"Triggers"}, summary = "Disable/enable triggers by query parameters")
|
||||
public MutableHttpResponse<?> disabledTriggersByQuery(
|
||||
@Parameter(description = "Filters") @QueryFilterFormat List<QueryFilter> filters,
|
||||
@Parameter(description = "Filters", in = ParameterIn.QUERY) @QueryFilterFormat List<QueryFilter> filters,
|
||||
|
||||
@Deprecated @Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix") @Nullable @QueryValue String namespace,
|
||||
@Deprecated @Parameter(description = "A string filter", deprecated = true) @Nullable @QueryValue(value = "q") String query,
|
||||
@Deprecated @Parameter(description = "A namespace filter prefix", deprecated = true) @Nullable @QueryValue String namespace,
|
||||
|
||||
@Parameter(description = "The disabled state") @QueryValue(defaultValue = "true") Boolean disabled
|
||||
) throws QueueException {
|
||||
@@ -557,24 +558,24 @@ public class TriggerController {
|
||||
|
||||
public void setTriggerDisabled(Trigger trigger, Boolean disabled) throws QueueException {
|
||||
Optional<Flow> maybeFlow = this.flowRepository.findById(this.tenantService.resolveTenant(), trigger.getNamespace(), trigger.getFlowId());
|
||||
|
||||
|
||||
if (maybeFlow.isEmpty()) {
|
||||
return; // Flow doesn't exist
|
||||
}
|
||||
|
||||
|
||||
Optional<AbstractTrigger> maybeAbstractTrigger = maybeFlow.flatMap(flow -> flow.getTriggers().stream().filter(t -> t.getId().equals(trigger.getTriggerId())).findFirst());
|
||||
|
||||
|
||||
if (maybeAbstractTrigger.isEmpty()) {
|
||||
return; // Trigger doesn't exist
|
||||
}
|
||||
|
||||
|
||||
if (maybeAbstractTrigger.get() instanceof RealtimeTriggerInterface) {
|
||||
return; // RealTimeTriggers can't be disabled/enabled through API.
|
||||
}
|
||||
|
||||
|
||||
setTriggerDisabled(trigger.uid(), disabled, maybeAbstractTrigger.get(), maybeFlow.get());
|
||||
}
|
||||
|
||||
|
||||
private Trigger setTriggerDisabled(String triggerUID, Boolean disabled, AbstractTrigger triggerDefinition, Flow flow) throws QueueException {
|
||||
return this.triggerRepository.lock(triggerUID, throwFunction(current -> {
|
||||
if (disabled.equals(current.getDisabled())) {
|
||||
@@ -583,46 +584,46 @@ public class TriggerController {
|
||||
return doSetTriggerDisabled(current, disabled, flow, triggerDefinition);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private Trigger setTriggerBackfill(Trigger newTrigger, Flow flow, AbstractTrigger abstractTrigger) throws Exception {
|
||||
return this.triggerRepository.lock(newTrigger.uid(), throwFunction(current -> doSetTriggerBackfill(current, newTrigger.getBackfill(), flow, abstractTrigger)));
|
||||
}
|
||||
|
||||
|
||||
protected Trigger doSetTriggerDisabled(Trigger currentState, Boolean disabled, Flow flow, AbstractTrigger trigger) throws QueueException {
|
||||
Trigger.TriggerBuilder<?, ?> builder = currentState.toBuilder().disabled(disabled);
|
||||
|
||||
|
||||
if (disabled) {
|
||||
builder = builder.nextExecutionDate(null);
|
||||
}
|
||||
|
||||
|
||||
Trigger updated = builder.build();
|
||||
triggerQueue.emit(updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
protected Trigger doSetTriggerBackfill(Trigger currentState, Backfill backfill, Flow flow, AbstractTrigger trigger) throws Exception {
|
||||
Trigger updated;
|
||||
ZonedDateTime nextExecutionDate = null;
|
||||
|
||||
|
||||
RunContext runContext = runContextFactory.of(flow, trigger);
|
||||
ConditionContext conditionContext = conditionService.conditionContext(runContext, flow, null);
|
||||
|
||||
|
||||
// We must set up the backfill before the update to calculate the next execution date
|
||||
updated = currentState.withBackfill(backfill);
|
||||
|
||||
|
||||
if (trigger instanceof PollingTriggerInterface pollingTriggerInterface) {
|
||||
nextExecutionDate = pollingTriggerInterface.nextEvaluationDate(conditionContext, Optional.of(updated));
|
||||
}
|
||||
|
||||
|
||||
updated = updated
|
||||
.toBuilder()
|
||||
.nextExecutionDate(nextExecutionDate)
|
||||
.build();
|
||||
|
||||
|
||||
triggerQueue.emit(updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
public int backfillsAction(List<Trigger> triggers, BACKFILL_ACTION action) throws QueueException {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
triggers.forEach(throwConsumer(trigger -> {
|
||||
|
||||
@@ -1294,13 +1294,14 @@ class ExecutionControllerRunnerTest {
|
||||
Execution pausedExecution = runnerUtils.runOneUntilPaused(TENANT_ID, TESTS_FLOW_NS, "pause-test");
|
||||
assertThat(pausedExecution.getState().isPaused()).isTrue();
|
||||
|
||||
// resume the execution
|
||||
HttpResponse<?> resumeResponse = client.toBlocking().exchange(
|
||||
// kill the execution
|
||||
HttpResponse<?> killResponse = client.toBlocking().exchange(
|
||||
HttpRequest.DELETE("/api/v1/main/executions/" + pausedExecution.getId() + "/kill"));
|
||||
assertThat(resumeResponse.getStatus().getCode()).isEqualTo(HttpStatus.ACCEPTED.getCode());
|
||||
assertThat(killResponse.getStatus().getCode()).isEqualTo(HttpStatus.ACCEPTED.getCode());
|
||||
|
||||
// check that the execution is no more paused
|
||||
awaitExecution(pausedExecution.getId(), exec -> !exec.getState().isPaused());
|
||||
// check that the execution is killed
|
||||
Execution killedExecution = awaitExecution(pausedExecution.getId(), exec -> exec.getState().getCurrent().isKilled());
|
||||
assertThat(killedExecution.getTaskRunList()).hasSize(1);
|
||||
}
|
||||
|
||||
// This test is flaky on CI as the flow may be already SUCCESS when we kill it if CI is super slow
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package io.kestra.webserver.controllers.api;
|
||||
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.within;
|
||||
|
||||
import io.kestra.core.exceptions.ResourceExpiredException;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import io.kestra.core.models.kv.KVType;
|
||||
@@ -23,6 +19,13 @@ import io.micronaut.http.client.annotation.Client;
|
||||
import io.micronaut.http.client.exceptions.HttpClientResponseException;
|
||||
import io.micronaut.reactor.http.client.ReactorHttpClient;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -36,12 +39,9 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static io.kestra.core.tenant.TenantService.MAIN_TENANT;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.within;
|
||||
|
||||
@KestraTest(resolveParameters = false)
|
||||
class KVControllerTest {
|
||||
@@ -163,24 +163,24 @@ class KVControllerTest {
|
||||
|
||||
static Stream<Arguments> kvSetKeyValueArgs() {
|
||||
return Stream.of(
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "{\"hello\":\"world\"}", Map.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "[\"hello\",\"world\"]", List.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "\"hello\"", String.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "1", Integer.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "1.0", BigDecimal.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "true", Boolean.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "false", Boolean.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "2021-09-01", LocalDate.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "2021-09-01T01:02:03Z", Instant.class),
|
||||
Arguments.of(MediaType.APPLICATION_JSON, "\"PT5S\"", Duration.class)
|
||||
Arguments.of("{\"hello\":\"world\"}", Map.class),
|
||||
Arguments.of("[\"hello\",\"world\"]", List.class),
|
||||
Arguments.of("\"hello\"", String.class),
|
||||
Arguments.of("1", Integer.class),
|
||||
Arguments.of("1.0", BigDecimal.class),
|
||||
Arguments.of("true", Boolean.class),
|
||||
Arguments.of("false", Boolean.class),
|
||||
Arguments.of("2021-09-01", LocalDate.class),
|
||||
Arguments.of("2021-09-01T01:02:03Z", Instant.class),
|
||||
Arguments.of("\"PT5S\"", Duration.class)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("kvSetKeyValueArgs")
|
||||
void setKeyValue(MediaType mediaType, String value, Class<?> expectedClass) throws IOException, ResourceExpiredException {
|
||||
void setKeyValue(String value, Class<?> expectedClass) throws IOException, ResourceExpiredException {
|
||||
String myDescription = "myDescription";
|
||||
client.toBlocking().exchange(HttpRequest.PUT("/api/v1/main/namespaces/" + NAMESPACE + "/kv/my-key", value).contentType(mediaType).header("ttl", "PT5M").header("description", myDescription));
|
||||
client.toBlocking().exchange(HttpRequest.PUT("/api/v1/main/namespaces/" + NAMESPACE + "/kv/my-key", value).header("ttl", "PT5M").header("description", myDescription));
|
||||
|
||||
KVStore kvStore = new InternalKVStore(MAIN_TENANT, NAMESPACE, storageInterface);
|
||||
Class<?> valueClazz = kvStore.getValue("my-key").get().value().getClass();
|
||||
@@ -256,7 +256,7 @@ class KVControllerTest {
|
||||
assertThat(httpClientResponseException.getStatus().getCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY.getCode());
|
||||
assertThat(httpClientResponseException.getMessage()).isEqualTo(expectedErrorMessage);
|
||||
|
||||
httpClientResponseException = Assertions.assertThrows(HttpClientResponseException.class, () -> client.toBlocking().exchange(HttpRequest.PUT("/api/v1/main/namespaces/" + NAMESPACE + "/kv/bad$key", "\"content\"").contentType(MediaType.APPLICATION_JSON)));
|
||||
httpClientResponseException = Assertions.assertThrows(HttpClientResponseException.class, () -> client.toBlocking().exchange(HttpRequest.PUT("/api/v1/main/namespaces/" + NAMESPACE + "/kv/bad$key", "\"content\"")));
|
||||
assertThat(httpClientResponseException.getStatus().getCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY.getCode());
|
||||
assertThat(httpClientResponseException.getMessage()).isEqualTo(expectedErrorMessage);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user