Repository: kestra-io/kestra
Issue: Nested metrics don't show up in Flow Metric tab
Labels: bug

### Expected Behavior

All metrics should be available at the Flow Metric levels, even nested one

### Actual Behaviour

When an execution have nested tasks (for example if task), sub task don't show up in the main Metric dashboard.

![image](https://github.com/kestra-io/kestra/assets/46634684/5eab7a83-5418-4842-8715-5305017c57ee)
![image](https://github.com/kestra-io/kestra/assets/46634684/6db6e531-c5af-4c39-8043-7767d42b0f07)


### Steps To Reproduce

_No response_

### Environment Information

- Kestra Version: 0.9.0
- Operating System (OS / Docker / Kubernetes): Docker
- Java Version (If not docker):


### Example flow

```  
- id: isLoading
    type: io.kestra.core.tasks.flows.If
    condition: "{{ inputs.is_load == 'true' }}"
    then:
      - id: load
        type: io.kestra.plugin.gcp.bigquery.LoadFromGcs
        from:
          - '{{ outputs.extract.uri}}'
        destinationTable: "kestra-dev.scraping_demo.cars_data_{{ taskrun.startDate | date('YYYYMMdd') }}"
        autodetect: true
        format: JSON
        
```

Issue comments:

Pull request review comments:
A unit test please? no controller entry point should always have one :sweat_smile: 

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/repositories/MetricRepositoryInterface.java b/core/src/main/java/io/kestra/core/repositories/MetricRepositoryInterface.java
index f76eb0394b..722b17b765 100644
--- a/core/src/main/java/io/kestra/core/repositories/MetricRepositoryInterface.java
+++ b/core/src/main/java/io/kestra/core/repositories/MetricRepositoryInterface.java
@@ -21,6 +21,8 @@ public interface MetricRepositoryInterface extends SaveRepositoryInterface<Metri
 
     List<String> taskMetrics(String namespace, String flowId, String taskId);
 
+    List<String> tasksWithMetrics(String namespace, String flowId);
+
     MetricAggregations aggregateByFlowId(String namespace, String flowId, @Nullable String taskId, String metric, ZonedDateTime startDate, ZonedDateTime endDate, String aggregation);
 
     Integer purge(Execution execution);
diff --git a/jdbc/src/main/java/io/kestra/jdbc/repository/AbstractJdbcMetricRepository.java b/jdbc/src/main/java/io/kestra/jdbc/repository/AbstractJdbcMetricRepository.java
index e0b9055b38..964a41441d 100644
--- a/jdbc/src/main/java/io/kestra/jdbc/repository/AbstractJdbcMetricRepository.java
+++ b/jdbc/src/main/java/io/kestra/jdbc/repository/AbstractJdbcMetricRepository.java
@@ -64,9 +64,10 @@ public List<String> flowMetrics(
         String namespace,
         String flowId
     ) {
-        return this.queryMetrics(
+        return this.queryDistinct(
             field("flow_id").eq(flowId)
-                .and(field("namespace").eq(namespace))
+                .and(field("namespace").eq(namespace)),
+            "metric_name"
         );
     }
 
@@ -76,10 +77,23 @@ public List<String> taskMetrics(
         String flowId,
         String taskId
     ) {
-        return this.queryMetrics(
+        return this.queryDistinct(
             field("flow_id").eq(flowId)
                 .and(field("namespace").eq(namespace))
-                .and(field("task_id").eq(taskId))
+                .and(field("task_id").eq(taskId)),
+            "metric_name"
+        );
+    }
+
+    @Override
+    public List<String> tasksWithMetrics(
+        String namespace,
+        String flowId
+    ) {
+        return this.queryDistinct(
+            field("flow_id").eq(flowId)
+                .and(field("namespace").eq(namespace)),
+            "task_id"
         );
     }
 
@@ -141,20 +155,20 @@ public MetricEntry save(DSLContext dslContext, MetricEntry metric) {
         return metric;
     }
 
-    private List<String> queryMetrics(Condition condition) {
+    private List<String> queryDistinct(Condition condition, String field) {
         return this.jdbcRepository
             .getDslContextWrapper()
             .transactionResult(configuration -> {
                 DSLContext context = DSL.using(configuration);
                 SelectConditionStep<Record1<Object>> select = DSL
                     .using(configuration)
-                    .selectDistinct(field("metric_name"))
+                    .selectDistinct(field(field))
                     .from(this.jdbcRepository.getTable())
                     .where(this.defaultFilter());
 
                 select = select.and(condition);
 
-                return select.fetch().map(record -> record.get("metric_name", String.class));
+                return select.fetch().map(record -> record.get(field, String.class));
             });
     }
 
diff --git a/repository-memory/src/main/java/io/kestra/repository/memory/MemoryMetricRepository.java b/repository-memory/src/main/java/io/kestra/repository/memory/MemoryMetricRepository.java
index c0271a6870..cf5e44c470 100644
--- a/repository-memory/src/main/java/io/kestra/repository/memory/MemoryMetricRepository.java
+++ b/repository-memory/src/main/java/io/kestra/repository/memory/MemoryMetricRepository.java
@@ -45,6 +45,11 @@ public List<String> taskMetrics(String namespace, String flowId, String taskId)
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public List<String> tasksWithMetrics(String namespace, String flowId) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public MetricAggregations aggregateByFlowId(String namespace, String flowId, @Nullable String taskId, String metric, @Nullable ZonedDateTime startDate, @Nullable ZonedDateTime endDate, String aggregation) {
         throw new UnsupportedOperationException();
diff --git a/ui/src/components/flows/FlowMetrics.vue b/ui/src/components/flows/FlowMetrics.vue
index db8a7b8c1f..440f4411e9 100644
--- a/ui/src/components/flows/FlowMetrics.vue
+++ b/ui/src/components/flows/FlowMetrics.vue
@@ -11,7 +11,7 @@
                     @update:model-value="updateQuery('task', $event)"
                 >
                     <el-option
-                        v-for="item in tasks"
+                        v-for="item in tasksWithMetrics"
                         :key="item"
                         :label="item"
                         :value="item"
@@ -83,12 +83,11 @@
                 <BarChart ref="chartRef" :chart-data="chartData" :options="options" v-if="aggregatedMetric" />
             </el-tooltip>
             <span v-else>
-            <el-alert type="info" :closable="false">
-                {{ $t("metric choice") }}
-            </el-alert>
-        </span>
+                <el-alert type="info" :closable="false">
+                    {{ $t("metric choice") }}
+                </el-alert>
+            </span>
         </el-card>
-
     </div>
 </template>
 
@@ -118,7 +117,7 @@
             }
         },
         computed: {
-            ...mapState("flow", ["flow", "metrics", "aggregatedMetric"]),
+            ...mapState("flow", ["flow", "metrics", "aggregatedMetric","tasksWithMetrics"]),
             theme() {
                 return localStorage.getItem("theme") || "light";
             },
@@ -192,9 +191,6 @@
                     }
                 })
             },
-            tasks() {
-                return this.flow.tasks.map(e => e.id);
-            },
             display() {
                 return this.$route.query.metric && this.$route.query.aggregation;
             }
@@ -207,6 +203,7 @@
         },
         methods: {
             loadMetrics() {
+                this.$store.dispatch("flow/loadTasksWithMetrics",{...this.$route.params})
                 this.$store
                     .dispatch(this.$route.query.task ? "flow/loadTaskMetrics" : "flow/loadFlowMetrics", {
                         ...this.$route.params,
diff --git a/ui/src/stores/flow.js b/ui/src/stores/flow.js
index 30b82ff066..fad564b447 100644
--- a/ui/src/stores/flow.js
+++ b/ui/src/stores/flow.js
@@ -23,7 +23,8 @@ export default {
         flowError: undefined,
         taskError: undefined,
         metrics: [],
-        aggregatedMetrics: undefined
+        aggregatedMetrics: undefined,
+        tasksWithMetrics: []
     },
 
     actions: {
@@ -240,6 +241,13 @@ export default {
                     return response.data
                 })
         },
+        loadTasksWithMetrics({commit}, options) {
+            return axios.get(`${apiRoot}metrics/tasks/${options.namespace}/${options.id}`)
+                .then(response => {
+                    commit("setTasksWithMetrics", response.data)
+                    return response.data
+                })
+        },
         loadFlowAggregatedMetrics({commit}, options) {
             return axios.get(`${apiRoot}metrics/aggregates/${options.namespace}/${options.id}/${options.metric}`, {params: options})
                 .then(response => {
@@ -335,6 +343,9 @@ export default {
         setAggregatedMetric(state, aggregatedMetric) {
             state.aggregatedMetric = aggregatedMetric
         },
+        setTasksWithMetrics(state, tasksWithMetrics) {
+            state.tasksWithMetrics = tasksWithMetrics
+        }
     },
     getters: {
         flow(state) {
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/MetricController.java b/webserver/src/main/java/io/kestra/webserver/controllers/MetricController.java
index c1eeeb1943..08d8ab3852 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/MetricController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/MetricController.java
@@ -79,6 +79,16 @@ public List<String> taskMetrics(
         return metricsRepository.taskMetrics(namespace, flowId, taskId);
     }
 
+    @ExecuteOn(TaskExecutors.IO)
+    @Get(uri = "/tasks/{namespace}/{flowId}", produces = MediaType.TEXT_JSON)
+    @Operation(tags = {"Metrics"}, summary = "Get tasks id that have metrics for a specific flow, include deleted or renamed tasks")
+    public List<String> tasks(
+        @Parameter(description = "The namespace") @PathVariable String namespace,
+        @Parameter(description = "The flow Id") @PathVariable String flowId
+    ) {
+        return metricsRepository.tasksWithMetrics(namespace, flowId);
+    }
+
     @ExecuteOn(TaskExecutors.IO)
     @Get(uri = "/aggregates/{namespace}/{flowId}/{metric}", produces = MediaType.TEXT_JSON)
     @Operation(tags = {"Metrics"}, summary = "Get metrics aggregations for a specific flow")


Test patch:
diff --git a/core/src/test/java/io/kestra/core/repositories/AbstractMetricRepositoryTest.java b/core/src/test/java/io/kestra/core/repositories/AbstractMetricRepositoryTest.java
index ef16429353..008f0f8e74 100644
--- a/core/src/test/java/io/kestra/core/repositories/AbstractMetricRepositoryTest.java
+++ b/core/src/test/java/io/kestra/core/repositories/AbstractMetricRepositoryTest.java
@@ -26,9 +26,9 @@ public abstract class AbstractMetricRepositoryTest {
     @Test
     void all() {
         String executionId = FriendlyId.createFriendlyId();
-        TaskRun taskRun1 = taskRun(executionId);
-        MetricEntry counter = MetricEntry.of(taskRun1, counter());
-        TaskRun taskRun2 = taskRun(executionId);
+        TaskRun taskRun1 = taskRun(executionId, "task");
+        MetricEntry counter = MetricEntry.of(taskRun1, counter("counter"));
+        TaskRun taskRun2 = taskRun(executionId, "task");
         MetricEntry timer = MetricEntry.of(taskRun2, timer());
         metricRepository.save(counter);
         metricRepository.save(timer);
@@ -70,20 +70,42 @@ void all() {
 
     }
 
-    private Counter counter() {
-        return Counter.of("counter", 1);
+     @Test
+     void names() {
+         String executionId = FriendlyId.createFriendlyId();
+         TaskRun taskRun1 = taskRun(executionId, "task");
+         MetricEntry counter = MetricEntry.of(taskRun1, counter("counter"));
+
+         TaskRun taskRun2 = taskRun(executionId, "task2");
+         MetricEntry counter2 = MetricEntry.of(taskRun2, counter("counter2"));
+
+         metricRepository.save(counter);
+         metricRepository.save(counter2);
+
+
+         List<String> flowMetricsNames = metricRepository.flowMetrics("namespace", "flow");
+         List<String> taskMetricsNames = metricRepository.taskMetrics("namespace", "flow", "task");
+         List<String> tasksWithMetrics = metricRepository.tasksWithMetrics("namespace", "flow");
+
+         assertThat(flowMetricsNames.size(), is(2));
+         assertThat(taskMetricsNames.size(), is(1));
+         assertThat(tasksWithMetrics.size(), is(2));
+     }
+
+    private Counter counter(String metricName) {
+        return Counter.of(metricName, 1);
     }
 
     private Timer timer() {
         return Timer.of("counter", Duration.ofSeconds(5));
     }
 
-    private TaskRun taskRun(String executionId) {
+    private TaskRun taskRun(String executionId, String taskId) {
         return TaskRun.builder()
             .flowId("flow")
             .namespace("namespace")
             .executionId(executionId)
-            .taskId("task")
+            .taskId(taskId)
             .id(FriendlyId.createFriendlyId())
             .build();
     }