Repository: kestra-io/kestra
Issue: Blueprint - Replace Copy by Use button
Labels: frontend

### Feature description

In the blueprint standalone view, replace the "copy" button by a "use" redirecting to the Editor where you user start using the selected Blueprint in a new flow.

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java b/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
index 175d901471..891981bd5c 100644
--- a/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
+++ b/core/src/main/java/io/kestra/core/models/validations/ValidateConstraintViolation.java
@@ -1,5 +1,6 @@
 package io.kestra.core.models.validations;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.micronaut.core.annotation.Introspected;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -31,14 +32,17 @@ public class ValidateConstraintViolation {
 
     private String constraints;
 
+    @JsonIgnore
     public String getIdentity(){
         return flow != null & namespace != null ? getFlowId() : flow != null ? flow : String.valueOf(index);
     }
 
+    @JsonIgnore
     public String getIdentity(Path directory) throws IOException {
         return flow != null & namespace != null ? getFlowId() : flow != null ? flow : String.valueOf(Files.walk(directory).collect(Collectors.toList()).get(index));
     }
 
+    @JsonIgnore
     public String getFlowId(){
         return namespace+"."+flow;
     }
diff --git a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
index 5be2060bdd..394e560e02 100644
--- a/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
+++ b/ui/src/override/components/flows/blueprints/BlueprintsBrowser.vue
@@ -50,12 +50,15 @@
                         </div>
                         <div class="side buttons ms-auto">
                             <slot name="buttons" :blueprint="blueprint" />
-                            <el-tooltip trigger="click" content="Copied" placement="left" :auto-close="2000">
+                            <el-tooltip v-if="embed" trigger="click" content="Copied" placement="left" :auto-close="2000">
                                 <el-button @click.prevent.stop="copy(blueprint.id)" :icon="icon.ContentCopy"
                                            size="large" text bg>
                                     {{ $t('copy') }}
                                 </el-button>
                             </el-tooltip>
+                            <el-button v-else size="large" text bg @click="blueprintToEditor(blueprint.id)">
+                                {{ $t('use') }}
+                            </el-button>
                         </div>
                     </component>
                 </el-card>
@@ -113,6 +116,10 @@
                     (await this.$http.get(`${this.blueprintBaseUri}/${blueprintId}/flow`)).data
                 );
             },
+            async blueprintToEditor(blueprintId) {
+                localStorage.setItem("autoRestore-creation_draft", (await this.$http.get(`${this.blueprintBaseUri}/${blueprintId}/flow`)).data);
+                this.$router.push({name: 'flows/create'});
+            },
             tagsToString(blueprintTags) {
                 return blueprintTags?.map(id => this.tags?.[id]?.name).join(" ")
             },
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 c9221ed3d7..4fc46e515e 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/FlowController.java
@@ -446,7 +446,7 @@ public List<ValidateConstraintViolation> validateFlows(
     ) {
         AtomicInteger index = new AtomicInteger(0);
         return Stream
-            .of(flows.split("---"))
+            .of(flows.split("\\n+---\\n*?"))
             .map(flow -> {
                 ValidateConstraintViolation.ValidateConstraintViolationBuilder<?, ?> validateConstraintViolationBuilder = ValidateConstraintViolation.builder();
                 validateConstraintViolationBuilder.index(index.getAndIncrement());


Test patch:
diff --git a/webserver/src/test/java/io/kestra/webserver/controllers/FlowControllerTest.java b/webserver/src/test/java/io/kestra/webserver/controllers/FlowControllerTest.java
index cf7246c3bb..596630f4b4 100644
--- a/webserver/src/test/java/io/kestra/webserver/controllers/FlowControllerTest.java
+++ b/webserver/src/test/java/io/kestra/webserver/controllers/FlowControllerTest.java
@@ -9,6 +9,7 @@
 import io.kestra.core.models.flows.input.StringInput;
 import io.kestra.core.models.hierarchies.FlowGraph;
 import io.kestra.core.models.tasks.Task;
+import io.kestra.core.models.validations.ValidateConstraintViolation;
 import io.kestra.core.runners.AbstractMemoryRunnerTest;
 import io.kestra.core.serializers.YamlFlowParser;
 import io.kestra.core.tasks.debugs.Return;
@@ -30,6 +31,7 @@
 import io.micronaut.http.hateoas.JsonError;
 import io.micronaut.rxjava2.http.client.RxHttpClient;
 import jakarta.inject.Inject;
+import org.hamcrest.Matchers;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -40,10 +42,7 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.zip.ZipFile;
 
 import static io.micronaut.http.HttpRequest.*;
@@ -664,6 +663,30 @@ void deleteFlowsByIds(){
         assertThat(e.getStatus(), is(HttpStatus.NOT_FOUND));
     }
 
+    @Test
+    void validateFlows() throws IOException {
+        URL resource = TestsUtils.class.getClassLoader().getResource("flows/validateMultipleValidFlows.yaml");
+        String flow = Files.readString(Path.of(Objects.requireNonNull(resource).getPath()), Charset.defaultCharset());
+
+        HttpResponse<List<ValidateConstraintViolation>> response = client.toBlocking().exchange(POST("/api/v1/flows/validate", flow).contentType(MediaType.APPLICATION_YAML), Argument.listOf(ValidateConstraintViolation.class));
+
+        List<ValidateConstraintViolation> body = response.body();
+        assertThat(body.size(), is(2));
+        assertThat(body, everyItem(
+            Matchers.hasProperty("constraints", is(nullValue()))
+        ));
+
+        resource = TestsUtils.class.getClassLoader().getResource("flows/validateMultipleInvalidFlows.yaml");
+        flow = Files.readString(Path.of(Objects.requireNonNull(resource).getPath()), Charset.defaultCharset());
+
+        response = client.toBlocking().exchange(POST("/api/v1/flows/validate", flow).contentType(MediaType.APPLICATION_YAML), Argument.listOf(ValidateConstraintViolation.class));
+
+        body = response.body();
+        assertThat(body.size(), is(2));
+        assertThat(body.get(0).getConstraints(), containsString("Unrecognized field \"unknownProp\""));
+        assertThat(body.get(1).getConstraints(), containsString("Invalid type: io.kestra.core.tasks.debugs.UnknownTask"));
+    }
+
     private Flow generateFlow(String namespace, String inputName) {
         return generateFlow(IdUtils.create(), namespace, inputName);
     }
diff --git a/webserver/src/test/resources/flows/validateMultipleInvalidFlows.yaml b/webserver/src/test/resources/flows/validateMultipleInvalidFlows.yaml
new file mode 100644
index 0000000000..7a5c502450
--- /dev/null
+++ b/webserver/src/test/resources/flows/validateMultipleInvalidFlows.yaml
@@ -0,0 +1,19 @@
+id: "first_flow"
+namespace: "validation"
+tasks:
+  - id: task_one
+    type: io.kestra.core.tasks.scripts.Bash
+    commands:
+      - 'echo strange---string'
+    unknownProp: unknownValue
+
+---
+
+id: "second_flow"
+namespace: "validation"
+tasks:
+  - id: task-two
+    type: io.kestra.core.tasks.debugs.UnknownTask
+  - id: task-three
+    type: io.kestra.core.tasks.debugs.Return
+    format: strangestring---
\ No newline at end of file
diff --git a/webserver/src/test/resources/flows/validateMultipleValidFlows.yaml b/webserver/src/test/resources/flows/validateMultipleValidFlows.yaml
new file mode 100644
index 0000000000..55513373cc
--- /dev/null
+++ b/webserver/src/test/resources/flows/validateMultipleValidFlows.yaml
@@ -0,0 +1,19 @@
+id: "first_flow"
+namespace: "validation"
+tasks:
+  - id: task_one
+    type: io.kestra.core.tasks.scripts.Bash
+    commands:
+      - 'echo strange---string'
+
+---
+
+id: "second_flow"
+namespace: "validation"
+tasks:
+  - id: task-two
+    type: io.kestra.core.tasks.debugs.Return
+    format: strangestring---
+  - id: task-three
+    type: io.kestra.core.tasks.debugs.Return
+    format: ---strangestring
\ No newline at end of file