diff --git a/core/pom.xml b/core/pom.xml
index b827fc4d9..e259beabe 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -156,6 +156,10 @@
../ui/spt-ui-lib/lib/server-api.ts
module
implementationFile
+
+ jakarta.annotation.Nullable
+ org.springframework.lang.Nullable
+
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/PersistentTaskService.java b/core/src/main/java/org/sterl/spring/persistent_tasks/PersistentTaskService.java
index b83411bfe..90cec913c 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/PersistentTaskService.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/PersistentTaskService.java
@@ -10,7 +10,6 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
@@ -21,7 +20,9 @@
import org.sterl.spring.persistent_tasks.api.event.TriggerTaskCommand;
import org.sterl.spring.persistent_tasks.history.HistoryService;
import org.sterl.spring.persistent_tasks.history.model.CompletedTriggerEntity;
+import org.sterl.spring.persistent_tasks.history.model.HistoryTriggerEntity;
import org.sterl.spring.persistent_tasks.scheduler.SchedulerService;
+import org.sterl.spring.persistent_tasks.shared.model.HasTrigger;
import org.sterl.spring.persistent_tasks.shared.model.TriggerEntity;
import org.sterl.spring.persistent_tasks.trigger.TriggerService;
import org.sterl.spring.persistent_tasks.trigger.model.RunningTriggerEntity;
@@ -47,27 +48,19 @@ public class PersistentTaskService {
* @param key the {@link TriggerKey} to look for
* @return the {@link TriggerEntity} to the {@link TriggerKey}
*/
- public Optional getLastTriggerData(TriggerKey key) {
+ public Optional getLastTriggerData(TriggerKey key) {
final Optional trigger = triggerService.get(key);
if (trigger.isEmpty()) {
var history = historyService.findLastKnownStatus(key);
if (history.isPresent()) {
- return Optional.ofNullable(history.get().getData());
+ return Optional.ofNullable(history.get());
}
return Optional.empty();
} else {
- return Optional.ofNullable(trigger.get().getData());
+ return Optional.ofNullable(trigger.get());
}
}
- public Optional getLastDetailData(TriggerKey key) {
- var data = historyService.findAllDetailsForKey(key, Pageable.ofSize(1));
- if (data.isEmpty()) {
- return Optional.empty();
- }
- return Optional.of(data.getContent().get(0).getData());
- }
-
@EventListener
void queue(TriggerTaskCommand extends Serializable> event) {
if (event.size() == 1) {
@@ -167,4 +160,10 @@ public Optional findLastTriggerByCorrelationId(String correlation
}
return result.isEmpty() ? Optional.empty() : Optional.of(result.getFirst());
}
+
+ public Optional getLastTriggerHistory(Long id) {
+ var result = historyService.findAllDetailsForInstance(id, PageRequest.ofSize(1));
+ if (result.isEmpty()) return Optional.empty();
+ return Optional.of(result.getContent().getFirst());
+ }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/api/HistoryTrigger.java b/core/src/main/java/org/sterl/spring/persistent_tasks/api/HistoryTrigger.java
new file mode 100644
index 000000000..8ba71b605
--- /dev/null
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/api/HistoryTrigger.java
@@ -0,0 +1,31 @@
+package org.sterl.spring.persistent_tasks.api;
+
+import java.time.OffsetDateTime;
+
+import org.springframework.lang.Nullable;
+
+import lombok.Data;
+
+@Data
+public class HistoryTrigger {
+
+ /** just a unique id of this trigger */
+ private Long id;
+
+ private Long instanceId;
+
+ /** the business key which is unique it is combination for triggers but not the history! */
+ private TriggerKey key;
+
+ private OffsetDateTime createdTime;
+
+ @Nullable
+ private OffsetDateTime start;
+
+ private int executionCount = 0;
+
+ private TriggerStatus status;
+
+ @Nullable
+ private String message;
+}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/api/Trigger.java b/core/src/main/java/org/sterl/spring/persistent_tasks/api/Trigger.java
index 719ab4274..cefa3ac66 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/api/Trigger.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/api/Trigger.java
@@ -2,6 +2,8 @@
import java.time.OffsetDateTime;
+import org.springframework.lang.Nullable;
+
import lombok.Data;
@Data
@@ -15,20 +17,26 @@ public class Trigger {
/** the business key which is unique it is combination for triggers but not the history! */
private TriggerKey key;
+ @Nullable
private String tag;
+ @Nullable
private String correlationId;
+ @Nullable
private String runningOn;
- private OffsetDateTime createdTime = OffsetDateTime.now();
+ private OffsetDateTime createdTime;
- private OffsetDateTime runAt = OffsetDateTime.now();
+ private OffsetDateTime runAt;
+ @Nullable
private OffsetDateTime lastPing;
+ @Nullable
private OffsetDateTime start;
+ @Nullable
private OffsetDateTime end;
private int executionCount = 0;
@@ -38,10 +46,14 @@ public class Trigger {
private TriggerStatus status = TriggerStatus.WAITING;
+ @Nullable
private Long runningDurationInMs;
+ @Nullable
private Object state;
+ @Nullable
private String exceptionName;
+ @Nullable
private String lastException;
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerSearch.java b/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerSearch.java
index 229f978e6..e06db2f21 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerSearch.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/api/TriggerSearch.java
@@ -26,17 +26,18 @@ public boolean hasValue() {
|| StringHelper.hasValue(tag);
}
-
public static TriggerSearch byCorrelationId(String correlationId) {
var result = new TriggerSearch();
result.setCorrelationId(correlationId);
return result;
}
+
public static TriggerSearch byStatus(TriggerStatus status) {
var result = new TriggerSearch();
result.setStatus(status);
return result;
}
+
public static TriggerSearch forTriggerRequest(TriggerRequest> trigger) {
var search = new TriggerSearch();
if (trigger.key() != null) {
@@ -57,6 +58,7 @@ public static TriggerSearch forTriggerRequest(TriggerRequest> trigger) {
public static Sort sortByCreatedTime(Direction direction) {
return Sort.by(direction, "data.createdTime");
}
+
public static Pageable applyDefaultSortIfNeeded(Pageable page) {
var result = page;
if (page.getSort() == Sort.unsorted()) {
@@ -64,5 +66,4 @@ public static Pageable applyDefaultSortIfNeeded(Pageable page) {
}
return result;
}
-
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryService.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryService.java
index 0158b7a6d..6a4602f14 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryService.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryService.java
@@ -22,6 +22,7 @@
import org.sterl.spring.persistent_tasks.history.model.QCompletedTriggerEntity;
import org.sterl.spring.persistent_tasks.history.repository.CompletedTriggerRepository;
import org.sterl.spring.persistent_tasks.history.repository.TriggerHistoryDetailRepository;
+import org.sterl.spring.persistent_tasks.shared.QueryHelper;
import org.sterl.spring.persistent_tasks.shared.model.HasTrigger;
import org.sterl.spring.persistent_tasks.shared.stereotype.TransactionalService;
@@ -53,9 +54,10 @@ public void deleteAll() {
triggerHistoryDetailRepository.deleteAllInBatch();
}
- public void deleteAllOlderThan(OffsetDateTime age) {
- completedTriggerRepository.deleteOlderThan(age);
- triggerHistoryDetailRepository.deleteOlderThan(age);
+ public long deleteAllOlderThan(OffsetDateTime age) {
+ var result = triggerHistoryDetailRepository.deleteOlderThan(age);
+ result += completedTriggerRepository.deleteOlderThan(age);
+ return result;
}
/**
@@ -65,16 +67,9 @@ public long countTriggers(TriggerStatus status) {
return triggerHistoryDetailRepository.countByStatus(status);
}
- public List findAllDetailsForInstance(long instanceId) {
- return triggerHistoryDetailRepository.findAllByInstanceId(instanceId);
- }
-
- public Page findAllDetailsForKey(TriggerKey key) {
- return findAllDetailsForKey(key, PageRequest.of(0, 100));
- }
- public Page findAllDetailsForKey(TriggerKey key, Pageable page) {
- page = applyDefaultSortIfNeeded(page);
- return triggerHistoryDetailRepository.listKnownStatusFor(key, page);
+ public Page findAllDetailsForInstance(long instanceId, Pageable page) {
+ page = QueryHelper.applySortIfEmpty(page, Sort.by(Direction.DESC, "id"));
+ return triggerHistoryDetailRepository.findAllByInstanceId(instanceId, page);
}
public Optional reQueue(Long id, OffsetDateTime runAt) {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryTimer.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryTimer.java
index abcc6ee79..da0b39fcd 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryTimer.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/HistoryTimer.java
@@ -30,8 +30,8 @@ class HistoryTimer {
void deleteOldHistory() {
try {
final var age = OffsetDateTime.now().minus(historyTimeout);
- historyService.deleteAllOlderThan(age);
- log.debug("Deleted triggers older than {}.", historyTimeout);
+ var count = historyService.deleteAllOlderThan(age);
+ log.debug("Deleted history {} older than {}.", count, historyTimeout);
} catch (Exception e) {
log.error("Failed to delete old triggers", e);
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/HistoryConverter.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/HistoryConverter.java
index da36fc5d3..851d60384 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/HistoryConverter.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/HistoryConverter.java
@@ -1,7 +1,9 @@
package org.sterl.spring.persistent_tasks.history.api;
import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
import org.sterl.spring.persistent_tasks.api.Trigger;
+import org.sterl.spring.persistent_tasks.api.HistoryTrigger;
import org.sterl.spring.persistent_tasks.history.model.CompletedTriggerEntity;
import org.sterl.spring.persistent_tasks.history.model.HistoryTriggerEntity;
import org.sterl.spring.persistent_tasks.shared.ExtendetConvert;
@@ -22,16 +24,23 @@ public Trigger convert(@NonNull CompletedTriggerEntity source) {
}
}
- enum FromTriggerStateDetailEntity implements ExtendetConvert {
+ enum ToHistoryTrigger implements ExtendetConvert {
INSTANCE;
- @NonNull
@Override
- public Trigger convert(@NonNull HistoryTriggerEntity source) {
- var result = ToTrigger.INSTANCE.convert(source);
+ @Nullable
+ public HistoryTrigger convert(@NonNull HistoryTriggerEntity source) {
+ var result = new HistoryTrigger();
+ result.setCreatedTime(source.getCreatedTime());
+ result.setExecutionCount(source.getExecutionCount());
result.setId(source.getId());
result.setInstanceId(source.getInstanceId());
+ result.setKey(source.getKey());
+ result.setMessage(source.getMessage());
+ result.setStart(source.getStart());
+ result.setStatus(source.getStatus());
return result;
}
+
}
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/TriggerHistoryResource.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/TriggerHistoryResource.java
index c93f87ad0..13983e14f 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/TriggerHistoryResource.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/api/TriggerHistoryResource.java
@@ -15,11 +15,12 @@
import org.sterl.spring.persistent_tasks.api.TaskStatusHistoryOverview;
import org.sterl.spring.persistent_tasks.api.Trigger;
import org.sterl.spring.persistent_tasks.api.TriggerGroup;
+import org.sterl.spring.persistent_tasks.api.HistoryTrigger;
import org.sterl.spring.persistent_tasks.api.TriggerKey;
import org.sterl.spring.persistent_tasks.api.TriggerSearch;
import org.sterl.spring.persistent_tasks.history.HistoryService;
import org.sterl.spring.persistent_tasks.history.api.HistoryConverter.FromLastTriggerStateEntity;
-import org.sterl.spring.persistent_tasks.history.api.HistoryConverter.FromTriggerStateDetailEntity;
+import org.sterl.spring.persistent_tasks.history.api.HistoryConverter.ToHistoryTrigger;
import lombok.RequiredArgsConstructor;
@@ -32,9 +33,11 @@ public class TriggerHistoryResource {
private final HistoryService historyService;
@GetMapping("history/instance/{instanceId}")
- public List listInstances(@PathVariable("instanceId") long instanceId) {
- return FromTriggerStateDetailEntity.INSTANCE.convert( //
- historyService.findAllDetailsForInstance(instanceId));
+ public PagedModel listInstances(
+ @PathVariable("instanceId") long instanceId,
+ @PageableDefault(size = 250) Pageable page) {
+
+ return ToHistoryTrigger.INSTANCE.toPage(historyService.findAllDetailsForInstance(instanceId, page));
}
@GetMapping("task-status-history")
public List taskStatusHistory() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/component/TriggerHistoryComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/component/TriggerHistoryComponent.java
index 4cd8703ed..98c96b294 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/component/TriggerHistoryComponent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/component/TriggerHistoryComponent.java
@@ -1,7 +1,6 @@
package org.sterl.spring.persistent_tasks.history.component;
-import java.time.OffsetDateTime;
-
+import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@@ -64,11 +63,16 @@ public void execute(final long triggerId, final TriggerEntity data, boolean isDo
}
var detail = new HistoryTriggerEntity();
+ detail.setExecutionCount(data.getExecutionCount());
detail.setInstanceId(triggerId);
- detail.setData(data.toBuilder()
- .state(null)
- .createdTime(OffsetDateTime.now())
- .build());
+ detail.setKey(data.getKey());
+
+ var msg = data.getExceptionName();
+ if (data.getLastException() != null) msg = data.getLastException();
+ detail.setMessage(StringUtils.substring(msg, 0, 200));
+
+ detail.setStart(data.getStart());
+ detail.setStatus(data.getStatus());
triggerHistoryDetailRepository.save(detail);
}
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/CompletedTriggerEntity.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/CompletedTriggerEntity.java
index 0819fc916..721cd8b26 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/CompletedTriggerEntity.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/CompletedTriggerEntity.java
@@ -21,12 +21,12 @@
@Entity
@Table(name = "pt_completed_triggers", indexes = {
- @Index(name = "idx_pt_trigger_history_last_states_task_name", columnList = "task_name"),
- @Index(name = "idx_pt_trigger_history_last_states_trigger_id", columnList = "trigger_id"),
- @Index(name = "idx_pt_trigger_history_last_states_status", columnList = "status"),
- @Index(name = "idx_pt_trigger_history_last_states_created_time", columnList = "created_time"),
- @Index(name = "idx_pt_trigger_history_last_states_correlation_id", columnList = "correlation_id"),
- @Index(name = "idx_pt_trigger_history_last_states_tag", columnList = "tag"),
+ @Index(name = "idx_pt_completed_triggers_task_name", columnList = "task_name"),
+ @Index(name = "idx_pt_completed_triggers_trigger_id", columnList = "trigger_id"),
+ @Index(name = "idx_pt_completed_triggers_status", columnList = "status"),
+ @Index(name = "idx_pt_completed_triggers_created_time", columnList = "created_time"),
+ @Index(name = "idx_pt_completed_triggers_correlation_id", columnList = "correlation_id"),
+ @Index(name = "idx_pt_completed_triggers_tag", columnList = "tag"),
})
@Data
@NoArgsConstructor
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/HistoryTriggerEntity.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/HistoryTriggerEntity.java
index 9e226a129..1dd853e8e 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/HistoryTriggerEntity.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/model/HistoryTriggerEntity.java
@@ -3,12 +3,16 @@
import java.time.OffsetDateTime;
import org.sterl.spring.persistent_tasks.api.TriggerKey;
-import org.sterl.spring.persistent_tasks.shared.model.HasTrigger;
+import org.sterl.spring.persistent_tasks.api.TriggerStatus;
import org.sterl.spring.persistent_tasks.shared.model.TriggerEntity;
+import jakarta.persistence.AttributeOverride;
+import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -17,6 +21,7 @@
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
+import lombok.Builder.Default;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@@ -26,41 +31,51 @@
*/
@Entity
@Table(name = "pt_trigger_history", indexes = {
- @Index(name = "idx_pt_trigger_history_instance_id", columnList = "instance_id"),
- @Index(name = "idx_pt_trigger_history_name", columnList = "task_name"),
- @Index(name = "idx_pt_trigger_history_trigger_id", columnList = "trigger_id"),
- @Index(name = "idx_pt_trigger_history_status", columnList = "status"),
- @Index(name = "idx_pt_trigger_history_created_time", columnList = "created_time"),
- @Index(name = "idx_pt_trigger_history_correlation_id", columnList = "correlation_id"),
- @Index(name = "idx_pt_trigger_history_tag", columnList = "tag"),
+ @Index(name = "idx_pt_trigger_history_instance_id", columnList = "instance_id"),
})
@Data
@NoArgsConstructor
@Builder(toBuilder = true)
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
-public class HistoryTriggerEntity implements HasTrigger {
+public class HistoryTriggerEntity {
- @GeneratedValue(generator = "seq_pt_trigger_history_details", strategy = GenerationType.SEQUENCE)
+ @GeneratedValue(generator = "seq_pt_trigger_history", strategy = GenerationType.SEQUENCE)
@Column(updatable = false)
@Id
private Long id;
+
+ @Enumerated(EnumType.STRING)
+ @Column(updatable = false, nullable = false)
+ private TriggerStatus status;
/**
* The original ID of this trigger in case grouping is needed
* as for each trigger multiple history entries are added.
*/
+ @Column(name = "instance_id", updatable = false)
private Long instanceId;
@Embedded
+ @AttributeOverrides(@AttributeOverride(
+ name = "id",
+ column = @Column(name = "trigger_id", nullable = false, length = 200, updatable = false)
+ )
+ )
+ private TriggerKey key;
+
+ @Default
@NotNull
- private TriggerEntity data;
+ @Column(nullable = false, updatable = false, name = "created_time")
+ private OffsetDateTime createdTime = OffsetDateTime.now();
- public TriggerKey getKey() {
- return data.getKey();
- }
+ @Column(name = "start_time", updatable = false)
+ private OffsetDateTime start;
+
+ @Column(updatable = false)
+ private int executionCount;
+
+ @Column(length = 200, updatable = false)
+ private String message;
- public void setCreatedTime(OffsetDateTime time) {
- this.data.setCreatedTime(time);
- }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/CompletedTriggerRepository.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/CompletedTriggerRepository.java
index 46a9e96da..01ce4e2f2 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/CompletedTriggerRepository.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/CompletedTriggerRepository.java
@@ -2,11 +2,16 @@
import java.util.List;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import org.sterl.spring.persistent_tasks.api.TaskStatusHistoryOverview;
+import org.sterl.spring.persistent_tasks.api.TriggerKey;
import org.sterl.spring.persistent_tasks.history.model.CompletedTriggerEntity;
+import org.sterl.spring.persistent_tasks.shared.repository.TriggerRepository;
-public interface CompletedTriggerRepository extends HistoryTriggerRepository {
+public interface CompletedTriggerRepository extends TriggerRepository {
@Query("""
SELECT new org.sterl.spring.persistent_tasks.api.TaskStatusHistoryOverview(
@@ -25,4 +30,10 @@ public interface CompletedTriggerRepository extends HistoryTriggerRepository listTriggerStatus();
+
+ @Query("""
+ SELECT e FROM #{#entityName} e
+ WHERE e.data.key = :key
+ """)
+ Page listKnownStatusFor(@Param("key") TriggerKey key, Pageable page);
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/HistoryTriggerRepository.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/HistoryTriggerRepository.java
deleted file mode 100644
index 37d62d881..000000000
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/HistoryTriggerRepository.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.sterl.spring.persistent_tasks.history.repository;
-
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.NoRepositoryBean;
-import org.springframework.data.repository.query.Param;
-import org.sterl.spring.persistent_tasks.api.TriggerKey;
-import org.sterl.spring.persistent_tasks.shared.model.HasTrigger;
-import org.sterl.spring.persistent_tasks.shared.repository.TriggerRepository;
-
-@NoRepositoryBean
-public interface HistoryTriggerRepository extends TriggerRepository {
-
- @Query("""
- SELECT e FROM #{#entityName} e
- WHERE e.data.key = :key
- """)
- Page listKnownStatusFor(@Param("key") TriggerKey key, Pageable page);
-}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/TriggerHistoryDetailRepository.java b/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/TriggerHistoryDetailRepository.java
index 2ba576f1d..160618194 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/TriggerHistoryDetailRepository.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/history/repository/TriggerHistoryDetailRepository.java
@@ -1,18 +1,39 @@
package org.sterl.spring.persistent_tasks.history.repository;
-import java.util.List;
+import java.time.OffsetDateTime;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.Param;
+import org.sterl.spring.persistent_tasks.api.TriggerStatus;
import org.sterl.spring.persistent_tasks.history.model.HistoryTriggerEntity;
-public interface TriggerHistoryDetailRepository extends HistoryTriggerRepository {
+public interface TriggerHistoryDetailRepository
+ extends JpaRepository, QuerydslPredicateExecutor {
- @Query("""
- SELECT e
- FROM #{#entityName} e
- WHERE e.instanceId = :instanceId
- ORDER BY e.id DESC
- """)
- List findAllByInstanceId(@Param("instanceId") long instanceId);
+ @Query("""
+ SELECT e
+ FROM #{#entityName} e
+ WHERE e.instanceId = :instanceId
+ """)
+ Page findAllByInstanceId(
+ @Param("instanceId") long instanceId, Pageable page);
+
+ @Query("""
+ DELETE FROM #{#entityName} e
+ WHERE e.createdTime < :age
+ """)
+ @Modifying
+ long deleteOlderThan(@Param("age") OffsetDateTime age);
+
+ @Query("""
+ SELECT COUNT(e.id)
+ FROM #{#entityName} e
+ WHERE e.status = :status
+ """)
+ long countByStatus(@Param("status") TriggerStatus status);
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerTimer.java b/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerTimer.java
index c6bee8b61..104c5171b 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerTimer.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerTimer.java
@@ -43,8 +43,8 @@ void rescheduleAbandonedTasks() {
for (SchedulerService s : schedulerServices) {
try {
final var count = s.rescheduleAbandonedTriggers(timeout);
- log.debug("Found {} abandoned tasks for {}. Timeout={}",
- count.size(), s.getName(), timeout);
+ log.info("Found {} abandoned tasks for {}. Timeout={}",
+ count.size(), s.getName(), taskTimeout);
} catch (Exception e) {
log.error("Scheduler {} failed schedule abandoned tasks", s.getName(), e);
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/DateUtil.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/DateUtil.java
new file mode 100644
index 000000000..370323775
--- /dev/null
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/DateUtil.java
@@ -0,0 +1,11 @@
+package org.sterl.spring.persistent_tasks.shared;
+
+import java.time.OffsetDateTime;
+
+public class DateUtil {
+
+ public static long secondsBeetween(OffsetDateTime start, long secondsEnd) {
+ if (start == null) return 0;
+ return secondsEnd - start.toEpochSecond();
+ }
+}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/QueryHelper.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/QueryHelper.java
index d69adb7dd..98cd156c4 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/QueryHelper.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/QueryHelper.java
@@ -1,5 +1,8 @@
package org.sterl.spring.persistent_tasks.shared;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@@ -8,6 +11,14 @@
import com.querydsl.core.types.dsl.StringPath;
public class QueryHelper {
+
+ public static Pageable applySortIfEmpty(Pageable page, Sort sort) {
+ Pageable result = page;
+ if (page.getSort() == null || page.getSort() == Sort.unsorted()) {
+ result = PageRequest.of(page.getPageNumber(), page.getPageSize(), sort);
+ }
+ return result;
+ }
@Nullable
public static Predicate eq(@NonNull SimpleExpression path, @Nullable T value) {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java
index 409fcf3e7..fa2a5775b 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/converter/ToTrigger.java
@@ -4,6 +4,7 @@
import org.sterl.spring.persistent_tasks.api.Trigger;
import org.sterl.spring.persistent_tasks.shared.ExtendetConvert;
import org.sterl.spring.persistent_tasks.shared.model.HasTrigger;
+import org.sterl.spring.persistent_tasks.shared.model.TriggerEntity;
import org.sterl.spring.persistent_tasks.trigger.component.StateSerializer;
public enum ToTrigger implements ExtendetConvert {
@@ -14,7 +15,7 @@ public enum ToTrigger implements ExtendetConvert {
@NonNull
@Override
public Trigger convert(@NonNull HasTrigger hasData) {
- final var source = hasData.getData();
+ final TriggerEntity source = hasData.getData();
final var result = new Trigger();
result.setKey(source.getKey());
result.setCorrelationId(source.getCorrelationId());
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/model/HasTrigger.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/model/HasTrigger.java
index ac9dfe600..3397bd977 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/model/HasTrigger.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/model/HasTrigger.java
@@ -7,6 +7,8 @@
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
public interface HasTrigger {
+ Long getId();
+
TriggerEntity getData();
default TriggerKey key() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/repository/TriggerRepository.java b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/repository/TriggerRepository.java
index 14a89ef0f..7f82c9297 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/shared/repository/TriggerRepository.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/shared/repository/TriggerRepository.java
@@ -103,13 +103,13 @@ default Page findByGroup(
Page findAll(@Param("taskName") String taskName, Pageable page);
@Query("""
- SELECT COUNT(e.data.key)
+ SELECT COUNT(e.id)
FROM #{#entityName} e WHERE e.data.key.taskName = :taskName
""")
long countByTaskName(@Param("taskName") String taskName);
@Query("""
- SELECT COUNT(e.data.key)
+ SELECT COUNT(e.id)
FROM #{#entityName} e WHERE e.data.key = :key
""")
long countByKey(@Param("key") TriggerKey key);
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/TriggerService.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/TriggerService.java
index a15f26a0c..9f9163a68 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/TriggerService.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/TriggerService.java
@@ -18,6 +18,7 @@
import org.sterl.spring.persistent_tasks.api.TriggerRequest;
import org.sterl.spring.persistent_tasks.api.TriggerSearch;
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
+import org.sterl.spring.persistent_tasks.shared.DateUtil;
import org.sterl.spring.persistent_tasks.shared.stereotype.TransactionalService;
import org.sterl.spring.persistent_tasks.task.TaskService;
import org.sterl.spring.persistent_tasks.trigger.component.EditTriggerComponent;
@@ -206,10 +207,15 @@ public long countTriggers(@Nullable TriggerStatus status) {
public List rescheduleAbandoned(OffsetDateTime timeout) {
final List result = readTrigger.findTriggersLastPingAfter(
timeout);
- final var e = new IllegalStateException("Trigger abandoned - timeout: " + timeout);
+ var now = OffsetDateTime.now().toEpochSecond();
result.forEach(t -> {
- var task = taskService.get(t.newTaskId());
- var state = stateSerializer.deserializeOrNull(t.getData().getState());
+ final var task = taskService.get(t.newTaskId());
+ final var state = stateSerializer.deserializeOrNull(t.getData().getState());
+
+ final var e = new IllegalStateException("Trigger abandoned. Timeout: "
+ + timeout + " running on: " + t.getRunningOn()
+ + " since: " + DateUtil.secondsBeetween(t.getData().getStart(), now));
+
failTrigger.execute(task.orElse(null), t, state, e);
});
log.debug("rescheduled {} triggers", result.size());
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java
index 190bbc042..bd5f3dbb3 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/ReadTriggerComponent.java
@@ -56,9 +56,16 @@ public boolean hasPendingTriggers() {
}
return false;
}
-
+
+ /**
+ * Searches for all triggers which are still running but the ping is long ago.
+ * @param dateTime the ping should be before this date
+ * @return all found triggers - never null
+ */
public List findTriggersLastPingAfter(OffsetDateTime dateTime) {
- return triggerRepository.findTriggersLastPingAfter(dateTime);
+ return triggerRepository.findTriggersLastPingAfter(
+ TriggerStatus.RUNNING,
+ dateTime);
}
public Page searchTriggers(@Nullable TriggerSearch search, Pageable page) {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
index 068918a9e..59745e778 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/component/RunTriggerComponent.java
@@ -1,5 +1,6 @@
package org.sterl.spring.persistent_tasks.trigger.component;
+import java.time.OffsetDateTime;
import java.util.Optional;
import org.springframework.lang.Nullable;
@@ -25,7 +26,8 @@ public class RunTriggerComponent {
private final StateSerializer serializer = new StateSerializer();
/**
- * Will execute the given {@link RunningTriggerEntity} and handle any errors etc.
+ * Will execute the given {@link RunningTriggerEntity} and handle any errors
+ * etc.
*/
@Transactional(propagation = Propagation.NEVER)
public Optional execute(RunningTriggerEntity trigger) {
@@ -35,9 +37,17 @@ public Optional execute(RunningTriggerEntity trigger) {
final var runTaskWithState = buildTaskWithStateFor(trigger);
// something went really wrong this trigger is crap
- if (runTaskWithState == null) return Optional.of(trigger);
+ if (runTaskWithState == null)
+ return Optional.of(trigger);
try {
+ if (OffsetDateTime.now().isAfter(trigger.getData().getRunAt())) {
+ log.debug("Running {} for {} time.", trigger.key(), trigger.executionCount());
+ } else {
+ log.info("Running to early {} start should be {} for {} time.",
+ trigger.key(), trigger.getData().getRunAt(), trigger.executionCount());
+ }
+
RunningTriggerContextHolder.setContext(runTaskWithState.runningTrigger());
return runTaskWithState.execute(editTrigger);
} catch (Exception e) {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerAddedEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerAddedEvent.java
index 057ad3163..c18b6f4e3 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerAddedEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerAddedEvent.java
@@ -10,7 +10,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerAddedEvent(long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
+public record TriggerAddedEvent(Long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
@Override
public boolean isDone() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerCanceledEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerCanceledEvent.java
index 79e24d506..dd14a6c20 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerCanceledEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerCanceledEvent.java
@@ -10,7 +10,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerCanceledEvent(long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
+public record TriggerCanceledEvent(Long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
@Override
public boolean isDone() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerExpiredEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerExpiredEvent.java
index 431363ab3..752a49d2b 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerExpiredEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerExpiredEvent.java
@@ -10,7 +10,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerExpiredEvent(long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
+public record TriggerExpiredEvent(Long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
@Override
public boolean isDone() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerFailedEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerFailedEvent.java
index dc72339b0..93c0d6028 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerFailedEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerFailedEvent.java
@@ -10,7 +10,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerFailedEvent(long id,
+public record TriggerFailedEvent(Long id,
TriggerEntity data, Serializable state,
Exception exception,
OffsetDateTime retryAt) implements TriggerLifeCycleEvent {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerLifeCycleEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerLifeCycleEvent.java
index 4b5cdc1e3..a86a37afa 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerLifeCycleEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerLifeCycleEvent.java
@@ -1,6 +1,8 @@
package org.sterl.spring.persistent_tasks.trigger.event;
import java.io.Serializable;
+import java.time.Duration;
+import java.time.OffsetDateTime;
import org.springframework.lang.Nullable;
import org.sterl.spring.persistent_tasks.api.event.PersistentTasksEvent;
@@ -15,12 +17,30 @@ public interface TriggerLifeCycleEvent extends HasTrigger, PersistentTasksEvent
default TriggerEntity getData() {
return data();
}
- long id();
+
+ Long id();
+
+ default Long getId() {
+ return id();
+ }
+
TriggerEntity data();
+
@Nullable
Serializable state();
+
/**
+ * If a trigger is done, it finished it's execution either successfully or with an final error without retries left.
+ *
* @return true if the trigger was completed, either with success, error or canceled.
*/
boolean isDone();
+
+ default long timePassedMs() {
+ var result = data().getRunningDurationInMs();
+ if (result == null && data().getStart() != null) {
+ result = Duration.between(data().getStart(), OffsetDateTime.now()).toMillis();
+ }
+ return result == null ? 0 : result.longValue();
+ }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerResumedEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerResumedEvent.java
index 032f5d3b2..0520b99ba 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerResumedEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerResumedEvent.java
@@ -10,7 +10,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerResumedEvent(long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
+public record TriggerResumedEvent(Long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
@Override
public boolean isDone() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerRunningEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerRunningEvent.java
index e8f2924be..701023e80 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerRunningEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerRunningEvent.java
@@ -10,7 +10,7 @@
* This event is maybe not in a transaction and so a transactional event listener will not work.
*
*/
-public record TriggerRunningEvent(long id, TriggerEntity data, Serializable state, String runningOn) implements TriggerLifeCycleEvent {
+public record TriggerRunningEvent(Long id, TriggerEntity data, Serializable state, String runningOn) implements TriggerLifeCycleEvent {
public boolean isRunningOn(String name) {
return isRunning() && name != null && name.equals(runningOn);
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerSuccessEvent.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerSuccessEvent.java
index 45690b68d..e13e3f784 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerSuccessEvent.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/event/TriggerSuccessEvent.java
@@ -9,7 +9,7 @@
* Inside a transaction, it is save to join or listen for the AFTER_COMMIT
*
*/
-public record TriggerSuccessEvent(long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
+public record TriggerSuccessEvent(Long id, TriggerEntity data, Serializable state) implements TriggerLifeCycleEvent {
@Override
public boolean isDone() {
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/TriggerMetricInterceptor.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/TriggerMetricInterceptor.java
index e47e74b06..5ec496fcc 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/TriggerMetricInterceptor.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/interceptor/TriggerMetricInterceptor.java
@@ -7,9 +7,7 @@
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
-import org.sterl.spring.persistent_tasks.trigger.event.TriggerCanceledEvent;
-import org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent;
-import org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent;
+import org.sterl.spring.persistent_tasks.trigger.event.TriggerLifeCycleEvent;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
@@ -26,23 +24,12 @@ public class TriggerMetricInterceptor {
private final Map cache = new ConcurrentHashMap<>();
@EventListener
- public void onFailed(TriggerFailedEvent data) {
- recordTime(data.key().getTaskName(),
- data.status(),
- data.getData().getRunningDurationInMs());
- }
- @EventListener
- public void onSuccess(TriggerSuccessEvent data) {
- recordTime(data.key().getTaskName(),
- data.status(),
- data.getData().getRunningDurationInMs());
-
- }
- @EventListener
- public void onSuccess(TriggerCanceledEvent data) {
- recordTime(data.key().getTaskName(),
- data.status(),
- data.getData().getRunningDurationInMs());
+ public void onFailed(TriggerLifeCycleEvent data) {
+ if (data.isDone()) {
+ recordTime(data.key().getTaskName(),
+ data.status(),
+ data.timePassedMs());
+ }
}
private void recordTime(String name, TriggerStatus status, Long timeMs) {
@@ -57,7 +44,7 @@ private void recordTime(String name, TriggerStatus status, Long timeMs) {
.register(meterRegistry);
cache.put(key, timer);
}
- timer.record(timeMs, TimeUnit.MICROSECONDS);
+ timer.record(timeMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("Failed to update timer for {}", name, e);
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java
index 4f9b08fce..19a8dad1e 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunTaskWithStateCommand.java
@@ -59,4 +59,9 @@ boolean hasValues(Collection> elements) {
public TriggerEntity getData() {
return trigger.getData();
}
+
+ @Override
+ public Long getId() {
+ return trigger.getId();
+ }
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunningTriggerEntity.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunningTriggerEntity.java
index 39c7ae96a..d2785f952 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunningTriggerEntity.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/RunningTriggerEntity.java
@@ -85,16 +85,22 @@ public RunningTriggerEntity cancel(Exception e) {
public void finishTriggerWithStatus(TriggerStatus status, Exception e) {
this.data.setEnd(OffsetDateTime.now());
- this.data.updateRunningDuration();
this.data.setStatus(status);
+ this.data.updateRunningDuration();
+ this.lastPing = null;
- if (e != null) {
+ if (e == null) {
+ this.data.setExceptionName(null);
+ this.data.setLastException(null);
+ } else {
this.data.setExceptionName(e.getClass().getName());
this.data.setLastException(ExceptionUtils.getStackTrace(e));
}
}
public RunningTriggerEntity runOn(String runningOn) {
+ this.data.setExceptionName(null);
+ this.data.setLastException(null);
this.data.setStart(OffsetDateTime.now());
this.data.setEnd(null);
this.data.setExecutionCount(data.getExecutionCount() + 1);
@@ -108,7 +114,8 @@ public RunningTriggerEntity runOn(String runningOn) {
public RunningTriggerEntity runAt(OffsetDateTime runAt) {
data.setStatus(TriggerStatus.WAITING);
data.setRunAt(runAt);
- setRunningOn(null);
+ this.runningOn = null;
+ this.lastPing = null;
return this;
}
diff --git a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/repository/RunningTriggerRepository.java b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/repository/RunningTriggerRepository.java
index 3750998c9..de031ddf8 100644
--- a/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/repository/RunningTriggerRepository.java
+++ b/core/src/main/java/org/sterl/spring/persistent_tasks/trigger/repository/RunningTriggerRepository.java
@@ -69,9 +69,11 @@ int markTriggersAsRunning(
@Query("""
SELECT e FROM #{#entityName} e
- WHERE e.lastPing < :lastPing
+ WHERE e.data.status = :status
+ AND e.lastPing < :lastPing
""")
List findTriggersLastPingAfter(
+ @Param("status") TriggerStatus status,
@Param("lastPing") OffsetDateTime lastPing);
@Query("""
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
index c9a08228b..956f8df08 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/history/HistoryServiceTest.java
@@ -47,24 +47,23 @@ void testReQueueTrigger() {
@Test
void testTriggerHistory() throws TimeoutException, InterruptedException {
// GIVEN
- final var trigger = Task3.ID.newUniqueTrigger("Hallo");
- triggerService.queue(trigger);
+ final var trigger = triggerService.queue(Task3.ID.newUniqueTrigger("Hallo"));
persistentTaskTestService.runNextTrigger();
// WHEN
- var triggers = subject.findAllDetailsForKey(trigger.key(), PageRequest.of(0, 100)).getContent();
+ var triggers = subject.findAllDetailsForInstance(trigger.getId(),
+ PageRequest.of(0, 100)).getContent();
// AND
assertThat(triggers).hasSize(3);
- assertThat(triggers.get(0).getData().getStatus()).isEqualTo(TriggerStatus.SUCCESS);
- assertThat(triggers.get(1).getData().getStatus()).isEqualTo(TriggerStatus.RUNNING);
- assertThat(triggers.get(2).getData().getStatus()).isEqualTo(TriggerStatus.WAITING);
+ assertThat(triggers.get(0).getStatus()).isEqualTo(TriggerStatus.SUCCESS);
+ assertThat(triggers.get(1).getStatus()).isEqualTo(TriggerStatus.RUNNING);
+ assertThat(triggers.get(2).getStatus()).isEqualTo(TriggerStatus.WAITING);
}
@RepeatedTest(3)
void testTriggerHistoryTrx() {
// GIVEN
- final var trigger = Task3.ID.newUniqueTrigger("Hallo");
- persistentTaskService.queue(trigger);
+ final var trigger = persistentTaskService.queue(Task3.ID.newUniqueTrigger("Hallo"));
// WHEN
hibernateAsserts.reset();
schedulerService.triggerNextTasks().forEach(t -> {
@@ -75,7 +74,6 @@ void testTriggerHistoryTrx() {
// 2 to get the work done
// 1 for the success history
hibernateAsserts.assertTrxCount(3);
- assertThat(subject.countTriggers(trigger.key())).isEqualTo(1);
- assertThat(subject.findAllDetailsForKey(trigger.key()).getTotalElements()).isEqualTo(3);
+ assertThat(subject.countTriggers(trigger)).isEqualTo(1);
}
}
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTest.java
index c08da9a7d..49fd6526a 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTest.java
@@ -129,7 +129,7 @@ void testRunOrQueue() throws Exception {
asserts.awaitValue(Task3.NAME + "::Hallo");
persistentTaskTestService.awaitRunningTriggers();
- assertThat(persistentTaskService.getLastTriggerData(ref).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(ref).get().status())
.isEqualTo(TriggerStatus.SUCCESS);
}
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTransactionTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTransactionTest.java
index e30445992..dc09f08b3 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTransactionTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTransactionTest.java
@@ -13,6 +13,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.data.domain.Pageable;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionTemplate;
import org.sterl.spring.persistent_tasks.AbstractSpringTest;
@@ -112,13 +113,13 @@ void testSaveNoTransactions() throws Exception {
hibernateAsserts.assertTrxCount(4);
assertThat(personRepository.count()).isOne();
// AND
- var data = persistentTaskService.getLastDetailData(trigger.key());
- assertThat(data.get().getStatus()).isEqualTo(TriggerStatus.SUCCESS);
+ var data = persistentTaskService.getLastTriggerHistory(trigger.getId()).get();
+ assertThat(data.getStatus()).isEqualTo(TriggerStatus.SUCCESS);
// AND
- var history = historyService.findAllDetailsForKey(trigger.key()).getContent();
- assertThat(history.get(0).getData().getStatus()).isEqualTo(TriggerStatus.SUCCESS);
- assertThat(history.get(1).getData().getStatus()).isEqualTo(TriggerStatus.RUNNING);
- assertThat(history.get(2).getData().getStatus()).isEqualTo(TriggerStatus.WAITING);
+ var history = historyService.findAllDetailsForInstance(trigger.getId(), Pageable.ofSize(10)).getContent();
+ assertThat(history.get(0).getStatus()).isEqualTo(TriggerStatus.SUCCESS);
+ assertThat(history.get(1).getStatus()).isEqualTo(TriggerStatus.RUNNING);
+ assertThat(history.get(2).getStatus()).isEqualTo(TriggerStatus.WAITING);
}
@Test
@@ -138,13 +139,13 @@ void testSaveTransactions() throws Exception {
hibernateAsserts.assertTrxCount(3);
assertThat(personRepository.count()).isOne();
// AND
- var data = persistentTaskService.getLastDetailData(trigger.key());
- assertThat(data.get().getStatus()).isEqualTo(TriggerStatus.SUCCESS);
+ var data = persistentTaskService.getLastTriggerHistory(trigger.getId()).get();
+ assertThat(data.getStatus()).isEqualTo(TriggerStatus.SUCCESS);
// AND
- var history = historyService.findAllDetailsForKey(trigger.key()).getContent();
- assertThat(history.get(0).getData().getStatus()).isEqualTo(TriggerStatus.SUCCESS);
- assertThat(history.get(1).getData().getStatus()).isEqualTo(TriggerStatus.RUNNING);
- assertThat(history.get(2).getData().getStatus()).isEqualTo(TriggerStatus.WAITING);
+ var history = historyService.findAllDetailsForInstance(trigger.getId(), Pageable.ofSize(10)).getContent();
+ assertThat(history.get(0).getStatus()).isEqualTo(TriggerStatus.SUCCESS);
+ assertThat(history.get(1).getStatus()).isEqualTo(TriggerStatus.RUNNING);
+ assertThat(history.get(2).getStatus()).isEqualTo(TriggerStatus.WAITING);
}
@Test
@@ -168,15 +169,16 @@ void test_fail_in_transaction() throws Exception {
// 4. Update the status to failed and write the history
hibernateAsserts.assertTrxCount(4);
// AND
- var data = persistentTaskService.getLastDetailData(trigger.key());
- assertThat(data.get().getStatus()).isEqualTo(TriggerStatus.FAILED);
+ var history = persistentTaskService.getLastTriggerHistory(trigger.getId()).get();
+ assertThat(history.getStatus()).isEqualTo(TriggerStatus.FAILED);
+ // AND
assertThat(triggerService.get(trigger.getKey()).get().getRunningOn()).isNull();
assertThat(triggerService.get(trigger.getKey()).get().status()).isEqualTo(TriggerStatus.WAITING);
// AND
- var history = historyService.findAllDetailsForKey(trigger.key()).getContent();
- assertThat(history.get(0).getData().getStatus()).isEqualTo(TriggerStatus.FAILED);
- assertThat(history.get(1).getData().getStatus()).isEqualTo(TriggerStatus.RUNNING);
- assertThat(history.get(2).getData().getStatus()).isEqualTo(TriggerStatus.WAITING);
+ var histories = historyService.findAllDetailsForInstance(trigger.getId(), Pageable.ofSize(10)).getContent();
+ assertThat(histories.get(0).getStatus()).isEqualTo(TriggerStatus.FAILED);
+ assertThat(histories.get(1).getStatus()).isEqualTo(TriggerStatus.RUNNING);
+ assertThat(histories.get(2).getStatus()).isEqualTo(TriggerStatus.WAITING);
}
@Test
@@ -187,7 +189,7 @@ void testRunOrQueueTransactions() throws Exception {
// THEN 1 to save and 1 to start it and 1 for the history
awaitHistoryThreads();
hibernateAsserts.assertTrxCount(3);
- assertThat(persistentTaskService.getLastTriggerData(k1).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k1).get().status())
.isEqualTo(TriggerStatus.RUNNING);
// WHEN
@@ -200,7 +202,7 @@ void testRunOrQueueTransactions() throws Exception {
// THEN
assertThat(personRepository.count()).isEqualTo(1);
// AND
- assertThat(persistentTaskService.getLastTriggerData(k1).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k1).get().status())
.isEqualTo(TriggerStatus.SUCCESS);
}
@@ -211,9 +213,9 @@ void testRunOrQueueShowsRunning() throws Exception {
var k2 = subject.runOrQueue(TriggerBuilder.newTrigger("savePersonInTrx").state("Paul").build());
// WHEN
- assertThat(persistentTaskService.getLastTriggerData(k1).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k1).get().status())
.isEqualTo(TriggerStatus.RUNNING);
- assertThat(persistentTaskService.getLastTriggerData(k2).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k2).get().status())
.isEqualTo(TriggerStatus.RUNNING);
COUNTDOWN.countDown();
@@ -222,9 +224,9 @@ void testRunOrQueueShowsRunning() throws Exception {
// THEN
assertThat(personRepository.count()).isEqualTo(2);
// AND
- assertThat(persistentTaskService.getLastTriggerData(k1).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k1).get().status())
.isEqualTo(TriggerStatus.SUCCESS);
- assertThat(persistentTaskService.getLastTriggerData(k2).get().getStatus())
+ assertThat(persistentTaskService.getLastTriggerData(k2).get().status())
.isEqualTo(TriggerStatus.SUCCESS);
}
@@ -241,13 +243,11 @@ void testRollbackAndRetry() throws Exception {
awaitRunningTasks();
// THEN
- var history = historyService.findAllDetailsForKey(key).getContent();
- assertThat(history.get(0).getData().getStatus())
- .isEqualTo(TriggerStatus.FAILED);
- assertThat(history.get(1).getData().getStatus())
- .isEqualTo(TriggerStatus.RUNNING);
- assertThat(history.get(2).getData().getStatus())
- .isEqualTo(TriggerStatus.WAITING);
+ var trigger = persistentTaskService.getLastTriggerData(key);
+ var history = historyService.findAllDetailsForInstance(trigger.get().getId(), Pageable.ofSize(10)).getContent();
+ assertThat(history.get(0).getStatus()).isEqualTo(TriggerStatus.FAILED);
+ assertThat(history.get(1).getStatus()).isEqualTo(TriggerStatus.RUNNING);
+ assertThat(history.get(2).getStatus()).isEqualTo(TriggerStatus.WAITING);
// WHEN
sendError.set(false);
@@ -262,7 +262,7 @@ void testRollbackAndRetry() throws Exception {
private void assertExecutionCount(TriggerKey triggerKey, int count) throws InterruptedException, ExecutionException {
var data = persistentTaskService.getLastTriggerData(triggerKey);
assertThat(data).isPresent();
- assertThat(data.get().getExecutionCount()).isEqualTo(count);
+ assertThat(data.get().getData().getExecutionCount()).isEqualTo(count);
}
protected void awaitRunningTasks() throws TimeoutException, InterruptedException {
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/TaskFailoverTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/TaskFailoverTest.java
index 87a0a1a04..28a0a6b81 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/TaskFailoverTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/TaskFailoverTest.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
import org.sterl.spring.persistent_tasks.AbstractSpringTest;
import org.sterl.spring.persistent_tasks.api.TaskId;
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
@@ -61,8 +62,9 @@ void rescheduleAbandonedTasksTest() throws Exception {
assertThat(triggerService.countTriggers(TriggerStatus.WAITING))
.isEqualTo(1);
// AND
- var timeoutTrigger = historyService.findAllDetailsForKey(willTimeout.getKey()).getContent().getFirst();
- assertThat(timeoutTrigger.status()).isEqualTo(TriggerStatus.FAILED);
- assertThat(timeoutTrigger.getData().getLastException()).contains("Trigger abandoned");
+ var timeoutTrigger = historyService.findAllDetailsForInstance(willTimeout.getId(),
+ Pageable.ofSize(10)).getContent().getFirst();
+ assertThat(timeoutTrigger.getStatus()).isEqualTo(TriggerStatus.FAILED);
+ assertThat(timeoutTrigger.getMessage()).contains("Trigger abandoned");
}
}
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerLifeCycleTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerLifeCycleTest.java
index dee56b786..f99553ec2 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerLifeCycleTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerLifeCycleTest.java
@@ -95,11 +95,12 @@ public void afterTriggerFailed(String state, Exception e) {
}
});
- trx.executeWithoutResult(trx -> {
+ trx.execute(trx -> {
var t = subject.queue(taskId.newTrigger().waitForSignal(OffsetDateTime.now().minusSeconds(1)).build());
t.runOn("foo-bar-gone");
t.setLastPing(OffsetDateTime.now().minusDays(1));
t.getData().setExecutionCount(99);
+ return t;
});
// WHEN
diff --git a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
index 41c9e1dff..39c20fb99 100644
--- a/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
+++ b/core/src/test/java/org/sterl/spring/persistent_tasks/trigger/TriggerServiceTest.java
@@ -28,7 +28,6 @@
import org.sterl.spring.persistent_tasks.trigger.component.StateSerializer.DeSerializationFailedException;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerAddedEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerCanceledEvent;
-import org.sterl.spring.persistent_tasks.trigger.event.TriggerExpiredEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerFailedEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerResumedEvent;
import org.sterl.spring.persistent_tasks.trigger.event.TriggerSuccessEvent;
@@ -430,6 +429,28 @@ void testRescheduleAbandonedTasks() {
assertThat(rescheduledTasks.get(0).getKey()).isEqualTo(t1.getKey());
}
+ @Test
+ void testRescheduleAbandonedTasksOnlyRunning() {
+ // GIVEN
+ var now = OffsetDateTime.now();
+ var t1 = new RunningTriggerEntity(new TriggerKey(UuidCreator.getTimeOrdered().toString(), "fooTask"))
+ .runOn("fooScheduler");
+ t1.setLastPing(now.minusSeconds(60));
+ triggerRepository.save(t1);
+
+ var t2 = new RunningTriggerEntity(
+ new TriggerKey(UuidCreator.getTimeOrdered().toString(), "barTask"));
+ t2.setLastPing(now.minusSeconds(60));
+ triggerRepository.save(t2);
+
+ // WHEN
+ final var rescheduledTasks = subject.rescheduleAbandoned(now.minusSeconds(59));
+
+ // THEN
+ assertThat(rescheduledTasks).hasSize(1);
+ assertThat(rescheduledTasks.get(0).getKey()).isEqualTo(t1.getKey());
+ }
+
@Test
void testUnknownTriggersNoRetry() {
// GIVEN
@@ -441,8 +462,8 @@ void testUnknownTriggersNoRetry() {
// WHEN
var triggerData = persistentTaskService.getLastTriggerData(t.getKey()).get();
- assertThat(triggerData.getStatus()).isEqualTo(TriggerStatus.FAILED);
- assertThat(triggerData.getExceptionName()).isEqualTo(IllegalStateException.class.getName());
+ assertThat(triggerData.status()).isEqualTo(TriggerStatus.FAILED);
+ assertThat(triggerData.getData().getExceptionName()).isEqualTo(IllegalStateException.class.getName());
}
@Test
@@ -457,8 +478,8 @@ void testBadStateNoRetry() {
// WHEN
var triggerData = persistentTaskService.getLastTriggerData(t.getKey()).get();
- assertThat(triggerData.getStatus()).isEqualTo(TriggerStatus.FAILED);
- assertThat(triggerData.getExceptionName()).isEqualTo(DeSerializationFailedException.class.getName());
+ assertThat(triggerData.status()).isEqualTo(TriggerStatus.FAILED);
+ assertThat(triggerData.getData().getExceptionName()).isEqualTo(DeSerializationFailedException.class.getName());
// AND
assertThat(events.stream(TriggerSuccessEvent.class).count()).isZero();
assertThat(events.stream(TriggerFailedEvent.class).count()).isOne();
diff --git a/db/src/main/resources/spring-persistent-tasks/db/pt-changelog-v6.xml b/db/src/main/resources/spring-persistent-tasks/db/pt-changelog-v6.xml
new file mode 100644
index 000000000..6d2e2ae48
--- /dev/null
+++ b/db/src/main/resources/spring-persistent-tasks/db/pt-changelog-v6.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/README.md b/example/README.md
index 378f9fae0..308888515 100644
--- a/example/README.md
+++ b/example/README.md
@@ -5,3 +5,8 @@
- url: http://localhost:8080/task-ui
- user: admin
- password: admin
+
+
+# Read metric by status
+
+- http://localhost:8080/actuator/metrics/persistent_tasks.task.failingBuildVehicleTask?tag=status:FAILED
diff --git a/example/src/main/resources/application.yml b/example/src/main/resources/application.yml
index a989a5159..a186a1d71 100644
--- a/example/src/main/resources/application.yml
+++ b/example/src/main/resources/application.yml
@@ -8,6 +8,8 @@ spring:
persistent-tasks:
max-threads: 1
+ trigger-timeout: PT2M
+ poll-abandoned-triggers: 60
liquibase:
change-log: classpath:db/changelog/db.changelog-master.xml
diff --git a/ui/spt-ui-lib/lib/server-api.ts b/ui/spt-ui-lib/lib/server-api.ts
index 25e4b92ae..2fbc4509c 100644
--- a/ui/spt-ui-lib/lib/server-api.ts
+++ b/ui/spt-ui-lib/lib/server-api.ts
@@ -3,7 +3,7 @@
export interface PagedModel {
content: T[];
- page: PageMetadata;
+ page?: PageMetadata;
}
export interface SchedulerEntity {
@@ -16,6 +16,17 @@ export interface SchedulerEntity {
lastPing: string;
}
+export interface HistoryTrigger {
+ id: number;
+ instanceId: number;
+ key: TriggerKey;
+ createdTime: string;
+ start?: string;
+ executionCount: number;
+ status: TriggerStatus;
+ message?: string;
+}
+
export interface TaskStatusHistoryOverview {
taskName: string;
status: TriggerStatus;
@@ -32,21 +43,21 @@ export interface Trigger {
id: number;
instanceId: number;
key: TriggerKey;
- tag: string;
- correlationId: string;
- runningOn: string;
+ tag?: string;
+ correlationId?: string;
+ runningOn?: string;
createdTime: string;
runAt: string;
- lastPing: string;
- start: string;
- end: string;
+ lastPing?: string;
+ start?: string;
+ end?: string;
executionCount: number;
priority: number;
status: TriggerStatus;
- runningDurationInMs: number;
- state: any;
- exceptionName: string;
- lastException: string;
+ runningDurationInMs?: number;
+ state?: any;
+ exceptionName?: string;
+ lastException?: string;
}
export interface TriggerGroup {
diff --git a/ui/spt-ui-lib/lib/shared/date.util.ts b/ui/spt-ui-lib/lib/shared/date.util.ts
index 146b1119e..91f7c4e68 100644
--- a/ui/spt-ui-lib/lib/shared/date.util.ts
+++ b/ui/spt-ui-lib/lib/shared/date.util.ts
@@ -30,18 +30,15 @@ export function formatShortDateTime(inputDate?: string | Date): string {
export function formatDateTime(inputDate?: string | Date): string {
if (!inputDate) return "";
const date = inputDate instanceof Date ? inputDate : new Date(inputDate);
- return new Intl.DateTimeFormat(
- navigator.language || "en-US",
- {
- day: "2-digit",
- month: "2-digit",
- year: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- hour12: false, // Use 24-hour format
- }
- ).format(date);
+ return new Intl.DateTimeFormat(navigator.language || "en-US", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false, // Use 24-hour format
+ }).format(date);
}
export function formatMs(ms?: number) {
@@ -56,7 +53,7 @@ export function formatMs(ms?: number) {
if (inS < 181) {
return sign + inS + "s " + (ms - inS * 1000) + "ms";
}
-
+
const inMin = Math.floor(inS / 60);
if (inMin < 181) {
return sign + inMin + "min " + (inS - inMin * 60) + "s";
@@ -73,4 +70,21 @@ export function runningSince(value?: string) {
if (!value) return "";
const msRuntime = new Date().getTime() - new Date(value).getTime();
return `since ${formatMs(msRuntime)}`;
-}
\ No newline at end of file
+}
+
+export function durationInSeconds(
+ start?: string | Date,
+ end?: string | Date
+): number | undefined {
+ if (!start || !end) return undefined;
+ const startDate = typeof start === "string" ? new Date(start) : start;
+ const endDate = typeof end === "string" ? new Date(end) : end;
+
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
+ return undefined; // invalid date input
+ }
+
+ const seconds = (endDate.getTime() - startDate.getTime()) / 1000;
+ if (seconds < 100) return Math.round(seconds * 10) / 10;
+ return Math.round(seconds);
+}
diff --git a/ui/spt-ui-lib/lib/shared/http-request.ts b/ui/spt-ui-lib/lib/shared/http-request.ts
index dd959cf8d..bb101c2cb 100644
--- a/ui/spt-ui-lib/lib/shared/http-request.ts
+++ b/ui/spt-ui-lib/lib/shared/http-request.ts
@@ -55,6 +55,7 @@ export const useServerObject = (
} else {
console.error(requestUrl, e);
setError(e);
+ throw e;
}
})
.finally(() => setIsLoading(false));
diff --git a/ui/spt-ui-lib/lib/shared/view/page.view.tsx b/ui/spt-ui-lib/lib/shared/view/page.view.tsx
index 4d4930a59..417ffd413 100644
--- a/ui/spt-ui-lib/lib/shared/view/page.view.tsx
+++ b/ui/spt-ui-lib/lib/shared/view/page.view.tsx
@@ -15,11 +15,11 @@ const PageView: React.FC = ({
}) => {
if (!data || isLoading) return ;
- const currentPage = data?.page.number ?? 0;
- const totalPages = data?.page.totalPages ?? 0;
- const totalElements = data?.page.totalElements ?? 0;
- const pageSize = data?.page.size ?? 0;
- const contentLength = data?.content?.length ?? 0;
+ const currentPage = data.page?.number ?? 0;
+ const totalPages = data.page?.totalPages ?? 0;
+ const totalElements = data.page?.totalElements ?? 0;
+ const pageSize = data.page?.size ?? 0;
+ const contentLength = data.content?.length ?? 0;
const handlePrevClick = () => {
if (currentPage > 0) {
diff --git a/ui/spt-ui-lib/lib/shared/view/trigger-history-list.view.tsx b/ui/spt-ui-lib/lib/shared/view/trigger-history-list.view.tsx
index 6602ec652..00f8d7e9a 100644
--- a/ui/spt-ui-lib/lib/shared/view/trigger-history-list.view.tsx
+++ b/ui/spt-ui-lib/lib/shared/view/trigger-history-list.view.tsx
@@ -1,13 +1,13 @@
-import { type Trigger } from "@lib/server-api";
-import { formatDateTime, formatMs } from "@lib/shared/date.util";
+import { type HistoryTrigger, type PagedModel } from "@lib/server-api";
+import { formatDateTime } from "@lib/shared/date.util";
import { useEffect } from "react";
import { Col, ListGroup, Row } from "react-bootstrap";
import { useServerObject } from "../http-request";
import HttpRequestView from "./http-request.view";
-import RunningTriggerStatusView from "./running-trigger-status.view";
+import TriggerStatusView from "./trigger-status.view";
const TriggerHistoryListView = ({ instanceId }: { instanceId: number }) => {
- const triggerHistory = useServerObject(
+ const triggerHistory = useServerObject>(
"/spring-tasks-api/history/instance/"
);
const doGet = triggerHistory.doGet;
@@ -18,18 +18,26 @@ const TriggerHistoryListView = ({ instanceId }: { instanceId: number }) => {
request={triggerHistory}
render={(history) => (
- {history.map((t) => (
+ {history.content.map((t) => (
-
-
-
+
+
+ #{t.executionCount} -
+
+
+
+ {formatDateTime(t.createdTime)}
+
+
+ {t.message}
- {formatDateTime(t.createdTime)}
- execution: {t.executionCount}
- {formatMs(t.runningDurationInMs)}
))}
diff --git a/ui/spt-ui-lib/lib/shared/view/trigger-status-icon.view.tsx b/ui/spt-ui-lib/lib/shared/view/trigger-status-icon.view.tsx
index 6163d91be..256d766df 100644
--- a/ui/spt-ui-lib/lib/shared/view/trigger-status-icon.view.tsx
+++ b/ui/spt-ui-lib/lib/shared/view/trigger-status-icon.view.tsx
@@ -29,13 +29,22 @@ const getIcon = (status: TriggerStatus) => {
const getVariant = (trigger: Trigger): string => {
if (["FAILED", "EXPIRED_SIGNAL"].includes(trigger.status)) return "danger";
if (trigger.status === "CANCELED") return "secondary";
- if (trigger.status === "SUCCESS") return "success";
if (trigger.executionCount > 1) return "warning";
+ if (trigger.status === "SUCCESS") return "success";
if (trigger.status === "RUNNING") return "primary";
if (trigger.status === "WAITING") return "secondary";
return "";
};
+const getStatusText = (trigger: Trigger): string => {
+ if (trigger.executionCount > 1 && trigger.status === "RUNNING") {
+ return trigger.status + " for " + trigger.executionCount + ". time";
+ } else if (trigger.executionCount > 1 && trigger.status === "WAITING") {
+ return trigger.status + " for " + trigger.executionCount + ". retry";
+ }
+ return trigger.status.replace(/_/g, " ");
+};
+
const TriggerStatusIcon = ({ trigger }: { trigger: Trigger }) => {
const variant = getVariant(trigger);
const icon = getIcon(trigger.status);
@@ -43,9 +52,7 @@ const TriggerStatusIcon = ({ trigger }: { trigger: Trigger }) => {
return (
{icon}
-
- {trigger.status.replace(/_/g, " ")}
-
+ {getStatusText(trigger)}
);
};
diff --git a/ui/spt-ui-lib/lib/shared/view/trigger-summary.view.tsx b/ui/spt-ui-lib/lib/shared/view/trigger-summary.view.tsx
deleted file mode 100644
index 075a9caeb..000000000
--- a/ui/spt-ui-lib/lib/shared/view/trigger-summary.view.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import type { Trigger } from "@lib/server-api";
-import { Badge, Col, Row, type ColProps } from "react-bootstrap";
-import LabeledText from "./labled-text.view";
-import { formatMs, formatShortDateTime, runningSince } from "../date.util";
-
-const TriggerSummaryView = ({
- trigger,
- col = { md: 3, xs: 6 },
-}: {
- trigger: Trigger;
- col?: ColProps;
-}) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default TriggerSummaryView;
-
-const TriggerExecutiomView = ({ trigger }: { trigger: Trigger }) => {
- if (trigger.runningOn) {
- return (
-
- );
- }
- if (trigger.status == "WAITING" && trigger.runAt) {
- return (
-
- );
- }
- return (
-
- 1 ? "warning" : "success"}
- >
- {trigger.executionCount}
-
-
- {formatMs(trigger.runningDurationInMs)}
-
-
- }
- />
- );
-};
diff --git a/ui/spt-ui-lib/lib/shared/view/trigger.view.tsx b/ui/spt-ui-lib/lib/shared/view/trigger.view.tsx
index a9d0528d2..4c3fe30ae 100644
--- a/ui/spt-ui-lib/lib/shared/view/trigger.view.tsx
+++ b/ui/spt-ui-lib/lib/shared/view/trigger.view.tsx
@@ -71,12 +71,27 @@ const TriggerView = ({
value={formatShortDateTime(trigger.end)}
/>
+
+
+
+
+
+
+
+
+
diff --git a/ui/spt-ui-lib/src/App.tsx b/ui/spt-ui-lib/src/App.tsx
index f56413a13..28811bec1 100644
--- a/ui/spt-ui-lib/src/App.tsx
+++ b/ui/spt-ui-lib/src/App.tsx
@@ -15,11 +15,20 @@ function App() {
content: [],
}
);
+ const triggerHistory = useServerObject>(
+ "/spring-tasks-api/history?size=15",
+ {
+ page: { number: 0, size: 0, totalElements: 0, totalPages: 0 },
+ content: [],
+ }
+ );
useEffect(triggers.doGet, []);
+ useEffect(triggerHistory.doGet, []);
return (
+
);