import org.sonar.server.user.ws.FavoritesWs;
import org.sonar.server.user.ws.UserPropertiesWs;
import org.sonar.server.user.ws.UsersWs;
+import org.sonar.server.usergroups.ws.UserGroupsWs;
import org.sonar.server.util.BooleanTypeValidation;
import org.sonar.server.util.FloatTypeValidation;
import org.sonar.server.util.IntegerTypeValidation;
// groups
GroupMembershipService.class,
GroupMembershipFinder.class,
+ UserGroupsWs.class,
+ org.sonar.server.usergroups.ws.SearchAction.class,
// permissions
PermissionFacade.class,
package org.sonar.server.user.db;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.apache.ibatis.session.RowBounds;
import org.sonar.api.utils.System2;
import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.user.GroupDto;
import org.sonar.core.user.GroupMapper;
-import java.util.Date;
-import java.util.List;
-
/**
* @since 3.2
*/
public class GroupDao implements DaoComponent {
+ private static final String SQL_WILDCARD = "%";
private System2 system;
public GroupDao(System2 system) {
return mapper(session).selectByKey(key);
}
+ public int countByQuery(DbSession session, @Nullable String query) {
+ return mapper(session).countByQuery(groupSearchToSql(query));
+ }
+
+ public List<GroupDto> selectByQuery(DbSession session, @Nullable String query, int offset, int limit) {
+ return mapper(session).selectByQuery(groupSearchToSql(query), new RowBounds(offset, limit));
+ }
+
public GroupDto insert(DbSession session, GroupDto item) {
Date createdAt = new Date(system.now());
item.setCreatedAt(createdAt)
private GroupMapper mapper(DbSession session) {
return session.getMapper(GroupMapper.class);
}
+
+ private String groupSearchToSql(@Nullable String query) {
+ String sql = SQL_WILDCARD;
+ if (query != null) {
+ sql = StringUtils.replace(StringUtils.upperCase(query), SQL_WILDCARD, "/%");
+ sql = StringUtils.replace(sql, "_", "/_");
+ sql = SQL_WILDCARD + sql + SQL_WILDCARD;
+ }
+ return sql;
+ }
}
--- /dev/null
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usergroups;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+/*
+ * 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 com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.user.GroupDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.es.SearchOptions;
+
+public class SearchAction implements UserGroupsWsAction {
+
+ private static final String FIELD_ID = "id";
+ private static final String FIELD_NAME = "name";
+ private static final String FIELD_DESCRIPTION = "description";
+ private static final String FIELD_MEMBERS_COUNT = "membersCount";
+ private static final List<String> ALL_FIELDS = Arrays.asList(FIELD_NAME, FIELD_DESCRIPTION, FIELD_MEMBERS_COUNT);
+
+ private DbClient dbClient;
+
+ public SearchAction(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void define(NewController context) {
+ context.createAction("search")
+ .setDescription("Search for user groups")
+ .setHandler(this)
+ .setResponseExample(getClass().getResource("example-search.json"))
+ .setSince("5.2")
+ .addFieldsParam(ALL_FIELDS)
+ .addPagingParams(100)
+ .addSearchQuery("sonar-users", "names");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ int page = request.mandatoryParamAsInt(Param.PAGE);
+ int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+ SearchOptions options = new SearchOptions()
+ .setPage(page, pageSize);
+
+ String query = StringUtils.defaultIfBlank(request.param(Param.TEXT_QUERY), "");
+ Set<String> fields = neededFields(request);
+
+ DbSession dbSession = dbClient.openSession(false);
+ try {
+ int limit = dbClient.groupDao().countByQuery(dbSession, query);
+ List<GroupDto> groups = dbClient.groupDao().selectByQuery(dbSession, query, options.getOffset(), pageSize);
+ Collection<Long> groupIds = Collections2.transform(groups, new Function<GroupDto, Long>() {
+ @Override
+ public Long apply(@Nonnull GroupDto input) {
+ return input.getId();
+ }
+ });
+ Map<String, Integer> userCountByGroup = dbClient.groupMembershipDao().countUsersByGroups(dbSession, groupIds);
+
+ JsonWriter json = response.newJsonWriter().beginObject();
+ options.writeJson(json, limit);
+ writeGroups(json, groups, userCountByGroup, fields);
+ json.endObject().close();
+ } finally {
+ MyBatis.closeQuietly(dbSession);
+ }
+ }
+
+ private void writeGroups(JsonWriter json, List<GroupDto> groups, Map<String, Integer> userCountByGroup, Set<String> fields) {
+ json.name("groups").beginArray();
+ for (GroupDto group : groups) {
+ writeGroup(json, group, userCountByGroup.get(group.getName()), fields);
+ }
+ json.endArray();
+ }
+
+ private void writeGroup(JsonWriter json, GroupDto group, Integer memberCount, Set<String> fields) {
+ json.beginObject()
+ .prop(FIELD_ID, group.getId().toString())
+ .prop(FIELD_NAME, fields.contains(FIELD_NAME) ? group.getName() : null)
+ .prop(FIELD_DESCRIPTION, fields.contains(FIELD_DESCRIPTION) ? group.getDescription() : null)
+ .prop(FIELD_MEMBERS_COUNT, fields.contains(FIELD_MEMBERS_COUNT) ? memberCount : null)
+ .endObject();
+ }
+
+ private Set<String> neededFields(Request request) {
+ Set<String> fields = Sets.newHashSet();
+ List<String> fieldsFromRequest = request.paramAsStrings(Param.FIELDS);
+ if (fieldsFromRequest == null || fieldsFromRequest.isEmpty()) {
+ fields.addAll(ALL_FIELDS);
+ } else {
+ fields.addAll(fieldsFromRequest);
+ }
+ return fields;
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.api.server.ws.WebService;
+
+public class UserGroupsWs implements WebService {
+
+ private UserGroupsWsAction[] actions;
+
+ public UserGroupsWs(UserGroupsWsAction... actions) {
+ this.actions = actions;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController controller = context.createController("api/usergroups")
+ .setDescription("User groups management")
+ .setSince("5.2");
+
+ for (UserGroupsWsAction action : actions) {
+ action.define(controller);
+ }
+
+ controller.done();
+ }
+
+}
--- /dev/null
+/*
+ * 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.sonar.server.ws.WsAction;
+
+public interface UserGroupsWsAction extends WsAction {
+
+ // Marker interface
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usergroups.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+{
+ "p": 1,
+ "ps": 100,
+ "total": 2,
+ "groups": [
+ {
+ "id": "1",
+ "name": "users",
+ "description": "Users",
+ "membersCount": 17
+ },
+ {
+ "id": "2",
+ "name": "administrators",
+ "description": "Administrators",
+ "membersCount": 2
+ }
+ ]
+}
import org.junit.After;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
+import org.junit.experimental.categories.Category;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
-import org.sonar.core.persistence.AbstractDaoTestCase;
import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
import org.sonar.core.user.GroupDto;
+import org.sonar.test.DbTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-public class GroupDaoTest extends AbstractDaoTestCase {
+@Category(DbTests.class)
+public class GroupDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
GroupDao dao;
DbSession session;
@Before
public void setUp() {
- this.session = getMyBatis().openSession(false);
+ dbTester.truncateTables();
+ this.session = dbTester.myBatis().openSession(false);
this.system2 = mock(System2.class);
this.dao = new GroupDao(system2);
}
@Test
public void select_by_key() {
- setupData("select_by_key");
+ dbTester.prepareDbUnit(getClass(), "select_by_key.xml");
GroupDto group = new GroupDao(system2).selectByKey(session, "sonar-users");
assertThat(group).isNotNull();
@Test
public void find_by_user_login() {
- setupData("find_by_user_login");
+ dbTester.prepareDbUnit(getClass(), "find_by_user_login.xml");
assertThat(dao.findByUserLogin(session, "john")).hasSize(2);
assertThat(dao.findByUserLogin(session, "max")).isEmpty();
public void insert() {
when(system2.now()).thenReturn(DateUtils.parseDate("2014-09-08").getTime());
- setupData("empty");
+ dbTester.prepareDbUnit(getClass(), "empty.xml");
GroupDto dto = new GroupDto()
.setId(1L)
dao.insert(session, dto);
session.commit();
- checkTables("insert", "groups");
+ dbTester.assertDbUnit(getClass(), "insert-result.xml", "groups");
+ }
+
+ @Test
+ public void select_by_query() {
+ dbTester.prepareDbUnit(getClass(), "select_by_query.xml");
+
+ /*
+ * Ordering and paging are not fully tested, case insensitive sort is broken on MySQL
+ */
+
+ // Null query
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 0, 10))
+ .hasSize(5)
+ .extracting("name").containsOnly("customers-group1", "customers-group2", "customers-group3", "SONAR-ADMINS", "sonar-users");
+
+ // Empty query
+ assertThat(new GroupDao(system2).selectByQuery(session, "", 0, 10))
+ .hasSize(5)
+ .extracting("name").containsOnly("customers-group1", "customers-group2", "customers-group3", "SONAR-ADMINS", "sonar-users");
+
+ // Filter on name
+ assertThat(new GroupDao(system2).selectByQuery(session, "sonar", 0, 10))
+ .hasSize(2)
+ .extracting("name").containsOnly("SONAR-ADMINS", "sonar-users");
+
+ // Pagination
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 0, 3))
+ .hasSize(3);
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 3, 3))
+ .hasSize(2);
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 6, 3)).isEmpty();
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 0, 5))
+ .hasSize(5);
+ assertThat(new GroupDao(system2).selectByQuery(session, null, 5, 5)).isEmpty();
+ }
+
+ @Test
+ public void count_by_query() {
+ dbTester.prepareDbUnit(getClass(), "select_by_query.xml");
+
+ // Null query
+ assertThat(new GroupDao(system2).countByQuery(session, null)).isEqualTo(5);
+
+ // Empty query
+ assertThat(new GroupDao(system2).countByQuery(session, "")).isEqualTo(5);
+
+ // Filter on name
+ assertThat(new GroupDao(system2).countByQuery(session, "sonar")).isEqualTo(2);
}
}
--- /dev/null
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.api.server.ws.WebService.Param;
+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.UserGroupDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.db.GroupDao;
+import org.sonar.server.user.db.UserGroupDao;
+import org.sonar.server.ws.WsTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class SearchActionTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ private WsTester tester;
+
+ private GroupDao groupDao;
+
+ private GroupMembershipDao groupMembershipDao;
+
+ private UserGroupDao userGroupDao;
+
+ private DbSession session;
+
+ @Before
+ public void setUp() {
+ dbTester.truncateTables();
+
+ groupDao = new GroupDao(System2.INSTANCE);
+ groupMembershipDao = new GroupMembershipDao(dbTester.myBatis());
+ userGroupDao = new UserGroupDao();
+
+ DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), groupDao, groupMembershipDao);
+
+ tester = new WsTester(new UserGroupsWs(new SearchAction(dbClient)));
+
+ session = dbClient.openSession(false);
+ }
+
+ @After
+ public void after() {
+ session.close();
+ }
+
+ @Test
+ public void search_empty() throws Exception {
+ tester.newGetRequest("api/usergroups", "search").execute().assertJson(getClass(), "empty.json");
+ }
+
+ @Test
+ public void search_without_parameters() throws Exception {
+ insertGroups("users", "admins", "customer1", "customer2", "customer3");
+ session.commit();
+
+ tester.newGetRequest("api/usergroups", "search").execute().assertJson(getClass(), "five_groups.json");
+ }
+
+ @Test
+ public void search_with_members() throws Exception {
+ insertGroups("users", "admins", "customer1", "customer2", "customer3");
+ insertMembers("users", 5);
+ insertMembers("admins", 1);
+ insertMembers("customer2", 4);
+ session.commit();
+
+ tester.newGetRequest("api/usergroups", "search").execute().assertJson(getClass(), "with_members.json");
+ }
+
+ @Test
+ public void search_with_query() throws Exception {
+ insertGroups("users", "admins", "customer1", "customer2", "customer3");
+ session.commit();
+
+ tester.newGetRequest("api/usergroups", "search").setParam(Param.TEXT_QUERY, "custom").execute().assertJson(getClass(), "customers.json");
+ }
+
+ @Test
+ public void search_with_paging() throws Exception {
+ insertGroups("users", "admins", "customer1", "customer2", "customer3");
+ session.commit();
+
+ tester.newGetRequest("api/usergroups", "search")
+ .setParam(Param.PAGE_SIZE, "3").execute().assertJson(getClass(), "page_1.json");
+ tester.newGetRequest("api/usergroups", "search")
+ .setParam(Param.PAGE_SIZE, "3").setParam(Param.PAGE, "2").execute().assertJson(getClass(), "page_2.json");
+ tester.newGetRequest("api/usergroups", "search")
+ .setParam(Param.PAGE_SIZE, "3").setParam(Param.PAGE, "3").execute().assertJson(getClass(), "page_3.json");
+ }
+
+ @Test
+ public void search_with_fields() throws Exception {
+ insertGroups("sonar-users");
+ session.commit();
+
+ assertThat(tester.newGetRequest("api/usergroups", "search").execute().outputAsString())
+ .contains("id")
+ .contains("name")
+ .contains("description")
+ .contains("membersCount");
+
+ assertThat(tester.newGetRequest("api/usergroups", "search").setParam(Param.FIELDS, "").execute().outputAsString())
+ .contains("id")
+ .contains("name")
+ .contains("description")
+ .contains("membersCount");
+
+ assertThat(tester.newGetRequest("api/usergroups", "search").setParam(Param.FIELDS, "name").execute().outputAsString())
+ .contains("id")
+ .contains("name")
+ .doesNotContain("description")
+ .doesNotContain("membersCount");
+
+ assertThat(tester.newGetRequest("api/usergroups", "search").setParam(Param.FIELDS, "description").execute().outputAsString())
+ .contains("id")
+ .doesNotContain("name")
+ .contains("description")
+ .doesNotContain("membersCount");
+
+ assertThat(tester.newGetRequest("api/usergroups", "search").setParam(Param.FIELDS, "membersCount").execute().outputAsString())
+ .contains("id")
+ .doesNotContain("name")
+ .doesNotContain("description")
+ .contains("membersCount");
+ }
+
+ private void insertGroups(String... groupNames) {
+ for (String groupName : groupNames) {
+ groupDao.insert(session, new GroupDto()
+ .setName(groupName)
+ .setDescription(StringUtils.capitalize(groupName)));
+ }
+ }
+
+ private void insertMembers(String groupName, int count) {
+ long groupId = groupDao.selectByKey(session, groupName).getId();
+ for (int i = 0; i < count; i++) {
+ userGroupDao.insert(session, new UserGroupDto().setGroupId(groupId).setUserId((long) i + 1));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class UserGroupsWsTest {
+ @Rule
+ public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ WebService.Controller controller;
+
+ @Before
+ public void setUp() {
+ WsTester tester = new WsTester(new UserGroupsWs(new SearchAction(mock(DbClient.class))));
+ controller = tester.controller("api/usergroups");
+ }
+
+ @Test
+ public void define_controller() {
+ assertThat(controller).isNotNull();
+ assertThat(controller.description()).isNotEmpty();
+ assertThat(controller.since()).isEqualTo("5.2");
+ assertThat(controller.actions()).hasSize(1);
+ }
+
+ @Test
+ public void define_search_action() {
+ WebService.Action action = controller.action("search");
+ assertThat(action).isNotNull();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.responseExampleAsString()).isNotEmpty();
+ assertThat(action.params()).hasSize(4);
+ }
+}
--- /dev/null
+<dataset>
+
+ <groups id="1" name="sonar-users" description="Sonar Users" created_at="2014-09-07" updated_at="2014-09-08"/>
+ <groups id="2" name="SONAR-ADMINS" description="Sonar Admins" created_at="2014-09-07" updated_at="2014-09-08"/>
+ <groups id="3" name="customers-group1" description="Group 1" created_at="2014-09-07" updated_at="2014-09-08"/>
+ <groups id="4" name="customers-group2" description="Group 2" created_at="2014-09-07" updated_at="2014-09-08"/>
+ <groups id="5" name="customers-group3" description="Group 3" created_at="2014-09-07" updated_at="2014-09-08"/>
+
+</dataset>
--- /dev/null
+{
+ "p": 1,
+ "ps": 100,
+ "total": 3,
+ "groups": [
+ {"name": "customer1", "description": "Customer1", "membersCount": 0},
+ {"name": "customer2", "description": "Customer2", "membersCount": 0},
+ {"name": "customer3", "description": "Customer3", "membersCount": 0}
+ ]
+}
--- /dev/null
+{
+ "p": 1,
+ "ps": 100,
+ "total": 0,
+ "groups": []
+}
--- /dev/null
+{
+ "p": 1,
+ "ps": 100,
+ "total": 5,
+ "groups": [
+ {"name": "admins", "description": "Admins", "membersCount": 0},
+ {"name": "customer1", "description": "Customer1", "membersCount": 0},
+ {"name": "customer2", "description": "Customer2", "membersCount": 0},
+ {"name": "customer3", "description": "Customer3", "membersCount": 0},
+ {"name": "users", "description": "Users", "membersCount": 0}
+ ]
+}
--- /dev/null
+{
+ "p": 1,
+ "ps": 3,
+ "total": 5,
+ "groups": [
+ {"name": "admins", "description": "Admins", "membersCount": 0},
+ {"name": "customer1", "description": "Customer1", "membersCount": 0},
+ {"name": "customer2", "description": "Customer2", "membersCount": 0}
+ ]
+}
--- /dev/null
+{
+ "p": 2,
+ "ps": 3,
+ "total": 5,
+ "groups": [
+ {"name": "customer3", "description": "Customer3", "membersCount": 0},
+ {"name": "users", "description": "Users", "membersCount": 0}
+ ]
+}
--- /dev/null
+{
+ "p": 3,
+ "ps": 3,
+ "total": 5,
+ "groups": []
+}
--- /dev/null
+{
+ "p": 1,
+ "ps": 100,
+ "total": 5,
+ "groups": [
+ {"name": "admins", "description": "Admins", "membersCount": 1},
+ {"name": "customer1", "description": "Customer1", "membersCount": 0},
+ {"name": "customer2", "description": "Customer2", "membersCount": 4},
+ {"name": "customer3", "description": "Customer3", "membersCount": 0},
+ {"name": "users", "description": "Users", "membersCount": 5}
+ ]
+}
package org.sonar.core.user;
+import org.apache.ibatis.session.RowBounds;
+
import javax.annotation.CheckForNull;
import java.util.List;
void insert(GroupDto groupDto);
+ List<GroupDto> selectByQuery(String query, RowBounds rowBounds);
+
+ int countByQuery(String query);
}
package org.sonar.core.user;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
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 java.util.List;
-import java.util.Map;
-
public class GroupMembershipDao implements DaoComponent {
private final MyBatis mybatis;
return mapper(session).countGroups(params);
}
+ public Map<String, Integer> countUsersByGroups(final DbSession session, Collection<Long> groupIds) {
+ final Map<String, Integer> result = Maps.newHashMap();
+ DaoUtils.executeLargeInputs(groupIds, new Function<List<Long>, List<GroupUserCount>>() {
+ @Override
+ public List<GroupUserCount> apply(@Nonnull List<Long> input) {
+ List<GroupUserCount> userCounts = mapper(session).countUsersByGroup(input);
+ for (GroupUserCount count : userCounts) {
+ result.put(count.groupName(), count.userCount());
+ }
+ return userCounts;
+ }
+ });
+ return result;
+ }
+
@VisibleForTesting
List<GroupMembershipDto> selectGroups(GroupMembershipQuery query, Long userId) {
return selectGroups(query, userId, 0, Integer.MAX_VALUE);
private GroupMembershipMapper mapper(SqlSession session) {
return session.getMapper(GroupMembershipMapper.class);
}
-
}
*/
package org.sonar.core.user;
-import org.apache.ibatis.session.RowBounds;
-
import java.util.List;
import java.util.Map;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.RowBounds;
public interface GroupMembershipMapper {
List<GroupMembershipDto> selectGroups(Map<String, Object> params, RowBounds rowBounds);
int countGroups(Map<String, Object> params);
+
+ List<GroupUserCount> countUsersByGroup(@Param("groupIds") List<Long> groupIds);
}
--- /dev/null
+/*
+ * 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 GroupUserCount {
+
+ private String groupName;
+ private int userCount;
+
+ public String groupName() {
+ return groupName;
+ }
+
+ public int userCount() {
+ return userCount;
+ }
+}
VALUES (#{name}, #{description}, #{createdAt}, #{updatedAt})
</insert>
+ <select id="selectByQuery" parameterType="map" resultType="Group">
+ SELECT <include refid="groupColumns" />
+ FROM groups g
+ WHERE UPPER(g.name) LIKE #{query}
+ ORDER BY UPPER(g.name)
+ </select>
+
+ <select id="countByQuery" parameterType="map" resultType="int">
+ SELECT count(g.id)
+ FROM groups g
+ WHERE UPPER(g.name) LIKE #{query}
+ </select>
</mapper>
<include refid="commonClauses" />
</select>
+ <select id="countUsersByGroup" parameterType="long" resultType="org.sonar.core.user.GroupUserCount">
+ SELECT g.name as groupName, count(gu.user_id) as userCount
+ FROM groups g
+ LEFT JOIN groups_users gu ON gu.group_id=g.id
+ <where>
+ g.id in
+ <foreach collection="groupIds" open="(" close=")" item="id" separator=",">
+ #{id}
+ </foreach>
+ </where>
+ GROUP BY g.name
+ </select>
+
</mapper>
package org.sonar.core.user;
+import java.util.Arrays;
+import java.util.List;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.core.persistence.DbTester;
import org.sonar.test.DbTests;
-import java.util.List;
-
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
@Category(DbTests.class)
public class GroupMembershipDaoTest {
session.close();
}
}
+
+ @Test
+ public void count_users_by_group() {
+ dbTester.prepareDbUnit(getClass(), "shared_plus_empty_group.xml");
+ DbSession session = dbTester.myBatis().openSession(false);
+
+ try {
+ assertThat(dao.countUsersByGroups(session, Arrays.asList(100L, 101L, 102L, 103L))).containsOnly(
+ entry("sonar-users", 2), entry("sonar-reviewers", 1), entry("sonar-administrators", 1), entry("sonar-nobody", 0));
+ assertThat(dao.countUsersByGroups(session, Arrays.asList(100L, 103L))).containsOnly(
+ entry("sonar-administrators", 1), entry("sonar-nobody", 0));
+ } finally {
+ session.close();
+ }
+ }
}
--- /dev/null
+<dataset>
+
+ <groups id="100" name="sonar-administrators" description="System administrators"/>
+ <groups id="101" name="sonar-users" description="Any new users created will automatically join this group"/>
+ <groups id="102" name="sonar-reviewers" description="Reviewers"/>
+ <groups id="103" name="sonar-nobody" description="Nobody in this group"/>
+
+ <!-- user 200 is in all groups -->
+ <groups_users user_id="200" group_id="100"/>
+ <groups_users user_id="200" group_id="101"/>
+ <groups_users user_id="200" group_id="102"/>
+
+ <!-- user 201 is in users group -->
+ <groups_users user_id="201" group_id="101"/>
+
+</dataset>
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.SonarException;
-
-import javax.annotation.CheckForNull;
-
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.SonarException;
/**
* @since 4.2