Repository: kestra-io/kestra (java)

Issue:
Link to "Migrations section" on Templates page is broken
### Issue description

The link to templates' migrations section document is incorrect on the following lines:

https://github.com/kestra-io/kestra/blob/b2ea51da09feac4818e7f3d766fba824cc01473e/ui/src/translations/en.json#L281

https://github.com/kestra-io/kestra/blob/b2ea51da09feac4818e7f3d766fba824cc01473e/ui/src/translations/fr.json#L270

Incorrect Link: [https://kestra.io/docs/migrations/templates](https://kestra.io/docs/migrations/templates)
Correct Link: [https://kestra.io/docs/migration-guide/templates](https://kestra.io/docs/migration-guide/templates)


I will raise a pull request shortly with a fix.

Pull request:
fix(docs): update link to templates migration page
<!-- Thanks for submitting a Pull Request to kestra. To help us review your contribution, please follow the guidelines below:

- Make sure that your commits follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification e.g. `feat(ui): add a new navigation menu item` or `fix(core): fix a bug in the core model` or `docs: update the README.md`. This will help us automatically generate the changelog.
- The title should briefly summarize the proposed changes.
- Provide a short overview of the change and the value it adds.
- Share a flow example to help the reviewer understand and QA the change.
- Use "close" to automatically close an issue. For example, `close #1234` will close issue #1234. -->

### What changes are being made and why?

The link to templates' migrations section document is incorrect on the following lines:

https://github.com/kestra-io/kestra/blob/b2ea51da09feac4818e7f3d766fba824cc01473e/ui/src/translations/en.json#L281

https://github.com/kestra-io/kestra/blob/b2ea51da09feac4818e7f3d766fba824cc01473e/ui/src/translations/fr.json#L270

Incorrect Link: [https://kestra.io/docs/migrations/templates](https://kestra.io/docs/migrations/templates)
Correct Link: [https://kestra.io/docs/migration-guide/templates](https://kestra.io/docs/migration-guide/templates)


closes #3738 

---


Patch:
diff --git a/.gitignore b/.gitignore
index 4ae414c84..ab55bb826 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,8 +29,6 @@ node_modules
 yarn-error.log
 yarn.lock
 ui/node_modules
-ui/public/vscode
-ui/public/vscode-web
 ui/.env.local
 ui/.env.*.local
 webserver/src/main/resources/ui
diff --git a/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java b/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
index e5d517295..7783b904e 100644
--- a/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
+++ b/cli/src/main/java/io/kestra/cli/commands/flows/FlowValidateCommand.java
@@ -39,6 +39,7 @@ public class FlowValidateCommand extends AbstractValidateCommand {
                 Flow flow = (Flow) object;
                 List<String> warnings = new ArrayList<>();
                 warnings.addAll(flowService.deprecationPaths(flow).stream().map(deprecation -> deprecation + " is deprecated").toList());
+                warnings.addAll(flowService.relocations(flow.generateSource()).stream().map(relocation -> relocation.from() + " is replaced by " + relocation.to()).toList());
                 warnings.addAll(flowService.warnings(flow));
                 return warnings;
             }
diff --git a/core/src/main/java/io/kestra/core/docs/ClassPluginDocumentation.java b/core/src/main/java/io/kestra/core/docs/ClassPluginDocumentation.java
index e341e9d05..b908eb4a1 100644
--- a/core/src/main/java/io/kestra/core/docs/ClassPluginDocumentation.java
+++ b/core/src/main/java/io/kestra/core/docs/ClassPluginDocumentation.java
@@ -14,6 +14,7 @@ public class ClassPluginDocumentation<T> extends AbstractClassDocumentation<T> {
     private String group;
     private String pluginTitle;
     private String subGroup;
+    private String replacement;
     private List<MetricDoc> docMetrics;
     private Map<String, Object> outputs = new TreeMap<>();
     private Map<String, Object> outputsSchema;
@@ -27,12 +28,15 @@ public class ClassPluginDocumentation<T> extends AbstractClassDocumentation<T> {
         this.group = plugin.group();
         this.pluginTitle = plugin.title();
         this.icon = plugin.icon(cls);
+        if (alias != null) {
+            replacement = cls.getName();
+        }
 
         if (this.group != null && cls.getPackageName().startsWith(this.group) && cls.getPackageName().length() > this.group.length() && cls.getPackageName().charAt(this.group.length()) == '.') {
             this.subGroup = cls.getPackageName().substring(this.group.length() + 1);
         }
 
-        this.shortName = cls.getSimpleName();
+        this.shortName = alias == null ? cls.getSimpleName() : alias.substring(alias.lastIndexOf('.') + 1);
 
         // outputs
         this.outputsSchema = jsonSchemaGenerator.outputs(baseCls, cls);
diff --git a/core/src/main/java/io/kestra/core/docs/Plugin.java b/core/src/main/java/io/kestra/core/docs/Plugin.java
index 98fd5ac6e..9a5443924 100644
--- a/core/src/main/java/io/kestra/core/docs/Plugin.java
+++ b/core/src/main/java/io/kestra/core/docs/Plugin.java
@@ -29,6 +29,7 @@ public class Plugin {
     private List<String> secrets;
     private List<String> taskRunners;
     private List<String> guides;
+    private List<String> aliases;
     private List<PluginSubGroup.PluginCategory> categories;
     private String subGroup;
 
@@ -45,6 +46,7 @@ public class Plugin {
         plugin.longDescription = registeredPlugin.longDescription();
         plugin.version = registeredPlugin.version();
         plugin.guides = registeredPlugin.getGuides();
+        plugin.aliases = registeredPlugin.getAliases().keySet().stream().toList();
         plugin.manifest = registeredPlugin
             .getManifest()
             .getMainAttributes()
diff --git a/core/src/main/java/io/kestra/core/services/FlowService.java b/core/src/main/java/io/kestra/core/services/FlowService.java
index 5ea1a8e16..bcccdbb69 100644
--- a/core/src/main/java/io/kestra/core/services/FlowService.java
+++ b/core/src/main/java/io/kestra/core/services/FlowService.java
@@ -107,42 +107,46 @@ public class FlowService {
         return Collections.emptyList();
     }
 
-    public List<String> aliasesPaths(String flowSource) {
+    public List<Relocation> relocations(String flowSource) {
         try {
-            List<String> aliases = pluginRegistry.plugins().stream().flatMap(plugin -> plugin.getAliases().keySet().stream()).toList();
+            Map<String, Class<?>> aliases = pluginRegistry.plugins().stream()
+                .flatMap(plugin -> plugin.getAliases().entrySet().stream())
+                .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
             Map<String, Object> stringObjectMap = JacksonMapper.ofYaml().readValue(flowSource, JacksonMapper.MAP_TYPE_REFERENCE);
-            return aliasesPaths(aliases, stringObjectMap);
+            return relocations(aliases, stringObjectMap);
         } catch (JsonProcessingException e) {
             // silent failure (we don't compromise the app / response for warnings)
             return Collections.emptyList();
         }
     }
+    public record Relocation(String from, String to) {}
 
-    private List<String> aliasesPaths(List<String> aliases, Map<String, Object> stringObjectMap) {
-        List<String> warnings = new ArrayList<>();
+    private List<Relocation> relocations(Map<String, Class<?>> aliases, Map<String, Object> stringObjectMap) {
+        List<Relocation> relocations = new ArrayList<>();
         for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
-            if (entry.getValue() instanceof String value && aliases.contains(value)) {
-                warnings.add(value);
+            if (entry.getValue() instanceof String value && aliases.containsKey(value)) {
+                relocations.add(new Relocation(value, aliases.get(value).getName()));
             }
 
             if (entry.getValue() instanceof Map<?, ?> value) {
-                warnings.addAll(aliasesPaths(aliases, (Map<String, Object>) value));
+                relocations.addAll(relocations(aliases, (Map<String, Object>) value));
             }
 
             if (entry.getValue() instanceof List<?> value) {
-                List<String> listAliases = value.stream().flatMap(item -> {
+                List<Relocation> listAliases = value.stream().flatMap(item -> {
                     if (item instanceof Map<?, ?> map) {
-                        return aliasesPaths(aliases, (Map<String, Object>) map).stream();
+                        return relocations(aliases, (Map<String, Object>) map).stream();
                     }
                     return Stream.empty();
                 }).toList();
-                warnings.addAll(listAliases);
+                relocations.addAll(listAliases);
             }
         }
 
-        return warnings;
+        return relocations;
     }
 
+
     private Stream<String> deprecationTraversal(String prefix, Object object) {
         if (object == null || ClassUtils.isPrimitiveOrWrapper(object.getClass()) || String.class.equals(object.getClass())) {
             return Stream.empty();
diff --git a/core/src/main/java/io/kestra/core/tasks/flows/WaitFor.java b/core/src/main/java/io/kestra/core/tasks/flows/WaitFor.java
index e95563fd9..7861b8a14 100644
--- a/core/src/main/java/io/kestra/core/tasks/flows/WaitFor.java
+++ b/core/src/main/java/io/kestra/core/tasks/flows/WaitFor.java
@@ -62,7 +62,7 @@ import java.util.stream.Stream;
                     task:
                       id: return
                       type: io.kestra.core.tasks.debugs.Return
-                      format: "{{ outputs.waitfor.iterationCount }}"
+                      format: "{{ outputs.waitFor.iterationCount }}"
                 """
         )
 
diff --git a/core/src/main/resources/docs/task.peb b/core/src/main/resources/docs/task.peb
index e2f5ab8a8..03122fa20 100644
--- a/core/src/main/resources/docs/task.peb
+++ b/core/src/main/resources/docs/task.peb
@@ -19,7 +19,7 @@ icon: {{ icon }}
 
 {% if deprecated %}
 ::alert{type="warning"}
-⚠ Deprecated
+⚠ Deprecated {% if replacement %}use `{{ replacement }}` instead {%- endif %}
 ::
 {%- endif %}
 
diff --git a/ui/src/components/Tabs.vue b/ui/src/components/Tabs.vue
index 5e174630f..95301df38 100644
--- a/ui/src/components/Tabs.vue
+++ b/ui/src/components/Tabs.vue
@@ -17,9 +17,9 @@
     </el-tabs>
 
     <section ref="container" v-bind="$attrs" :class="{...containerClass, 'd-flex flex-row': isEditorActiveTab}">
-        <EditorSidebar v-if="isEditorActiveTab" ref="sidebar" :style="`flex: 0 1 calc(${explorerWidth}% - 11px);`" />
+        <EditorSidebar v-if="isEditorActiveTab" ref="sidebar" :style="`flex: 0 0 calc(${explorerWidth}% - 11px);`" />
         <div v-if="isEditorActiveTab && explorerVisible" @mousedown.prevent.stop="dragSidebar" class="slider" />
-        <div :key="`${explorerVisible}__${explorerWidth}`" :style="`flex: 0 1 ${100 - (isEditorActiveTab && explorerVisible ? explorerWidth : 0)}%;`">
+        <div :style="`flex: 1 1 ${100 - (isEditorActiveTab && explorerVisible ? explorerWidth : 0)}%;`">
             <component
                 v-bind="{...activeTab.props, ...attrsWithoutClass}"
                 v-on="activeTab['v-on'] ?? {}"
diff --git a/ui/src/components/flows/FlowCreate.vue b/ui/src/components/flows/FlowCreate.vue
index a79c071ff..86d820298 100644
--- a/ui/src/components/flows/FlowCreate.vue
+++ b/ui/src/components/flows/FlowCreate.vue
@@ -2,8 +2,9 @@
     <top-nav-bar :title="routeInfo.title" />
     <section class="full-container">
         <editor-view
-            :flow-id="defaultFlowTemplate.id"
-            :namespace="defaultFlowTemplate.namespace"
+            v-if="this.source"
+            :flow-id="flowParsed?.id"
+            :namespace="flowParsed?.namespace"
             :is-creating="true"
             :flow-graph="flowGraph"
             :is-read-only="false"
@@ -23,6 +24,8 @@
     import {mapGetters, mapState} from "vuex";
     import RouteContext from "../../mixins/routeContext";
     import TopNavBar from "../../components/layout/TopNavBar.vue";
+    import {apiUrl} from "override/utils/route";
+    import {YamlUtils} from "@kestra-io/ui-libs";
 
     export default {
         mixins: [RouteContext],
@@ -30,6 +33,11 @@
             EditorView,
             TopNavBar
         },
+        data() {
+            return {
+                source: null
+            }
+        },
         created() {
             if (this.$route.query.reset) {
                 localStorage.setItem("tourDoneOrSkip", undefined);
@@ -44,20 +52,23 @@
                 });
                 this.$tours["guidedTour"].start();
             }
+            this.setupFlow()
         },
         beforeUnmount() {
             this.$store.commit("flow/setFlowValidation", undefined);
         },
-        computed: {
-            sourceWrapper() {
-                return {source: this.defaultFlowTemplate};
+        methods: {
+            async queryBlueprint(blueprintId) {
+                return (await this.$http.get(`${this.blueprintUri}/${blueprintId}/flow`)).data;
             },
-            defaultFlowTemplate() {
-                if(this.$route.query.copy && this.flow){
-                    return this.flow.source;
+            async setupFlow() {
+                if (this.$route.query.copy && this.flow){
+                    this.source = this.flow.source;
                 }
-
-                return `id: myflow
+                if (this.$route.query.blueprintId) {
+                    this.source = await this.queryBlueprint(this.$route.query.blueprintId)
+                } else {
+                    this.source = `id: myflow
 namespace: company.myteam
 description: Save and Execute the flow
 
@@ -87,6 +98,12 @@ triggers:
   - id: daily
     type: io.kestra.core.models.triggers.types.Schedule
     cron: "0 9 * * *"`;
+                }
+            }
+        },
+        computed: {
+            sourceWrapper() {
+                return {source: this.source};
             },
             ...mapState("flow", ["flowGraph", "total"]),
             ...mapState("auth", ["user"]),
@@ -98,6 +115,12 @@ triggers:
                     title: this.$t("flows")
                 };
             },
+            blueprintUri() {
+                return `${apiUrl(this.$store)}/blueprints/community`
+            },
+            flowParsed() {
+                return YamlUtils.parse(this.source);
+            }
         },
         beforeRouteLeave(to, from, next) {
             this.$store.commit("flow/setFlow", null);
diff --git a/ui/src/components/flows/FlowRoot.vue b/ui/src/components/flows/FlowRoot.vue
index dc4ec9e34..796eb8d89 100644
--- a/ui/src/components/flows/FlowRoot.vue
+++ b/ui/src/components/flows/FlowRoot.vue
@@ -102,7 +102,7 @@
                             this.$http
                                 .get(`${apiUrl(this.$store)}/flows/${this.flow.namespace}/${this.flow.id}/dependencies`)
                                 .then(response => {
-                                    this.dependenciesCount = response.data && response.data.nodes ? [...new Set(response.data.nodes.map(r => r.uid))].length - 1 : 0;
+                                    this.dependenciesCount = response.data && response.data.nodes ? [...new Set(response.data.nodes.map(r => r.uid))].length : 0;
                                 })
                         }
                     });
diff --git a/ui/src/components/flows/blueprints/BlueprintDetail.vue b/ui/src/components/flows/blueprints/BlueprintDetail.vue
index 362b2d566..11a0a5989 100644
--- a/ui/src/components/flows/blueprints/BlueprintDetail.vue
+++ b/ui/src/components/flows/blueprints/BlueprintDetail.vue
@@ -2,7 +2,7 @@
     <top-nav-bar v-if="!embed && blueprint" :title="blueprint.title" :breadcrumb="breadcrumb" v-loading="!blueprint">
         <template #additional-right>
             <ul v-if="userCanCreateFlow">
-                <router-link :to="{name: 'flows/create'}" @click="asAutoRestoreDraft">
+                <router-link :to="{name: 'flows/create', query: {blueprintId: blueprint.id}}">
                     <el-button type="primary" v-if="!embed">
                         {{ $t('use') }}
                     </el-button>
@@ -132,9 +132,6 @@
                         }
                     })
                 }
-            },
-            asAutoRestoreDraft() {
-                localStorage.setItem("autoRestore-creation_draft", this.blueprint.flow);
             }
         },
         async created() {
diff --git a/ui/src/components/inputs/EditorView.vue b/ui/src/components/inputs/EditorView.vue
index 111d74c0e..f30e41a09 100644
--- a/ui/src/components/inputs/EditorView.vue
+++ b/ui/src/components/inputs/EditorView.vue
@@ -261,18 +261,6 @@
         localStorage.setItem(editorViewTypes.STORAGE_KEY, value);
     };
 
-    const localStorageKey = computed(() => {
-        return (
-            (props.isCreating
-                ? "creation"
-                : `${props.flow.namespace}.${props.flow.id}`) + "_draft"
-        );
-    });
-
-    const autoRestorelocalStorageKey = computed(() => {
-        return "autoRestore-" + localStorageKey.value;
-    });
-
     watch(
         () => store.getters["flow/taskError"],
         async () => {
@@ -326,35 +314,6 @@
             }
         }
 
-        if (!props.isReadOnly) {
-            let restoredLocalStorageKey;
-            const sourceFromLocalStorage =
-                localStorage.getItem(
-                    (restoredLocalStorageKey = autoRestorelocalStorageKey.value)
-                ) ??
-                localStorage.getItem(
-                    (restoredLocalStorageKey = localStorageKey.value)
-                );
-            if (sourceFromLocalStorage !== null) {
-                if (restoredLocalStorageKey === autoRestorelocalStorageKey.value) {
-                    onEdit(sourceFromLocalStorage);
-                } else {
-                    toast.confirm(
-                        props.isCreating
-                            ? t("save draft.retrieval.creation")
-                            : t("save draft.retrieval.existing", {
-                                flowFullName: `${props.flow.namespace}.${props.flow.id}`,
-                            }),
-                        () => {
-                            onEdit(sourceFromLocalStorage);
-                        }
-                    );
-                }
-
-                localStorage.removeItem(restoredLocalStorageKey);
-            }
-        }
-
         // validate flow on first load
         store
             .dispatch("flow/validateFlow", {flow: yamlWithNextRevision.value})
@@ -424,10 +383,7 @@
         window.removeEventListener("beforeunload", persistEditorWidth);
         persistEditorWidth();
 
-        // Will get redirected to login page
-        if (!store.getters["auth/isLogged"] && haveChange.value) {
-            persistEditorContent(true);
-        }
+        store.commit("editor/closeTabs");
     });
 
     const stopTour = () => {
@@ -479,41 +435,6 @@
         }
     };
 
-    const persistEditorContent = (autoRestore) => {
-        if (autoRestore && localStorage.getItem(localStorageKey.value)) {
-            return;
-        }
-
-        localStorage.setItem(
-            autoRestore ? autoRestorelocalStorageKey.value : localStorageKey.value,
-            flowYaml.value
-        );
-        store.dispatch("core/isUnsaved", false);
-        haveChange.value = false;
-    };
-
-    const errorsToast = (title, message, errors) => {
-        store.dispatch("core/showMessage", {
-            title: title,
-            message: message,
-            content: {
-                _embedded: {
-                    errors: errors.map((error) => {
-                        return {
-                            message: error,
-                        };
-                    }),
-                },
-            },
-            variant: "error",
-        });
-    };
-
-    const saveAsDraft = (errors) => {
-        errorsToast(t("save draft.message"), t("invalid flow"), errors);
-        persistEditorContent(false);
-    };
-
     const fetchGraph = () => {
         return store.dispatch("flow/loadGraphFromSource", {
             flow: flowYaml.value,
@@ -746,12 +667,6 @@
                         return false;
                     });
             }
-
-            if (!overrideFlow.value) {
-                saveAsDraft(flowErrors.value);
-
-                return;
-            }
         }
 
         if (props.isCreating && !overrideFlow.value) {
@@ -1103,7 +1018,7 @@
             id="editorWrapper"
             v-if="combinedEditor || viewType === editorViewTypes.SOURCE"
             :class="combinedEditor ? 'editor-combined' : ''"
-            :style="`flex: 0 0 calc(${combinedEditor ? editorWidth : 100}% - 11px)`"
+            style="flex: 1"
         >
             <editor
                 ref="editorDomElement"
@@ -1122,7 +1037,7 @@
             />
         </div>
         <div class="slider" @mousedown.prevent.stop="dragEditor" v-if="combinedEditor" />
-        <div class="d-flex" :style="viewType === editorViewTypes.SOURCE ? `display: none` : combinedEditor ? `flex: 0 0 calc(${100 - editorWidth}% - 11px)` : {}">           
+        <div :class="{'d-flex': combinedEditor}" :style="viewType === editorViewTypes.SOURCE ? `display: none` : combinedEditor ? `flex: 0 0 calc(${100 - editorWidth}% - 11px)` : 'flex: 0 0 0%'">           
             <div
                 v-if="viewType === editorViewTypes.SOURCE_BLUEPRINTS"
                 class="combined-right-view enhance-readability"
diff --git a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
index a4f6f08ad..2bbde3df1 100644
--- a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
+++ b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
@@ -143,12 +143,12 @@
             },
             async blueprintToEditor(blueprintId) {
                 localStorage.setItem(editorViewTypes.STORAGE_KEY, editorViewTypes.SOURCE_TOPOLOGY);
-                localStorage.setItem("autoRestore-creation_draft", (await this.$http.get(`${this.blueprintBaseUri}/${blueprintId}/flow`)).data);
                 this.$router.push({
                     name: "flows/create",
                     params: {
                         tenant: this.$route.params.tenant
-                    }
+                    },
+                    query: {blueprintId: blueprintId}
                 });
             },
             tagsToString(blueprintTags) {
diff --git a/ui/src/stores/plugins.js b/ui/src/stores/plugins.js
index 9cd962af8..6ef5881e3 100644
--- a/ui/src/stores/plugins.js
+++ b/ui/src/stores/plugins.js
@@ -17,14 +17,14 @@ export default {
         list({commit}) {
             return this.$http.get(`${apiUrl(this)}/plugins`, {}).then(response => {
                 commit("setPlugins", response.data)
-                commit("setPluginSingleList", response.data.map(plugin => plugin.tasks.concat(plugin.triggers, plugin.conditions, plugin.controllers, plugin.storages, plugin.taskRunners)).flat())
+                commit("setPluginSingleList", response.data.map(plugin => plugin.tasks.concat(plugin.triggers, plugin.conditions, plugin.controllers, plugin.storages, plugin.taskRunners, plugin.aliases)).flat())
                 return response.data;
             })
         },
         listWithSubgroup({commit}) {
             return this.$http.get(`${apiUrl(this)}/plugins/groups/subgroups`, {}).then(response => {
                 commit("setPlugins", response.data)
-                commit("setPluginSingleList", response.data.map(plugin => plugin.tasks.concat(plugin.triggers, plugin.conditions, plugin.controllers, plugin.storages, plugin.taskRunners)).flat())
+                commit("setPluginSingleList", response.data.map(plugin => plugin.tasks.concat(plugin.triggers, plugin.conditions, plugin.controllers, plugin.storages, plugin.taskRunners, plugin.aliases)).flat())
                 return response.data;
             })
         },
diff --git a/ui/src/translations/en.json b/ui/src/translations/en.json
index 5f07625d2..f8caf104e 100644
--- a/ui/src/translations/en.json
+++ b/ui/src/translations/en.json
@@ -278,7 +278,7 @@
     "template": "Template",
     "template creation": "Template creation",
     "templates": "Templates",
-    "templates deprecated": "Templates are deprecated. Please use subflows instead. See the <a href=\"https://kestra.io/docs/migrations/templates\" target=\"_blank\">Migrations section</a> explaining how you can migrate from templates to subflows.",
+    "templates deprecated": "Templates are deprecated. Please use subflows instead. See the <a href=\"https://kestra.io/docs/migration-guide/templates\" target=\"_blank\">Migrations section</a> explaining how you can migrate from templates to subflows.",
     "no result": "No results for the current selection",
     "trigger": "Trigger",
     "triggers": "Triggers",
diff --git a/ui/src/translations/fr.json b/ui/src/translations/fr.json
index a3e698ba8..589f4aba4 100644
--- a/ui/src/translations/fr.json
+++ b/ui/src/translations/fr.json
@@ -267,7 +267,7 @@
     "template": "Template",
     "template creation": "Création de template",
     "templates": "Templates",
-    "templates deprecated": "Les templates sont obsolètes. Veuillez utiliser des sous-flux au lieu des templates. Consultez <a href=\"https://kestra.io/docs/migrations/templates\" target=\"_blank\">section Migrations</a> expliquant comment vous pouvez migrer vers les sous-flux.",
+    "templates deprecated": "Les templates sont obsolètes. Veuillez utiliser des sous-flux au lieu des templates. Consultez <a href=\"https://kestra.io/docs/migration-guide/templates\" target=\"_blank\">section Migrations</a> expliquant comment vous pouvez migrer vers les sous-flux.",
     "no result": "Aucun résultat pour la selection courante.",
     "trigger": "Déclencheur",
     "triggers": "Déclencheurs",
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/api/FlowController.java b/webserver/src/main/java/io/kestra/webserver/controllers/api/FlowController.java
index 6e48f2449..e295516f4 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/api/FlowController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/api/FlowController.java
@@ -509,11 +509,11 @@ public class FlowController {
                         validateConstraintViolationBuilder.outdated(!sentRevision.equals(lastRevision + 1));
                     }
 
-                    List<String> deprecationPaths = new ArrayList<>();
-                    deprecationPaths.addAll(flowService.deprecationPaths(flowParse));
-                    deprecationPaths.addAll(flowService.aliasesPaths(flow));
-                    validateConstraintViolationBuilder.deprecationPaths(deprecationPaths);
-                    validateConstraintViolationBuilder.warnings(flowService.warnings(flowParse));
+                    validateConstraintViolationBuilder.deprecationPaths(flowService.deprecationPaths(flowParse));
+                    List<String> warnings = new ArrayList<>();
+                    warnings.addAll(flowService.warnings(flowParse));
+                    warnings.addAll(flowService.relocations(flow).stream().map(relocation -> relocation.from() + " is replaced by " + relocation.to()).toList());
+                    validateConstraintViolationBuilder.warnings(warnings);
                     validateConstraintViolationBuilder.flow(flowParse.getId());
                     validateConstraintViolationBuilder.namespace(flowParse.getNamespace());
 


Test patch:
diff --git a/core/src/test/java/io/kestra/core/services/FlowServiceTest.java b/core/src/test/java/io/kestra/core/services/FlowServiceTest.java
index cfad057c4..d231be207 100644
--- a/core/src/test/java/io/kestra/core/services/FlowServiceTest.java
+++ b/core/src/test/java/io/kestra/core/services/FlowServiceTest.java
@@ -121,7 +121,7 @@ class FlowServiceTest {
 
     @Test
     void aliases() {
-        List<String> warnings = flowService.aliasesPaths("""
+        List<FlowService.Relocation> warnings = flowService.relocations("""
             id: hello-alias
             namespace: myteam
 
@@ -144,7 +144,8 @@ class FlowServiceTest {
                     message: Hello, {{taskrun.value}}""");
 
         assertThat(warnings.size(), is(2));
-        assertThat(warnings.get(0), is("io.kestra.core.runners.test.task.Alias"));
+        assertThat(warnings.get(0).from(), is("io.kestra.core.runners.test.task.Alias"));
+        assertThat(warnings.get(0).to(), is("io.kestra.core.runners.test.TaskWithAlias"));
     }
 
     @Test