From 4bfd437e8827cee36ae501dd83bd774a59239abd Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Fri, 11 Mar 2016 15:38:36 +0100 Subject: [PATCH] SONAR-7196 WS api/measures/component_tree supports sorting by period variation --- .../measure/ws/ComponentTreeAction.java | 21 +++- .../measure/ws/ComponentTreeDataLoader.java | 13 ++- .../server/measure/ws/ComponentTreeSort.java | 100 ++++++++++++++---- .../measure/ws/ComponentTreeActionTest.java | 47 +++++++- .../measure/ws/ComponentTreeSortTest.java | 38 ++++++- .../measure/ComponentTreeWsRequest.java | 11 ++ .../client/measure/MeasuresWsParameters.java | 1 + 7 files changed, 197 insertions(+), 34 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 3126dfdbdf5..b7d26b033b2 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 @@ -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 SORTS = ImmutableSortedSet.of(NAME_SORT, PATH_SORT, QUALIFIER_SORT, METRIC_SORT); + static final String METRIC_PERIOD_SORT = "metricPeriod"; + static final Set 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:
    " + @@ -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; } } 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 42f695add9b..fd26d5ba2b6 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 @@ -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 sortComponents(List components, ComponentTreeWsRequest wsRequest, List metrics, Table measuresByComponentUuidAndMetric) { - if (!wsRequest.getSort().contains(METRIC_SORT)) { + if (!isSortByMetric(wsRequest)) { return components; } @@ -240,7 +241,7 @@ public class ComponentTreeDataLoader { } private static List paginateComponents(List 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 childrenQualifiers(ComponentTreeWsRequest request, String baseQualifier) { List 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); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java index 6c136b2f490..3f9cc3e042e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java @@ -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 NUMERIC_VALUE_TYPES = ImmutableSet.of(BOOL, FLOAT, INT, MILLISEC, WORK_DUR, PERCENT, RATING); + private static final Set 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 metricOrdering(ComponentTreeWsRequest wsRequest, List metrics, + private static Ordering metricValueOrdering(ComponentTreeWsRequest wsRequest, List metrics, Table 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 metricPeriodOrdering(ComponentTreeWsRequest wsRequest, List metrics, + Table measuresByComponentUuidAndMetric) { + if (wsRequest.getMetricSort() == null || wsRequest.getMetricPeriodSort() == null) { + return componentNameOrdering(wsRequest.getAsc()); + } + Map 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 numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric, @@ -137,6 +161,17 @@ class ComponentTreeSort { return ordering.nullsLast().onResultOf(new ComponentDtoWithSnapshotIdToNumericalMeasureValue(metric, measuresByComponentUuidAndMetric)); } + private static Ordering numericalMetricPeriodOrdering(ComponentTreeWsRequest request, @Nullable MetricDto metric, + Table measuresByComponentUuidAndMetric) { + Ordering 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 { private final MetricDto metric; private final Table measuresByComponentUuidAndMetric; @@ -158,6 +193,29 @@ class ComponentTreeSort { } } + private static class ComponentDtoWithSnapshotIdToMeasureVariationValue implements Function { + private final MetricDto metric; + private final Table measuresByComponentUuidAndMetric; + private final int variationIndex; + + private ComponentDtoWithSnapshotIdToMeasureVariationValue(@Nullable MetricDto metric, + Table 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 { private final MetricDto metric; private final Table measuresByComponentUuidAndMetric; 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 4461b351508..c087d4c949c 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 @@ -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")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java index 13439ef9655..f57686501eb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java @@ -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 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 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 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")); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java index 0d3864e8e29..9194be603f6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/ComponentTreeWsRequest.java @@ -34,6 +34,7 @@ public class ComponentTreeWsRequest { private List sort; private Boolean asc; private String metricSort; + private Integer metricPeriodSort; private List 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; + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java index 5874c4ab8e0..4aca69b797e 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/measure/MeasuresWsParameters.java @@ -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"; -- 2.39.5