From: Jean-Baptiste Lievremont Date: Wed, 27 May 2015 13:53:44 +0000 (+0200) Subject: SONAR-6475 Add WS to list members of a group X-Git-Tag: 5.2-RC1~1746 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9b20fa3ea387c0b23e90240c5b77f80d5668d8d4;p=sonarqube.git SONAR-6475 Add WS to list members of a group --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java index 8c2406e1c91..f02fcaca448 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UserGroupsModule.java @@ -31,7 +31,8 @@ public class UserGroupsModule extends Module { SearchAction.class, CreateAction.class, DeleteAction.class, - UpdateAction.class); + UpdateAction.class, + UsersAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java new file mode 100644 index 00000000000..c6c5d185e1f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java @@ -0,0 +1,141 @@ +/* + * 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.server.usergroups.ws; + +import java.util.List; +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.NewAction; +import org.sonar.api.server.ws.WebService.NewController; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.Paging; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.user.GroupDto; +import org.sonar.core.user.GroupMembershipQuery; +import org.sonar.core.user.UserMembershipDto; +import org.sonar.core.user.UserMembershipQuery; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.NotFoundException; + +public class UsersAction implements UserGroupsWsAction { + + private static final String PARAM_ID = "id"; + private static final String PARAM_SELECTED = "selected"; + + private static final String SELECTION_ALL = "all"; + private static final String SELECTION_SELECTED = "selected"; + private static final String SELECTION_DESELECTED = "deselected"; + + private final DbClient dbClient; + + public UsersAction(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public void define(NewController context) { + NewAction action = context.createAction("users") + .setDescription("List the members of a group.") + .setHandler(this) + .setResponseExample(getClass().getResource("example-users.json")) + .setSince("5.2"); + + action.createParam(PARAM_ID) + .setDescription("A group ID") + .setExampleValue("42") + .setRequired(true); + + action.createParam(PARAM_SELECTED) + .setDescription("If specified, only show users who belong to this group (selected) or not (deselected).") + .setPossibleValues(SELECTION_SELECTED, SELECTION_DESELECTED, SELECTION_ALL) + .setDefaultValue(SELECTION_ALL); + + action.createParam(Param.TEXT_QUERY) + .setDescription("If specified, only show users whose name or login contains the query.") + .setExampleValue("freddy"); + + action.addPagingParams(25); + } + + @Override + public void handle(Request request, Response response) throws Exception { + Long groupId = request.mandatoryParamAsLong(PARAM_ID); + int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE); + int page = request.mandatoryParamAsInt(Param.PAGE); + String queryString = request.param(Param.TEXT_QUERY); + String selected = request.param(PARAM_SELECTED); + + UserMembershipQuery query = UserMembershipQuery.builder() + .groupId(groupId) + .memberSearch(queryString) + .membership(getMembership(selected)) + .pageIndex(page) + .pageSize(pageSize) + .build(); + + DbSession session = dbClient.openSession(false); + try { + GroupDto group = dbClient.groupDao().selectById(session, groupId); + if (group == null) { + throw new NotFoundException(String.format("Unable to find a group with ID '%d'", groupId)); + } + int total = dbClient.groupMembershipDao().countMembers(session, query); + Paging paging = Paging.create(pageSize, page, total); + List users = dbClient.groupMembershipDao().selectMembers(session, query, paging.offset(), pageSize); + + JsonWriter json = response.newJsonWriter().beginObject(); + writeMembers(json, users); + writePaging(json, paging); + json.endObject().close(); + } finally { + session.close(); + } + } + + private void writeMembers(JsonWriter json, List users) { + json.name("users").beginArray(); + for (UserMembershipDto user : users) { + json.beginObject() + .prop("login", user.getLogin()) + .prop("name", user.getName()) + .prop("selected", user.getGroupId() != null) + .endObject(); + } + json.endArray(); + } + + private void writePaging(JsonWriter json, Paging paging) { + json.prop("p", paging.pageIndex()) + .prop("ps", paging.pageSize()) + .prop("total", paging.total()); + } + + private String getMembership(@Nullable String selected) { + String membership = GroupMembershipQuery.ANY; + if (SELECTION_SELECTED.equals(selected)) { + membership = GroupMembershipQuery.IN; + } else if (SELECTION_DESELECTED.equals(selected)) { + membership = GroupMembershipQuery.OUT; + } + return membership; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java new file mode 100644 index 00000000000..1540f031856 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/UsersActionTest.java @@ -0,0 +1,197 @@ +/* + * 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.server.usergroups.ws; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +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.exceptions.NotFoundException; +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.ws.WsTester; + +public class UsersActionTest { + + @ClassRule + public static final DbTester dbTester = new DbTester(); + + WebService.Controller controller; + + WsTester tester; + + DbClient dbClient; + + DbSession session; + + @Before + public void setUp() { + dbTester.truncateTables(); + + System2 system2 = new System2(); + UserDao userDao = new UserDao(dbTester.myBatis(), system2); + GroupDao groupDao = new GroupDao(system2); + UserGroupDao userGroupDao = new UserGroupDao(); + GroupMembershipDao groupMembershipDao = new GroupMembershipDao(dbTester.myBatis()); + + dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, groupDao, userGroupDao, groupMembershipDao); + session = dbClient.openSession(false); + session.commit(); + + tester = new WsTester(new UserGroupsWs(new UsersAction(dbClient))); + controller = tester.controller("api/users"); + + } + + @After + public void tearDown() { + session.close(); + } + + @Test(expected = NotFoundException.class) + public void fail_on_unknown_user() throws Exception { + tester.newGetRequest("api/usergroups", "users") + .setParam("id", "42") + .setParam("login", "john").execute(); + } + + @Test + public void empty_users() throws Exception { + GroupDto group = createGroup(); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("login", "john") + .setParam("id", group.getId().toString()) + .execute() + .assertJson(getClass(), "empty.json"); + } + + @Test + public void all_users() throws Exception { + GroupDto group = createGroup(); + UserDto groupUser = createUser("ada", "Ada Lovelace"); + createUser("grace", "Grace Hopper"); + addUserToGroup(groupUser, group); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .execute() + .assertJson(getClass(), "all.json"); + } + + @Test + public void selected_users() throws Exception { + GroupDto group = createGroup(); + UserDto groupUser = createUser("ada", "Ada Lovelace"); + createUser("grace", "Grace Hopper"); + addUserToGroup(groupUser, group); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("selected", "selected") + .execute() + .assertJson(getClass(), "selected.json"); + } + + @Test + public void deselected_users() throws Exception { + GroupDto group = createGroup(); + UserDto groupUser = createUser("ada", "Ada Lovelace"); + createUser("grace", "Grace Hopper"); + addUserToGroup(groupUser, group); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("selected", "deselected") + .execute() + .assertJson(getClass(), "deselected.json"); + } + + @Test + public void paging() throws Exception { + GroupDto group = createGroup(); + UserDto groupUser = createUser("ada", "Ada Lovelace"); + createUser("grace", "Grace Hopper"); + addUserToGroup(groupUser, group); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("ps", "1") + .execute() + .assertJson(getClass(), "all_page1.json"); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("ps", "1") + .setParam("p", "2") + .execute() + .assertJson(getClass(), "all_page2.json"); + } + + @Test + public void filtering() throws Exception { + GroupDto group = createGroup(); + UserDto groupUser = createUser("ada", "Ada Lovelace"); + createUser("grace", "Grace Hopper"); + addUserToGroup(groupUser, group); + session.commit(); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("q", "ace") + .execute() + .assertJson(getClass(), "all.json"); + + tester.newGetRequest("api/usergroups", "users") + .setParam("id", group.getId().toString()) + .setParam("q", "love") + .execute() + .assertJson(getClass(), "all_ada.json"); + } + + private GroupDto createGroup() { + return dbClient.groupDao().insert(session, new GroupDto() + .setName("sonar-users")); + } + + private UserDto createUser(String login, String name) { + return dbClient.userDao().insert(session, new UserDto().setLogin(login).setName(name)); + } + + private void addUserToGroup(UserDto user, GroupDto usersGroup) { + dbClient.userGroupDao().insert(session, new UserGroupDto().setUserId(user.getId()).setGroupId(usersGroup.getId())); + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json new file mode 100644 index 00000000000..8f0e903fdb9 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all.json @@ -0,0 +1,8 @@ +{ + "p": 1, + "total": 2, + "users": [ + {"login": "ada", "name": "Ada Lovelace", "selected": true}, + {"login": "grace", "name": "Grace Hopper", "selected": false} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json new file mode 100644 index 00000000000..cb5b2a66035 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_ada.json @@ -0,0 +1,7 @@ +{ + "p": 1, + "total": 1, + "users": [ + {"login": "ada", "name": "Ada Lovelace", "selected": true} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json new file mode 100644 index 00000000000..779ba39e9aa --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page1.json @@ -0,0 +1,8 @@ +{ + "p": 1, + "ps": 1, + "total": 2, + "users": [ + {"login": "ada", "name": "Ada Lovelace", "selected": true} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json new file mode 100644 index 00000000000..be6289aef60 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_page2.json @@ -0,0 +1,8 @@ +{ + "p": 2, + "ps": 1, + "total": 2, + "users": [ + {"login": "grace", "name": "Grace Hopper", "selected": false} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json new file mode 100644 index 00000000000..cb5b2a66035 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/all_users.json @@ -0,0 +1,7 @@ +{ + "p": 1, + "total": 1, + "users": [ + {"login": "ada", "name": "Ada Lovelace", "selected": true} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json new file mode 100644 index 00000000000..d181382426a --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/deselected.json @@ -0,0 +1,7 @@ +{ + "p": 1, + "total": 1, + "users": [ + {"login": "grace", "name": "Grace Hopper", "selected": false} + ] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json new file mode 100644 index 00000000000..89d34eea6e0 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/empty.json @@ -0,0 +1,5 @@ +{ + "p": 1, + "total": 0, + "users": [] +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json new file mode 100644 index 00000000000..cb5b2a66035 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/usergroups/ws/UsersActionTest/selected.json @@ -0,0 +1,7 @@ +{ + "p": 1, + "total": 1, + "users": [ + {"login": "ada", "name": "Ada Lovelace", "selected": true} + ] +} \ No newline at end of file diff --git a/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java b/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java index 9a03d9335c3..09b25209add 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java +++ b/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipDao.java @@ -65,6 +65,16 @@ public class GroupMembershipDao implements DaoComponent { return mapper(session).countGroups(params); } + public List selectMembers(SqlSession session, UserMembershipQuery query, int offset, int limit) { + Map params = ImmutableMap.of("query", query, "groupId", query.groupId()); + return mapper(session).selectMembers(params, new RowBounds(offset, limit)); + } + + public int countMembers(SqlSession session, UserMembershipQuery query) { + Map params = ImmutableMap.of("query", query, "groupId", query.groupId()); + return mapper(session).countMembers(params); + } + public Map countUsersByGroups(final DbSession session, Collection groupIds) { final Map result = Maps.newHashMap(); DaoUtils.executeLargeInputs(groupIds, new Function, List>() { diff --git a/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java b/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java index 777aa1eea00..2d68ac5514c 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/user/GroupMembershipMapper.java @@ -32,6 +32,10 @@ public interface GroupMembershipMapper { int countGroups(Map params); + List selectMembers(Map params, RowBounds rowBounds); + + int countMembers(Map params); + List countUsersByGroup(@Param("groupIds") List groupIds); List selectGroupsByLogins(@Param("logins") List logins); diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java new file mode 100644 index 00000000000..afb6ca9584a --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipDto.java @@ -0,0 +1,72 @@ +/* + * 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; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +/** + * @since 5.2 + */ +public class UserMembershipDto { + + private Long id; + private Long groupId; + private String login; + private String name; + + public Long getId() { + return id; + } + + public UserMembershipDto setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public UserMembershipDto setName(String name) { + this.name = name; + return this; + } + + @CheckForNull + public String getLogin() { + return login; + } + + public UserMembershipDto setLogin(@Nullable String login) { + this.login = login; + return this; + } + + @CheckForNull + public Long getGroupId() { + return groupId; + } + + public UserMembershipDto setGroupId(@Nullable Long groupId) { + this.groupId = groupId; + return this; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java new file mode 100644 index 00000000000..209b2063c73 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/user/UserMembershipQuery.java @@ -0,0 +1,169 @@ +/* + * 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; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; + +public class UserMembershipQuery { + + public static final int DEFAULT_PAGE_INDEX = 1; + public static final int DEFAULT_PAGE_SIZE = 100; + + public static final String ANY = "ANY"; + public static final String IN = "IN"; + public static final String OUT = "OUT"; + public static final Set AVAILABLE_MEMBERSHIP = ImmutableSet.of(ANY, IN, OUT); + + private final Long groupId; + private final String membership; + + private final String memberSearch; + + // for internal use in MyBatis + final String memberSearchSql; + + // max results per page + private final int pageSize; + + // index of selected page. Start with 1. + private final int pageIndex; + + + private UserMembershipQuery(Builder builder) { + this.groupId = builder.groupId; + this.membership = builder.membership; + this.memberSearch = builder.memberSearch; + this.memberSearchSql = memberSearchToSql(memberSearch); + + this.pageSize = builder.pageSize; + this.pageIndex = builder.pageIndex; + } + + private String memberSearchToSql(@Nullable String s) { + String sql = null; + if (s != null) { + sql = StringUtils.replace(StringUtils.upperCase(s), "%", "/%"); + sql = StringUtils.replace(sql, "_", "/_"); + sql = "%" + sql + "%"; + } + return sql; + } + + public Long groupId() { + return groupId; + } + + @CheckForNull + public String membership() { + return membership; + } + + /** + * Search for users names/logins containing a given string + */ + @CheckForNull + public String memberSearch() { + return memberSearch; + } + + public int pageSize() { + return pageSize; + } + + public int pageIndex() { + return pageIndex; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long groupId; + private String membership; + private String memberSearch; + + private Integer pageIndex = DEFAULT_PAGE_INDEX; + private Integer pageSize = DEFAULT_PAGE_SIZE; + + private Builder() { + } + + public Builder groupId(Long groupId) { + this.groupId = groupId; + return this; + } + + public Builder membership(@Nullable String membership) { + this.membership = membership; + return this; + } + + public Builder memberSearch(@Nullable String s) { + this.memberSearch = StringUtils.defaultIfBlank(s, null); + return this; + } + + public Builder pageSize(@Nullable Integer i) { + this.pageSize = i; + return this; + } + + public Builder pageIndex(@Nullable Integer i) { + this.pageIndex = i; + return this; + } + + private void initMembership() { + if (membership == null) { + membership = UserMembershipQuery.ANY; + } else { + Preconditions.checkArgument(AVAILABLE_MEMBERSHIP.contains(membership), + "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIP); + } + } + + private void initPageSize() { + if (pageSize == null) { + pageSize = DEFAULT_PAGE_SIZE; + } + } + + private void initPageIndex() { + if (pageIndex == null) { + pageIndex = DEFAULT_PAGE_INDEX; + } + Preconditions.checkArgument(pageIndex > 0, "Page index must be greater than 0 (got " + pageIndex + ")"); + } + + public UserMembershipQuery build() { + Preconditions.checkNotNull(groupId, "Group ID cant be null."); + initMembership(); + initPageIndex(); + initPageSize(); + return new UserMembershipQuery(this); + } + } +} diff --git a/sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml b/sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml index 2663c390189..21ba56c9785 100644 --- a/sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/user/GroupMembershipMapper.xml @@ -58,4 +58,34 @@ + + FROM users u + LEFT JOIN groups_users gu ON gu.user_id=u.id AND gu.group_id=#{groupId} + + + + AND gu.group_id IS NOT NULL + + + AND gu.group_id IS NULL + + + + AND ((UPPER(u.login) LIKE #{query.memberSearchSql} ESCAPE '/') OR (UPPER(u.name) LIKE #{query.memberSearchSql} ESCAPE '/')) + + AND u.active=${_true} + + + + + + + diff --git a/sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java b/sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java index ed86008d099..8b8992f07f0 100644 --- a/sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/user/GroupMembershipDaoTest.java @@ -202,4 +202,146 @@ public class GroupMembershipDaoTest { session.close(); } } + + @Test + public void count_members() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + DbSession session = dbTester.myBatis().openSession(false); + + try { + // 100 has 1 member and 1 non member + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build())).isEqualTo(1); + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1); + // 101 has 2 members + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build())).isEqualTo(2); + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build())).isZero(); + // 102 has 1 member and 1 non member + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build())).isEqualTo(1); + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build())).isEqualTo(1); + // 103 has no member + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build())).isZero(); + assertThat(dao.countMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build())).isEqualTo(2); + } finally { + session.close(); + } + } + + @Test + public void select_group_members_by_query() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + // 100 has 1 member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1); + // 101 has 2 members + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(2); + // 102 has 1 member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.IN).build(), 0, 10)).hasSize(1); + // 103 has no member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.IN).build(), 0, 10)).isEmpty(); + } finally { + session.close(); + } + } + + @Test + public void select_users_not_affected_to_a_group_by_query() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + // 100 has 1 member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1); + // 101 has 2 members + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(101L).membership(UserMembershipQuery.OUT).build(), 0, 10)).isEmpty(); + // 102 has 1 member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(102L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(1); + // 103 has no member + assertThat(dao.selectMembers(session, UserMembershipQuery.builder().groupId(103L).membership(UserMembershipQuery.OUT).build(), 0, 10)).hasSize(2); + } finally { + session.close(); + } + } + + @Test + public void search_by_user_name_or_login() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + + List result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10); + assertThat(result).hasSize(2); + + assertThat(result.get(0).getName()).isEqualTo("Admin"); + assertThat(result.get(1).getName()).isEqualTo("Not Admin"); + + result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("not").build(), 0, 10); + assertThat(result).hasSize(1); + + } finally { + session.close(); + } + } + + @Test + public void search_by_login_or_name_with_capitalization() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + List result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("admin").build(), 0, 10); + assertThat(result).hasSize(2); + + result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).memberSearch("AdMiN").build(), 0, 10); + assertThat(result).hasSize(2); + } finally { + session.close(); + } + + } + + @Test + public void should_be_sorted_by_user_name() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + List result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 0, 10); + assertThat(result).hasSize(2); + assertThat(result.get(0).getName()).isEqualTo("Admin"); + assertThat(result.get(1).getName()).isEqualTo("Not Admin"); + } finally { + session.close(); + } + } + + @Test + public void members_should_be_paginated() { + dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml"); + + DbSession session = dbTester.myBatis().openSession(false); + + try { + List result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 0, 2); + assertThat(result).hasSize(2); + assertThat(result.get(0).getName()).isEqualTo("Admin"); + assertThat(result.get(1).getName()).isEqualTo("Not Admin"); + + result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 1, 2); + assertThat(result).hasSize(1); + assertThat(result.get(0).getName()).isEqualTo("Not Admin"); + + result = dao.selectMembers(session, UserMembershipQuery.builder().groupId(100L).build(), 2, 1); + assertThat(result).isEmpty(); + } finally { + session.close(); + } + } } diff --git a/sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared_plus_empty_group.xml b/sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared_plus_empty_group.xml index 487e11ff71f..36b89224326 100644 --- a/sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared_plus_empty_group.xml +++ b/sonar-core/src/test/resources/org/sonar/core/user/GroupMembershipDaoTest/shared_plus_empty_group.xml @@ -13,4 +13,8 @@ + + + +