From dd0fa0f97aece83d26772a839ead80dc20a5afad Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Mon, 25 Sep 2017 16:56:35 +0200 Subject: [PATCH] SONAR-1330 Search for groups allowed to edit single quality profile --- .../db/qualityprofile/GroupMembershipDto.java | 41 +++ .../qualityprofile/QProfileEditGroupsDao.java | 10 + .../QProfileEditGroupsMapper.java | 6 + .../db/qualityprofile/SearchGroupsQuery.java | 119 +++++++ .../QProfileEditGroupsMapper.xml | 55 +++ .../QProfileEditGroupsDaoTest.java | 140 ++++++++ .../qualityprofile/ws/SearchGroupsAction.java | 94 +++++- .../ws/search_groups-example.json | 12 +- .../ws/SearchGroupsActionTest.java | 319 +++++++++++++++++- .../QualityProfilesService.java | 15 + .../qualityprofile/SearchGroupsRequest.java | 127 +++++++ .../main/protobuf/ws-qualityprofiles.proto | 12 + .../QualityProfilesServiceTest.java | 27 ++ .../QualityProfilesEditTest.java | 90 +++++ 14 files changed, 1055 insertions(+), 12 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/GroupMembershipDto.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchGroupsQuery.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/SearchGroupsRequest.java 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 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 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 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} + + + + + + + + + + FROM groups g + LEFT JOIN qprofile_edit_groups qeg ON qeg.group_id=g.id AND qeg.qprofile_uuid=#{query.qProfileUuid, jdbcType=VARCHAR} + + AND g.organization_uuid=#{query.organizationUuid,jdbcType=VARCHAR} + + + AND qeg.uuid IS NOT NULL + + + AND qeg.uuid IS NULL + + + + AND (LOWER(g.name) LIKE #{query.querySqlLowercase} ESCAPE '/') + + + + 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 { @@ -56,6 +62,140 @@ public class QProfileEditGroupsDaoTest { assertThat(underTest.exists(db.getSession(), anotherProfile, anotherGroup)).isFalse(); } + @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() 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 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 groupMemberships = dbClient.qProfileEditGroupsDao().selectByQuery(dbSession, query, + forPage(wsRequest.getPage()).andSize(wsRequest.getPageSize())); + Map 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(); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java index ed65f9d6dab..fe2a4171b73 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesService.java @@ -23,6 +23,7 @@ import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.QualityProfiles; import org.sonarqube.ws.QualityProfiles.CopyWsResponse; import org.sonarqube.ws.QualityProfiles.CreateWsResponse; +import org.sonarqube.ws.QualityProfiles.SearchGroupsResponse; import org.sonarqube.ws.QualityProfiles.SearchUsersResponse; import org.sonarqube.ws.QualityProfiles.SearchWsResponse; import org.sonarqube.ws.QualityProfiles.ShowResponse; @@ -49,6 +50,7 @@ import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters. import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_REMOVE_USER; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_RESTORE; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SEARCH; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SEARCH_GROUPS; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SEARCH_USERS; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SET_DEFAULT; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SHOW; @@ -228,4 +230,17 @@ public class QualityProfilesService extends BaseService { .setParam(PARAM_LANGUAGE, request.getLanguage()) .setParam(PARAM_GROUP, request.getGroup())); } + + public SearchGroupsResponse searchGroups(SearchGroupsRequest request) { + return call( + new GetRequest(path(ACTION_SEARCH_GROUPS)) + .setParam(PARAM_ORGANIZATION, request.getOrganization()) + .setParam(PARAM_QUALITY_PROFILE, request.getQualityProfile()) + .setParam(PARAM_LANGUAGE, request.getLanguage()) + .setParam(TEXT_QUERY, request.getQuery()) + .setParam(SELECTED, request.getSelected()) + .setParam(PAGE, request.getPage()) + .setParam(PAGE_SIZE, request.getPageSize()), + SearchGroupsResponse.parser()); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/SearchGroupsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/SearchGroupsRequest.java new file mode 100644 index 00000000000..bf515ef724b --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/qualityprofile/SearchGroupsRequest.java @@ -0,0 +1,127 @@ +/* + * 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.sonarqube.ws.client.qualityprofile; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class SearchGroupsRequest { + + private String organization; + private String qualityProfile; + private String language; + private String selected; + private String query; + private Integer page; + private Integer pageSize; + + private SearchGroupsRequest(Builder builder) { + this.organization = builder.organization; + this.qualityProfile = builder.qualityProfile; + this.language = builder.language; + this.selected = builder.selected; + this.query = builder.query; + this.page = builder.page; + this.pageSize = builder.pageSize; + } + + @CheckForNull + public String getOrganization() { + return organization; + } + + public String getQualityProfile() { + return qualityProfile; + } + + public String getLanguage() { + return language; + } + + @CheckForNull + public String getQuery() { + return query; + } + + public String getSelected() { + return selected; + } + + public Integer getPage() { + return page; + } + + public Integer getPageSize() { + return pageSize; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String organization; + private String qualityProfile; + private String language; + private String selected; + private String query; + private Integer page; + private Integer pageSize; + + public Builder setOrganization(@Nullable String organization) { + this.organization = organization; + return this; + } + + public Builder setQualityProfile(String qualityProfile) { + this.qualityProfile = qualityProfile; + return this; + } + + public Builder setLanguage(String language) { + this.language = language; + return this; + } + + public Builder setSelected(String selected) { + this.selected = selected; + return this; + } + + public Builder setQuery(@Nullable String query) { + this.query = query; + return this; + } + + public Builder setPage(Integer page) { + this.page = page; + return this; + } + + public Builder setPageSize(Integer pageSize) { + this.pageSize = pageSize; + return this; + } + + public SearchGroupsRequest build() { + return new SearchGroupsRequest(this); + } + } +} diff --git a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto index 2300e8baa77..cd3143aa43a 100644 --- a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto +++ b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto @@ -152,3 +152,15 @@ message SearchUsersResponse { optional bool selected = 4; } } + +// WS api/qualityprofiles/search_groups +message SearchGroupsResponse { + optional sonarqube.ws.commons.Paging paging = 1; + repeated Group groups = 2; + + message Group { + optional string name = 1; + optional string description = 2; + optional bool selected = 3; + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesServiceTest.java index a3dd96ee3c0..8d90866470c 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/qualityprofile/QualityProfilesServiceTest.java @@ -306,4 +306,31 @@ public class QualityProfilesServiceTest { .hasParam(PARAM_GROUP, "users") .andNoOtherParam(); } + + @Test + public void search_groups() { + underTest.searchGroups(SearchGroupsRequest.builder() + .setOrganization("O1") + .setQualityProfile("P1") + .setLanguage("Xoo") + .setQuery("users") + .setSelected("all") + .setPage(5) + .setPageSize(50) + .build() + ); + GetRequest getRequest = serviceTester.getGetRequest(); + + assertThat(serviceTester.getGetParser()).isSameAs(QualityProfiles.SearchGroupsResponse.parser()); + serviceTester.assertThat(getRequest) + .hasPath("search_groups") + .hasParam(PARAM_ORGANIZATION, "O1") + .hasParam(PARAM_QUALITY_PROFILE, "P1") + .hasParam(PARAM_LANGUAGE, "Xoo") + .hasParam(TEXT_QUERY, "users") + .hasParam(SELECTED, "all") + .hasParam(PAGE, 5) + .hasParam(PAGE_SIZE, 50) + .andNoOtherParam(); + } } diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesEditTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesEditTest.java index cb56d80ab8b..4d4764ef87b 100644 --- a/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesEditTest.java +++ b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesEditTest.java @@ -28,14 +28,20 @@ import org.sonarqube.tests.Tester; import org.sonarqube.ws.Common; import org.sonarqube.ws.Organizations.Organization; import org.sonarqube.ws.QualityProfiles.CreateWsResponse; +import org.sonarqube.ws.QualityProfiles.SearchGroupsResponse; import org.sonarqube.ws.QualityProfiles.SearchUsersResponse; +import org.sonarqube.ws.WsUserGroups; import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.qualityprofile.AddGroupRequest; import org.sonarqube.ws.client.qualityprofile.AddUserRequest; +import org.sonarqube.ws.client.qualityprofile.RemoveGroupRequest; import org.sonarqube.ws.client.qualityprofile.RemoveUserRequest; +import org.sonarqube.ws.client.qualityprofile.SearchGroupsRequest; import org.sonarqube.ws.client.qualityprofile.SearchUsersRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.sonarqube.ws.QualityProfiles.SearchGroupsResponse.Group; public class QualityProfilesEditTest { @@ -123,4 +129,88 @@ public class QualityProfilesEditTest { .extracting(SearchUsersResponse.User::getLogin) .isEmpty(); } + + @Test + public void search_groups_allowed_to_edit_a_profile() { + Organization organization = tester.organizations().generate(); + WsUserGroups.Group group1 = tester.groups().generate(organization); + WsUserGroups.Group group2 = tester.groups().generate(organization); + WsUserGroups.Group group3 = tester.groups().generate(organization); + CreateWsResponse.QualityProfile xooProfile = tester.qProfiles().createXooProfile(organization); + tester.qProfiles().service().addGroup(AddGroupRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setGroup(group1.getName()) + .build()); + tester.qProfiles().service().addGroup(AddGroupRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setGroup(group2.getName()) + .build()); + + SearchGroupsResponse groups = tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .build()); + + assertThat(groups.getGroupsList()).extracting(Group::getName, Group::getDescription, Group::getSelected) + .containsExactlyInAnyOrder( + tuple(group1.getName(), group1.getDescription(), true), + tuple(group2.getName(), group2.getDescription(), true)); + assertThat(groups.getPaging()).extracting(Common.Paging::getPageIndex, Common.Paging::getPageSize, Common.Paging::getTotal) + .containsExactlyInAnyOrder(1, 25, 2); + } + + @Test + public void add_and_remove_group() { + Organization organization = tester.organizations().generate(); + WsUserGroups.Group group1 = tester.groups().generate(organization); + WsUserGroups.Group group2 = tester.groups().generate(organization); + CreateWsResponse.QualityProfile xooProfile = tester.qProfiles().createXooProfile(organization); + + // No group added + assertThat(tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setSelected("selected") + .build()).getGroupsList()) + .extracting(Group::getName) + .isEmpty(); + + // Add user 1 + tester.qProfiles().service().addGroup(AddGroupRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setGroup(group1.getName()) + .build()); + assertThat(tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setSelected("selected") + .build()).getGroupsList()) + .extracting(Group::getName) + .containsExactlyInAnyOrder(group1.getName()); + + // Remove user 1 + tester.qProfiles().service().removeGroup(RemoveGroupRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setGroup(group1.getName()) + .build()); + assertThat(tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder() + .setOrganization(organization.getKey()) + .setQualityProfile(xooProfile.getName()) + .setLanguage(xooProfile.getLanguage()) + .setSelected("selected") + .build()).getGroupsList()) + .extracting(Group::getName) + .isEmpty(); + } } -- 2.39.5