]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7196 WS api/measures/component_tree supports sorting by period variation
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 11 Mar 2016 14:38:36 +0000 (15:38 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 11 Mar 2016 15:57:21 +0000 (16:57 +0100)
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java

index 3126dfdbdf5e7250521ab183ac641c41c223ac5e..b7d26b033b248852a53bf9cee5b385e8859c2d1a 100644 (file)
@@ -53,6 +53,7 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIO
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_ID;
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_KEY;
 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_QUALIFIERS;
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
@@ -84,7 +85,8 @@ public class ComponentTreeAction implements MeasuresWsAction {
   static final String PATH_SORT = "path";
   static final String QUALIFIER_SORT = "qualifier";
   static final String METRIC_SORT = "metric";
-  static final Set<String> SORTS = ImmutableSortedSet.of(NAME_SORT, PATH_SORT, QUALIFIER_SORT, METRIC_SORT);
+  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);
 
   private final ComponentTreeDataLoader dataLoader;
   private final UserSession userSession;
@@ -118,7 +120,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
 
     action.createSortParams(SORTS, NAME_SORT, true)
       .setDescription("Comma-separated list of sort fields")
-      .setExampleValue(NAME_SORT + ", " + PATH_SORT);
+      .setExampleValue(NAME_SORT + "," + PATH_SORT);
 
     action.createParam(Param.TEXT_QUERY)
       .setDescription(format("Limit search to: <ul>" +
@@ -141,6 +143,11 @@ public class ComponentTreeAction implements MeasuresWsAction {
         format("Metric key to sort by. The '%s' parameter must contain the '%s' value. It must be part of the '%s' parameter", Param.SORT, METRIC_SORT, PARAM_METRIC_KEYS))
       .setExampleValue("ncloc");
 
+    action.createParam(PARAM_METRIC_PERIOD_SORT)
+      .setDescription(format("Measure period to sort by. The '%s' parameter must contain the '%s' value.", Param.SORT, METRIC_PERIOD_SORT))
+      .setSince("5.5")
+      .setPossibleValues(1, 2, 3, 4, 5);
+
     createMetricKeysParameter(action);
     createAdditionalFieldsParameter(action);
     createQualifiersParameter(action, newQualifierParameterContext(userSession, i18n, resourceTypes));
@@ -241,6 +248,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
       .setSort(request.paramAsStrings(Param.SORT))
       .setAsc(request.paramAsBoolean(Param.ASCENDING))
       .setMetricSort(request.param(PARAM_METRIC_SORT))
+      .setMetricPeriodSort(request.paramAsInt(PARAM_METRIC_PERIOD_SORT))
       .setPage(request.mandatoryParamAsInt(Param.PAGE))
       .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
       .setQuery(request.param(Param.TEXT_QUERY));
@@ -250,11 +258,14 @@ public class ComponentTreeAction implements MeasuresWsAction {
       "The '%s' parameter must have at least %d characters", Param.TEXT_QUERY, QUERY_MINIMUM_LENGTH);
     String metricSortValue = componentTreeWsRequest.getMetricSort();
     checkRequest(!componentTreeWsRequest.getMetricKeys().isEmpty(), "The '%s' parameter must contain at least one metric key", PARAM_METRIC_KEYS);
-    checkRequest(metricSortValue == null ^ componentTreeWsRequest.getSort().contains(METRIC_SORT),
-      "To sort by a metric, the '%s' parameter must contain '%s' and a metric key must be provided in the '%s' parameter",
-      Param.SORT, METRIC_SORT, PARAM_METRIC_SORT);
+    checkRequest(metricSortValue == null ^ componentTreeWsRequest.getSort().contains(METRIC_SORT)
+      ^ componentTreeWsRequest.getSort().contains(METRIC_PERIOD_SORT),
+      "To sort by a metric, the '%s' parameter must contain '%s' or '%s', and a metric key must be provided in the '%s' parameter",
+      Param.SORT, METRIC_SORT, METRIC_PERIOD_SORT, PARAM_METRIC_SORT);
     checkRequest(metricSortValue == null ^ componentTreeWsRequest.getMetricKeys().contains(metricSortValue),
       "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);
     return componentTreeWsRequest;
   }
 }
index 42f695add9b4f2b4e780f59be012bf4a8a4fef20..fd26d5ba2b62db548ef5657ff39d79c5d6ebce1c 100644 (file)
@@ -69,6 +69,7 @@ import static org.sonar.server.component.ComponentFinder.ParamNames.BASE_COMPONE
 import static org.sonar.server.measure.ws.ComponentTreeAction.ALL_STRATEGY;
 import static org.sonar.server.measure.ws.ComponentTreeAction.CHILDREN_STRATEGY;
 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.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
@@ -232,7 +233,7 @@ public class ComponentTreeDataLoader {
 
   private static List<ComponentDtoWithSnapshotId> sortComponents(List<ComponentDtoWithSnapshotId> components, ComponentTreeWsRequest wsRequest, List<MetricDto> metrics,
     Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric) {
-    if (!wsRequest.getSort().contains(METRIC_SORT)) {
+    if (!isSortByMetric(wsRequest)) {
       return components;
     }
 
@@ -240,7 +241,7 @@ public class ComponentTreeDataLoader {
   }
 
   private static List<ComponentDtoWithSnapshotId> paginateComponents(List<ComponentDtoWithSnapshotId> components, int componentCount, ComponentTreeWsRequest wsRequest) {
-    if (!wsRequest.getSort().contains(METRIC_SORT)) {
+    if (!isSortByMetric(wsRequest)) {
       return components;
     }
 
@@ -254,6 +255,10 @@ public class ComponentTreeDataLoader {
       .toList();
   }
 
+  private static boolean isSortByMetric(ComponentTreeWsRequest wsRequest) {
+    return wsRequest.getSort().contains(METRIC_SORT) || wsRequest.getSort().contains(METRIC_PERIOD_SORT);
+  }
+
   @CheckForNull
   private List<String> childrenQualifiers(ComponentTreeWsRequest request, String baseQualifier) {
     List<String> requestQualifiers = request.getQualifiers();
@@ -295,7 +300,7 @@ public class ComponentTreeDataLoader {
       dbQuery.setQualifiers(childrenQualifiers);
     }
     // load all components if we must sort by metric value
-    if (wsRequest.getSort().contains(METRIC_SORT)) {
+    if (isSortByMetric(wsRequest)) {
       dbQuery.setPage(1);
       dbQuery.setPageSize(Integer.MAX_VALUE);
     }
@@ -358,7 +363,7 @@ public class ComponentTreeDataLoader {
 
     @Override
     public boolean apply(@Nonnull String input) {
-      return !input.equals(METRIC_SORT);
+      return !input.equals(METRIC_SORT) && !input.equals(METRIC_PERIOD_SORT);
     }
   }
 
index 6c136b2f4904a0aa6c00574bdc762741b462db2e..3f9cc3e042e5a5c00d1322c83ed53b4e239440a9 100644 (file)
@@ -21,11 +21,13 @@ package org.sonar.server.measure.ws;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Table;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.sonar.api.measures.Metric.ValueType;
@@ -33,9 +35,23 @@ import org.sonar.db.component.ComponentDtoWithSnapshotId;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.metric.MetricDtoFunctions;
+import org.sonar.server.exceptions.BadRequestException;
 import org.sonarqube.ws.client.measure.ComponentTreeWsRequest;
 
 import static java.lang.String.CASE_INSENSITIVE_ORDER;
+import static java.lang.String.format;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.LEVEL;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+import static org.sonar.api.measures.Metric.ValueType.PERCENT;
+import static org.sonar.api.measures.Metric.ValueType.RATING;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
+import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
+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.PATH_SORT;
@@ -43,6 +59,9 @@ import static org.sonar.server.measure.ws.ComponentTreeAction.QUALIFIER_SORT;
 
 class ComponentTreeSort {
 
+  private static final Set<ValueType> NUMERIC_VALUE_TYPES = ImmutableSet.of(BOOL, FLOAT, INT, MILLISEC, WORK_DUR, PERCENT, RATING);
+  private static final Set<ValueType> TEXTUAL_VALUE_TYPES = ImmutableSet.of(DATA, DISTRIB, LEVEL, STRING);
+
   private ComponentTreeSort() {
     // static method only
   }
@@ -58,7 +77,8 @@ class ComponentTreeSort {
       .put(NAME_SORT, componentNameOrdering(isAscending))
       .put(QUALIFIER_SORT, componentQualifierOrdering(isAscending))
       .put(PATH_SORT, componentPathOrdering(isAscending))
-      .put(METRIC_SORT, metricOrdering(wsRequest, metrics, measuresByComponentUuidAndMetric))
+      .put(METRIC_SORT, metricValueOrdering(wsRequest, metrics, measuresByComponentUuidAndMetric))
+      .put(METRIC_PERIOD_SORT, metricPeriodOrdering(wsRequest, metrics, measuresByComponentUuidAndMetric))
       .build();
 
     String firstSortParameter = sortParameters.get(0);
@@ -95,10 +115,7 @@ class ComponentTreeSort {
     return ordering.nullsLast().onResultOf(function);
   }
 
-  /**
-   * Order by measure value, taking the metric direction into account
-   */
-  private static Ordering<ComponentDtoWithSnapshotId> metricOrdering(ComponentTreeWsRequest wsRequest, List<MetricDto> metrics,
+  private static Ordering<ComponentDtoWithSnapshotId> metricValueOrdering(ComponentTreeWsRequest wsRequest, List<MetricDto> metrics,
     Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric) {
     if (wsRequest.getMetricSort() == null) {
       return componentNameOrdering(wsRequest.getAsc());
@@ -107,23 +124,30 @@ class ComponentTreeSort {
     MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());
 
     boolean isAscending = wsRequest.getAsc();
-    switch (ValueType.valueOf(metric.getValueType())) {
-      case BOOL:
-      case INT:
-      case MILLISEC:
-      case WORK_DUR:
-      case FLOAT:
-      case PERCENT:
-      case RATING:
-        return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
-      case DATA:
-      case DISTRIB:
-      case LEVEL:
-      case STRING:
-        return stringOrdering(isAscending, new ComponentDtoWithSnapshotIdToTextualMeasureValue(metric, measuresByComponentUuidAndMetric));
-      default:
-        throw new IllegalStateException("Unrecognized metric value type: " + metric.getValueType());
+    ValueType metricValueType = ValueType.valueOf(metric.getValueType());
+    if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
+      return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
+    } else if (TEXTUAL_VALUE_TYPES.contains(metricValueType)) {
+      return stringOrdering(isAscending, new ComponentDtoWithSnapshotIdToTextualMeasureValue(metric, measuresByComponentUuidAndMetric));
+    }
+
+    throw new IllegalStateException("Unrecognized metric value type: " + metric.getValueType());
+  }
+
+  private static Ordering<ComponentDtoWithSnapshotId> metricPeriodOrdering(ComponentTreeWsRequest wsRequest, List<MetricDto> metrics,
+    Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric) {
+    if (wsRequest.getMetricSort() == null || wsRequest.getMetricPeriodSort() == null) {
+      return componentNameOrdering(wsRequest.getAsc());
+    }
+    Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDtoFunctions.toKey());
+    MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());
+
+    ValueType metricValueType = ValueType.valueOf(metric.getValueType());
+    if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
+      return numericalMetricPeriodOrdering(wsRequest, metric, measuresByComponentUuidAndMetric);
     }
+
+    throw new BadRequestException(format("Impossible to sort metric '%s' by measure period.", metric.getKey()));
   }
 
   private static Ordering<ComponentDtoWithSnapshotId> numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric,
@@ -137,6 +161,17 @@ class ComponentTreeSort {
     return ordering.nullsLast().onResultOf(new ComponentDtoWithSnapshotIdToNumericalMeasureValue(metric, measuresByComponentUuidAndMetric));
   }
 
+  private static Ordering<ComponentDtoWithSnapshotId> numericalMetricPeriodOrdering(ComponentTreeWsRequest request, @Nullable MetricDto metric,
+    Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric) {
+    Ordering<Double> ordering = Ordering.natural();
+
+    if (!request.getAsc()) {
+      ordering = ordering.reverse();
+    }
+
+    return ordering.nullsLast().onResultOf(new ComponentDtoWithSnapshotIdToMeasureVariationValue(metric, measuresByComponentUuidAndMetric, request.getMetricPeriodSort()));
+  }
+
   private static class ComponentDtoWithSnapshotIdToNumericalMeasureValue implements Function<ComponentDtoWithSnapshotId, Double> {
     private final MetricDto metric;
     private final Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric;
@@ -158,6 +193,29 @@ class ComponentTreeSort {
     }
   }
 
+  private static class ComponentDtoWithSnapshotIdToMeasureVariationValue implements Function<ComponentDtoWithSnapshotId, Double> {
+    private final MetricDto metric;
+    private final Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric;
+    private final int variationIndex;
+
+    private ComponentDtoWithSnapshotIdToMeasureVariationValue(@Nullable MetricDto metric,
+      Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric, int variationIndex) {
+      this.metric = metric;
+      this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
+      this.variationIndex = variationIndex;
+    }
+
+    @Override
+    public Double apply(@Nonnull ComponentDtoWithSnapshotId input) {
+      MeasureDto measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
+      if (measure == null || measure.getVariation(variationIndex) == null) {
+        return null;
+      }
+
+      return measure.getVariation(variationIndex);
+    }
+  }
+
   private static class ComponentDtoWithSnapshotIdToTextualMeasureValue implements Function<ComponentDtoWithSnapshotId, String> {
     private final MetricDto metric;
     private final Table<String, MetricDto, MeasureDto> measuresByComponentUuidAndMetric;
index 4461b351508cc3cc614de032b6fe403671b510fb..c087d4c949c36374f5df3a4548c2f965e0da9e27 100644 (file)
@@ -65,6 +65,7 @@ import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.component.SnapshotTesting.newSnapshotForProject;
 import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
 import static org.sonar.db.metric.MetricTesting.newMetricDto;
+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.test.JsonAssert.assertJson;
@@ -72,11 +73,11 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.ADDITIONAL_PE
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS;
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_BASE_COMPONENT_ID;
 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_QUALIFIERS;
 import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEGY;
 
-
 public class ComponentTreeActionTest {
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
@@ -248,7 +249,7 @@ public class ComponentTreeActionTest {
   }
 
   @Test
-  public void sort_by_metric_key() {
+  public void sort_by_metric_value() {
     ComponentDto projectDto = newProjectDto("project-uuid");
     SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(projectDto);
     SnapshotDto fileSnapshot3 = componentDb.insertComponentAndSnapshot(newFileDto(projectDto, "file-uuid-3"), projectSnapshot);
@@ -271,6 +272,31 @@ public class ComponentTreeActionTest {
     assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3");
   }
 
+  @Test
+  public void sort_by_metric_period() {
+    ComponentDto projectDto = newProjectDto("project-uuid");
+    SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(projectDto);
+    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()).setVariation(1, 1.0d),
+      newMeasureDto(ncloc, fileSnapshot2.getId()).setVariation(1, 2.0d),
+      newMeasureDto(ncloc, fileSnapshot3.getId()).setVariation(1, 3.0d));
+    db.commit();
+
+    ComponentTreeWsResponse response = call(ws.newRequest()
+      .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid")
+      .setParam(Param.SORT, METRIC_PERIOD_SORT)
+      .setParam(PARAM_METRIC_SORT, "ncloc")
+      .setParam(PARAM_METRIC_KEYS, "ncloc")
+      .setParam(PARAM_METRIC_PERIOD_SORT, "1"));
+
+    assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3");
+  }
+
   @Test
   public void load_developer_descendants() {
     ComponentDto developer = newDeveloper("developer").setUuid("developer-uuid");
@@ -345,7 +371,8 @@ public class ComponentTreeActionTest {
   public void fail_when_sort_by_metric_and_no_metric_sort_provided() {
     componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid"));
     expectedException.expect(BadRequestException.class);
-    expectedException.expectMessage("To sort by a metric, the 's' parameter must contain 'metric' and a metric key must be provided in the 'metricSort' parameter");
+    expectedException
+      .expectMessage("To sort by a metric, the 's' parameter must contain 'metric' or 'metricPeriod', and a metric key must be provided in the 'metricSort' parameter");
 
     call(ws.newRequest()
       .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid")
@@ -367,6 +394,20 @@ public class ComponentTreeActionTest {
       .setParam(Param.SORT, METRIC_SORT));
   }
 
+  @Test
+  public void fail_when_sort_by_metric_period_and_no_metric_period_sort_provided() {
+    componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid"));
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("To sort by a metric period, the 's' parameter must contain 'metricPeriod' and the 'metricPeriodSort' must be provided.");
+
+    call(ws.newRequest()
+      .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid")
+      .setParam(PARAM_METRIC_KEYS, "ncloc")
+      .setParam(PARAM_METRIC_SORT, "ncloc")
+      // PARAM_METRIC_PERIOD_SORT_IS_NOT_SET
+      .setParam(Param.SORT, METRIC_PERIOD_SORT));
+  }
+
   @Test
   public void fail_when_paging_parameter_is_too_big() {
     componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid"));
index 13439ef9655761aeb8c6245e0ffa0051cd9086a2..f57686501eb8a7da24f36c41263cbe7ce8de3b7f 100644 (file)
@@ -36,6 +36,7 @@ import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.metric.MetricTesting.newMetricDto;
+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.PATH_SORT;
@@ -75,7 +76,9 @@ public class ComponentTreeSortTest {
     // same number than path field
     double currentValue = 9;
     for (ComponentDtoWithSnapshotId component : components) {
-      measuresByComponentUuidAndMetric.put(component.uuid(), violationsMetric, new MeasureDto().setValue(currentValue));
+      measuresByComponentUuidAndMetric.put(component.uuid(), violationsMetric, new MeasureDto().setValue(currentValue)
+        .setVariation(1, -currentValue)
+        .setVariation(5, currentValue));
       measuresByComponentUuidAndMetric.put(component.uuid(), sqaleIndexMetric, new MeasureDto().setData(String.valueOf(currentValue)));
       currentValue--;
     }
@@ -132,6 +135,39 @@ public class ComponentTreeSortTest {
       .containsExactly("path-9", "path-8", "path-7", "path-6", "path-5", "path-4", "path-3", "path-2", "path-1", "path-without-measure");
   }
 
+  @Test
+  public void sort_by_numerical_metric_period_1_key_ascending() {
+    components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
+    ComponentTreeWsRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), true, NUM_METRIC_KEY).setMetricPeriodSort(1);
+
+    List<ComponentDtoWithSnapshotId> result = sortComponents(wsRequest);
+
+    assertThat(result).extracting("path")
+      .containsExactly("path-9", "path-8", "path-7", "path-6", "path-5", "path-4", "path-3", "path-2", "path-1", "path-without-measure");
+  }
+
+  @Test
+  public void sort_by_numerical_metric_period_1_key_descending() {
+    components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
+    ComponentTreeWsRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), false, NUM_METRIC_KEY).setMetricPeriodSort(1);
+
+    List<ComponentDtoWithSnapshotId> result = sortComponents(wsRequest);
+
+    assertThat(result).extracting("path")
+      .containsExactly("path-1", "path-2", "path-3", "path-4", "path-5", "path-6", "path-7", "path-8", "path-9", "path-without-measure");
+  }
+
+  @Test
+  public void sort_by_numerical_metric_period_5_key() {
+    components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
+    ComponentTreeWsRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY).setMetricPeriodSort(5);
+
+    List<ComponentDtoWithSnapshotId> result = sortComponents(wsRequest);
+
+    assertThat(result).extracting("path")
+      .containsExactly("path-9", "path-8", "path-7", "path-6", "path-5", "path-4", "path-3", "path-2", "path-1", "path-without-measure");
+  }
+
   @Test
   public void sort_by_textual_metric_key_ascending() {
     components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
index 0d3864e8e290ca42304bb1007d47aae22c2e412b..9194be603f6c4756b74aa062b72dcf0e96fe2e07 100644 (file)
@@ -34,6 +34,7 @@ public class ComponentTreeWsRequest {
   private List<String> sort;
   private Boolean asc;
   private String metricSort;
+  private Integer metricPeriodSort;
   private List<String> metricKeys;
   private Integer page;
   private Integer pageSize;
@@ -157,4 +158,14 @@ public class ComponentTreeWsRequest {
     this.pageSize = pageSize;
     return this;
   }
+
+  @CheckForNull
+  public Integer getMetricPeriodSort() {
+    return metricPeriodSort;
+  }
+
+  public ComponentTreeWsRequest setMetricPeriodSort(@Nullable Integer metricPeriodSort) {
+    this.metricPeriodSort = metricPeriodSort;
+    return this;
+  }
 }
index 5874c4ab8e08d33474f03ea381f38e127a878d94..4aca69b797e71351417fb74ad6101d7ffbdefd69 100644 (file)
@@ -38,6 +38,7 @@ public class MeasuresWsParameters {
   public static final String PARAM_QUALIFIERS = "qualifiers";
   public static final String PARAM_METRIC_KEYS = "metricKeys";
   public static final String PARAM_METRIC_SORT = "metricSort";
+  public static final String PARAM_METRIC_PERIOD_SORT = "metricPeriodSort";
   public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields";
   public static final String PARAM_COMPONENT_ID = "componentId";
   public static final String PARAM_COMPONENT_KEY = "componentKey";