]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8120 WS measures/search flatten the response 1272/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 30 Sep 2016 12:16:45 +0000 (14:16 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 30 Sep 2016 12:17:25 +0000 (14:17 +0200)
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java
server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json
server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java
sonar-ws/src/main/protobuf/ws-measures.proto

index f0d8356d63b5b8cc4a85becc627be5fba4033923..cca86c0b8875548cdd15e1f7784ec1f327a37761 100644 (file)
 package org.sonar.server.measure.ws;
 
 import java.util.Map;
+import java.util.function.Function;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.metric.MetricDto;
-import org.sonarqube.ws.WsMeasures;
+import org.sonarqube.ws.WsMeasures.Component;
+import org.sonarqube.ws.WsMeasures.SearchWsResponse;
 
 import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.measureDtoToWsMeasure;
 
@@ -32,9 +34,9 @@ class ComponentDtoToWsComponent {
     // static methods only
   }
 
-  static WsMeasures.Component.Builder componentDtoToWsComponent(ComponentDto component, Map<MetricDto, MeasureDto> measuresByMetric,
+  static Component.Builder componentDtoToWsComponent(ComponentDto component, Map<MetricDto, MeasureDto> measuresByMetric,
     Map<String, ComponentDto> referenceComponentsByUuid) {
-    WsMeasures.Component.Builder wsComponent = componentDtoToWsComponent(component);
+    Component.Builder wsComponent = componentDtoToWsComponent(component);
 
     ComponentDto referenceComponent = referenceComponentsByUuid.get(component.getCopyResourceUuid());
     if (referenceComponent != null) {
@@ -49,12 +51,16 @@ class ComponentDtoToWsComponent {
     return wsComponent;
   }
 
-  static WsMeasures.Component dbToWsComponent(ComponentDto dbComponent, Iterable<WsMeasures.Measure> measures) {
-    return componentDtoToWsComponent(dbComponent).addAllMeasures(measures).build();
+  static Function<ComponentDto, SearchWsResponse.Component> dbToWsComponent() {
+    return dbComponent -> SearchWsResponse.Component.newBuilder()
+      .setId(dbComponent.uuid())
+      .setKey(dbComponent.key())
+      .setName(dbComponent.name())
+      .build();
   }
 
-  static WsMeasures.Component.Builder componentDtoToWsComponent(ComponentDto component) {
-    WsMeasures.Component.Builder wsComponent = WsMeasures.Component.newBuilder()
+  static Component.Builder componentDtoToWsComponent(ComponentDto component) {
+    Component.Builder wsComponent = Component.newBuilder()
       .setId(component.uuid())
       .setKey(component.key())
       .setName(component.name())
index 85019fac7d652d23f82484855a0d304b7ede97fb..ab09564a5d1bd1652b08cfff17d6eff164782c4a 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.measure.ws;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonarqube.ws.WsMeasures;
+import org.sonarqube.ws.WsMeasures.Measure;
 
 import static org.sonar.server.measure.ws.MeasureValueFormatter.formatMeasureValue;
 import static org.sonar.server.measure.ws.MeasureValueFormatter.formatNumericalValue;
@@ -32,29 +33,42 @@ class MeasureDtoToWsMeasure {
     // static methods
   }
 
-  static WsMeasures.Measure measureDtoToWsMeasure(MetricDto metricDto, MeasureDto measureDto) {
+  /**
+   * @see #measureDtoToWsMeasure(MetricDto, MeasureDto)
+   * add component uuid to the WS Measure object
+   *
+   */
+  static Measure dbToWsMeasure(MeasureDto dbMeasure, MetricDto dbMetric) {
+    return map(dbMetric, dbMeasure).setComponent(dbMeasure.getComponentUuid()).build();
+  }
+
+  static Measure measureDtoToWsMeasure(MetricDto metricDto, MeasureDto measureDto) {
+    return map(metricDto, measureDto).build();
+  }
+
+  private static Measure.Builder map(MetricDto dbMetric, MeasureDto dbMeasure) {
     try {
-      WsMeasures.Measure.Builder measure = WsMeasures.Measure.newBuilder();
-      measure.setMetric(metricDto.getKey());
+      Measure.Builder measure = Measure.newBuilder();
+      measure.setMetric(dbMetric.getKey());
       // a measure value can be null, new_violations metric for example
-      if (measureDto.getValue() != null
-        || measureDto.getData() != null) {
-        measure.setValue(formatMeasureValue(measureDto, metricDto));
+      if (dbMeasure.getValue() != null
+        || dbMeasure.getData() != null) {
+        measure.setValue(formatMeasureValue(dbMeasure, dbMetric));
       }
 
       WsMeasures.PeriodValue.Builder periodBuilder = WsMeasures.PeriodValue.newBuilder();
       for (int i = 1; i <= 5; i++) {
-        if (measureDto.getVariation(i) != null) {
+        if (dbMeasure.getVariation(i) != null) {
           measure.getPeriodsBuilder().addPeriodsValue(periodBuilder
             .clear()
             .setIndex(i)
-            .setValue(formatNumericalValue(measureDto.getVariation(i), metricDto)));
+            .setValue(formatNumericalValue(dbMeasure.getVariation(i), dbMetric)));
         }
       }
 
-      return measure.build();
+      return measure;
     } catch (Exception e) {
-      throw new IllegalStateException(String.format("Error while mapping a measure of metric key '%s' and parameters %s", metricDto.getKey(), measureDto.toString()), e);
+      throw new IllegalStateException(String.format("Error while mapping a measure of metric key '%s' and parameters %s", dbMetric.getKey(), dbMeasure.toString()), e);
     }
   }
 }
index cf8bcb25cb71bc5cb4e9b3182ada0a6d86851ad0..d1fcdddaeffbd9fc05b7aad15dbd0b2eba3e7e85 100644 (file)
@@ -45,18 +45,20 @@ 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.Measure;
 import org.sonarqube.ws.WsMeasures.SearchWsResponse;
+import org.sonarqube.ws.WsMeasures.SearchWsResponse.Component;
 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.Comparator.comparing;
 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.MeasureDtoToWsMeasure.dbToWsMeasure;
 import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
 import static org.sonar.server.measure.ws.MetricDtoWithBestValue.buildBestMeasure;
 import static org.sonar.server.measure.ws.MetricDtoWithBestValue.isEligibleForBestValue;
@@ -133,7 +135,7 @@ public class SearchAction implements MeasuresWsAction {
     }
 
     SearchWsResponse build() {
-      this.request = setRequest();
+      this.request = createRequest();
       this.components = searchComponents();
       this.metrics = searchMetrics();
       this.measures = searchMeasures();
@@ -149,16 +151,13 @@ public class SearchAction implements MeasuresWsAction {
       return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);
     }
 
-    private SearchRequest setRequest() {
+    private SearchRequest createRequest() {
       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;
     }
 
@@ -200,11 +199,6 @@ public class SearchAction implements MeasuresWsAction {
       requireNonNull(components);
       requireNonNull(metrics);
 
-      Set<String> projectUuids = components.stream().map(ComponentDto::projectUuid).collect(Collectors.toSet());
-
-      dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids).stream()
-        .map(SnapshotDtoToWsPeriods::snapshotToWsPeriods);
-
       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()))
@@ -226,34 +220,35 @@ public class SearchAction implements MeasuresWsAction {
       requireNonNull(components);
       requireNonNull(snapshots);
 
-      Map<Integer, MetricDto> metricsById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity()));
+      List<Measure> wsMeasures = buildWsMeasures();
+      List<Component> wsComponents = buildWsComponents();
 
-      ImmutableMultimap<String, WsMeasures.Measure> wsMeasuresByComponentUuid = Stream
-        .concat(measures.stream(), buildBestMeasures().stream())
-        .collect(Collectors.toMap(identity(), MeasureDto::getComponentUuid))
-        .entrySet().stream()
-        .map(entry -> immutableEntry(
-          measureDtoToWsMeasure(metricsById.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 while building WS measures");
-          },
-          ImmutableMultimap.Builder::build));
+      return SearchWsResponse.newBuilder()
+        .addAllMeasures(wsMeasures)
+        .addAllComponents(wsComponents)
+        .build();
+    }
 
+    private List<Component> buildWsComponents() {
       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 building SearchWsResponse");
-          },
-          SearchWsResponse.Builder::build));
+        .map(dbToWsComponent())
+        .sorted(comparing(Component::getName))
+        .collect(Collectors.toList());
+    }
+
+    private List<Measure> buildWsMeasures() {
+      Map<String, String> componentNamesByUuid = components.stream().collect(Collectors.toMap(ComponentDto::uuid, ComponentDto::name));
+      Map<Integer, MetricDto> metricsById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity()));
+
+      Function<MeasureDto, MetricDto> dbMeasureToDbMetric = dbMeasure -> metricsById.get(dbMeasure.getMetricId());
+      Function<Measure, String> byMetricKey = Measure::getMetric;
+      Function<Measure, String> byComponentName = wsMeasure -> componentNamesByUuid.get(wsMeasure.getComponent());
+
+      return Stream
+        .concat(measures.stream(), buildBestMeasures().stream())
+        .map(dbMeasure -> dbToWsMeasure(dbMeasure, dbMeasureToDbMetric.apply(dbMeasure)))
+        .sorted(comparing(byMetricKey).thenComparing(byComponentName))
+        .collect(Collectors.toList());
     }
 
     private List<MeasureDto> buildBestMeasures() {
index 5c74a9b27649357ecfe8d9ea7879a03d8f0becb3..1ca772d5db5ac9cea5c37933535de5a491109c3e 100644 (file)
 {
-  "components": [
+  "measures": [
     {
-      "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",
+      "component": "AVIwDXE-bJbJqrw6wFv5",
+      "value": "12"
+    },
+    {
+      "metric": "complexity",
+      "component": "project-id",
+      "value": "42"
+    },
+    {
+      "metric": "complexity",
+      "component": "AVIwDXE-bJbJqrw6wFv8",
+      "value": "35",
+      "periods": [
+        {
+          "index": 2,
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "metric": "ncloc",
+      "component": "AVIwDXE-bJbJqrw6wFv5",
+      "value": "114"
+    },
+    {
+      "metric": "ncloc",
+      "component": "project-id",
+      "value": "1984"
+    },
+    {
+      "metric": "ncloc",
+      "component": "AVIwDXE-bJbJqrw6wFv8",
+      "value": "217",
+      "periods": [
+        {
+          "index": 2,
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "metric": "new_violations",
+      "component": "AVIwDXE-bJbJqrw6wFv5",
+      "periods": [
         {
-          "metric": "complexity",
-          "value": "12"
+          "index": 1,
+          "value": "25"
         },
         {
-          "metric": "ncloc",
-          "value": "114"
+          "index": 2,
+          "value": "0"
         },
         {
-          "metric": "new_violations",
-          "periods": [
-            {
-              "index": 1,
-              "value": "25"
-            },
-            {
-              "index": 2,
-              "value": "0"
-            },
-            {
-              "index": 3,
-              "value": "25"
-            }
-          ]
+          "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": [
+      "metric": "new_violations",
+      "component": "AVIwDXE_bJbJqrw6wFwJ",
+      "periods": [
+        {
+          "index": 1,
+          "value": "0"
+        },
+        {
+          "index": 2,
+          "value": "0"
+        },
         {
-          "metric": "new_violations",
-          "periods": [
-            {
-              "index": 1,
-              "value": "0"
-            },
-            {
-              "index": 2,
-              "value": "0"
-            },
-            {
-              "index": 3,
-              "value": "0"
-            }
-          ]
+          "index": 3,
+          "value": "0"
         }
       ]
     },
     {
-      "id": "project-id",
-      "key": "MY_PROJECT",
-      "name": "My Project",
-      "description": "My Project Description",
-      "qualifier": "TRK",
-      "measures": [
+      "metric": "new_violations",
+      "component": "project-id",
+      "periods": [
         {
-          "metric": "complexity",
-          "value": "42"
+          "index": 1,
+          "value": "255"
         },
         {
-          "metric": "ncloc",
-          "value": "1984"
+          "index": 2,
+          "value": "0"
         },
         {
-          "metric": "new_violations",
-          "periods": [
-            {
-              "index": 1,
-              "value": "255"
-            },
-            {
-              "index": 2,
-              "value": "0"
-            },
-            {
-              "index": 3,
-              "value": "255"
-            }
-          ]
+          "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": "new_violations",
+      "component": "AVIwDXE-bJbJqrw6wFv8",
+      "periods": [
         {
-          "metric": "complexity",
-          "value": "35",
-          "periods": [
-            {
-              "index": 2,
-              "value": "0"
-            }
-          ]
+          "index": 1,
+          "value": "25"
         },
         {
-          "metric": "ncloc",
-          "value": "217",
-          "periods": [
-            {
-              "index": 2,
-              "value": "0"
-            }
-          ]
+          "index": 2,
+          "value": "0"
         },
         {
-          "metric": "new_violations",
-          "periods": [
-            {
-              "index": 1,
-              "value": "25"
-            },
-            {
-              "index": 2,
-              "value": "0"
-            },
-            {
-              "index": 3,
-              "value": "25"
-            }
-          ]
+          "index": 3,
+          "value": "25"
         }
       ]
     }
+  ],
+  "components": [
+    {
+      "id": "AVIwDXE-bJbJqrw6wFv5",
+      "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl/ElementImpl.java",
+      "name": "ElementImpl.java"
+    },
+    {
+      "id": "AVIwDXE_bJbJqrw6wFwJ",
+      "key": "com.sonarsource:java-markdown:src/test/java/com/sonarsource/markdown/impl/ElementImplTest.java",
+      "name": "ElementImplTest.java"
+    },
+    {
+      "id": "project-id",
+      "key": "MY_PROJECT",
+      "name": "My Project"
+    },
+    {
+      "id": "AVIwDXE-bJbJqrw6wFv8",
+      "key": "com.sonarsource:java-markdown:src/main/java/com/sonarsource/markdown/impl",
+      "name": "src/main/java/com/sonarsource/markdown/impl"
+    }
   ]
 }
index c4c1515cfb5588ab210fdb7bbf49d72840280a6d..02c578da9542224c6eec8350cba9a0d08850dd8c 100644 (file)
@@ -43,8 +43,7 @@ import org.sonar.db.metric.MetricDto;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
-import org.sonarqube.ws.WsMeasures;
-import org.sonarqube.ws.WsMeasures.Component;
+import org.sonarqube.ws.WsMeasures.Measure;
 import org.sonarqube.ws.WsMeasures.SearchWsResponse;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -87,7 +86,6 @@ public class SearchActionTest {
       .getInput();
 
     assertJson(result).withStrictArrayOrder().isSimilarTo(ws.getDef().responseExampleAsString());
-
   }
 
   @Test
@@ -98,18 +96,21 @@ public class SearchActionTest {
     SearchWsResponse result = callByComponentUuids(singletonList(dbComponent.uuid()), singletonList("complexity"));
 
     assertThat(result.getComponentsCount()).isEqualTo(1);
-    Component wsComponent = result.getComponents(0);
-    assertThat(wsComponent.getMeasuresCount()).isEqualTo(0);
+    SearchWsResponse.Component wsComponent = result.getComponents(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_uuid() {
+    ComponentDto project = componentDb.insertProject();
+    insertComplexityMetric();
+
+    SearchWsResponse result = callByComponentUuids(singletonList(project.uuid()), singletonList("complexity"));
+
+    assertThat(result.getComponentsCount()).isEqualTo(1);
+    assertThat(result.getComponents(0).getId()).isEqualTo(project.uuid());
   }
 
   @Test
@@ -151,11 +152,14 @@ public class SearchActionTest {
     SearchWsResponse result = callByComponentUuids(newArrayList(directoryDto.uuid(), file.uuid()), newArrayList("ncloc", "coverage", "new_violations"));
 
     // directory is not eligible for best value
-    assertThat(result.getComponentsList().get(0).getMeasuresList()).extracting("metric").containsOnly("coverage");
+    assertThat(result.getMeasuresList().stream()
+      .filter(measure -> directoryDto.uuid().equals(measure.getComponent()))
+      .map(Measure::getMetric))
+        .containsOnly("coverage");
     // file measures
-    List<WsMeasures.Measure> fileMeasures = result.getComponentsList().get(1).getMeasuresList();
-    assertThat(fileMeasures).extracting("metric").containsOnly("ncloc", "coverage", "new_violations");
-    assertThat(fileMeasures).extracting("value").containsOnly("100", "15.5", "");
+    List<Measure> fileMeasures = result.getMeasuresList().stream().filter(measure -> file.uuid().equals(measure.getComponent())).collect(Collectors.toList());
+    assertThat(fileMeasures).extracting(Measure::getMetric).containsOnly("ncloc", "coverage", "new_violations");
+    assertThat(fileMeasures).extracting(Measure::getValue).containsOnly("100", "15.5", "");
   }
 
   @Test
index b29ffdf8c12456b680735c107e3520c61fba4944..7034be2dd3a41f225d11f3952fc27ddaf9935eb9 100644 (file)
@@ -44,16 +44,23 @@ message ComponentWsResponse {
 
 // WS api/measures/search
 message SearchWsResponse {
-  repeated Component components = 1;
+  repeated Measure measures = 1;
+  repeated Component components = 2;
+
+  message Component {
+    optional string id = 1;
+    optional string key = 2;
+    optional string name = 3;
+  }
 }
 
 message Component {
   optional string id = 1;
   optional string key = 2;
-  optional string refId = 3;
-  optional string refKey = 4;
-  optional string projectId = 5;
-  optional string name = 6;
+  optional string name = 3;
+  optional string refId = 4;
+  optional string refKey = 5;
+  optional string projectId = 6;
   optional string description = 7;
   optional string qualifier = 8;
   optional string path = 9;
@@ -78,8 +85,9 @@ message Metrics {
 
 message Measure {
   optional string metric = 1;
-  optional string value = 2;
-  optional PeriodsValue periods = 3;
+  optional string component = 2;
+  optional string value = 3;
+  optional PeriodsValue periods = 4;
 }
 
 message PeriodsValue {