]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1330 Return available actions in api/qualityprofiles/search
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 27 Sep 2017 10:28:56 +0000 (12:28 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 2 Oct 2017 15:18:15 +0000 (17:18 +0200)
15 files changed:
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/QProfileEditUsersDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QProfileEditUsersMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditGroupsMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QProfileEditUsersMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditGroupsDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QProfileEditUsersDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java
server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search-example.json
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java
sonar-ws/src/main/protobuf/ws-qualityprofiles.proto
tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfilesEditTest.java

index bd023bec7dc34af946c61809f8c8b26100cba657..93df5f96361d6ef16c925b7d2ac4ef32131ae68d 100644 (file)
  */
 package org.sonar.db.qualityprofile;
 
+import java.util.Collection;
 import java.util.List;
 import org.sonar.api.utils.System2;
 import org.sonar.db.Dao;
+import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
 import org.sonar.db.Pagination;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+
 public class QProfileEditGroupsDao implements Dao {
 
   private final System2 system2;
@@ -38,14 +43,19 @@ public class QProfileEditGroupsDao implements Dao {
     return mapper(dbSession).selectByQProfileAndGroup(profile.getKee(), group.getId()) != null;
   }
 
-  public int countByQuery(DbSession dbSession, SearchGroupsQuery query){
+  public int countByQuery(DbSession dbSession, SearchGroupsQuery query) {
     return mapper(dbSession).countByQuery(query);
   }
 
-  public List<GroupMembershipDto> selectByQuery(DbSession dbSession, SearchGroupsQuery query, Pagination pagination){
+  public List<GroupMembershipDto> selectByQuery(DbSession dbSession, SearchGroupsQuery query, Pagination pagination) {
     return mapper(dbSession).selectByQuery(query, pagination);
   }
 
+  public List<String> selectQProfileUuidsByOrganizationAndGroups(DbSession dbSession, OrganizationDto organization, Collection<GroupDto> groups) {
+    return DatabaseUtils.executeLargeInputs(groups.stream().map(GroupDto::getId).collect(toList()), g ->
+      mapper(dbSession).selectQProfileUuidsByOrganizationAndGroups(organization.getUuid(), g));
+  }
+
   public void insert(DbSession dbSession, QProfileEditGroupsDto dto) {
     mapper(dbSession).insert(dto, system2.now());
   }
index dde92469d0f574daa705eb5f176a5b64b5e24a47..5bdaac413bb46d6f86c90c14e828d7d843be1cd5 100644 (file)
@@ -31,6 +31,8 @@ public interface QProfileEditGroupsMapper {
 
   List<GroupMembershipDto> selectByQuery(@Param("query") SearchGroupsQuery query, @Param("pagination") Pagination pagination);
 
+  List<String> selectQProfileUuidsByOrganizationAndGroups(@Param("organizationUuid") String organizationUuid, @Param("groupIds") List<Integer> groupIds);
+
   void insert(@Param("dto") QProfileEditGroupsDto dto, @Param("now") long now);
 
   void delete(@Param("qProfileUuid") String qProfileUuid, @Param("groupId") int groupId);
index 2cab9a929aa3ba6a114a25f096e91e014dd5985c..527ca96d1d6378091e1243a429cc3c321dc5c7a6 100644 (file)
@@ -24,6 +24,7 @@ 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.organization.OrganizationDto;
 import org.sonar.db.user.UserDto;
 
 public class QProfileEditUsersDao implements Dao {
@@ -46,6 +47,10 @@ public class QProfileEditUsersDao implements Dao {
     return mapper(dbSession).selectByQuery(query, pagination);
   }
 
+  public List<String> selectQProfileUuidsByOrganizationAndUser(DbSession dbSession, OrganizationDto organization, UserDto userDto){
+    return mapper(dbSession).selectQProfileUuidsByOrganizationAndUser(organization.getUuid(), userDto.getId());
+  }
+
   public void insert(DbSession dbSession, QProfileEditUsersDto dto) {
     mapper(dbSession).insert(dto, system2.now());
   }
index 70694c3307f202289f5fd765c63aa924e1f9886d..07232efe63f48d2ff29df86375f3912785444fb2 100644 (file)
@@ -31,6 +31,8 @@ public interface QProfileEditUsersMapper {
 
   List<UserMembershipDto> selectByQuery(@Param("query") SearchUsersQuery query, @Param("pagination") Pagination pagination);
 
+  List<String> selectQProfileUuidsByOrganizationAndUser(@Param("organizationUuid") String organizationUuid, @Param("userId") int userId);
+
   void insert(@Param("dto") QProfileEditUsersDto dto, @Param("now") long now);
 
   void delete(@Param("qProfileUuid") String qProfileUuid, @Param("userId") int userId);
index ee1a1fc6adb1ae590ba6cd4524846f08839bccdc..50c1d355e1af51b7e7ea0ca1a71208aad3924e7f 100644 (file)
     </where>
   </sql>
 
+  <select id="selectQProfileUuidsByOrganizationAndGroups" parameterType="map" resultType="string">
+    select distinct qeg.qprofile_uuid as qProfileUuid
+    from qprofile_edit_groups qeg
+    inner join org_qprofiles oq on qeg.qprofile_uuid=oq.uuid and oq.organization_uuid=#{organizationUuid, jdbcType=VARCHAR}
+    <where>
+      qeg.group_id in <foreach collection="groupIds" open="(" close=")" item="groupId" separator=",">#{groupId, jdbcType=INTEGER}</foreach>
+    </where>
+  </select>
+
   <insert id="insert" useGeneratedKeys="false" parameterType="map">
     insert into qprofile_edit_groups(
     uuid,
index 8bf84485bc4ee73322273667a52ef3d93f925c68..b678ef808d1fee47d66f16bf1d71f5bc2220e13b 100644 (file)
     </where>
   </sql>
 
+  <select id="selectQProfileUuidsByOrganizationAndUser" parameterType="map" resultType="string">
+    SELECT qeu.qprofile_uuid as qProfileUuid
+    FROM qprofile_edit_users qeu
+    INNER JOIN org_qprofiles oq ON qeu.qprofile_uuid=oq.uuid AND oq.organization_uuid=#{organizationUuid, jdbcType=VARCHAR}
+    <where>
+      qeu.user_id=#{userId, jdbcType=INTEGER}
+    </where>
+  </select>
+
   <insert id="insert" useGeneratedKeys="false" parameterType="map">
     insert into qprofile_edit_users(
     uuid,
index db5204ecb987b857056c6fc15539ac6fc17a5936..4b370cbdc0a8b9a3aaa324fcbd8da79917fcd3ae 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.qualityprofile;
 
+import java.util.Collections;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
@@ -28,6 +29,7 @@ import org.sonar.db.Pagination;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 
+import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
@@ -196,6 +198,30 @@ public class QProfileEditGroupsDaoTest {
       .containsExactly(group1.getId(), group2.getId(), group3.getId());
   }
 
+  @Test
+  public void selectQProfileUuidsByOrganizationAndGroups() {
+    OrganizationDto organization = db.organizations().insert();
+    OrganizationDto anotherOrganization = db.organizations().insert();
+    QProfileDto profile1 = db.qualityProfiles().insert(organization);
+    QProfileDto profile2 = db.qualityProfiles().insert(organization);
+    QProfileDto anotherProfile = db.qualityProfiles().insert(anotherOrganization);
+    GroupDto group1 = db.users().insertGroup(organization, "group1");
+    GroupDto group2 = db.users().insertGroup(organization, "group2");
+    GroupDto group3 = db.users().insertGroup(organization, "group3");
+    db.qualityProfiles().addGroupPermission(profile1, group1);
+    db.qualityProfiles().addGroupPermission(profile1, group2);
+    db.qualityProfiles().addGroupPermission(profile2, group2);
+    db.qualityProfiles().addGroupPermission(anotherProfile, group1);
+    db.qualityProfiles().addGroupPermission(anotherProfile, group3);
+
+    assertThat(underTest.selectQProfileUuidsByOrganizationAndGroups(db.getSession(), organization, asList(group1, group2)))
+      .containsExactlyInAnyOrder(profile1.getKee(), profile2.getKee())
+      .doesNotContain(anotherProfile.getKee());
+    assertThat(underTest.selectQProfileUuidsByOrganizationAndGroups(db.getSession(), organization, asList(group1, group2, group3)))
+      .containsExactlyInAnyOrder(profile1.getKee(), profile2.getKee());
+    assertThat(underTest.selectQProfileUuidsByOrganizationAndGroups(db.getSession(), organization, Collections.emptyList())).isEmpty();
+  }
+
   @Test
   public void insert() {
     underTest.insert(db.getSession(), new QProfileEditGroupsDto()
index e0a90e7cbb45ceca94cc88c6fe6cddc0ef45d29d..741f396b8586711bf24522df81dc71a744672040 100644 (file)
  */
 package org.sonar.db.qualityprofile;
 
+import java.sql.SQLException;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.TestSystem2;
 import org.sonar.db.DbTester;
@@ -35,6 +37,7 @@ import static org.sonar.db.qualityprofile.SearchUsersQuery.ANY;
 import static org.sonar.db.qualityprofile.SearchUsersQuery.IN;
 import static org.sonar.db.qualityprofile.SearchUsersQuery.OUT;
 import static org.sonar.db.qualityprofile.SearchUsersQuery.builder;
+import static org.sonar.test.ExceptionCauseMatcher.hasType;
 
 public class QProfileEditUsersDaoTest {
 
@@ -42,6 +45,8 @@ public class QProfileEditUsersDaoTest {
 
   private System2 system2 = new TestSystem2().setNow(NOW);
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
   @Rule
   public DbTester db = DbTester.create(system2);
 
@@ -82,15 +87,15 @@ public class QProfileEditUsersDaoTest {
       .isEqualTo(3);
 
     assertThat(underTest.countByQuery(db.getSession(), builder()
-        .setOrganization(organization)
-        .setProfile(profile)
-        .setMembership(IN).build()))
+      .setOrganization(organization)
+      .setProfile(profile)
+      .setMembership(IN).build()))
       .isEqualTo(2);
 
     assertThat(underTest.countByQuery(db.getSession(), builder()
-        .setOrganization(organization)
-        .setProfile(profile)
-        .setMembership(OUT).build()))
+      .setOrganization(organization)
+      .setProfile(profile)
+      .setMembership(OUT).build()))
       .isEqualTo(1);
   }
 
@@ -217,6 +222,25 @@ public class QProfileEditUsersDaoTest {
       .containsExactly(user1.getId(), user2.getId(), user3.getId());
   }
 
+  @Test
+  public void selectQProfileUuidsByOrganizationAndUser() {
+    OrganizationDto organization = db.organizations().insert();
+    OrganizationDto anotherOrganization = db.organizations().insert();
+    QProfileDto profile1 = db.qualityProfiles().insert(organization);
+    QProfileDto profile2 = db.qualityProfiles().insert(organization);
+    QProfileDto anotherProfile = db.qualityProfiles().insert(anotherOrganization);
+    UserDto user1 = db.users().insertUser(u -> u.setName("user1"));
+    UserDto user2 = db.users().insertUser(u -> u.setName("user2"));
+    db.qualityProfiles().addUserPermission(profile1, user1);
+    db.qualityProfiles().addUserPermission(profile2, user1);
+    db.qualityProfiles().addUserPermission(anotherProfile, user1);
+
+    assertThat(underTest.selectQProfileUuidsByOrganizationAndUser(db.getSession(), organization, user1))
+      .containsExactlyInAnyOrder(profile1.getKee(), profile2.getKee())
+      .doesNotContain(anotherProfile.getKee());
+    assertThat(underTest.selectQProfileUuidsByOrganizationAndUser(db.getSession(), organization, user2)).isEmpty();
+  }
+
   @Test
   public void insert() {
     underTest.insert(db.getSession(), new QProfileEditUsersDto()
@@ -232,6 +256,23 @@ public class QProfileEditUsersDaoTest {
       entry("createdAt", NOW));
   }
 
+  @Test
+  public void fail_to_insert_same_row_twice() {
+    underTest.insert(db.getSession(), new QProfileEditUsersDto()
+      .setUuid("UUID-1")
+      .setUserId(100)
+      .setQProfileUuid("QPROFILE")
+    );
+
+    expectedException.expectCause(hasType(SQLException.class));
+
+    underTest.insert(db.getSession(), new QProfileEditUsersDto()
+      .setUuid("UUID-2")
+      .setUserId(100)
+      .setQProfileUuid("QPROFILE")
+    );
+  }
+
   @Test
   public void deleteByQProfileAndUser() {
     OrganizationDto organization = db.organizations().insert();
index 6d4a594d26f79c3c82cdb695c991fe5dcb2c5333..fe6af2db9f51dc01bb0fc66823f01af434f17b7b 100644 (file)
@@ -28,6 +28,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.resources.Language;
@@ -44,8 +45,10 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.ActiveRuleCountQuery;
 import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
 import org.sonar.server.util.LanguageParamUtils;
 import org.sonarqube.ws.QualityProfiles.SearchWsResponse;
 import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile;
@@ -54,10 +57,13 @@ import org.sonarqube.ws.client.qualityprofile.SearchWsRequest;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.lang.String.format;
+import static java.util.Collections.emptyList;
 import static java.util.function.Function.identity;
 import static org.sonar.api.rule.RuleStatus.DEPRECATED;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_SEARCH;
@@ -72,12 +78,14 @@ public class SearchAction implements QProfileWsAction {
     .comparing(QProfileDto::getLanguage)
     .thenComparing(QProfileDto::getName);
 
+  private final UserSession userSession;
   private final Languages languages;
   private final DbClient dbClient;
   private final QProfileWsSupport wsSupport;
   private final ComponentFinder componentFinder;
 
-  public SearchAction(Languages languages, DbClient dbClient, QProfileWsSupport wsSupport, ComponentFinder componentFinder) {
+  public SearchAction(UserSession userSession, Languages languages, DbClient dbClient, QProfileWsSupport wsSupport, ComponentFinder componentFinder) {
+    this.userSession = userSession;
     this.languages = languages;
     this.dbClient = dbClient;
     this.wsSupport = wsSupport;
@@ -151,6 +159,7 @@ public class SearchAction implements QProfileWsAction {
       ComponentDto project = findProject(dbSession, organization, request);
 
       List<QProfileDto> defaultProfiles = dbClient.qualityProfileDao().selectDefaultProfiles(dbSession, organization, getLanguageKeys());
+      List<String> editableProfiles = searchEditableProfiles(dbSession, organization);
       List<QProfileDto> profiles = searchProfiles(dbSession, request, organization, defaultProfiles, project);
 
       ActiveRuleCountQuery.Builder builder = ActiveRuleCountQuery.builder().setOrganization(organization);
@@ -162,7 +171,9 @@ public class SearchAction implements QProfileWsAction {
         .setActiveDeprecatedRuleCountByProfileKey(
           dbClient.activeRuleDao().countActiveRulesByQuery(dbSession, builder.setProfiles(profiles).setRuleStatus(DEPRECATED).build()))
         .setProjectCountByProfileKey(dbClient.qualityProfileDao().countProjectsByOrganizationAndProfiles(dbSession, organization, profiles))
-        .setDefaultProfileKeys(defaultProfiles);
+        .setDefaultProfileKeys(defaultProfiles)
+        .setEditableProfileKeys(editableProfiles)
+        .setGlobalQProfileAdmin(userSession.hasPermission(ADMINISTER_QUALITY_PROFILES, organization));
     }
   }
 
@@ -184,8 +195,24 @@ public class SearchAction implements QProfileWsAction {
     return component;
   }
 
+  private List<String> searchEditableProfiles(DbSession dbSession, OrganizationDto organization) {
+    if (!userSession.isLoggedIn()) {
+      return emptyList();
+    }
+
+    String login = userSession.getLogin();
+    UserDto user = dbClient.userDao().selectActiveUserByLogin(dbSession, login);
+    checkState(user != null, "User with login '%s' is not found'", login);
+
+    return
+      Stream.concat(
+        dbClient.qProfileEditUsersDao().selectQProfileUuidsByOrganizationAndUser(dbSession, organization, user).stream(),
+        dbClient.qProfileEditGroupsDao().selectQProfileUuidsByOrganizationAndGroups(dbSession, organization, userSession.getGroups()).stream())
+        .collect(toList());
+  }
+
   private List<QProfileDto> searchProfiles(DbSession dbSession, SearchWsRequest request, OrganizationDto organization, List<QProfileDto> defaultProfiles,
-    @Nullable ComponentDto project) {
+                                           @Nullable ComponentDto project) {
     Collection<QProfileDto> profiles = selectAllProfiles(dbSession, organization);
 
     return profiles.stream()
@@ -238,7 +265,7 @@ public class SearchAction implements QProfileWsAction {
     Map<String, QProfileDto> profilesByKey = profiles.stream().collect(Collectors.toMap(QProfileDto::getKee, identity()));
 
     SearchWsResponse.Builder response = SearchWsResponse.newBuilder();
-    response.setActions(SearchWsResponse.Actions.newBuilder().setCreate(true));
+    response.setActions(SearchWsResponse.Actions.newBuilder().setCreate(data.isGlobalQProfileAdmin()));
 
     for (QProfileDto profile : profiles) {
       QualityProfile.Builder profileBuilder = response.addProfilesBuilder();
@@ -263,10 +290,10 @@ public class SearchAction implements QProfileWsAction {
       profileBuilder.setIsInherited(profile.getParentKee() != null);
       profileBuilder.setIsBuiltIn(profile.isBuiltIn());
 
-      profileBuilder.setActions(SearchWsResponse.Actions.newBuilder()
-        .setEdit(true)
-        .setSetAsDefault(false)
-        .setCopy(false));
+      profileBuilder.setActions(SearchWsResponse.QualityProfile.Actions.newBuilder()
+        .setEdit(data.isEditable(profile))
+        .setSetAsDefault(data.isGlobalQProfileAdmin())
+        .setCopy(data.isGlobalQProfileAdmin()));
     }
 
     return response.build();
index cf7f39fd75980627a3c1039d0c079b97490876fe..d07bfcb13d3f6e45cb0087839b0135c62915b53b 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.qualityprofile.ws;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -30,56 +31,58 @@ import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.collect.ImmutableList.copyOf;
 import static com.google.common.collect.ImmutableMap.copyOf;
 
-public class SearchData {
+class SearchData {
   private OrganizationDto organization;
   private List<QProfileDto> profiles;
   private Map<String, Long> activeRuleCountByProfileKey;
   private Map<String, Long> activeDeprecatedRuleCountByProfileKey;
   private Map<String, Long> projectCountByProfileKey;
   private Set<String> defaultProfileKeys;
+  private Set<String> editableProfileKeys;
+  private boolean isGlobalQProfileAdmin;
 
-  public SearchData setOrganization(OrganizationDto organization) {
+  SearchData setOrganization(OrganizationDto organization) {
     this.organization = organization;
     return this;
   }
 
-  public OrganizationDto getOrganization() {
+  OrganizationDto getOrganization() {
     return organization;
   }
 
-  public List<QProfileDto> getProfiles() {
+  List<QProfileDto> getProfiles() {
     return profiles;
   }
 
-  public SearchData setProfiles(List<QProfileDto> profiles) {
+  SearchData setProfiles(List<QProfileDto> profiles) {
     this.profiles = copyOf(profiles);
     return this;
   }
 
-  public SearchData setActiveRuleCountByProfileKey(Map<String, Long> activeRuleCountByProfileKey) {
+  SearchData setActiveRuleCountByProfileKey(Map<String, Long> activeRuleCountByProfileKey) {
     this.activeRuleCountByProfileKey = copyOf(activeRuleCountByProfileKey);
     return this;
   }
 
-  public SearchData setActiveDeprecatedRuleCountByProfileKey(Map<String, Long> activeDeprecatedRuleCountByProfileKey) {
+  SearchData setActiveDeprecatedRuleCountByProfileKey(Map<String, Long> activeDeprecatedRuleCountByProfileKey) {
     this.activeDeprecatedRuleCountByProfileKey = activeDeprecatedRuleCountByProfileKey;
     return this;
   }
 
-  public SearchData setProjectCountByProfileKey(Map<String, Long> projectCountByProfileKey) {
+  SearchData setProjectCountByProfileKey(Map<String, Long> projectCountByProfileKey) {
     this.projectCountByProfileKey = copyOf(projectCountByProfileKey);
     return this;
   }
 
-  public long getActiveRuleCount(String profileKey) {
+  long getActiveRuleCount(String profileKey) {
     return firstNonNull(activeRuleCountByProfileKey.get(profileKey), 0L);
   }
 
-  public long getProjectCount(String profileKey) {
+  long getProjectCount(String profileKey) {
     return firstNonNull(projectCountByProfileKey.get(profileKey), 0L);
   }
 
-  public long getActiveDeprecatedRuleCount(String profileKey) {
+  long getActiveDeprecatedRuleCount(String profileKey) {
     return firstNonNull(activeDeprecatedRuleCountByProfileKey.get(profileKey), 0L);
   }
 
@@ -91,4 +94,22 @@ public class SearchData {
     this.defaultProfileKeys = s.stream().map(QProfileDto::getKee).collect(MoreCollectors.toSet());
     return this;
   }
+
+  boolean isEditable(QProfileDto profile) {
+    return !profile.isBuiltIn() && (isGlobalQProfileAdmin || editableProfileKeys.contains(profile.getKee()));
+  }
+
+  SearchData setEditableProfileKeys(List<String> editableProfileKeys) {
+    this.editableProfileKeys = new HashSet<>(editableProfileKeys);
+    return this;
+  }
+
+  boolean isGlobalQProfileAdmin() {
+    return isGlobalQProfileAdmin;
+  }
+
+  SearchData setGlobalQProfileAdmin(boolean globalQProfileAdmin) {
+    isGlobalQProfileAdmin = globalQProfileAdmin;
+    return this;
+  }
 }
index 706984106218a40abff66611135c6fc00fa61bc5..66df3d862bcbe3ee0353b17cc360911fb6b3e92c 100644 (file)
@@ -13,7 +13,7 @@
       "ruleUpdatedAt": "2016-12-22T19:10:03+0100",
       "lastUsed": "2016-12-01T19:10:03+0100",
       "actions": {
-        "edit": true,
+        "edit": false,
         "setAsDefault": false,
         "copy": false
       }
@@ -53,7 +53,7 @@
       "ruleUpdatedAt": "2016-12-22T19:10:03+0100",
       "userUpdatedAt": "2016-06-29T21:57:01+0200",
       "actions": {
-        "edit": true,
+        "edit": false,
         "setAsDefault": false,
         "copy": false
       }
       "isDefault": true,
       "ruleUpdatedAt": "2014-12-22T19:10:03+0100",
       "actions": {
-        "edit": true,
+        "edit": false,
         "setAsDefault": false,
         "copy": false
       }
     }
   ],
   "actions": {
-    "create": true
+    "create": false
   }
 }
index 1797e4935a073f6ed38bed623e3d87f6200d53c8..0ffe19905561e08485c1264c706df1682c18eb1f 100644 (file)
@@ -42,13 +42,13 @@ import static org.mockito.Mockito.mock;
 
 public class QProfilesWsTest {
   @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  public UserSessionRule userSession = UserSessionRule.standalone();
 
   private WebService.Controller controller;
   private String xoo1Key = "xoo1";
   private String xoo2Key = "xoo2";
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.fromUuid("ORG1");
-  private QProfileWsSupport wsSupport = new QProfileWsSupport(mock(DbClient.class), userSessionRule, defaultOrganizationProvider);
+  private QProfileWsSupport wsSupport = new QProfileWsSupport(mock(DbClient.class), userSession, defaultOrganizationProvider);
 
   @Before
   public void setUp() {
@@ -59,18 +59,18 @@ public class QProfilesWsTest {
     ProfileImporter[] importers = createImporters(languages);
 
     controller = new WsTester(new QProfilesWs(
-      new CreateAction(null, null, null, languages, wsSupport, userSessionRule, null, importers),
+      new CreateAction(null, null, null, languages, wsSupport, userSession, null, importers),
       new ImportersAction(importers),
-      new SearchAction(languages, dbClient, wsSupport, null),
+      new SearchAction(userSession, languages, dbClient, wsSupport, null),
       new SetDefaultAction(languages, null, null, wsSupport),
-      new ProjectsAction(null, userSessionRule, wsSupport),
+      new ProjectsAction(null, userSession, wsSupport),
       new ChangelogAction(null, wsSupport, languages, dbClient),
-      new ChangeParentAction(dbClient, null, languages, wsSupport, userSessionRule),
+      new ChangeParentAction(dbClient, null, languages, wsSupport, userSession),
       new CompareAction(null, null, languages),
-      new DeleteAction(languages, null, null, userSessionRule, wsSupport),
+      new DeleteAction(languages, null, null, userSession, wsSupport),
       new ExportersAction(),
       new InheritanceAction(null, null, languages),
-      new RenameAction(dbClient, userSessionRule, wsSupport))).controller(QProfilesWs.API_ENDPOINT);
+      new RenameAction(dbClient, userSession, wsSupport))).controller(QProfilesWs.API_ENDPOINT);
   }
 
   private ProfileImporter[] createImporters(Languages languages) {
index 9616039f8a9dd7f7d393eecd33a40eee8b372a1c..1e53bbf1ef04e32edcce1795cc36bfc22089e084 100644 (file)
@@ -34,9 +34,12 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.qualityprofile.QualityProfileDbTester;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.organization.DefaultOrganizationProvider;
@@ -61,8 +64,8 @@ import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_DEFAULTS;
 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;
 import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PROJECT_KEY;
+import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_QUALITY_PROFILE;
 
 public class SearchActionTest {
 
@@ -81,7 +84,7 @@ public class SearchActionTest {
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
   private QProfileWsSupport qProfileWsSupport = new QProfileWsSupport(dbClient, userSession, defaultOrganizationProvider);
 
-  private SearchAction underTest = new SearchAction(LANGUAGES, dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null));
+  private SearchAction underTest = new SearchAction(userSession, LANGUAGES, dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null));
   private WsActionTester ws = new WsActionTester(underTest);
 
   @Test
@@ -130,7 +133,7 @@ public class SearchActionTest {
 
   @Test
   public void empty_when_no_language_installed() {
-    WsActionTester ws = new WsActionTester(new SearchAction(new Languages(), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null)));
+    WsActionTester ws = new WsActionTester(new SearchAction(userSession, new Languages(), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null)));
     db.qualityProfiles().insert(db.getDefaultOrganization());
 
     SearchWsResponse result = call(ws.newRequest());
@@ -307,7 +310,7 @@ public class SearchActionTest {
 
   @Test
   public void empty_when_filtering_on_project_and_no_language_installed() {
-    WsActionTester ws = new WsActionTester(new SearchAction(new Languages(), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null)));
+    WsActionTester ws = new WsActionTester(new SearchAction(userSession, new Languages(), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null)));
     db.qualityProfiles().insert(db.getDefaultOrganization());
     ComponentDto project = db.components().insertPrivateProject();
     QProfileDto profileOnXoo1 = db.qualityProfiles().insert(db.getDefaultOrganization(), q -> q.setLanguage(XOO1.getKey()));
@@ -320,6 +323,63 @@ public class SearchActionTest {
     assertThat(result.getProfilesList()).isEmpty();
   }
 
+  @Test
+  public void actions_when_user_is_global_qprofile_administer() {
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto customProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO1.getKey()));
+    QProfileDto builtInProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO1.getKey()).setIsBuiltIn(true));
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, organization);
+
+    SearchWsResponse result = call(ws.newRequest()
+      .setParam(PARAM_ORGANIZATION, organization.getKey()));
+
+    assertThat(result.getProfilesList()).extracting(QualityProfile::getKey, qp -> qp.getActions().getEdit(), qp -> qp.getActions().getCopy(), qp -> qp.getActions().getSetAsDefault())
+      .containsExactlyInAnyOrder(
+        tuple(customProfile.getKee(), true, true, true),
+        tuple(builtInProfile.getKee(), false, true, true));
+    assertThat(result.getActions().getCreate()).isTrue();
+  }
+
+  @Test
+  public void actions_when_user_can_edit_profile() {
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile1 = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO1.getKey()));
+    QProfileDto profile2 = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO2.getKey()));
+    QProfileDto profile3 = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO2.getKey()));
+    QProfileDto builtInProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO2.getKey()).setIsBuiltIn(true));
+    UserDto user = db.users().insertUser();
+    GroupDto group = db.users().insertGroup(organization);
+    db.qualityProfiles().addUserPermission(profile1, user);
+    db.qualityProfiles().addGroupPermission(profile3, group);
+    userSession.logIn(user).setGroups(group);
+
+    SearchWsResponse result = call(ws.newRequest()
+      .setParam(PARAM_ORGANIZATION, organization.getKey()));
+
+    assertThat(result.getProfilesList()).extracting(QualityProfile::getKey, qp -> qp.getActions().getEdit(), qp -> qp.getActions().getCopy(), qp -> qp.getActions().getSetAsDefault())
+      .containsExactlyInAnyOrder(
+        tuple(profile1.getKee(), true, false, false),
+        tuple(profile2.getKee(), false, false, false),
+        tuple(profile3.getKee(), true, false, false),
+        tuple(builtInProfile.getKee(), false, false, false));
+    assertThat(result.getActions().getCreate()).isFalse();
+  }
+
+  @Test
+  public void actions_when_not_logged_in() {
+    OrganizationDto organization = db.organizations().insert();
+    QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(XOO1.getKey()));
+    userSession.anonymous();
+
+    SearchWsResponse result = call(ws.newRequest()
+      .setParam(PARAM_ORGANIZATION, organization.getKey()));
+
+    assertThat(result.getProfilesList()).extracting(QualityProfile::getKey, qp -> qp.getActions().getEdit(), qp -> qp.getActions().getCopy(), qp -> qp.getActions().getSetAsDefault())
+      .containsExactlyInAnyOrder(tuple(profile.getKee(), false, false, false));
+    assertThat(result.getActions().getCreate()).isFalse();
+  }
+
   @Test
   public void fail_if_project_does_not_exist() {
     expectedException.expect(NotFoundException.class);
@@ -408,18 +468,19 @@ public class SearchActionTest {
 
   @Test
   public void json_example() {
+    OrganizationDto organization = db.organizations().insertForKey("My Organization");
     // languages
     Language cs = newLanguage("cs", "C#");
     Language java = newLanguage("java", "Java");
     Language python = newLanguage("py", "Python");
     // profiles
-    QProfileDto sonarWayCs = db.qualityProfiles().insert(db.getDefaultOrganization(),
+    QProfileDto sonarWayCs = db.qualityProfiles().insert(organization,
       p -> p.setName("Sonar way").setKee("AU-TpxcA-iU5OvuD2FL3").setIsBuiltIn(true).setLanguage(cs.getKey()));
-    QProfileDto myCompanyProfile = db.qualityProfiles().insert(db.getDefaultOrganization(),
+    QProfileDto myCompanyProfile = db.qualityProfiles().insert(organization,
       p -> p.setName("My Company Profile").setKee("iU5OvuD2FLz").setLanguage(java.getKey()));
-    QProfileDto myBuProfile = db.qualityProfiles().insert(db.getDefaultOrganization(),
+    QProfileDto myBuProfile = db.qualityProfiles().insert(organization,
       p -> p.setName("My BU Profile").setKee("AU-TpxcA-iU5OvuD2FL1").setParentKee(myCompanyProfile.getKee()).setLanguage(java.getKey()));
-    QProfileDto sonarWayPython = db.qualityProfiles().insert(db.getDefaultOrganization(),
+    QProfileDto sonarWayPython = db.qualityProfiles().insert(organization,
       p -> p.setName("Sonar way").setKee("AU-TpxcB-iU5OvuD2FL7").setIsBuiltIn(true).setLanguage(python.getKey()));
     db.qualityProfiles().setAsDefault(sonarWayCs, myCompanyProfile, sonarWayPython);
     // rules
@@ -440,12 +501,17 @@ public class SearchActionTest {
       .forEach(rule -> db.qualityProfiles().activateRule(sonarWayCs, rule));
     // project
     range(0, 7)
-      .mapToObj(i -> db.components().insertPrivateProject())
+      .mapToObj(i -> db.components().insertPrivateProject(organization))
       .forEach(project -> db.qualityProfiles().associateWithProject(project, myBuProfile));
+    // User
+    UserDto user = db.users().insertUser();
+    db.qualityProfiles().addUserPermission(sonarWayCs, user);
+    db.qualityProfiles().addUserPermission(myBuProfile, user);
+    userSession.logIn(user);
 
-    underTest = new SearchAction(new Languages(cs, java, python), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null));
+    underTest = new SearchAction(userSession, new Languages(cs, java, python), dbClient, qProfileWsSupport, new ComponentFinder(dbClient, null));
     ws = new WsActionTester(underTest);
-    String result = ws.newRequest().execute().getInput();
+    String result = ws.newRequest().setParam(PARAM_ORGANIZATION, organization.getKey()).execute().getInput();
     assertJson(result).ignoreFields("ruleUpdatedAt", "lastUsed", "userUpdatedAt")
       .isSimilarTo(ws.getDef().responseExampleAsString());
   }
index cd3143aa43ac49e0d89cf5a385a6af78b1811f2d..ae1097dd5b541cdb7f4e3bae8876cfc1779db677 100644 (file)
@@ -49,13 +49,16 @@ message SearchWsResponse {
     optional string organization = 15;
     optional bool isBuiltIn = 16;
     optional Actions actions = 17;
+
+    message Actions {
+        optional bool edit = 1;
+        optional bool setAsDefault = 2;
+        optional bool copy = 3;
+      }
   }
 
   message Actions {
     optional bool create = 1;
-    optional bool edit = 2;
-    optional bool setAsDefault = 3;
-    optional bool copy = 4;
   }
 }
 
index 4d4764ef87bfd38b797309008f3f2ff58f5b0fbc..02c71b7a9ad8f3bcc318b8bd21c4f6a082cfd037 100644 (file)
@@ -27,17 +27,20 @@ import org.sonarqube.tests.Category6Suite;
 import org.sonarqube.tests.Tester;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Organizations.Organization;
+import org.sonarqube.ws.QualityProfiles;
 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.permission.AddUserWsRequest;
 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 org.sonarqube.ws.client.qualityprofile.SearchWsRequest;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
@@ -57,12 +60,7 @@ public class QualityProfilesEditTest {
     WsUsers.CreateWsResponse.User user1 = tester.users().generateMember(organization, u -> u.setEmail("user1@email.com"));
     WsUsers.CreateWsResponse.User user2 = tester.users().generateMember(organization, u -> u.setEmail("user2@email.com"));
     CreateWsResponse.QualityProfile xooProfile = tester.qProfiles().createXooProfile(organization);
-    tester.qProfiles().service().addUser(AddUserRequest.builder()
-      .setOrganization(organization.getKey())
-      .setQualityProfile(xooProfile.getName())
-      .setLanguage(xooProfile.getLanguage())
-      .setUserLogin(user1.getLogin())
-      .build());
+    addUserPermission(organization, user1, xooProfile);
 
     SearchUsersResponse users = tester.qProfiles().service().searchUsers(SearchUsersRequest.builder()
       .setOrganization(organization.getKey())
@@ -98,12 +96,7 @@ public class QualityProfilesEditTest {
       .isEmpty();
 
     // Add user 1
-    tester.qProfiles().service().addUser(AddUserRequest.builder()
-      .setOrganization(organization.getKey())
-      .setQualityProfile(xooProfile.getName())
-      .setLanguage(xooProfile.getLanguage())
-      .setUserLogin(user1.getLogin())
-      .build());
+    addUserPermission(organization, user1, xooProfile);
     assertThat(tester.qProfiles().service().searchUsers(SearchUsersRequest.builder()
       .setOrganization(organization.getKey())
       .setQualityProfile(xooProfile.getName())
@@ -137,18 +130,8 @@ public class QualityProfilesEditTest {
     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());
+    addGroupPermission(organization, group1, xooProfile);
+    addGroupPermission(organization, group2, xooProfile);
 
     SearchGroupsResponse groups = tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder()
       .setOrganization(organization.getKey())
@@ -181,13 +164,8 @@ public class QualityProfilesEditTest {
       .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());
+    // Add group 1
+    addGroupPermission(organization, group1, xooProfile);
     assertThat(tester.qProfiles().service().searchGroups(SearchGroupsRequest.builder()
       .setOrganization(organization.getKey())
       .setQualityProfile(xooProfile.getName())
@@ -197,7 +175,7 @@ public class QualityProfilesEditTest {
       .extracting(Group::getName)
       .containsExactlyInAnyOrder(group1.getName());
 
-    // Remove user 1
+    // Remove group 1
     tester.qProfiles().service().removeGroup(RemoveGroupRequest.builder()
       .setOrganization(organization.getKey())
       .setQualityProfile(xooProfile.getName())
@@ -213,4 +191,61 @@ public class QualityProfilesEditTest {
       .extracting(Group::getName)
       .isEmpty();
   }
+
+  @Test
+  public void actions_when_user_can_edit_profiles() {
+    Organization organization = tester.organizations().generate();
+    WsUsers.CreateWsResponse.User user = tester.users().generateMember(organization);
+    CreateWsResponse.QualityProfile xooProfile1 = tester.qProfiles().createXooProfile(organization);
+    addUserPermission(organization, user, xooProfile1);
+    CreateWsResponse.QualityProfile xooProfile2 = tester.qProfiles().createXooProfile(organization);
+    WsUserGroups.Group group = tester.groups().generate(organization);
+    tester.groups().addMemberToGroups(organization, user.getLogin(), group.getName());
+    addGroupPermission(organization, group, xooProfile2);
+    CreateWsResponse.QualityProfile xooProfile3 = tester.qProfiles().createXooProfile(organization);
+
+    QualityProfiles.SearchWsResponse result = tester.as(user.getLogin())
+      .qProfiles().service().search(new SearchWsRequest().setOrganizationKey(organization.getKey()));
+    assertThat(result.getActions().getCreate()).isFalse();
+    assertThat(result.getProfilesList())
+      .extracting(QualityProfiles.SearchWsResponse.QualityProfile::getKey, qp -> qp.getActions().getEdit(), qp -> qp.getActions().getCopy(), qp -> qp.getActions().getSetAsDefault())
+    .contains(
+      tuple(xooProfile1.getKey(), true, false, false),
+      tuple(xooProfile2.getKey(), true, false, false),
+      tuple(xooProfile3.getKey(), false, false, false));
+  }
+
+  @Test
+  public void actions_when_user_is_global_qprofile_administer() {
+    Organization organization = tester.organizations().generate();
+    WsUsers.CreateWsResponse.User user = tester.users().generateMember(organization);
+    CreateWsResponse.QualityProfile xooProfile = tester.qProfiles().createXooProfile(organization);
+    tester.wsClient().permissions().addUser(new AddUserWsRequest().setOrganization(organization.getKey()).setLogin(user.getLogin()).setPermission("profileadmin"));
+
+    QualityProfiles.SearchWsResponse result = tester.as(user.getLogin())
+      .qProfiles().service().search(new SearchWsRequest().setOrganizationKey(organization.getKey()));
+    assertThat(result.getActions().getCreate()).isTrue();
+    assertThat(result.getProfilesList())
+      .extracting(QualityProfiles.SearchWsResponse.QualityProfile::getKey, qp -> qp.getActions().getEdit(), qp -> qp.getActions().getCopy(), qp -> qp.getActions().getSetAsDefault())
+      .contains(
+        tuple(xooProfile.getKey(), true, true, true));
+  }
+
+  private void addUserPermission(Organization organization, WsUsers.CreateWsResponse.User user, CreateWsResponse.QualityProfile qProfile){
+    tester.qProfiles().service().addUser(AddUserRequest.builder()
+      .setOrganization(organization.getKey())
+      .setQualityProfile(qProfile.getName())
+      .setLanguage(qProfile.getLanguage())
+      .setUserLogin(user.getLogin())
+      .build());
+  }
+
+  private void addGroupPermission(Organization organization, WsUserGroups.Group group, CreateWsResponse.QualityProfile qProfile){
+    tester.qProfiles().service().addGroup(AddGroupRequest.builder()
+      .setOrganization(organization.getKey())
+      .setQualityProfile(qProfile.getName())
+      .setLanguage(qProfile.getLanguage())
+      .setGroup(group.getName())
+      .build());
+  }
 }