Repository: kestra-io/kestra
Issue: Update a flow and changing id or namespace failed

Changing the namespace or the id will led to a 404 on the update from the webui.
It's a normal behaviour on the `FlowControler` we ask and update on a flow that don't exists. 

This led to 2 issues : 
* 404 are not catch and loading bar keep growing, this must be capture and displayed as error. 
* How can we rename a flow ? is this a possible action ? 

Since there is a lot of "foreign keys" (see #60), we need to disabled the edition of namespace and flow id for now (waiting #60)


Issue comments:
The ui logic must follow backen constraints. So we have to define the appropriate behavior.

For this issue we can :
* Allo  id change ( which is obviously not what is possible and desired now)
* Clone flow with new name ans ns on clone operation
* Clone and delete flow
* Manage virtual flows or allow id change on revision update which seems complicated and looks like a trap

I think the best way to do this is clone only. Ask new id when operation is in progress.
This prevent existing flow and it's exexution to be deleted. conservative approach is often good ans we may *disable* some unwanted flows.
We should avoid complex user data management to keep sustem ux as kiss as possible.

Also I really think description in flow should be quite mandatory : it forces self documentation and legacy flow easier to manage.
Maybe I'm not clear but the idea is to forbid the edition of namespace and Id.
On the #60, we are going on further

Pull request review comments:
add revision also here please :) 
done !

Patch that resolved the issue:
diff --git a/ui/src/components/flows/FlowEdit.vue b/ui/src/components/flows/FlowEdit.vue
index 57a2c7ae42..c7d2b183c8 100644
--- a/ui/src/components/flows/FlowEdit.vue
+++ b/ui/src/components/flows/FlowEdit.vue
@@ -42,14 +42,28 @@ export default {
     },
     data() {
         return {
-            content: ""
+            content: "",
+            readOnlyEditFields: {}
         };
     },
     created() {
         this.loadFlow();
+        if (this.isEdit) {
+            this.readOnlyEditFields = {
+                id: this.flow.id,
+                namespace: this.flow.namespace,
+                revision: this.flow.revision
+            };
+        }
     },
     computed: {
         ...mapState("flow", ["flow"]),
+        isEdit() {
+            return (
+                this.$route.name === "flow" &&
+                this.$route.query.tab === "data-source"
+            );
+        },
         flowName() {
             return (this.flow && this.flow.id) || this.$t("new");
         },
@@ -77,11 +91,56 @@ export default {
         },
         save() {
             if (this.flow) {
+                const flow = Yaml.parse(this.content);
+                if (this.isEdit) {
+                    for (const key in this.readOnlyEditFields) {
+                        if (flow[key] !== this.readOnlyEditFields[key]) {
+                            this.$bvToast.toast(
+                                this.$t(
+                                    "read only fields have changed (id, namespace...)"
+                                ).capitalize(),
+                                {
+                                    title: this.$t(
+                                        "unable to save"
+                                    ).capitalize(),
+                                    autoHideDelay: 5000,
+                                    toaster: "b-toaster-top-right",
+                                    variant: "warning"
+                                }
+                            );
+                            return;
+                        }
+                    }
+                }
                 this.$store
                     .dispatch("flow/saveFlow", {
-                        flow: Yaml.parse(this.content)
+                        flow
                     })
-                    .finally(this.loadFlow);
+                    .then(() => {
+                        this.$bvToast.toast(
+                            this.$t("flow update ok").capitalize(),
+                            {
+                                title: this.$t("saved").capitalize(),
+                                autoHideDelay: 5000,
+                                toaster: "b-toaster-top-right",
+                                variant: "success"
+                            }
+                        );
+                    })
+                    .catch(() => {
+                        this.$bvToast.toast(
+                            this.$t("flow update aborted").capitalize(),
+                            {
+                                title: this.$t("fail").capitalize(),
+                                autoHideDelay: 5000,
+                                toaster: "b-toaster-top-right",
+                                variant: "danger"
+                            }
+                        );
+                    })
+                    .finally(() => {
+                        this.loadFlow();
+                    });
             } else {
                 const flow = Yaml.parse(this.content);
                 this.$store


Test patch: