]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6465 Add groupsCount on users search
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Thu, 21 May 2015 09:44:12 +0000 (11:44 +0200)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 26 May 2015 14:04:31 +0000 (16:04 +0200)
12 files changed:
server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
server/sonar-server/src/test/resources/org/sonar/server/user/ws/SearchActionTest/five_users.json
server/sonar-server/src/test/resources/org/sonar/server/user/ws/SearchActionTest/user_with_groups.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java
sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java
sonar-core/src/main/java/org/sonar/core/user/UserGroupCount.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml
sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java
sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared.xml

index 3b813e0ddd09b906c0c6d381024321b45f924020..27ceda98e936eafc9a3b0bd334ae4b4e394851a8 100644 (file)
@@ -20,6 +20,8 @@
 
 package org.sonar.server.user.db;
 
+import java.util.List;
+import javax.annotation.CheckForNull;
 import org.sonar.api.utils.System2;
 import org.sonar.core.persistence.DaoComponent;
 import org.sonar.core.persistence.DbSession;
@@ -28,10 +30,6 @@ import org.sonar.core.user.UserDto;
 import org.sonar.core.user.UserMapper;
 import org.sonar.server.exceptions.NotFoundException;
 
-import javax.annotation.CheckForNull;
-
-import java.util.List;
-
 public class UserDao extends org.sonar.core.user.UserDao implements DaoComponent {
 
   public UserDao(MyBatis mybatis, System2 system2) {
index 7f4cf2439ce16992bb4114863149cb6556dfc8e3..0e6e76c62083ab05eedd2b032b1e23e03b33382d 100644 (file)
 
 package org.sonar.server.user.ws;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.SearchResult;
 import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
 
-import javax.annotation.Nullable;
-
-import java.util.List;
-import java.util.Set;
-
 public class SearchAction implements UsersWsAction {
 
   private static final String FIELD_LOGIN = "login";
   private static final String FIELD_NAME = "name";
   private static final String FIELD_EMAIL = "email";
   private static final String FIELD_SCM_ACCOUNTS = "scmAccounts";
-  private static final Set<String> FIELDS = ImmutableSet.of(FIELD_LOGIN, FIELD_NAME, FIELD_EMAIL, FIELD_SCM_ACCOUNTS);
+  private static final String FIELD_GROUPS_COUNT = "groupsCount";
+  private static final Set<String> FIELDS = ImmutableSet.of(FIELD_LOGIN, FIELD_NAME, FIELD_EMAIL, FIELD_SCM_ACCOUNTS, FIELD_GROUPS_COUNT);
 
   private final UserIndex userIndex;
+  private final DbClient dbClient;
 
-  public SearchAction(UserIndex userIndex) {
+  public SearchAction(UserIndex userIndex, DbClient dbClient) {
     this.userIndex = userIndex;
+    this.dbClient = dbClient;
   }
 
   @Override
@@ -72,13 +81,27 @@ public class SearchAction implements UsersWsAction {
     List<String> fields = request.paramAsStrings(Param.FIELDS);
     SearchResult<UserDoc> result = userIndex.search(request.param(Param.TEXT_QUERY), options);
 
+    Map<String, Integer> groupsByLogin = Maps.newHashMap();
+    DbSession session = dbClient.openSession(false);
+    try {
+      Collection<String> logins = Collections2.transform(result.getDocs(), new Function<UserDoc, String>() {
+        @Override
+        public String apply(@Nonnull UserDoc input) {
+          return input.login();
+        }
+      });
+      groupsByLogin = dbClient.groupMembershipDao().countGroupsByLogins(session, logins);
+    } finally {
+      session.close();
+    }
+
     JsonWriter json = response.newJsonWriter().beginObject();
     options.writeJson(json, result.getTotal());
-    writeUsers(json, result, fields);
+    writeUsers(json, result, fields, groupsByLogin);
     json.endObject().close();
   }
 
-  private void writeUsers(JsonWriter json, SearchResult<UserDoc> result, @Nullable List<String> fields) {
+  private void writeUsers(JsonWriter json, SearchResult<UserDoc> result, @Nullable List<String> fields, Map<String, Integer> groupsByLogin) {
 
     json.name("users").beginArray();
     for (UserDoc user : result.getDocs()) {
@@ -86,6 +109,7 @@ public class SearchAction implements UsersWsAction {
       writeIfNeeded(json, user.login(), FIELD_LOGIN, fields);
       writeIfNeeded(json, user.name(), FIELD_NAME, fields);
       writeIfNeeded(json, user.email(), FIELD_EMAIL, fields);
+      writeIfNeeded(json, groupsByLogin.get(user.login()), FIELD_GROUPS_COUNT, fields);
       if (fieldIsWanted(FIELD_SCM_ACCOUNTS, fields)) {
         json.name(FIELD_SCM_ACCOUNTS)
           .beginArray()
@@ -103,6 +127,12 @@ public class SearchAction implements UsersWsAction {
     }
   }
 
+  private void writeIfNeeded(JsonWriter json, Integer value, String field, @Nullable List<String> fields) {
+    if (fieldIsWanted(field, fields)) {
+      json.prop(field, value);
+    }
+  }
+
   private boolean fieldIsWanted(String field, @Nullable List<String> fields) {
     return fields == null || fields.isEmpty() || fields.contains(field);
   }
index ab3c60cb8f22bd9815d241865494cf3e6dd656e8..a2456afbac0c589383bfa6a5dc0c63f6804b6907 100644 (file)
 
 package org.sonar.server.user.ws;
 
+import com.google.common.collect.Lists;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.api.config.Settings;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMembershipDao;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserGroupDto;
+import org.sonar.server.db.DbClient;
 import org.sonar.server.es.EsTester;
+import org.sonar.server.user.db.GroupDao;
+import org.sonar.server.user.db.UserDao;
+import org.sonar.server.user.db.UserGroupDao;
 import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.ws.WsTester;
 
-import java.util.Arrays;
-
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class SearchActionTest {
 
+  @ClassRule
+  public static final DbTester dbTester = new DbTester();
+
   @ClassRule
   public static final EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
 
@@ -46,14 +62,30 @@ public class SearchActionTest {
 
   UserIndex index;
 
+  DbClient dbClient;
+
+  DbSession session;
+
   @Before
   public void setUp() {
+    dbTester.truncateTables();
     esTester.truncateIndices();
 
+    dbClient = new DbClient(dbTester.database(), dbTester.myBatis(),
+      new GroupMembershipDao(dbTester.myBatis()),
+      new UserDao(dbTester.myBatis(), new System2()),
+      new GroupDao(new System2()),
+      new UserGroupDao());
+    session = dbClient.openSession(false);
+
     index = new UserIndex(esTester.client());
-    tester = new WsTester(new UsersWs(new SearchAction(index)));
+    tester = new WsTester(new UsersWs(new SearchAction(index, dbClient)));
     controller = tester.controller("api/users");
+  }
 
+  @After
+  public void tearDown() {
+    session.close();
   }
 
   @Test
@@ -91,40 +123,81 @@ public class SearchActionTest {
       .contains("login")
       .contains("name")
       .contains("email")
-      .contains("scmAccounts");
+      .contains("scmAccounts")
+      .contains("groupsCount");
 
     assertThat(tester.newGetRequest("api/users", "search").setParam("f", "").execute().outputAsString())
       .contains("login")
       .contains("name")
       .contains("email")
-      .contains("scmAccounts");
+      .contains("scmAccounts")
+      .contains("groupsCount");
 
     assertThat(tester.newGetRequest("api/users", "search").setParam("f", "login").execute().outputAsString())
       .contains("login")
       .doesNotContain("name")
       .doesNotContain("email")
-      .doesNotContain("scmAccounts");
+      .doesNotContain("scmAccounts")
+      .doesNotContain("groupsCount");
 
     assertThat(tester.newGetRequest("api/users", "search").setParam("f", "scmAccounts").execute().outputAsString())
       .doesNotContain("login")
       .doesNotContain("name")
       .doesNotContain("email")
-      .contains("scmAccounts");
+      .contains("scmAccounts")
+      .doesNotContain("groupsCount");
+
+    assertThat(tester.newGetRequest("api/users", "search").setParam("f", "groupsCount").execute().outputAsString())
+      .doesNotContain("login")
+      .doesNotContain("name")
+      .doesNotContain("email")
+      .doesNotContain("scmAccounts")
+      .contains("groupsCount");
   }
 
-  private void injectUsers(int numberOfUsers) throws Exception {
+  @Test
+  public void search_with_groups() throws Exception {
+    List<UserDto> users = injectUsers(1);
+
+    GroupDto group1 = dbClient.groupDao().insert(session, new GroupDto().setName("sonar-users"));
+    GroupDto group2 = dbClient.groupDao().insert(session, new GroupDto().setName("sonar-admins"));
+    dbClient.userGroupDao().insert(session, new UserGroupDto().setGroupId(group1.getId()).setUserId(users.get(0).getId()));
+    dbClient.userGroupDao().insert(session, new UserGroupDto().setGroupId(group2.getId()).setUserId(users.get(0).getId()));
+    session.commit();
+
+    tester.newGetRequest("api/users", "search").execute().assertJson(getClass(), "user_with_groups.json");
+  }
+
+  private List<UserDto> injectUsers(int numberOfUsers) throws Exception {
+    List<UserDto> userDtos = Lists.newArrayList();
     long createdAt = System.currentTimeMillis();
     UserDoc[] users = new UserDoc[numberOfUsers];
     for (int index = 0; index < numberOfUsers; index++) {
+      String email = String.format("user-%d@mail.com", index);
+      String login = String.format("user-%d", index);
+      String name = String.format("User %d", index);
+      List<String> scmAccounts = Arrays.asList(String.format("user-%d", index));
+
+      userDtos.add(dbClient.userDao().insert(session, new UserDto()
+        .setActive(true)
+        .setCreatedAt(createdAt)
+        .setEmail(email)
+        .setLogin(login)
+        .setName(name)
+        .setScmAccounts(scmAccounts)
+        .setUpdatedAt(createdAt)));
+
       users[index] = new UserDoc()
         .setActive(true)
         .setCreatedAt(createdAt)
-        .setEmail(String.format("user-%d@mail.com", index))
-        .setLogin(String.format("user-%d", index))
-        .setName(String.format("User %d", index))
-        .setScmAccounts(Arrays.asList(String.format("user-%d", index)))
+        .setEmail(email)
+        .setLogin(login)
+        .setName(name)
+        .setScmAccounts(scmAccounts)
         .setUpdatedAt(createdAt);
     }
+    session.commit();
     esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, users);
+    return userDtos;
   }
 }
index ccbb54a383060d7efee62a886d16b592b3bbde7c..8a2a5f43c46186824f168fbb607129c07506dd53 100644 (file)
@@ -25,6 +25,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.db.DbClient;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.user.UserUpdater;
 import org.sonar.server.user.index.UserIndex;
@@ -46,7 +47,7 @@ public class UsersWsTest {
       new CurrentAction(userSessionRule),
       new DeactivateAction(mock(UserIndex.class), mock(UserUpdater.class), userSessionRule),
       new ChangePasswordAction(mock(UserUpdater.class), userSessionRule),
-      new SearchAction(mock(UserIndex.class))));
+      new SearchAction(mock(UserIndex.class), mock(DbClient.class))));
     controller = tester.controller("api/users");
   }
 
index 88a6fec9ecb943b1008cbe522c7dbc802d2f7306..d568e8b0b7e6a0cd36653c35faf00cc6b10b930c 100644 (file)
@@ -9,7 +9,8 @@
       "email": "user-0@mail.com",
       "scmAccounts": [
         "user-0"
-      ]
+      ],
+      "groupsCount": 0
     },
     {
       "login": "user-1",
@@ -17,7 +18,8 @@
       "email": "user-1@mail.com",
       "scmAccounts": [
         "user-1"
-      ]
+      ],
+      "groupsCount": 0
     },
     {
       "login": "user-2",
@@ -25,7 +27,8 @@
       "email": "user-2@mail.com",
       "scmAccounts": [
         "user-2"
-      ]
+      ],
+      "groupsCount": 0
     },
     {
       "login": "user-3",
@@ -33,7 +36,8 @@
       "email": "user-3@mail.com",
       "scmAccounts": [
         "user-3"
-      ]
+      ],
+      "groupsCount": 0
     },
     {
       "login": "user-4",
@@ -41,7 +45,8 @@
       "email": "user-4@mail.com",
       "scmAccounts": [
         "user-4"
-      ]
+      ],
+      "groupsCount": 0
     }
   ]
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/SearchActionTest/user_with_groups.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/SearchActionTest/user_with_groups.json
new file mode 100644 (file)
index 0000000..a49e825
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "p": 1,
+  "ps": 50,
+  "total": 1,
+  "users": [
+    {
+      "login": "user-0",
+      "name": "User 0",
+      "email": "user-0@mail.com",
+      "scmAccounts": [
+        "user-0"
+      ],
+      "groupsCount": 2
+    }
+  ]
+}
index 8dcba5a78390000e64e818f564ebe9ca00856853..31ff32405c293fd00215800827e855384e43358b 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.core.persistence.DaoComponent;
 import org.sonar.core.persistence.DaoUtils;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.util.NonNullInputFunction;
 
 public class GroupMembershipDao implements DaoComponent {
 
@@ -75,6 +76,23 @@ public class GroupMembershipDao implements DaoComponent {
         return userCounts;
       }
     });
+
+    return result;
+  }
+
+  public Map<String, Integer> countGroupsByLogins(final DbSession session, Collection<String> logins) {
+    final Map<String, Integer> result = Maps.newHashMap();
+    DaoUtils.executeLargeInputs(logins, new NonNullInputFunction<List<String>, List<UserGroupCount>>() {
+      @Override
+      protected List<UserGroupCount> doApply(List<String> input) {
+        List<UserGroupCount> groupCounts = mapper(session).countGroupsByLogins(input);
+        for (UserGroupCount count : groupCounts) {
+          result.put(count.login(), count.groupCount());
+        }
+        return groupCounts;
+      }
+    });
+
     return result;
   }
 
index 4c848759cf28156a7fb3f881d44d5a796bc5659e..b4a66a5ba9a303ab92268c8f9a4d8563e7b558e4 100644 (file)
@@ -33,4 +33,6 @@ public interface GroupMembershipMapper {
   int countGroups(Map<String, Object> params);
 
   List<GroupUserCount> countUsersByGroup(@Param("groupIds") List<Long> groupIds);
+
+  List<UserGroupCount> countGroupsByLogins(@Param("logins") List<String> logins);
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserGroupCount.java b/sonar-core/src/main/java/org/sonar/core/user/UserGroupCount.java
new file mode 100644 (file)
index 0000000..712c1b4
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.user;
+
+public class UserGroupCount {
+
+  private String login;
+  private int groupCount;
+
+  public String login() {
+    return login;
+  }
+
+  public int groupCount() {
+    return groupCount;
+  }
+}
\ No newline at end of file
index 0b9116328d74f39d2a056f3701f0572fad679a91..7774f9eb4b0b0e7243fa1832b0869c2c67f504a0 100644 (file)
     GROUP BY g.name
   </select>
 
+  <select id="countGroupsByLogins" parameterType="string" resultType="org.sonar.core.user.UserGroupCount">
+    SELECT u.login as login, count(gu.user_id) as groupCount
+    FROM users u
+    LEFT JOIN groups_users gu ON gu.user_id=u.id
+    <where>
+      u.login in
+      <foreach collection="logins" open="(" close=")" item="login" separator=",">
+        #{login}
+      </foreach>
+    </where>
+    GROUP BY u.login
+  </select>
+
 </mapper>
index d1a5759d912dee268a4f6ddd20b3eaa6bca2af9e..570a79217778525f2a397469993e429b63571d72 100644 (file)
@@ -185,4 +185,24 @@ public class GroupMembershipDaoTest {
       session.close();
     }
   }
+
+  @Test
+  public void count_groups_by_login() {
+    dbTester.prepareDbUnit(getClass(), "shared.xml");
+    DbSession session = dbTester.myBatis().openSession(false);
+
+    try {
+      assertThat(dao.countGroupsByLogins(session, Arrays.<String>asList())).isEmpty();
+      assertThat(dao.countGroupsByLogins(session, Arrays.asList("two-hundred")))
+        .containsExactly(entry("two-hundred", 3));
+      assertThat(dao.countGroupsByLogins(session, Arrays.asList("two-hundred", "two-hundred-one")))
+        .containsOnly(entry("two-hundred", 3), entry("two-hundred-one", 1));
+      assertThat(dao.countGroupsByLogins(session, Arrays.asList("two-hundred", "two-hundred-one", "two-hundred-two")))
+        .containsOnly(entry("two-hundred", 3), entry("two-hundred-one", 1), entry("two-hundred-two", 0));
+      assertThat(dao.countGroupsByLogins(session, Arrays.asList("two-hundred-two")))
+        .containsOnly(entry("two-hundred-two", 0));
+    } finally {
+      session.close();
+    }
+  }
 }
index f8ab9693a66a48cb7542487b0e621634bae09821..a882f396f6c7e868c0cf0293922067856fa93052 100644 (file)
@@ -12,4 +12,8 @@
   <!-- user 201 is in users group -->
   <groups_users user_id="201" group_id="101"/>
 
+  <users id="200" login="two-hundred"/>
+  <users id="201" login="two-hundred-one"/>
+  <users id="202" login="two-hundred-two"/>
+
 </dataset>