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 <btell...@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-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org