mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-26 14:00:23 -05:00
Compare commits
59 Commits
v1.0.5
...
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 | ||
|
|
483f7dc3b2 | ||
|
|
3c2da63837 | ||
|
|
31527891b2 | ||
|
|
6364f419d9 | ||
|
|
3c14432412 | ||
|
|
eaea4f5012 | ||
|
|
d43390a579 | ||
|
|
2404c36d35 | ||
|
|
bdbd217171 | ||
|
|
019c16af3c | ||
|
|
ff7d7c6a0b | ||
|
|
1042be87da | ||
|
|
104805d780 | ||
|
|
33c8e54f36 | ||
|
|
ff2e00d1ca | ||
|
|
0fe3f317c7 | ||
|
|
f753d15c91 | ||
|
|
c03e31de68 | ||
|
|
9a79f9a64c |
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
build.gradle
62
build.gradle
@@ -205,23 +205,59 @@ subprojects {
|
||||
testImplementation 'org.assertj:assertj-core'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
def commonTestConfig = { Test t ->
|
||||
// set Xmx for test workers
|
||||
maxHeapSize = '4g'
|
||||
t.maxHeapSize = '4g'
|
||||
|
||||
// configure en_US default locale for tests
|
||||
systemProperty 'user.language', 'en'
|
||||
systemProperty 'user.country', 'US'
|
||||
t.systemProperty 'user.language', 'en'
|
||||
t.systemProperty 'user.country', 'US'
|
||||
|
||||
environment 'SECRET_MY_SECRET', "{\"secretKey\":\"secretValue\"}".bytes.encodeBase64().toString()
|
||||
environment 'SECRET_NEW_LINE', "cGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2\nZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZl\neXJsb25n"
|
||||
environment 'SECRET_WEBHOOK_KEY', "secretKey".bytes.encodeBase64().toString()
|
||||
environment 'SECRET_NON_B64_SECRET', "some secret value"
|
||||
environment 'SECRET_PASSWORD', "cGFzc3dvcmQ="
|
||||
environment 'ENV_TEST1', "true"
|
||||
environment 'ENV_TEST2', "Pass by env"
|
||||
t.environment 'SECRET_MY_SECRET', "{\"secretKey\":\"secretValue\"}".bytes.encodeBase64().toString()
|
||||
t.environment 'SECRET_NEW_LINE', "cGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2\nZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZl\neXJsb25n"
|
||||
t.environment 'SECRET_WEBHOOK_KEY', "secretKey".bytes.encodeBase64().toString()
|
||||
t.environment 'SECRET_NON_B64_SECRET', "some secret value"
|
||||
t.environment 'SECRET_PASSWORD', "cGFzc3dvcmQ="
|
||||
t.environment 'ENV_TEST1', "true"
|
||||
t.environment 'ENV_TEST2', "Pass by env"
|
||||
|
||||
}
|
||||
|
||||
tasks.register('flakyTest', Test) { Test t ->
|
||||
group = 'verification'
|
||||
description = 'Runs tests tagged @Flaky but does not fail the build.'
|
||||
|
||||
useJUnitPlatform {
|
||||
includeTags 'flaky'
|
||||
}
|
||||
ignoreFailures = true
|
||||
|
||||
reports {
|
||||
junitXml.required = true
|
||||
junitXml.outputPerTestCase = true
|
||||
junitXml.mergeReruns = true
|
||||
junitXml.includeSystemErrLog = true
|
||||
junitXml.outputLocation = layout.buildDirectory.dir("test-results/flakyTest")
|
||||
}
|
||||
commonTestConfig(t)
|
||||
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform {
|
||||
excludeTags 'flaky'
|
||||
}
|
||||
reports {
|
||||
junitXml.required = true
|
||||
junitXml.outputPerTestCase = true
|
||||
junitXml.mergeReruns = true
|
||||
junitXml.includeSystemErrLog = true
|
||||
junitXml.outputLocation = layout.buildDirectory.dir("test-results/test")
|
||||
}
|
||||
commonTestConfig(it)
|
||||
|
||||
|
||||
finalizedBy(tasks.named('flakyTest'))
|
||||
}
|
||||
|
||||
testlogger {
|
||||
|
||||
@@ -117,7 +117,7 @@ public abstract class AbstractValidateCommand extends AbstractApiCommand {
|
||||
|
||||
try(DefaultHttpClient client = client()) {
|
||||
MutableHttpRequest<String> request = HttpRequest
|
||||
.POST(apiUri("/flows/validate", tenantService.getTenantId(tenantId)), body).contentType(MediaType.APPLICATION_YAML);
|
||||
.POST(apiUri("/flows/validate", tenantService.getTenantIdAndAllowEETenants(tenantId)), body).contentType(MediaType.APPLICATION_YAML);
|
||||
|
||||
List<ValidateConstraintViolation> validations = client.toBlocking().retrieve(
|
||||
this.requestOptions(request),
|
||||
|
||||
@@ -24,7 +24,8 @@ public class FlowValidateCommand extends AbstractValidateCommand {
|
||||
private FlowService flowService;
|
||||
|
||||
@Inject
|
||||
private TenantIdSelectorService tenantService;
|
||||
private TenantIdSelectorService tenantIdSelectorService;
|
||||
|
||||
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
@@ -39,7 +40,7 @@ public class FlowValidateCommand extends AbstractValidateCommand {
|
||||
FlowWithSource flow = (FlowWithSource) object;
|
||||
List<String> warnings = new ArrayList<>();
|
||||
warnings.addAll(flowService.deprecationPaths(flow).stream().map(deprecation -> deprecation + " is deprecated").toList());
|
||||
warnings.addAll(flowService.warnings(flow, tenantService.getTenantId(tenantId)));
|
||||
warnings.addAll(flowService.warnings(flow, tenantIdSelectorService.getTenantIdAndAllowEETenants(tenantId)));
|
||||
return warnings;
|
||||
},
|
||||
(Object object) -> {
|
||||
|
||||
@@ -64,7 +64,7 @@ public class FlowNamespaceUpdateCommand extends AbstractServiceNamespaceUpdateCo
|
||||
}
|
||||
try(DefaultHttpClient client = client()) {
|
||||
MutableHttpRequest<String> request = HttpRequest
|
||||
.POST(apiUri("/flows/", tenantService.getTenantId(tenantId)) + namespace + "?delete=" + delete, body).contentType(MediaType.APPLICATION_YAML);
|
||||
.POST(apiUri("/flows/", tenantService.getTenantIdAndAllowEETenants(tenantId)) + namespace + "?delete=" + delete, body).contentType(MediaType.APPLICATION_YAML);
|
||||
|
||||
List<UpdateResult> updated = client.toBlocking().retrieve(
|
||||
this.requestOptions(request),
|
||||
|
||||
@@ -49,7 +49,7 @@ public class NamespaceFilesUpdateCommand extends AbstractApiCommand {
|
||||
|
||||
try (var files = Files.walk(from); DefaultHttpClient client = client()) {
|
||||
if (delete) {
|
||||
client.toBlocking().exchange(this.requestOptions(HttpRequest.DELETE(apiUri("/namespaces/", tenantService.getTenantId(tenantId)) + namespace + "/files?path=" + to, null)));
|
||||
client.toBlocking().exchange(this.requestOptions(HttpRequest.DELETE(apiUri("/namespaces/", tenantService.getTenantIdAndAllowEETenants(tenantId)) + namespace + "/files?path=" + to, null)));
|
||||
}
|
||||
|
||||
KestraIgnore kestraIgnore = new KestraIgnore(from);
|
||||
@@ -67,7 +67,7 @@ public class NamespaceFilesUpdateCommand extends AbstractApiCommand {
|
||||
client.toBlocking().exchange(
|
||||
this.requestOptions(
|
||||
HttpRequest.POST(
|
||||
apiUri("/namespaces/", tenantService.getTenantId(tenantId)) + namespace + "/files?path=" + destination,
|
||||
apiUri("/namespaces/", tenantService.getTenantIdAndAllowEETenants(tenantId)) + namespace + "/files?path=" + destination,
|
||||
body
|
||||
).contentType(MediaType.MULTIPART_FORM_DATA)
|
||||
)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -49,7 +49,7 @@ public class TemplateNamespaceUpdateCommand extends AbstractServiceNamespaceUpda
|
||||
|
||||
try (DefaultHttpClient client = client()) {
|
||||
MutableHttpRequest<List<Template>> request = HttpRequest
|
||||
.POST(apiUri("/templates/", tenantService.getTenantId(tenantId)) + namespace + "?delete=" + delete, templates);
|
||||
.POST(apiUri("/templates/", tenantService.getTenantIdAndAllowEETenants(tenantId)) + namespace + "?delete=" + delete, templates);
|
||||
|
||||
List<UpdateResult> updated = client.toBlocking().retrieve(
|
||||
this.requestOptions(request),
|
||||
|
||||
@@ -16,4 +16,11 @@ public class TenantIdSelectorService {
|
||||
}
|
||||
return MAIN_TENANT;
|
||||
}
|
||||
|
||||
public String getTenantIdAndAllowEETenants(String tenantId) {
|
||||
if (StringUtils.isNotBlank(tenantId)){
|
||||
return tenantId;
|
||||
}
|
||||
return MAIN_TENANT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,26 @@ class FlowValidateCommandTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
// github action kestra-io/validate-action requires being able to validate Flows from OSS CLI against a remote EE instance
|
||||
void runForEEInstance() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
|
||||
String[] args = {
|
||||
"--tenant",
|
||||
"some-ee-tenant",
|
||||
"--local",
|
||||
"src/test/resources/helper/include.yaml"
|
||||
};
|
||||
Integer call = PicocliRunner.call(FlowValidateCommand.class, ctx, args);
|
||||
|
||||
assertThat(call).isZero();
|
||||
assertThat(out.toString()).contains("✓ - io.kestra.cli / include");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void warning() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
@@ -7,6 +7,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
/**
|
||||
* Top-level marker interface for Kestra's plugin of type App.
|
||||
*/
|
||||
@@ -18,6 +20,6 @@ public interface AppBlockInterface extends io.kestra.core.models.Plugin {
|
||||
)
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
String getType();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
/**
|
||||
* Top-level marker interface for Kestra's plugin of type App.
|
||||
*/
|
||||
@@ -18,6 +20,6 @@ public interface AppPluginInterface extends io.kestra.core.models.Plugin {
|
||||
)
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
String getType();
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import lombok.experimental.SuperBuilder;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@io.kestra.core.models.annotations.Plugin
|
||||
@SuperBuilder
|
||||
@Getter
|
||||
@@ -20,6 +22,6 @@ import jakarta.validation.constraints.Pattern;
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public abstract class Condition implements Plugin, Rethrow.PredicateChecked<ConditionContext, InternalException> {
|
||||
@NotNull
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
protected String type;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@@ -28,7 +30,7 @@ import java.util.Set;
|
||||
public abstract class DataFilter<F extends Enum<F>, C extends ColumnDescriptor<F>> implements io.kestra.core.models.Plugin, IData<F> {
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
private String type;
|
||||
|
||||
private Map<String, C> columns;
|
||||
|
||||
@@ -19,6 +19,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@@ -27,7 +29,7 @@ import java.util.Set;
|
||||
public abstract class DataFilterKPI<F extends Enum<F>, C extends ColumnDescriptor<F>> implements io.kestra.core.models.Plugin, IData<F> {
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
private String type;
|
||||
|
||||
private C columns;
|
||||
|
||||
@@ -12,6 +12,8 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@@ -26,7 +28,7 @@ public abstract class Chart<P extends ChartOption> implements io.kestra.core.mod
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
protected String type;
|
||||
|
||||
@Valid
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.Hidden;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -120,6 +121,16 @@ public class LogEntry implements DeletedInterface, TenantInterface {
|
||||
return logEntry.getTimestamp().toString() + " " + logEntry.getLevel() + " " + logEntry.getMessage();
|
||||
}
|
||||
|
||||
public static String toPrettyString(LogEntry logEntry, Integer maxMessageSize) {
|
||||
String message;
|
||||
if (maxMessageSize != null && maxMessageSize > 0) {
|
||||
message = StringUtils.truncate(logEntry.getMessage(), maxMessageSize);
|
||||
} else {
|
||||
message = logEntry.getMessage();
|
||||
}
|
||||
return logEntry.getTimestamp().toString() + " " + logEntry.getLevel() + " " + message;
|
||||
}
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
return Stream
|
||||
.of(
|
||||
|
||||
@@ -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;
|
||||
@@ -52,6 +53,8 @@ public class TaskRun implements TenantInterface {
|
||||
|
||||
@With
|
||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||
@Nullable
|
||||
@Schema(implementation = Object.class)
|
||||
Variables outputs;
|
||||
|
||||
@NotNull
|
||||
@@ -64,7 +67,6 @@ public class TaskRun implements TenantInterface {
|
||||
Boolean dynamic;
|
||||
|
||||
// Set it to true to force execution even if the execution is killed
|
||||
@Nullable
|
||||
@With
|
||||
Boolean forceExecution;
|
||||
|
||||
@@ -217,7 +219,7 @@ public class TaskRun implements TenantInterface {
|
||||
public boolean isSame(TaskRun taskRun) {
|
||||
return this.getId().equals(taskRun.getId()) &&
|
||||
((this.getValue() == null && taskRun.getValue() == null) || (this.getValue() != null && this.getValue().equals(taskRun.getValue()))) &&
|
||||
((this.getIteration() == null && taskRun.getIteration() == null) || (this.getIteration() != null && this.getIteration().equals(taskRun.getIteration()))) ;
|
||||
((this.getIteration() == null && taskRun.getIteration() == null) || (this.getIteration() != null && this.getIteration().equals(taskRun.getIteration())));
|
||||
}
|
||||
|
||||
public String toString(boolean pretty) {
|
||||
@@ -249,7 +251,7 @@ public class TaskRun implements TenantInterface {
|
||||
* This method is used when the retry is apply on a task
|
||||
* but the retry type is NEW_EXECUTION
|
||||
*
|
||||
* @param retry Contains the retry configuration
|
||||
* @param retry Contains the retry configuration
|
||||
* @param execution Contains the attempt number and original creation date
|
||||
* @return The next retry date, null if maxAttempt || maxDuration is reached
|
||||
*/
|
||||
@@ -270,6 +272,7 @@ public class TaskRun implements TenantInterface {
|
||||
|
||||
/**
|
||||
* This method is used when the Retry definition comes from the flow
|
||||
*
|
||||
* @param retry The retry configuration
|
||||
* @return The next retry date, null if maxAttempt || maxDuration is reached
|
||||
*/
|
||||
|
||||
@@ -77,14 +77,6 @@ public abstract class AbstractFlow implements FlowInterface {
|
||||
Map<String, Object> variables;
|
||||
|
||||
|
||||
@Schema(
|
||||
oneOf = {
|
||||
String.class, // Corresponds to 'type: string' in OpenAPI
|
||||
Map.class // Corresponds to 'type: object' in OpenAPI
|
||||
}
|
||||
)
|
||||
interface StringOrMapValue {}
|
||||
|
||||
@Valid
|
||||
private WorkerGroup workerGroup;
|
||||
|
||||
|
||||
@@ -24,10 +24,6 @@ public class PluginDefault {
|
||||
|
||||
@Schema(
|
||||
type = "object",
|
||||
oneOf = {
|
||||
Map.class,
|
||||
String.class
|
||||
},
|
||||
additionalProperties = Schema.AdditionalPropertiesValue.FALSE
|
||||
)
|
||||
private final Map<String, Object> values;
|
||||
|
||||
@@ -8,6 +8,8 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public interface TaskInterface extends Plugin, PluginVersioning {
|
||||
@NotNull
|
||||
@@ -17,7 +19,7 @@ public interface TaskInterface extends Plugin, PluginVersioning {
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
@Schema(title = "The class name of this task.")
|
||||
String getType();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@Plugin
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@@ -22,7 +24,7 @@ public abstract class LogExporter<T extends Output> implements io.kestra.core.m
|
||||
protected String id;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
protected String type;
|
||||
|
||||
public abstract T sendLogs(RunContext runContext, Flux<LogRecord> logRecords) throws Exception;
|
||||
|
||||
@@ -8,12 +8,16 @@ public final class LogRecordMapper {
|
||||
private LogRecordMapper(){}
|
||||
|
||||
public static LogRecord mapToLogRecord(LogEntry log) {
|
||||
return mapToLogRecord(log, null);
|
||||
}
|
||||
|
||||
public static LogRecord mapToLogRecord(LogEntry log, Integer maxMessageSize) {
|
||||
return LogRecord.builder()
|
||||
.resource("Kestra")
|
||||
.timestampEpochNanos(instantInNanos(log.getTimestamp()))
|
||||
.severity(log.getLevel().name())
|
||||
.attributes(log.toLogMap())
|
||||
.bodyValue(LogEntry.toPrettyString(log))
|
||||
.bodyValue(LogEntry.toPrettyString(log, maxMessageSize))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
package io.kestra.core.models.tasks.runners;
|
||||
|
||||
public interface RemoteRunnerInterface {}
|
||||
import io.kestra.core.models.property.Property;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
public interface RemoteRunnerInterface {
|
||||
@Schema(
|
||||
title = "Whether to synchronize working directory from remote runner back to local one after run."
|
||||
)
|
||||
Property<Boolean> getSyncWorkingDirectory();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ public interface TaskCommands {
|
||||
|
||||
Map<String, Object> getAdditionalVars();
|
||||
|
||||
default String outputDirectoryName() {
|
||||
return this.getWorkingDirectory().relativize(this.getOutputDirectory()).toString();
|
||||
}
|
||||
|
||||
Path getWorkingDirectory();
|
||||
|
||||
Path getOutputDirectory();
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static io.kestra.core.utils.WindowsUtils.windowsToUnixPath;
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
/**
|
||||
* Base class for all task runners.
|
||||
@@ -36,7 +37,7 @@ import static io.kestra.core.utils.WindowsUtils.windowsToUnixPath;
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public abstract class TaskRunner<T extends TaskRunnerDetailResult> implements Plugin, PluginVersioning, WorkerJobLifecycle {
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
protected String type;
|
||||
|
||||
@PluginProperty(hidden = true, group = PluginProperty.CORE_GROUP)
|
||||
|
||||
@@ -47,7 +47,6 @@ abstract public class AbstractTrigger implements TriggerInterface {
|
||||
@Valid
|
||||
protected List<@Valid @NotNull Condition> conditions;
|
||||
|
||||
@NotNull
|
||||
@Builder.Default
|
||||
@PluginProperty(hidden = true, group = PluginProperty.CORE_GROUP)
|
||||
@Schema(defaultValue = "false")
|
||||
|
||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
public interface TriggerInterface extends Plugin, PluginVersioning {
|
||||
@NotNull
|
||||
@@ -17,7 +18,7 @@ public interface TriggerInterface extends Plugin, PluginVersioning {
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
@Schema(title = "The class name for this current trigger.")
|
||||
String getType();
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@io.kestra.core.models.annotations.Plugin
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@Getter
|
||||
@@ -15,6 +17,6 @@ import lombok.experimental.SuperBuilder;
|
||||
public abstract class AdditionalPlugin implements Plugin {
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
protected String type;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,8 @@ public abstract class FilesService {
|
||||
}
|
||||
|
||||
private static String resolveUniqueNameForFile(final Path path) {
|
||||
return IdUtils.from(path.toString()) + "-" + path.toFile().getName();
|
||||
String filename = path.getFileName().toString();
|
||||
String encodedFilename = java.net.URLEncoder.encode(filename, java.nio.charset.StandardCharsets.UTF_8);
|
||||
return IdUtils.from(path.toString()) + "-" + encodedFilename;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.kestra.core.utils;
|
||||
|
||||
public class RegexPatterns {
|
||||
public static final String JAVA_IDENTIFIER_REGEX = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$";
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Getter;
|
||||
|
||||
import static io.kestra.core.utils.RegexPatterns.JAVA_IDENTIFIER_REGEX;
|
||||
|
||||
@Getter
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
@@ -20,6 +22,6 @@ import lombok.Getter;
|
||||
public class MarkdownSource {
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$")
|
||||
@Pattern(regexp = JAVA_IDENTIFIER_REGEX)
|
||||
private String type;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,29 @@ public class LogRecordMapperTest {
|
||||
softly.then(logRecord.getBodyValue()).isEqualTo("2011-12-03T10:15:30.123456789Z INFO message");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_map_with_truncate(){
|
||||
LogEntry logEntry = LogEntry.builder()
|
||||
.tenantId("tenantId")
|
||||
.namespace("namespace")
|
||||
.flowId("flowId")
|
||||
.taskId("taskId")
|
||||
.executionId("executionId")
|
||||
.taskRunId("taskRunId")
|
||||
.attemptNumber(1)
|
||||
.triggerId("triggerId")
|
||||
.timestamp(Instant.parse("2011-12-03T10:15:30.123456789Z"))
|
||||
.level(Level.INFO)
|
||||
.thread("thread")
|
||||
.message("message")
|
||||
.build();
|
||||
LogRecord logRecord = LogRecordMapper.mapToLogRecord(logEntry, 1);
|
||||
assertThat(logRecord.getBodyValue()).isEqualTo("2011-12-03T10:15:30.123456789Z INFO m");
|
||||
|
||||
logRecord = LogRecordMapper.mapToLogRecord(logEntry, 0);
|
||||
assertThat(logRecord.getBodyValue()).isEqualTo("2011-12-03T10:15:30.123456789Z INFO message");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_convert_instant_in_nanos(){
|
||||
Instant instant = Instant.parse("2011-12-03T10:15:30.123456789Z");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.micronaut.context.annotation.Property;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
@@ -97,6 +98,35 @@ class FilesServiceTest {
|
||||
assertThat(outputs.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOutputFilesWithSpecialCharacters(@TempDir Path tempDir) throws Exception {
|
||||
var runContext = runContextFactory.of();
|
||||
|
||||
Path fileWithSpace = tempDir.resolve("with space.txt");
|
||||
Path fileWithUnicode = tempDir.resolve("สวัสดี.txt");
|
||||
|
||||
Files.writeString(fileWithSpace, "content");
|
||||
Files.writeString(fileWithUnicode, "content");
|
||||
|
||||
Path targetFileWithSpace = runContext.workingDir().path().resolve("with space.txt");
|
||||
Path targetFileWithUnicode = runContext.workingDir().path().resolve("สวัสดี.txt");
|
||||
|
||||
Files.copy(fileWithSpace, targetFileWithSpace);
|
||||
Files.copy(fileWithUnicode, targetFileWithUnicode);
|
||||
|
||||
Map<String, URI> outputFiles = FilesService.outputFiles(
|
||||
runContext,
|
||||
List.of("with space.txt", "สวัสดี.txt")
|
||||
);
|
||||
|
||||
assertThat(outputFiles).hasSize(2);
|
||||
assertThat(outputFiles).containsKey("with space.txt");
|
||||
assertThat(outputFiles).containsKey("สวัสดี.txt");
|
||||
|
||||
assertThat(runContext.storage().getFile(outputFiles.get("with space.txt"))).isNotNull();
|
||||
assertThat(runContext.storage().getFile(outputFiles.get("สวัสดี.txt"))).isNotNull();
|
||||
}
|
||||
|
||||
private URI createFile() throws IOException {
|
||||
File tempFile = File.createTempFile("file", ".txt");
|
||||
Files.write(tempFile.toPath(), "Hello World".getBytes());
|
||||
|
||||
@@ -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.5
|
||||
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")
|
||||
|
||||
@@ -4,11 +4,18 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
|
||||
/**
|
||||
* used to document that a test is flaky and needs to be reworked
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Tag("flaky")
|
||||
public @interface FlakyTest {
|
||||
|
||||
/**
|
||||
* Use to explain why the test is flaky
|
||||
*/
|
||||
String description() default "";
|
||||
}
|
||||
@@ -173,18 +173,32 @@ public abstract class AbstractTaskRunnerTest {
|
||||
var commands = initScriptCommands(runContext);
|
||||
Mockito.when(commands.getEnableOutputDirectory()).thenReturn(false);
|
||||
Mockito.when(commands.outputDirectoryEnabled()).thenReturn(false);
|
||||
|
||||
Mockito.when(commands.getCommands()).thenReturn(
|
||||
Property.ofValue(ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("echo 'Hello World' > file.txt")))
|
||||
);
|
||||
Mockito.when(commands.relativeWorkingDirectoryFilesPaths()).thenCallRealMethod();
|
||||
Mockito.when(commands.relativeWorkingDirectoryFilesPaths(false)).thenCallRealMethod();
|
||||
|
||||
var taskRunner = taskRunner();
|
||||
Property<List<String>> renderedCommands = Property.ofValue(ScriptService.replaceInternalStorage(
|
||||
runContext,
|
||||
taskRunner.additionalVars(runContext, commands),
|
||||
ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("echo 'Hello World' > " + (needsToSpecifyWorkingDirectory() ? "{{workingDir}}/" : "") + "file.txt")),
|
||||
taskRunner instanceof RemoteRunnerInterface
|
||||
));
|
||||
Mockito.when(commands.getCommands()).thenReturn(
|
||||
renderedCommands
|
||||
);
|
||||
|
||||
var result = taskRunner.run(runContext, commands, Collections.emptyList());
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getExitCode()).isZero();
|
||||
|
||||
renderedCommands = Property.ofValue(ScriptService.replaceInternalStorage(
|
||||
runContext,
|
||||
taskRunner.additionalVars(runContext, commands),
|
||||
ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("cat " + (needsToSpecifyWorkingDirectory() ? "{{workingDir}}/" : "") + "file.txt")),
|
||||
taskRunner instanceof RemoteRunnerInterface
|
||||
));
|
||||
Mockito.when(commands.getCommands()).thenReturn(
|
||||
Property.ofValue(ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("cat file.txt")))
|
||||
renderedCommands
|
||||
);
|
||||
|
||||
result = taskRunner.run(runContext, commands, Collections.emptyList());
|
||||
@@ -249,6 +263,7 @@ public abstract class AbstractTaskRunnerTest {
|
||||
var outputDirectory = workingDirectory.resolve(IdUtils.create());
|
||||
outputDirectory.toFile().mkdirs();
|
||||
Mockito.when(commands.getOutputDirectory()).thenReturn(outputDirectory);
|
||||
Mockito.when(commands.outputDirectoryName()).thenCallRealMethod();
|
||||
Mockito.when(commands.getAdditionalVars()).thenReturn(Collections.emptyMap());
|
||||
Mockito.when(commands.getEnableOutputDirectory()).thenReturn(true);
|
||||
Mockito.when(commands.outputDirectoryEnabled()).thenReturn(true);
|
||||
|
||||
@@ -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;
|
||||
|
||||
559
ui/package-lock.json
generated
559
ui/package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.250",
|
||||
"@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",
|
||||
@@ -1968,23 +1968,6 @@
|
||||
"mlly": "^1.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/utils/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/utils/node_modules/globals": {
|
||||
"version": "15.15.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
|
||||
@@ -1998,13 +1981,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "11.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.11.tgz",
|
||||
"integrity": "sha512-1Z0N8jTfkcD2Luq9HNZt+GmjpFe4/4PpZF3AOzoO1u5PTtSuXZcfhwBatywbfE2ieB/B5QHIoOFmCXY2jqVKEQ==",
|
||||
"version": "11.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.12.tgz",
|
||||
"integrity": "sha512-whh0trqRsSqVLNEUCwU59pyJZYpU8AmSWl8M3Jz2Mv5ESPP6kFh4juas2NpZ1iCvy7GlNRffUD1xr84gceimjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "11.1.11",
|
||||
"@intlify/shared": "11.1.11"
|
||||
"@intlify/message-compiler": "11.1.12",
|
||||
"@intlify/shared": "11.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -2014,12 +1997,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "11.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.11.tgz",
|
||||
"integrity": "sha512-7PC6neomoc/z7a8JRjPBbu0T2TzR2MQuY5kn2e049MP7+o32Ve7O8husylkA7K9fQRe4iNXZWTPnDJ6vZdtS1Q==",
|
||||
"version": "11.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.12.tgz",
|
||||
"integrity": "sha512-Fv9iQSJoJaXl4ZGkOCN1LDM3trzze0AS2zRz2EHLiwenwL6t0Ki9KySYlyr27yVOj5aVz0e55JePO+kELIvfdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "11.1.11",
|
||||
"@intlify/shared": "11.1.12",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2030,9 +2013,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "11.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.11.tgz",
|
||||
"integrity": "sha512-RIBFTIqxZSsxUqlcyoR7iiC632bq7kkOwYvZlvcVObHfrF4NhuKc4FKvu8iPCrEO+e3XsY7/UVpfgzg+M7ETzA==",
|
||||
"version": "11.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.12.tgz",
|
||||
"integrity": "sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -3219,28 +3202,28 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs": {
|
||||
"version": "0.0.250",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.250.tgz",
|
||||
"integrity": "sha512-Y0ANjGn91f+3G6ZeH0niorf0ZCNe/BPWfur+yHni4AKHbyNUZjrE8UN9ETvOlYe5c2qQSyQdM9yK/LdG1Thtzw==",
|
||||
"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.16.1",
|
||||
"@nuxtjs/mdc": "^0.17.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"html-to-image": "^1.11.11",
|
||||
"mermaid": "^11.4.1",
|
||||
"shiki": "^3.9.2",
|
||||
"html-to-image": "^1.11.13",
|
||||
"mermaid": "^11.11.0",
|
||||
"shiki": "^3.12.2",
|
||||
"slugify": "^1.6.6",
|
||||
"vue-i18n": "^11.0.1"
|
||||
"vue-i18n": "^11.1.12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/darwin-arm64": "^0.24.2",
|
||||
"@esbuild/darwin-x64": "^0.24.2",
|
||||
"@esbuild/linux-x64": "^0.24.2",
|
||||
"@rollup/rollup-darwin-arm64": "^4.30.1",
|
||||
"@rollup/rollup-darwin-x64": "^4.30.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.30.1",
|
||||
"@swc/core-darwin-arm64": "^1.10.6",
|
||||
"@swc/core-darwin-x64": "^1.10.6",
|
||||
"@swc/core-linux-x64-gnu": "^1.10.6"
|
||||
"@esbuild/darwin-arm64": "^0.25.9",
|
||||
"@esbuild/darwin-x64": "^0.25.9",
|
||||
"@esbuild/linux-x64": "^0.25.9",
|
||||
"@rollup/rollup-darwin-arm64": "^4.50.1",
|
||||
"@rollup/rollup-darwin-x64": "^4.50.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.50.1",
|
||||
"@swc/core-darwin-arm64": "^1.13.5",
|
||||
"@swc/core-darwin-x64": "^1.13.5",
|
||||
"@swc/core-linux-x64-gnu": "^1.13.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue-flow/background": "^1.3.0",
|
||||
@@ -3259,54 +3242,6 @@
|
||||
"yaml": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs/node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
@@ -3571,32 +3506,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/kit": {
|
||||
"version": "3.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.18.1.tgz",
|
||||
"integrity": "sha512-z6w1Fzv27CIKFlhct05rndkJSfoslplWH5fJ9dtusEvpYScLXp5cATWIbWkte9e9zFSmQTgDQJjNs3geQHE7og==",
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.1.3.tgz",
|
||||
"integrity": "sha512-WK0yPIqcb3GQ8r4GutF6p/2fsyXnmmmkuwVLzN4YaJHrpA2tjEagjbxdjkWYeHW8o4XIKJ4micah4wPOVK49Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"c12": "^3.2.0",
|
||||
"c12": "^3.3.0",
|
||||
"consola": "^3.4.2",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.5",
|
||||
"errx": "^0.1.0",
|
||||
"exsolve": "^1.0.7",
|
||||
"ignore": "^7.0.5",
|
||||
"jiti": "^2.5.1",
|
||||
"jiti": "^2.6.1",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.2.0",
|
||||
"mlly": "^1.7.4",
|
||||
"mlly": "^1.8.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.2.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"rc9": "^2.1.2",
|
||||
"scule": "^1.3.0",
|
||||
"semver": "^7.7.2",
|
||||
"std-env": "^3.9.0",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ufo": "^1.6.1",
|
||||
"unctx": "^2.4.1",
|
||||
"unimport": "^5.2.0",
|
||||
"unimport": "^5.4.1",
|
||||
"untyped": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3604,20 +3539,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/mdc": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/mdc/-/mdc-0.16.1.tgz",
|
||||
"integrity": "sha512-di9Ox9QY5pO2eIkQPyKFe9O8L3RvIrGbmjI3rJQRj1xGYRFj2S9RvBPCFbvfaqQGOTjOfxHLg8KtQIGj1Iw/lg==",
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@nuxtjs/mdc/-/mdc-0.17.4.tgz",
|
||||
"integrity": "sha512-I5ZYUWVlE2xZAkfBG6B0/l2uddDZlr8X2WPVMPYNY4zocobBjMgykj4aqYXHY+N35HRYsa+IpuUCf30bR8xCbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.16.1",
|
||||
"@shikijs/transformers": "^3.2.1",
|
||||
"@nuxt/kit": "^4.1.1",
|
||||
"@shikijs/core": "^3.12.2",
|
||||
"@shikijs/langs": "^3.12.2",
|
||||
"@shikijs/themes": "^3.12.2",
|
||||
"@shikijs/transformers": "^3.12.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@vue/compiler-core": "^3.5.13",
|
||||
"@vue/compiler-core": "^3.5.21",
|
||||
"consola": "^3.4.2",
|
||||
"debug": "4.4.0",
|
||||
"debug": "^4.4.1",
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3",
|
||||
"destr": "^2.0.5",
|
||||
"detab": "^3.0.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-format": "^1.1.0",
|
||||
@@ -3625,32 +3563,56 @@
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"mdast-util-to-hast": "^13.2.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.1",
|
||||
"parse5": "^7.2.1",
|
||||
"parse5": "^8.0.0",
|
||||
"pathe": "^2.0.3",
|
||||
"property-information": "^7.0.0",
|
||||
"property-information": "^7.1.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-minify-whitespace": "^6.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-remark": "^10.0.0",
|
||||
"rehype-remark": "^10.0.1",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"rehype-sort-attribute-values": "^5.0.1",
|
||||
"rehype-sort-attributes": "^5.0.1",
|
||||
"remark-emoji": "^5.0.1",
|
||||
"remark-emoji": "^5.0.2",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-mdc": "^3.5.3",
|
||||
"remark-mdc": "v3.6.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"scule": "^1.3.0",
|
||||
"shiki": "^3.2.1",
|
||||
"ufo": "^1.5.4",
|
||||
"shiki": "^3.12.2",
|
||||
"ufo": "^1.6.1",
|
||||
"unified": "^11.0.5",
|
||||
"unist-builder": "^4.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"unwasm": "^0.3.9",
|
||||
"unwasm": "^0.3.11",
|
||||
"vfile": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/mdc/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxtjs/mdc/node_modules/parse5": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
@@ -4165,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"
|
||||
@@ -4240,9 +4202,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz",
|
||||
"integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4253,9 +4215,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz",
|
||||
"integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
|
||||
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4420,9 +4382,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.50.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz",
|
||||
"integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4503,12 +4465,12 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.11.0.tgz",
|
||||
"integrity": "sha512-oJwU+DxGqp6lUZpvtQgVOXNZcVsirN76tihOLBmwILkKuRuwHteApP8oTXmL4tF5vS5FbOY0+8seXmiCoslk4g==",
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.13.0.tgz",
|
||||
"integrity": "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "3.11.0",
|
||||
"@shikijs/types": "3.13.0",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-html": "^9.0.5"
|
||||
@@ -4613,19 +4575,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/transformers": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.11.0.tgz",
|
||||
"integrity": "sha512-fhSpVoq0FoCtKbBpzE3mXcIbr0b7ozFDSSWiVjWrQy+wrOfaFfwxgJqh8kY3Pbv/i+4pcuMIVismLD2MfO62eQ==",
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.13.0.tgz",
|
||||
"integrity": "sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "3.11.0",
|
||||
"@shikijs/types": "3.11.0"
|
||||
"@shikijs/core": "3.13.0",
|
||||
"@shikijs/types": "3.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.11.0.tgz",
|
||||
"integrity": "sha512-RB7IMo2E7NZHyfkqAuaf4CofyY8bPzjWPjJRzn6SEak3b46fIQyG6Vx5fG/obqkfppQ+g8vEsiD7Uc6lqQt32Q==",
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz",
|
||||
"integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
@@ -6855,24 +6817,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
@@ -7737,16 +7681,6 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
@@ -8151,22 +8085,22 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.2.0.tgz",
|
||||
"integrity": "sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.1.tgz",
|
||||
"integrity": "sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.2.2",
|
||||
"defu": "^6.1.4",
|
||||
"dotenv": "^17.2.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"exsolve": "^1.0.7",
|
||||
"giget": "^2.0.0",
|
||||
"jiti": "^2.5.1",
|
||||
"jiti": "^2.6.1",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"pkg-types": "^2.2.0",
|
||||
"perfect-debounce": "^2.0.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"rc9": "^2.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -8178,6 +8112,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/perfect-debounce": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz",
|
||||
"integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
@@ -9559,9 +9499,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -9912,9 +9852,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||
"version": "17.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -11189,19 +11129,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
@@ -14278,9 +14217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
@@ -14787,24 +14726,6 @@
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.3.tgz",
|
||||
@@ -15087,9 +15008,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.18",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
|
||||
"integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
|
||||
"version": "0.30.19",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
|
||||
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -16235,15 +16156,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
|
||||
"integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
|
||||
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
"pathe": "^2.0.1",
|
||||
"pkg-types": "^1.3.0",
|
||||
"ufo": "^1.5.4"
|
||||
"acorn": "^8.15.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
@@ -16956,15 +16877,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz",
|
||||
"integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==",
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.2.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -17112,16 +17033,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@@ -17298,9 +17209,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
|
||||
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
|
||||
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -17309,15 +17220,14 @@
|
||||
"ci-info": "^3.7.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^7.5.3",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33",
|
||||
"tmp": "^0.2.4",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
@@ -17328,20 +17238,6 @@
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
@@ -17613,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"
|
||||
@@ -17632,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": {
|
||||
@@ -18445,9 +18341,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/remark-emoji": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-5.0.1.tgz",
|
||||
"integrity": "sha512-QCqTSvcZ65Ym+P+VyBKd4JfJfh7icMl7cIOGVmPMzWkDtdD8pQ0nQG7yxGolVIiMzSx90EZ7SwNiVpYpfTxn7w==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-5.0.2.tgz",
|
||||
"integrity": "sha512-IyIqGELcyK5AVdLFafoiNww+Eaw/F+rGrNSXoKucjo95uL267zrddgxGM83GN1wFIb68pyDuAsY3m5t2Cav1pQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.4",
|
||||
@@ -19715,9 +19611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strip-literal": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
|
||||
"integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
|
||||
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^9.0.1"
|
||||
@@ -19921,13 +19817,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.14",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2"
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -20016,16 +19912,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
@@ -20421,9 +20314,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unctx/node_modules/unplugin": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.7.tgz",
|
||||
"integrity": "sha512-zU7Osb4D5YNc9eLKsKaG6WQi9soLS+Yd9MDhOHlhAR+uoNy3BmWuddjLMhJpBpSBSIYtK5/MQvAWx9nAURTN6Q==",
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
@@ -20484,25 +20377,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-5.2.0.tgz",
|
||||
"integrity": "sha512-bTuAMMOOqIAyjV4i4UH7P07pO+EsVxmhOzQ2YJ290J6mkLUdozNhb5I/YoOEheeNADC03ent3Qj07X0fWfUpmw==",
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/unimport/-/unimport-5.5.0.tgz",
|
||||
"integrity": "sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"local-pkg": "^1.1.1",
|
||||
"magic-string": "^0.30.17",
|
||||
"mlly": "^1.7.4",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.19",
|
||||
"mlly": "^1.8.0",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"pkg-types": "^2.2.0",
|
||||
"pkg-types": "^2.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"strip-literal": "^3.0.0",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"unplugin": "^2.3.5",
|
||||
"unplugin-utils": "^0.2.4"
|
||||
"strip-literal": "^3.1.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.10",
|
||||
"unplugin-utils": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
@@ -20533,9 +20426,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/unplugin": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.7.tgz",
|
||||
"integrity": "sha512-zU7Osb4D5YNc9eLKsKaG6WQi9soLS+Yd9MDhOHlhAR+uoNy3BmWuddjLMhJpBpSBSIYtK5/MQvAWx9nAURTN6Q==",
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
@@ -20674,16 +20567,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.5.tgz",
|
||||
"integrity": "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
||||
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
@@ -20750,9 +20643,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unwasm/node_modules/unplugin": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.7.tgz",
|
||||
"integrity": "sha512-zU7Osb4D5YNc9eLKsKaG6WQi9soLS+Yd9MDhOHlhAR+uoNy3BmWuddjLMhJpBpSBSIYtK5/MQvAWx9nAURTN6Q==",
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
@@ -20890,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": {
|
||||
@@ -20987,24 +20880,6 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -21109,24 +20984,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
@@ -21431,13 +21288,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "11.1.11",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.11.tgz",
|
||||
"integrity": "sha512-LvyteQoXeQiuILbzqv13LbyBna/TEv2Ha+4ZWK2AwGHUzZ8+IBaZS0TJkCgn5izSPLcgZwXy9yyTrewCb2u/MA==",
|
||||
"version": "11.1.12",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.12.tgz",
|
||||
"integrity": "sha512-BnstPj3KLHLrsqbVU2UOrPmr0+Mv11bsUZG0PyCOzsawCivk8W00GMXHeVUWIDOgNaScCuZah47CZFE+Wnl8mw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.1.11",
|
||||
"@intlify/shared": "11.1.11",
|
||||
"@intlify/core-base": "11.1.12",
|
||||
"@intlify/shared": "11.1.12",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@js-joda/core": "^5.6.5",
|
||||
"@kestra-io/ui-libs": "^0.0.250",
|
||||
"@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";
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, markRaw, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import Utils from "../../utils/utils";
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {useCoreStore} from "../../stores/core";
|
||||
@@ -166,8 +167,10 @@
|
||||
|
||||
const TABS = isTourRunning.value ? DEFAULT_TOUR_TABS.flatMap(t => t.tabs) : DEFAULT_ACTIVE_TABS;
|
||||
|
||||
flowStore.creationId = flowStore.creationId ?? Utils.uid()
|
||||
|
||||
const panels = useStorage<Panel[]>(
|
||||
`el-fl-${flowStore.flow?.namespace}-${flowStore.flow?.id}`,
|
||||
`el-fl-${flowStore.flow?.namespace ?? `creation-${flowStore.creationId}`}${flowStore.flow?.id ? `-${flowStore.flow.id}` : ""}`,
|
||||
TABS
|
||||
.map((t) => ({
|
||||
...staticGetPanelFromValue(t).panel,
|
||||
|
||||
@@ -4,4 +4,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Blueprints from "override/components/flows/blueprints/Blueprints.vue";
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
</script>
|
||||
@@ -475,7 +475,7 @@
|
||||
return this.namespacesStore
|
||||
.createKv({
|
||||
...this.kv,
|
||||
contentType: ["DATE", "DATETIME"].includes(type) ? "text/plain" : "application/json",
|
||||
contentType: "text/plain",
|
||||
value
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -67,7 +67,10 @@ export function useBaseNamespacesStore() {
|
||||
}
|
||||
|
||||
async function kv(this: any, payload: {namespace: string; key: string}) {
|
||||
const response = await this.$http.get(`${apiUrl(this.vuexStore)}/namespaces/${payload.namespace}/kv/${payload.key}`);
|
||||
const response = await this.$http.get(`${apiUrl(this.vuexStore)}/namespaces/${payload.namespace}/kv/${payload.key}`, VALIDATE);
|
||||
if (response.status === 404) {
|
||||
throw new Error(response.data.message);
|
||||
}
|
||||
const data = response.data;
|
||||
const contentLength = response.headers?.["content-length"];
|
||||
if (contentLength === (data.length + 2).toString()) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -2,6 +2,8 @@ import {defineStore} from "pinia";
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import {trackBlueprintSelection} from "../utils/tabTracking";
|
||||
|
||||
export const VALIDATE = {validateStatus: (status: number) => status === 200 || status === 401};
|
||||
|
||||
interface Blueprint {
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -66,7 +68,7 @@ export const useBlueprintsStore = defineStore("blueprints", {
|
||||
const kind = options.kind && options.type !== "custom" ? `/${options.kind}` : "";
|
||||
const response = await this.$http.get(
|
||||
`${apiUrl(this.vuexStore)}/blueprints/${options.type}${kind}`,
|
||||
{params: options.params}
|
||||
{params: options.params, ...VALIDATE}
|
||||
);
|
||||
this.blueprints = response.data;
|
||||
return response.data;
|
||||
@@ -76,7 +78,7 @@ export const useBlueprintsStore = defineStore("blueprints", {
|
||||
const kind = options.kind && options.type !== "custom" ? `/${options.kind}` : "";
|
||||
const response = await this.$http.get(
|
||||
`${apiUrl(this.vuexStore)}/blueprints/${options.type}${kind}/tags`,
|
||||
{params: options.params}
|
||||
{params: options.params, ...VALIDATE}
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@ export const useFlowStore = defineStore("flow", () => {
|
||||
const haveChange = ref<boolean>(false)
|
||||
const expandedSubflows = ref<string[]>([])
|
||||
const metadata = ref<Record<string, any>>()
|
||||
const creationId = ref<string>();
|
||||
|
||||
const store = useStore() as Store<any> & {
|
||||
$http: {
|
||||
@@ -490,8 +491,16 @@ export const useFlowStore = defineStore("flow", () => {
|
||||
|
||||
function createFlow(options: { flow: string }) {
|
||||
return store.$http.post(`${apiUrl(store)}/flows`, options.flow, textYamlHeader).then(response => {
|
||||
|
||||
const creationPanels = localStorage.getItem(`el-fl-creation-${creationId.value}`) ?? YAML_UTILS.stringify([]);
|
||||
localStorage.setItem(`el-fl-${flow.value!.namespace}-${flow.value!.id}`, creationPanels);
|
||||
|
||||
flow.value = response.data;
|
||||
|
||||
// clean-up
|
||||
localStorage.removeItem(`el-fl-creation-${creationId.value}`);
|
||||
creationId.value = undefined;
|
||||
|
||||
return response.data;
|
||||
})
|
||||
}
|
||||
@@ -863,6 +872,7 @@ function deleteFlowAndDependencies() {
|
||||
})
|
||||
|
||||
return {
|
||||
creationId,
|
||||
isFlow,
|
||||
isAllowedEdit,
|
||||
readOnlySystemLabel,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
const maybeText = (allowSeparators: boolean) => "(?:\"[^\"]*\")|(?:'[^']*')|(?:(?:(?!\\}\\})" + (allowSeparators ? "[\\S\\n ]" : "[^~+,:\\n ]") + ")*)";
|
||||
const maybeAnotherPebbleExpression = "(?:[\\n ]*\\{\\{[\\n ]*" + maybeText(true) + "[\\n ]*\\}\\}[\\n ]*)*";
|
||||
const pebbleStart = "\\{\\{[\\n ]*";
|
||||
const fieldWithoutDotCapture = "([^\\(\\)\\}:~+.\\n '\"]*)(?![^\\(\\)\\}\\n ])";
|
||||
const dotAccessedFieldWithParentCapture = "([^\\(\\)\\}:~+\\n '\"]*)\\." + fieldWithoutDotCapture;
|
||||
const maybeTextFollowedBySeparator = "(?:" + maybeText(false) + "[~+ ]+)*";
|
||||
const paramKey = "[^\\n \\(\\)~+\\},:=]+";
|
||||
const paramValue = "(?:(?:(?:\"[^\"]*\"?)|(?:'[^']*'?)|[^,)]))*";
|
||||
const fieldWithoutDotCapture = "([^()}:~+.\\n '\"]*)(?![^()}\\n ])";
|
||||
const dotAccessedFieldWithParentCapture = "([^()}:~+\\n '\"]*)\\." + fieldWithoutDotCapture;
|
||||
const maybeTextFollowedBySeparator = "(?:" + maybeText(true) + "[\\n ]*(?:(?:[~+]+)|(?:\\}\\}[\\n ]*" + pebbleStart + "))[\\n ]*)*";
|
||||
const paramKey = "[^\\n ()~+},:=]+";
|
||||
const paramValue = "(?:(?:(?:\"[^\"]*\"?)|(?:'[^']*'?)|[^,)}]))*";
|
||||
const maybeParams = "(" +
|
||||
"(?:[\\n ]*" + paramKey + "[\\n ]*=[\\n ]*" + paramValue + "(?:[\\n ]*,[\\n ]*)?)+)?" +
|
||||
"([^\\n \\(\\)~+\\},:=]*)?";
|
||||
const functionWithMaybeParams = "([^\\n\\(\\)\\},:~ ]+)\\(" + maybeParams
|
||||
"([^\\n ()~+},:=]*)?";
|
||||
const functionWithMaybeParams = "([^\\n()},:~ ]+)\\(" + maybeParams
|
||||
|
||||
export default {
|
||||
beforeSeparator: (additionalSeparators: string[] = []) => `([^\\}:\\n ${additionalSeparators.join("")}]*)`,
|
||||
/** [fullMatch, dotForbiddenField] */
|
||||
capturePebbleVarRoot: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${fieldWithoutDotCapture}`,
|
||||
capturePebbleVarRoot: `${pebbleStart}${maybeTextFollowedBySeparator}${fieldWithoutDotCapture}`,
|
||||
/** [fullMatch, parentFieldMaybeIncludingDots, childField] */
|
||||
capturePebbleVarParent: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${dotAccessedFieldWithParentCapture}`,
|
||||
capturePebbleVarParent: `${pebbleStart}${maybeTextFollowedBySeparator}${dotAccessedFieldWithParentCapture}`,
|
||||
/** [fullMatch, functionName, textBetweenParenthesis, maybeTypedWordStart] */
|
||||
capturePebbleFunction: `${maybeAnotherPebbleExpression}${pebbleStart}${maybeTextFollowedBySeparator}${functionWithMaybeParams}`,
|
||||
capturePebbleFunction: `${pebbleStart}${maybeTextFollowedBySeparator}${functionWithMaybeParams}`,
|
||||
captureStringValue: "^[\"']([^\"']+)[\"']$"
|
||||
}
|
||||
|
||||
@@ -109,16 +109,7 @@ tasks:
|
||||
"\\")) | (.key + \\"->\\" + .value)"
|
||||
}} {{myFunc(my-param_1='value1', my-param_2="value2", myK`
|
||||
expect([...(regex.exec(shouldMatchLastFunction) ?? [])]).toEqual([
|
||||
`id: breaking-ui
|
||||
namespace: io.kestra.blx
|
||||
description: "Upload multiple files to s3 sequentially"
|
||||
|
||||
|
||||
tasks:
|
||||
- id: placeholder
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: |-
|
||||
{{
|
||||
`{{
|
||||
"to_entries[] | select(.key | startswith(\\"" +
|
||||
inputs.selector +
|
||||
"\\")) | (.key + \\"->\\" + .value)"
|
||||
|
||||
@@ -26,7 +26,9 @@ dependencies {
|
||||
|
||||
// ai
|
||||
implementation("dev.langchain4j:langchain4j")
|
||||
implementation("dev.langchain4j:langchain4j-google-ai-gemini:1.4.0")
|
||||
implementation("dev.langchain4j:langchain4j-google-ai-gemini")
|
||||
implementation("dev.langchain4j:langchain4j-http-client-jdk")
|
||||
implementation('org.bouncycastle:bcpkix-jdk18on')
|
||||
|
||||
implementation("de.siegmar:fastcsv")
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Keep the name static
|
||||
micronaut.openapi.filename=kestra
|
||||
micronaut.openapi.filename=kestra
|
||||
micronaut.openapi.constructor-arguments-as-required=false
|
||||
|
||||
@@ -44,12 +44,10 @@ import io.kestra.webserver.responses.BulkResponse;
|
||||
import io.kestra.webserver.responses.PagedResults;
|
||||
import io.kestra.webserver.services.ExecutionDependenciesStreamingService;
|
||||
import io.kestra.webserver.services.ExecutionStreamingService;
|
||||
import io.kestra.core.runners.SecureVariableRendererFactory;
|
||||
import io.kestra.webserver.utils.PageableUtils;
|
||||
import io.kestra.webserver.utils.RequestUtils;
|
||||
import io.kestra.webserver.utils.filepreview.FileRender;
|
||||
import io.kestra.webserver.utils.filepreview.FileRenderBuilder;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.context.event.ApplicationEventPublisher;
|
||||
import io.micronaut.core.annotation.Introspected;
|
||||
@@ -73,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;
|
||||
@@ -123,7 +124,7 @@ public class ExecutionController {
|
||||
@Nullable
|
||||
@Value("${micronaut.server.context-path}")
|
||||
protected String basePath;
|
||||
|
||||
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepository;
|
||||
|
||||
@@ -170,7 +171,7 @@ public class ExecutionController {
|
||||
|
||||
@Inject
|
||||
private ApplicationEventPublisher<CrudEvent<Execution>> eventPublisher;
|
||||
|
||||
|
||||
@Inject
|
||||
private RunContextFactory runContextFactory;
|
||||
|
||||
@@ -188,7 +189,7 @@ public class ExecutionController {
|
||||
|
||||
@Inject
|
||||
private Optional<OpenTelemetry> openTelemetry;
|
||||
|
||||
|
||||
@Inject
|
||||
private ExecutionStreamingService executionStreamingService;
|
||||
|
||||
@@ -208,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,
|
||||
@@ -357,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()) {
|
||||
@@ -378,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<>();
|
||||
@@ -419,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,
|
||||
@@ -668,8 +669,18 @@ 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
|
||||
public Publisher<ExecutionResponse> createExecution(
|
||||
@Parameter(description = "The flow namespace") @PathVariable String namespace,
|
||||
@@ -997,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,
|
||||
@@ -1057,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()) {
|
||||
@@ -1240,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(
|
||||
@@ -1303,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()
|
||||
@@ -1513,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,
|
||||
@@ -1622,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,
|
||||
@@ -1666,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,
|
||||
@@ -1710,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 {
|
||||
@@ -1801,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
|
||||
) {
|
||||
@@ -2018,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
|
||||
) {
|
||||
@@ -2135,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(
|
||||
@@ -2249,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,
|
||||
@@ -2339,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,
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
package io.kestra.webserver.controllers.api;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.kestra.core.docs.JsonSchemaGenerator;
|
||||
import io.kestra.core.exceptions.FlowProcessingException;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.exceptions.InternalException;
|
||||
import io.kestra.core.models.QueryFilter;
|
||||
import io.kestra.core.models.HasSource;
|
||||
import io.kestra.core.models.QueryFilter;
|
||||
import io.kestra.core.models.SearchResult;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.FlowId;
|
||||
import io.kestra.core.models.flows.FlowInterface;
|
||||
import io.kestra.core.models.flows.FlowScope;
|
||||
import io.kestra.core.models.flows.FlowWithException;
|
||||
import io.kestra.core.models.flows.FlowWithSource;
|
||||
import io.kestra.core.models.flows.GenericFlow;
|
||||
import io.kestra.core.models.flows.*;
|
||||
import io.kestra.core.models.hierarchies.FlowGraph;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.topologies.FlowTopology;
|
||||
@@ -24,7 +17,6 @@ import io.kestra.core.models.validations.ManualConstraintViolation;
|
||||
import io.kestra.core.models.validations.ModelValidator;
|
||||
import io.kestra.core.models.validations.ValidateConstraintViolation;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.repositories.FlowTopologyRepositoryInterface;
|
||||
import io.kestra.core.serializers.JacksonMapper;
|
||||
import io.kestra.core.serializers.YamlParser;
|
||||
import io.kestra.core.services.FlowService;
|
||||
@@ -54,6 +46,8 @@ 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;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -229,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,
|
||||
@@ -284,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(
|
||||
@@ -341,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.
|
||||
@@ -444,7 +439,8 @@ 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,
|
||||
@Parameter(description = "The flow id") @PathVariable String id,
|
||||
@@ -482,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(
|
||||
@@ -624,13 +620,13 @@ public class FlowController {
|
||||
@Post(uri = "/validate/task", consumes = MediaType.APPLICATION_JSON)
|
||||
@Operation(tags = {"Flows"}, summary = "Validate task")
|
||||
public ValidateConstraintViolation validateTask(
|
||||
@RequestBody(description = "The task") @Body String task
|
||||
@RequestBody(description = "The task") @Schema(implementation = Object.class) @Body String task
|
||||
) {
|
||||
ValidateConstraintViolation.ValidateConstraintViolationBuilder<?, ?> validateConstraintViolationBuilder = ValidateConstraintViolation.builder();
|
||||
|
||||
try {
|
||||
var taskParse = parseTaskTrigger(task, Task.class);
|
||||
modelValidator.validate(taskParse);
|
||||
var parsedTask = parseTaskTrigger(task, Task.class);
|
||||
modelValidator.validate(parsedTask);
|
||||
} catch (ConstraintViolationException e) {
|
||||
validateConstraintViolationBuilder.constraints(e.getMessage());
|
||||
} catch (RuntimeException re) {
|
||||
@@ -649,13 +645,13 @@ public class FlowController {
|
||||
@Post(uri = "/validate/trigger", consumes = MediaType.APPLICATION_JSON)
|
||||
@Operation(tags = {"Flows"}, summary = "Validate trigger")
|
||||
public ValidateConstraintViolation validateTrigger(
|
||||
@RequestBody(description = "The trigger") @Body String trigger
|
||||
@RequestBody(description = "The trigger") @Schema(implementation = Object.class) @Body String trigger
|
||||
) {
|
||||
ValidateConstraintViolation.ValidateConstraintViolationBuilder<?, ?> validateConstraintViolationBuilder = ValidateConstraintViolation.builder();
|
||||
|
||||
try {
|
||||
var triggerParse = parseTaskTrigger(trigger, AbstractTrigger.class);
|
||||
modelValidator.validate(triggerParse);
|
||||
var parsedTrigger = parseTaskTrigger(trigger, AbstractTrigger.class);
|
||||
modelValidator.validate(parsedTrigger);
|
||||
} catch (ConstraintViolationException e) {
|
||||
validateConstraintViolationBuilder.constraints(e.getMessage());
|
||||
} catch (RuntimeException re) {
|
||||
@@ -672,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();
|
||||
@@ -709,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);
|
||||
|
||||
@@ -747,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);
|
||||
|
||||
@@ -790,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);
|
||||
|
||||
@@ -822,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 -> {
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.kestra.webserver.services.posthog.PosthogService;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -72,7 +73,10 @@ public abstract class AiService<T extends AiConfiguration> implements AiServiceI
|
||||
|
||||
public String generateFlow(String ip, FlowGenerationPrompt flowGenerationPrompt) {
|
||||
String parentSpanId = IdUtils.create();
|
||||
Map<String, String> inputState = Map.of("flowYaml", flowGenerationPrompt.flowYaml(), "userPrompt", flowGenerationPrompt.userPrompt());
|
||||
Map<String, String> inputState = new HashMap<>();
|
||||
inputState.put("flowYaml", flowGenerationPrompt.flowYaml());
|
||||
inputState.put("userPrompt", flowGenerationPrompt.userPrompt());
|
||||
|
||||
this.postHogService.capture(flowGenerationPrompt.conversationId(), "$ai_trace", Map.of(
|
||||
"$ai_trace_id", flowGenerationPrompt.conversationId(),
|
||||
"$ai_span_name", "FlowGenerationSession",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package io.kestra.webserver.services.ai.gemini;
|
||||
|
||||
import dev.langchain4j.http.client.HttpClientBuilderLoader;
|
||||
import dev.langchain4j.http.client.jdk.JdkHttpClientBuilder;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.listener.ChatModelListener;
|
||||
import dev.langchain4j.model.googleai.GeminiThinkingConfig;
|
||||
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
|
||||
import io.kestra.core.docs.JsonSchemaGenerator;
|
||||
import io.kestra.core.plugins.PluginRegistry;
|
||||
@@ -9,10 +12,13 @@ import io.kestra.core.services.InstanceService;
|
||||
import io.kestra.core.utils.VersionProvider;
|
||||
import io.kestra.webserver.services.ai.AiService;
|
||||
import io.kestra.webserver.services.posthog.PosthogService;
|
||||
import io.kestra.webserver.utils.HttpClientUtils;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Singleton
|
||||
@@ -27,7 +33,8 @@ public class GeminiAiService extends AiService<GeminiConfiguration> {
|
||||
}
|
||||
|
||||
public ChatModel chatModel(List<ChatModelListener> listeners) {
|
||||
return GoogleAiGeminiChatModel.builder()
|
||||
GoogleAiGeminiChatModel.GoogleAiGeminiChatModelBuilder builder = GoogleAiGeminiChatModel.builder()
|
||||
.baseUrl(getAiConfiguration().baseUrl())
|
||||
.listeners(listeners)
|
||||
.modelName(getAiConfiguration().modelName())
|
||||
.apiKey(getAiConfiguration().apiKey())
|
||||
@@ -37,7 +44,22 @@ public class GeminiAiService extends AiService<GeminiConfiguration> {
|
||||
.maxOutputTokens(getAiConfiguration().maxOutputTokens())
|
||||
.logRequests(getAiConfiguration().logRequests())
|
||||
.logResponses(getAiConfiguration().logResponses())
|
||||
.build();
|
||||
.thinkingConfig(GeminiThinkingConfig.builder().includeThoughts(false).build())
|
||||
.returnThinking(false);
|
||||
|
||||
if (getAiConfiguration().clientPem() != null) {
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(getAiConfiguration().clientPem().getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayInputStream caPem = getAiConfiguration().caPem() == null ? null : new ByteArrayInputStream(getAiConfiguration().caPem().getBytes(StandardCharsets.UTF_8))) {
|
||||
JdkHttpClientBuilder jdkHttpClientBuilder = ((JdkHttpClientBuilder) HttpClientBuilderLoader.loadHttpClientBuilder()).httpClientBuilder(
|
||||
HttpClientUtils.withPemCertificate(is, caPem)
|
||||
);
|
||||
|
||||
builder = builder.httpClientBuilder(jdkHttpClientBuilder);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Exception while trying to setup AI Service certificates", e);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import io.kestra.webserver.services.ai.AiConfiguration;
|
||||
import io.micronaut.context.annotation.ConfigurationProperties;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.micronaut.core.bind.annotation.Bindable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@ConfigurationProperties(value = "kestra.ai.gemini")
|
||||
public record GeminiConfiguration (
|
||||
@Nullable
|
||||
String baseUrl,
|
||||
String apiKey,
|
||||
@Bindable(defaultValue = "gemini-2.5-flash")
|
||||
String modelName,
|
||||
@@ -16,6 +19,11 @@ public record GeminiConfiguration (
|
||||
Double topP,
|
||||
@Nullable
|
||||
Integer topK,
|
||||
@Nullable
|
||||
String clientPem,
|
||||
@Schema(description = "Not required but can be useful to add further trust", nullable = true)
|
||||
@Nullable
|
||||
String caPem,
|
||||
@Bindable(defaultValue = "8000")
|
||||
int maxOutputTokens,
|
||||
@Bindable(defaultValue = "false")
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.kestra.webserver.utils;
|
||||
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.http.HttpClient;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
|
||||
public class HttpClientUtils {
|
||||
public static HttpClient.Builder withPemCertificate(InputStream clientPemIs, InputStream caPem) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
|
||||
PrivateKey privateKey = null;
|
||||
Certificate clientCertificate = null;
|
||||
|
||||
// Parse the PEM content to extract certificate and private key
|
||||
try (PEMParser pemParser = new PEMParser(new InputStreamReader(clientPemIs))) {
|
||||
JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();
|
||||
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
|
||||
Object object;
|
||||
while ((object = pemParser.readObject()) != null) {
|
||||
if (object instanceof PrivateKeyInfo privateKeyInfo) {
|
||||
privateKey = keyConverter.getPrivateKey(privateKeyInfo);
|
||||
} else if (object instanceof X509CertificateHolder) {
|
||||
clientCertificate = certConverter.getCertificate((X509CertificateHolder) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||
keyStore.load(null, null);
|
||||
|
||||
Certificate[] privateKeyCertificatesChain = new Certificate[]{clientCertificate};
|
||||
|
||||
if (caPem != null) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
keyStore.setCertificateEntry("ca", cf.generateCertificate(caPem));
|
||||
}
|
||||
|
||||
keyStore.setKeyEntry("client-key", privateKey, "".toCharArray(), privateKeyCertificatesChain);
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, "".toCharArray());
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keyStore);
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
|
||||
|
||||
return HttpClient.newBuilder().sslContext(sslContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.kestra.webserver.controllers.api;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
|
||||
import com.github.tomakehurst.wiremock.admin.model.GetServeEventsResult;
|
||||
import com.github.tomakehurst.wiremock.http.Body;
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.webserver.models.ai.FlowGenerationPrompt;
|
||||
import io.kestra.webserver.utils.PosthogUtil;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.HttpResponse;
|
||||
import io.micronaut.http.client.HttpClient;
|
||||
import io.micronaut.http.client.annotation.Client;
|
||||
import io.kestra.core.junit.annotations.KestraTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@KestraTest
|
||||
@WireMockTest(httpPort = 28181)
|
||||
class AiControllerTest {
|
||||
@Inject
|
||||
@Client("/")
|
||||
HttpClient client;
|
||||
|
||||
@RegisterExtension
|
||||
static WireMockExtension extension = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig()
|
||||
.dynamicPort()
|
||||
.httpsPort(28183)
|
||||
.keystorePath(Objects.requireNonNull(AiControllerTest.class.getClassLoader().getResource("mtls/server-keystore.p12")).getPath())
|
||||
.keystorePassword("keystorePassword")
|
||||
.keyManagerPassword("keystorePassword")
|
||||
.keystoreType("PKCS12")
|
||||
.needClientAuth(true) // This enables mTLS
|
||||
.trustStorePath(Objects.requireNonNull(AiControllerTest.class.getClassLoader().getResource("mtls/client-truststore.p12")).getPath()) // Contains trusted client CAs
|
||||
.trustStorePassword("changeit")
|
||||
.trustStoreType("PKCS12"))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
void baseMocks(WireMockRuntimeInfo wmRuntimeInfo) {
|
||||
PosthogUtil.mockPosthog(wmRuntimeInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mTLS() {
|
||||
extension.stubFor(post(anyUrl())
|
||||
.inScenario("Regular flow generation")
|
||||
.whenScenarioStateIs("Started")
|
||||
.willReturn(
|
||||
aResponse().withResponseBody(
|
||||
Body.fromJsonBytes("""
|
||||
{
|
||||
"responseId" : "3NvjaPPRAo_WvdIP46DvmQE",
|
||||
"modelVersion" : "gemini-2.5-flash",
|
||||
"candidates" : [ {
|
||||
"content" : {
|
||||
"parts" : [ {
|
||||
"text" : "io.kestra.plugin.core.log.Log"
|
||||
} ],
|
||||
"role" : "model"
|
||||
},
|
||||
"finishReason" : "STOP",
|
||||
"index" : 0
|
||||
} ],
|
||||
"usageMetadata" : {
|
||||
"promptTokenCount" : 3658,
|
||||
"candidatesTokenCount" : 25,
|
||||
"totalTokenCount" : 3939
|
||||
}
|
||||
}""".getBytes()
|
||||
)))
|
||||
.willSetStateTo("Tasks fetched"));
|
||||
|
||||
String expectedFlowResponse = "id: my-flow\\nnamespace: io.kestra.tests\\ntasks:\\n - id: log\\n type: io.kestra.plugin.core.log.Log\\n format: \\\"hi\\\"";
|
||||
extension.stubFor(post(anyUrl())
|
||||
.inScenario("Regular flow generation")
|
||||
.whenScenarioStateIs("Tasks fetched")
|
||||
.willReturn(
|
||||
aResponse().withResponseBody(
|
||||
Body.fromJsonBytes("""
|
||||
{
|
||||
"responseId" : "3NvjaPPRAo_WvdIP46DvmQF",
|
||||
"modelVersion" : "gemini-2.5-flash",
|
||||
"candidates" : [ {
|
||||
"content" : {
|
||||
"parts" : [ {
|
||||
"text" : "%s"
|
||||
} ],
|
||||
"role" : "model"
|
||||
},
|
||||
"finishReason" : "STOP",
|
||||
"index" : 0
|
||||
} ],
|
||||
"usageMetadata" : {
|
||||
"promptTokenCount" : 3658,
|
||||
"candidatesTokenCount" : 25,
|
||||
"totalTokenCount" : 3939
|
||||
}
|
||||
}""".formatted(expectedFlowResponse).getBytes()
|
||||
))));
|
||||
|
||||
HttpResponse<String> response = client.toBlocking().exchange(
|
||||
HttpRequest.POST("/api/v1/main/ai/generate/flow", new FlowGenerationPrompt(IdUtils.create(), "Say 'hi'", null)),
|
||||
String.class
|
||||
);
|
||||
|
||||
GetServeEventsResult serveEvents = extension.getServeEvents();
|
||||
|
||||
serveEvents.getServeEvents().forEach(serveEvent -> {
|
||||
assertThat(serveEvent.getResponse().getStatus()).isEqualTo(200);
|
||||
});
|
||||
|
||||
assertThat(response.getStatus().getCode()).isEqualTo(200);
|
||||
assertThat(response.getBody().get()).isEqualTo(expectedFlowResponse.replace("\\n", "\n").replace("\\\"", "\""));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class MiscControllerTest {
|
||||
assertThat(response.getUuid()).isNotNull();
|
||||
assertThat(response.getIsTaskRunEnabled()).isFalse();
|
||||
assertThat(response.getIsAnonymousUsageEnabled()).isTrue();
|
||||
assertThat(response.getIsAiEnabled()).isFalse();
|
||||
assertThat(response.getIsAiEnabled()).isTrue();
|
||||
assertThat(response.getSystemNamespace()).isEqualTo("some.system.ns");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.kestra.webserver.utils;
|
||||
|
||||
import com.github.tomakehurst.wiremock.http.Body;
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
|
||||
public class PosthogUtil {
|
||||
public static void mockPosthog(WireMockRuntimeInfo wmRuntimeInfo) {
|
||||
stubFor(get(urlEqualTo("/v1/config"))
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withResponseBody(Body.fromJsonBytes(
|
||||
"""
|
||||
{
|
||||
"posthog": {
|
||||
"apiHost": "%s"
|
||||
}
|
||||
}""".formatted(wmRuntimeInfo.getHttpBaseUrl()).getBytes(StandardCharsets.UTF_8)
|
||||
))));
|
||||
|
||||
stubFor(post(urlEqualTo("/batch"))
|
||||
.willReturn(aResponse()));
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,123 @@ jackson:
|
||||
FAIL_ON_UNKNOWN_PROPERTIES: false
|
||||
|
||||
kestra:
|
||||
ai:
|
||||
type: gemini
|
||||
gemini:
|
||||
base-url: https://localhost:28183
|
||||
api-key: "fakeApiKey"
|
||||
ca-pem: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFCTCCAvGgAwIBAgIUQY9OI1ErtFFu3iujvLu5NzPqBrowDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTAwNjE1NTc0OFoXDTM1MTAw
|
||||
NDE1NTc0OFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAn8x9Byga8F9vBQtZTUBvAzDMLF4MGI+L7TJXAMzziAPo
|
||||
uagMq1b7V+Ni3ZBBp84fuxfgowkJ9t2B5rvUQgf+Tc6x2N/5diVyThi/GCRt1nCl
|
||||
LXATsetQqYHaoJmjBBvbRdOrtAUwvfDXWO2PZDLOrAFa29EurRTcBJ1uv/Uegcd9
|
||||
sTb9yzAvNCppLidaGwT7JVcHiYcDekEz/3jCFQKwvBWNOFgX3JvwF8+5pHVdKRgE
|
||||
/IqpgYErnJjcTWCSK0M/HkM8eUHxtztCSUkO5ak4NM521IxGA2jOQfgTVONbSjW1
|
||||
MJ4CWsdTxcHcX9TwIRbeNxBI9muRIdLcHR4l4w69QxQJtY1jGmW8fClfAoVO8Obz
|
||||
4wp7VuzkE9oTDtR95iVKO+9M15mSofkg3SFHE2O0aTIDGneEaHKH6NTIiPcLR/xW
|
||||
QV1ZFHSM7cbh0iZh5kQFphZ6UGjqXWN3xm77er/4i7QoLY4342eicqHBSS+ovoyq
|
||||
RdUYpZgyTKA5bIxiuVwcGBjyZfFpoxL50efcTLE8M01BOwHuoYtb9gARAGVotgmI
|
||||
n6O7GzrTj1z43dPH1+u2pshzz67W0+vzBz/lNXgpa3Wxwg4kFCRnw34mEu+cd+tQ
|
||||
tzSNtZm9wc0YYLT21xlFIacE2fmsWPUC28OSiLdDj3lKbJnDOogjPtEsMnTl4LsC
|
||||
AwEAAaNTMFEwHQYDVR0OBBYEFONyqmci0bm+Nu8SqHwz0FGBBgsQMB8GA1UdIwQY
|
||||
MBaAFONyqmci0bm+Nu8SqHwz0FGBBgsQMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
|
||||
hvcNAQELBQADggIBAGJGpb1vQtvBQCiC58F0jAlIoSCleifKbdlQOMcanMpr/zaG
|
||||
U4L2MLZGVuWpmfEW1VPyxzbECsU2tHcWno4iJ7dj7PDPEBMKwWTa55SNXIU227Ef
|
||||
lYMfnXYwgg/9Q6K/U8uXvhB3hquDE03YV2xrOhWP+fROCAgTnOMI3XvYcBpTMhFb
|
||||
YZifc4HUWNM0+M6cSWV4D4NgI/piY5dvcGwv5U/WrlaYAD+HBJ9/xyaLcirIhRm4
|
||||
qZUBfhwA2zuzVoC45iA3hm7Go2eK5UCxHGXzi3CAi2fD8/ibhi05ExkDKkePP4BI
|
||||
WOCgOJ2zGv9KqE8Bq962ipshGl6ceIdOE0uRLhLWVWAA/lgtBkP1871WlXjcdf0t
|
||||
9GZ2ORMwlLZo7SvQ4EKXrfmrg24nW5Bf+R6vd67nWiGopnUdgE8cdzEP1U0YnXTq
|
||||
bs6kr2RpdNw/0EyDOU7/lJYvZUh2/ImSEe014B+WwLy7VsFHzqwz/aaivlcwpsrt
|
||||
BWY7NfBWEPQnofbE4MOH0G6ueFcYUbBWP3EG2BN0hz7rTueQPemCb6NrWR09XPs9
|
||||
AMVo3oupFL0WR6acXV87fCukNTVLEEn5UHKFSOgoPD+vy1dHXov1Ej2VwGP94Dec
|
||||
mRdgSSfyRhyPGwlxjaiPcTLIkaJBEdR3H5Xzws87QT+SlKiCOrFjksPvYWXs
|
||||
-----END CERTIFICATE-----
|
||||
client-pem: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE+DCCAuCgAwIBAgIUbbuIICr5CKGTNxe2P9a0D2xuwx4wDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTAwNjE1NTgwNFoXDTMwMDQx
|
||||
MzE1NTgwNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAtuVdYOFn5rXr4EocBKuvCsPLXAWJzgioUlDZ9LDjdS+7
|
||||
L3i+cW/3Msig7ihv/85cYjNeVVnB3JslgcpcE5KMSkdjZiEvd7LeSbfWYINTTX3m
|
||||
xQFg2XZm6xH6bCX9yhe32jtr/Ib9eS+Z+YpoPzeqGuC8gXHwZi621Al17NdXEEJD
|
||||
qmVtRsPFUE0d9/E4uPlxJ92jqV8XyG6QColQRWGUpmLWjF3eVtdBGmqr454PDI6x
|
||||
brKpoIWF5qwcjHzEg7QcWJrAS/nRf3S0SYxEChEodASgqsyI1sCi2qbC98Mjk0UE
|
||||
gqaxvcQNmIlGyxHHugr1UGc1FbziRVxwJnLS9ybGDOj1LZPXbW1kkz5+vSvByWNf
|
||||
nISk9FKtdS7ehsHh1Sv55kGijC4f7cE2nf+WGXWv2j9gyBjhU3LIE0efjXHRBqpc
|
||||
9Bw+8DvXK/JXM1uexTLlxlC9fy+vuo8DJYaThQTJIFRwY/TDNdcoQze+O+wjlNe0
|
||||
TPjMQScGODGMKvduDrnGeht7qi2mFnczdskfYVTTCJ5YX/rXQFdK6XlGSK3ZQZeJ
|
||||
4kZm2UZrtHE2z4RununXONmqCLT63aVrdZI8drs+zKH5xFvHfZgonMuV5JVJsZSt
|
||||
JpM2K2sRk9W9h5OegsEpymVJLxAbXD3KNUa7sWv/NRnfFfRyGJjuGesbV6bTCM0C
|
||||
AwEAAaNCMEAwHQYDVR0OBBYEFMBvh+ehi6bnz+E7UgX64J9CEEYWMB8GA1UdIwQY
|
||||
MBaAFONyqmci0bm+Nu8SqHwz0FGBBgsQMA0GCSqGSIb3DQEBCwUAA4ICAQB6jHm1
|
||||
/QUF22ThKuFSsw8scPKdKmDaxdoZkQ5JMUOAa1T9FzUpjYCn0yUFe/YmpwrF0ubA
|
||||
+SOQZIfijC5iRsftexavNCmc1B/Z91uaplF9PocI/MkuDPIa3vFQioPw3z0H6ue4
|
||||
m5mr0S7tUwt9dKJdPIGy5Zw2oq1JXpq04XL+fvigoNCh3i09xIraBEuzA1ra9PKM
|
||||
IJXuj340MuNF+HkvEwlKRkSeQpNDinaIphzoUPXP/oeo6ite3lWuurz8kgrTNFAz
|
||||
Em82pFaYH5RGWDaVJb1z/plhfAC4AZ4xylb3tbIAamCjJoWzGwFq3odKhIS1YkBv
|
||||
yBJsW2f52dEOzs6l0xVZy+f8GfwFS3UAOjzeAWOaw1BJ8Zk8Tt5gQ0DOKV2Wum8x
|
||||
PO3K4ITs7Ep/QNHHk7QtaTOBNPK8iA0RRkn63p/3TlA2E716KyCPmkMEvrRojocz
|
||||
eJvUGW/TJmRkQVp5cI1ALDuuQJgxBD7k6JnIuzMALOaySLqCkwCZz/Idb7ojX219
|
||||
K8g24o/vP83SgfTWd1FIon8wBnfgV1nGOQeTB9Rp5+ve4fOmVAcctY2bZNVRmA2h
|
||||
ExqHQaOTUCm3W04sWQPZRTdf14LQvlP5qvUdgX04bmLPphDqABFuYkPRoTcutGm2
|
||||
UtYRdDVx60ei9XePaJbsNJctiDPvQJX9nhZjcQ==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC25V1g4Wfmtevg
|
||||
ShwEq68Kw8tcBYnOCKhSUNn0sON1L7sveL5xb/cyyKDuKG//zlxiM15VWcHcmyWB
|
||||
ylwTkoxKR2NmIS93st5Jt9Zgg1NNfebFAWDZdmbrEfpsJf3KF7faO2v8hv15L5n5
|
||||
img/N6oa4LyBcfBmLrbUCXXs11cQQkOqZW1Gw8VQTR338Ti4+XEn3aOpXxfIbpAK
|
||||
iVBFYZSmYtaMXd5W10Eaaqvjng8MjrFusqmghYXmrByMfMSDtBxYmsBL+dF/dLRJ
|
||||
jEQKESh0BKCqzIjWwKLapsL3wyOTRQSCprG9xA2YiUbLEce6CvVQZzUVvOJFXHAm
|
||||
ctL3JsYM6PUtk9dtbWSTPn69K8HJY1+chKT0Uq11Lt6GweHVK/nmQaKMLh/twTad
|
||||
/5YZda/aP2DIGOFTcsgTR5+NcdEGqlz0HD7wO9cr8lczW57FMuXGUL1/L6+6jwMl
|
||||
hpOFBMkgVHBj9MM11yhDN7477COU17RM+MxBJwY4MYwq924OucZ6G3uqLaYWdzN2
|
||||
yR9hVNMInlhf+tdAV0rpeUZIrdlBl4niRmbZRmu0cTbPhG6e6dc42aoItPrdpWt1
|
||||
kjx2uz7MofnEW8d9mCicy5XklUmxlK0mkzYraxGT1b2Hk56CwSnKZUkvEBtcPco1
|
||||
Rruxa/81Gd8V9HIYmO4Z6xtXptMIzQIDAQABAoICAAzt0Y2NMZ6bYqAZgmSJdxdw
|
||||
OC/PoRUPA0hvuGX18TEHXZihVPkePJ/QPfbH+1mJJCUO/jlTXdhzS+dNFPg55lqZ
|
||||
NFDJF3AYlWJ/BpO5BN+tVEMTR149YqYgaw76b+KO4wDK2Ds4QbKE58ITri3giKZb
|
||||
bQf2NCkiXpXw+0ScWRETAMc1YfsG0NZ4inNMupac5N+4E8nf09sShx0vkByaJXt4
|
||||
8N4TA/995Pdx7f0VCc74nANiyYn0QRR0Oz+/0YEr73W2BCelcnwA6ykTK3t4s17h
|
||||
it1iwGj0oi6JwQOUaFnl9EfWHSojMgDZR3IIgObJFTFeB7XQg0BQT19Fw7MUAHD/
|
||||
mCVg/s37QLhbb6BhIWUlLdEhV+Bqu5sa+GyCQyNpDPLyYTyIXcqYGBuqt6ZTdeqx
|
||||
4rOqUQRW5tUhklHT3BHjGDehYTlT4VVpfce9Yji0BiHqieJgY4ohJa6EbpR80GSI
|
||||
ynADH7VVF84XNONlhzCUcJVvHhKNUjY7cHGK2thzIyl+MXb/lBYFt63/sg2mwnYH
|
||||
1DqE+G+NaZbPS5JPmNAvP+AjmDcuq32ImSK0RPJM66hdBGTcrjYzH5q496Vsbdrr
|
||||
6Y7scRzVuRx7tmSjfRQMSfWuK9MYqTZxHGdVjN27kyaG6+8OYfOEfU6avT4pSWzV
|
||||
hC/cGeVDsqIO0pQtJlqBAoIBAQDbxLsKXTqOz7HYISf8csFEEHuLahQsL8PEK++p
|
||||
SPhiVZH88bqochaMi4nqe8BEa4zbl0YGKrXL4VTzS8Jyg2f91jPoOt9m94gQ53Oj
|
||||
AQjPzYgXpEkctJsRSGciIoawNvJzUi8eDfoSa4LtrM6zR3n+A2uxNmw7p1N+6dhG
|
||||
dcicKibxSkhmKlhfGdSJBqCptBwYIQ61eDS3CaA7j5WYZ3tA46o/XlrREUUIy5ke
|
||||
Sh11MuiQcuB5N7VvHJngSewY4htLIcStOSk9JqOlIuLiewo8C6BdZN6zrdU/vY4M
|
||||
svdH5/iKdTo2a7AAuV8GffuoMZw3+xb4t90prIe7u7+6qIWNAoIBAQDVDHBypU9s
|
||||
4wSglJx7qOcHLDnEJ47ODLfgsu6fkLYro95sFFCb/pfEViRdkLCROhbl7MTZ4T9u
|
||||
yWsPrnVF+8mxNVNEL9PgYHBmNdXQsTf51nZFyC4vUHxsoztOMdtSOqsjeZOoWliY
|
||||
rhkBSRozspdUaM2f1z0DMydlD5ZIXSYFZHFfBYrxoZRPzEnhtqtZan3FjgZ39lSo
|
||||
UvTQSEB4SNTlOrrxqA3LTm7ZtM7vIKI/zvwLivP5UewQYA1GmRqKKYXpB34e7goB
|
||||
jGHtu+ZckyE+MusYLkPpqpRJYmROVZP6N3144r+S1g4J8ZCmtz7puTy77WfpPSUt
|
||||
0XUTbIFiFaBBAoIBAQDPuErxqNzITxdRqUUaH3z80Hd1dnZKrXrj2INWBlp+11J9
|
||||
Oh2rSOp3PQzGTOGVyfIBPCI7gfMDGaAptdm8UuffzK6TOdIeiKhbEekCkN+7ShDw
|
||||
B5/zOeG3nC+e2/Niaw0OYweV6LAM6QF/lG5qlYyAwsrvXPlACQ+qTWzWbE2JDW5x
|
||||
cjysFCoi+U8hlNoWjN5hEB3O+CcbOkXxBe3ndyfQVV99NbmxEhmmDopTTso5FD0t
|
||||
CueQq08aDnaCwFwfyNbzVJ+I+xY3bmYOl8LLPnCWAIc6vzSfsBZ3gLra1e1UUbVh
|
||||
aRv0hCR/Crb+c+WBPCLj5rf3rhGkaiaBExxNWSuhAoIBAHphUN6qUvuLVIchltn5
|
||||
5eva3bvttTxrVdy/LA+AwwRCd2vCJ6PUFT309aLBkIt7wNeGsHUvzI5JKTSy1C7F
|
||||
OdCPfys2BhFnlGlCF3ZvtmXPadUf7lfDdhW5lkGOphuQE+qm1cjpTOc3aqmwYlq8
|
||||
Be243hUpQKTr539H+t3KlCKY1f7tYij21gkYooADvF36rBClStXyLCMctABI4K+3
|
||||
toOOvwA9tt7ISSjJke0O4+Sfc2z1/ruC8YVeh4G8ROCEhaWcZjhCKIFHMKGtQ+B9
|
||||
q6Tc/uq++Mfq8o9M862DvyMGaQ7dwYoJZ7sShjMeJAeCHn8dGomCVC8DmKr1s0Sy
|
||||
g4ECggEAVd9jSlvn9374COKieU4sxO01Ms7/ICyyIAe1lXZCp7BdPzbxnKB2REgl
|
||||
nWbpZ3nxogp7vsk3wBYo38uvXjR/ReE1yp6jUcBZgcGqh1hHWwTNLVkTrOrg5UpV
|
||||
6LJ9v3REI5N0kNQM+4OubhrS62dDCpzAON6LRyHoi3zFdTTgTm0XFx7ttybx2Xn1
|
||||
Ze1FgzhMERJaBS/xhQfVFczUZ2Zmv5D3SDhlDIZLXQEc+qHn9Qm5pfVdxnqcAqA8
|
||||
gjEw0qur7SE6U7WN/CrsvqMDIdiBjoNh9yyVW9b+eJJ8qH4L+AJ2B6LoxOAlE+7O
|
||||
NGLwUUB3wlL2w0B6cBCFQ2J3TtRVBQ==
|
||||
-----END PRIVATE KEY-----
|
||||
url: http://localhost:8081
|
||||
encryption:
|
||||
secret-key: I6EGNzRESu3X3pKZidrqCGOHQFUFC0yK
|
||||
@@ -77,4 +194,4 @@ flyway:
|
||||
locations:
|
||||
- classpath:migrations/h2
|
||||
ignore-migration-patterns: "*:missing,*:future"
|
||||
out-of-order: true
|
||||
out-of-order: true
|
||||
|
||||
BIN
webserver/src/test/resources/mtls/client-truststore.p12
Normal file
BIN
webserver/src/test/resources/mtls/client-truststore.p12
Normal file
Binary file not shown.
BIN
webserver/src/test/resources/mtls/server-keystore.p12
Normal file
BIN
webserver/src/test/resources/mtls/server-keystore.p12
Normal file
Binary file not shown.
Reference in New Issue
Block a user