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