Repository: kestra-io/kestra
Issue: [UI] Grey out the trigger in the topology view when the trigger is disabled
Labels: bug

### Describe the issue

This used to be greyed out, but now it's not - it seems like a **regression**.

In addition, that trigger in the topology view should also be greyed out when the API-side toggle for the trigger is set to disabled.

![image](https://github.com/kestra-io/kestra/assets/86264395/15619c62-88bb-4c93-805b-cce201d6a4c5)


### Environment

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


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/models/hierarchies/AbstractGraphTrigger.java b/core/src/main/java/io/kestra/core/models/hierarchies/AbstractGraphTrigger.java
index 3515447115..976afab992 100644
--- a/core/src/main/java/io/kestra/core/models/hierarchies/AbstractGraphTrigger.java
+++ b/core/src/main/java/io/kestra/core/models/hierarchies/AbstractGraphTrigger.java
@@ -1,6 +1,7 @@
 package io.kestra.core.models.hierarchies;
 
 import io.kestra.core.models.triggers.AbstractTrigger;
+import io.kestra.core.models.triggers.Trigger;
 import io.micronaut.core.annotation.Introspected;
 import lombok.Getter;
 import lombok.ToString;
@@ -9,18 +10,20 @@
 @Getter
 @Introspected
 public abstract class AbstractGraphTrigger extends AbstractGraph {
-    private final AbstractTrigger trigger;
+    private final AbstractTrigger triggerDeclaration;
+    private final Trigger trigger;
 
-    public AbstractGraphTrigger(AbstractTrigger trigger) {
+    public AbstractGraphTrigger(AbstractTrigger triggerDeclaration, Trigger trigger) {
         super();
 
+        this.triggerDeclaration = triggerDeclaration;
         this.trigger = trigger;
     }
 
     @Override
     public String getUid() {
-        if (this.trigger != null) {
-            return this.trigger.getId();
+        if (this.triggerDeclaration != null) {
+            return this.triggerDeclaration.getId();
         }
 
         return this.uid;
diff --git a/core/src/main/java/io/kestra/core/models/hierarchies/GraphTrigger.java b/core/src/main/java/io/kestra/core/models/hierarchies/GraphTrigger.java
index 584dc2b206..131a25c277 100644
--- a/core/src/main/java/io/kestra/core/models/hierarchies/GraphTrigger.java
+++ b/core/src/main/java/io/kestra/core/models/hierarchies/GraphTrigger.java
@@ -1,10 +1,11 @@
 package io.kestra.core.models.hierarchies;
 
 import io.kestra.core.models.triggers.AbstractTrigger;
+import io.kestra.core.models.triggers.Trigger;
 
 
 public class GraphTrigger extends AbstractGraphTrigger {
-    public GraphTrigger(AbstractTrigger trigger) {
-        super(trigger);
+    public GraphTrigger(AbstractTrigger triggerDeclaration, Trigger trigger) {
+        super(triggerDeclaration, trigger);
     }
 }
diff --git a/core/src/main/java/io/kestra/core/services/GraphService.java b/core/src/main/java/io/kestra/core/services/GraphService.java
index 5729d2027a..2a59af363b 100644
--- a/core/src/main/java/io/kestra/core/services/GraphService.java
+++ b/core/src/main/java/io/kestra/core/services/GraphService.java
@@ -1,11 +1,15 @@
 package io.kestra.core.services;
 
 import io.kestra.core.exceptions.IllegalVariableEvaluationException;
+import io.kestra.core.models.executions.Execution;
 import io.kestra.core.models.flows.Flow;
 import io.kestra.core.models.hierarchies.*;
+import io.kestra.core.models.triggers.Trigger;
 import io.kestra.core.repositories.FlowRepositoryInterface;
+import io.kestra.core.repositories.TriggerRepositoryInterface;
 import io.kestra.core.utils.GraphUtils;
 import io.kestra.core.utils.Rethrow;
+import io.micronaut.data.model.Pageable;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 import lombok.extern.slf4j.Slf4j;
@@ -19,20 +23,38 @@ public class GraphService {
     @Inject
     private FlowRepositoryInterface flowRepository;
     @Inject
+    private TriggerRepositoryInterface triggerRepository;
+    @Inject
     private TaskDefaultService taskDefaultService;
 
     public FlowGraph flowGraph(Flow flow, List<String> expandedSubflows) throws IllegalVariableEvaluationException {
-        return FlowGraph.of(this.of(flow, Optional.ofNullable(expandedSubflows).orElse(Collections.emptyList()), new HashMap<>()));
+        return this.flowGraph(flow, expandedSubflows, null);
+    }
+
+    public FlowGraph flowGraph(Flow flow, List<String> expandedSubflows, Execution execution) throws IllegalVariableEvaluationException {
+        return FlowGraph.of(this.of(flow, Optional.ofNullable(expandedSubflows).orElse(Collections.emptyList()), new HashMap<>(), execution));
     }
 
     public GraphCluster of(Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid) throws IllegalVariableEvaluationException {
-        return this.of(null, flow, expandedSubflows, flowByUid);
+        return this.of(flow, expandedSubflows, flowByUid, null);
+    }
+
+    public GraphCluster of(Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid, Execution execution) throws IllegalVariableEvaluationException {
+        return this.of(null, flow, expandedSubflows, flowByUid, execution);
     }
 
     public GraphCluster of(GraphCluster baseGraph, Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid) throws IllegalVariableEvaluationException {
+        return this.of(baseGraph, flow, expandedSubflows, flowByUid, null);
+    }
+
+    public GraphCluster of(GraphCluster baseGraph, Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid, Execution execution) throws IllegalVariableEvaluationException {
         String tenantId = flow.getTenantId();
         flow = taskDefaultService.injectDefaults(flow);
-        GraphCluster graphCluster = GraphUtils.of(baseGraph, flow, null);
+        List<Trigger> triggers = null;
+        if (flow.getTriggers() != null) {
+            triggers = triggerRepository.find(Pageable.UNPAGED, null, tenantId, flow.getNamespace(), flow.getId());
+        }
+        GraphCluster graphCluster = GraphUtils.of(baseGraph, flow, execution, triggers);
 
 
         Stream<Map.Entry<GraphCluster, SubflowGraphTask>> subflowToReplaceByParent = graphCluster.allNodesByParent().entrySet().stream()
diff --git a/core/src/main/java/io/kestra/core/utils/GraphUtils.java b/core/src/main/java/io/kestra/core/utils/GraphUtils.java
index d3b24e0dd5..503438c1a0 100644
--- a/core/src/main/java/io/kestra/core/utils/GraphUtils.java
+++ b/core/src/main/java/io/kestra/core/utils/GraphUtils.java
@@ -9,27 +9,33 @@
 import io.kestra.core.models.tasks.FlowableTask;
 import io.kestra.core.models.tasks.Task;
 import io.kestra.core.models.triggers.AbstractTrigger;
+import io.kestra.core.models.triggers.Trigger;
 import io.kestra.core.tasks.flows.Dag;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public class GraphUtils {
     public static FlowGraph flowGraph(Flow flow, Execution execution) throws IllegalVariableEvaluationException {
-        return FlowGraph.of(GraphUtils.of(flow, execution));
+        return GraphUtils.flowGraph(flow, execution, null);
     }
 
-    public static GraphCluster of(GraphCluster graph, Flow flow, Execution execution) throws IllegalVariableEvaluationException {
+    public static FlowGraph flowGraph(Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
+        return FlowGraph.of(GraphUtils.of(flow, execution, triggers));
+    }
+
+    public static GraphCluster of(GraphCluster graph, Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
         if (graph == null) {
             graph = new GraphCluster();
         }
 
         if (flow.getTriggers() != null) {
-            GraphCluster triggers = GraphUtils.triggers(graph, flow.getTriggers());
-            graph.addEdge(triggers.getEnd(), graph.getRoot(), new Relation());
+            GraphCluster triggersClusters = GraphUtils.triggers(graph, flow.getTriggers(), triggers);
+            graph.addEdge(triggersClusters.getEnd(), graph.getRoot(), new Relation());
         }
 
         GraphUtils.sequential(
@@ -44,16 +50,29 @@ public static GraphCluster of(GraphCluster graph, Flow flow, Execution execution
     }
 
     public static GraphCluster of(Flow flow, Execution execution) throws IllegalVariableEvaluationException {
-        return GraphUtils.of(new GraphCluster(), flow, execution);
+        return GraphUtils.of(flow, execution, null);
+    }
+
+    public static GraphCluster of(Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
+        return GraphUtils.of(new GraphCluster(), flow, execution, triggers);
     }
 
-    public static GraphCluster triggers(GraphCluster graph, List<AbstractTrigger> triggers) throws IllegalVariableEvaluationException {
+    public static GraphCluster triggers(GraphCluster graph, List<AbstractTrigger> triggersDeclarations, List<Trigger> triggers) throws IllegalVariableEvaluationException {
         GraphCluster triggerCluster = new GraphCluster("Triggers");
 
         graph.addNode(triggerCluster);
 
-        triggers.forEach(trigger -> {
-            GraphTrigger triggerNode = new GraphTrigger(trigger);
+        Map<String, Trigger> triggersById = Optional.ofNullable(triggers)
+            .map(Collection::stream)
+            .map(s -> s.collect(Collectors.toMap(
+                Trigger::getTriggerId,
+                Function.identity(),
+                (a, b) -> a.getNamespace().length() <= b.getNamespace().length() ? a : b
+            )))
+            .orElse(Collections.emptyMap());
+
+        triggersDeclarations.forEach(trigger -> {
+            GraphTrigger triggerNode = new GraphTrigger(trigger, triggersById.get(trigger.getId()));
             triggerCluster.addNode(triggerNode);
             triggerCluster.addEdge(triggerCluster.getRoot(), triggerNode, new Relation());
             triggerCluster.addEdge(triggerNode, triggerCluster.getEnd(), new Relation());
diff --git a/repository-memory/src/main/java/io/kestra/repository/memory/MemoryTriggerRepository.java b/repository-memory/src/main/java/io/kestra/repository/memory/MemoryTriggerRepository.java
index 99de0803ba..16ac786b10 100644
--- a/repository-memory/src/main/java/io/kestra/repository/memory/MemoryTriggerRepository.java
+++ b/repository-memory/src/main/java/io/kestra/repository/memory/MemoryTriggerRepository.java
@@ -65,6 +65,21 @@ public ArrayListTotal<Trigger> find(Pageable from, String query, String tenantId
 
     @Override
     public ArrayListTotal<Trigger> find(Pageable from, String query, String tenantId, String namespace, String flowId) {
-        throw new UnsupportedOperationException();
+        List<Trigger> filteredTriggers = triggers.stream().filter(trigger -> {
+            if (tenantId != null && !tenantId.equals(trigger.getTenantId())) {
+                return false;
+            }
+
+            if (namespace != null && !namespace.equals(trigger.getNamespace())) {
+                return false;
+            }
+
+            if (flowId != null && !flowId.equals(trigger.getFlowId())) {
+                return false;
+            }
+
+            return true;
+        }).toList();
+        return new ArrayListTotal<>(filteredTriggers, filteredTriggers.size());
     }
 }
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 995fba80ae..84397a56d9 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -8,7 +8,7 @@
       "name": "kestra",
       "version": "0.1.0",
       "dependencies": {
-        "@kestra-io/ui-libs": "^0.0.37",
+        "@kestra-io/ui-libs": "^0.0.38",
         "@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
         "@vue-flow/background": "^1.2.0",
         "@vue-flow/controls": "1.0.6",
@@ -292,9 +292,9 @@
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
     "node_modules/@kestra-io/ui-libs": {
-      "version": "0.0.37",
-      "resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.37.tgz",
-      "integrity": "sha512-86l7zSjXkjyjc1pKTH3sYj4NemWdgq718yUiif8zye8XaI1np/gAeHtovyS+VF57Sx5rd3exKzMnN7HDwFgb0A==",
+      "version": "0.0.38",
+      "resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.38.tgz",
+      "integrity": "sha512-PbToBOVhkyuqyrOIMaN+6aLrEHtSgVhrAE6751rOm7WC2z7HRnxvAdodyQFWSB+FuRsooxz+4f4WZH/RYddoqQ==",
       "peerDependencies": {
         "@vue-flow/background": "^1.2.0",
         "@vue-flow/controls": "1.0.6",
diff --git a/ui/package.json b/ui/package.json
index 13bd1d8098..b375984dd0 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -12,7 +12,7 @@
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix"
   },
   "dependencies": {
-    "@kestra-io/ui-libs": "^0.0.37",
+    "@kestra-io/ui-libs": "^0.0.38",
     "@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
     "@vue-flow/background": "^1.2.0",
     "@vue-flow/controls": "1.0.6",
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/ExecutionController.java b/webserver/src/main/java/io/kestra/webserver/controllers/ExecutionController.java
index 313e3360bb..9da26bcb45 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/ExecutionController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/ExecutionController.java
@@ -27,11 +27,11 @@
 import io.kestra.core.runners.RunnerUtils;
 import io.kestra.core.services.ConditionService;
 import io.kestra.core.services.ExecutionService;
+import io.kestra.core.services.GraphService;
 import io.kestra.core.storages.StorageContext;
 import io.kestra.core.storages.StorageInterface;
 import io.kestra.core.tenant.TenantService;
 import io.kestra.core.utils.Await;
-import io.kestra.core.utils.GraphUtils;
 import io.kestra.webserver.responses.BulkErrorResponse;
 import io.kestra.webserver.responses.BulkResponse;
 import io.kestra.webserver.responses.PagedResults;
@@ -115,6 +115,9 @@ public class ExecutionController {
     @Inject
     protected ExecutionRepositoryInterface executionRepository;
 
+    @Inject
+    private GraphService graphService;
+
     @Inject
     private RunnerUtils runnerUtils;
 
@@ -217,7 +220,7 @@ public FlowGraph flowGraph(
                 );
 
                 return flow
-                    .map(throwFunction(value -> GraphUtils.flowGraph(value, execution)))
+                    .map(throwFunction(value -> graphService.flowGraph(value, null,  execution)))
                     .orElse(null);
             }))
             .orElse(null);


Test patch:
diff --git a/core/src/test/java/io/kestra/core/models/hierarchies/FlowGraphTest.java b/core/src/test/java/io/kestra/core/models/hierarchies/FlowGraphTest.java
index 735ec4050f..b0eb73c9bc 100644
--- a/core/src/test/java/io/kestra/core/models/hierarchies/FlowGraphTest.java
+++ b/core/src/test/java/io/kestra/core/models/hierarchies/FlowGraphTest.java
@@ -4,6 +4,8 @@
 import io.kestra.core.exceptions.InternalException;
 import io.kestra.core.models.executions.Execution;
 import io.kestra.core.models.flows.Flow;
+import io.kestra.core.models.triggers.Trigger;
+import io.kestra.core.repositories.TriggerRepositoryInterface;
 import io.kestra.core.runners.AbstractMemoryRunnerTest;
 import io.kestra.core.serializers.YamlFlowParser;
 import io.kestra.core.services.GraphService;
@@ -11,6 +13,7 @@
 import io.kestra.core.tasks.flows.Switch;
 import io.kestra.core.utils.GraphUtils;
 import io.kestra.core.utils.TestsUtils;
+import io.micronaut.data.model.Pageable;
 import jakarta.inject.Inject;
 import org.junit.jupiter.api.Test;
 
@@ -31,6 +34,9 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
     @Inject
     private GraphService graphService;
 
+    @Inject
+    private TriggerRepositoryInterface triggerRepositoryInterface;
+
     @Test
     void simple() throws IllegalVariableEvaluationException {
         Flow flow = this.parse("flows/valids/return.yaml");
@@ -205,11 +211,17 @@ void eachWithExecution() throws TimeoutException, IllegalVariableEvaluationExcep
     @Test
     void trigger() throws IllegalVariableEvaluationException {
         Flow flow = this.parse("flows/valids/trigger-flow-listener.yaml");
-        FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
+        triggerRepositoryInterface.save(
+            Trigger.of(flow, flow.getTriggers().get(0)).toBuilder().disabled(true).build()
+        );
+
+        FlowGraph flowGraph = graphService.flowGraph(flow, null);
 
         assertThat(flowGraph.getNodes().size(), is(6));
         assertThat(flowGraph.getEdges().size(), is(5));
         assertThat(flowGraph.getClusters().size(), is(1));
+        AbstractGraph triggerGraph = flowGraph.getNodes().stream().filter(e -> e instanceof GraphTrigger).findFirst().orElseThrow();
+        assertThat(((GraphTrigger) triggerGraph).getTrigger().getDisabled(), is(true));
     }
 
     @Test
diff --git a/core/src/test/java/io/kestra/core/repositories/AbstractTriggerRepositoryTest.java b/core/src/test/java/io/kestra/core/repositories/AbstractTriggerRepositoryTest.java
index de1c6a7184..672dada625 100644
--- a/core/src/test/java/io/kestra/core/repositories/AbstractTriggerRepositoryTest.java
+++ b/core/src/test/java/io/kestra/core/repositories/AbstractTriggerRepositoryTest.java
@@ -72,6 +72,10 @@ void all() {
         assertThat(find.size(), is(4));
         assertThat(find.get(0).getNamespace(), is(namespace));
 
+        find = triggerRepository.find(Pageable.from(1, 4, Sort.of(Sort.Order.asc("namespace"))), null, null, null, searchedTrigger.getFlowId());
+        assertThat(find.size(), is(1));
+        assertThat(find.get(0).getFlowId(), is(searchedTrigger.getFlowId()));
+
         find = triggerRepository.find(Pageable.from(1, 100, Sort.of(Sort.Order.asc(triggerRepository.sortMapping().apply("triggerId")))), null, null, namespacePrefix);
         assertThat(find.size(), is(1));
         assertThat(find.get(0).getTriggerId(), is(trigger.getTriggerId()));