Skip to content

Commit 844ad16

Browse files
committed
- Added Re-Queue / Re-Run trigger to history page
- Correlation Id is shown in the UI - ID search includes also Correlation Id
1 parent d96b0f0 commit 844ad16

File tree

10 files changed

+238
-220
lines changed

10 files changed

+238
-220
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## v1.6
4+
5+
- Running triggers can be canceled now
6+
- Running triggers can be failed now
7+
- https://github.com/sterlp/spring-persistent-tasks/wiki/Cancel-a-task-trigger
8+
- Triggers have now correlationId to collect them
9+
- Correlation Id is shown in the UI
10+
- ID search includes also Correlation Id
11+
312
## v1.5.6 - (2025-03-06)
413

514
- Better ID search

core/src/main/java/org/sterl/spring/persistent_tasks/history/api/TriggerHistoryResource.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package org.sterl.spring.persistent_tasks.history.api;
22

3+
import java.time.OffsetDateTime;
34
import java.util.List;
45

56
import org.apache.commons.lang3.StringUtils;
67
import org.springframework.data.domain.Pageable;
78
import org.springframework.data.web.PageableDefault;
89
import org.springframework.data.web.PagedModel;
10+
import org.springframework.http.ResponseEntity;
911
import org.springframework.web.bind.annotation.GetMapping;
1012
import org.springframework.web.bind.annotation.PathVariable;
13+
import org.springframework.web.bind.annotation.PostMapping;
1114
import org.springframework.web.bind.annotation.RequestMapping;
1215
import org.springframework.web.bind.annotation.RequestParam;
1316
import org.springframework.web.bind.annotation.RestController;
@@ -18,6 +21,7 @@
1821
import org.sterl.spring.persistent_tasks.history.HistoryService;
1922
import org.sterl.spring.persistent_tasks.history.api.HistoryConverter.FromLastTriggerStateEntity;
2023
import org.sterl.spring.persistent_tasks.history.api.HistoryConverter.FromTriggerStateDetailEntity;
24+
import org.sterl.spring.persistent_tasks.trigger.api.TriggerConverter.FromTriggerEntity;
2125

2226
import lombok.RequiredArgsConstructor;
2327

@@ -49,4 +53,10 @@ public PagedModel<Trigger> list(
4953
return FromLastTriggerStateEntity.INSTANCE.toPage( //
5054
historyService.findTriggerState(key, status, page));
5155
}
56+
57+
@PostMapping("history/{id}/re-run")
58+
public ResponseEntity<Trigger> reRunTrigger(@PathVariable(name = "id", required = true) Long id) {
59+
var newTrigger = historyService.reQueue(id, OffsetDateTime.now());
60+
return ResponseEntity.of(FromTriggerEntity.INSTANCE.convert(newTrigger));
61+
}
5262
}

core/src/main/java/org/sterl/spring/persistent_tasks/shared/repository/TriggerDataRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
public interface TriggerDataRepository<T extends HasTriggerData> extends JpaRepository<T, Long> {
2020
@Query("""
2121
SELECT e FROM #{#entityName} e
22-
WHERE (:id IS NULL OR e.data.key.id LIKE :id)
22+
WHERE ((:id IS NULL OR e.data.key.id LIKE :id)
23+
OR (:id IS NULL OR e.data.correlationId LIKE :id))
2324
AND (:taskName IS NULL OR e.data.key.taskName = :taskName)
2425
AND (:status IS NULL OR e.data.status = :status)
2526
""")

core/src/main/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerConverter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import org.sterl.spring.persistent_tasks.shared.converter.ToTrigger;
66
import org.sterl.spring.persistent_tasks.trigger.model.TriggerEntity;
77

8-
class TriggerConverter {
8+
public class TriggerConverter {
99

10-
enum FromTriggerEntity implements ExtendetConvert<TriggerEntity, Trigger> {
10+
public enum FromTriggerEntity implements ExtendetConvert<TriggerEntity, Trigger> {
1111
INSTANCE;
1212

1313
@Override

core/src/test/java/org/sterl/spring/persistent_tasks/trigger/api/TriggerResourceTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ void testSearchById() {
9696
assertThat(response.getBody()).contains(key1.getId());
9797
assertThat(response.getBody()).doesNotContain(key2.getId());
9898
}
99+
100+
@Test
101+
void testSearchByCorrelationId() {
102+
// GIVEN
103+
var t1 = triggerService.queue(TriggerBuilder.newTrigger("task1").build());
104+
var t2 = triggerService.queue(TriggerBuilder.newTrigger("task1").build());
105+
var t3 = triggerService.queue(TriggerBuilder.newTrigger("task2").build());
106+
107+
// WHEN
108+
var response = template.exchange(
109+
baseUrl + "?id=" + t3.getData().getCorrelationId().substring(0, 28) + "*",
110+
HttpMethod.GET,
111+
null,
112+
String.class);
113+
// THEN
114+
assertThat(response.getBody()).contains(t3.getData().getCorrelationId());
115+
assertThat(response.getBody()).doesNotContain(t2.getData().getCorrelationId());
116+
assertThat(response.getBody()).doesNotContain(t1.getData().getCorrelationId());
117+
}
99118

100119
@Test
101120
void testSearchByStatus() {

example/src/main/resources/application.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ spring:
55
open-in-view: false
66

77
persistent-tasks:
8-
scheduler-enabled: true
98
max-threads: 1
109

1110
springdoc:

ui/src/history/history.page.tsx

Lines changed: 6 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,12 @@
1-
import { PagedModel, Trigger } from "@src/server-api";
2-
import { useServerObject } from "@src/shared/http-request";
3-
import useAutoRefresh from "@src/shared/use-auto-refresh";
4-
import HttpErrorView from "@src/shared/view/http-error.view";
5-
import PageView from "@src/shared/view/page.view";
6-
import ReloadButton from "@src/shared/view/reload-button.view";
7-
import TriggerStatusSelect from "@src/shared/view/triger-status-select.view";
8-
import TriggerItemView from "@src/shared/view/trigger-list-item.view";
9-
import TaskSelect from "@src/task/view/task-select.view";
10-
import { useQuery } from "crossroad";
11-
import { Accordion, Col, Form, Row, Stack } from "react-bootstrap";
1+
import TriggersSearchView from "@src/shared/view/trigger-search.view";
122

133
const HistoryPage = () => {
14-
const [query, setQuery] = useQuery();
15-
16-
const triggers = useServerObject<PagedModel<Trigger>>(
17-
"/spring-tasks-api/history"
18-
);
19-
20-
const doReload = () => {
21-
triggers.doGet("?size=4&" + new URLSearchParams(query).toString());
22-
};
23-
24-
useAutoRefresh(10000, doReload, [query]);
25-
264
return (
27-
<>
28-
<Stack gap={1}>
29-
<HttpErrorView error={triggers.error} />
30-
<Row>
31-
<Col>
32-
<Form.Control
33-
defaultValue={query.id || ""}
34-
type="text"
35-
placeholder="ID search, '*' any string, '_' any character ..."
36-
onKeyUp={(e) =>
37-
e.key == "Enter"
38-
? setQuery((prev) => ({
39-
...prev,
40-
page: 0 + "",
41-
id: (e.target as HTMLInputElement)
42-
.value,
43-
}))
44-
: null
45-
}
46-
/>
47-
</Col>
48-
<Col>
49-
<TriggerStatusSelect
50-
value={query.status}
51-
onTaskChange={(status) =>
52-
setQuery((prev) => ({
53-
...prev,
54-
status,
55-
}))
56-
}
57-
/>
58-
</Col>
59-
</Row>
60-
<Row className="align-items-center mb-2">
61-
<Col>
62-
<TaskSelect
63-
value={query.taskName}
64-
onTaskChange={(taskName) =>
65-
setQuery((prev) => ({
66-
...prev,
67-
taskName: taskName,
68-
}))
69-
}
70-
/>
71-
</Col>
72-
<Col>
73-
<PageView
74-
onPage={(page) =>
75-
setQuery((prev) => ({
76-
...prev,
77-
page: page + "",
78-
}))
79-
}
80-
data={triggers.data}
81-
/>
82-
</Col>
83-
<Col>
84-
<ReloadButton
85-
className="float-end"
86-
isLoading={triggers.isLoading}
87-
onClick={doReload}
88-
/>
89-
</Col>
90-
</Row>
91-
<Accordion>
92-
{triggers.data?.content.map((t) => (
93-
<TriggerItemView key={"history-" + t.id} trigger={t} />
94-
))}
95-
</Accordion>
96-
</Stack>
97-
</>
5+
<TriggersSearchView
6+
allowUpdateAnCancel={false}
7+
showReRunButton={true}
8+
url="/spring-tasks-api/history"
9+
/>
9810
);
9911
};
10012

ui/src/shared/view/trigger-list-item.view.tsx

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,43 @@ import { formatMs, formatShortDateTime } from "../date.util";
88
import { useServerObject } from "../http-request";
99
import HttpErrorView from "./http-error.view";
1010
import StackTraceView from "./stacktrace-view";
11+
import { useUrl } from "crossroad";
12+
import { useEffect } from "react";
1113
interface TriggerProps {
1214
trigger: Trigger;
1315
afterTriggerChanged?: () => void;
16+
showReRunButton: boolean;
1417
}
1518

16-
const TriggerItemView = ({ trigger, afterTriggerChanged }: TriggerProps) => {
19+
const TriggerItemView = ({
20+
trigger,
21+
afterTriggerChanged,
22+
showReRunButton,
23+
}: TriggerProps) => {
1724
// className="d-flex justify-content-between align-items-center"
25+
const [url, setUrl] = useUrl();
1826

1927
const triggerHistory = useServerObject<Trigger[]>(
2028
"/spring-tasks-api/history/instance/" + trigger.instanceId
2129
);
2230

31+
const reRunTrigger = useServerObject<Trigger>(
32+
`/spring-tasks-api/history/${trigger.id}/re-run`
33+
);
34+
2335
const editTrigger = useServerObject<Trigger[]>(
2436
"/spring-tasks-api/triggers/" +
2537
trigger.key.taskName +
2638
"/" +
2739
trigger.key.id
2840
);
2941

42+
useEffect(() => {
43+
if (reRunTrigger.data && reRunTrigger.data.id) {
44+
setUrl("/task-ui/triggers");
45+
}
46+
}, [setUrl, reRunTrigger.data]);
47+
3048
return (
3149
<Accordion.Item
3250
eventKey={trigger.id + ""}
@@ -42,33 +60,51 @@ const TriggerItemView = ({ trigger, afterTriggerChanged }: TriggerProps) => {
4260
</Accordion.Header>
4361
<Accordion.Body>
4462
<HttpErrorView
45-
error={triggerHistory.error || editTrigger.error}
63+
error={
64+
triggerHistory.error ||
65+
editTrigger.error ||
66+
reRunTrigger.error
67+
}
4668
/>
47-
{trigger.status === "WAITING" && afterTriggerChanged ? (
48-
<div className="d-flex gap-2 mb-2">
69+
<div className="d-flex gap-2 mb-2">
70+
{trigger.status === "WAITING" && afterTriggerChanged ? (
71+
<>
72+
<Button
73+
onClick={() => {
74+
editTrigger
75+
.doCall("/run-at", "POST", new Date())
76+
.then(afterTriggerChanged)
77+
.catch((e) => console.info(e));
78+
}}
79+
>
80+
Run now
81+
</Button>
82+
<Button
83+
variant="danger"
84+
onClick={() => {
85+
editTrigger
86+
.doCall("", "DELETE")
87+
.then(afterTriggerChanged)
88+
.catch((e) => console.info(e));
89+
}}
90+
>
91+
Cancel Trigger
92+
</Button>
93+
</>
94+
) : undefined}
95+
{showReRunButton ? (
4996
<Button
97+
variant="warning"
5098
onClick={() => {
51-
editTrigger
52-
.doCall("/run-at", "POST", new Date())
53-
.then(afterTriggerChanged)
99+
reRunTrigger
100+
.doCall("", "POST")
54101
.catch((e) => console.info(e));
55102
}}
56103
>
57-
Run now
104+
Run Trigger again
58105
</Button>
59-
<Button
60-
variant="danger"
61-
onClick={() => {
62-
editTrigger
63-
.doCall("", "DELETE")
64-
.then(afterTriggerChanged)
65-
.catch((e) => console.info(e));
66-
}}
67-
>
68-
Cancel Trigger
69-
</Button>
70-
</div>
71-
) : undefined}
106+
) : undefined}
107+
</div>
72108
<TriggerDetailsView
73109
key={trigger.id + "TriggerDetailsView"}
74110
trigger={trigger}
@@ -124,36 +160,44 @@ const TriggerDetailsView = ({
124160
return (
125161
<>
126162
<Row>
127-
<Col xs="6">
163+
<Col md="6" xl="4">
128164
<LabeledText label="Key Id" value={trigger.key.id} />
129165
</Col>
130-
<Col xs="3">
166+
<Col md="6" xl="4">
131167
<LabeledText label="Task" value={trigger.key.taskName} />
132168
</Col>
133-
<Col xs="3">
169+
<Col md="6" xl="4">
134170
<LabeledText label="Priority" value={trigger.priority} />
135171
</Col>
136172
</Row>
137173
<Row>
138-
<Col md="6" xl="3">
174+
<Col md="6" xl="4">
175+
<LabeledText
176+
label="Correlation Id"
177+
value={trigger.correlationId}
178+
/>
179+
</Col>
180+
<Col md="6" xl="4">
139181
<LabeledText
140182
label="Run at"
141183
value={formatShortDateTime(trigger.runAt)}
142184
/>
143185
</Col>
144-
<Col md="6" xl="3">
186+
</Row>
187+
<Row>
188+
<Col md="6" xl="4">
145189
<LabeledText
146190
label="Started at"
147191
value={formatShortDateTime(trigger.start)}
148192
/>
149193
</Col>
150-
<Col md="6" xl="3">
194+
<Col md="6" xl="4">
151195
<LabeledText
152196
label="Finished at"
153197
value={formatShortDateTime(trigger.end)}
154198
/>
155199
</Col>
156-
<Col md="6" xl="3">
200+
<Col md="6" xl="4">
157201
<LabeledText
158202
label="Duration MS"
159203
value={formatMs(trigger.runningDurationInMs)}

0 commit comments

Comments
 (0)