Repository: kestra-io/kestra
Issue: Deprecate `tasks` property from the Pause task or make it optional or both
Labels: enhancement
quick-win

### Feature description

The Pause task has a `tasks` property which is not needed. Compare the two examples below.


the current one

```yaml
id: human_in_the_loop
namespace: dev

tasks:
  - id: before_approval
    type: io.kestra.core.tasks.debugs.Return
    format: output data that needs to be validated before continuing the execution
  
  - id: pause
    type: io.kestra.core.tasks.flows.Pause
    tasks:
     - id: run_post_approval
       type: io.kestra.plugin.scripts.shell.Commands
       runner: PROCESS
       commands:
        - echo "manual approval received! continuing the execution"

  - id: post_resume
    type: io.kestra.core.tasks.debugs.Return
    format: "{{task.id}} > {{taskrun.startDate}}"
```

and this one:

```yaml
id: human_in_the_loop
namespace: dev

tasks:
  - id: before_approval
    type: io.kestra.core.tasks.debugs.Return
    format: output data that needs to be validated before continuing the execution
  
  - id: pause
    type: io.kestra.core.tasks.flows.Pause

  - id: run_post_approval
    type: io.kestra.plugin.scripts.shell.Commands
    runner: PROCESS
    commands:
    - echo "manual approval received! continuing the execution"

  - id: post_resume
    type: io.kestra.core.tasks.debugs.Return
    format: "{{task.id}} > {{taskrun.startDate}}"
```

they are both functionally equivalent as all tasks, whether children of Pause or not, are put on hold until you resume the execution



Issue comments:

Pull request review comments:
Errors should not be deprecated as it's part of the Flowable interface
Will Pause still be a flowable later ? I don't see any reason, and errors is used for tasks in the flowable, no tasks, no errors ?
Yes, pause will still be a flowable as it's executed by the executor.

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/tasks/flows/Pause.java b/core/src/main/java/io/kestra/core/tasks/flows/Pause.java
index 762f9c9c1c..ca967805e3 100644
--- a/core/src/main/java/io/kestra/core/tasks/flows/Pause.java
+++ b/core/src/main/java/io/kestra/core/tasks/flows/Pause.java
@@ -8,11 +8,16 @@
 import io.kestra.core.models.executions.NextTaskRun;
 import io.kestra.core.models.executions.TaskRun;
 import io.kestra.core.models.flows.State;
+import io.kestra.core.models.hierarchies.GraphCluster;
+import io.kestra.core.models.hierarchies.RelationType;
 import io.kestra.core.models.tasks.FlowableTask;
+import io.kestra.core.models.tasks.Task;
 import io.kestra.core.models.tasks.VoidOutput;
 import io.kestra.core.runners.FlowableUtils;
 import io.kestra.core.runners.RunContext;
+import io.kestra.core.utils.GraphUtils;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -76,6 +81,28 @@ public class Pause extends Sequential implements FlowableTask<VoidOutput> {
     @PluginProperty
     private Duration timeout;
 
+    @Valid
+    @PluginProperty
+    @Deprecated
+    private List<Task> tasks;
+
+    @Override
+    public GraphCluster tasksTree(Execution execution, TaskRun taskRun, List<String> parentValues) throws IllegalVariableEvaluationException {
+        GraphCluster subGraph = new GraphCluster(this, taskRun, parentValues, RelationType.SEQUENTIAL);
+
+        List<Task> tasks = this.tasks != null && !this.tasks.isEmpty() ? this.tasks : List.of(this);
+
+        GraphUtils.sequential(
+            subGraph,
+            tasks,
+            this.errors,
+            taskRun,
+            execution
+        );
+
+        return subGraph;
+    }
+
     @Override
     public List<NextTaskRun> resolveNexts(RunContext runContext, Execution execution, TaskRun parentTaskRun) throws IllegalVariableEvaluationException {
         if (this.needPause(parentTaskRun) || parentTaskRun.getState().getCurrent() == State.Type.PAUSED) {
@@ -101,6 +128,10 @@ public Optional<State.Type> resolveState(RunContext runContext, Execution execut
             return Optional.of(State.Type.PAUSED);
         }
 
+        if (this.tasks == null || this.tasks.isEmpty()) {
+            return Optional.of(State.Type.SUCCESS);
+        }
+
         return super.resolveState(runContext, execution, parentTaskRun);
     }
 }
diff --git a/core/src/main/java/io/kestra/core/tasks/flows/Sequential.java b/core/src/main/java/io/kestra/core/tasks/flows/Sequential.java
index f548780806..6eca26a288 100644
--- a/core/src/main/java/io/kestra/core/tasks/flows/Sequential.java
+++ b/core/src/main/java/io/kestra/core/tasks/flows/Sequential.java
@@ -78,7 +78,7 @@ public GraphCluster tasksTree(Execution execution, TaskRun taskRun, List<String>
 
         GraphUtils.sequential(
             subGraph,
-            this.tasks,
+            this.getTasks(),
             this.errors,
             taskRun,
             execution
@@ -91,15 +91,15 @@ public GraphCluster tasksTree(Execution execution, TaskRun taskRun, List<String>
     public List<Task> allChildTasks() {
         return Stream
             .concat(
-                this.tasks != null ? this.tasks.stream() : Stream.empty(),
-                this.errors != null ? this.errors.stream() : Stream.empty()
+                this.getTasks() != null ? this.getTasks().stream() : Stream.empty(),
+                this.getErrors() != null ? this.getErrors().stream() : Stream.empty()
             )
             .collect(Collectors.toList());
     }
 
     @Override
     public List<ResolvedTask> childTasks(RunContext runContext, TaskRun parentTaskRun) throws IllegalVariableEvaluationException {
-        return FlowableUtils.resolveTasks(this.tasks, parentTaskRun);
+        return FlowableUtils.resolveTasks(this.getTasks(), parentTaskRun);
     }
 
     @Override
@@ -107,7 +107,7 @@ public List<NextTaskRun> resolveNexts(RunContext runContext, Execution execution
         return FlowableUtils.resolveSequentialNexts(
             execution,
             this.childTasks(runContext, parentTaskRun),
-            FlowableUtils.resolveTasks(this.errors, parentTaskRun),
+            FlowableUtils.resolveTasks(this.getErrors(), parentTaskRun),
             parentTaskRun
         );
     }
diff --git a/ui/src/App.vue b/ui/src/App.vue
index 64dfa3f549..850433371e 100644
--- a/ui/src/App.vue
+++ b/ui/src/App.vue
@@ -2,7 +2,7 @@
     <el-config-provider>
         <left-menu v-if="configs" @menu-collapse="onMenuCollapse" />
         <error-toast v-if="message" :no-auto-hide="true" :message="message" />
-                <main v-if="loaded">
+        <main v-if="loaded">
             <router-view v-if="!error" />
             <template v-else>
                 <errors :code="error" />


Test patch:
diff --git a/core/src/test/java/io/kestra/core/tasks/flows/PauseTest.java b/core/src/test/java/io/kestra/core/tasks/flows/PauseTest.java
index 1fe202d3e7..be2042917f 100644
--- a/core/src/test/java/io/kestra/core/tasks/flows/PauseTest.java
+++ b/core/src/test/java/io/kestra/core/tasks/flows/PauseTest.java
@@ -46,6 +46,11 @@ void timeout() throws Exception {
         suite.runTimeout(runnerUtils);
     }
 
+    @Test
+    void runEmptyTasks() throws Exception {
+        suite.runEmptyTasks(runnerUtils);
+    }
+
     @Singleton
     public static class Suite {
         @Inject
@@ -122,5 +127,27 @@ public void runTimeout(RunnerUtils runnerUtils) throws Exception {
             assertThat(execution.getTaskRunList().get(0).getState().getHistories().stream().filter(history -> history.getState() == State.Type.FAILED).count(), is(1L));
             assertThat(execution.getTaskRunList(), hasSize(1));
         }
+
+        public void runEmptyTasks(RunnerUtils runnerUtils) throws Exception {
+            Execution execution = runnerUtils.runOneUntilPaused(null, "io.kestra.tests", "pause_no_tasks", null, null, Duration.ofSeconds(30));
+
+            assertThat(execution.getState().getCurrent(), is(State.Type.PAUSED));
+            assertThat(execution.getTaskRunList().get(0).getState().getCurrent(), is(State.Type.PAUSED));
+            assertThat(execution.getTaskRunList(), hasSize(1));
+
+            Execution restarted = executionService.markAs(
+                execution,
+                execution.findTaskRunByTaskIdAndValue("pause", List.of()).getId(),
+                State.Type.RUNNING
+            );
+
+            execution = runnerUtils.awaitExecution(
+                e -> e.getState().getCurrent() == State.Type.SUCCESS,
+                () -> executionQueue.emit(restarted),
+                Duration.ofSeconds(5)
+            );
+
+            assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
+        }
     }
 }
\ No newline at end of file
diff --git a/core/src/test/resources/flows/valids/pause_no_tasks.yaml b/core/src/test/resources/flows/valids/pause_no_tasks.yaml
new file mode 100644
index 0000000000..1cc1b3f954
--- /dev/null
+++ b/core/src/test/resources/flows/valids/pause_no_tasks.yaml
@@ -0,0 +1,9 @@
+id: pause_no_tasks
+namespace: io.kestra.tests
+
+tasks:
+  - id: pause
+    type: io.kestra.core.tasks.flows.Pause
+  - id: last
+    type: io.kestra.core.tasks.debugs.Return
+    format: "{{task.id}}"