Repository: kestra-io/kestra
Issue: Task edit don't handle the revision version
Labels: bug

On an execution Topology, if I click on task edit, the version will always the last version and don't take the current execution version. 

We need to 
- fetch the good version,
- disabled the save button
- put the editor on readonly
- display a warning to explain it's not the last version 

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/ui/src/components/executions/Topology.vue b/ui/src/components/executions/Topology.vue
index 0124f9c1fd..b700224247 100644
--- a/ui/src/components/executions/Topology.vue
+++ b/ui/src/components/executions/Topology.vue
@@ -1,5 +1,6 @@
 <template>
     <topology
+        :key="execution.id"
         v-if="execution && flowGraph"
         :flow-id="execution.flowId"
         :namespace="execution.namespace"
diff --git a/ui/src/components/flows/TaskEdit.vue b/ui/src/components/flows/TaskEdit.vue
index 1577e16598..2a7bd051fa 100644
--- a/ui/src/components/flows/TaskEdit.vue
+++ b/ui/src/components/flows/TaskEdit.vue
@@ -15,20 +15,30 @@
             :append-to-body="true"
         >
             <template #footer>
-                <el-button :icon="ContentSave" @click="saveTask" v-if="canSave" type="primary">
-                    {{ $t('save') }}
-                </el-button>
+                <div v-loading="isLoading">
+                    <el-button :icon="ContentSave" @click="saveTask" v-if="canSave && !isReadOnly" type="primary">
+                        {{ $t('save') }}
+                    </el-button>
+                    <el-alert show-icon :closable="false" class="mb-0 mt-3" v-if="revision && isReadOnly" type="warning">
+                        <strong>{{ $t('seeing old revision', {revision: revision}) }}</strong>
+                    </el-alert>
+                </div>
             </template>
 
-            <editor
-                ref="editor"
-                @save="saveTask"
-                v-model="taskYaml"
-                schema-type="task"
-                :full-height="false"
-                :navbar="false"
-                lang="yaml"
-            />
+            <div v-loading="isLoading">
+                <editor
+                    v-if="taskYaml"
+                    :read-only="isReadOnly"
+                    ref="editor"
+                    @save="saveTask"
+                    v-model="taskYaml"
+                    schema-type="task"
+                    :full-height="false"
+                    :navbar="false"
+                    lang="yaml"
+                />
+            </div>
+
         </el-drawer>
     </component>
 </template>
@@ -68,8 +78,15 @@
                 type: String,
                 required: true
             },
+            revision: {
+                type: Number,
+                default: undefined
+            }
         },
         methods: {
+            loadWithRevision(taskId){
+                return this.$store.dispatch("flow/loadTask", {namespace: this.namespace, id: this.flowId, taskId: taskId, revision: this.revision});
+            },
             load(taskId) {
                 return YamlUtils.extractTask(this.flow.source, taskId).toString();
             },
@@ -97,7 +114,11 @@
             onShow() {
                 this.isModalOpen = !this.isModalOpen;
                 if (this.taskId || this.task.id) {
-                    this.taskYaml = this.load(this.taskId ? this.taskId : this.task.id)
+                    if (this.revision) {
+                        this.loadWithRevision(this.taskId || this.task.id).then(value => this.taskYaml = YamlUtils.stringify(value));
+                    } else {
+                        this.taskYaml = this.load(this.taskId ? this.taskId : this.task.id);
+                    }
                 } else {
                     this.taskYaml = YamlUtils.stringify(this.task);
                 }
@@ -118,6 +139,12 @@
             ...mapState("auth", ["user"]),
             canSave() {
                 return canSaveFlowTemplate(true, this.user, {namespace: this.namespace}, "flow");
+            },
+            isLoading() {
+                return this.taskYaml === undefined;
+            },
+            isReadOnly() {
+                return this.flow && this.revision && this.flow.revision !== this.revision
             }
         }
     };
diff --git a/ui/src/components/graph/Topology.vue b/ui/src/components/graph/Topology.vue
index 95119f0be9..bc88d32c6d 100644
--- a/ui/src/components/graph/Topology.vue
+++ b/ui/src/components/graph/Topology.vue
@@ -171,6 +171,7 @@
                     node: node,
                     namespace: props.namespace,
                     flowId: props.flowId,
+                    revision: props.execution ? props.execution.flowRevision : undefined,
                 },
             })
         }
diff --git a/ui/src/components/graph/TreeNode.vue b/ui/src/components/graph/TreeNode.vue
index 60cc45659a..f6e466c37b 100644
--- a/ui/src/components/graph/TreeNode.vue
+++ b/ui/src/components/graph/TreeNode.vue
@@ -7,7 +7,7 @@
         <div class="task-content">
             <div class="card-header">
                 <div class="task-title">
-                    <span>{{ task.id }} {{ state }}</span>
+                    <span>{{ task.id }}</span>
                 </div>
             </div>
             <div v-if="task.state" class="status-wrapper">
@@ -66,6 +66,7 @@
                         :flow-id="flowId"
                         size="small"
                         :namespace="namespace"
+                        :revision="revision"
                     />
                 </el-button-group>
             </div>
@@ -141,7 +142,10 @@
                 type: String,
                 required: true
             },
-
+            revision: {
+                type: Number,
+                default: undefined
+            },
         },
         methods: {
             forwardEvent(type, event) {
diff --git a/ui/src/components/graph/nodes/Task.vue b/ui/src/components/graph/nodes/Task.vue
index 533d26bea8..c51e5e52bc 100644
--- a/ui/src/components/graph/nodes/Task.vue
+++ b/ui/src/components/graph/nodes/Task.vue
@@ -44,6 +44,7 @@
         :n="data.node"
         :namespace="data.namespace"
         :flow-id="data.flowId"
+        :revision="data.revision"
         @follow="forwardEvent('follow', $event)"
         @mouseover="mouseover"
         @mouseleave="mouseleave"
diff --git a/ui/src/components/logs/LogList.vue b/ui/src/components/logs/LogList.vue
index 143a53d906..a8f93537d4 100644
--- a/ui/src/components/logs/LogList.vue
+++ b/ui/src/components/logs/LogList.vue
@@ -87,6 +87,7 @@
                                                 :task-id="currentTaskRun.taskId"
                                                 :flow-id="execution.flowId"
                                                 :namespace="execution.namespace"
+                                                :revision="execution.flowRevision"
                                             />
                                         </el-dropdown-menu>
                                     </template>
diff --git a/ui/src/stores/flow.js b/ui/src/stores/flow.js
index 0d84709d99..8c172a26b8 100644
--- a/ui/src/stores/flow.js
+++ b/ui/src/stores/flow.js
@@ -66,7 +66,7 @@ export default {
                 })
         },
         loadTask({commit}, options) {
-            return this.$http.get(`/api/v1/flows/${options.namespace}/${options.id}/tasks/${options.taskId}`).then(response => {
+            return this.$http.get(`/api/v1/flows/${options.namespace}/${options.id}/tasks/${options.taskId}${options.revision ? "?revision=" + options.revision : ""}`).then(response => {
                 commit("setTask", response.data)
 
                 return response.data;
diff --git a/ui/src/translations.json b/ui/src/translations.json
index c85cdfb8b4..1b0bebadc0 100644
--- a/ui/src/translations.json
+++ b/ui/src/translations.json
@@ -267,7 +267,8 @@
     "invalid bulk delete": "Could not delete executions",
     "execution not found": "Execution <code>{executionId}</code> not found",
     "execution not in state FAILED": "Execution <code>{executionId}</code> not in state FAILED",
-    "execution already finished": "Execution <code>{executionId}</code> already finished"
+    "execution already finished": "Execution <code>{executionId}</code> already finished",
+    "seeing old revision": "You are seeing an old revision: {revision}"
   },
   "fr": {
     "id": "Identifiant",
@@ -538,7 +539,8 @@
     "execution not found": "Execution <code>{executionId}</code> non trouvée",
     "execution not in state FAILED": "Execution <code>{executionId}</code> n'est pas dans l'état FAILED",
     "execution already finished": "Execution <code>{executionId}</code> déjà terminée",
-    "restore revision": "Êtes-vous sur de vouloir restaurer la revision <code>{revision}</code> ?"
+    "restore revision": "Êtes-vous sur de vouloir restaurer la revision <code>{revision}</code> ?",
+    "seeing old revision": "Vous visualisez une ancienne revision : {revision}"
   },
   "de": {
     "id": "Id",
@@ -808,6 +810,7 @@
     "invalid bulk delete": "Ausführungen konnten nicht gelöscht werden",
     "execution not found": "Ausführung <code>{executionId}</code> nicht gefunden",
     "execution not in state FAILED": "Ausführung <code>{executionId}</code> nicht im Status FAILED",
-    "execution already finished": "Ausführung <code>{executionId}</code> bereits abgeschlossen"
+    "execution already finished": "Ausführung <code>{executionId}</code> bereits abgeschlossen",
+    "seeing old revision": "You are seeing an old revision: {revision}"
   }
 }
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java b/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
index ebd79fa682..101f67c918 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
@@ -104,10 +104,11 @@ public List<FlowWithSource> revisions(
     public Task flowTask(
         @Parameter(description = "The flow namespace") String namespace,
         @Parameter(description = "The flow id") String id,
-        @Parameter(description = "The task id") String taskId
+        @Parameter(description = "The task id") String taskId,
+        @Parameter(description = "The flow revision") @Nullable @QueryValue(value = "revision") Integer revision
     ) {
         return flowRepository
-            .findById(namespace, id)
+            .findById(namespace, id, Optional.ofNullable(revision))
             .flatMap(flow -> {
                 try {
                     return Optional.of(flow.findTaskByTaskId(taskId));


Test patch: