Repository: kestra-io/kestra (java)

Issue:
FlowLabelsCondition didn't work
### Describe the issue

Try with the following two flows:

```yaml
id: flow-listener
namespace: myteam

tasks:
  - id: print_status
    type: io.kestra.core.tasks.log.Log
    message: hello World

triggers:
  - id: flow
    type: io.kestra.core.models.triggers.types.Flow
    conditions:
      - type: io.kestra.core.models.conditions.types.FlowNamespaceCondition
        namespace: myteam
      - type: io.kestra.core.models.conditions.types.FlowLabelsCondition
        labels:
          key: value
```

and

```yaml
id: hello-world
namespace: myteam

labels:
  key: value

tasks:
  - id: hellow-world
    type: io.kestra.core.tasks.log.Log
    message: Hello World
```

Executing the flow hellow-world didn't trigger the flow flow-listener.

### Environment

- Kestra Version:
- Operating System (OS/Docker/Kubernetes):
- Java Version (if you don't run kestra in Docker):


Pull request:
fix(core): remove FlowLabelsCondition and deprecate FlowNamespaceCond…
…ition

Fixes #3517 and #3324

Patch:
diff --git a/core/src/main/java/io/kestra/core/models/conditions/types/ExecutionNamespaceCondition.java b/core/src/main/java/io/kestra/core/models/conditions/types/ExecutionNamespaceCondition.java
index fcf646ef2..3ac4d8ab3 100644
--- a/core/src/main/java/io/kestra/core/models/conditions/types/ExecutionNamespaceCondition.java
+++ b/core/src/main/java/io/kestra/core/models/conditions/types/ExecutionNamespaceCondition.java
@@ -1,5 +1,6 @@
 package io.kestra.core.models.conditions.types;
 
+import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonSetter;
 import io.kestra.core.exceptions.IllegalConditionEvaluation;
 import io.kestra.core.exceptions.InternalException;
@@ -40,23 +41,24 @@ import java.util.function.BiPredicate;
 public class ExecutionNamespaceCondition extends Condition {
     @NotNull
     @Schema(
-        description = "String against which to match the execution namespace depending on the provided comparison."
+        title = "String against which to match the execution namespace depending on the provided comparison."
     )
     @PluginProperty
     private String namespace;
 
     @Schema(
-        description = "Comparison to use when checking if namespace matches. If not provided, it will use `EQUALS` by default."
+        title = "Comparison to use when checking if namespace matches. If not provided, it will use `EQUALS` by default."
     )
     @PluginProperty
     private Comparison comparison;
 
-    @JsonSetter
-    public void setPrefix(boolean prefix) {
-        if (this.comparison == null) {
-            this.comparison = prefix ? Comparison.PREFIX : Comparison.EQUALS;
-        }
-    }
+    @Schema(
+        title = "Whether to look at the flow namespace by prefix. Shortcut for `comparison: PREFIX`.",
+        description = "Only used when `comparison` is not set"
+    )
+    @PluginProperty
+    @Builder.Default
+    private boolean prefix = false;
 
     @Override
     public boolean test(ConditionContext conditionContext) throws InternalException {
@@ -64,7 +66,7 @@ public class ExecutionNamespaceCondition extends Condition {
             throw new IllegalConditionEvaluation("Invalid condition with null execution");
         }
 
-        return Optional.ofNullable(this.comparison).orElse(Comparison.EQUALS)
+        return Optional.ofNullable(this.comparison).orElse(prefix ? Comparison.PREFIX : Comparison.EQUALS)
             .test(conditionContext.getExecution().getNamespace(), this.namespace);
     }
 
diff --git a/core/src/main/java/io/kestra/core/models/conditions/types/FlowLabelsCondition.java b/core/src/main/java/io/kestra/core/models/conditions/types/FlowLabelsCondition.java
deleted file mode 100644
index ff4222a6e..000000000
--- a/core/src/main/java/io/kestra/core/models/conditions/types/FlowLabelsCondition.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package io.kestra.core.models.conditions.types;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import io.kestra.core.exceptions.InternalException;
-import io.kestra.core.models.Label;
-import io.kestra.core.models.annotations.Example;
-import io.kestra.core.models.annotations.Plugin;
-import io.kestra.core.models.annotations.PluginProperty;
-import io.kestra.core.models.conditions.Condition;
-import io.kestra.core.models.conditions.ConditionContext;
-import io.kestra.core.serializers.ListOrMapOfLabelDeserializer;
-import io.kestra.core.serializers.ListOrMapOfLabelSerializer;
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotNull;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.ToString;
-import lombok.experimental.SuperBuilder;
-
-import java.util.List;
-import java.util.Map;
-
-@SuperBuilder
-@ToString
-@EqualsAndHashCode
-@Getter
-@NoArgsConstructor
-@Schema(
-    title = "Condition that check labels of a flow."
-)
-@Plugin(
-    examples = {
-        @Example(
-            full = true,
-            code = {
-                "- conditions:",
-                "    - type: io.kestra.core.models.conditions.types.FlowLabelsCondition",
-                "      labels:",
-                "         owner: john.doe"
-            }
-        )
-    }
-)
-public class FlowLabelsCondition extends Condition {
-
-    @JsonSerialize(using = ListOrMapOfLabelSerializer.class)
-    @JsonDeserialize(using = ListOrMapOfLabelDeserializer.class)
-    @NotNull
-    @Schema(
-        description = "List of labels to match in the flow.",
-        implementation = Object.class, anyOf = {List.class, Map.class}
-    )
-    @PluginProperty
-    List<Label> labels;
-
-    @Override
-    public boolean test(ConditionContext conditionContext) throws InternalException {
-        for (Label label : this.labels) {
-            if (conditionContext.getFlow().getLabels() == null || !conditionContext.getFlow().getLabels().contains(label)) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
diff --git a/core/src/main/java/io/kestra/core/models/conditions/types/FlowNamespaceCondition.java b/core/src/main/java/io/kestra/core/models/conditions/types/FlowNamespaceCondition.java
index d574be080..445ada74b 100644
--- a/core/src/main/java/io/kestra/core/models/conditions/types/FlowNamespaceCondition.java
+++ b/core/src/main/java/io/kestra/core/models/conditions/types/FlowNamespaceCondition.java
@@ -18,7 +18,8 @@ import jakarta.validation.constraints.NotNull;
 @Getter
 @NoArgsConstructor
 @Schema(
-    title = "Condition for a flow namespace."
+    title = "Condition for a flow namespace.",
+    description = "Use `io.kestra.core.models.conditions.types.ExecutionNamespaceCondition` instead."
 )
 @Plugin(
     examples = {
@@ -34,6 +35,7 @@ import jakarta.validation.constraints.NotNull;
         )
     }
 )
+@Deprecated
 public class FlowNamespaceCondition extends Condition {
     @NotNull
     @Schema(
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/api/PluginController.java b/webserver/src/main/java/io/kestra/webserver/controllers/api/PluginController.java
index da9deec4e..bbf2b56cf 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/api/PluginController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/api/PluginController.java
@@ -12,12 +12,10 @@ import io.kestra.core.plugins.RegisteredPlugin;
 import io.kestra.core.services.PluginService;
 import io.micronaut.cache.annotation.Cacheable;
 import io.micronaut.core.annotation.NonNull;
+import io.micronaut.http.HttpHeaders;
 import io.micronaut.http.HttpResponse;
 import io.micronaut.http.MutableHttpResponse;
-import io.micronaut.http.annotation.Controller;
-import io.micronaut.http.annotation.Get;
-import io.micronaut.http.annotation.PathVariable;
-import io.micronaut.http.annotation.QueryValue;
+import io.micronaut.http.annotation.*;
 import io.micronaut.scheduling.TaskExecutors;
 import io.micronaut.scheduling.annotation.ExecuteOn;
 import io.micronaut.validation.Validated;
@@ -35,6 +33,8 @@ import static io.kestra.core.utils.Rethrow.throwFunction;
 @Validated
 @Controller("/api/v1/plugins/")
 public class PluginController {
+    private static final String CACHE_DIRECTIVE = "public, max-age=3600";
+
     @Inject
     private JsonSchemaGenerator jsonSchemaGenerator;
 
@@ -104,7 +104,7 @@ public class PluginController {
                     classInputDocumentation.getDefs()
                 )
             ))
-            .header("Cache-Control", "public, max-age=3600");
+            .header(HttpHeaders.CACHE_CONTROL, CACHE_DIRECTIVE);
     }
 
     @Cacheable("default")
@@ -128,8 +128,8 @@ public class PluginController {
     @Get(uri = "icons")
     @ExecuteOn(TaskExecutors.IO)
     @Operation(tags = {"Plugins"}, summary = "Get plugins icons")
-    public Map<String, PluginIcon> icons() {
-        return pluginService
+    public MutableHttpResponse<Map<String, PluginIcon>> icons() {
+        Map<String, PluginIcon> icons = pluginService
             .allPlugins()
             .stream()
             .flatMap(plugin -> Stream
@@ -154,13 +154,14 @@ public class PluginController {
             )
             .filter(entry -> entry.getKey() != null)
             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
+        return HttpResponse.ok(icons).header(HttpHeaders.CACHE_CONTROL, CACHE_DIRECTIVE);
     }
 
     @Get(uri = "icons/groups")
     @ExecuteOn(TaskExecutors.IO)
     @Operation(tags = {"Plugins"}, summary = "Get plugins icons")
-    public Map<String, PluginIcon> pluginGroupIcons() {
-        return pluginService
+    public MutableHttpResponse<Map<String, PluginIcon>> pluginGroupIcons() {
+        Map<String, PluginIcon> icons = pluginService
             .allPlugins()
             .stream()
             .filter(plugin -> plugin.group() != null)
@@ -169,6 +170,7 @@ public class PluginController {
                 plugin -> new PluginIcon("plugin-icon", plugin.icon("plugin-icon"), false),
                 (a1, a2) -> a1
             ));
+        return HttpResponse.ok(icons).header(HttpHeaders.CACHE_CONTROL, CACHE_DIRECTIVE);
     }
 
     @SuppressWarnings({"rawtypes", "unchecked"})
@@ -196,7 +198,7 @@ public class PluginController {
                     classPluginDocumentation.getDefs()
                 )
             ))
-            .header("Cache-Control", "public, max-age=3600");
+            .header(HttpHeaders.CACHE_CONTROL, CACHE_DIRECTIVE);
     }
 
     @SuppressWarnings({"rawtypes", "unchecked"})


Test patch:
diff --git a/core/src/test/java/io/kestra/core/models/conditions/types/ExecutionNamespaceConditionTest.java b/core/src/test/java/io/kestra/core/models/conditions/types/ExecutionNamespaceConditionTest.java
index 013ba5892..285fa2b1a 100644
--- a/core/src/test/java/io/kestra/core/models/conditions/types/ExecutionNamespaceConditionTest.java
+++ b/core/src/test/java/io/kestra/core/models/conditions/types/ExecutionNamespaceConditionTest.java
@@ -79,6 +79,14 @@ class ExecutionNamespaceConditionTest {
 
         test = conditionService.isValid(build, flow, execution);
         assertThat(test, is(true));
+
+        build = ExecutionNamespaceCondition.builder()
+            .namespace(flow.getNamespace().substring(0, 3))
+            .prefix(true)
+            .build();
+
+        test = conditionService.isValid(build, flow, execution);
+        assertThat(test, is(true));
     }
 
     @Test
diff --git a/core/src/test/java/io/kestra/core/models/conditions/types/FlowLabelsConditionTest.java b/core/src/test/java/io/kestra/core/models/conditions/types/FlowLabelsConditionTest.java
deleted file mode 100644
index 95b203260..000000000
--- a/core/src/test/java/io/kestra/core/models/conditions/types/FlowLabelsConditionTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package io.kestra.core.models.conditions.types;
-
-import com.google.common.collect.ImmutableMap;
-import io.kestra.core.models.Label;
-import io.kestra.core.models.executions.Execution;
-import io.kestra.core.models.flows.Flow;
-import io.kestra.core.services.ConditionService;
-import io.kestra.core.utils.TestsUtils;
-import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
-import jakarta.inject.Inject;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-@MicronautTest
-public class FlowLabelsConditionTest {
-    @Inject
-    ConditionService conditionService;
-
-    @Test
-    void valid() {
-        Flow flow = TestsUtils.mockFlow()
-            .toBuilder()
-            .labels(List.of(new Label("key", "value")))
-            .build();
-        Execution execution = TestsUtils .mockExecution(flow, ImmutableMap.of());
-
-        FlowLabelsCondition build = FlowLabelsCondition.builder()
-            .labels(List.of(new Label("key", "value")))
-            .build();
-
-        boolean test = conditionService.isValid(build, flow, execution);
-
-        assertThat(test, is(true));
-    }
-
-    @Test
-    void validMultiples() {
-        Flow flow = TestsUtils.mockFlow()
-            .toBuilder()
-            .labels(List.of(new Label("key", "value"), new Label("key2", "value2")))
-            .build();
-        Execution execution = TestsUtils .mockExecution(flow, ImmutableMap.of());
-
-        FlowLabelsCondition build = FlowLabelsCondition.builder()
-            .labels(List.of(new Label("key", "value"), new Label("key2", "value2")))
-            .build();
-
-        boolean test = conditionService.isValid(build, flow, execution);
-
-        assertThat(test, is(true));
-    }
-
-    @Test
-    void invalid() {
-        Flow flow = TestsUtils.mockFlow();
-        Execution execution = TestsUtils.mockExecution(flow, ImmutableMap.of());
-
-        FlowLabelsCondition build = FlowLabelsCondition.builder()
-            .labels(List.of(new Label("key", "value")))
-            .build();
-
-        boolean test = conditionService.isValid(build, flow, execution);
-
-        assertThat(test, is(false));
-    }
-
-    @Test
-    void invalidMultiples() {
-        Flow flow = TestsUtils.mockFlow()
-            .toBuilder()
-            .labels(List.of(new Label("key", "value")))
-            .build();
-        Execution execution = TestsUtils.mockExecution(flow, ImmutableMap.of());
-
-        FlowLabelsCondition build = FlowLabelsCondition.builder()
-            .labels(List.of(new Label("key", "value"), new Label("key2", "value2")))
-            .build();
-
-        boolean test = conditionService.isValid(build, flow, execution);
-
-        assertThat(test, is(false));
-    }
-}