Browse Source

SONAR-8120 WS measures/search flatten the response

tags/6.2-RC1
Teryk Bellahsene 7 years ago
parent
commit
7bc5f69462

+ 13
- 7
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentDtoToWsComponent.java View File

@@ -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<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())

+ 24
- 10
server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java View 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);
}
}
}

+ 32
- 37
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java View 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() {

+ 105
- 111
server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json View File

@@ -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"
}
]
}

+ 20
- 16
server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java View 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

+ 15
- 7
sonar-ws/src/main/protobuf/ws-measures.proto View 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 {

Loading…
Cancel
Save