Repository: kestra-io/kestra
Issue: Removing a condition on a trigger didn't recompute the next execution date
Labels: bug

### Describe the issue

For example with the following flow:

```
id: hello-schedule-condition-bug
namespace: company.team

triggers:
  - id: schedule
    type: io.kestra.core.models.triggers.types.Schedule
    cron: "* * * * *"
    scheduleConditions:
     -  type: io.kestra.core.models.conditions.types.DayWeekInMonthCondition
        date: "{{ trigger.date }}"
        dayOfWeek: "MONDAY"
        dayInMonth: "FIRST"

tasks:
  - id: hello
    type: io.kestra.core.tasks.log.Log
    message: Schedule with condition
```

Due to the condition, the next evaluation date will be, for ex 2024-03-04 (as we are the 2024-02-15) and not in a minute.
Removing the condition didn't update the next evaluation date.

### Environment

- Kestra Version:
- Operating System (OS/Docker/Kubernetes):
- Java Version (if you don't run kestra in Docker):


Issue comments:

Pull request review comments:
I think it's better to calculate the next date out of this method so any exception can be handled easily.
Those methods inside the Trigger object are designed to be builders/mappers.

Patch that resolved the issue:
diff --git a/core/src/main/java/io/kestra/core/models/triggers/Trigger.java b/core/src/main/java/io/kestra/core/models/triggers/Trigger.java
index 557e97b0bf..ea78eab732 100644
--- a/core/src/main/java/io/kestra/core/models/triggers/Trigger.java
+++ b/core/src/main/java/io/kestra/core/models/triggers/Trigger.java
@@ -6,7 +6,10 @@
 import io.kestra.core.models.flows.State;
 import io.kestra.core.utils.IdUtils;
 import io.micronaut.core.annotation.Nullable;
-import lombok.*;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
 import lombok.experimental.SuperBuilder;
 
 import java.time.Instant;
@@ -214,11 +217,11 @@ public static Trigger of(Flow flow, AbstractTrigger abstractTrigger, ConditionCo
             .nextExecutionDate(((PollingTriggerInterface) abstractTrigger).nextEvaluationDate(conditionContext, Optional.empty()))
             .stopAfter(abstractTrigger.getStopAfter())
             .disabled(lastTrigger.map(TriggerContext::getDisabled).orElse(Boolean.FALSE))
-            .backfill(lastTrigger.map(TriggerContext::getBackfill).orElse(null))
+            .backfill(null)
             .build();
     }
 
-    public static Trigger update(Trigger currentTrigger, Trigger newTrigger) {
+    public static Trigger update(Trigger currentTrigger, Trigger newTrigger, ZonedDateTime nextExecutionDate) throws Exception {
         Trigger updated = currentTrigger;
 
         // If a backfill is created, we update the currentTrigger
@@ -237,10 +240,13 @@ public static Trigger update(Trigger currentTrigger, Trigger newTrigger) {
                             currentTrigger.getNextExecutionDate())
                         .build())
                 .build();
+        } else {
+
         }
 
         return updated.toBuilder()
-            .nextExecutionDate(ZonedDateTime.now())
+            .nextExecutionDate(newTrigger.getDisabled() ?
+                null : nextExecutionDate)
             .disabled(newTrigger.getDisabled())
             .build();
     }
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 8779145142..30cf1c3558 100644
--- a/core/src/main/java/io/kestra/core/services/FlowService.java
+++ b/core/src/main/java/io/kestra/core/services/FlowService.java
@@ -184,9 +184,9 @@ public static List<AbstractTrigger> findRemovedTrigger(Flow flow, Flow previous)
     }
 
     public static List<AbstractTrigger> findUpdatedTrigger(Flow flow, Flow previous) {
-        return ListUtils.emptyOnNull(previous.getTriggers())
+        return ListUtils.emptyOnNull(flow.getTriggers())
             .stream()
-            .filter(oldTrigger -> ListUtils.emptyOnNull(flow.getTriggers())
+            .filter(oldTrigger -> ListUtils.emptyOnNull(previous.getTriggers())
                 .stream()
                 .anyMatch(trigger -> trigger.getId().equals(oldTrigger.getId()) && !trigger.equals(oldTrigger))
             )
diff --git a/webserver/src/main/java/io/kestra/webserver/controllers/TriggerController.java b/webserver/src/main/java/io/kestra/webserver/controllers/TriggerController.java
index 406a58df3e..564e38c6f4 100644
--- a/webserver/src/main/java/io/kestra/webserver/controllers/TriggerController.java
+++ b/webserver/src/main/java/io/kestra/webserver/controllers/TriggerController.java
@@ -1,9 +1,17 @@
 package io.kestra.webserver.controllers;
 
+import io.kestra.core.models.conditions.ConditionContext;
+import io.kestra.core.models.flows.Flow;
+import io.kestra.core.models.triggers.AbstractTrigger;
+import io.kestra.core.models.triggers.PollingTriggerInterface;
 import io.kestra.core.models.triggers.Trigger;
 import io.kestra.core.models.triggers.TriggerContext;
 import io.kestra.core.queues.QueueInterface;
+import io.kestra.core.repositories.FlowRepositoryInterface;
 import io.kestra.core.repositories.TriggerRepositoryInterface;
+import io.kestra.core.runners.RunContext;
+import io.kestra.core.runners.RunContextFactory;
+import io.kestra.core.services.ConditionService;
 import io.kestra.core.tenant.TenantService;
 import io.kestra.webserver.responses.PagedResults;
 import io.kestra.webserver.utils.PageableUtils;
@@ -18,6 +26,7 @@
 import io.swagger.v3.oas.annotations.Parameter;
 import jakarta.inject.Inject;
 
+import java.time.ZonedDateTime;
 import java.util.List;
 import java.util.Optional;
 
@@ -29,9 +38,18 @@ public class TriggerController {
     @Inject
     private QueueInterface<Trigger> triggerQueue;
 
+    @Inject
+    private FlowRepositoryInterface flowRepository;
+
     @Inject
     private TenantService tenantService;
 
+    @Inject
+    private  RunContextFactory runContextFactory;
+
+    @Inject
+    private  ConditionService conditionService;
+
     @ExecuteOn(TaskExecutors.IO)
     @Get(uri = "/search")
     @Operation(tags = {"Triggers"}, summary = "Search for triggers")
@@ -107,8 +125,26 @@ public PagedResults<Trigger> find(
     public HttpResponse<Trigger> update(
         @Parameter(description = "The trigger") @Body final Trigger newTrigger
     ) throws HttpStatusException {
+
+        Optional<Flow> maybeFlow = this.flowRepository.findById(this.tenantService.resolveTenant(), newTrigger.getNamespace(), newTrigger.getFlowId());
+        if (maybeFlow.isEmpty()) {
+            throw new HttpStatusException(HttpStatus.NOT_FOUND, String.format("Flow of trigger %s not found", newTrigger.getTriggerId()));
+        }
+        AbstractTrigger abstractTrigger = maybeFlow.get().getTriggers().stream().filter(t -> t.getId().equals(newTrigger.getTriggerId())).findFirst().orElse(null);
+        if (abstractTrigger == null) {
+            throw new HttpStatusException(HttpStatus.NOT_FOUND, String.format("Flow %s has no trigger %s", newTrigger.getFlowId(), newTrigger.getTriggerId()));
+        }
+
         Trigger updatedTrigger = this.triggerRepository.lock(newTrigger.uid(), (current) -> {
-            Trigger updated = Trigger.update(current, newTrigger);
+            Trigger updated = null;
+            try {
+                RunContext runContext = runContextFactory.of(maybeFlow.get(), abstractTrigger);
+                ConditionContext conditionContext = conditionService.conditionContext(runContext, maybeFlow.get(), null);
+                ZonedDateTime nextExecutionDate = ((PollingTriggerInterface) abstractTrigger).nextEvaluationDate(conditionContext, Optional.empty());
+                updated = Trigger.update(current, newTrigger, nextExecutionDate);
+            } catch (Exception e) {
+                throw new HttpStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
+            }
             triggerQueue.emit(updated);
 
             return updated;


Test patch:
diff --git a/webserver/src/test/java/io/kestra/webserver/controllers/TriggerControllerTest.java b/webserver/src/test/java/io/kestra/webserver/controllers/TriggerControllerTest.java
index 85a058a4f3..ff87066459 100644
--- a/webserver/src/test/java/io/kestra/webserver/controllers/TriggerControllerTest.java
+++ b/webserver/src/test/java/io/kestra/webserver/controllers/TriggerControllerTest.java
@@ -147,10 +147,13 @@ void unlock() {
 
     @Test
     void updated() {
+        Flow flow = generateFlow("flow-with-triggers-updated");
+        jdbcFlowRepository.create(flow, flow.generateSource(), flow);
+
         Trigger trigger = Trigger.builder()
-            .flowId(IdUtils.create())
-            .namespace("io.kestra.unittest")
-            .triggerId("controllerUpdated")
+            .flowId(flow.getId())
+            .namespace(flow.getNamespace())
+            .triggerId("trigger-nextexec-schedule")
             .executionId(IdUtils.create())
             .disabled(true)
             .build();
@@ -173,7 +176,7 @@ void updated() {
 
     @Test
     void nextExecutionDate() throws InterruptedException, TimeoutException {
-        Flow flow = generateFlow();
+        Flow flow = generateFlow("flow-with-triggers");
         jdbcFlowRepository.create(flow, flow.generateSource(), flow);
         Await.until(
             () -> client.toBlocking().retrieve(HttpRequest.GET("/api/v1/triggers/search?q=trigger-nextexec"), Argument.of(PagedResults.class, Trigger.class)).getTotal() >= 2,
@@ -185,9 +188,9 @@ void nextExecutionDate() throws InterruptedException, TimeoutException {
         assertThat(triggers.getResults().get(1).getNextExecutionDate(), notNullValue());
     }
 
-    private Flow generateFlow() {
+    private Flow generateFlow(String flowId) {
         return Flow.builder()
-            .id("flow-with-triggers")
+            .id(flowId)
             .namespace("io.kestra.tests.scheduler")
             .tasks(Collections.singletonList(Return.builder()
                 .id("task")