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

package org.sonar.server.measure.ws; package org.sonar.server.measure.ws;


import java.util.Map; import java.util.Map;
import java.util.function.Function;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.measure.MeasureDto; import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto; 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; import static org.sonar.server.measure.ws.MeasureDtoToWsMeasure.measureDtoToWsMeasure;


// static methods only // 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) { Map<String, ComponentDto> referenceComponentsByUuid) {
WsMeasures.Component.Builder wsComponent = componentDtoToWsComponent(component);
Component.Builder wsComponent = componentDtoToWsComponent(component);


ComponentDto referenceComponent = referenceComponentsByUuid.get(component.getCopyResourceUuid()); ComponentDto referenceComponent = referenceComponentsByUuid.get(component.getCopyResourceUuid());
if (referenceComponent != null) { if (referenceComponent != null) {
return wsComponent; 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()) .setId(component.uuid())
.setKey(component.key()) .setKey(component.key())
.setName(component.name()) .setName(component.name())

+ 24
- 10
server/sonar-server/src/main/java/org/sonar/server/measure/ws/MeasureDtoToWsMeasure.java View File

import org.sonar.db.measure.MeasureDto; import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto; import org.sonar.db.metric.MetricDto;
import org.sonarqube.ws.WsMeasures; 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.formatMeasureValue;
import static org.sonar.server.measure.ws.MeasureValueFormatter.formatNumericalValue; import static org.sonar.server.measure.ws.MeasureValueFormatter.formatNumericalValue;
// static methods // 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 { 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 // 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(); WsMeasures.PeriodValue.Builder periodBuilder = WsMeasures.PeriodValue.newBuilder();
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++) {
if (measureDto.getVariation(i) != null) {
if (dbMeasure.getVariation(i) != null) {
measure.getPeriodsBuilder().addPeriodsValue(periodBuilder measure.getPeriodsBuilder().addPeriodsValue(periodBuilder
.clear() .clear()
.setIndex(i) .setIndex(i)
.setValue(formatNumericalValue(measureDto.getVariation(i), metricDto)));
.setValue(formatNumericalValue(dbMeasure.getVariation(i), dbMetric)));
} }
} }


return measure.build();
return measure;
} catch (Exception e) { } 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

import org.sonar.db.measure.MeasureQuery; import org.sonar.db.measure.MeasureQuery;
import org.sonar.db.metric.MetricDto; import org.sonar.db.metric.MetricDto;
import org.sonarqube.ws.WsMeasures; import org.sonarqube.ws.WsMeasures;
import org.sonarqube.ws.WsMeasures.Measure;
import org.sonarqube.ws.WsMeasures.SearchWsResponse; import org.sonarqube.ws.WsMeasures.SearchWsResponse;
import org.sonarqube.ws.WsMeasures.SearchWsResponse.Component;
import org.sonarqube.ws.client.measure.SearchRequest; import org.sonarqube.ws.client.measure.SearchRequest;


import static com.google.common.base.Preconditions.checkArgument; 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.Objects.requireNonNull;
import static java.util.function.Function.identity; 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_01;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_03; 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.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.MeasuresWsParametersBuilder.createMetricKeysParameter;
import static org.sonar.server.measure.ws.MetricDtoWithBestValue.buildBestMeasure; import static org.sonar.server.measure.ws.MetricDtoWithBestValue.buildBestMeasure;
import static org.sonar.server.measure.ws.MetricDtoWithBestValue.isEligibleForBestValue; import static org.sonar.server.measure.ws.MetricDtoWithBestValue.isEligibleForBestValue;
} }


SearchWsResponse build() { SearchWsResponse build() {
this.request = setRequest();
this.request = createRequest();
this.components = searchComponents(); this.components = searchComponents();
this.metrics = searchMetrics(); this.metrics = searchMetrics();
this.measures = searchMeasures(); this.measures = searchMeasures();
return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids); return dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);
} }


private SearchRequest setRequest() {
private SearchRequest createRequest() {
request = SearchRequest.builder() request = SearchRequest.builder()
.setMetricKeys(httpRequest.mandatoryParamAsStrings(PARAM_METRIC_KEYS)) .setMetricKeys(httpRequest.mandatoryParamAsStrings(PARAM_METRIC_KEYS))
.setComponentIds(httpRequest.paramAsStrings(PARAM_COMPONENT_IDS)) .setComponentIds(httpRequest.paramAsStrings(PARAM_COMPONENT_IDS))
.setComponentKeys(httpRequest.paramAsStrings(PARAM_COMPONENT_KEYS)) .setComponentKeys(httpRequest.paramAsStrings(PARAM_COMPONENT_KEYS))
.build(); .build();


this.components = searchComponents();
this.metrics = searchMetrics();

return request; return request;
} }


requireNonNull(components); requireNonNull(components);
requireNonNull(metrics); 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() return dbClient.measureDao().selectByQuery(dbSession, MeasureQuery.builder()
.setComponentUuids(components.stream().map(ComponentDto::uuid).collect(Collectors.toList())) .setComponentUuids(components.stream().map(ComponentDto::uuid).collect(Collectors.toList()))
.setMetricIds(metrics.stream().map(MetricDto::getId).collect(Collectors.toList())) .setMetricIds(metrics.stream().map(MetricDto::getId).collect(Collectors.toList()))
requireNonNull(components); requireNonNull(components);
requireNonNull(snapshots); 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() 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() { private List<MeasureDto> buildBestMeasures() {

+ 105
- 111
server/sonar-server/src/main/resources/org/sonar/server/measure/ws/search-example.json View 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"
}
] ]
} }

+ 20
- 16
server/sonar-server/src/test/java/org/sonar/server/measure/ws/SearchActionTest.java View File

import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester; 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 org.sonarqube.ws.WsMeasures.SearchWsResponse;


import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
.getInput(); .getInput();


assertJson(result).withStrictArrayOrder().isSimilarTo(ws.getDef().responseExampleAsString()); assertJson(result).withStrictArrayOrder().isSimilarTo(ws.getDef().responseExampleAsString());

} }


@Test @Test
SearchWsResponse result = callByComponentUuids(singletonList(dbComponent.uuid()), singletonList("complexity")); SearchWsResponse result = callByComponentUuids(singletonList(dbComponent.uuid()), singletonList("complexity"));


assertThat(result.getComponentsCount()).isEqualTo(1); 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.getId()).isEqualTo(dbComponent.uuid());
assertThat(wsComponent.getKey()).isEqualTo(dbComponent.key()); assertThat(wsComponent.getKey()).isEqualTo(dbComponent.key());
assertThat(wsComponent.getQualifier()).isEqualTo(dbComponent.qualifier());
assertThat(wsComponent.getName()).isEqualTo(dbComponent.name()); 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 @Test
SearchWsResponse result = callByComponentUuids(newArrayList(directoryDto.uuid(), file.uuid()), newArrayList("ncloc", "coverage", "new_violations")); SearchWsResponse result = callByComponentUuids(newArrayList(directoryDto.uuid(), file.uuid()), newArrayList("ncloc", "coverage", "new_violations"));


// directory is not eligible for best value // 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 // 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 @Test

+ 15
- 7
sonar-ws/src/main/protobuf/ws-measures.proto View File



// WS api/measures/search // WS api/measures/search
message SearchWsResponse { 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 { message Component {
optional string id = 1; optional string id = 1;
optional string key = 2; 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 description = 7;
optional string qualifier = 8; optional string qualifier = 8;
optional string path = 9; optional string path = 9;


message Measure { message Measure {
optional string metric = 1; 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 { message PeriodsValue {

Loading…
Cancel
Save