Compare commits

...

17 Commits

Author SHA1 Message Date
YannC
c757827b9d chore: upgrade to version 0.16.2 2024-04-22 18:49:39 +02:00
YannC
2a5c82b2a3 fix(scheduler): better handling of locked triggers (#3603) 2024-04-22 18:47:17 +02:00
Loïc Mathieu
15bb0ee65b fea(core): mandate that both key and value are present for labels 2024-04-22 18:47:05 +02:00
Ludovic DEHON
d06e8dad6e fix(core): handle secret in trigger 2024-04-22 18:46:57 +02:00
brian.mulier
e9f5752278 fix(cli): API commands work against a pre-micronaut-upgrade server 2024-04-22 18:46:50 +02:00
brian.mulier
c16c5ddaf5 chore(deps): update ui-libs to 0.0.43 2024-04-22 18:46:44 +02:00
Ludovic DEHON
b706ca1911 fix(ui): flow full revision is truncated
close #3478
2024-04-22 18:46:36 +02:00
brian.mulier
366246e0a8 fix(ui): Gantt clicks are working again 2024-04-22 18:46:28 +02:00
brian.mulier
dcea4551cc fix(ui): prevent editor shrink on loading task runner doc 2024-04-22 18:46:22 +02:00
brian.mulier
c0ff6fcc52 fix(tests): add real launch to outputDirDisabled test for task runners 2024-04-22 18:46:16 +02:00
Florian Hussonnois
0525e7eaca fix(core): VariableRenderer should expose alternativeRender 2024-04-22 18:46:06 +02:00
YannC
d1fb098f5b chore(version): update to version 'v0.16.1' 2024-04-15 17:04:32 +02:00
YannC
9703cc48cb feat(ui): click anywhere on the row to open logs of a task in Gantt vue 2024-04-15 17:02:18 +02:00
YannC
31c3e5a4f6 feat(ui): set plugins menu back in the UI (#3558) 2024-04-15 17:02:13 +02:00
brian.mulier
bda52eb49d fix(ui): use new Monaco API for decorations to prevent editor from disappearing
closes #3536
2024-04-15 17:02:00 +02:00
brian.mulier
8a54b8ec7f fix(validate): restore ability to run validate command without any configuration 2024-04-15 17:01:34 +02:00
Loïc Mathieu
c34c82c1f9 fix: downgrade Micronaut
Go back to previously working version 4.3.4 as 4.3.7 have a bug when randomly the routeMatch is null on the security filter.
See https://github.com/kestra-io/kestra-ee/issues/1085
2024-04-15 17:01:24 +02:00
25 changed files with 265 additions and 56 deletions

View File

@@ -197,6 +197,7 @@ subprojects {
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 'KESTRA_TEST1', "true"
environment 'KESTRA_TEST2', "Pass by env"
}

View File

@@ -3,10 +3,15 @@ package io.kestra.cli;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MediaType;
import io.micronaut.http.MutableHttpRequest;
import io.micronaut.http.body.ContextlessMessageBodyHandlerRegistry;
import io.micronaut.http.body.MessageBodyHandlerRegistry;
import io.micronaut.http.client.DefaultHttpClientConfiguration;
import io.micronaut.http.client.HttpClientConfiguration;
import io.micronaut.http.client.netty.DefaultHttpClient;
import io.micronaut.http.netty.body.NettyJsonHandler;
import io.micronaut.json.JsonMapper;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import picocli.CommandLine;
@@ -39,7 +44,12 @@ public abstract class AbstractApiCommand extends AbstractCommand {
private HttpClientConfiguration httpClientConfiguration;
protected DefaultHttpClient client() throws URISyntaxException {
return new DefaultHttpClient(server.toURI(), httpClientConfiguration != null ? httpClientConfiguration : new DefaultHttpClientConfiguration());
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(server.toURI(), httpClientConfiguration != null ? httpClientConfiguration : new DefaultHttpClientConfiguration());
MessageBodyHandlerRegistry defaultHandlerRegistry = defaultHttpClient.getHandlerRegistry();
if (defaultHandlerRegistry instanceof ContextlessMessageBodyHandlerRegistry modifiableRegistry) {
modifiableRegistry.add(MediaType.TEXT_JSON_TYPE, new NettyJsonHandler<>(JsonMapper.createDefault()));
}
return defaultHttpClient;
}
protected <T> HttpRequest<T> requestOptions(MutableHttpRequest<T> request) {

View File

@@ -19,7 +19,7 @@ class FlowValidateCommandTest {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
EmbeddedServer embeddedServer = ctx.getBean(EmbeddedServer.class);
embeddedServer.start();
@@ -39,7 +39,7 @@ class FlowValidateCommandTest {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
EmbeddedServer embeddedServer = ctx.getBean(EmbeddedServer.class);
embeddedServer.start();

View File

@@ -532,6 +532,10 @@ public class RunContext {
this.initBean(applicationContext);
this.initLogger(workerTrigger.getTriggerContext(), workerTrigger.getTrigger());
Map<String, Object> clone = new HashMap<>(this.variables);
clone.put("addSecretConsumer", (Consumer<String>) s -> runContextLogger.usedSecret(s));
this.variables = ImmutableMap.copyOf(clone);
// Mutability hack to update the triggerExecutionId for each evaluation on the worker
return forScheduler(workerTrigger.getTriggerContext(), workerTrigger.getTrigger());
}

View File

@@ -99,10 +99,16 @@ public class VariableRenderer {
Writer writer = new JsonWriter(new StringWriter());
compiledTemplate.evaluate(writer, variables);
result = writer.toString();
} catch (IOException e) {
throw new IllegalVariableEvaluationException(e);
} catch (PebbleException e) {
throw properPebbleException(e);
} catch (IOException | PebbleException e) {
String alternativeRender = this.alternativeRender(e, inline, variables);
if (alternativeRender == null) {
if (e instanceof PebbleException) {
throw properPebbleException((PebbleException) e);
}
throw new IllegalVariableEvaluationException(e);
} else {
result = alternativeRender;
}
}
// post-process raw tags
@@ -111,6 +117,18 @@ public class VariableRenderer {
return result;
}
/**
* This method can be used in fallback for rendering an input string.
*
* @param e The exception that was throw by the default variable renderer.
* @param inline The expression to be rendered.
* @param variables The context variables.
* @return The rendered string.
*/
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
return null;
}
private static String putBackRawTags(Map<String, String> replacers, String result) {
for (var entry : replacers.entrySet()) {
result = result.replace(entry.getKey(), entry.getValue());

View File

@@ -352,6 +352,11 @@ public abstract class AbstractScheduler implements Scheduler, Service {
.conditionContext(flowWithTriggers.getConditionContext())
.triggerContext(flowWithTriggers.TriggerContext.toBuilder().date(now()).stopAfter(flowWithTriggers.getAbstractTrigger().getStopAfter()).build())
.build())
.peek(f -> {
if (f.getTriggerContext().getEvaluateRunningDate() != null || isExecutionNotRunning(f)) {
this.triggerState.unlock(f.getTriggerContext());
}
})
.filter(f -> f.getTriggerContext().getEvaluateRunningDate() == null)
.filter(this::isExecutionNotRunning)
.map(FlowWithPollingTriggerNextDate::of)

View File

@@ -26,6 +26,13 @@ public interface SchedulerTriggerStateInterface {
List<Trigger> findByNextExecutionDateReadyForAllTenants(ZonedDateTime now, ScheduleContextInterface scheduleContext);
// Required for Kafka
/**
* Required for Kafka
*/
List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext);
/**
* Required for Kafka
*/
void unlock(Trigger trigger);
}

View File

@@ -36,6 +36,8 @@ import java.util.stream.StreamSupport;
@Singleton
@Slf4j
public class FlowService {
private final IllegalStateException NO_REPOSITORY_EXCEPTION = new IllegalStateException("No flow repository found. Make sure the `kestra.repository.type` property is set.");
@Inject
RunContextFactory runContextFactory;
@@ -43,7 +45,7 @@ public class FlowService {
ConditionService conditionService;
@Inject
FlowRepositoryInterface flowRepository;
Optional<FlowRepositoryInterface> flowRepository;
@Inject
YamlFlowParser yamlFlowParser;
@@ -62,6 +64,11 @@ public class FlowService {
.tenantId(tenantId)
.build();
if (flowRepository.isEmpty()) {
throw NO_REPOSITORY_EXCEPTION;
}
FlowRepositoryInterface flowRepository = this.flowRepository.get();
return flowRepository
.findById(withTenant.getTenantId(), withTenant.getNamespace(), withTenant.getId())
.map(previous -> flowRepository.update(withTenant, previous, source, taskDefaultService.injectDefaults(withTenant)))
@@ -69,7 +76,11 @@ public class FlowService {
}
public List<FlowWithSource> findByNamespaceWithSource(String tenantId, String namespace) {
return flowRepository.findByNamespaceWithSource(tenantId, namespace);
if (flowRepository.isEmpty()) {
throw NO_REPOSITORY_EXCEPTION;
}
return flowRepository.get().findByNamespaceWithSource(tenantId, namespace);
}
public Stream<Flow> keepLastVersion(Stream<Flow> stream) {

View File

@@ -34,6 +34,7 @@ public class TaskDefaultService {
@Inject
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
@Nullable
protected QueueInterface<LogEntry> logQueue;
/**

View File

@@ -51,10 +51,15 @@ 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(ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("echo 'Hello World'")));
var taskRunner = taskRunner();
assertThat(taskRunner.additionalVars(runContext, commands).containsKey(ScriptService.VAR_OUTPUT_DIR), is(false));
assertThat(taskRunner.env(runContext, commands).containsKey(ScriptService.ENV_OUTPUT_DIR), is(false));
var result = taskRunner.run(runContext, commands, Collections.emptyList(), Collections.emptyList());
assertThat(result, notNullValue());
assertThat(result.getExitCode(), is(0));
}
@Test

View File

@@ -5,8 +5,6 @@ import io.kestra.core.models.executions.LogEntry;
import io.kestra.core.models.flows.Flow;
import io.kestra.core.queues.QueueFactoryInterface;
import io.kestra.core.queues.QueueInterface;
import io.kestra.core.tasks.log.Log;
import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.TestsUtils;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
@@ -40,7 +38,6 @@ class RunContextLoggerTest {
Flow flow = TestsUtils.mockFlow();
Execution execution = TestsUtils.mockExecution(flow, Map.of());
Log log = Log.builder().id(IdUtils.create()).type(Log.class.getName()).build();
RunContextLogger runContextLogger = new RunContextLogger(
logQueue,
@@ -86,14 +83,13 @@ class RunContextLoggerTest {
}
@Test
void secrets() throws InterruptedException {
void secrets() {
List<LogEntry> logs = new CopyOnWriteArrayList<>();
List<LogEntry> matchingLog;
logQueue.receive(either -> logs.add(either.getLeft()));
Flow flow = TestsUtils.mockFlow();
Execution execution = TestsUtils.mockExecution(flow, Map.of());
Log log = Log.builder().id(IdUtils.create()).type(Log.class.getName()).build();
RunContextLogger runContextLogger = new RunContextLogger(
logQueue,

View File

@@ -4,6 +4,8 @@ import io.kestra.core.encryption.EncryptionService;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.kestra.core.metrics.MetricRegistry;
import io.kestra.core.models.Label;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.conditions.ConditionContext;
import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.executions.LogEntry;
import io.kestra.core.models.executions.TaskRun;
@@ -14,16 +16,27 @@ import io.kestra.core.models.flows.State;
import io.kestra.core.models.flows.Type;
import io.kestra.core.models.flows.input.StringInput;
import io.kestra.core.models.tasks.common.EncryptedString;
import io.kestra.core.models.triggers.AbstractTrigger;
import io.kestra.core.models.triggers.PollingTriggerInterface;
import io.kestra.core.models.triggers.TriggerContext;
import io.kestra.core.queues.QueueFactoryInterface;
import io.kestra.core.queues.QueueInterface;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.tasks.test.PollingTrigger;
import io.kestra.core.tasks.test.SleepTrigger;
import io.kestra.core.utils.IdUtils;
import io.kestra.core.utils.TestsUtils;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Value;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.exparity.hamcrest.date.ZonedDateTimeMatchers;
import org.junit.jupiter.api.Test;
import org.slf4j.event.Level;
@@ -47,6 +60,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
@Property(name = "kestra.tasks.tmp-dir.path", value = "/tmp/sub/dir/tmp/")
class RunContextTest extends AbstractMemoryRunnerTest {
@Inject
ApplicationContext applicationContext;
@Inject
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
QueueInterface<LogEntry> workerTaskLogQueue;
@@ -66,6 +82,10 @@ class RunContextTest extends AbstractMemoryRunnerTest {
@Value("${kestra.encryption.secret-key}")
private String secretKey;
@Inject
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
private QueueInterface<LogEntry> logQueue;
@Test
void logs() throws TimeoutException {
List<LogEntry> logs = new CopyOnWriteArrayList<>();
@@ -289,4 +309,57 @@ class RunContextTest extends AbstractMemoryRunnerTest {
));
assertThat(rendered.get("key"), is("value"));
}
@Test
void secretTrigger() throws IllegalVariableEvaluationException {
List<LogEntry> logs = new CopyOnWriteArrayList<>();
List<LogEntry> matchingLog;
logQueue.receive(either -> logs.add(either.getLeft()));
LogTrigger trigger = LogTrigger.builder()
.type(SleepTrigger.class.getName())
.id("unit-test")
.format("john {{ secret('PASSWORD') }} doe")
.build();
Map.Entry<ConditionContext, TriggerContext> mockedTrigger = TestsUtils.mockTrigger(runContextFactory, trigger);
WorkerTrigger workerTrigger = WorkerTrigger.builder()
.trigger(trigger)
.triggerContext(mockedTrigger.getValue())
.conditionContext(mockedTrigger.getKey())
.build();
RunContext runContext = mockedTrigger.getKey().getRunContext().forWorker(applicationContext, workerTrigger);
Optional<Execution> evaluate = trigger.evaluate(mockedTrigger.getKey().withRunContext(runContext), mockedTrigger.getValue());
matchingLog = TestsUtils.awaitLogs(logs, 3);
assertThat(matchingLog.stream().filter(logEntry -> logEntry.getLevel().equals(Level.INFO)).findFirst().orElse(null).getMessage(), is("john ******** doe"));
}
@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
public static class LogTrigger extends AbstractTrigger implements PollingTriggerInterface {
@PluginProperty
@NotNull
private String format;
@Override
public Optional<Execution> evaluate(ConditionContext conditionContext, TriggerContext context) throws IllegalVariableEvaluationException {
conditionContext.getRunContext().logger().info(conditionContext.getRunContext().render(format));
return Optional.empty();
}
@Override
public Duration getInterval() {
return null;
}
}
}

View File

@@ -0,0 +1,43 @@
package io.kestra.core.runners;
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
import io.micronaut.context.ApplicationContext;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Map;
@MicronautTest
class VariableRendererTest {
@Inject
ApplicationContext applicationContext;
@Inject
VariableRenderer.VariableConfiguration variableConfiguration;
@Test
void shouldRenderUsingAlternativeRendering() throws IllegalVariableEvaluationException {
TestVariableRenderer renderer = new TestVariableRenderer(applicationContext, variableConfiguration);
String render = renderer.render("{{ dummy }}", Map.of());
Assertions.assertEquals("result", render);
}
public static class TestVariableRenderer extends VariableRenderer {
public TestVariableRenderer(ApplicationContext applicationContext,
VariableConfiguration variableConfiguration) {
super(applicationContext, variableConfiguration);
}
@Override
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
return "result";
}
}
}

View File

@@ -1,7 +1,8 @@
version=0.16.0
version=0.16.2
jacksonVersion=2.16.2
micronautVersion=4.3.7
# Cannot upgrade for now due to https://github.com/kestra-io/kestra-ee/issues/1085
micronautVersion=4.3.4
lombokVersion=1.18.32
slf4jVersion=2.0.12

View File

@@ -84,4 +84,7 @@ public class JdbcSchedulerTriggerState implements SchedulerTriggerStateInterface
public List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext) {
throw new NotImplementedException();
}
@Override
public void unlock(Trigger trigger) {}
}

View File

@@ -79,4 +79,7 @@ public class MemorySchedulerTriggerState implements SchedulerTriggerStateInterfa
public List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext) {
throw new NotImplementedException();
}
@Override
public void unlock(Trigger trigger) {}
}

8
ui/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "kestra",
"version": "0.1.0",
"dependencies": {
"@kestra-io/ui-libs": "^0.0.42",
"@kestra-io/ui-libs": "^0.0.43",
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.1",
"@vue-flow/core": "^1.33.5",
@@ -704,9 +704,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@kestra-io/ui-libs": {
"version": "0.0.42",
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.42.tgz",
"integrity": "sha512-3Uti8oK94KDNcxxxpODdWk7Ed1BUzGNdKOrJXMt0IfYqrCLJ3bh11C92oBJyS4LN5PHxZjLsAFay/akiGNavtA==",
"version": "0.0.43",
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.43.tgz",
"integrity": "sha512-tDGJD+yTn3L5e+c64hJBYb3qOzpw0y3OZML8nXb3trZ5/q0s1TkruY1twu5MrJxhZzNXUr0EA/VlxEQZ2ZKVCQ==",
"peerDependencies": {
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.1",

View File

@@ -12,7 +12,7 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix"
},
"dependencies": {
"@kestra-io/ui-libs": "^0.0.42",
"@kestra-io/ui-libs": "^0.0.43",
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.1",
"@vue-flow/core": "^1.33.5",

View File

@@ -25,7 +25,7 @@
</span>
</el-tooltip>
</th>
<td :colspan="dates.length">
<td :colspan="dates.length" @click="onTaskSelect(serie.task)" class="cursor-pointer">
<el-tooltip placement="top" :persistent="false" transition="" :hide-after="0">
<template #content>
<span style="white-space: pre-wrap;">
@@ -35,7 +35,7 @@
<div
:style="{left: serie.start + '%', width: serie.width + '%'}"
class="task-progress"
@click="onTaskSelect(serie.task)"
@click="onTaskSelect(serie.id)"
>
<div class="progress">
<div
@@ -58,7 +58,7 @@
@follow="forwardEvent('follow', $event)"
:target-execution="execution"
:target-flow="flow"
:show-logs="taskTypeByTaskRunId[serie.task.id] !== 'io.kestra.core.tasks.flows.ForEachItem'"
:show-logs="taskTypeByTaskRunId[serie.id] !== 'io.kestra.core.tasks.flows.ForEachItem'"
/>
</td>
</tr>
@@ -294,13 +294,13 @@
}
this.dates = dates;
},
onTaskSelect(taskRun) {
if(this.selectedTaskRuns.includes(taskRun.id)) {
this.selectedTaskRuns = this.selectedTaskRuns.filter(id => id !== taskRun.id);
onTaskSelect(taskRunId) {
if(this.selectedTaskRuns.includes(taskRunId)) {
this.selectedTaskRuns = this.selectedTaskRuns.filter(id => id !== taskRunId);
return
}
this.selectedTaskRuns.push(taskRun.id);
this.selectedTaskRuns.push(taskRunId);
},
stopRealTime() {
this.realTime = false
@@ -323,6 +323,10 @@
}
.cursor-pointer {
cursor: pointer;
}
table {
table-layout: fixed;
width: 100%;

View File

@@ -79,7 +79,7 @@
<h5>{{ $t("revision") + `: ` + revision }}</h5>
</template>
<editor v-model="revisionYaml" lang="yaml" />
<editor v-model="revisionYaml" lang="yaml" :full-height="false" :input="true" :navbar="false" :read-only="true" />
</drawer>
</div>
<div v-else>

View File

@@ -102,7 +102,6 @@
BookMultipleOutline: shallowRef(BookMultipleOutline),
Close: shallowRef(Close)
},
oldDecorations: [],
editorDocumentation: undefined,
plugin: undefined,
taskType: undefined,
@@ -209,6 +208,8 @@
this.editor = editor;
this.decorations = this.editor.createDecorationsCollection();
if (!this.original) {
this.editor.onDidBlurEditorWidget(() => {
this.$emit("focusout", editor.getValue());
@@ -308,27 +309,27 @@
this.editor.onDidContentSizeChange(_ => {
if (this.guidedProperties.monacoRange) {
editor.revealLine(this.guidedProperties.monacoRange.endLineNumber);
let decorations = [
{
range: this.guidedProperties.monacoRange,
options: {
isWholeLine: true,
inlineClassName: "highlight-text"
},
className: "highlight-text",
}
];
decorations = this.guidedProperties.monacoDisableRange ? decorations.concat([
{
const decorationsToAdd = [];
decorationsToAdd.push({
range: this.guidedProperties.monacoRange,
options: {
isWholeLine: true,
inlineClassName: "highlight-text"
},
className: "highlight-text",
});
if (this.guidedProperties.monacoDisableRange) {
decorationsToAdd.push({
range: this.guidedProperties.monacoDisableRange,
options: {
isWholeLine: true,
inlineClassName: "disable-text"
},
className: "disable-text",
},
]) : decorations;
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations)
});
}
this.decorations.set(decorationsToAdd);
} else {
this.highlightPebble();
}
@@ -363,14 +364,14 @@
highlightPebble() {
// Highlight code that match pebble content
let model = this.editor.getModel();
let decorations = [];
let text = model.getValue();
let regex = new RegExp("\\{\\{(.+?)}}", "g");
let match;
const decorationsToAdd = [];
while ((match = regex.exec(text)) !== null) {
let startPos = model.getPositionAt(match.index);
let endPos = model.getPositionAt(match.index + match[0].length);
decorations.push({
decorationsToAdd.push({
range: {
startLineNumber: startPos.lineNumber,
startColumn: startPos.column,
@@ -382,7 +383,7 @@
}
});
}
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations);
this.decorations.set(decorationsToAdd);
}
},
};

View File

@@ -809,7 +809,7 @@
ref="editorDomElement"
v-if="combinedEditor || viewType === editorViewTypes.SOURCE"
:class="combinedEditor ? 'editor-combined' : ''"
:style="combinedEditor ? {'flex-basis': leftEditorWidth, 'flex-grow': 0} : {}"
:style="combinedEditor ? {'flex': '0 0 ' + leftEditorWidth} : {}"
@save="save"
@execute="execute"
v-model="flowYaml"

View File

@@ -45,6 +45,7 @@
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
import {mapState} from "vuex";
import ChartBoxOutline from "vue-material-design-icons/ChartBoxOutline.vue";
import Connection from "vue-material-design-icons/Connection.vue";
import {shallowRef} from "vue";
export default {
@@ -169,6 +170,15 @@
class: "menu-icon"
},
},
{
href: {name: "plugins/list"},
routes: this.routeStartWith("plugins"),
title: this.$t("plugins.names"),
icon: {
element: shallowRef(Connection),
class: "menu-icon"
},
},
{
title: this.$t("administration"),
routes: this.routeStartWith("admin"),

View File

@@ -4,7 +4,6 @@ import io.micronaut.http.HttpStatus;
import io.micronaut.http.exceptions.HttpStatusException;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -15,7 +14,7 @@ public class RequestUtils {
.stream()
.map(s -> {
String[] split = s.split("[: ]+");
if (split.length < 2) {
if (split.length < 2 || split[0] == null || split[0].isEmpty()) {
throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Invalid queryString parameter");
}

View File

@@ -24,10 +24,7 @@ import io.kestra.webserver.responses.BulkResponse;
import io.kestra.webserver.responses.PagedResults;
import io.micronaut.core.type.Argument;
import io.micronaut.data.model.Pageable;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.*;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.http.client.multipart.MultipartBody;
@@ -1086,4 +1083,21 @@ class ExecutionControllerTest extends JdbcH2ControllerTest {
assertThat(response.getCount(), is(3));
}
@Test
void nullLabels() {
MultipartBody requestBody = createInputsFlowBody();
// null keys are forbidden
MutableHttpRequest<MultipartBody> requestNullKey = HttpRequest
.POST("/api/v1/executions/" + TESTS_FLOW_NS + "/inputs?labels=:value", requestBody)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE);
assertThrows(HttpClientResponseException.class, () -> client.toBlocking().retrieve(requestNullKey, Execution.class));
// null values are forbidden
MutableHttpRequest<MultipartBody> requestNullValue = HttpRequest
.POST("/api/v1/executions/" + TESTS_FLOW_NS + "/inputs?labels=key:", requestBody)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE);
assertThrows(HttpClientResponseException.class, () -> client.toBlocking().retrieve(requestNullValue, Execution.class));
}
}