diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-09-26 12:11:12 +0200 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-09-30 14:16:55 +0200 |
commit | 2bbfd0c6ca169d20ae2d51e7cbb029ad3f9ea73b (patch) | |
tree | b942ab1833e9f3bf51234cd24fbcfdaccd7e93c5 | |
parent | 8492c97d913d9aa4330975017b68200977a02123 (diff) | |
download | sonarqube-2bbfd0c6ca169d20ae2d51e7cbb029ad3f9ea73b.tar.gz sonarqube-2bbfd0c6ca169d20ae2d51e7cbb029ad3f9ea73b.zip |
SONAR-8120 Create WS measures/search to search for measures
11 files changed, 888 insertions, 2 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java index ea97045cd0c..f0d8356d63b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java @@ -49,6 +49,10 @@ class ComponentDtoToWsComponent { return wsComponent; } + static WsMeasures.Component dbToWsComponent(ComponentDto dbComponent, Iterable<WsMeasures.Measure> measures) { + return componentDtoToWsComponent(dbComponent).addAllMeasures(measures).build(); + } + static WsMeasures.Component.Builder componentDtoToWsComponent(ComponentDto component) { WsMeasures.Component.Builder wsComponent = WsMeasures.Component.newBuilder() .setId(component.uuid()) diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java index 23d6b832814..eeac683913a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasuresWsModule.java @@ -29,6 +29,7 @@ public class MeasuresWsModule extends Module { ComponentTreeDataLoader.class, MeasuresWs.class, ComponentTreeAction.class, - ComponentAction.class); + ComponentAction.class, + SearchAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java new file mode 100644 index 00000000000..8d290cfa9b0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java @@ -0,0 +1,214 @@ +/* + * 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.measure.ws; + +import com.google.common.collect.ImmutableMultimap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.measure.MeasureDto; +import org.sonar.db.measure.MeasureQuery; +import org.sonar.db.metric.MetricDto; +import org.sonarqube.ws.WsMeasures; +import org.sonarqube.ws.WsMeasures.SearchWsResponse; +import org.sonarqube.ws.client.measure.SearchRequest; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Maps.immutableEntry; +import static java.util.Objects.requireNonNull; +import static java.util.function.Function.identity; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_03; +import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.dbToWsComponent; +import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.measureDtoToWsMeasure; +import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter; +import static org.sonar.server.ws.KeyExamples.KEY_FILE_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_FILE_EXAMPLE_002; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS; + +public class SearchAction implements MeasuresWsAction { + private static final int MAX_NB_COMPONENTS = 100; + static final String PARAM_COMPONENT_IDS = "componentIds"; + static final String PARAM_COMPONENT_KEYS = "componentKeys"; + + private final DbClient dbClient; + + public SearchAction(DbClient dbClient) { + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("search") + .setInternal(true) + .setDescription("Search for component measures ordered by component names.<br>" + + "At most %d components can be provided.<br>" + + "Either '%s' or '%s' must be provided, not both.<br>" + + "Requires one of the following permissions:" + + "<ul>" + + " <li>'Administer System'</li>" + + " <li>'Administer' rights on the provided components</li>" + + " <li>'Browse' on the provided components</li>" + + "</ul>", + MAX_NB_COMPONENTS, PARAM_COMPONENT_IDS, PARAM_COMPONENT_KEYS) + .setSince("6.1") + .setResponseExample(getClass().getResource("search-example.json")) + .setHandler(this); + + createMetricKeysParameter(action); + + action.createParam(PARAM_COMPONENT_IDS) + .setDescription("Comma-separated list of component ids") + .setExampleValue(String.join(",", UUID_EXAMPLE_01, UUID_EXAMPLE_02, UUID_EXAMPLE_03)); + + action.createParam(PARAM_COMPONENT_KEYS) + .setDescription("Comma-separated list of component keys") + .setExampleValue(String.join(",", KEY_PROJECT_EXAMPLE_001, KEY_FILE_EXAMPLE_001, KEY_PROJECT_EXAMPLE_002, KEY_FILE_EXAMPLE_002)); + + } + + @Override + public void handle(Request httpRequest, Response httpResponse) throws Exception { + try (DbSession dbSession = dbClient.openSession(false)) { + SearchWsResponse response = new ResponseBuilder(httpRequest, dbSession).build(); + writeProtobuf(response, httpRequest, httpResponse); + } + + } + + private class ResponseBuilder { + private final DbSession dbSession; + private final Request httpRequest; + private SearchRequest request; + private List<ComponentDto> components; + private List<MetricDto> metrics; + private List<MeasureDto> measures; + + ResponseBuilder(Request httpRequest, DbSession dbSession) { + this.dbSession = dbSession; + this.httpRequest = httpRequest; + } + + SearchWsResponse build() { + this.request = setRequest(); + this.components = searchComponents(); + this.metrics = searchMetrics(); + this.measures = searchMeasures(); + + return buildResponse(); + } + + private SearchRequest setRequest() { + request = SearchRequest.builder() + .setMetricKeys(httpRequest.mandatoryParamAsStrings(PARAM_METRIC_KEYS)) + .setComponentIds(httpRequest.paramAsStrings(PARAM_COMPONENT_IDS)) + .setComponentKeys(httpRequest.paramAsStrings(PARAM_COMPONENT_KEYS)) + .build(); + + this.components = searchComponents(); + this.metrics = searchMetrics(); + + return request; + } + + private List<MetricDto> searchMetrics() { + requireNonNull(request); + return dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys()); + } + + private List<ComponentDto> searchComponents() { + requireNonNull(request); + if (request.hasComponentIds()) { + List<ComponentDto> componentsByUuid = searchByComponentUuids(dbSession, request.getComponentIds()); + checkArgument(componentsByUuid.size() == request.getComponentIds().size(), "Some components are not found in: '%s'", String.join(", ", request.getComponentIds())); + return componentsByUuid; + } else { + List<ComponentDto> componentsByKey = searchByComponentKeys(dbSession, request.getComponentKeys()); + checkArgument(componentsByKey.size() == request.getComponentKeys().size(), "Some components are not found in: '%s'", String.join(", ", request.getComponentKeys())); + return componentsByKey; + } + } + + private List<ComponentDto> searchByComponentUuids(DbSession dbSession, List<String> componentUuids) { + return dbClient.componentDao().selectByUuids(dbSession, componentUuids); + } + + private List<ComponentDto> searchByComponentKeys(DbSession dbSession, List<String> componentKeys) { + return dbClient.componentDao().selectByKeys(dbSession, componentKeys); + } + + private List<MeasureDto> searchMeasures() { + requireNonNull(components); + requireNonNull(metrics); + + return dbClient.measureDao().selectByQuery(dbSession, MeasureQuery.builder() + .setComponentUuids(components.stream().map(ComponentDto::uuid).collect(Collectors.toList())) + .setMetricIds(metrics.stream().map(MetricDto::getId).collect(Collectors.toList())) + .build()); + } + + private SearchWsResponse buildResponse() { + requireNonNull(metrics); + requireNonNull(measures); + requireNonNull(components); + + Map<Integer, MetricDto> metricById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity())); + + ImmutableMultimap<String, WsMeasures.Measure> wsMeasuresByComponentUuid = measures.stream() + .collect(Collectors.toMap(identity(), MeasureDto::getComponentUuid)) + .entrySet().stream() + .map(entry -> immutableEntry( + measureDtoToWsMeasure(metricById.get(entry.getKey().getMetricId()), entry.getKey()), + entry.getValue())) + .sorted((e1, e2) -> e1.getKey().getMetric().compareTo(e2.getKey().getMetric())) + .collect(Collector.of( + ImmutableMultimap::<String, WsMeasures.Measure>builder, + (result, entry) -> result.put(entry.getValue(), entry.getKey()), + (result1, result2) -> { + throw new IllegalStateException("Parallel execution forbidden during WS measures"); + }, + ImmutableMultimap.Builder::build)); + + return components.stream() + .map(dbComponent -> dbToWsComponent(dbComponent, wsMeasuresByComponentUuid.get(dbComponent.uuid()))) + .sorted((c1, c2) -> c1.getName().compareTo(c2.getName())) + .collect(Collector.of( + SearchWsResponse::newBuilder, + SearchWsResponse.Builder::addComponents, + (result1, result2) -> { + throw new IllegalStateException("Parallel execution forbidden while build SearchWsResponse"); + }, + SearchWsResponse.Builder::build)); + } + + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java index 72ecdebd799..d017bb1c013 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java @@ -21,7 +21,9 @@ package org.sonar.server.ws; public class KeyExamples { public static final String KEY_FILE_EXAMPLE_001 = "my_project:/src/foo/Bar.php"; + public static final String KEY_FILE_EXAMPLE_002 = "another_project:/src/foo/Foo.php"; public static final String KEY_PROJECT_EXAMPLE_001 = "my_project"; + public static final String KEY_PROJECT_EXAMPLE_002 = "another_project"; public static final String KEY_DEVELOPER_EXAMPLE_001 = "DEV:ada@lovelace.com"; private KeyExamples() { diff --git a/server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json new file mode 100644 index 00000000000..57d4989debb --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json @@ -0,0 +1,128 @@ +{ + "components": [ + { + "id": "AVIwDXE-bJbJqrw6wFv5", + "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl/ElementImpl.java", + "name": "ElementImpl.java", + "qualifier": "FIL", + "path": "src/main/java/com/sonarsource/markdown/impl/ElementImpl.java", + "language": "java", + "measures": [ + { + "metric": "complexity", + "value": "12" + }, + { + "metric": "ncloc", + "value": "114" + }, + { + "metric": "new_violations", + "periods": [ + { + "index": 1, + "value": "25" + }, + { + "index": 2, + "value": "0" + }, + { + "index": 3, + "value": "25" + } + ] + } + ] + }, + { + "id": "AVIwDXE_bJbJqrw6wFwJ", + "key": "com.sonarsource:java-markdown:src/test/java/com/sonarsource/markdown/impl/ElementImplTest.java", + "name": "ElementImplTest.java", + "qualifier": "UTS", + "path": "src/test/java/com/sonarsource/markdown/impl/ElementImplTest.java", + "language": "java", + "measures": [] + }, + { + "id": "project-id", + "key": "MY_PROJECT", + "name": "My Project", + "description": "My Project Description", + "qualifier": "TRK", + "measures": [ + { + "metric": "complexity", + "value": "42" + }, + { + "metric": "ncloc", + "value": "1984" + }, + { + "metric": "new_violations", + "periods": [ + { + "index": 1, + "value": "255" + }, + { + "index": 2, + "value": "0" + }, + { + "index": 3, + "value": "255" + } + ] + } + ] + }, + { + "id": "AVIwDXE-bJbJqrw6wFv8", + "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl", + "name": "src/main/java/com/sonarsource/markdown/impl", + "qualifier": "DIR", + "path": "src/main/java/com/sonarsource/markdown/impl", + "measures": [ + { + "metric": "complexity", + "value": "35", + "periods": [ + { + "index": 2, + "value": "0" + } + ] + }, + { + "metric": "ncloc", + "value": "217", + "periods": [ + { + "index": 2, + "value": "0" + } + ] + }, + { + "metric": "new_violations", + "periods": [ + { + "index": 1, + "value": "25" + }, + { + "index": 2, + "value": "0" + }, + { + "index": 3, + "value": "25" + } + ] + } + ] + } + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java index 67e60760bba..c5a2b957d11 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsModuleTest.java @@ -29,6 +29,6 @@ public class MeasuresWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new MeasuresWsModule().configure(container); - assertThat(container.size()).isEqualTo(4 + 2); + assertThat(container.size()).isEqualTo(5 + 2); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java new file mode 100644 index 00000000000..9563cf615d5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java @@ -0,0 +1,312 @@ +/* + * 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.measure.ws; + +import com.google.common.base.Throwables; +import java.io.IOException; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.server.ws.WebService; +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.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.metric.MetricDto; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.WsMeasures.Component; +import org.sonarqube.ws.WsMeasures.SearchWsResponse; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.utils.DateUtils.parseDateTime; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; +import static org.sonar.db.measure.MeasureTesting.newMeasureDto; +import static org.sonar.db.metric.MetricTesting.newMetricDto; +import static org.sonar.server.measure.ws.SearchAction.PARAM_COMPONENT_IDS; +import static org.sonar.server.measure.ws.SearchAction.PARAM_COMPONENT_KEYS; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.MediaTypes.PROTOBUF; +import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS; + +public class SearchActionTest { + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + ComponentDbTester componentDb = new ComponentDbTester(db); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + + WsActionTester ws = new WsActionTester(new SearchAction(dbClient)); + + @Test + public void json_example() { + insertJsonExampleData(); + + String result = ws.newRequest() + .setParam(PARAM_COMPONENT_IDS, "project-id,AVIwDXE-bJbJqrw6wFv5,AVIwDXE-bJbJqrw6wFv8,AVIwDXE_bJbJqrw6wFwJ") + .setParam(PARAM_METRIC_KEYS, "ncloc, complexity, new_violations") + .execute() + .getInput(); + + assertJson(result).withStrictArrayOrder().isSimilarTo(ws.getDef().responseExampleAsString()); + + } + + @Test + public void project_without_measures_map_all_fields() { + ComponentDto dbComponent = componentDb.insertComponent(newProjectDto()); + insertComplexityMetric(); + + SearchWsResponse result = callByComponentUuids(singletonList(dbComponent.uuid()), singletonList("complexity")); + + assertThat(result.getComponentsCount()).isEqualTo(1); + Component wsComponent = result.getComponents(0); + assertThat(wsComponent.getMeasuresCount()).isEqualTo(0); + assertThat(wsComponent.getId()).isEqualTo(dbComponent.uuid()); + assertThat(wsComponent.getKey()).isEqualTo(dbComponent.key()); + assertThat(wsComponent.getQualifier()).isEqualTo(dbComponent.qualifier()); + assertThat(wsComponent.getName()).isEqualTo(dbComponent.name()); + assertThat(wsComponent.getDescription()).isEqualTo(dbComponent.description()); + assertThat(wsComponent.getProjectId()).isEqualTo(""); + assertThat(wsComponent.getLanguage()).isEqualTo(""); + assertThat(wsComponent.getPath()).isEqualTo(""); + assertThat(wsComponent.getRefId()).isEqualTo(""); + assertThat(wsComponent.getRefKey()).isEqualTo(""); + } + + @Test + public void search_by_component_key() { + ComponentDto project = componentDb.insertProject(); + insertComplexityMetric(); + + SearchWsResponse result = callByComponentKeys(singletonList(project.key()), singletonList("complexity")); + + assertThat(result.getComponentsCount()).isEqualTo(1); + assertThat(result.getComponents(0).getId()).isEqualTo(project.uuid()); + } + + @Test + public void definition() { + WebService.Action result = ws.getDef(); + + assertThat(result.key()).isEqualTo("search"); + assertThat(result.isPost()).isFalse(); + assertThat(result.isInternal()).isTrue(); + assertThat(result.since()).isEqualTo("6.1"); + assertThat(result.params()).hasSize(3); + assertThat(result.responseExampleAsString()).isNotEmpty(); + assertThat(result.description()).isEqualToIgnoringWhitespace("" + + "Search for component measures ordered by component names.<br>" + + "At most 100 components can be provided.<br>" + + "Either 'componentIds' or 'componentKeys' must be provided, not both.<br>" + + "Requires one of the following permissions:" + + "<ul>" + + " <li>'Administer System'</li>" + + " <li>'Administer' rights on the provided components</li>" + + " <li>'Browse' on the provided components</li>" + + "</ul>"); + } + + private SearchWsResponse callByComponentUuids(@Nullable List<String> uuids, @Nullable List<String> metrics) { + return call(uuids, null, metrics); + } + + private SearchWsResponse callByComponentKeys(@Nullable List<String> keys, @Nullable List<String> metrics) { + return call(null, keys, metrics); + } + + private SearchWsResponse call(@Nullable List<String> uuids, @Nullable List<String> keys, @Nullable List<String> metrics) { + TestRequest request = ws.newRequest() + .setMediaType(PROTOBUF); + + if (uuids != null) { + request.setParam(PARAM_COMPONENT_IDS, String.join(",", uuids)); + } + if (keys != null) { + request.setParam(PARAM_COMPONENT_KEYS, String.join(",", keys)); + } + if (metrics != null) { + request.setParam(PARAM_METRIC_KEYS, String.join(",", metrics)); + } + + try { + return SearchWsResponse.parseFrom(request.execute().getInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + private static MetricDto newMetricDtoWithoutOptimization() { + return newMetricDto() + .setWorstValue(null) + .setBestValue(null) + .setOptimizedBestValue(false) + .setUserManaged(false); + } + + private MetricDto insertNewViolationsMetric() { + MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization() + .setKey("new_violations") + .setShortName("New issues") + .setDescription("New Issues") + .setDomain("Issues") + .setValueType("INT") + .setDirection(-1) + .setQualitative(true) + .setHidden(false) + .setUserManaged(false) + .setOptimizedBestValue(true) + .setBestValue(0.0d)); + db.commit(); + return metric; + } + + private MetricDto insertNclocMetric() { + MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization() + .setKey("ncloc") + .setShortName("Lines of code") + .setDescription("Non Commenting Lines of Code") + .setDomain("Size") + .setValueType("INT") + .setDirection(-1) + .setQualitative(false) + .setHidden(false) + .setUserManaged(false)); + db.commit(); + return metric; + } + + private MetricDto insertComplexityMetric() { + MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization() + .setKey("complexity") + .setShortName("Complexity") + .setDescription("Cyclomatic complexity") + .setDomain("Complexity") + .setValueType("INT") + .setDirection(-1) + .setQualitative(false) + .setHidden(false) + .setUserManaged(false)); + db.commit(); + return metric; + } + + private MetricDto insertCoverageMetric() { + MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization() + .setKey("coverage") + .setShortName("Coverage") + .setDescription("Code Coverage") + .setDomain("Coverage") + .setValueType(Metric.ValueType.FLOAT.name()) + .setDirection(1) + .setQualitative(false) + .setHidden(false) + .setUserManaged(false)); + db.commit(); + return metric; + } + + private void insertJsonExampleData() { + ComponentDto project = newProjectDto("project-id") + .setKey("MY_PROJECT") + .setName("My Project") + .setDescription("My Project Description") + .setQualifier(Qualifiers.PROJECT); + componentDb.insertComponent(project); + SnapshotDto projectSnapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project) + .setPeriodDate(1, parseDateTime("2016-01-11T10:49:50+0100").getTime()) + .setPeriodMode(1, "previous_version") + .setPeriodParam(1, "1.0-SNAPSHOT") + .setPeriodDate(2, parseDateTime("2016-01-11T10:50:06+0100").getTime()) + .setPeriodMode(2, "previous_analysis") + .setPeriodParam(2, "2016-01-11") + .setPeriodDate(3, parseDateTime("2016-01-11T10:38:45+0100").getTime()) + .setPeriodMode(3, "days") + .setPeriodParam(3, "30")); + + ComponentDto file1 = componentDb.insertComponent(newFileDto(project, null) + .setUuid("AVIwDXE-bJbJqrw6wFv5") + .setKey("com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl/ElementImpl.java") + .setName("ElementImpl.java") + .setLanguage("java") + .setQualifier(Qualifiers.FILE) + .setPath("src/main/java/com/sonarsource/markdown/impl/ElementImpl.java")); + componentDb.insertComponent(newFileDto(project, null) + .setUuid("AVIwDXE_bJbJqrw6wFwJ") + .setKey("com.sonarsource:java-markdown:src/test/java/com/sonarsource/markdown/impl/ElementImplTest.java") + .setName("ElementImplTest.java") + .setLanguage("java") + .setQualifier(Qualifiers.UNIT_TEST_FILE) + .setPath("src/test/java/com/sonarsource/markdown/impl/ElementImplTest.java")); + ComponentDto dir = componentDb.insertComponent(newDirectory(project, "src/main/java/com/sonarsource/markdown/impl") + .setUuid("AVIwDXE-bJbJqrw6wFv8") + .setKey("com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl") + .setQualifier(Qualifiers.DIRECTORY)); + + MetricDto complexity = insertComplexityMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(complexity, file1, projectSnapshot) + .setValue(12.0d), + newMeasureDto(complexity, dir, projectSnapshot) + .setValue(35.0d) + .setVariation(2, 0.0d), + newMeasureDto(complexity, project, projectSnapshot) + .setValue(42.0d)); + + MetricDto ncloc = insertNclocMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(ncloc, file1, projectSnapshot) + .setValue(114.0d), + newMeasureDto(ncloc, dir, projectSnapshot) + .setValue(217.0d) + .setVariation(2, 0.0d), + newMeasureDto(ncloc, project, projectSnapshot) + .setValue(1984.0d)); + + MetricDto newViolations = insertNewViolationsMetric(); + dbClient.measureDao().insert(dbSession, + newMeasureDto(newViolations, file1, projectSnapshot) + .setVariation(1, 25.0d) + .setVariation(2, 0.0d) + .setVariation(3, 25.0d), + newMeasureDto(newViolations, dir, projectSnapshot) + .setVariation(1, 25.0d) + .setVariation(2, 0.0d) + .setVariation(3, 25.0d), + newMeasureDto(newViolations, project, projectSnapshot) + .setVariation(1, 255.0d) + .setVariation(2, 0.0d) + .setVariation(3, 255.0d)); + + db.commit(); + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java b/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java index 2116b81f5d2..e1b2f364b3b 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java +++ b/sonar-db/src/test/java/org/sonar/db/component/ComponentTesting.java @@ -100,6 +100,7 @@ public class ComponentTesting { .setKey("KEY_" + uuid) .setName("NAME_" + uuid) .setLongName("LONG_NAME_" + uuid) + .setDescription("DESCRIPTION_" + uuid) .setScope(Scopes.PROJECT) .setQualifier(Qualifiers.PROJECT) .setPath(null) diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/SearchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/SearchRequest.java new file mode 100644 index 00000000000..ad16a8c3a5b --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/SearchRequest.java @@ -0,0 +1,96 @@ +/* + * 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.measure; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public class SearchRequest { + private final List<String> metricKeys; + private final List<String> componentIds; + private final List<String> componentKeys; + + public SearchRequest(Builder builder) { + metricKeys = builder.metricKeys; + componentIds = builder.componentIds; + componentKeys = builder.componentKeys; + } + + public List<String> getMetricKeys() { + return metricKeys; + } + + public boolean hasComponentIds() { + return componentIds != null; + } + + public List<String> getComponentIds() { + return requireNonNull(componentIds, "No component id in request"); + } + + public boolean hasComponentKeys() { + return componentKeys != null; + } + + public List<String> getComponentKeys() { + return requireNonNull(componentKeys, "No component key in request"); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List<String> metricKeys; + private List<String> componentIds; + private List<String> componentKeys; + + private Builder() { + // enforce method constructor + } + + public Builder setMetricKeys(List<String> metricKeys) { + this.metricKeys = metricKeys; + return this; + } + + public Builder setComponentIds(List<String> componentIds) { + this.componentIds = componentIds; + return this; + } + + public Builder setComponentKeys(List<String> componentKeys) { + this.componentKeys = componentKeys; + return this; + } + + public SearchRequest build() { + checkArgument(metricKeys != null && !metricKeys.isEmpty(), "Metric keys must be provided"); + checkArgument( + (componentIds != null && !componentIds.isEmpty()) + ^ (componentKeys != null && !componentKeys.isEmpty()), + "Either component ids or component keys must be provided, not both."); + return new SearchRequest(this); + } + } +} diff --git a/sonar-ws/src/main/protobuf/ws-measures.proto b/sonar-ws/src/main/protobuf/ws-measures.proto index 2ade7c75b56..b29ffdf8c12 100644 --- a/sonar-ws/src/main/protobuf/ws-measures.proto +++ b/sonar-ws/src/main/protobuf/ws-measures.proto @@ -42,6 +42,11 @@ message ComponentWsResponse { optional Periods periods = 3; } +// WS api/measures/search +message SearchWsResponse { + repeated Component components = 1; +} + message Component { optional string id = 1; optional string key = 2; diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/SearchRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/SearchRequestTest.java new file mode 100644 index 00000000000..7cffedb9471 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/measure/SearchRequestTest.java @@ -0,0 +1,123 @@ +/* + * 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.measure; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class SearchRequestTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + SearchRequest.Builder underTest = SearchRequest.builder(); + + @Test + public void with_component_ids() { + SearchRequest result = underTest + .setMetricKeys(singletonList("metric")) + .setComponentIds(singletonList("uuid")) + .build(); + + assertThat(result.getMetricKeys()).containsExactly("metric"); + assertThat(result.getComponentIds()).containsExactly("uuid"); + assertThat(result.hasComponentKeys()).isFalse(); + } + + @Test + public void with_component_keys() { + SearchRequest result = underTest + .setMetricKeys(singletonList("metric")) + .setComponentKeys(singletonList("key")) + .build(); + + assertThat(result.getMetricKeys()).containsExactly("metric"); + assertThat(result.getComponentKeys()).containsExactly("key"); + assertThat(result.hasComponentIds()).isFalse(); + } + + @Test + public void fail_when_non_null_metric_keys() { + expectExceptionOnMetricKeys(); + + underTest.setMetricKeys(null).build(); + } + + @Test + public void fail_when_non_empty_metric_keys() { + expectExceptionOnMetricKeys(); + + underTest.setMetricKeys(emptyList()).build(); + } + + @Test + public void fail_when_unset_metric_keys() { + expectExceptionOnMetricKeys(); + + underTest.build(); + } + + @Test + public void fail_when_component_ids_and_keys_provided() { + expectExceptionOnComponents(); + + underTest + .setMetricKeys(singletonList("metric")) + .setComponentIds(singletonList("uuid")) + .setComponentKeys(singletonList("key")) + .build(); + } + + @Test + public void fail_when_component_ids_is_empty() { + expectExceptionOnComponents(); + + underTest + .setMetricKeys(singletonList("metric")) + .setComponentIds(emptyList()) + .build(); + } + + @Test + public void fail_when_component_keys_is_empty() { + expectExceptionOnComponents(); + + underTest + .setMetricKeys(singletonList("metric")) + .setComponentKeys(emptyList()) + .build(); + } + + private void expectExceptionOnMetricKeys() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Metric keys must be provided"); + } + + private void expectExceptionOnComponents() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Either component ids or component keys must be provided, not both."); + } +} |