summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2016-05-09 16:13:08 +0200
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2016-05-10 15:06:05 +0200
commit0d04b2f74fd9ab532b3dc82f4ebeebac8b2cca4f (patch)
treec8db045be4f52b806390083f5d34fa01682d4ea7 /server
parent861645c8498ef6b1782fa0b3ad1537d5226f1e41 (diff)
downloadsonarqube-0d04b2f74fd9ab532b3dc82f4ebeebac8b2cca4f.tar.gz
sonarqube-0d04b2f74fd9ab532b3dc82f4ebeebac8b2cca4f.zip
SONAR-7576 WS api/measures/component_tree filter components without measure on the sorted metric
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java19
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java54
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java50
3 files changed, 118 insertions, 5 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
index 8af4ffe482b..be23ef35f58 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
@@ -58,6 +58,7 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOP
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
@@ -80,16 +81,21 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEG
public class ComponentTreeAction implements MeasuresWsAction {
private static final int MAX_SIZE = 500;
private static final int QUERY_MINIMUM_LENGTH = 3;
+ // tree exploration strategies
static final String ALL_STRATEGY = "all";
static final String CHILDREN_STRATEGY = "children";
static final String LEAVES_STRATEGY = "leaves";
static final Set<String> STRATEGIES = ImmutableSortedSet.of(ALL_STRATEGY, CHILDREN_STRATEGY, LEAVES_STRATEGY);
+ // sort
static final String NAME_SORT = "name";
static final String PATH_SORT = "path";
static final String QUALIFIER_SORT = "qualifier";
static final String METRIC_SORT = "metric";
static final String METRIC_PERIOD_SORT = "metricPeriod";
static final Set<String> SORTS = ImmutableSortedSet.of(NAME_SORT, PATH_SORT, QUALIFIER_SORT, METRIC_SORT, METRIC_PERIOD_SORT);
+ static final String ALL_METRIC_SORT_FILTER = "all";
+ static final String WITH_MEASURES_ONLY_METRIC_SORT_FILTER = "withMeasuresOnly";
+ static final Set<String> METRIC_SORT_FILTERS = ImmutableSortedSet.of(ALL_METRIC_SORT_FILTER, WITH_MEASURES_ONLY_METRIC_SORT_FILTER);
private final ComponentTreeDataLoader dataLoader;
private final UserSession userSession;
@@ -152,6 +158,15 @@ public class ComponentTreeAction implements MeasuresWsAction {
.setSince("5.5")
.setPossibleValues(1, 2, 3, 4, 5);
+ action.createParam(PARAM_METRIC_SORT_FILTER)
+ .setDescription(format("Filter components. Sort must be on a metric. Possible values are: " +
+ "<ul>" +
+ "<li>%s: return all components</li>" +
+ "<li>%s: filter out components that do not have a measure on the sorted metric</li>" +
+ "</ul>", ALL_METRIC_SORT_FILTER, WITH_MEASURES_ONLY_METRIC_SORT_FILTER))
+ .setDefaultValue(ALL_METRIC_SORT_FILTER)
+ .setPossibleValues(METRIC_SORT_FILTERS);
+
createMetricKeysParameter(action);
createAdditionalFieldsParameter(action);
createDeveloperParameters(action);
@@ -253,6 +268,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
.setSort(request.paramAsStrings(Param.SORT))
.setAsc(request.paramAsBoolean(Param.ASCENDING))
.setMetricSort(request.param(PARAM_METRIC_SORT))
+ .setMetricSortFilter(request.mandatoryParam(PARAM_METRIC_SORT_FILTER))
.setMetricPeriodSort(request.paramAsInt(PARAM_METRIC_PERIOD_SORT))
.setDeveloperId(request.param(PARAM_DEVELOPER_ID))
.setDeveloperKey(request.param(PARAM_DEVELOPER_KEY))
@@ -273,6 +289,9 @@ public class ComponentTreeAction implements MeasuresWsAction {
"To sort by the '%s' metric, it must be in the list of metric keys in the '%s' parameter", metricSortValue, PARAM_METRIC_KEYS);
checkRequest(componentTreeWsRequest.getMetricPeriodSort() == null ^ componentTreeWsRequest.getSort().contains(METRIC_PERIOD_SORT),
"To sort by a metric period, the '%s' parameter must contain '%s' and the '%s' must be provided.", Param.SORT, METRIC_PERIOD_SORT, PARAM_METRIC_PERIOD_SORT);
+ checkRequest(ALL_METRIC_SORT_FILTER.equals(componentTreeWsRequest.getMetricSortFilter()) || metricSortValue != null,
+ "To filter components based on the sort metric, the '%s' parameter must contain '%s' or '%s' and the '%s' parameter must be provided",
+ Param.SORT, METRIC_SORT, METRIC_PERIOD_SORT, PARAM_METRIC_SORT);
return componentTreeWsRequest;
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java
index 789a2171d00..aa82944b8b6 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java
@@ -21,6 +21,7 @@ package org.sonar.server.measure.ws;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashBasedTable;
@@ -61,6 +62,7 @@ import org.sonarqube.ws.WsMeasures;
import org.sonarqube.ws.client.measure.ComponentTreeWsRequest;
import static com.google.common.base.Objects.firstNonNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
@@ -76,6 +78,7 @@ import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY;
import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_PERIOD_SORT;
import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_SORT;
import static org.sonar.server.measure.ws.ComponentTreeAction.NAME_SORT;
+import static org.sonar.server.measure.ws.ComponentTreeAction.WITH_MEASURES_ONLY_METRIC_SORT_FILTER;
import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
@@ -116,6 +119,7 @@ public class ComponentTreeDataLoader {
Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric = searchMeasuresByComponentUuidAndMetric(dbSession, baseComponent, baseSnapshot, components, metrics,
periods, developerId);
+ components = filterComponents(components, measuresByComponentUuidAndMetric, metrics, wsRequest);
components = sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric);
components = paginateComponents(components, componentCount, wsRequest);
Map<Long, ComponentDto> referenceComponentsById = searchReferenceComponentsById(dbSession, components);
@@ -227,8 +231,8 @@ public class ComponentTreeDataLoader {
/**
* Conditions for best value measure:
* <ul>
- * <li>component is a production file or test file</li>
- * <li>metric is optimized for best value</li>
+ * <li>component is a production file or test file</li>
+ * <li>metric is optimized for best value</li>
* </ul>
*/
private static void addBestValuesToMeasures(Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric, List<ComponentDtoWithSnapshotId> components,
@@ -251,6 +255,22 @@ public class ComponentTreeDataLoader {
}
}
+ private static List<ComponentDtoWithSnapshotId> filterComponents(List<ComponentDtoWithSnapshotId> components,
+ Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric,
+ List<MetricDto> metrics, ComponentTreeWsRequest wsRequest) {
+ if (!WITH_MEASURES_ONLY_METRIC_SORT_FILTER.equals(wsRequest.getMetricSortFilter())) {
+ return components;
+ }
+
+ final String metricKeyToSort = wsRequest.getMetricSort();
+ Optional<MetricDto> metricToSort = from(metrics).firstMatch(new MatchMetricKey(metricKeyToSort));
+ checkState(metricToSort.isPresent(), "Metric '%s' not found", metricKeyToSort, wsRequest.getMetricKeys());
+
+ return from(components)
+ .filter(new HasMeasure(measuresByComponentUuidAndMetric, metricToSort.get()))
+ .toList();
+ }
+
private static List<ComponentDtoWithSnapshotId> sortComponents(List<ComponentDtoWithSnapshotId> components, ComponentTreeWsRequest wsRequest, List<MetricDto> metrics,
Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric) {
if (!isSortByMetric(wsRequest)) {
@@ -276,6 +296,7 @@ public class ComponentTreeDataLoader {
}
private static boolean isSortByMetric(ComponentTreeWsRequest wsRequest) {
+ requireNonNull(wsRequest.getSort());
return wsRequest.getSort().contains(METRIC_SORT) || wsRequest.getSort().contains(METRIC_PERIOD_SORT);
}
@@ -389,9 +410,38 @@ public class ComponentTreeDataLoader {
private enum ComponentDtoWithSnapshotIdToCopyResourceIdFunction implements Function<ComponentDtoWithSnapshotId, Long> {
INSTANCE;
+
@Override
public Long apply(@Nonnull ComponentDtoWithSnapshotId input) {
return input.getCopyResourceId();
}
}
+
+ private static class HasMeasure implements Predicate<ComponentDtoWithSnapshotId> {
+ private final Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric;
+ private final MetricDto metric;
+
+ private HasMeasure(Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric, MetricDto metric) {
+ this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
+ this.metric = metric;
+ }
+
+ @Override
+ public boolean apply(@Nonnull ComponentDtoWithSnapshotId input) {
+ return measuresByComponentUuidAndMetric.contains(input.uuid(), metric);
+ }
+ }
+
+ private static class MatchMetricKey implements Predicate<MetricDto> {
+ private final String metricKeyToSort;
+
+ private MatchMetricKey(String metricKeyToSort) {
+ this.metricKeyToSort = metricKeyToSort;
+ }
+
+ @Override
+ public boolean apply(@Nonnull MetricDto input) {
+ return input.getKey().equals(metricKeyToSort);
+ }
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
index cc932d405b9..f523642673f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
@@ -70,6 +70,7 @@ import static org.sonar.server.measure.ws.ComponentTreeAction.LEAVES_STRATEGY;
import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_PERIOD_SORT;
import static org.sonar.server.measure.ws.ComponentTreeAction.METRIC_SORT;
import static org.sonar.server.measure.ws.ComponentTreeAction.NAME_SORT;
+import static org.sonar.server.measure.ws.ComponentTreeAction.WITH_MEASURES_ONLY_METRIC_SORT_FILTER;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ADDITIONAL_PERIODS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS;
@@ -79,6 +80,7 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_DEVELOP
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_KEYS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_PERIOD_SORT;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT;
+import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_METRIC_SORT_FILTER;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
@@ -256,6 +258,7 @@ public class ComponentTreeActionTest {
public void sort_by_metric_value() {
ComponentDto projectDto = newProjectDto("project-uuid");
SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(projectDto);
+ componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-4"), projectSnapshot);
SnapshotDto fileSnapshot3 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-3"), projectSnapshot);
SnapshotDto fileSnapshot1 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-1"), projectSnapshot);
SnapshotDto fileSnapshot2 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-2"), projectSnapshot);
@@ -273,7 +276,35 @@ public class ComponentTreeActionTest {
.setParam(PARAM_METRIC_SORT, "ncloc")
.setParam(PARAM_METRIC_KEYS, "ncloc"));
- assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3");
+ assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3", "file-uuid-4");
+ }
+
+ @Test
+ public void remove_components_without_measure_on_the_metric_sort() {
+ ComponentDto projectDto = newProjectDto("project-uuid");
+ SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(projectDto);
+ componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-4"), projectSnapshot);
+ SnapshotDto fileSnapshot3 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-3"), projectSnapshot);
+ SnapshotDto fileSnapshot1 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-1"), projectSnapshot);
+ SnapshotDto fileSnapshot2 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-2"), projectSnapshot);
+ MetricDto ncloc = newMetricDtoWithoutOptimization().setKey("ncloc").setValueType(ValueType.INT.name()).setDirection(1);
+ dbClient.metricDao().insert(dbSession, ncloc);
+ dbClient.measureDao().insert(dbSession,
+ newMeasureDto(ncloc, fileSnapshot1.getId()).setValue(1.0d),
+ newMeasureDto(ncloc, fileSnapshot2.getId()).setValue(2.0d),
+ newMeasureDto(ncloc, fileSnapshot3.getId()).setValue(3.0d));
+ db.commit();
+
+ ComponentTreeWsResponse response = call(ws.newRequest()
+ .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid")
+ .setParam(Param.SORT, METRIC_SORT)
+ .setParam(PARAM_METRIC_SORT, "ncloc")
+ .setParam(PARAM_METRIC_KEYS, "ncloc")
+ .setParam(PARAM_METRIC_SORT_FILTER, WITH_MEASURES_ONLY_METRIC_SORT_FILTER));
+
+ assertThat(response.getComponentsList()).extracting("id")
+ .containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3")
+ .doesNotContain("file-uuid-4");
}
@Test
@@ -393,8 +424,7 @@ public class ComponentTreeActionTest {
ComponentTreeWsResponse result = call(ws.newRequest()
.setParam(PARAM_BASE_COMPONENT_ID, projectUuid)
.setParam(PARAM_STRATEGY, LEAVES_STRATEGY)
- .setParam(PARAM_METRIC_KEYS, "ncloc")
- );
+ .setParam(PARAM_METRIC_KEYS, "ncloc"));
assertThat(result.getBaseComponent().getId()).isEqualTo(projectUuid);
assertThat(result.getComponentsCount()).isEqualTo(0);
@@ -527,6 +557,20 @@ public class ComponentTreeActionTest {
.setParam(Param.PAGE_SIZE, "2540"));
}
+ @Test
+ public void fail_when_with_measures_only_and_no_metric_sort() {
+ componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid"));
+ insertNclocMetric();
+ expectedException.expect(BadRequestException.class);
+ expectedException
+ .expectMessage("To filter components based on the sort metric, the 's' parameter must contain 'metric' or 'metricPeriod' and the 'metricSort' parameter must be provided");
+
+ call(ws.newRequest()
+ .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid")
+ .setParam(PARAM_METRIC_KEYS, "ncloc")
+ .setParam(PARAM_METRIC_SORT_FILTER, WITH_MEASURES_ONLY_METRIC_SORT_FILTER));
+ }
+
private static ComponentTreeWsResponse call(TestRequest request) {
TestResponse testResponse = request
.setMediaType(MediaTypes.PROTOBUF)