From 7bc5f6946250e336f1c61e607f5314b68f29d828 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Fri, 30 Sep 2016 14:16:45 +0200 Subject: [PATCH] SONAR-8120 WS measures/search flatten the response --- .../measure/ws/ComponentDtoToWsComponent.java | 20 +- .../measure/ws/MeasureDtoToWsMeasure.java | 34 ++- .../sonar/server/measure/ws/SearchAction.java | 69 +++--- .../server/measure/ws/search-example.json | 216 +++++++++--------- .../server/measure/ws/SearchActionTest.java | 36 +-- sonar-ws/src/main/protobuf/ws-measures.proto | 22 +- 6 files changed, 209 insertions(+), 188 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 f0d8356d63b..cca86c0b887 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 @@ -20,10 +20,12 @@ 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 measuresByMetric, + static Component.Builder componentDtoToWsComponent(ComponentDto component, Map measuresByMetric, Map 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 measures) { - return componentDtoToWsComponent(dbComponent).addAllMeasures(measures).build(); + static Function 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()) diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java index 85019fac7d6..ab09564a5d1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java @@ -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); } } } 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 index cf8bcb25cb7..d1fcdddaeff 100644 --- 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 @@ -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 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 metricsById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity())); + List wsMeasures = buildWsMeasures(); + List wsComponents = buildWsComponents(); - ImmutableMultimap 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::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 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 buildWsMeasures() { + Map componentNamesByUuid = components.stream().collect(Collectors.toMap(ComponentDto::uuid, ComponentDto::name)); + Map metricsById = metrics.stream().collect(Collectors.toMap(MetricDto::getId, identity())); + + Function dbMeasureToDbMetric = dbMeasure -> metricsById.get(dbMeasure.getMetricId()); + Function byMetricKey = Measure::getMetric; + Function 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 buildBestMeasures() { 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 index 5c74a9b2764..1ca772d5db5 100644 --- 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 @@ -1,146 +1,140 @@ { - "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" + } ] } 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 index c4c1515cfb5..02c578da954 100644 --- 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 @@ -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 fileMeasures = result.getComponentsList().get(1).getMeasuresList(); - assertThat(fileMeasures).extracting("metric").containsOnly("ncloc", "coverage", "new_violations"); - assertThat(fileMeasures).extracting("value").containsOnly("100", "15.5", ""); + List 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 diff --git a/sonar-ws/src/main/protobuf/ws-measures.proto b/sonar-ws/src/main/protobuf/ws-measures.proto index b29ffdf8c12..7034be2dd3a 100644 --- a/sonar-ws/src/main/protobuf/ws-measures.proto +++ b/sonar-ws/src/main/protobuf/ws-measures.proto @@ -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 { -- 2.39.5