]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8647 add organization param to api/components/search_projects 1573/head
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 26 Jan 2017 16:36:57 +0000 (17:36 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 27 Jan 2017 09:05:57 +0000 (10:05 +0100)
server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java

index 294e833b7c5cf7a9cbf9ace3e8cd2882b2a2e35b..a7483c0d514d168eece5a5b0a0f4260e4576c10d 100644 (file)
 package org.sonar.server.component.ws;
 
 import com.google.common.base.Splitter;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import org.apache.commons.lang.StringUtils;
+import java.util.stream.StreamSupport;
+import javax.annotation.Nullable;
 import org.sonar.api.measures.Metric.Level;
+import org.sonar.core.util.stream.Collectors;
 import org.sonar.server.measure.index.ProjectMeasuresQuery;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -43,27 +47,34 @@ class ProjectMeasuresQueryFactory {
     // prevent instantiation
   }
 
-  static ProjectMeasuresQuery newProjectMeasuresQuery(String filter, Set<String> favoriteProjectUuids) {
-    if (StringUtils.isBlank(filter)) {
-      return new ProjectMeasuresQuery();
-    }
+  static List<String> toCriteria(String filter) {
+    return StreamSupport.stream(CRITERIA_SPLITTER.split(filter).spliterator(), false)
+      .filter(Objects::nonNull)
+      .filter(criterion -> !criterion.isEmpty())
+      .collect(Collectors.toList());
+  }
 
-    ProjectMeasuresQuery query = new ProjectMeasuresQuery();
+  static boolean hasIsFavouriteCriterion(List<String> criteria) {
+    return criteria.stream().anyMatch(IS_FAVORITE_CRITERION::equalsIgnoreCase);
+  }
 
-    CRITERIA_SPLITTER.split(filter)
-      .forEach(criteria -> processCriterion(criteria, query, favoriteProjectUuids));
-    return query;
+  static ProjectMeasuresQuery newProjectMeasuresQuery(List<String> criteria, @Nullable Set<String> projectUuids) {
+    ProjectMeasuresQuery res = new ProjectMeasuresQuery();
+    if (projectUuids != null) {
+      res.setProjectUuids(projectUuids);
+    }
+    criteria.forEach(criterion -> processCriterion(criterion, res));
+    return res;
   }
 
-  private static void processCriterion(String rawCriterion, ProjectMeasuresQuery query, Set<String> favoriteProjectUuids) {
+  private static void processCriterion(String rawCriterion, ProjectMeasuresQuery query) {
     String criterion = rawCriterion.trim();
 
-    try {
-      if (IS_FAVORITE_CRITERION.equalsIgnoreCase(criterion)) {
-        query.setProjectUuids(favoriteProjectUuids);
-        return;
-      }
+    if (IS_FAVORITE_CRITERION.equalsIgnoreCase(criterion)) {
+      return;
+    }
 
+    try {
       Matcher matcher = CRITERIA_PATTERN.matcher(criterion);
       checkArgument(matcher.find() && matcher.groupCount() == 3, "Criterion should be 'isFavourite' or criterion should have a metric, an operator and a value");
       String metric = matcher.group(1).toLowerCase(ENGLISH);
index 8bbc7cdda0b1d6f792ff7fe9db6c9662e96312e1..005ee55d449bfab875ace492f31e590c7c84067b 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.sonar.server.component.ws;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Ordering;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -30,6 +31,8 @@ import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collector;
 import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -56,10 +59,15 @@ import org.sonarqube.ws.client.component.SearchProjectsRequest;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static java.lang.String.format;
+import static org.sonar.core.util.stream.Collectors.toSet;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.hasIsFavouriteCriterion;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.toCriteria;
 import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;
 import static org.sonarqube.ws.client.component.SearchProjectsRequest.DEFAULT_PAGE_SIZE;
 import static org.sonarqube.ws.client.component.SearchProjectsRequest.MAX_PAGE_SIZE;
 
@@ -86,6 +94,10 @@ public class SearchProjectsAction implements ComponentsWsAction {
       .setResponseExample(getClass().getResource("search_projects-example.json"))
       .setHandler(this);
 
+    action.createParam(PARAM_ORGANIZATION)
+      .setDescription("the organization to search projects in")
+      .setRequired(false)
+      .setSince("6.3");
     action.createParam(Param.FACETS)
       .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.")
       .setPossibleValues(SUPPORTED_FACETS);
@@ -130,22 +142,39 @@ public class SearchProjectsAction implements ComponentsWsAction {
 
   private SearchProjectsWsResponse doHandle(SearchProjectsRequest request) {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      SearchResults searchResults = searchData(dbSession, request);
-      Set<String> organizationUuids = searchResults.projects.stream().map(ComponentDto::getOrganizationUuid).collect(Collectors.toSet());
-      Map<String, OrganizationDto> organizationsByUuid = dbClient.organizationDao().selectByUuids(dbSession, organizationUuids)
-        .stream()
-        .collect(Collectors.uniqueIndex(OrganizationDto::getUuid));
-
-      return buildResponse(request, searchResults, organizationsByUuid);
+      String organizationKey = request.getOrganization();
+      if (organizationKey == null) {
+        return handleForAnyOrganization(dbSession, request);
+      } else {
+        OrganizationDto organization = checkFoundWithOptional(
+          dbClient.organizationDao().selectByKey(dbSession, organizationKey),
+          "No organization for key '%s'", organizationKey);
+        return handleForOrganization(dbSession, request, organization);
+      }
     }
   }
 
-  private SearchResults searchData(DbSession dbSession, SearchProjectsRequest request) {
-    String filter = firstNonNull(request.getFilter(), "");
+  private SearchProjectsWsResponse handleForAnyOrganization(DbSession dbSession, SearchProjectsRequest request) {
+    SearchResults searchResults = searchData(dbSession, request, null);
+    Set<String> organizationUuids = searchResults.projects.stream().map(ComponentDto::getOrganizationUuid).collect(toSet());
+    Map<String, OrganizationDto> organizationsByUuid = dbClient.organizationDao().selectByUuids(dbSession, organizationUuids)
+      .stream()
+      .collect(Collectors.uniqueIndex(OrganizationDto::getUuid));
+    return buildResponse(request, searchResults, organizationsByUuid);
+  }
+
+  private SearchProjectsWsResponse handleForOrganization(DbSession dbSession, SearchProjectsRequest request, OrganizationDto organization) {
+    SearchResults searchResults = searchData(dbSession, request, organization);
+    return buildResponse(request, searchResults, ImmutableMap.of(organization.getUuid(), organization));
+  }
+
+  private SearchResults searchData(DbSession dbSession, SearchProjectsRequest request, @Nullable OrganizationDto organization) {
+    List<String> criteria = toCriteria(firstNonNull(request.getFilter(), ""));
 
-    Set<String> favoriteProjectUuids = searchFavoriteProjects(dbSession);
+    List<ComponentDto> favoriteProjects = searchFavoriteProjects(dbSession);
+    Set<String> projectUuids = buildFilterOnProjectUuids(dbSession, criteria, favoriteProjects, organization);
 
-    ProjectMeasuresQuery query = newProjectMeasuresQuery(filter, favoriteProjectUuids);
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(criteria, projectUuids);
     queryValidator.validate(dbSession, query);
 
     SearchIdResult<String> esResults = index.search(query, new SearchOptions()
@@ -155,27 +184,60 @@ public class SearchProjectsAction implements ComponentsWsAction {
     Ordering<ComponentDto> ordering = Ordering.explicit(esResults.getIds()).onResultOf(ComponentDto::uuid);
     List<ComponentDto> projects = ordering.immutableSortedCopy(dbClient.componentDao().selectByUuids(dbSession, esResults.getIds()));
 
-    return new SearchResults(projects, favoriteProjectUuids, esResults);
+    return new SearchResults(projects, favoriteProjects.stream().map(ComponentDto::uuid).collect(toSet()), esResults);
+  }
+
+  /**
+   * Builds the set of project uuid on which the query on index measure should be filtering.
+   * <ul>
+   *   <li>if neither isFavourite criterion nor an organization is specified, there is not filtering on projects at all</li>
+   *   <li>if isFavourite criterion and an organization are specified, filtering is done on favourite projects of
+   *   the user which belong to the specified organization</li>
+   *   <li>if only isFavourite criterion is specified, filtering is done on favourite projects of the user</li>
+   *   <li>if only an organization is specified, filtering is done on the projects of this organization</li>
+   * </ul>
+   */
+  @CheckForNull
+  private Set<String> buildFilterOnProjectUuids(DbSession dbSession, List<String> criteria, List<ComponentDto> favoriteProjects, @Nullable OrganizationDto organization) {
+    boolean hasIsFavouriteCriterion = hasIsFavouriteCriterion(criteria);
+    if (hasIsFavouriteCriterion && organization != null) {
+      return favoriteProjects.stream()
+        .filter(project -> project.getOrganizationUuid().equals(organization.getUuid()))
+        .map(ComponentDto::uuid)
+        .collect(toSet());
+    }
+    if (hasIsFavouriteCriterion) {
+      return favoriteProjects.stream()
+        .map(ComponentDto::uuid)
+        .collect(toSet());
+    }
+    if (organization != null) {
+      return dbClient.componentDao().selectAllRootsByOrganization(dbSession, organization.getUuid())
+        .stream()
+        .filter(componentDto -> Qualifiers.PROJECT.equals(componentDto.qualifier()))
+        .map(ComponentDto::uuid)
+        .collect(toSet());
+    }
+    return null;
   }
 
-  private Set<String> searchFavoriteProjects(DbSession dbSession) {
-    List<Long> favoriteDbIds = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserId(userSession.getUserId())
-      .setKey("favourite")
-      .build(), dbSession)
+  private List<ComponentDto> searchFavoriteProjects(DbSession dbSession) {
+    List<Long> favoriteDbIds = dbClient.propertiesDao().selectByQuery(
+      PropertyQuery.builder()
+        .setUserId(userSession.getUserId())
+        .setKey("favourite")
+        .build(),
+      dbSession)
       .stream()
       .map(PropertyDto::getResourceId)
       .collect(Collectors.toList());
 
-    return dbClient.componentDao().selectByIds(dbSession, favoriteDbIds)
-      .stream()
-      .filter(dbComponent -> Qualifiers.PROJECT.equals(dbComponent.qualifier()))
-      .map(ComponentDto::uuid)
-      .collect(Collectors.toSet());
+    return dbClient.componentDao().selectByIds(dbSession, favoriteDbIds);
   }
 
   private static SearchProjectsRequest toRequest(Request httpRequest) {
     SearchProjectsRequest.Builder request = SearchProjectsRequest.builder()
+      .setOrganization(httpRequest.param(PARAM_ORGANIZATION))
       .setFilter(httpRequest.param(PARAM_FILTER))
       .setPage(httpRequest.mandatoryParamAsInt(Param.PAGE))
       .setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE));
index ea80e3c427369d6215fceb69ae891b2c9a29cf2d..6253d59e439a722127e6b7d2e9842e0defc2ce4d 100644 (file)
 
 package org.sonar.server.component.ws;
 
+import java.util.Collections;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.server.measure.index.ProjectMeasuresQuery;
 import org.sonar.server.tester.UserSessionRule;
 
+import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.toCriteria;
 import static org.sonar.server.computation.task.projectanalysis.measure.Measure.Level.OK;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
@@ -43,7 +46,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc > 10 and coverage <= 80", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc > 10 and coverage <= 80"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -54,7 +57,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_having_lesser_than_operation() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc < 10", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc < 10"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -63,7 +66,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_having_lesser_than_or_equals_operation() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc <= 10", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc <= 10"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -72,7 +75,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_having_greater_than_operation() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc > 10", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -81,7 +84,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_having_greater_than_or_equals_operation() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc >= 10", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc >= 10"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -90,7 +93,7 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_having_equal_operation() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc = 10", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc = 10"), emptySet());
 
     assertThat(query.getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
@@ -99,21 +102,42 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void create_query_on_quality_gate() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("alert_status = OK", emptySet());
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("alert_status = OK"), emptySet());
 
     assertThat(query.getQualityGateStatus().name()).isEqualTo(OK.name());
   }
 
   @Test
-  public void query_without_favorites_by_default() {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("ncloc = 10", emptySet());
+  public void do_not_filter_on_projectUuids_if_criteria_non_empty_and_projectUuid_is_null() {
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc = 10"), null);
 
     assertThat(query.doesFilterOnProjectUuids()).isFalse();
   }
 
   @Test
-  public void create_query_with_favorites() throws Exception {
-    ProjectMeasuresQuery query = newProjectMeasuresQuery("isFavorite", emptySet());
+  public void filter_on_projectUuids_if_projectUuid_is_empty_and_criteria_non_empty() throws Exception {
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet());
+
+    assertThat(query.doesFilterOnProjectUuids()).isTrue();
+  }
+
+  @Test
+  public void filter_on_projectUuids_if_projectUuid_is_non_empty_and_criteria_non_empty() throws Exception {
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(toCriteria("ncloc > 10"), Collections.singleton("foo"));
+
+    assertThat(query.doesFilterOnProjectUuids()).isTrue();
+  }
+
+  @Test
+  public void filter_on_projectUuids_if_projectUuid_is_empty_and_criteria_is_empty() throws Exception {
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(emptyList(), emptySet());
+
+    assertThat(query.doesFilterOnProjectUuids()).isTrue();
+  }
+
+  @Test
+  public void filter_on_projectUuids_if_projectUuid_is_non_empty_and_criteria_empty() throws Exception {
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(emptyList(), Collections.singleton("foo"));
 
     assertThat(query.doesFilterOnProjectUuids()).isTrue();
   }
@@ -121,17 +145,17 @@ public class ProjectMeasuresQueryFactoryTest {
   @Test
   public void fail_to_create_query_on_quality_gate_when_operator_is_not_equal() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
-    newProjectMeasuresQuery("alert_status > OK", emptySet());
+    newProjectMeasuresQuery(toCriteria("alert_status > OK"), emptySet());
   }
 
   @Test
   public void search_is_case_insensitive() throws Exception {
-    assertThat(newProjectMeasuresQuery("ncloc > 10 AnD coverage <= 80 AND debt = 10 AND issues = 20", emptySet()).getMetricCriteria()).hasSize(4);
+    assertThat(newProjectMeasuresQuery(toCriteria("ncloc > 10 AnD coverage <= 80 AND debt = 10 AND issues = 20"), emptySet()).getMetricCriteria()).hasSize(4);
   }
 
   @Test
   public void convert_metric_to_lower_case() throws Exception {
-    assertThat(newProjectMeasuresQuery("NCLOC > 10 AND coVERage <= 80", emptySet()).getMetricCriteria())
+    assertThat(newProjectMeasuresQuery(toCriteria("NCLOC > 10 AND coVERage <= 80"), emptySet()).getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
       .containsOnly(
         tuple("ncloc", Operator.GT, 10d),
@@ -140,14 +164,14 @@ public class ProjectMeasuresQueryFactoryTest {
 
   @Test
   public void ignore_white_spaces() throws Exception {
-    assertThat(newProjectMeasuresQuery("   ncloc    >    10   ", emptySet()).getMetricCriteria())
+    assertThat(newProjectMeasuresQuery(toCriteria("   ncloc    >    10   "), emptySet()).getMetricCriteria())
       .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
       .containsOnly(tuple("ncloc", Operator.GT, 10d));
   }
 
   @Test
   public void accept_empty_query() throws Exception {
-    ProjectMeasuresQuery result = newProjectMeasuresQuery("", emptySet());
+    ProjectMeasuresQuery result = newProjectMeasuresQuery(emptyList(), emptySet());
 
     assertThat(result.getMetricCriteria()).isEmpty();
   }
@@ -156,7 +180,8 @@ public class ProjectMeasuresQueryFactoryTest {
   public void fail_on_invalid_criteria() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion 'ncloc ? 10'");
-    newProjectMeasuresQuery("ncloc ? 10", emptySet());
+
+    newProjectMeasuresQuery(toCriteria("ncloc ? 10"), emptySet());
   }
 
   @Test
@@ -164,34 +189,34 @@ public class ProjectMeasuresQueryFactoryTest {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion 'ncloc ? 10'");
 
-    newProjectMeasuresQuery("    ncloc ? 10    ", emptySet());
+    newProjectMeasuresQuery(toCriteria("    ncloc ? 10    "), emptySet());
   }
 
   @Test
   public void fail_when_not_double() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion 'ncloc > ten'");
-    newProjectMeasuresQuery("ncloc > ten", emptySet());
+    newProjectMeasuresQuery(toCriteria("ncloc > ten"), emptySet());
   }
 
   @Test
   public void fail_when_no_operator() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion 'ncloc 10'");
-    newProjectMeasuresQuery("ncloc 10", emptySet());
+    newProjectMeasuresQuery(toCriteria("ncloc 10"), emptySet());
   }
 
   @Test
   public void fail_when_no_key() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion '>= 10'");
-    newProjectMeasuresQuery(">= 10", emptySet());
+    newProjectMeasuresQuery(toCriteria(">= 10"), emptySet());
   }
 
   @Test
   public void fail_when_no_value() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Invalid criterion 'ncloc >='");
-    newProjectMeasuresQuery("ncloc >=", emptySet());
+    newProjectMeasuresQuery(toCriteria("ncloc >="), emptySet());
   }
 }
index 7aa322f010852e15f0e683e20994a593acdfe68f..e3fb204883df5ee61ce4f45fe7196a325e98979d 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.db.DbTester;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.server.tester.UserSessionRule;
 
+import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static org.sonar.api.measures.Metric.ValueType.DATA;
 import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
@@ -38,6 +39,7 @@ import static org.sonar.api.measures.Metric.ValueType.STRING;
 import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
 import static org.sonar.db.metric.MetricTesting.newMetricDto;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
+import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.toCriteria;
 
 public class ProjectMeasuresQueryValidatorTest {
 
@@ -57,14 +59,14 @@ public class ProjectMeasuresQueryValidatorTest {
 
   @Test
   public void query_with_empty_metrics_is_valid() throws Exception {
-    underTest.validate(dbSession, newProjectMeasuresQuery("", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(emptyList(), emptySet()));
   }
 
   @Test
   public void does_not_fail_when_metric_criteria_contains_an_existing_metric() throws Exception {
     insertValidMetric("ncloc");
 
-    underTest.validate(dbSession, newProjectMeasuresQuery("ncloc > 10", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()));
   }
 
   @Test
@@ -77,7 +79,7 @@ public class ProjectMeasuresQueryValidatorTest {
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Following metrics are not numeric : [data, distrib, string]");
-    underTest.validate(dbSession, newProjectMeasuresQuery("data > 10 and distrib = 11 and ncloc <= 20 and debt < 30 and string = 40", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("data > 10 and distrib = 11 and ncloc <= 20 and debt < 30 and string = 40"), emptySet()));
   }
 
   @Test
@@ -86,7 +88,7 @@ public class ProjectMeasuresQueryValidatorTest {
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Following metrics are disabled : [ncloc]");
-    underTest.validate(dbSession, newProjectMeasuresQuery("ncloc > 10", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()));
   }
 
   @Test
@@ -95,7 +97,7 @@ public class ProjectMeasuresQueryValidatorTest {
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Unknown metric(s) [unknown]");
-    underTest.validate(dbSession, newProjectMeasuresQuery("unknown > 10", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("unknown > 10"), emptySet()));
   }
 
   @Test
@@ -104,7 +106,7 @@ public class ProjectMeasuresQueryValidatorTest {
 
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Unknown metric(s) [coverage, debt]");
-    underTest.validate(dbSession, newProjectMeasuresQuery("debt > 10 AND ncloc <= 20 AND coverage > 30", emptySet()));
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("debt > 10 AND ncloc <= 20 AND coverage > 30"), emptySet()));
   }
 
   private void insertValidMetric(String metricKey) {
index dbe896fa0b595f6bfa6f62495ca9ca2fd1dc3f37..726114b0bbac32797858b027ddbe02ef4cf8780d 100644 (file)
@@ -28,6 +28,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -76,10 +77,12 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDE
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURE;
 import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;
 
 public class SearchProjectsActionTest {
   private static final String NCLOC = "ncloc";
   private static final String COVERAGE = "coverage";
+  private static final String IS_FAVOURITE_CRITERION = "isFavorite";
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
@@ -103,11 +106,26 @@ public class SearchProjectsActionTest {
 
   private SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
 
+  @Test
+  public void verify_definition() {
+    WebService.Action def = ws.getDef();
+
+    assertThat(def.key()).isEqualTo("search_projects");
+    assertThat(def.since()).isEqualTo("6.2");
+    assertThat(def.isInternal()).isTrue();
+    assertThat(def.isPost()).isFalse();
+    assertThat(def.responseExampleAsString()).isNotEmpty();
+    Param organization = def.param("organization");
+    assertThat(organization.isRequired()).isFalse();
+    assertThat(organization.description()).isEqualTo("the organization to search projects in");
+    assertThat(organization.since()).isEqualTo("6.3");
+  }
+
   @Test
   public void json_example() {
     OrganizationDto organization1Dto = db.organizations().insertForKey("my-org-key-1");
     OrganizationDto organization2Dto = db.organizations().insertForKey("my-org-key-2");
-    long project1Id = insertProjectInDbAndEs(newProjectDto(organization1Dto)
+    ComponentDto project1 = insertProjectInDbAndEs(newProjectDto(organization1Dto)
       .setUuid(Uuids.UUID_EXAMPLE_01)
       .setKey(KeyExamples.KEY_PROJECT_EXAMPLE_001)
       .setName("My Project 1"));
@@ -120,7 +138,7 @@ public class SearchProjectsActionTest {
       .setKey(KeyExamples.KEY_PROJECT_EXAMPLE_003)
       .setName("My Project 3"));
     userSession.login().setUserId(23);
-    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite").setResourceId(project1Id).setUserId(23L));
+    addFavourite(project1);
     dbSession.commit();
 
     String result = ws.newRequest().execute().getInput();
@@ -195,13 +213,83 @@ public class SearchProjectsActionTest {
     assertThat(result.getComponents(0).getName()).isEqualTo("Sonar Markdown");
   }
 
+  @Test
+  public void filter_projects_with_query_within_specified_organization() {
+    OrganizationDto organization1 = db.organizations().insert();
+    OrganizationDto organization2 = db.organizations().insert();
+    insertProjectInDbAndEs(newProjectDto(organization1).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization1).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization2).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d)));
+    insertMetrics(COVERAGE, NCLOC);
+
+    assertThat(call(request.setOrganization(null)).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly("Sonar Java", "Sonar Markdown", "Sonar Qube");
+    assertThat(call(request.setOrganization(organization1.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly("Sonar Java", "Sonar Markdown");
+    assertThat(call(request.setOrganization(organization2.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly("Sonar Qube");
+  }
+
+  @Test
+  public void filter_favourite_projects_with_query_with_or_without_a_specified_organization() {
+    OrganizationDto organization1 = db.organizations().insert();
+    OrganizationDto organization2 = db.organizations().insert();
+    OrganizationDto organization3 = db.organizations().insert();
+    OrganizationDto organization4 = db.organizations().insert();
+    OrganizationDto organization5 = db.organizations().insert();
+    List<Map<String, Object>> someMeasure = singletonList(newMeasure(COVERAGE, 81));
+    ComponentDto favourite1_1 = insertProjectInDbAndEs(newProjectDto(organization1), someMeasure);
+    ComponentDto favourite1_2 = insertProjectInDbAndEs(newProjectDto(organization1), someMeasure);
+    ComponentDto nonFavourite1 = insertProjectInDbAndEs(newProjectDto(organization1), someMeasure);
+    ComponentDto favourite2 = insertProjectInDbAndEs(newProjectDto(organization2), someMeasure);
+    ComponentDto nonFavourite2 = insertProjectInDbAndEs(newProjectDto(organization2), someMeasure);
+    ComponentDto favourite3 = insertProjectInDbAndEs(newProjectDto(organization3), someMeasure);
+    ComponentDto nonFavourite4 = insertProjectInDbAndEs(newProjectDto(organization4), someMeasure);
+    Stream.of(favourite1_1, favourite1_2, favourite2, favourite3)
+      .forEach(this::addFavourite);
+    insertMetrics(COVERAGE, NCLOC);
+
+    assertThat(call(request.setFilter(null).setOrganization(null)).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite1_1.name(), favourite1_2.name(), nonFavourite1.name(), favourite2.name(), nonFavourite2.name(), favourite3.name(), nonFavourite4.name());
+    assertThat(call(request.setFilter(IS_FAVOURITE_CRITERION).setOrganization(null)).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite1_1.name(), favourite1_2.name(), favourite2.name(), favourite3.name());
+    assertThat(call(request.setFilter(null).setOrganization(organization1.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite1_1.name(), favourite1_2.name(), nonFavourite1.name());
+    assertThat(call(request.setFilter(IS_FAVOURITE_CRITERION).setOrganization(organization1.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite1_1.name(), favourite1_2.name());
+    assertThat(call(request.setFilter(null).setOrganization(organization3.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite3.name());
+    assertThat(call(request.setFilter(IS_FAVOURITE_CRITERION).setOrganization(organization3.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(favourite3.name());
+    assertThat(call(request.setFilter(null).setOrganization(organization4.getKey())).getComponentsList())
+      .extracting(Component::getName)
+      .containsOnly(nonFavourite4.name());
+    assertThat(call(request.setFilter(IS_FAVOURITE_CRITERION).setOrganization(organization4.getKey())).getComponentsList())
+      .isEmpty();
+    assertThat(call(request.setFilter(null).setOrganization(organization5.getKey())).getComponentsList())
+      .isEmpty();
+    assertThat(call(request.setFilter(IS_FAVOURITE_CRITERION).setOrganization(organization5.getKey())).getComponentsList())
+      .isEmpty();
+  }
+
   @Test
   public void filter_projects_on_favorites() {
-    long javaId = insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization(), "java-id").setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 10_000d)));
-    long markDownId = insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization(), "markdown-id").setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    ComponentDto javaProject = insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization(), "java-id").setName("Sonar Java"),
+      newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 10_000d)));
+    ComponentDto markDownProject = insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization(), "markdown-id").setName("Sonar Markdown"),
+      newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
     insertProjectInDbAndEs(newProjectDto(db.organizations().insert()).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d)));
-    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite").setResourceId(javaId).setUserId(Long.valueOf(userSession.getUserId())));
-    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite").setResourceId(markDownId).setUserId(Long.valueOf(userSession.getUserId())));
+    addFavourite(javaProject);
+    addFavourite(markDownProject);
     dbSession.commit();
     request.setFilter("isFavorite");
 
@@ -237,10 +325,11 @@ public class SearchProjectsActionTest {
 
   @Test
   public void return_nloc_facet() {
-    insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization()).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
-    insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization()).setName("Sonar Groovy"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
-    insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization()).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
-    insertProjectInDbAndEs(newProjectDto(db.getDefaultOrganization()).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
+    OrganizationDto organization = db.getDefaultOrganization();
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Groovy"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
     insertMetrics(COVERAGE, NCLOC);
     SearchProjectsWsResponse result = call(request.setFacets(singletonList(NCLOC)));
 
@@ -281,6 +370,10 @@ public class SearchProjectsActionTest {
     TestRequest httpRequest = ws.newRequest()
       .setMediaType(MediaTypes.PROTOBUF);
 
+    String organization = wsRequest.getOrganization();
+    if (organization != null) {
+      httpRequest.setParam(PARAM_ORGANIZATION, organization);
+    }
     httpRequest.setParam(Param.PAGE, String.valueOf(wsRequest.getPage()));
     httpRequest.setParam(Param.PAGE_SIZE, String.valueOf(wsRequest.getPageSize()));
     String filter = wsRequest.getFilter();
@@ -296,23 +389,12 @@ public class SearchProjectsActionTest {
     }
   }
 
-  @Test
-  public void definition() {
-    WebService.Action def = ws.getDef();
-
-    assertThat(def.key()).isEqualTo("search_projects");
-    assertThat(def.since()).isEqualTo("6.2");
-    assertThat(def.isInternal()).isTrue();
-    assertThat(def.isPost()).isFalse();
-    assertThat(def.responseExampleAsString()).isNotEmpty();
-  }
-
-  private long insertProjectInDbAndEs(ComponentDto project) {
+  private ComponentDto insertProjectInDbAndEs(ComponentDto project) {
     return insertProjectInDbAndEs(project, emptyList());
   }
 
-  private long insertProjectInDbAndEs(ComponentDto project, List<Map<String, Object>> measures) {
-    componentDb.insertComponent(project);
+  private ComponentDto insertProjectInDbAndEs(ComponentDto project, List<Map<String, Object>> measures) {
+    ComponentDto res = componentDb.insertComponent(project);
     try {
       es.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURE,
         new ProjectMeasuresDoc().setId(project.uuid()).setKey(project.key()).setName(project.name()).setMeasures(measures));
@@ -321,7 +403,7 @@ public class SearchProjectsActionTest {
       Throwables.propagate(e);
     }
 
-    return project.getId();
+    return res;
   }
 
   private void insertMetrics(String... metricKeys) {
@@ -334,4 +416,8 @@ public class SearchProjectsActionTest {
   private static Map<String, Object> newMeasure(String key, double value) {
     return ImmutableMap.of("key", key, "value", value);
   }
+
+  private void addFavourite(ComponentDto project) {
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite").setResourceId(project.getId()).setUserId(Long.valueOf(userSession.getUserId())));
+  }
 }
index eb648f2410927e9e59cfc2dc904554f417f1b834..bd555b39a44ce69a67ca935e390167a26743f28c 100644 (file)
@@ -45,6 +45,7 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FRO
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_KEY;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_NEW_KEY;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_TO;
@@ -105,6 +106,7 @@ public class ComponentsService extends BaseService {
 
   public SearchProjectsWsResponse searchProjects(SearchProjectsRequest request) {
     GetRequest get = new GetRequest(path(ACTION_SEARCH_PROJECTS))
+      .setParam(PARAM_ORGANIZATION, request.getOrganization())
       .setParam(PARAM_FILTER, request.getFilter())
       .setParam(Param.FACETS, request.getFacets())
       .setParam(Param.PAGE, request.getPage())
index 76487243281e6ebd29762e3fa908f03fba386cd0..16ae5feb354c859788f5957b926e3aa3a5cc20f4 100644 (file)
@@ -33,6 +33,7 @@ public class ComponentsWsParameters {
   public static final String ACTION_SUGGESTIONS = "suggestions";
 
   // parameters
+  public static final String PARAM_ORGANIZATION = "organization";
   public static final String PARAM_QUALIFIERS = "qualifiers";
   public static final String PARAM_LANGUAGE = "language";
   public static final String PARAM_BASE_COMPONENT_ID = "baseComponentId";
index 3b6ebceeceb26fe7b279ab8092a98f23a6157d8f..c30dd9655ec1e1e3e9264be76be7a3d520bbc90a 100644 (file)
@@ -23,6 +23,7 @@ package org.sonarqube.ws.client.component;
 import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
@@ -33,16 +34,23 @@ public class SearchProjectsRequest {
 
   private final int page;
   private final int pageSize;
+  private final String organization;
   private final String filter;
   private final List<String> facets;
 
   private SearchProjectsRequest(Builder builder) {
     this.page = builder.page;
     this.pageSize = builder.pageSize;
+    this.organization = builder.organization;
     this.filter = builder.filter;
     this.facets = builder.facets;
   }
 
+  @CheckForNull
+  public String getOrganization() {
+    return organization;
+  }
+
   @CheckForNull
   public String getFilter() {
     return filter;
@@ -65,6 +73,7 @@ public class SearchProjectsRequest {
   }
 
   public static class Builder {
+    private String organization;
     private Integer page;
     private Integer pageSize;
     private String filter;
@@ -74,7 +83,12 @@ public class SearchProjectsRequest {
       // enforce static factory method
     }
 
-    public Builder setFilter(String filter) {
+    public Builder setOrganization(@Nullable String organization) {
+      this.organization = organization;
+      return this;
+    }
+
+    public Builder setFilter(@Nullable String filter) {
       this.filter = filter;
       return this;
     }