]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15441 - QG Groups Search Endpoint
authorBelen Pruvost <belen.pruvost@sonarsource.com>
Fri, 15 Oct 2021 07:13:01 +0000 (09:13 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 22 Oct 2021 20:03:27 +0000 (20:03 +0000)
27 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateGroupPermissionsDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/QualityGateGroupPermissionsMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/SearchQualityGateGroupsQuery.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/GroupMembershipDto.java [deleted file]
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchGroupsQuery.java [deleted file]
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchQualityProfileGroupsQuery.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupMembershipDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupsQuery.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/QualityGateGroupPermissionsMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualitygate/QualityGateGroupPermissionsDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateWsModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGatesWsParameters.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchGroupsAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchQualityGateUsersRequest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchGroupsAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchQualityProfileUsersRequest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersRequest.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchUsersRequest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search_groups-example.json [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateWsModuleTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/SearchGroupsActionTest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-qualitygates.proto

index eecb9287f33d6374d4810513be0417ebbaffb2b0..76b901601a7a4cd006c9fd82c2ed0a0bcc41ce36 100644 (file)
 package org.sonar.db.qualitygate;
 
 import java.util.Collection;
+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;
+import org.sonar.db.user.SearchGroupMembershipDto;
+import org.sonar.db.user.SearchGroupsQuery;
 
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
@@ -56,4 +60,12 @@ public class QualityGateGroupPermissionsDao implements Dao {
   private static QualityGateGroupPermissionsMapper mapper(DbSession dbSession) {
     return dbSession.getMapper(QualityGateGroupPermissionsMapper.class);
   }
+
+  public List<SearchGroupMembershipDto> selectByQuery(DbSession dbSession, SearchGroupsQuery query, Pagination pagination) {
+    return mapper(dbSession).selectByQuery(query, pagination);
+  }
+
+  public int countByQuery(DbSession dbSession, SearchGroupsQuery query) {
+    return mapper(dbSession).countByQuery(query);
+  }
 }
index 1c46b5b1385c198acd368b4528adfa6fa8a2a72b..b7c23e1fa993591a1c509f1e955dcb9a7d0560d6 100644 (file)
@@ -21,6 +21,9 @@ package org.sonar.db.qualitygate;
 
 import java.util.List;
 import org.apache.ibatis.annotations.Param;
+import org.sonar.db.Pagination;
+import org.sonar.db.user.SearchGroupMembershipDto;
+import org.sonar.db.user.SearchGroupsQuery;
 
 public interface QualityGateGroupPermissionsMapper {
 
@@ -29,4 +32,8 @@ public interface QualityGateGroupPermissionsMapper {
   List<QualityGateGroupPermissionsDto> selectByQualityGateAndGroups(@Param("qualityGateUuid") String qualityGateUuid, @Param("groupUuids") List<String> groupUuids);
 
   void insert(@Param("dto") QualityGateGroupPermissionsDto dto, @Param("now") long now);
+
+  List<SearchGroupMembershipDto> selectByQuery(@Param("query") SearchGroupsQuery query, @Param("pagination") Pagination pagination);
+
+  int countByQuery(@Param("query") SearchGroupsQuery query);
 }
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/SearchQualityGateGroupsQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/SearchQualityGateGroupsQuery.java
new file mode 100644 (file)
index 0000000..7bfeef9
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.qualitygate;
+
+import java.util.Locale;
+import org.sonar.db.user.SearchGroupsQuery;
+
+import static org.sonar.db.DaoUtils.buildLikeValue;
+import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
+
+public class SearchQualityGateGroupsQuery extends SearchGroupsQuery {
+
+  private final String qualityGateUuid;
+
+  public SearchQualityGateGroupsQuery(Builder builder) {
+    this.qualityGateUuid = builder.qualityGate.getUuid();
+    this.query = builder.getQuery();
+    this.membership = builder.getMembership();
+    this.querySqlLowercase = query == null ? null : buildLikeValue(query, BEFORE_AND_AFTER).toLowerCase(Locale.ENGLISH);
+  }
+
+  public String getQualityGateUuid() {
+    return qualityGateUuid;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends SearchGroupsQuery.Builder<Builder> {
+    private QualityGateDto qualityGate;
+
+    public Builder setQualityGate(QualityGateDto qualityGate) {
+      this.qualityGate = qualityGate;
+      return this;
+    }
+
+    public SearchQualityGateGroupsQuery build() {
+      initMembership();
+      return new SearchQualityGateGroupsQuery(this);
+    }
+  }
+}
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
deleted file mode 100644 (file)
index db299a2..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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 String groupUuid;
-  // Set by MyBatis
-  private String uuid;
-
-  public String getGroupUuid() {
-    return groupUuid;
-  }
-
-  public GroupMembershipDto setGroupUuid(String groupUuid) {
-    this.groupUuid = groupUuid;
-    return this;
-  }
-
-  public boolean isSelected() {
-    return uuid != null;
-  }
-
-}
index ce947215e1805bd1f791682c5961a82c18f90283..8b324e9c89819b9c2978afc17fc221ded4ace78e 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
 import org.sonar.db.Pagination;
 import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.SearchGroupMembershipDto;
 
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
@@ -50,11 +51,11 @@ public class QProfileEditGroupsDao implements Dao {
       .isEmpty();
   }
 
-  public int countByQuery(DbSession dbSession, SearchGroupsQuery query) {
+  public int countByQuery(DbSession dbSession, SearchQualityProfileGroupsQuery query) {
     return mapper(dbSession).countByQuery(query);
   }
 
-  public List<GroupMembershipDto> selectByQuery(DbSession dbSession, SearchGroupsQuery query, Pagination pagination) {
+  public List<SearchGroupMembershipDto> selectByQuery(DbSession dbSession, SearchQualityProfileGroupsQuery query, Pagination pagination) {
     return mapper(dbSession).selectByQuery(query, pagination);
   }
 
index c1f536f9e7a7bc227ce7697b2d05f89af5e830ce..d8cb1e723e6981a55fdb9cc8f169f4621a384e24 100644 (file)
@@ -23,14 +23,15 @@ import java.util.Collection;
 import java.util.List;
 import org.apache.ibatis.annotations.Param;
 import org.sonar.db.Pagination;
+import org.sonar.db.user.SearchGroupMembershipDto;
 
 public interface QProfileEditGroupsMapper {
 
   List<QProfileEditGroupsDto> selectByQProfileAndGroups(@Param("qProfileUuid") String qProfileUuid, @Param("groupUuids") List<String> groupUuids);
 
-  int countByQuery(@Param("query") SearchGroupsQuery query);
+  int countByQuery(@Param("query") SearchQualityProfileGroupsQuery query);
 
-  List<GroupMembershipDto> selectByQuery(@Param("query") SearchGroupsQuery query, @Param("pagination") Pagination pagination);
+  List<SearchGroupMembershipDto> selectByQuery(@Param("query") SearchQualityProfileGroupsQuery query, @Param("pagination") Pagination pagination);
 
   List<String> selectQProfileUuidsByGroups(@Param("groupUuids") List<String> groupUuids);
 
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
deleted file mode 100644 (file)
index 1b17815..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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 static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.db.DaoUtils.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 qProfileUuid;
-  private final String query;
-  private final String membership;
-
-  // for internal use in MyBatis
-  final String querySqlLowercase;
-
-  private SearchGroupsQuery(Builder builder) {
-    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 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 QProfileDto profile;
-    private String query;
-    private String membership;
-
-    private Builder() {
-    }
-
-    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/java/org/sonar/db/qualityprofile/SearchQualityProfileGroupsQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/SearchQualityProfileGroupsQuery.java
new file mode 100644 (file)
index 0000000..cdbe85e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 java.util.Locale;
+import org.sonar.db.user.SearchGroupsQuery;
+
+import static org.sonar.db.DaoUtils.buildLikeValue;
+import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
+
+public class SearchQualityProfileGroupsQuery extends SearchGroupsQuery {
+
+  private final String qProfileUuid;
+
+  public SearchQualityProfileGroupsQuery(Builder builder) {
+    this.qProfileUuid = builder.profile.getKee();
+    this.query = builder.getQuery();
+    this.membership = builder.getMembership();
+    this.querySqlLowercase = query == null ? null : buildLikeValue(query, BEFORE_AND_AFTER).toLowerCase(Locale.ENGLISH);
+  }
+
+  public String getQProfileUuid() {
+    return qProfileUuid;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends SearchGroupsQuery.Builder<Builder> {
+    private QProfileDto profile;
+
+    public Builder setProfile(QProfileDto profile) {
+      this.profile = profile;
+      return this;
+    }
+
+    public SearchQualityProfileGroupsQuery build() {
+      initMembership();
+      return new SearchQualityProfileGroupsQuery(this);
+    }
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupMembershipDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupMembershipDto.java
new file mode 100644 (file)
index 0000000..43a7d40
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.user;
+
+public class SearchGroupMembershipDto {
+
+  private String groupUuid;
+  // Set by MyBatis
+  private String uuid;
+
+  public String getGroupUuid() {
+    return groupUuid;
+  }
+
+  public SearchGroupMembershipDto setGroupUuid(String groupUuid) {
+    this.groupUuid = groupUuid;
+    return this;
+  }
+
+  public boolean isSelected() {
+    return uuid != null;
+  }
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupsQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/SearchGroupsQuery.java
new file mode 100644 (file)
index 0000000..fd45b7b
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.user;
+
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
+
+public abstract 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 = Set.of(ANY, IN, OUT);
+
+  protected String query;
+  protected String membership;
+
+  // for internal use in MyBatis
+  protected String querySqlLowercase;
+
+  public String getMembership() {
+    return membership;
+  }
+
+  @CheckForNull
+  public String getQuery() {
+    return query;
+  }
+
+  public abstract static class Builder<T extends Builder<T>> {
+    private String query;
+    private String membership;
+
+    public String getQuery(){
+      return query;
+    }
+
+    public T setQuery(@Nullable String s) {
+      this.query = StringUtils.defaultIfBlank(s, null);
+      return self();
+    }
+
+    public String getMembership(){
+      return membership;
+    }
+
+    public T setMembership(@Nullable String membership) {
+      this.membership = membership;
+      return self();
+    }
+
+    public void initMembership() {
+      membership = firstNonNull(membership, ANY);
+      checkArgument(AVAILABLE_MEMBERSHIPS.contains(membership),
+        "Membership is not valid (got " + membership + "). Availables values are " + AVAILABLE_MEMBERSHIPS);
+    }
+
+    @SuppressWarnings("unchecked")
+    final T self() {
+      return (T) this;
+    }
+  }
+}
index efec92a4183022b7ad599ab36781ba9ed342477e..740123d047fdc6e72614ba865968dd74161c3ff0 100644 (file)
     and qggp.quality_gate_uuid = #{qualityGateUuid, jdbcType=VARCHAR}
   </select>
 
+  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.user.SearchGroupMembershipDto">
+    SELECT g.uuid as groupUuid, g.name as name, qggp.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.user.SearchGroupMembershipDto" databaseId="mssql">
+    select * from (
+    select row_number() over(order by g.name asc) as number,
+      g.uuid as groupUuid, g.name as name, qggp.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.user.SearchGroupMembershipDto" databaseId="oracle">
+    select * from (
+      select rownum as rn, t.* from (
+        select g.uuid as groupUuid, g.name as name, qggp.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>
+
+   <select id="countByQuery" resultType="int">
+    select count(g.uuid)
+    <include refid="sqlSelectByQuery" />
+  </select>
+
+  <sql id="sqlSelectByQuery">
+    FROM groups g
+    LEFT JOIN qgate_group_permissions qggp ON qggp.group_uuid=g.uuid AND qggp.quality_gate_uuid=#{query.qualityGateUuid, jdbcType=VARCHAR}
+    <where>
+      <choose>
+        <when test="query.getMembership() == 'IN'">
+          AND qggp.uuid IS NOT NULL
+        </when>
+        <when test="query.getMembership() == 'OUT'">
+          AND qggp.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 qgate_group_permissions(
     uuid,
index 7539227c211742e00a1cf834d374bd5782fc81a6..4314c7ac88a4bfb4b4f54a14cee1eb70b3ac04b0 100644 (file)
@@ -25,7 +25,7 @@
     <include refid="sqlSelectByQuery" />
   </select>
 
-  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto">
+  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.user.SearchGroupMembershipDto">
     SELECT g.uuid as groupUuid, g.name as name, qeg.uuid as uuid
     <include refid="sqlSelectByQuery"/>
     ORDER BY g.name ASC
@@ -33,7 +33,7 @@
     OFFSET #{pagination.offset,jdbcType=INTEGER}
   </select>
 
-  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto" databaseId="mssql">
+  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.user.SearchGroupMembershipDto" databaseId="mssql">
     select * from (
     select row_number() over(order by g.name asc) as number,
       g.uuid as groupUuid, g.name as name, qeg.uuid as uuid
@@ -44,7 +44,7 @@
     order by query.name asc
   </select>
 
-  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.qualityprofile.GroupMembershipDto" databaseId="oracle">
+  <select id="selectByQuery" parameterType="map" resultType="org.sonar.db.user.SearchGroupMembershipDto" databaseId="oracle">
     select * from (
       select rownum as rn, t.* from (
         select g.uuid as groupUuid, g.name as name, qeg.uuid as uuid
index b94e8ee3e15bc091b3e34abc570b128babfecd99..0acdca11c79415b9ca91159c9cc117c5cadedccc 100644 (file)
@@ -27,11 +27,18 @@ import org.sonar.api.utils.System2;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.Pagination;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.GroupTesting;
+import org.sonar.db.user.SearchGroupMembershipDto;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.db.qualitygate.SearchQualityGateGroupsQuery.builder;
+import static org.sonar.db.user.SearchGroupsQuery.ANY;
+import static org.sonar.db.user.SearchGroupsQuery.IN;
+import static org.sonar.db.user.SearchGroupsQuery.OUT;
 
 public class QualityGateGroupPermissionsDaoTest {
 
@@ -70,6 +77,131 @@ public class QualityGateGroupPermissionsDaoTest {
     assertThat(underTest.exists(dbSession, randomAlphabetic(5), randomAlphabetic(5))).isFalse();
   }
 
+  @Test
+  public void countByQuery() {
+    QualityGateDto qualityGateDto = insertQualityGate();
+    GroupDto group1 = dbTester.users().insertGroup();
+    GroupDto group2 = dbTester.users().insertGroup();
+    GroupDto group3 = dbTester.users().insertGroup();
+
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group1.getUuid());
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group2.getUuid());
+
+    assertThat(underTest.exists(dbSession, qualityGateDto, List.of(group1, group2))).isTrue();
+
+    assertThat(underTest.countByQuery(dbSession, builder()
+      .setQualityGate(qualityGateDto)
+      .setMembership(ANY).build()))
+      .isEqualTo(3);
+
+    assertThat(underTest.countByQuery(dbSession, builder()
+      .setQualityGate(qualityGateDto)
+      .setMembership(IN).build()))
+      .isEqualTo(2);
+
+    assertThat(underTest.countByQuery(dbSession, builder()
+      .setQualityGate(qualityGateDto)
+      .setMembership(OUT).build()))
+      .isEqualTo(1);
+  }
+
+  @Test
+  public void selectByQuery() {
+    QualityGateDto qualityGateDto = insertQualityGate();
+    GroupDto group1 = dbTester.users().insertGroup();
+    GroupDto group2 = dbTester.users().insertGroup();
+    GroupDto group3 = dbTester.users().insertGroup();
+
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group1.getUuid());
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group2.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+      .setQualityGate(qualityGateDto)
+      .setMembership(ANY).build(), Pagination.all()))
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
+      .containsExactlyInAnyOrder(
+        tuple(group1.getUuid(), true),
+        tuple(group2.getUuid(), true),
+        tuple(group3.getUuid(), false));
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(IN).build(),
+      Pagination.all()))
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
+      .containsExactlyInAnyOrder(tuple(group1.getUuid(), true), tuple(group2.getUuid(), true));
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(OUT).build(),
+      Pagination.all()))
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
+      .containsExactlyInAnyOrder(tuple(group3.getUuid(), false));
+  }
+
+  @Test
+  public void selectByQuery_search_by_name() {
+    QualityGateDto qualityGateDto = insertQualityGate();
+    GroupDto group1 = dbTester.users().insertGroup("sonar-users-project");
+    GroupDto group2 = dbTester.users().insertGroup("sonar-users-qprofile");
+    GroupDto group3 = dbTester.users().insertGroup("sonar-admin");
+
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group1.getUuid());
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group2.getUuid());
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group3.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(IN)
+        .setQuery("project").build(),
+      Pagination.all()))
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
+      .containsExactlyInAnyOrder(group1.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(IN)
+        .setQuery("UserS").build(),
+      Pagination.all()))
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
+      .containsExactlyInAnyOrder(group1.getUuid(), group2.getUuid());
+  }
+
+  @Test
+  public void selectByQuery_with_paging() {
+    QualityGateDto qualityGateDto = insertQualityGate();
+    GroupDto group1 = dbTester.users().insertGroup("group1");
+    GroupDto group2 = dbTester.users().insertGroup("group2");
+    GroupDto group3 = dbTester.users().insertGroup("group3");
+
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group1.getUuid());
+    insertQualityGateGroupPermission(qualityGateDto.getUuid(), group2.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(ANY)
+        .build(),
+      Pagination.forPage(1).andSize(1)))
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
+      .containsExactly(group1.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(ANY)
+        .build(),
+      Pagination.forPage(3).andSize(1)))
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
+      .containsExactly(group3.getUuid());
+
+    assertThat(underTest.selectByQuery(dbSession, builder()
+        .setQualityGate(qualityGateDto)
+        .setMembership(ANY)
+        .build(),
+      Pagination.forPage(1).andSize(10)))
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
+      .containsExactly(group1.getUuid(), group2.getUuid(), group3.getUuid());
+  }
+
   private QualityGateDto insertQualityGate() {
     QualityGateDto qg = new QualityGateDto()
       .setUuid(randomAlphabetic(5))
index 2e5948a8fd1550249451b5f04b63f9e7347797a0..02ef1d812c01893b926e65ada4f8472377f15c74 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.Pagination;
 import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.SearchGroupMembershipDto;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
@@ -33,10 +34,10 @@ import static java.util.Collections.singletonList;
 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;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.ANY;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.IN;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.OUT;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.builder;
 
 public class QProfileEditGroupsDaoTest {
 
@@ -103,7 +104,7 @@ public class QProfileEditGroupsDaoTest {
     assertThat(underTest.selectByQuery(db.getSession(), builder()
       .setProfile(profile)
       .setMembership(ANY).build(), Pagination.all()))
-      .extracting(GroupMembershipDto::getGroupUuid, GroupMembershipDto::isSelected)
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
       .containsExactlyInAnyOrder(
         tuple(group1.getUuid(), true),
         tuple(group2.getUuid(), true),
@@ -113,14 +114,14 @@ public class QProfileEditGroupsDaoTest {
         .setProfile(profile)
         .setMembership(IN).build(),
       Pagination.all()))
-      .extracting(GroupMembershipDto::getGroupUuid, GroupMembershipDto::isSelected)
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
       .containsExactlyInAnyOrder(tuple(group1.getUuid(), true), tuple(group2.getUuid(), true));
 
     assertThat(underTest.selectByQuery(db.getSession(), builder()
         .setProfile(profile)
         .setMembership(OUT).build(),
       Pagination.all()))
-      .extracting(GroupMembershipDto::getGroupUuid, GroupMembershipDto::isSelected)
+      .extracting(SearchGroupMembershipDto::getGroupUuid, SearchGroupMembershipDto::isSelected)
       .containsExactlyInAnyOrder(tuple(group3.getUuid(), false));
   }
 
@@ -139,7 +140,7 @@ public class QProfileEditGroupsDaoTest {
         .setMembership(IN)
         .setQuery("project").build(),
       Pagination.all()))
-      .extracting(GroupMembershipDto::getGroupUuid)
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
       .containsExactlyInAnyOrder(group1.getUuid());
 
     assertThat(underTest.selectByQuery(db.getSession(), builder()
@@ -147,7 +148,7 @@ public class QProfileEditGroupsDaoTest {
         .setMembership(IN)
         .setQuery("UserS").build(),
       Pagination.all()))
-      .extracting(GroupMembershipDto::getGroupUuid)
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
       .containsExactlyInAnyOrder(group1.getUuid(), group2.getUuid());
   }
 
@@ -165,7 +166,7 @@ public class QProfileEditGroupsDaoTest {
         .setMembership(ANY)
         .build(),
       Pagination.forPage(1).andSize(1)))
-      .extracting(GroupMembershipDto::getGroupUuid)
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
       .containsExactly(group1.getUuid());
 
     assertThat(underTest.selectByQuery(db.getSession(), builder()
@@ -173,7 +174,7 @@ public class QProfileEditGroupsDaoTest {
         .setMembership(ANY)
         .build(),
       Pagination.forPage(3).andSize(1)))
-      .extracting(GroupMembershipDto::getGroupUuid)
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
       .containsExactly(group3.getUuid());
 
     assertThat(underTest.selectByQuery(db.getSession(), builder()
@@ -181,7 +182,7 @@ public class QProfileEditGroupsDaoTest {
         .setMembership(ANY)
         .build(),
       Pagination.forPage(1).andSize(10)))
-      .extracting(GroupMembershipDto::getGroupUuid)
+      .extracting(SearchGroupMembershipDto::getGroupUuid)
       .containsExactly(group1.getUuid(), group2.getUuid(), group3.getUuid());
   }
 
index b018c949ffba14f71df9a3fba5a6217e5d89d7b4..fcd075d46bd92e7124797921bf3809dca777e8a3 100644 (file)
@@ -43,7 +43,8 @@ public class QualityGateWsModule extends Module {
       UpdateConditionAction.class,
       ProjectStatusAction.class,
       GetByProjectAction.class,
-      AddGroupAction.class
+      AddGroupAction.class,
+      SearchGroupsAction.class
     );
   }
 }
index cc2e826581eb0f2e6e0a628ed391734b2d2e2528..d93769833ae23fe5de6ddc28cd2a47ae2aa29b50 100644 (file)
@@ -31,6 +31,7 @@ public class QualityGatesWsParameters {
   public static final String ACTION_UPDATE_CONDITION = "update_condition";
   public static final String ACTION_ADD_GROUP = "add_group";
   public static final String ACTION_ADD_USER = "add_user";
+  public static final String ACTION_SEARCH_GROUPS = "search_groups";
 
   public static final String PARAM_ANALYSIS_ID = "analysisId";
   public static final String PARAM_BRANCH = "branch";
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchGroupsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchGroupsAction.java
new file mode 100644 (file)
index 0000000..2cda1a5
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.server.qualitygate.ws;
+
+import java.util.List;
+import java.util.Map;
+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.qualitygate.QualityGateDto;
+import org.sonar.db.user.SearchGroupMembershipDto;
+import org.sonar.db.user.SearchGroupsQuery;
+import org.sonar.db.user.GroupDto;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Qualitygates;
+
+import static java.util.Optional.ofNullable;
+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.stream.MoreCollectors.toList;
+import static org.sonar.db.Pagination.forPage;
+import static org.sonar.db.qualitygate.SearchQualityGateGroupsQuery.builder;
+import static org.sonar.db.user.SearchGroupsQuery.ANY;
+import static org.sonar.db.user.SearchGroupsQuery.IN;
+import static org.sonar.db.user.SearchGroupsQuery.OUT;
+import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.ACTION_SEARCH_GROUPS;
+import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_GATE_NAME;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class SearchGroupsAction implements QualityGatesWsAction {
+
+  private static final Map<WebService.SelectionMode, String> MEMBERSHIP = Map.of(WebService.SelectionMode.SELECTED, IN, DESELECTED, OUT, ALL, ANY);
+
+  private final DbClient dbClient;
+  private final QualityGatesWsSupport wsSupport;
+
+  public SearchGroupsAction(DbClient dbClient, QualityGatesWsSupport wsSupport) {
+    this.dbClient = dbClient;
+    this.wsSupport = wsSupport;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context
+      .createAction(ACTION_SEARCH_GROUPS)
+      .setDescription("List the groups that are allowed to edit a Quality Gate.<br>" +
+        "Requires one of the following permissions:" +
+        "<ul>" +
+        "  <li>'Administer Quality Gates'</li>" +
+        "  <li>Edit right on the specified quality gate</li>" +
+        "</ul>")
+      .setHandler(this)
+      .setInternal(true)
+      .addSelectionModeParam()
+      .addSearchQuery("sonar", "group names")
+      .addPagingParams(25)
+      .setResponseExample(getClass().getResource("search_groups-example.json"))
+      .setSince("9.2");
+
+    action.createParam(PARAM_GATE_NAME)
+      .setDescription("Quality Gate name")
+      .setRequired(true)
+      .setExampleValue("SonarSource Way");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    SearchQualityGateUsersRequest wsRequest = buildRequest(request);
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      QualityGateDto gate = wsSupport.getByName(dbSession, wsRequest.getQualityGate());
+      wsSupport.checkCanLimitedEdit(dbSession, gate);
+
+      SearchGroupsQuery query = builder()
+        .setQualityGate(gate)
+        .setQuery(wsRequest.getQuery())
+        .setMembership(MEMBERSHIP.get(fromParam(wsRequest.getSelected())))
+        .build();
+      int total = dbClient.qualityGateGroupPermissionsDao().countByQuery(dbSession, query);
+      List<SearchGroupMembershipDto> groupMemberships = dbClient.qualityGateGroupPermissionsDao().selectByQuery(dbSession, query,
+        forPage(wsRequest.getPage()).andSize(wsRequest.getPageSize()));
+      Map<String, GroupDto> groupsByUuid = dbClient.groupDao().selectByUuids(dbSession,
+          groupMemberships.stream().map(SearchGroupMembershipDto::getGroupUuid).collect(MoreCollectors.toList()))
+        .stream()
+        .collect(MoreCollectors.uniqueIndex(GroupDto::getUuid));
+      writeProtobuf(
+        Qualitygates.SearchGroupsResponse.newBuilder()
+          .addAllGroups(groupMemberships.stream()
+            .map(groupsMembership -> toGroup(groupsByUuid.get(groupsMembership.getGroupUuid()), groupsMembership.isSelected()))
+            .collect(toList()))
+          .setPaging(buildPaging(wsRequest, total)).build(),
+        request, response);
+    }
+  }
+
+  private static SearchQualityGateUsersRequest buildRequest(Request request) {
+    return SearchQualityGateUsersRequest.builder()
+      .setQualityGate(request.mandatoryParam(PARAM_GATE_NAME))
+      .setQuery(request.param(TEXT_QUERY))
+      .setSelected(request.mandatoryParam(SELECTED))
+      .setPage(request.mandatoryParamAsInt(PAGE))
+      .setPageSize(request.mandatoryParamAsInt(PAGE_SIZE))
+      .build();
+  }
+
+  private static Qualitygates.SearchGroupsResponse.Group toGroup(GroupDto group, boolean isSelected) {
+    Qualitygates.SearchGroupsResponse.Group.Builder builder = Qualitygates.SearchGroupsResponse.Group.newBuilder()
+      .setName(group.getName())
+      .setSelected(isSelected);
+    ofNullable(group.getDescription()).ifPresent(builder::setDescription);
+    return builder.build();
+  }
+
+  private static Common.Paging buildPaging(SearchQualityGateUsersRequest wsRequest, int total) {
+    return Common.Paging.newBuilder()
+      .setPageIndex(wsRequest.getPage())
+      .setPageSize(wsRequest.getPageSize())
+      .setTotal(total)
+      .build();
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchQualityGateUsersRequest.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchQualityGateUsersRequest.java
new file mode 100644 (file)
index 0000000..58f1637
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.server.qualitygate.ws;
+
+import org.sonar.server.user.ws.SearchUsersRequest;
+
+class SearchQualityGateUsersRequest extends SearchUsersRequest {
+  private String qualityGate;
+
+  private SearchQualityGateUsersRequest(Builder builder) {
+    this.qualityGate = builder.qualityGate;
+    this.selected = builder.getSelected();
+    this.query = builder.getQuery();
+    this.page = builder.getPage();
+    this.pageSize = builder.getPageSize();
+  }
+
+  public String getQualityGate() {
+    return qualityGate;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends SearchUsersRequest.Builder<Builder> {
+    private String qualityGate;
+
+    public Builder setQualityGate(String qualityGate) {
+      this.qualityGate = qualityGate;
+      return this;
+    }
+
+    public SearchQualityGateUsersRequest build() {
+      return new SearchQualityGateUsersRequest(this);
+    }
+  }
+}
index 79330bbbe919b35eaeb254fbdcc21a5fd874f017..e71ef37e67ed4226f37b013d77c354d25f09d0e4 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.qualityprofile.ws;
 
-import com.google.common.collect.ImmutableMap;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -31,10 +30,10 @@ 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.qualityprofile.GroupMembershipDto;
 import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.SearchGroupsQuery;
+import org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery;
 import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.SearchGroupMembershipDto;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Qualityprofiles;
 
@@ -49,10 +48,10 @@ import static org.sonar.api.server.ws.WebService.SelectionMode.fromParam;
 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.db.qualityprofile.SearchQualityProfileGroupsQuery.ANY;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.IN;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.OUT;
+import static org.sonar.db.qualityprofile.SearchQualityProfileGroupsQuery.builder;
 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;
@@ -60,7 +59,7 @@ import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.
 
 public class SearchGroupsAction implements QProfileWsAction {
 
-  private static final Map<WebService.SelectionMode, String> MEMBERSHIP = ImmutableMap.of(WebService.SelectionMode.SELECTED, IN, DESELECTED, OUT, ALL, ANY);
+  private static final Map<WebService.SelectionMode, String> MEMBERSHIP = Map.of(WebService.SelectionMode.SELECTED, IN, DESELECTED, OUT, ALL, ANY);
 
   private final DbClient dbClient;
   private final QProfileWsSupport wsSupport;
@@ -104,21 +103,21 @@ public class SearchGroupsAction implements QProfileWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    SearchUsersRequest wsRequest = buildRequest(request);
+    SearchQualityProfileUsersRequest wsRequest = buildRequest(request);
     try (DbSession dbSession = dbClient.openSession(false)) {
       QProfileDto profile = wsSupport.getProfile(dbSession, wsRequest.getQualityProfile(), wsRequest.getLanguage());
       wsSupport.checkCanEdit(dbSession, profile);
 
-      SearchGroupsQuery query = builder()
+      SearchQualityProfileGroupsQuery query = builder()
         .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,
+      List<SearchGroupMembershipDto> groupMemberships = dbClient.qProfileEditGroupsDao().selectByQuery(dbSession, query,
         forPage(wsRequest.getPage()).andSize(wsRequest.getPageSize()));
       Map<String, GroupDto> groupsByUuid = dbClient.groupDao().selectByUuids(dbSession,
-        groupMemberships.stream().map(GroupMembershipDto::getGroupUuid).collect(MoreCollectors.toList()))
+        groupMemberships.stream().map(SearchGroupMembershipDto::getGroupUuid).collect(MoreCollectors.toList()))
         .stream()
         .collect(MoreCollectors.uniqueIndex(GroupDto::getUuid));
       writeProtobuf(
@@ -131,8 +130,8 @@ public class SearchGroupsAction implements QProfileWsAction {
     }
   }
 
-  private static SearchUsersRequest buildRequest(Request request) {
-    return SearchUsersRequest.builder()
+  private static SearchQualityProfileUsersRequest buildRequest(Request request) {
+    return SearchQualityProfileUsersRequest.builder()
       .setQualityProfile(request.mandatoryParam(PARAM_QUALITY_PROFILE))
       .setLanguage(request.mandatoryParam(PARAM_LANGUAGE))
       .setQuery(request.param(TEXT_QUERY))
@@ -150,7 +149,7 @@ public class SearchGroupsAction implements QProfileWsAction {
     return builder.build();
   }
 
-  private static Common.Paging buildPaging(SearchUsersRequest wsRequest, int total) {
+  private static Common.Paging buildPaging(SearchQualityProfileUsersRequest wsRequest, int total) {
     return Common.Paging.newBuilder()
       .setPageIndex(wsRequest.getPage())
       .setPageSize(wsRequest.getPageSize())
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchQualityProfileUsersRequest.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchQualityProfileUsersRequest.java
new file mode 100644 (file)
index 0000000..bcb2ed7
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.server.qualityprofile.ws;
+
+import org.sonar.server.user.ws.SearchUsersRequest;
+
+class SearchQualityProfileUsersRequest extends SearchUsersRequest {
+  private String qualityProfile;
+  private String language;
+
+  private SearchQualityProfileUsersRequest(Builder builder) {
+    this.qualityProfile = builder.qualityProfile;
+    this.language = builder.language;
+    this.selected = builder.getSelected();
+    this.query = builder.getQuery();
+    this.page = builder.getPage();
+    this.pageSize = builder.getPageSize();
+  }
+
+  public String getQualityProfile() {
+    return qualityProfile;
+  }
+
+  public String getLanguage() {
+    return language;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder extends SearchUsersRequest.Builder<Builder> {
+    private String qualityProfile;
+    private String language;
+
+    public Builder setQualityProfile(String qualityProfile) {
+      this.qualityProfile = qualityProfile;
+      return this;
+    }
+
+    public Builder setLanguage(String language) {
+      this.language = language;
+      return this;
+    }
+
+    public SearchQualityProfileUsersRequest build() {
+      return new SearchQualityProfileUsersRequest(this);
+    }
+  }
+}
index d4456bcdb3d372877d39c20f32c06d85746fad60..2b0b5263eb463224972571ed25fd103ee482bae6 100644 (file)
@@ -109,7 +109,7 @@ public class SearchUsersAction implements QProfileWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    SearchUsersRequest wsRequest = buildRequest(request);
+    SearchQualityProfileUsersRequest wsRequest = buildRequest(request);
     try (DbSession dbSession = dbClient.openSession(false)) {
       QProfileDto profile = wsSupport.getProfile(dbSession, wsRequest.getQualityProfile(), wsRequest.getLanguage());
       wsSupport.checkCanEdit(dbSession, profile);
@@ -134,8 +134,8 @@ public class SearchUsersAction implements QProfileWsAction {
     }
   }
 
-  private static SearchUsersRequest buildRequest(Request request) {
-    return SearchUsersRequest.builder()
+  private static SearchQualityProfileUsersRequest buildRequest(Request request) {
+    return SearchQualityProfileUsersRequest.builder()
       .setQualityProfile(request.mandatoryParam(PARAM_QUALITY_PROFILE))
       .setLanguage(request.mandatoryParam(PARAM_LANGUAGE))
       .setQuery(request.param(TEXT_QUERY))
@@ -155,7 +155,7 @@ public class SearchUsersAction implements QProfileWsAction {
       .build();
   }
 
-  private static Common.Paging buildPaging(SearchUsersRequest wsRequest, int total) {
+  private static Common.Paging buildPaging(SearchQualityProfileUsersRequest wsRequest, int total) {
     return Common.Paging.newBuilder()
       .setPageIndex(wsRequest.getPage())
       .setPageSize(wsRequest.getPageSize())
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersRequest.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/SearchUsersRequest.java
deleted file mode 100644 (file)
index d4ac5a2..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.server.qualityprofile.ws;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-class SearchUsersRequest {
-  private String qualityProfile;
-  private String language;
-  private String selected;
-  private String query;
-  private Integer page;
-  private Integer pageSize;
-
-  private SearchUsersRequest(Builder builder) {
-    this.qualityProfile = builder.qualityProfile;
-    this.language = builder.language;
-    this.selected = builder.selected;
-    this.query = builder.query;
-    this.page = builder.page;
-    this.pageSize = builder.pageSize;
-  }
-
-  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 qualityProfile;
-    private String language;
-    private String selected;
-    private String query;
-    private Integer page;
-    private Integer pageSize;
-
-    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 SearchUsersRequest build() {
-      return new SearchUsersRequest(this);
-    }
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchUsersRequest.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchUsersRequest.java
new file mode 100644 (file)
index 0000000..3a97bfc
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.server.user.ws;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public abstract class SearchUsersRequest {
+  protected String selected;
+  protected String query;
+  protected Integer page;
+  protected Integer pageSize;
+
+  @CheckForNull
+  public String getQuery() {
+    return query;
+  }
+
+  public String getSelected() {
+    return selected;
+  }
+
+  public Integer getPage() {
+    return page;
+  }
+
+  public Integer getPageSize() {
+    return pageSize;
+  }
+
+  public abstract static class Builder<T extends Builder<T>> {
+    private String selected;
+    private String query;
+    private Integer page;
+    private Integer pageSize;
+
+    public String getSelected() {
+      return selected;
+    }
+
+    public T setSelected(String selected) {
+      this.selected = selected;
+      return self();
+    }
+
+    public String getQuery() {
+      return query;
+    }
+
+    public T setQuery(@Nullable String query) {
+      this.query = query;
+      return self();
+    }
+
+    public Integer getPage() {
+      return page;
+    }
+
+    public T setPage(Integer page) {
+      this.page = page;
+      return self();
+    }
+
+    public Integer getPageSize() {
+      return pageSize;
+    }
+
+    public T setPageSize(Integer pageSize) {
+      this.pageSize = pageSize;
+      return self();
+    }
+
+    @SuppressWarnings("unchecked")
+    final T self() {
+      return (T) this;
+    }
+  }
+}
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search_groups-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search_groups-example.json
new file mode 100644 (file)
index 0000000..a8fdbcf
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "paging": {
+    "pageSize": 25,
+    "total": 2,
+    "pageIndex": 1
+  },
+  "groups": [
+    {
+      "name": "users",
+      "description": "Users",
+      "selected": true
+    },
+    {
+      "name": "administrators",
+      "description": "Administrators",
+      "selected": false
+    }
+  ]
+}
index d7b3096964714706671c479e6554c5f44f9cbe94..9c6ab4fd0d25f161e38b0be700e956c51488d1d1 100644 (file)
@@ -30,7 +30,7 @@ public class QualityGateWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new QualityGateWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 19);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 20);
   }
 
 }
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/SearchGroupsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/SearchGroupsActionTest.java
new file mode 100644 (file)
index 0000000..b8fcb18
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.server.qualitygate.ws;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbTester;
+import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Qualitygates;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.TEXT_QUERY;
+import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
+import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_GATE_NAME;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.MediaTypes.JSON;
+
+public class SearchGroupsActionTest {
+  private static final String XOO = "xoo";
+  private static final String FOO = "foo";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private final QualityGatesWsSupport wsSupport = new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db));
+
+  private final WsActionTester ws = new WsActionTester(new SearchGroupsAction(db.getDbClient(), wsSupport));
+
+  @Test
+  public void test_definition() {
+    WebService.Action def = ws.getDef();
+    assertThat(def.key()).isEqualTo("search_groups");
+    assertThat(def.isPost()).isFalse();
+    assertThat(def.isInternal()).isTrue();
+    assertThat(def.params()).extracting(WebService.Param::key)
+      .containsExactlyInAnyOrder("gateName", "selected", "q", "p", "ps");
+  }
+
+  @Test
+  public void test_example() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group1 = db.users().insertGroup(newGroupDto().setName("users").setDescription("Users"));
+    GroupDto group2 = db.users().insertGroup(newGroupDto().setName("administrators").setDescription("Administrators"));
+    db.qualityGates().addGroupPermission(gate, group1);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    String result = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .setMediaType(JSON)
+      .execute()
+      .getInput();
+
+    assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result);
+  }
+
+  @Test
+  public void search_all_groups() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group1 = db.users().insertGroup();
+    GroupDto group2 = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group1);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName,
+        Qualitygates.SearchGroupsResponse.Group::getDescription, Qualitygates.SearchGroupsResponse.Group::getSelected)
+      .containsExactlyInAnyOrder(
+        Assertions.tuple(group1.getName(), group1.getDescription(), true),
+        Assertions.tuple(group2.getName(), group2.getDescription(), false));
+  }
+
+  @Test
+  public void search_selected_groups() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group1 = db.users().insertGroup();
+    GroupDto group2 = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group1);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "selected")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName,
+        Qualitygates.SearchGroupsResponse.Group::getDescription, Qualitygates.SearchGroupsResponse.Group::getSelected)
+      .containsExactlyInAnyOrder(
+        Assertions.tuple(group1.getName(), group1.getDescription(), true));
+  }
+
+  @Test
+  public void search_deselected_groups() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group1 = db.users().insertGroup();
+    GroupDto group2 = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group1);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "deselected")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName,
+        Qualitygates.SearchGroupsResponse.Group::getDescription, Qualitygates.SearchGroupsResponse.Group::getSelected)
+      .containsExactlyInAnyOrder(
+        Assertions.tuple(group2.getName(), group2.getDescription(), false));
+  }
+
+  @Test
+  public void search_by_name() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group1 = db.users().insertGroup("sonar-users-project");
+    GroupDto group2 = db.users().insertGroup("sonar-users-qgate");
+    GroupDto group3 = db.users().insertGroup("sonar-admin");
+    db.qualityGates().addGroupPermission(gate, group1);
+    db.qualityGates().addGroupPermission(gate, group2);
+    db.qualityGates().addGroupPermission(gate, group3);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(TEXT_QUERY, "UsErS")
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList()).extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactlyInAnyOrder(group1.getName(), group2.getName());
+  }
+
+  @Test
+  public void group_without_description() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group = db.users().insertGroup(newGroupDto().setDescription(null));
+    db.qualityGates().addGroupPermission(gate, group);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName, Qualitygates.SearchGroupsResponse.Group::hasDescription)
+      .containsExactlyInAnyOrder(Assertions.tuple(group.getName(), false));
+  }
+
+  @Test
+  public void paging_search() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group3 = db.users().insertGroup("group3");
+    GroupDto group1 = db.users().insertGroup("group1");
+    GroupDto group2 = db.users().insertGroup("group2");
+    db.qualityGates().addGroupPermission(gate, group1);
+    db.qualityGates().addGroupPermission(gate, group2);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    assertThat(ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .setParam(PAGE, "1")
+      .setParam(PAGE_SIZE, "1")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class).getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactly(group1.getName());
+
+    assertThat(ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .setParam(PAGE, "3")
+      .setParam(PAGE_SIZE, "1")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class).getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactly(group3.getName());
+
+    assertThat(ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .setParam(PAGE, "1")
+      .setParam(PAGE_SIZE, "10")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class).getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactly(group1.getName(), group2.getName(), group3.getName());
+  }
+
+  @Test
+  public void uses_global_permission() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group);
+    userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactlyInAnyOrder(group.getName());
+  }
+
+  @Test
+  public void qg_administers_can_search_groups() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group);
+    userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_GATES);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactlyInAnyOrder(group.getName());
+  }
+
+  @Test
+  public void qg_editors_can_search_groups() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    GroupDto group = db.users().insertGroup();
+    db.qualityGates().addGroupPermission(gate, group);
+    UserDto userAllowedToEditQualityGate = db.users().insertUser();
+    db.qualityGates().addUserPermission(gate, userAllowedToEditQualityGate);
+    userSession.logIn(userAllowedToEditQualityGate);
+
+    Qualitygates.SearchGroupsResponse response = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .setParam(WebService.Param.SELECTED, "all")
+      .executeProtobuf(Qualitygates.SearchGroupsResponse.class);
+
+    assertThat(response.getGroupsList())
+      .extracting(Qualitygates.SearchGroupsResponse.Group::getName)
+      .containsExactlyInAnyOrder(group.getName());
+  }
+
+  @Test
+  public void fail_when_qgate_does_not_exist() {
+    userSession.logIn().addPermission(GlobalPermission.ADMINISTER_QUALITY_GATES);
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("No quality gate has been found for name unknown");
+
+    ws.newRequest()
+      .setParam(PARAM_GATE_NAME, "unknown")
+      .execute();
+  }
+
+  @Test
+  public void fail_when_not_enough_permission() {
+    QualityGateDto gate = db.qualityGates().insertQualityGate();
+    userSession.logIn(db.users().insertUser());
+
+    expectedException.expect(ForbiddenException.class);
+
+    ws.newRequest()
+      .setParam(PARAM_GATE_NAME, gate.getName())
+      .execute();
+  }
+
+}
index 781e5b2c147d383203b822bde539b86b4ecfdec7..5538d4b5922f2e37fac01c49730b55dc76499280 100644 (file)
@@ -182,5 +182,17 @@ message Actions {
   optional bool manageConditions = 6;
 }
 
+// WS api/qualitygates/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;
+  }
+}
+