From 3ecbbba4ff5fa8062a7d52ad01051c9785dbbcd2 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 14 Oct 2016 17:28:11 +0200 Subject: [PATCH] SONAR-8287 Add filter parameter in api/components/search_projects --- .../component/ws/BulkUpdateKeyAction.java | 3 +- .../server/component/ws/ComponentsWs.java | 4 +- .../component/ws/ComponentsWsModule.java | 3 +- .../server/component/ws/SearchAction.java | 3 +- .../component/ws/SearchProjectsAction.java | 28 +++-- .../ws/SearchProjectsQueryBuilder.java | 119 ++++++++++++++++++ .../SearchProjectsQueryBuilderValidator.java | 52 ++++++++ .../server/component/ws/UpdateKeyAction.java | 3 +- .../component/ws/ComponentsWsModuleTest.java | 2 +- .../ws/SearchProjectsActionTest.java | 2 +- .../ws/SearchProjectsQueryBuilderTest.java | 107 ++++++++++++++++ ...archProjectsQueryBuilderValidatorTest.java | 75 +++++++++++ .../client/component/ComponentsService.java | 40 ++++-- .../component/ComponentsWsParameters.java | 11 +- .../component/SearchProjectsRequest.java | 12 ++ .../component/ComponentsServiceTest.java | 55 ++++++++ .../component/SearchProjectsRequestTest.java | 9 ++ 17 files changed, 500 insertions(+), 28 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidator.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidatorTest.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/BulkUpdateKeyAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/BulkUpdateKeyAction.java index c735e7afe99..76f4e4c6036 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/BulkUpdateKeyAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/BulkUpdateKeyAction.java @@ -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.
" + "It's possible to simulate the bulk update by setting the parameter '%s' at true. No key is updated with a dry run.
" + diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java index 0da1a4f5787..d7d8d964001 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java @@ -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."); diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java index 78d82efee89..c1b7e4f463a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWsModule.java @@ -36,6 +36,7 @@ public class ComponentsWsModule extends Module { SearchViewComponentsAction.class, UpdateKeyAction.class, BulkUpdateKeyAction.class, - SearchProjectsAction.class); + SearchProjectsAction.class, + SearchProjectsQueryBuilderValidator.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java index 1e6aa56116b..22ac602f1eb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java @@ -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") diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java index ef7ffb4a3a5..5c68ae9e95b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java @@ -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 searchResult = index.search(new SearchOptions().setPage(request.getPage(), request.getPageSize())); Ordering 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 index 00000000000..381db467ea6 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java @@ -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 metricCriterias = new ArrayList<>(); + + SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) { + metricCriterias.add(metricCriteria); + return this; + } + + public List 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 index 00000000000..6148b303840 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidator.java @@ -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 metricKeys = new ArrayList<>(query.getMetricCriterias().stream().map(MetricCriteria::getMetricKey).collect(Collectors.toSet())); + List metricDtos = dbClient.metricDao().selectByKeys(dbSession, metricKeys); + if (metricDtos.size() == metricKeys.size()) { + return; + } + List metricDtoKeys = metricDtos.stream().map(MetricDto::getKey).collect(Collectors.toList()); + Set unknownKeys = metricKeys.stream().filter(metricKey -> !metricDtoKeys.contains(metricKey)).collect(Collectors.toSet()); + throw new IllegalArgumentException(String.format("Unknown metric(s) %s", unknownKeys)); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/UpdateKeyAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/UpdateKeyAction.java index bcc69a6e1ca..36475f774ae 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/UpdateKeyAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/UpdateKeyAction.java @@ -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.
" + "Either '%s' or '%s' must be provided, not both.
" + "Requires one of the following permissions: " + diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java index a126f8c6182..f8dee7ef5f4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsModuleTest.java @@ -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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java index 2d501b0dfe7..2210b7c17ab 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java @@ -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 index 00000000000..1415769ddfe --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java @@ -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 index 00000000000..8de2d9d4c1c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderValidatorTest.java @@ -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)); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java index b7e71d43a3e..2deb2884b23 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java @@ -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()); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java index 4ae95bf46b0..7301b14808b 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsWsParameters.java @@ -20,13 +20,19 @@ 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 diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java index 03292b2bf32..c8ba9187cb5 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java @@ -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 index 00000000000..5627eaf8beb --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java @@ -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 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(); + } + +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java index 1db380272ee..49aaafa722f 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/component/SearchProjectsRequestTest.java @@ -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(); -- 2.39.5