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/05/25 05:45:23 UTC

[james-project] 03/03: JAMES-3909 Add listing failedUsers to task additional information

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

commit 375aebbd21cb4bf16a9a9cc74df221a86a6abc83
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Mon May 22 16:52:30 2023 +0700

    JAMES-3909 Add listing failedUsers to task additional information
---
 .../docs/modules/ROOT/pages/operate/webadmin.adoc  |  3 +++
 .../service/DeleteUsersDataOfDomainTask.java       | 27 ++++++++++++++++++++--
 ...rsDataOfDomainTaskAdditionalInformationDTO.java | 26 +++++++++++++++++++--
 .../james/webadmin/routes/DomainsRoutesTest.java   |  7 ++++--
 ...leteUsersDataOfDomainTaskSerializationTest.java | 13 +++++++++--
 .../service/DeleteUsersDataOfDomainTaskTest.java   |  5 ++++
 src/site/markdown/server/manage-webadmin.md        |  3 +++
 7 files changed, 76 insertions(+), 8 deletions(-)

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 0a69f5618f..fa1a311569 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
@@ -478,10 +478,13 @@ The scheduled task will have the following type `DeleteUsersDataOfDomainTask` an
         "domain": "domain.tld",
         "successfulUsersCount": 2,
         "failedUsersCount": 1,
+        "failedUsers": ["faileduser@domain.tld"],
         "timestamp": "2023-05-22T08:52:47.076261Z"
 }
 ....
 
+Notes: `failedUsers` only lists maximum 100 failed users.
+
 == Administrating users
 
 === Create a user
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTask.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTask.java
index 84487aade6..146fa7a787 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTask.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTask.java
@@ -23,6 +23,8 @@ import java.time.Clock;
 import java.time.Instant;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 
@@ -42,18 +44,21 @@ import reactor.core.publisher.Mono;
 public class DeleteUsersDataOfDomainTask implements Task {
     static final TaskType TYPE = TaskType.of("DeleteUsersDataOfDomainTask");
     private static final int LOW_CONCURRENCY = 2;
+    private static final int MAX_STORED_FAILED_USERS = 100;
 
     public static class AdditionalInformation implements TaskExecutionDetails.AdditionalInformation {
         private final Instant timestamp;
         private final Domain domain;
         private final long successfulUsersCount;
         private final long failedUsersCount;
+        private final Set<Username> failedUsers;
 
-        public AdditionalInformation(Instant timestamp, Domain domain, long successfulUsersCount, long failedUsersCount) {
+        public AdditionalInformation(Instant timestamp, Domain domain, long successfulUsersCount, long failedUsersCount, Set<Username> failedUsers) {
             this.timestamp = timestamp;
             this.domain = domain;
             this.successfulUsersCount = successfulUsersCount;
             this.failedUsersCount = failedUsersCount;
+            this.failedUsers = failedUsers;
         }
 
         public Domain getDomain() {
@@ -68,6 +73,10 @@ public class DeleteUsersDataOfDomainTask implements Task {
             return failedUsersCount;
         }
 
+        public Set<Username> getFailedUsers() {
+            return failedUsers;
+        }
+
         @Override
         public Instant timestamp() {
             return timestamp;
@@ -95,10 +104,12 @@ public class DeleteUsersDataOfDomainTask implements Task {
     static class Context {
         private final AtomicLong successfulUsersCount;
         private final AtomicLong failedUsersCount;
+        private final Set<Username> failedUsers;
 
         public Context() {
             this.successfulUsersCount = new AtomicLong();
             this.failedUsersCount = new AtomicLong();
+            this.failedUsers = ConcurrentHashMap.newKeySet();
         }
 
         private void increaseSuccessfulUsers() {
@@ -109,6 +120,10 @@ public class DeleteUsersDataOfDomainTask implements Task {
             failedUsersCount.incrementAndGet();
         }
 
+        private void addFailedUser(Username username) {
+            failedUsers.add(username);
+        }
+
         public long getSuccessfulUsersCount() {
             return successfulUsersCount.get();
         }
@@ -116,6 +131,10 @@ public class DeleteUsersDataOfDomainTask implements Task {
         public long getFailedUsersCount() {
             return failedUsersCount.get();
         }
+
+        public Set<Username> getFailedUsers() {
+            return failedUsers;
+        }
     }
 
     private final Domain domain;
@@ -148,6 +167,9 @@ public class DeleteUsersDataOfDomainTask implements Task {
             .onErrorResume(error -> {
                 LOGGER.error("Error when deleting data of user {}", username.asString(), error);
                 context.increaseFailedUsers();
+                if (context.failedUsers.size() < MAX_STORED_FAILED_USERS) {
+                    context.addFailedUser(username);
+                }
                 return Mono.just(Result.PARTIAL);
             });
     }
@@ -159,7 +181,8 @@ public class DeleteUsersDataOfDomainTask implements Task {
 
     @Override
     public Optional<TaskExecutionDetails.AdditionalInformation> details() {
-        return Optional.of(new AdditionalInformation(Clock.systemUTC().instant(), domain, context.getSuccessfulUsersCount(), context.getFailedUsersCount()));
+        return Optional.of(new AdditionalInformation(Clock.systemUTC().instant(), domain, context.getSuccessfulUsersCount(),
+            context.getFailedUsersCount(), context.getFailedUsers()));
     }
 
     public Domain getDomain() {
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskAdditionalInformationDTO.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskAdditionalInformationDTO.java
index 7bcd3c033d..f601fe8785 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskAdditionalInformationDTO.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskAdditionalInformationDTO.java
@@ -20,8 +20,11 @@
 package org.apache.james.webadmin.service;
 
 import java.time.Instant;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
 import org.apache.james.json.DTOModule;
 import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
 import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
@@ -33,28 +36,43 @@ public class DeleteUsersDataOfDomainTaskAdditionalInformationDTO implements Addi
         return DTOModule.forDomainObject(DeleteUsersDataOfDomainTask.AdditionalInformation.class)
             .convertToDTO(DeleteUsersDataOfDomainTaskAdditionalInformationDTO.class)
             .toDomainObjectConverter(dto -> new DeleteUsersDataOfDomainTask.AdditionalInformation(
-                dto.timestamp, Domain.of(dto.domain), dto.successfulUsersCount, dto.failedUsersCount))
+                dto.timestamp, Domain.of(dto.domain), dto.successfulUsersCount, dto.failedUsersCount, toSetUsername(dto.failedUsers)))
             .toDTOConverter((details, type) -> new DeleteUsersDataOfDomainTaskAdditionalInformationDTO(
-                type, details.getDomain().asString(), details.getSuccessfulUsersCount(), details.getFailedUsersCount(), details.timestamp()))
+                type, details.getDomain().asString(), details.getSuccessfulUsersCount(), details.getFailedUsersCount(), toSetString(details.getFailedUsers()), details.timestamp()))
             .typeName(DeleteUsersDataOfDomainTask.TYPE.asString())
             .withFactory(AdditionalInformationDTOModule::new);
     }
 
+    private static Set<Username> toSetUsername(Set<String> usernames) {
+        return usernames.stream()
+            .map(Username::of)
+            .collect(Collectors.toSet());
+    }
+
+    private static Set<String> toSetString(Set<Username> usernames) {
+        return usernames.stream()
+            .map(Username::asString)
+            .collect(Collectors.toSet());
+    }
+
     private final String type;
     private final String domain;
     private final long successfulUsersCount;
     private final long failedUsersCount;
+    private final Set<String> failedUsers;
     private final Instant timestamp;
 
     public DeleteUsersDataOfDomainTaskAdditionalInformationDTO(@JsonProperty("type") String type,
                                                                @JsonProperty("domain") String domain,
                                                                @JsonProperty("successfulUsersCount") long successfulUsersCount,
                                                                @JsonProperty("failedUsersCount") long failedUsersCount,
+                                                               @JsonProperty("failedUsers") Set<String> failedUsers,
                                                                @JsonProperty("timestamp") Instant timestamp) {
         this.type = type;
         this.domain = domain;
         this.successfulUsersCount = successfulUsersCount;
         this.failedUsersCount = failedUsersCount;
+        this.failedUsers = failedUsers;
         this.timestamp = timestamp;
     }
 
@@ -70,6 +88,10 @@ public class DeleteUsersDataOfDomainTaskAdditionalInformationDTO implements Addi
         return failedUsersCount;
     }
 
+    public Set<String> getFailedUsers() {
+        return failedUsers;
+    }
+
     public Instant getTimestamp() {
         return timestamp;
     }
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
index baefeaf289..82b88af391 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DomainsRoutesTest.java
@@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.hasSize;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doThrow;
@@ -184,7 +185,8 @@ class DomainsRoutesTest {
                 .body("additionalInformation.type", is("DeleteUsersDataOfDomainTask"))
                 .body("additionalInformation.domain", is("localhost"))
                 .body("additionalInformation.successfulUsersCount", is(2))
-                .body("additionalInformation.failedUsersCount", is(0));
+                .body("additionalInformation.failedUsersCount", is(0))
+                .body("additionalInformation.failedUsers", empty());
 
             // then should delete data of the 2 users
             assertThat(recordProcessedUsersStep.processedUsers)
@@ -244,7 +246,8 @@ class DomainsRoutesTest {
                 .body("additionalInformation.type", is("DeleteUsersDataOfDomainTask"))
                 .body("additionalInformation.domain", is("localhost"))
                 .body("additionalInformation.successfulUsersCount", is(2))
-                .body("additionalInformation.failedUsersCount", is(0));
+                .body("additionalInformation.failedUsersCount", is(0))
+                .body("additionalInformation.failedUsers", empty());
 
             // THEN users data of domain.tld should not be clear
             assertThat(recordProcessedUsersStep.processedUsers)
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskSerializationTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskSerializationTest.java
index 8de0149656..9d1cf3ffb5 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskSerializationTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskSerializationTest.java
@@ -22,6 +22,7 @@ package org.apache.james.webadmin.service;
 import static org.mockito.Mockito.mock;
 
 import java.time.Instant;
+import java.util.Set;
 
 import org.apache.james.JsonSerializationVerifier;
 import org.apache.james.core.Domain;
@@ -40,6 +41,7 @@ class DeleteUsersDataOfDomainTaskSerializationTest {
     private static final Domain DOMAIN = Domain.of("domain");
     private static final long SUCCESSFUL_USERS_COUNT = 99L;
     private static final long FAILED_USERS_COUNT = 1L;
+    private static final Set<Username> FAILED_USERS = Set.of(Username.of("faileduser@domain"));
     private static final DeleteUserDataTaskStep.StepName STEP_A = new DeleteUserDataTaskStep.StepName("A");
     private static final DeleteUserDataTaskStep.StepName STEP_B = new DeleteUserDataTaskStep.StepName("B");
     private static final DeleteUserDataTaskStep.StepName STEP_C = new DeleteUserDataTaskStep.StepName("C");
@@ -69,7 +71,14 @@ class DeleteUsersDataOfDomainTaskSerializationTest {
     }
 
     private static final String SERIALIZED_TASK = "{\"type\":\"DeleteUsersDataOfDomainTask\",\"domain\":\"domain\"}";
-    private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{\"type\":\"DeleteUsersDataOfDomainTask\",\"domain\":\"domain\",\"successfulUsersCount\":99,\"failedUsersCount\":1,\"timestamp\":\"2018-11-13T12:00:55Z\"}";
+    private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{\n" +
+        "  \"type\": \"DeleteUsersDataOfDomainTask\",\n" +
+        "  \"domain\": \"domain\",\n" +
+        "  \"successfulUsersCount\": 99,\n" +
+        "  \"failedUsersCount\": 1,\n" +
+        "  \"failedUsers\": [\"faileduser@domain\"],\n" +
+        "  \"timestamp\": \"2018-11-13T12:00:55Z\"\n" +
+        "}";
 
     private static final DeleteUserDataService SERVICE = new DeleteUserDataService(ImmutableSet.of(A, B, C, D));
 
@@ -86,7 +95,7 @@ class DeleteUsersDataOfDomainTaskSerializationTest {
     void additionalInformationShouldBeSerializable() throws Exception {
         JsonSerializationVerifier.dtoModule(DeleteUsersDataOfDomainTaskAdditionalInformationDTO.module())
             .bean(new DeleteUsersDataOfDomainTask.AdditionalInformation(
-                TIMESTAMP, DOMAIN, SUCCESSFUL_USERS_COUNT, FAILED_USERS_COUNT))
+                TIMESTAMP, DOMAIN, SUCCESSFUL_USERS_COUNT, FAILED_USERS_COUNT, FAILED_USERS))
             .json(SERIALIZED_ADDITIONAL_INFORMATION)
             .verify();
     }
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskTest.java
index f9bc9cecea..d5650d1bde 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/DeleteUsersDataOfDomainTaskTest.java
@@ -147,6 +147,8 @@ class DeleteUsersDataOfDomainTaskTest {
             softly.assertThat(result).isEqualTo(Task.Result.PARTIAL);
             softly.assertThat(task.getContext().getSuccessfulUsersCount()).isEqualTo(0L);
             softly.assertThat(task.getContext().getFailedUsersCount()).isEqualTo(2L);
+            softly.assertThat(task.getContext().getFailedUsers())
+                .containsExactlyInAnyOrder(Username.of("user1@domain1.tld"), Username.of("user2@domain1.tld"));
         });
     }
 
@@ -168,6 +170,8 @@ class DeleteUsersDataOfDomainTaskTest {
             softly.assertThat(result).isEqualTo(Task.Result.PARTIAL);
             softly.assertThat(task.getContext().getSuccessfulUsersCount()).isEqualTo(0L);
             softly.assertThat(task.getContext().getFailedUsersCount()).isEqualTo(2L);
+            softly.assertThat(task.getContext().getFailedUsers())
+                .containsExactlyInAnyOrder(Username.of("user1@domain1.tld"), Username.of("user2@domain1.tld"));
         });
     }
 
@@ -189,6 +193,7 @@ class DeleteUsersDataOfDomainTaskTest {
             softly.assertThat(result).isEqualTo(Task.Result.PARTIAL);
             softly.assertThat(task.getContext().getSuccessfulUsersCount()).isEqualTo(2L);
             softly.assertThat(task.getContext().getFailedUsersCount()).isEqualTo(1L);
+            softly.assertThat(task.getContext().getFailedUsers()).containsExactly(Username.of("user1@domain1.tld"));
         });
     }
 }
diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 5864d0a644..5ea35c2dd3 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -326,10 +326,13 @@ The scheduled task will have the following type `DeleteUsersDataOfDomainTask` an
         "domain": "domain.tld",
         "successfulUsersCount": 2,
         "failedUsersCount": 1,
+        "failedUsers": ["faileduser@domain.tld"],
         "timestamp": "2023-05-22T08:52:47.076261Z"
 }
 ```
 
+Notes: `failedUsers` only lists maximum 100 failed users.
+
 ## Administrating users
 
    - [Create a user](#Create_a_user)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org