diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-09-25 16:56:35 +0200 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-10-02 17:18:15 +0200 |
commit | dd0fa0f97aece83d26772a839ead80dc20a5afad (patch) | |
tree | a3ac360a4eadf108d9d23533f13a8fe1dc60e842 /server | |
parent | bb0bd260f3742c010b9e7034ea5d6947e694a130 (diff) | |
download | sonarqube-dd0fa0f97aece83d26772a839ead80dc20a5afad.tar.gz sonarqube-dd0fa0f97aece83d26772a839ead80dc20a5afad.zip |
SONAR-1330 Search for groups allowed to edit single quality profile
Diffstat (limited to 'server')
9 files changed, 784 insertions, 12 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/GroupMembershipDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/GroupMembershipDto.java new file mode 100644 index 00000000000..75bd679ad75 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/GroupMembershipDto.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.db.qualityprofile; + +public class GroupMembershipDto { + + private int groupId; + // Set by MyBatis + private String uuid; + + public int getGroupId() { + return groupId; + } + + public GroupMembershipDto setGroupId(int groupId) { + this.groupId = groupId; + return this; + } + + public boolean isSelected() { + return uuid != null; + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsDao.java index b690eecdb09..bd023bec7dc 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsDao.java @@ -19,9 +19,11 @@ */ package org.sonar.db.qualityprofile; +import java.util.List; import org.sonar.api.utils.System2; import org.sonar.db.Dao; import org.sonar.db.DbSession; +import org.sonar.db.Pagination; import org.sonar.db.user.GroupDto; public class QProfileEditGroupsDao implements Dao { @@ -36,6 +38,14 @@ public class QProfileEditGroupsDao implements Dao { return mapper(dbSession).selectByQProfileAndGroup(profile.getKee(), group.getId()) != null; } + public int countByQuery(DbSession dbSession, SearchGroupsQuery query){ + return mapper(dbSession).countByQuery(query); + } + + public List<GroupMembershipDto> selectByQuery(DbSession dbSession, SearchGroupsQuery query, Pagination pagination){ + return mapper(dbSession).selectByQuery(query, pagination); + } + public void insert(DbSession dbSession, QProfileEditGroupsDto dto) { mapper(dbSession).insert(dto, system2.now()); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.java index d4eb535299a..dde92469d0f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.java @@ -19,12 +19,18 @@ */ package org.sonar.db.qualityprofile; +import java.util.List; import org.apache.ibatis.annotations.Param; +import org.sonar.db.Pagination; public interface QProfileEditGroupsMapper { QProfileEditGroupsDto selectByQProfileAndGroup(@Param("qProfileUuid") String qProfileUuid, @Param("groupId") int groupId); + int countByQuery(@Param("query") SearchGroupsQuery query); + + List<GroupMembershipDto> selectByQuery(@Param("query") SearchGroupsQuery query, @Param("pagination") Pagination pagination); + void insert(@Param("dto") QProfileEditGroupsDto dto, @Param("now") long now); void delete(@Param("qProfileUuid") String qProfileUuid, @Param("groupId") int groupId); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchGroupsQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchGroupsQuery.java new file mode 100644 index 00000000000..74b83a36fd0 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchGroupsQuery.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.db.qualityprofile; + +import com.google.common.collect.ImmutableSet; +import java.util.Locale; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.db.organization.OrganizationDto; + +import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; +import static org.sonar.db.DaoDatabaseUtils.buildLikeValue; +import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER; + +public class SearchGroupsQuery { + + public static final String ANY = "ANY"; + public static final String IN = "IN"; + public static final String OUT = "OUT"; + public static final Set<String> AVAILABLE_MEMBERSHIPS = ImmutableSet.of(ANY, IN, OUT); + + private final String organizationUuid; + private final String qProfileUuid; + private final String query; + private final String membership; + + // for internal use in MyBatis + final String querySqlLowercase; + + private SearchGroupsQuery(Builder builder) { + this.organizationUuid = builder.organization.getUuid(); + this.qProfileUuid = builder.profile.getKee(); + this.query = builder.query; + this.membership = builder.membership; + this.querySqlLowercase = query == null ? null : buildLikeValue(query, BEFORE_AND_AFTER).toLowerCase(Locale.ENGLISH); + } + + public String getOrganizationUuid() { + return organizationUuid; + } + + public String getQProfileUuid() { + return qProfileUuid; + } + + public String getMembership() { + return membership; + } + + @CheckForNull + public String getQuery() { + return query; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private OrganizationDto organization; + private QProfileDto profile; + private String query; + private String membership; + + private Builder() { + } + + public Builder setOrganization(OrganizationDto organization) { + this.organization = organization; + return this; + } + + public Builder setProfile(QProfileDto profile) { + this.profile = profile; + return this; + } + + public Builder setMembership(@Nullable String membership) { + this.membership = membership; + return this; + } + + public Builder setQuery(@Nullable String s) { + this.query = StringUtils.defaultIfBlank(s, null); + return this; + } + + private void initMembership() { + membership = firstNonNull(membership, ANY); + checkArgument(AVAILABLE_MEMBERSHIPS.contains(membership), + "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIPS); + } + + public SearchGroupsQuery build() { + initMembership(); + return new SearchGroupsQuery(this); + } + } +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml index 9be145a233e..ee1a1fc6adb 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml @@ -18,6 +18,61 @@ and qeg.qprofile_uuid = #{qProfileUuid, jdbcType=VARCHAR} </select> + <select id="countByQuery" resultType="int"> + select count(g.id) + <include refid="sqlSelectByQuery" /> + </select> + + <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto"> + SELECT g.id as groupId, g.name as name, qeg.uuid as uuid + <include refid="sqlSelectByQuery"/> + ORDER BY g.name ASC + LIMIT #{pagination.pageSize,jdbcType=INTEGER} + OFFSET #{pagination.offset,jdbcType=INTEGER} + </select> + + <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto" databaseId="mssql"> + select * from ( + select row_number() over(order by g.name asc) as number, + g.id as groupId, g.name as name, qeg.uuid as uuid + <include refid="sqlSelectByQuery" /> + ) as query + where + query.number between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER} + order by query.name asc + </select> + + <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto" databaseId="oracle"> + select * from ( + select rownum as rn, t.* from ( + select g.id as groupId, g.name as name, qeg.uuid as uuid + <include refid="sqlSelectByQuery"/> + order by g.name ASC + ) t + ) t + where + t.rn between #{pagination.startRowNumber,jdbcType=INTEGER} and #{pagination.endRowNumber,jdbcType=INTEGER} + </select> + + <sql id="sqlSelectByQuery"> + FROM groups g + LEFT JOIN qprofile_edit_groups qeg ON qeg.group_id=g.id AND qeg.qprofile_uuid=#{query.qProfileUuid, jdbcType=VARCHAR} + <where> + AND g.organization_uuid=#{query.organizationUuid,jdbcType=VARCHAR} + <choose> + <when test="query.getMembership() == 'IN'"> + AND qeg.uuid IS NOT NULL + </when> + <when test="query.getMembership() == 'OUT'"> + AND qeg.uuid IS NULL + </when> + </choose> + <if test="query.getQuery() != null"> + AND (LOWER(g.name) LIKE #{query.querySqlLowercase} ESCAPE '/') + </if> + </where> + </sql> + <insert id="insert" useGeneratedKeys="false" parameterType="map"> insert into qprofile_edit_groups( uuid, diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java index d19ccf125ca..db5204ecb98 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java @@ -24,11 +24,17 @@ import org.junit.Test; import org.sonar.api.utils.System2; import org.sonar.api.utils.internal.TestSystem2; import org.sonar.db.DbTester; +import org.sonar.db.Pagination; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.user.GroupDto; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.ANY; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.IN; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.OUT; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.builder; public class QProfileEditGroupsDaoTest { @@ -57,6 +63,140 @@ public class QProfileEditGroupsDaoTest { } @Test + public void countByQuery() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization); + GroupDto group1 = db.users().insertGroup(organization); + GroupDto group2 = db.users().insertGroup(organization); + GroupDto group3 = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + + assertThat(underTest.countByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(ANY).build())) + .isEqualTo(3); + + assertThat(underTest.countByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(IN).build())) + .isEqualTo(2); + + assertThat(underTest.countByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(OUT).build())) + .isEqualTo(1); + } + + @Test + public void selectByQuery() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization); + GroupDto group1 = db.users().insertGroup(organization); + GroupDto group2 = db.users().insertGroup(organization); + GroupDto group3 = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(ANY).build(), Pagination.all())) + .extracting(GroupMembershipDto::getGroupId, GroupMembershipDto::isSelected) + .containsExactlyInAnyOrder( + tuple(group1.getId(), true), + tuple(group2.getId(), true), + tuple(group3.getId(), false)); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(IN).build(), + Pagination.all())) + .extracting(GroupMembershipDto::getGroupId, GroupMembershipDto::isSelected) + .containsExactlyInAnyOrder(tuple(group1.getId(), true), tuple(group2.getId(), true)); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(OUT).build(), + Pagination.all())) + .extracting(GroupMembershipDto::getGroupId, GroupMembershipDto::isSelected) + .containsExactlyInAnyOrder(tuple(group3.getId(), false)); + } + + @Test + public void selectByQuery_search_by_name() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization); + GroupDto group1 = db.users().insertGroup(organization, "sonar-users-project"); + GroupDto group2 = db.users().insertGroup(organization, "sonar-users-qprofile"); + GroupDto group3 = db.users().insertGroup(organization, "sonar-admin"); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + db.qualityProfiles().addGroupPermission(profile, group3); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(IN) + .setQuery("project").build(), + Pagination.all())) + .extracting(GroupMembershipDto::getGroupId) + .containsExactlyInAnyOrder(group1.getId()); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(IN) + .setQuery("UserS").build(), + Pagination.all())) + .extracting(GroupMembershipDto::getGroupId) + .containsExactlyInAnyOrder(group1.getId(), group2.getId()); + } + + @Test + public void selectByQuery_with_paging() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization); + GroupDto group1 = db.users().insertGroup(organization, "group1"); + GroupDto group2 = db.users().insertGroup(organization, "group2"); + GroupDto group3 = db.users().insertGroup(organization, "group3"); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(ANY) + .build(), + Pagination.forPage(1).andSize(1))) + .extracting(GroupMembershipDto::getGroupId) + .containsExactly(group1.getId()); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(ANY) + .build(), + Pagination.forPage(3).andSize(1))) + .extracting(GroupMembershipDto::getGroupId) + .containsExactly(group3.getId()); + + assertThat(underTest.selectByQuery(db.getSession(), builder() + .setOrganization(organization) + .setProfile(profile) + .setMembership(ANY) + .build(), + Pagination.forPage(1).andSize(10))) + .extracting(GroupMembershipDto::getGroupId) + .containsExactly(group1.getId(), group2.getId(), group3.getId()); + } + + @Test public void insert() { underTest.insert(db.getSession(), new QProfileEditGroupsDto() .setUuid("ABCD") diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchGroupsAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchGroupsAction.java index 0803b39e2b8..dffbefe4e3e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchGroupsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchGroupsAction.java @@ -19,25 +19,60 @@ */ package org.sonar.server.qualityprofile.ws; +import com.google.common.collect.ImmutableMap; import java.util.Arrays; -import org.apache.commons.io.IOUtils; +import java.util.List; +import java.util.Map; import org.sonar.api.resources.Language; import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.GroupMembershipDto; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.qualityprofile.SearchGroupsQuery; +import org.sonar.db.user.GroupDto; +import org.sonarqube.ws.Common; +import org.sonarqube.ws.QualityProfiles; +import org.sonarqube.ws.client.qualityprofile.SearchUsersRequest; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.SELECTED; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.api.server.ws.WebService.SelectionMode.ALL; +import static org.sonar.api.server.ws.WebService.SelectionMode.DESELECTED; +import static org.sonar.api.server.ws.WebService.SelectionMode.fromParam; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.core.util.stream.MoreCollectors.toList; import static org.sonar.core.util.stream.MoreCollectors.toSet; +import static org.sonar.db.Pagination.forPage; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.ANY; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.IN; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.OUT; +import static org.sonar.db.qualityprofile.SearchGroupsQuery.builder; import static org.sonar.server.qualityprofile.ws.QProfileWsSupport.createOrganizationParam; +import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SEARCH_GROUPS; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_ORGANIZATION; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_QUALITY_PROFILE; public class SearchGroupsAction implements QProfileWsAction { + private static final Map<WebService.SelectionMode, String> MEMBERSHIP = ImmutableMap.of(WebService.SelectionMode.SELECTED, IN, DESELECTED, OUT, ALL, ANY); + + private final DbClient dbClient; + private final QProfileWsSupport wsSupport; private final Languages languages; - public SearchGroupsAction(Languages languages) { + public SearchGroupsAction(DbClient dbClient, QProfileWsSupport wsSupport, Languages languages) { + this.dbClient = dbClient; + this.wsSupport = wsSupport; this.languages = languages; } @@ -71,6 +106,59 @@ public class SearchGroupsAction implements QProfileWsAction { @Override public void handle(Request request, Response response) throws Exception { - IOUtils.write(IOUtils.toString(getClass().getResource("search_groups-example.json")), response.stream().output()); + SearchUsersRequest wsRequest = buildRequest(request); + try (DbSession dbSession = dbClient.openSession(false)) { + OrganizationDto organization = wsSupport.getOrganizationByKey(dbSession, wsRequest.getOrganization()); + QProfileDto profile = wsSupport.getProfile(dbSession, organization, wsRequest.getQualityProfile(), wsRequest.getLanguage()); + wsSupport.checkCanEdit(dbSession, profile); + + SearchGroupsQuery query = builder() + .setOrganization(organization) + .setProfile(profile) + .setQuery(wsRequest.getQuery()) + .setMembership(MEMBERSHIP.get(fromParam(wsRequest.getSelected()))) + .build(); + int total = dbClient.qProfileEditGroupsDao().countByQuery(dbSession, query); + List<GroupMembershipDto> groupMemberships = dbClient.qProfileEditGroupsDao().selectByQuery(dbSession, query, + forPage(wsRequest.getPage()).andSize(wsRequest.getPageSize())); + Map<Integer, GroupDto> groupsById = dbClient.groupDao().selectByIds(dbSession, + groupMemberships.stream().map(GroupMembershipDto::getGroupId).collect(MoreCollectors.toList())) + .stream() + .collect(MoreCollectors.uniqueIndex(GroupDto::getId)); + writeProtobuf( + QualityProfiles.SearchGroupsResponse.newBuilder() + .addAllGroups(groupMemberships.stream() + .map(groupsMembership -> toGroup(groupsById.get(groupsMembership.getGroupId()), groupsMembership.isSelected())) + .collect(toList())) + .setPaging(buildPaging(wsRequest, total)).build(), request, response); + } + } + + private static SearchUsersRequest buildRequest(Request request) { + return SearchUsersRequest.builder() + .setOrganization(request.param(PARAM_ORGANIZATION)) + .setQualityProfile(request.mandatoryParam(PARAM_QUALITY_PROFILE)) + .setLanguage(request.mandatoryParam(PARAM_LANGUAGE)) + .setQuery(request.param(TEXT_QUERY)) + .setSelected(request.mandatoryParam(SELECTED)) + .setPage(request.mandatoryParamAsInt(PAGE)) + .setPageSize(request.mandatoryParamAsInt(PAGE_SIZE)) + .build(); + } + + private static QualityProfiles.SearchGroupsResponse.Group toGroup(GroupDto group, boolean isSelected) { + QualityProfiles.SearchGroupsResponse.Group.Builder builder = QualityProfiles.SearchGroupsResponse.Group.newBuilder() + .setName(group.getName()) + .setSelected(isSelected); + setNullable(group.getDescription(), builder::setDescription); + return builder.build(); + } + + private static Common.Paging buildPaging(SearchUsersRequest wsRequest, int total) { + return Common.Paging.newBuilder() + .setPageIndex(wsRequest.getPage()) + .setPageSize(wsRequest.getPageSize()) + .setTotal(total) + .build(); } } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search_groups-example.json b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search_groups-example.json index 084dfd4bb4e..a8fdbcf1066 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search_groups-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search_groups-example.json @@ -1,4 +1,9 @@ { + "paging": { + "pageSize": 25, + "total": 2, + "pageIndex": 1 + }, "groups": [ { "name": "users", @@ -10,10 +15,5 @@ "description": "Administrators", "selected": false } - ], - "paging": { - "pageSize": 100, - "total": 2, - "pageIndex": 1 - } + ] } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchGroupsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchGroupsActionTest.java index fa2dfe68bc7..de50235bc56 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchGroupsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchGroupsActionTest.java @@ -21,25 +21,54 @@ package org.sonar.server.qualityprofile.ws; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.permission.OrganizationPermission; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.language.LanguageTesting; +import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.QualityProfiles.SearchGroupsResponse; +import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.SELECTED; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; +import static org.sonar.db.user.GroupTesting.newGroupDto; import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.MediaTypes.JSON; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_ORGANIZATION; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_QUALITY_PROFILE; public class SearchGroupsActionTest { private static final String XOO = "xoo"; - private static final Languages LANGUAGES = LanguageTesting.newLanguages(XOO); + private static final String FOO = "foo"; + private static final Languages LANGUAGES = LanguageTesting.newLanguages(XOO, FOO); @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(); + + private QProfileWsSupport wsSupport = new QProfileWsSupport(db.getDbClient(), userSession, TestDefaultOrganizationProvider.from(db)); - private WsActionTester ws = new WsActionTester(new SearchGroupsAction(LANGUAGES)); + private WsActionTester ws = new WsActionTester(new SearchGroupsAction(db.getDbClient(), wsSupport, LANGUAGES)); @Test public void test_definition() { @@ -53,8 +82,292 @@ public class SearchGroupsActionTest { @Test public void test_example() { - String result = ws.newRequest().setMediaType(JSON).execute().getInput(); + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group1 = db.users().insertGroup(newGroupDto().setName("users").setDescription("Users").setOrganizationUuid(organization.getUuid())); + GroupDto group2 = db.users().insertGroup(newGroupDto().setName("administrators").setDescription("Administrators").setOrganizationUuid(organization.getUuid())); + db.qualityProfiles().addGroupPermission(profile, group1); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + String result = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .setMediaType(JSON) + .execute() + .getInput(); assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result); } + + @Test + public void search_all_groups() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group1 = db.users().insertGroup(organization); + GroupDto group2 = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group1); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName, SearchGroupsResponse.Group::getDescription, SearchGroupsResponse.Group::getSelected) + .containsExactlyInAnyOrder( + tuple(group1.getName(), group1.getDescription(), true), + tuple(group2.getName(), group2.getDescription(), false)); + } + + @Test + public void search_selected_groups() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group1 = db.users().insertGroup(organization); + GroupDto group2 = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group1); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "selected") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName, SearchGroupsResponse.Group::getDescription, SearchGroupsResponse.Group::getSelected) + .containsExactlyInAnyOrder( + tuple(group1.getName(), group1.getDescription(), true)); + } + + @Test + public void search_deselected_groups() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group1 = db.users().insertGroup(organization); + GroupDto group2 = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group1); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "deselected") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName, SearchGroupsResponse.Group::getDescription, SearchGroupsResponse.Group::getSelected) + .containsExactlyInAnyOrder( + tuple(group2.getName(), group2.getDescription(), false)); + } + + @Test + public void search_by_name() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group1 = db.users().insertGroup(organization, "sonar-users-project"); + GroupDto group2 = db.users().insertGroup(organization, "sonar-users-qprofile"); + GroupDto group3 = db.users().insertGroup(organization, "sonar-admin"); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + db.qualityProfiles().addGroupPermission(profile, group3); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(TEXT_QUERY, "UsErS") + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName) + .containsExactlyInAnyOrder(group1.getName(), group2.getName()); + } + + @Test + public void group_without_description() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group = db.users().insertGroup(newGroupDto().setDescription(null).setOrganizationUuid(organization.getUuid())); + db.qualityProfiles().addGroupPermission(profile, group); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName, SearchGroupsResponse.Group::hasDescription) + .containsExactlyInAnyOrder(tuple(group.getName(), false)); + } + + @Test + public void paging_search() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group3 = db.users().insertGroup(organization, "group3"); + GroupDto group1 = db.users().insertGroup(organization, "group1"); + GroupDto group2 = db.users().insertGroup(organization, "group2"); + db.qualityProfiles().addGroupPermission(profile, group1); + db.qualityProfiles().addGroupPermission(profile, group2); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + assertThat(ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .setParam(PAGE, "1") + .setParam(PAGE_SIZE, "1") + .executeProtobuf(SearchGroupsResponse.class).getGroupsList()) + .extracting(SearchGroupsResponse.Group::getName) + .containsExactly(group1.getName()); + + assertThat(ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .setParam(PAGE, "3") + .setParam(PAGE_SIZE, "1") + .executeProtobuf(SearchGroupsResponse.class).getGroupsList()) + .extracting(SearchGroupsResponse.Group::getName) + .containsExactly(group3.getName()); + + assertThat(ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .setParam(PAGE, "1") + .setParam(PAGE_SIZE, "10") + .executeProtobuf(SearchGroupsResponse.class).getGroupsList()) + .extracting(SearchGroupsResponse.Group::getName) + .containsExactly(group1.getName(), group2.getName(), group3.getName()); + } + + @Test + public void uses_default_organization_when_no_organization() { + OrganizationDto organization = db.getDefaultOrganization(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName).containsExactlyInAnyOrder(group.getName()); + } + + @Test + public void qp_administers_can_search_groups() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group); + userSession.logIn().addPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, organization); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName).containsExactlyInAnyOrder(group.getName()); + } + + @Test + public void qp_editors_can_search_groups() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + GroupDto group = db.users().insertGroup(organization); + db.qualityProfiles().addGroupPermission(profile, group); + UserDto userAllowedToEditProfile = db.users().insertUser(); + db.qualityProfiles().addUserPermission(profile, userAllowedToEditProfile); + userSession.logIn(userAllowedToEditProfile); + + SearchGroupsResponse response = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(SELECTED, "all") + .executeProtobuf(SearchGroupsResponse.class); + + assertThat(response.getGroupsList()).extracting(SearchGroupsResponse.Group::getName).containsExactlyInAnyOrder(group.getName()); + } + + @Test + public void fail_when_qprofile_does_not_exist() { + OrganizationDto organization = db.organizations().insert(); + userSession.logIn().addPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, organization); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(format("Quality Profile for language 'xoo' and name 'unknown' does not exist in organization '%s'", organization.getKey())); + + ws.newRequest() + .setParam(PARAM_QUALITY_PROFILE, "unknown") + .setParam(PARAM_LANGUAGE, XOO) + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .execute(); + } + + @Test + public void fail_when_qprofile_does_not_belong_to_organization() { + OrganizationDto organization = db.organizations().insert(); + OrganizationDto anotherOrganization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(anotherOrganization, p -> p.setLanguage(XOO)); + userSession.logIn().addPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, organization); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(format("Quality Profile for language 'xoo' and name '%s' does not exist in organization '%s'", profile.getName(), organization.getKey())); + + ws.newRequest() + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .execute(); + } + + @Test + public void fail_when_wrong_language() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES, organization); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage(format("Quality Profile for language 'foo' and name '%s' does not exist in organization '%s'", profile.getName(), organization.getKey())); + + ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, FOO) + .executeProtobuf(SearchGroupsResponse.class); + } + + @Test + public void fail_when_not_enough_permission() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO)); + userSession.logIn(db.users().insertUser()).addPermission(OrganizationPermission.ADMINISTER_QUALITY_GATES, organization); + + expectedException.expect(ForbiddenException.class); + + ws.newRequest() + .setParam(PARAM_QUALITY_PROFILE, profile.getName()) + .setParam(PARAM_LANGUAGE, XOO) + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .execute(); + } } |