You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/04/19 01:25:26 UTC
[james-project] branch master updated: JAMES-3292 More filters for listing webadmin tasks (#1520)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new 7a2ff7e12d JAMES-3292 More filters for listing webadmin tasks (#1520)
7a2ff7e12d is described below
commit 7a2ff7e12d0f14d3ba7bc1505171832e958c1fbe
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Wed Apr 19 08:25:20 2023 +0700
JAMES-3292 More filters for listing webadmin tasks (#1520)
---
.../docs/modules/ROOT/pages/operate/webadmin.adoc | 10 +
.../apache/james/webadmin/routes/TasksRoutes.java | 133 ++++++++++++
.../james/webadmin/routes/TasksRoutesTest.java | 233 +++++++++++++++++++++
src/site/markdown/server/manage-webadmin.md | 10 +
4 files changed, 386 insertions(+)
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
index 0fcca321ba..449216344b 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/operate/webadmin.adoc
@@ -276,9 +276,19 @@ Additional optional task parameters are supported:
- `status` one of `waiting`, `inProgress`, `canceledRequested`, `completed`, `canceled`, `failed`. Only
tasks with the given status are returned.
- `type`: only tasks with the given type are returned.
+- `submittedBefore`: Date. Returns only tasks submitted before this date.
+- `submittedAfter`: Date. Returns only tasks submitted after this date.
+- `startedBefore`: Date. Returns only tasks started before this date.
+- `startedAfter`: Date. Returns only tasks started after this date.
+- `completedBefore`: Date. Returns only tasks completed before this date.
+- `completedAfter`: Date. Returns only tasks completed after this date.
+- `failedBefore`: Date. Returns only tasks failed before this date.
+- `failedAfter`: Date. Returns only tasks faield after this date.
- `offset`: Integer, number of tasks to skip in the response. Useful for paging.
- `limit`: Integer, maximum number of tasks to return in one call
+Example of date format: `2023-04-15T07:23:27.541254+07:00` and `2023-04-15T07%3A23%3A27.541254%2B07%3A00` once URL encoded.
+
=== Endpoints returning a task
Many endpoints do generate a task.
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
index fd7214b1e0..b92f64b76b 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
@@ -20,6 +20,7 @@
package org.apache.james.webadmin.routes;
import java.time.Duration;
+import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Optional;
@@ -74,6 +75,122 @@ public class TasksRoutes implements Routes {
}
}
+ static class SubmittedBeforeTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public SubmittedBeforeTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getSubmittedDate().isBefore(time));
+ }
+ }
+
+ static class StartedBeforeTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public StartedBeforeTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getStartedDate()
+ .map(started -> started.isBefore(time))
+ .orElse(false));
+ }
+ }
+
+ static class FailedBeforeTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public FailedBeforeTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getFailedDate()
+ .map(started -> started.isBefore(time))
+ .orElse(false));
+ }
+ }
+
+ static class CompletedBeforeTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public CompletedBeforeTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getCompletedDate()
+ .map(started -> started.isBefore(time))
+ .orElse(false));
+ }
+ }
+
+ static class SubmittedAfterTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public SubmittedAfterTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getSubmittedDate().isAfter(time));
+ }
+ }
+
+ static class StartedAfterTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public StartedAfterTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getStartedDate()
+ .map(started -> started.isAfter(time))
+ .orElse(false));
+ }
+ }
+
+ static class FailedAfterTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public FailedAfterTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getFailedDate()
+ .map(started -> started.isAfter(time))
+ .orElse(false));
+ }
+ }
+
+ static class CompletedAfterTaskListTransformation implements TaskListTransformation {
+ private final ZonedDateTime time;
+
+ public CompletedAfterTaskListTransformation(ZonedDateTime time) {
+ this.time = time;
+ }
+
+ @Override
+ public Stream<TaskExecutionDetails> apply(Stream<TaskExecutionDetails> stream) {
+ return stream.filter(taskExecutionDetails -> taskExecutionDetails.getCompletedDate()
+ .map(started -> started.isAfter(time))
+ .orElse(false));
+ }
+ }
+
static class OffsetTaskListTransformation implements TaskListTransformation {
private final int offset;
@@ -157,11 +274,27 @@ public class TasksRoutes implements Routes {
Stream<TaskListTransformation> taskListTransformations(Request req) {
return Stream.of(Optional.ofNullable(req.queryParams("type")).map(TaskType::of).map(TypeTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("failedBefore")).map(this::parseDate).map(FailedBeforeTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("failedAfter")).map(this::parseDate).map(FailedAfterTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("completedBefore")).map(this::parseDate).map(CompletedBeforeTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("completedAfter")).map(this::parseDate).map(CompletedAfterTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("startedBefore")).map(this::parseDate).map(StartedBeforeTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("startedAfter")).map(this::parseDate).map(StartedAfterTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("submittedBefore")).map(this::parseDate).map(SubmittedBeforeTaskListTransformation::new),
+ Optional.ofNullable(req.queryParams("submittedAfter")).map(this::parseDate).map(SubmittedAfterTaskListTransformation::new),
Optional.ofNullable(req.queryParams("offset")).map(Integer::valueOf).map(OffsetTaskListTransformation::new),
Optional.ofNullable(req.queryParams("limit")).map(Integer::valueOf).map(LimitTaskListTransformation::new))
.flatMap(Optional::stream);
}
+ private ZonedDateTime parseDate(String s) {
+ try {
+ return ZonedDateTime.parse(s);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
public Object getStatus(Request req, Response response) {
TaskId taskId = getTaskId(req);
return respondStatus(taskId,
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
index 7ebd020602..e2935a6243 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
@@ -22,6 +22,7 @@ package org.apache.james.webadmin.routes;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static io.restassured.RestAssured.with;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
@@ -30,6 +31,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.oneOf;
+import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@@ -49,6 +51,8 @@ import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import io.restassured.RestAssured;
@@ -479,4 +483,233 @@ class TasksRoutesTest {
.body("type", is("InvalidArgument"))
.body("message", is("Invalid status query parameter"));
}
+
+ @ParameterizedTest
+ @ValueSource(strings = {"failedBefore", "failedAfter", "startedBefore", "startedAfter", "completedBefore",
+ "completedAfter", "submittedBefore", "submittedAfter"})
+ void listShouldRejectInvalidDateParameter(String paramName) {
+ given()
+ .param(paramName, "invalid")
+ .get()
+ .then()
+ .statusCode(HttpStatus.BAD_REQUEST_400)
+ .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+ .body("type", is("InvalidArgument"))
+ .body("message", is("Invalid status query parameter"));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"failedBefore", "failedAfter", "startedBefore", "startedAfter", "completedBefore",
+ "completedAfter", "submittedBefore", "submittedAfter"})
+ void listShouldAcceptValidDateParameter(String paramName) {
+ given()
+ .param(paramName, ZonedDateTime.now().toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200);
+ }
+
+ @Test
+ void getTasksShouldFilterWhenSubmitBefore() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(1);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(1);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(1);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("submittedBefore", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId2.asString(), taskId1.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenSubmitAfter() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(1);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(1);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(1);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(1);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("submittedAfter", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId3.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenCompletedBefore() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("completedBefore", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId2.asString(), taskId1.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenCompletedAfter() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("completedAfter", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId3.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenFailedBefore() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("failedBefore", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId2.asString(), taskId1.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenFailedAfter() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("failedAfter", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId3.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenStartedBefore() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("startedBefore", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId2.asString(), taskId1.asString()));
+ }
+
+ @Test
+ void getTasksShouldFilterWhenStartedAfter() throws Exception {
+ ZonedDateTime t1 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId1 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t2 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId2 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.COMPLETED));
+ Thread.sleep(100);
+ ZonedDateTime t3 = ZonedDateTime.now();
+ Thread.sleep(100);
+ TaskId taskId3 = taskManager.submit(new MemoryReferenceTask(() -> Task.Result.PARTIAL));
+ Thread.sleep(100);
+ ZonedDateTime t4 = ZonedDateTime.now();
+
+ given()
+ .param("startedAfter", t3.toString())
+ .get()
+ .then()
+ .statusCode(HttpStatus.OK_200)
+ .body("taskId", contains(taskId3.asString()));
+ }
}
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 14eadcc942..f918215dd1 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -4354,9 +4354,19 @@ Additionnal optional task parameters are supported:
- `status` one of `waiting`, `inProgress`, `canceledRequested`, `completed`, `canceled`, `failed`. Only
tasks with the given status are returned.
- `type`: only tasks with the given type are returned.
+- `submittedBefore`: Date. Returns only tasks submitted before this date.
+- `submittedAfter`: Date. Returns only tasks submitted after this date.
+- `startedBefore`: Date. Returns only tasks started before this date.
+- `startedAfter`: Date. Returns only tasks started after this date.
+- `completedBefore`: Date. Returns only tasks completed before this date.
+- `completedAfter`: Date. Returns only tasks completed after this date.
+- `failedBefore`: Date. Returns only tasks failed before this date.
+- `failedAfter`: Date. Returns only tasks faield after this date.
- `offset`: Integer, number of tasks to skip in the response. Useful for paging.
- `limit`: Integer, maximum number of tasks to return in one call
+Example of date format: `2023-04-15T07:23:27.541254+07:00` and `2023-04-15T07%3A23%3A27.541254%2B07%3A00` once URL encoded.
+
### Endpoints returning a task
Many endpoints do generate a task.
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org