]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9182 Add visibility to WS api/projects/search 2038/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 8 May 2017 17:31:41 +0000 (19:31 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 8 May 2017 17:31:41 +0000 (19:31 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java

index f5e6df3a26af83225e62116f60d68b159a88b821..72ed52dd99c18c007ac11d2c10bc3c2ca1e8adfd 100644 (file)
@@ -32,6 +32,7 @@ public class ComponentQuery {
   private final String nameOrKeyQuery;
   private final String[] qualifiers;
   private final String language;
+  private final Boolean isPrivate;
   private final Set<Long> componentIds;
 
   /**
@@ -46,6 +47,7 @@ public class ComponentQuery {
     this.qualifiers = Builder.validateQualifiers(qualifiers);
     this.language = null;
     this.componentIds = null;
+    this.isPrivate = null;
   }
 
   private ComponentQuery(Builder builder) {
@@ -53,6 +55,7 @@ public class ComponentQuery {
     this.qualifiers = builder.qualifiers;
     this.language = builder.language;
     this.componentIds = builder.componentIds;
+    this.isPrivate = builder.isPrivate;
   }
 
   public String[] getQualifiers() {
@@ -82,6 +85,11 @@ public class ComponentQuery {
     return componentIds;
   }
 
+  @CheckForNull
+  public Boolean getPrivate() {
+    return isPrivate;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -90,6 +98,7 @@ public class ComponentQuery {
     private String nameOrKeyQuery;
     private String[] qualifiers;
     private String language;
+    private Boolean isPrivate;
     private Set<Long> componentIds;
 
     public Builder setNameOrKeyQuery(@Nullable String nameOrKeyQuery) {
@@ -112,6 +121,11 @@ public class ComponentQuery {
       return this;
     }
 
+    public Builder setPrivate(@Nullable Boolean isPrivate) {
+      this.isPrivate = isPrivate;
+      return this;
+    }
+
     protected static String[] validateQualifiers(@Nullable String... qualifiers) {
       checkArgument(qualifiers != null && qualifiers.length > 0, "At least one qualifier must be provided");
       return qualifiers;
index 1c4920b51e3d846491064c19d0199ad89636b627..fa61bb41a3b2ff6f3a778726c263ba18302d3e96 100644 (file)
           upper(p.name) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
         )
       </if>
+      <if test="query.private!=null">
+        <if test="query.private.equals(true)">
+          and p.private=${_true}
+        </if>
+        <if test="query.private.equals(false)">
+          and p.private=${_false}
+        </if>
+      </if>
   </sql>
 
   <select id="selectDescendants" resultType="Component">
index 72e3356998669e27471e47538d818fa98e8b7936..7e3e3208d01d18e5d50c3fffa2ad4173269158f9 100644 (file)
@@ -960,6 +960,20 @@ public class ComponentDaoTest {
     assertThat(result.get(0).key()).isEqualTo("java-project-key");
   }
 
+  @Test
+  public void selectByQuery_filter_on_visibility() {
+    db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setKey("private-key"));
+    db.components().insertComponent(ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setKey("public-key"));
+
+    ComponentQuery privateProjectsQuery = ComponentQuery.builder().setPrivate(true).setQualifiers(Qualifiers.PROJECT).build();
+    ComponentQuery publicProjectsQuery = ComponentQuery.builder().setPrivate(false).setQualifiers(Qualifiers.PROJECT).build();
+    ComponentQuery allProjectsQuery = ComponentQuery.builder().setPrivate(null).setQualifiers(Qualifiers.PROJECT).build();
+
+    assertThat(underTest.selectByQuery(dbSession, privateProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsExactly("private-key");
+    assertThat(underTest.selectByQuery(dbSession, publicProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsExactly("public-key");
+    assertThat(underTest.selectByQuery(dbSession, allProjectsQuery, 0, 10)).extracting(ComponentDto::getKey).containsOnly("public-key", "private-key");
+  }
+
   @Test
   public void selectByQuery_on_empty_list_of_component_id() {
     ComponentQuery dbQuery = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT).setComponentIds(emptySet()).build();
index 47b94ec9082166e4b159aa2a7218b9800f06822e..9db9df75b8b6c9ff2bf0aff0e263407f531a2885 100644 (file)
@@ -31,8 +31,9 @@ import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentQuery;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.db.permission.OrganizationPermission;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.project.Visibility;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.WsProjects.SearchWsResponse;
 import org.sonarqube.ws.client.project.SearchWsRequest;
@@ -41,6 +42,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Optional.ofNullable;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.resources.Qualifiers.VIEW;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.project.Visibility.PRIVATE;
 import static org.sonar.server.project.Visibility.PUBLIC;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -50,6 +52,7 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
 
 public class SearchAction implements ProjectsWsAction {
 
@@ -84,6 +87,15 @@ public class SearchAction implements ProjectsWsAction {
       .setPossibleValues(PROJECT, VIEW)
       .setDefaultValue(PROJECT);
     support.addOrganizationParam(action);
+
+    action.createParam(PARAM_VISIBILITY)
+      .setDescription("Filter the projects that should be visible to everyone (%s), or only specific user/groups (%s).<br/>" +
+        "If no visibility is specified, the default project visibility of the organization will be used.",
+        Visibility.PUBLIC.getLabel(), Visibility.PRIVATE.getLabel())
+      .setRequired(false)
+      .setInternal(true)
+      .setSince("6.4")
+      .setPossibleValues(Visibility.getLabels());
   }
 
   @Override
@@ -98,7 +110,9 @@ public class SearchAction implements ProjectsWsAction {
       .setQualifiers(request.mandatoryParamAsStrings(PARAM_QUALIFIERS))
       .setQuery(request.param(Param.TEXT_QUERY))
       .setPage(request.mandatoryParamAsInt(Param.PAGE))
-      .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)).build();
+      .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
+      .setVisibility(request.param(PARAM_VISIBILITY))
+      .build();
   }
 
   private SearchWsResponse doHandle(SearchWsRequest request) {
@@ -115,10 +129,13 @@ public class SearchAction implements ProjectsWsAction {
 
   private static ComponentQuery buildQuery(SearchWsRequest request) {
     List<String> qualifiers = request.getQualifiers();
-    return ComponentQuery.builder()
+    ComponentQuery.Builder query = ComponentQuery.builder()
       .setNameOrKeyQuery(request.getQuery())
-      .setQualifiers(qualifiers.toArray(new String[qualifiers.size()]))
-      .build();
+      .setQualifiers(qualifiers.toArray(new String[qualifiers.size()]));
+
+    setNullable(request.getVisibility(), v -> query.setPrivate(Visibility.isPrivate(v)));
+
+    return query.build();
   }
 
   private Paging buildPaging(DbSession dbSession, SearchWsRequest request, OrganizationDto organization, ComponentQuery query) {
index 90ce3fd69ed0469f92de56e2283b7200fcfedece..603873ebddb4d7d56eb199d9dc2da7f6071304b9 100644 (file)
@@ -64,6 +64,7 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
 import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
 
 public class SearchActionTest {
 
@@ -97,6 +98,30 @@ public class SearchActionTest {
     assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key");
   }
 
+  @Test
+  public void search_private_projects() {
+    userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
+    db.components().insertComponents(
+      ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setKey("private-key"),
+      ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setKey("public-key"));
+
+    SearchWsResponse response = call(SearchWsRequest.builder().setVisibility("private").build());
+
+    assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("private-key");
+  }
+
+  @Test
+  public void search_public_projects() {
+    userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
+    db.components().insertComponents(
+      ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setKey("private-key"),
+      ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setKey("public-key"));
+
+    SearchWsResponse response = call(SearchWsRequest.builder().setVisibility("public").build());
+
+    assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("public-key");
+  }
+
   @Test
   public void search_projects_when_no_qualifier_set() throws IOException {
     userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
@@ -226,7 +251,7 @@ public class SearchActionTest {
     assertThat(action.isInternal()).isTrue();
     assertThat(action.since()).isEqualTo("6.3");
     assertThat(action.handler()).isEqualTo(ws.getDef().handler());
-    assertThat(action.params()).hasSize(5);
+    assertThat(action.params()).hasSize(6);
     assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json"));
 
     WebService.Param organization = action.param("organization");
@@ -254,6 +279,11 @@ public class SearchActionTest {
     assertThat(psParam.isRequired()).isFalse();
     assertThat(psParam.defaultValue()).isEqualTo("100");
     assertThat(psParam.description()).isEqualTo("Page size. Must be greater than 0 and less than 500");
+
+    WebService.Param visibilityParam = action.param("visibility");
+    assertThat(visibilityParam.isRequired()).isFalse();
+    assertThat(visibilityParam.description()).isEqualTo("Filter the projects that should be visible to everyone (public), or only specific user/groups (private).<br/>" +
+      "If no visibility is specified, the default project visibility of the organization will be used.");
   }
 
   @Test
@@ -281,6 +311,7 @@ public class SearchActionTest {
     setNullable(wsRequest.getQuery(), query -> request.setParam(TEXT_QUERY, query));
     setNullable(wsRequest.getPage(), page -> request.setParam(PAGE, String.valueOf(page)));
     setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(PAGE_SIZE, String.valueOf(pageSize)));
+    setNullable(wsRequest.getVisibility(), v -> request.setParam(PARAM_VISIBILITY, v));
     return request.executeProtobuf(SearchWsResponse.class);
   }
 
index 0bb627a9c61e4ec4f9f6b1dd3d72a2ed6ceaa84f..2b0be938a082fd93818af9b6d61d164b7b0e5876 100644 (file)
@@ -33,6 +33,7 @@ public class SearchWsRequest {
   private final String organization;
   private final String query;
   private final List<String> qualifiers;
+  private final String visibility;
   private final Integer page;
   private final Integer pageSize;
 
@@ -40,6 +41,7 @@ public class SearchWsRequest {
     this.organization = builder.organization;
     this.query = builder.query;
     this.qualifiers = builder.qualifiers;
+    this.visibility = builder.visibility;
     this.page = builder.page;
     this.pageSize = builder.pageSize;
   }
@@ -68,6 +70,11 @@ public class SearchWsRequest {
     return query;
   }
 
+  @CheckForNull
+  public String getVisibility() {
+    return visibility;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -78,6 +85,7 @@ public class SearchWsRequest {
     private Integer page;
     private Integer pageSize;
     private String query;
+    private String visibility;
 
     public Builder setOrganization(@Nullable String organization) {
       this.organization = organization;
@@ -104,6 +112,11 @@ public class SearchWsRequest {
       return this;
     }
 
+    public Builder setVisibility(@Nullable String visibility) {
+      this.visibility = visibility;
+      return this;
+    }
+
     public SearchWsRequest build() {
       checkArgument(pageSize == null || pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE);
       return new SearchWsRequest(this);