]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8287 Add filter parameter in api/components/search_projects
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 14 Oct 2016 15:28:11 +0000 (17:28 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 18 Oct 2016 15:01:19 +0000 (17:01 +0200)
17 files changed:
server/sonar-server/src/main/java/org/sonar/server/component/ws/BulkUpdateKeyAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/component/ws/UpdateKeyAction.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidatorTest.java [new file with mode: 0644]
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
sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java [new file with mode: 0644]
sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java

index c735e7afe99ad8191a0c3240f2f2bdb101e0c7d9..76f4e4c6036d6ee1bc83f6daf6233d77d905b847 100644 (file)
@@ -42,6 +42,7 @@ import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
 import static org.sonar.db.component.ComponentKeyUpdaterDao.checkIsProjectOrModule;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_BULK_UPDATE_KEY;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_DRY_RUN;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FROM;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID;
@@ -65,7 +66,7 @@ public class BulkUpdateKeyAction implements ComponentsWsAction {
 
   @Override
   public void define(WebService.NewController context) {
-    WebService.NewAction action = context.createAction("bulk_update_key")
+    WebService.NewAction action = context.createAction(ACTION_BULK_UPDATE_KEY)
       .setDescription("Bulk update a project or module key and all its sub-components keys. " +
         "The bulk update allows to replace a part of the current key by another string on the current project and all its sub-modules.<br>" +
         "It's possible to simulate the bulk update by setting the parameter '%s' at true. No key is updated with a dry run.<br>" +
index 0da1a4f57870a4c8f7680dd5882f0558ac71621c..d7d8d964001f67617b1a12324ebe95ceb8f6150d 100644 (file)
@@ -23,6 +23,8 @@ import com.google.common.io.Resources;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
 
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.CONTROLLER_COMPONENTS;
+
 public class ComponentsWs implements WebService {
 
   private final AppAction appAction;
@@ -37,7 +39,7 @@ public class ComponentsWs implements WebService {
 
   @Override
   public void define(Context context) {
-    NewController controller = context.createController("api/components")
+    NewController controller = context.createController(CONTROLLER_COMPONENTS)
       .setSince("4.2")
       .setDescription("Get information about a component (file, directory, project, ...) and its ancestors or descendants. " +
         "Update a project or module key.");
index 78d82efee898e8e3acfbdebb5f41befbed9313d2..c1b7e4f463afc5b0e88923f6ca291db8e4d2c385 100644 (file)
@@ -36,6 +36,7 @@ public class ComponentsWsModule extends Module {
       SearchViewComponentsAction.class,
       UpdateKeyAction.class,
       BulkUpdateKeyAction.class,
-      SearchProjectsAction.class);
+      SearchProjectsAction.class,
+      SearchProjectsQueryBuilderValidator.class);
   }
 }
index 1e6aa56116b121164542c91f257f211676b0d1f7..22ac602f1eb794ad0956a57cd8dde80da1724653 100644 (file)
@@ -45,6 +45,7 @@ import static com.google.common.collect.FluentIterable.from;
 import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
 import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SEARCH;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_LANGUAGE;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS;
 
@@ -65,7 +66,7 @@ public class SearchAction implements ComponentsWsAction {
 
   @Override
   public void define(WebService.NewController context) {
-    WebService.NewAction action = context.createAction("search")
+    WebService.NewAction action = context.createAction(ACTION_SEARCH)
       .setSince("5.2")
       .setInternal(true)
       .setDescription("Search for components")
index ef7ffb4a3a561e15a9fce4d063dbdace8a1ae2b0..5c68ae9e95b9508b7748afb1a416310a4c89ba1a 100644 (file)
@@ -38,28 +38,38 @@ import org.sonarqube.ws.WsComponents.Component;
 import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
 import org.sonarqube.ws.client.component.SearchProjectsRequest;
 
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.build;
 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.SearchProjectsRequest.DEFAULT_PAGE_SIZE;
 import static org.sonarqube.ws.client.component.SearchProjectsRequest.MAX_PAGE_SIZE;
 
 public class SearchProjectsAction implements ComponentsWsAction {
   private final DbClient dbClient;
   private final ProjectMeasuresIndex index;
+  private final SearchProjectsQueryBuilderValidator searchProjectsQueryBuilderValidator;
 
-  public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index) {
+  public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, SearchProjectsQueryBuilderValidator searchProjectsQueryBuilderValidator) {
     this.dbClient = dbClient;
     this.index = index;
+    this.searchProjectsQueryBuilderValidator = searchProjectsQueryBuilderValidator;
   }
 
   @Override
   public void define(WebService.NewController context) {
-    context.createAction("search_projects")
+    WebService.NewAction action = context.createAction("search_projects")
       .setSince("6.2")
       .setDescription("Search for projects")
       .addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
       .setInternal(true)
       .setResponseExample(getClass().getResource("search_projects-example.json"))
       .setHandler(this);
+
+    action
+      .createParam(PARAM_FILTER)
+      .setDescription("TODO")
+      .setSince("6.2");
   }
 
   @Override
@@ -78,6 +88,11 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private SearchResults searchProjects(DbSession dbSession, SearchProjectsRequest request) {
+    String filter = request.getFilter();
+    if (filter != null) {
+      SearchProjectsCriteriaQuery query = build(filter);
+      searchProjectsQueryBuilderValidator.validate(dbSession, query);
+    }
     SearchIdResult<String> searchResult = index.search(new SearchOptions().setPage(request.getPage(), request.getPageSize()));
 
     Ordering<ComponentDto> ordering = Ordering.explicit(searchResult.getIds()).onResultOf(ComponentDto::uuid);
@@ -87,11 +102,10 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private static SearchProjectsRequest toRequest(Request httpRequest) {
-    SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
-
-    request.setPage(httpRequest.mandatoryParamAsInt(Param.PAGE));
-    request.setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE));
-
+    SearchProjectsRequest.Builder request = SearchProjectsRequest.builder()
+      .setFilter(httpRequest.param(PARAM_FILTER))
+      .setPage(httpRequest.mandatoryParamAsInt(Param.PAGE))
+      .setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE));
     return request.build();
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java
new file mode 100644 (file)
index 0000000..381db46
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.component.ws;
+
+import com.google.common.base.Splitter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static java.util.Locale.ENGLISH;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.Operator;
+
+public class SearchProjectsQueryBuilder {
+
+  private static final Splitter CRITERIA_SPLITTER = Splitter.on("and");
+  private static final Pattern CRITERIA_PATTERN = Pattern.compile("(\\w+)\\s*([<>][=]?)\\s*(\\w+)");
+
+  private SearchProjectsQueryBuilder() {
+    // Only static methods
+  }
+
+  public static SearchProjectsCriteriaQuery build(String filter) {
+    SearchProjectsCriteriaQuery query = new SearchProjectsCriteriaQuery();
+    CRITERIA_SPLITTER.split(filter.toLowerCase(ENGLISH))
+      .forEach(criteria -> processCriteria(criteria, query));
+    return query;
+  }
+
+  private static void processCriteria(String criteria, SearchProjectsCriteriaQuery query) {
+    Matcher matcher = CRITERIA_PATTERN.matcher(criteria);
+    checkArgument(matcher.find() && matcher.groupCount() == 3, "Invalid criteria '%s'", criteria);
+    String metric = matcher.group(1);
+    Operator operator = Operator.create(matcher.group(2));
+    Double value = Double.parseDouble(matcher.group(3));
+    query.addMetricCriteria(new MetricCriteria(metric, operator, value));
+  }
+
+  public static class SearchProjectsCriteriaQuery {
+    public enum Operator {
+      LT("<="), GT(">"), EQ("=");
+
+      String value;
+
+      Operator(String value) {
+        this.value = value;
+      }
+
+      String getValue() {
+        return value;
+      }
+
+      public static Operator create(String value) {
+        return stream(Operator.values())
+          .filter(operator -> operator.getValue().equals(value))
+          .findFirst()
+          .orElseThrow(() -> new IllegalArgumentException(format("Unknown operator '%s'", value)));
+      }
+    }
+
+    private List<MetricCriteria> metricCriterias = new ArrayList<>();
+
+    SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) {
+      metricCriterias.add(metricCriteria);
+      return this;
+    }
+
+    public List<MetricCriteria> getMetricCriterias() {
+      return metricCriterias;
+    }
+
+    public static class MetricCriteria {
+      private String metricKey;
+      private Operator operator;
+      private double value;
+
+      private MetricCriteria(String metricKey, Operator operator, double value) {
+        this.metricKey = metricKey;
+        this.operator = operator;
+        this.value = value;
+      }
+
+      public String getMetricKey() {
+        return metricKey;
+      }
+
+      public Operator getOperator() {
+        return operator;
+      }
+
+      public double getValue() {
+        return value;
+      }
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidator.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidator.java
new file mode 100644 (file)
index 0000000..6148b30
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.component.ws;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
+
+public class SearchProjectsQueryBuilderValidator {
+
+  private final DbClient dbClient;
+
+  public SearchProjectsQueryBuilderValidator(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  public void validate(DbSession dbSession, SearchProjectsCriteriaQuery query) {
+    List<String> metricKeys = new ArrayList<>(query.getMetricCriterias().stream().map(MetricCriteria::getMetricKey).collect(Collectors.toSet()));
+    List<MetricDto> metricDtos = dbClient.metricDao().selectByKeys(dbSession, metricKeys);
+    if (metricDtos.size() == metricKeys.size()) {
+      return;
+    }
+    List<String> metricDtoKeys = metricDtos.stream().map(MetricDto::getKey).collect(Collectors.toList());
+    Set<String> unknownKeys = metricKeys.stream().filter(metricKey -> !metricDtoKeys.contains(metricKey)).collect(Collectors.toSet());
+    throw new IllegalArgumentException(String.format("Unknown metric(s) %s", unknownKeys));
+  }
+}
index bcc69a6e1caedcf6ef1e9c0cd72ca3c9ea2ce0d6..36475f774ae142db3e6cfc7e2c0b2df2af6418d0 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.server.component.ComponentService;
 import org.sonarqube.ws.client.component.UpdateWsRequest;
 
 import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_UPDATE_KEY;
 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;
@@ -49,7 +50,7 @@ public class UpdateKeyAction implements ComponentsWsAction {
 
   @Override
   public void define(WebService.NewController context) {
-    WebService.NewAction action = context.createAction("update_key")
+    WebService.NewAction action = context.createAction(ACTION_UPDATE_KEY)
       .setDescription("Update a project or module key and all its sub-components keys.<br>" +
         "Either '%s' or '%s' must be provided, not both.<br> " +
         "Requires one of the following permissions: " +
index a126f8c618271f68ca39c4c810088f1304974b15..f8dee7ef5f4b3dc94fb2c2aea283f347cbb8aa27 100644 (file)
@@ -29,6 +29,6 @@ public class ComponentsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ComponentsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(11 + 2);
+    assertThat(container.size()).isEqualTo(12 + 2);
   }
 }
index 2d501b0dfe7b0b93660ffafa41387aaa8d881216..2210b7c17ab8dfd84875e0685f4cec14760ceac9 100644 (file)
@@ -69,7 +69,7 @@ public class SearchProjectsActionTest {
   ComponentDbTester componentDb = new ComponentDbTester(db);
   DbClient dbClient = db.getDbClient();
 
-  WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, new ProjectMeasuresIndex(es.client())));
+  WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, new ProjectMeasuresIndex(es.client()), new SearchProjectsQueryBuilderValidator(dbClient)));
 
   SearchProjectsRequest.Builder request = SearchProjectsRequest.builder();
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java
new file mode 100644 (file)
index 0000000..1415769
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.component.ws;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.build;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
+import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.Operator;
+
+public class SearchProjectsQueryBuilderTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void create_query() throws Exception {
+    SearchProjectsCriteriaQuery query = build("ncloc > 10 and coverage <= 80");
+
+    assertThat(query.getMetricCriterias())
+      .extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
+      .containsOnly(
+        tuple("ncloc", Operator.GT, 10d),
+        tuple("coverage", Operator.LT, 80d));
+  }
+
+  @Test
+  public void convert_upper_case_to_lower_case() throws Exception {
+    assertThat(build("NCLOC > 10 AND coVERage <= 80").getMetricCriterias())
+      .extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
+      .containsOnly(
+        tuple("ncloc", Operator.GT, 10d),
+        tuple("coverage", Operator.LT, 80d));
+  }
+
+  @Test
+  public void ignore_white_spaces() throws Exception {
+    assertThat(build("   ncloc    >    10   ").getMetricCriterias())
+      .extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
+      .containsOnly(tuple("ncloc", Operator.GT, 10d));
+  }
+
+  @Test
+  public void fail_on_unknown_operator() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Unknown operator '>='");
+    build("ncloc >= 10");
+  }
+
+  @Test
+  public void fail_on_invalid_criteria() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid criteria 'ncloc ? 10'");
+    build("ncloc ? 10");
+  }
+
+  @Test
+  public void fail_when_no_operator() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid criteria 'ncloc 10'");
+    build("ncloc 10");
+  }
+
+  @Test
+  public void fail_when_no_key() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid criteria '>= 10'");
+    build(">= 10");
+  }
+
+  @Test
+  public void fail_when_no_value() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid criteria 'ncloc >='");
+    build("ncloc >=");
+  }
+
+  @Test
+  public void fail_when_no_criteria_provided() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Invalid criteria ''");
+    build("");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidatorTest.java
new file mode 100644 (file)
index 0000000..8de2d9d
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.component.ws;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricTesting;
+
+@Ignore
+public class SearchProjectsQueryBuilderValidatorTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  DbClient dbClient = db.getDbClient();
+  DbSession dbSession = db.getSession();
+
+  SearchProjectsQueryBuilderValidator validator = new SearchProjectsQueryBuilderValidator(dbClient);
+
+  @Test
+  public void does_not_fail_when_metric_criteria_contains_an_existing_metric() throws Exception {
+    insertMetric("ncloc");
+
+    validator.validate(dbSession, SearchProjectsQueryBuilder.build("ncloc > 10"));
+  }
+
+  @Test
+  public void fail_when_metric_does_not_exists() throws Exception {
+    insertMetric("ncloc");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Unknown metric(s) [unknown]");
+    validator.validate(dbSession, SearchProjectsQueryBuilder.build("unknown > 10"));
+  }
+
+  @Test
+  public void return_all_unknown_metrics() throws Exception {
+    insertMetric("ncloc");
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Unknown metric(s) [coverage, debt]");
+    validator.validate(dbSession, SearchProjectsQueryBuilder.build("debt > 10 AND ncloc <= 20 AND coverage > 30"));
+  }
+
+  private void insertMetric(String metricKey) {
+    dbClient.metricDao().insert(dbSession, MetricTesting.newMetricDto().setKey(metricKey));
+  }
+}
index b7e71d43a3ebb25631a9efddb7f5e500986f4e4d..2deb2884b23d9987e5fa584d3990a8a7ba1013e4 100644 (file)
@@ -21,6 +21,7 @@ package org.sonarqube.ws.client.component;
 
 import com.google.common.base.Joiner;
 import org.sonarqube.ws.WsComponents.BulkUpdateKeyWsResponse;
+import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
 import org.sonarqube.ws.WsComponents.SearchWsResponse;
 import org.sonarqube.ws.WsComponents.ShowWsResponse;
 import org.sonarqube.ws.WsComponents.TreeWsResponse;
@@ -29,10 +30,17 @@ import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.PostRequest;
 import org.sonarqube.ws.client.WsConnector;
 
+import static org.sonar.api.server.ws.WebService.Param;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_BULK_UPDATE_KEY;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SEARCH;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SEARCH_PROJECTS;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SHOW;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_TREE;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_UPDATE_KEY;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.CONTROLLER_COMPONENTS;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BASE_COMPONENT_ID;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BASE_COMPONENT_KEY;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FROM;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ID;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_KEY;
@@ -44,15 +52,15 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_TO;
 public class ComponentsService extends BaseService {
 
   public ComponentsService(WsConnector wsConnector) {
-    super(wsConnector, "api/components");
+    super(wsConnector, CONTROLLER_COMPONENTS);
   }
 
   public SearchWsResponse search(SearchWsRequest request) {
-    GetRequest get = new GetRequest(path("search"))
-      .setParam("qualifiers", Joiner.on(",").join(request.getQualifiers()))
-      .setParam("p", request.getPage())
-      .setParam("ps", request.getPageSize())
-      .setParam("q", request.getQuery());
+    GetRequest get = new GetRequest(path(ACTION_SEARCH))
+      .setParam(PARAM_QUALIFIERS, Joiner.on(",").join(request.getQualifiers()))
+      .setParam(Param.PAGE, request.getPage())
+      .setParam(Param.PAGE_SIZE, request.getPageSize())
+      .setParam(Param.TEXT_QUERY, request.getQuery());
     return call(get, SearchWsResponse.parser());
   }
 
@@ -62,10 +70,10 @@ public class ComponentsService extends BaseService {
       .setParam(PARAM_BASE_COMPONENT_KEY, request.getBaseComponentKey())
       .setParam(PARAM_QUALIFIERS, inlineMultipleParamValue(request.getQualifiers()))
       .setParam(PARAM_STRATEGY, request.getStrategy())
-      .setParam("p", request.getPage())
-      .setParam("ps", request.getPageSize())
-      .setParam("q", request.getQuery())
-      .setParam("s", request.getSort());
+      .setParam(Param.PAGE, request.getPage())
+      .setParam(Param.PAGE_SIZE, request.getPageSize())
+      .setParam(Param.TEXT_QUERY, request.getQuery())
+      .setParam(Param.SORT, request.getSort());
     return call(get, TreeWsResponse.parser());
   }
 
@@ -77,7 +85,7 @@ public class ComponentsService extends BaseService {
   }
 
   public void updateKey(UpdateWsRequest request) {
-    PostRequest post = new PostRequest(path("update_key"))
+    PostRequest post = new PostRequest(path(ACTION_UPDATE_KEY))
       .setParam(PARAM_ID, request.getId())
       .setParam(PARAM_KEY, request.getKey())
       .setParam(PARAM_NEW_KEY, request.getNewKey());
@@ -86,7 +94,7 @@ public class ComponentsService extends BaseService {
   }
 
   public BulkUpdateKeyWsResponse bulkUpdateKey(BulkUpdateWsRequest request) {
-    PostRequest post = new PostRequest(path("bulk_update_key"))
+    PostRequest post = new PostRequest(path(ACTION_BULK_UPDATE_KEY))
       .setParam(PARAM_ID, request.getId())
       .setParam(PARAM_KEY, request.getKey())
       .setParam(PARAM_FROM, request.getFrom())
@@ -94,4 +102,12 @@ public class ComponentsService extends BaseService {
 
     return call(post, BulkUpdateKeyWsResponse.parser());
   }
+
+  public SearchProjectsWsResponse searchProjects(SearchProjectsRequest request) {
+    GetRequest get = new GetRequest(path(ACTION_SEARCH_PROJECTS))
+      .setParam(PARAM_FILTER, request.getFilter())
+      .setParam(Param.PAGE, request.getPage())
+      .setParam(Param.PAGE_SIZE, request.getPageSize());
+    return call(get, SearchProjectsWsResponse.parser());
+  }
 }
index 4ae95bf46b0ccc6f5eb7195c0c6be9599cbc2fe8..7301b14808bc4733d0927898e37203e245cccc52 100644 (file)
 package org.sonarqube.ws.client.component;
 
 public class ComponentsWsParameters {
+
+  public static final String CONTROLLER_COMPONENTS = "api/components";
+
   // actions
+  public static final String ACTION_SEARCH = "search";
+  public static final String ACTION_UPDATE_KEY = "update_key";
   public static final String ACTION_TREE = "tree";
-
   public static final String ACTION_SHOW = "show";
+  public static final String ACTION_BULK_UPDATE_KEY = "bulk_update_key";
+  public static final String ACTION_SEARCH_PROJECTS = "search_projects";
+
   // parameters
   public static final String PARAM_QUALIFIERS = "qualifiers";
-
   public static final String PARAM_LANGUAGE = "language";
   public static final String PARAM_BASE_COMPONENT_ID = "baseComponentId";
   public static final String PARAM_BASE_COMPONENT_KEY = "baseComponentKey";
@@ -37,6 +43,7 @@ public class ComponentsWsParameters {
   public static final String PARAM_FROM = "from";
   public static final String PARAM_TO = "to";
   public static final String PARAM_DRY_RUN = "dryRun";
+  public static final String PARAM_FILTER = "filter";
 
   private ComponentsWsParameters() {
     // static utility class
index 03292b2bf32df6ec828a201191f8220b58c94999..c8ba9187cb5729981b9051142bfdf53c8afbb8dd 100644 (file)
@@ -28,10 +28,16 @@ public class SearchProjectsRequest {
 
   private final int page;
   private final int pageSize;
+  private final String filter;
 
   private SearchProjectsRequest(Builder builder) {
     this.page = builder.page;
     this.pageSize = builder.pageSize;
+    this.filter = builder.filter;
+  }
+
+  public String getFilter() {
+    return filter;
   }
 
   public int getPageSize() {
@@ -49,11 +55,17 @@ public class SearchProjectsRequest {
   public static class Builder {
     private Integer page;
     private Integer pageSize;
+    private String filter;
 
     private Builder() {
       // enforce static factory method
     }
 
+    public Builder setFilter(String filter) {
+      this.filter = filter;
+      return this;
+    }
+
     public Builder setPage(int page) {
       this.page = page;
       return this;
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java
new file mode 100644 (file)
index 0000000..5627eaf
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.component;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonarqube.ws.client.ServiceTester;
+import org.sonarqube.ws.client.WsConnector;
+
+import static org.mockito.Mockito.mock;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
+
+public class ComponentsServiceTest {
+
+  @Rule
+  public ServiceTester<ComponentsService> serviceTester = new ServiceTester<>(new ComponentsService(mock(WsConnector.class)));
+
+  private ComponentsService underTest = serviceTester.getInstanceUnderTest();
+
+  @Test
+  public void search_projects() {
+    underTest.searchProjects(SearchProjectsRequest.builder()
+      .setFilter("ncloc > 10")
+      .setPage(3)
+      .setPageSize(10)
+      .build());
+
+    serviceTester.assertThat(serviceTester.getGetRequest())
+      .hasPath("search_projects")
+      .hasParam(PARAM_FILTER, "ncloc > 10")
+      .hasParam(Param.PAGE, 3)
+      .hasParam(Param.PAGE_SIZE, 10)
+      .andNoOtherParam();
+  }
+
+}
index 1db380272ee1b979cbe52b1619a72dc82efc7b6e..49aaafa722fdad53e9040e741bff7b3b444c9259 100644 (file)
@@ -33,6 +33,15 @@ public class SearchProjectsRequestTest {
 
   SearchProjectsRequest.Builder underTest = SearchProjectsRequest.builder();
 
+  @Test
+  public void filter_parameter() throws Exception {
+    SearchProjectsRequest result = underTest
+      .setFilter("ncloc > 10")
+      .build();
+
+    assertThat(result.getFilter()).isEqualTo("ncloc > 10");
+  }
+
   @Test
   public void default_page_values() {
     SearchProjectsRequest result = underTest.build();