Skip to content

Commit d0c810e

Browse files
committed
filter trigger by status
1 parent 724ac9d commit d0c810e

File tree

7 files changed

+139
-27
lines changed

7 files changed

+139
-27
lines changed

core/src/main/java/org/sterl/spring/persistent_tasks/trigger/model/TriggerEntity.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public TriggerEntity complete(Exception e) {
104104
public TriggerEntity runAt(OffsetDateTime runAt) {
105105
data.setStatus(TriggerStatus.WAITING);
106106
data.setRunAt(runAt);
107+
setRunningOn(null);
107108
return this;
108109
}
109110

core/src/test/java/org/sterl/spring/persistent_tasks/scheduler/SchedulerServiceTransactionTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ void test_fail_in_transaction() throws Exception {
161161
// AND
162162
var data = persistentTaskService.getLastDetailData(trigger.key());
163163
assertThat(data.get().getStatus()).isEqualTo(TriggerStatus.FAILED);
164+
assertThat(triggerService.get(trigger.getKey()).get().getRunningOn()).isNull();
165+
assertThat(triggerService.get(trigger.getKey()).get().status()).isEqualTo(TriggerStatus.WAITING);
164166
// AND
165167
var history = historyService.findAllDetailsForKey(trigger.key()).getContent();
166168
assertThat(history.get(0).getData().getStatus()).isEqualTo(TriggerStatus.FAILED);

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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.junit.jupiter.api.BeforeEach;
99
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
1011
import org.springframework.boot.test.web.server.LocalServerPort;
1112
import org.springframework.http.HttpEntity;
1213
import org.springframework.http.HttpMethod;
@@ -16,12 +17,18 @@
1617
import org.sterl.spring.persistent_tasks.AbstractSpringTest.TaskConfig.Task3;
1718
import org.sterl.spring.persistent_tasks.api.TaskId.TaskTriggerBuilder;
1819
import org.sterl.spring.persistent_tasks.api.Trigger;
20+
import org.sterl.spring.persistent_tasks.api.TriggerKey;
1921
import org.sterl.spring.persistent_tasks.api.TriggerStatus;
22+
import org.sterl.spring.persistent_tasks.shared.model.TriggerData;
23+
import org.sterl.spring.persistent_tasks.trigger.model.TriggerEntity;
24+
import org.sterl.spring.persistent_tasks.trigger.repository.TriggerRepository;
2025

2126
class TriggerResourceTest extends AbstractSpringTest {
2227

2328
@LocalServerPort
2429
private int port;
30+
@Autowired
31+
private TriggerRepository triggerRepository;
2532
private String baseUrl;
2633
private final RestTemplate template = new RestTemplate();
2734

@@ -33,7 +40,8 @@ void setupRest() {
3340
@Test
3441
void testList() {
3542
// GIVEN
36-
var triggerKey = triggerService.queue(TaskTriggerBuilder.newTrigger("task1").build()).getKey();
43+
var k1 = createStatus(new TriggerKey("1-foo", "foo"), TriggerStatus.WAITING).getKey();
44+
var k2 = createStatus(new TriggerKey("2-foo", "bar"), TriggerStatus.WAITING).getKey();
3745

3846
// WHEN
3947
var response = template.exchange(
@@ -45,8 +53,11 @@ void testList() {
4553
// THEN
4654
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
4755
assertThat(response.getBody()).isNotNull();
48-
assertThat(response.getBody()).contains(triggerKey.getId());
49-
assertThat(response.getBody()).contains(triggerKey.getTaskName());
56+
assertThat(response.getBody()).contains(k1.getId());
57+
assertThat(response.getBody()).contains(k1.getTaskName());
58+
// AND
59+
assertThat(response.getBody()).contains(k2.getId());
60+
assertThat(response.getBody()).contains(k2.getTaskName());
5061
}
5162

5263
@Test
@@ -71,6 +82,26 @@ void testSearchById() {
7182
assertThat(response.getBody()).doesNotContain(key2.getId());
7283
}
7384

85+
@Test
86+
void testSearchByStatus() {
87+
// GIVEN
88+
var k1 = createStatus(new TriggerKey("1-foo", "foo"), TriggerStatus.WAITING).getKey();
89+
var k2 = createStatus(new TriggerKey("2-foo", "bar"), TriggerStatus.RUNNING).getKey();
90+
91+
// WHEN
92+
var response = template.exchange(
93+
baseUrl + "?status=" + TriggerStatus.RUNNING,
94+
HttpMethod.GET,
95+
null,
96+
String.class);
97+
98+
// THEN
99+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
100+
assertThat(response.getBody()).isNotNull();
101+
assertThat(response.getBody()).contains(k2.getId());
102+
assertThat(response.getBody()).doesNotContain(k1.getId());
103+
}
104+
74105
@Test
75106
void testCancel() {
76107
// GIVEN
@@ -115,5 +146,25 @@ void testUpdateRunAt() {
115146
assertThat(triggerService.countTriggers(TriggerStatus.WAITING)).isOne();
116147
assertThat(response.getBody().getKey()).isEqualTo(triggerKey);
117148
}
149+
150+
151+
private TriggerEntity createStatus(TriggerKey key, TriggerStatus status) {
152+
final var now = OffsetDateTime.now();
153+
final var isCancel = status == TriggerStatus.CANCELED;
154+
155+
TriggerEntity result = new TriggerEntity();
156+
result.setData(TriggerData
157+
.builder()
158+
.start(isCancel ? null : now.minusMinutes(1))
159+
.end(isCancel ? null : now)
160+
.createdTime(now)
161+
.key(key)
162+
.status(status)
163+
.runningDurationInMs(isCancel ? null : 600L)
164+
.build()
165+
);
166+
167+
return triggerRepository.save(result);
168+
}
118169

119170
}

ui/src/shared/http-request.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import axios from "axios";
2-
import { useMemo, useState } from "react";
2+
import { useCallback, useMemo, useState } from "react";
33

44
export interface ServerObject<T> {
55
isLoading: boolean,
@@ -36,9 +36,9 @@ export const useServerObject = <T>(url: string, startValue?: T): ServerObject<T>
3636
})
3737
.finally(() => setIsLoading(false));
3838
return () => controller.abort();
39-
}
39+
};
4040

41-
const doCall = (urlPart?: string, method: HttpMethod = "GET", dataToSend?: unknown) => {
41+
const doCall = useCallback((urlPart?: string, method: HttpMethod = "GET", dataToSend?: unknown) => {
4242
return axios.request({
4343
baseURL: url + (urlPart ?? ""),
4444
data: dataToSend,
@@ -47,9 +47,10 @@ export const useServerObject = <T>(url: string, startValue?: T): ServerObject<T>
4747
.then((response) => setData(response.data as T))
4848
.catch(e => steError(e))
4949
.finally(() => setIsLoading(false));
50-
}
50+
}, [url]);
51+
5152
return useMemo(
5253
() => ({ isLoading, data, error, doGet, doCall }),
53-
[isLoading, data, error, doCall]
54+
[isLoading, data, error, doCall, url]
5455
);
5556
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { TriggerStatus } from "@src/server-api";
2+
import { Form } from "react-bootstrap";
3+
4+
interface TaskSelectProps {
5+
value: string;
6+
onTaskChange: (task: string) => void;
7+
}
8+
9+
function TriggerStatusSelect({ value = "", onTaskChange }: TaskSelectProps) {
10+
const handleTaskChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
11+
const newTask = event.target.value ?? "";
12+
if (newTask !== value) {
13+
if (onTaskChange) onTaskChange(newTask);
14+
}
15+
};
16+
17+
return (
18+
<Form.Select value={value} onChange={handleTaskChange}>
19+
<option value={"" as TriggerStatus}>Any state</option>
20+
<option value={"WAITING" as TriggerStatus}>Waiting</option>
21+
<option value={"RUNNING" as TriggerStatus}>Running</option>
22+
<option value={"SUCCESS" as TriggerStatus}>Success</option>
23+
<option value={"FAILED" as TriggerStatus}>Failed</option>
24+
<option value={"CANCELED" as TriggerStatus}>Canceled</option>
25+
</Form.Select>
26+
);
27+
}
28+
29+
export default TriggerStatusSelect;

ui/src/task/view/task-select.view.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
import { useEffect, useState } from "react";
2-
import { Col, Form, Row, Spinner } from "react-bootstrap";
31
import { useServerObject } from "@src/shared/http-request";
2+
import { useEffect } from "react";
3+
import { Col, Form, Row, Spinner } from "react-bootstrap";
44

55
interface TaskSelectProps {
6+
value?: string;
67
onTaskChange?: (task: string) => void; // Define type for the callback
78
}
89

9-
function TaskSelect({ onTaskChange }: TaskSelectProps) {
10-
const [selectedTask, setSelectedTask] = useState<string>("");
10+
function TaskSelect({ value = "", onTaskChange }: TaskSelectProps) {
1111
const tasksState = useServerObject<string[]>("/spring-tasks-api/tasks");
1212

1313
useEffect(tasksState.doGet, []);
1414

1515
const handleTaskChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
1616
const newTask = event.target.value ?? "";
17-
if (newTask !== selectedTask) {
18-
setSelectedTask(newTask);
17+
if (newTask !== value) {
1918
if (onTaskChange) onTaskChange(newTask);
2019
}
2120
};
@@ -41,7 +40,7 @@ function TaskSelect({ onTaskChange }: TaskSelectProps) {
4140
<Col sm="10">
4241
<Form.Select
4342
aria-label="Select task"
44-
value={selectedTask || ""}
43+
value={value}
4544
onChange={handleTaskChange}
4645
>
4746
<option value="">All</option>

ui/src/trigger/triggers.page.tsx

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,78 @@ import useAutoRefresh from "@src/shared/use-auto-refresh";
44
import HttpErrorView from "@src/shared/view/http-error.view";
55
import PageView from "@src/shared/view/page.view";
66
import ReloadButton from "@src/shared/view/reload-button.view";
7+
import TriggerStatusSelect from "@src/shared/view/triger-status-select.view";
78
import TaskSelect from "@src/task/view/task-select.view";
8-
import { useState } from "react";
9+
import { useQuery } from "crossroad";
910
import { Accordion, Col, Form, Row, Stack } from "react-bootstrap";
1011
import TriggerItemView from "../shared/view/trigger-list-item.view";
1112

1213
const TriggersPage = () => {
13-
const [page, setPage] = useState(0);
14-
const [taskName, setTaskName] = useState("");
15-
const [id, setId] = useState("");
14+
const [query, setQuery] = useQuery();
1615
const triggers = useServerObject<PagedModel<Trigger>>(
1716
"/spring-tasks-api/triggers"
1817
);
19-
2018
const doReload = () => {
21-
triggers.doGet(
22-
"?size=10&page=" + page + "&taskName=" + taskName + "&id=" + id
19+
return triggers.doGet(
20+
"?size=10&" + new URLSearchParams(query).toString()
2321
);
2422
};
25-
26-
useAutoRefresh(10000, doReload, [page, taskName, id]);
23+
console.info(query);
24+
useAutoRefresh(10000, doReload, [query]);
2725

2826
return (
2927
<Stack gap={1}>
3028
<HttpErrorView error={triggers.error} />
3129
<Row>
3230
<Col>
3331
<Form.Control
32+
value={query.id || ""}
3433
type="text"
3534
placeholder="Search..."
3635
onKeyUp={(e) =>
3736
e.key == "Enter"
38-
? setId((e.target as HTMLInputElement).value)
37+
? setQuery((prev) => ({
38+
...prev,
39+
id: (e.target as HTMLInputElement).value,
40+
}))
3941
: null
4042
}
4143
/>
4244
</Col>
45+
<Col>
46+
<TriggerStatusSelect
47+
value={query.status}
48+
onTaskChange={(status) =>
49+
setQuery((prev) => ({
50+
...prev,
51+
status,
52+
}))
53+
}
54+
/>
55+
</Col>
4356
</Row>
4457
<Row className="align-items-center mb-2">
4558
<Col>
46-
<TaskSelect onTaskChange={setTaskName} />
59+
<TaskSelect
60+
value={query.taskName}
61+
onTaskChange={(taskName) =>
62+
setQuery((prev) => ({
63+
...prev,
64+
taskName: taskName,
65+
}))
66+
}
67+
/>
4768
</Col>
4869
<Col className="align-items-center">
49-
<PageView onPage={(p) => setPage(p)} data={triggers.data} />
70+
<PageView
71+
onPage={(page) =>
72+
setQuery((prev) => ({
73+
...prev,
74+
page: page + "",
75+
}))
76+
}
77+
data={triggers.data}
78+
/>
5079
</Col>
5180
<Col>
5281
<ReloadButton

0 commit comments

Comments
 (0)