Browse Source

SONAR-21799 fix issue with sorting by impact data metric

tags/10.5.0.89998
Léo Geoffroy 1 month ago
parent
commit
5f3312ab96

+ 6
- 0
server/sonar-server-common/src/main/java/org/sonar/server/measure/ImpactMeasureBuilder.java View File

@@ -26,6 +26,7 @@ import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.sonar.api.issue.impact.Severity;

import static org.sonar.api.utils.Preconditions.checkArgument;
@@ -85,6 +86,11 @@ public class ImpactMeasureBuilder {
return this;
}

@CheckForNull
public Long getTotal() {
return map.get(TOTAL_KEY);
}

public ImpactMeasureBuilder add(ImpactMeasureBuilder other) {
other.buildAsMap().forEach((key, val) -> map.merge(key, val, Long::sum));
return this;

+ 19
- 12
server/sonar-server-common/src/test/java/org/sonar/server/measure/ImpactMeasureBuilderTest.java View File

@@ -20,16 +20,16 @@
package org.sonar.server.measure;

import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.sonar.api.issue.impact.Severity;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class ImpactMeasureBuilderTest {
class ImpactMeasureBuilderTest {

@Test
public void createEmptyMeasure_shouldReturnMeasureWithAllFields() {
void createEmptyMeasure_shouldReturnMeasureWithAllFields() {
ImpactMeasureBuilder builder = ImpactMeasureBuilder.createEmpty();
assertThat(builder.buildAsMap())
.containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L));
@@ -40,7 +40,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void fromMap_shouldInitializeCorrectlyTheBuilder() {
void fromMap_shouldInitializeCorrectlyTheBuilder() {
Map<String, Long> map = getImpactMap(6L, 3L, 2L, 1L);
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(map);
assertThat(builder.buildAsMap())
@@ -48,7 +48,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void fromMap_whenMissingField_shouldThrowException() {
void fromMap_whenMissingField_shouldThrowException() {
Map<String, Long> map = Map.of();
assertThatThrownBy(() -> ImpactMeasureBuilder.fromMap(map))
.isInstanceOf(IllegalArgumentException.class)
@@ -56,7 +56,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void toString_shouldInitializeCorrectlyTheBuilder() {
void toString_shouldInitializeCorrectlyTheBuilder() {
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromString("""
{
total: 6,
@@ -70,7 +70,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void buildAsMap_whenIsEmpty_shouldThrowException() {
void buildAsMap_whenIsEmpty_shouldThrowException() {
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance();
assertThatThrownBy(impactMeasureBuilder::buildAsMap)
.isInstanceOf(IllegalArgumentException.class)
@@ -78,7 +78,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void buildAsMap_whenMissingSeverity_shouldThrowException() {
void buildAsMap_whenMissingSeverity_shouldThrowException() {
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance()
.setTotal(1L)
.setSeverity(Severity.HIGH, 1L)
@@ -89,7 +89,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void buildAsString_whenMissingSeverity_shouldThrowException() {
void buildAsString_whenMissingSeverity_shouldThrowException() {
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance()
.setTotal(1L)
.setSeverity(Severity.HIGH, 1L)
@@ -100,7 +100,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void setSeverity_shouldInitializeSeverityValues() {
void setSeverity_shouldInitializeSeverityValues() {
ImpactMeasureBuilder builder = ImpactMeasureBuilder.newInstance()
.setSeverity(Severity.HIGH, 3L)
.setSeverity(Severity.MEDIUM, 2L)
@@ -111,7 +111,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void add_shouldSumImpactsAndTotal() {
void add_shouldSumImpactsAndTotal() {
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L))
.add(ImpactMeasureBuilder.newInstance().setTotal(6L).setSeverity(Severity.HIGH, 3L).setSeverity(Severity.MEDIUM, 2L).setSeverity(Severity.LOW, 1L));
assertThat(builder.buildAsMap())
@@ -119,7 +119,7 @@ public class ImpactMeasureBuilderTest {
}

@Test
public void add_whenOtherMapHasMissingField_shouldThrowException() {
void add_whenOtherMapHasMissingField_shouldThrowException() {
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance();
ImpactMeasureBuilder otherBuilder = ImpactMeasureBuilder.newInstance();
assertThatThrownBy(() -> impactMeasureBuilder.add(otherBuilder))
@@ -127,4 +127,11 @@ public class ImpactMeasureBuilderTest {
.hasMessage("Map must contain a total key");
}

@Test
void getTotal_shoudReturnExpectedTotal() {
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L));

assertThat(builder.getTotal()).isEqualTo(6L);
}

}

+ 18
- 30
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java View File

@@ -78,12 +78,6 @@ import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static org.sonar.api.measures.CoreMetrics.MAINTAINABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_ISSUES_KEY;
import static org.sonar.api.measures.Metric.ValueType.DATA;
import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
import static org.sonar.api.utils.Paging.offset;
@@ -179,9 +173,9 @@ public class ComponentTreeAction implements MeasuresWsAction {
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_COMPONENT_TREE)
.setDescription(format("Navigate through components based on the chosen strategy with specified measures.<br>" +
"Requires the following permission: 'Browse' on the specified project.<br>" +
"For applications, it also requires 'Browse' permission on its child projects. <br>" +
"When limiting search with the %s parameter, directories are not returned.", Param.TEXT_QUERY))
"Requires the following permission: 'Browse' on the specified project.<br>" +
"For applications, it also requires 'Browse' permission on its child projects. <br>" +
"When limiting search with the %s parameter, directories are not returned.", Param.TEXT_QUERY))
.setResponseExample(getClass().getResource("component_tree-example.json"))
.setSince("5.4")
.setHandler(this)
@@ -193,7 +187,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
MeasuresWsModule.getDeprecatedMetricsInSonarQube105())),
new Change("10.5", "Added new accepted values for the 'metricKeys' param: 'maintainability_issues', 'reliability_issues', 'security_issues'"),
new Change("10.4", String.format("The metrics %s are now deprecated " +
"without exact replacement. Use 'maintainability_issues', 'reliability_issues' and 'security_issues' instead.",
"without exact replacement. Use 'maintainability_issues', 'reliability_issues' and 'security_issues' instead.",
MeasuresWsModule.getDeprecatedMetricsInSonarQube104())),
new Change("10.4", "The metrics 'open_issues', 'reopened_issues' and 'confirmed_issues' are now deprecated in the response. Consume 'violations' instead."),
new Change("10.4", "The use of 'open_issues', 'reopened_issues' and 'confirmed_issues' values in 'metricKeys' param are now deprecated. Use 'violations' instead."),
@@ -226,9 +220,9 @@ public class ComponentTreeAction implements MeasuresWsAction {

action.createParam(Param.TEXT_QUERY)
.setDescription("Limit search to: <ul>" +
"<li>component names that contain the supplied string</li>" +
"<li>component keys that are exactly the same as the supplied string</li>" +
"</ul>")
"<li>component names that contain the supplied string</li>" +
"<li>component keys that are exactly the same as the supplied string</li>" +
"</ul>")
.setMinimumLength(QUERY_MINIMUM_LENGTH)
.setExampleValue("FILE_NAM");

@@ -260,10 +254,10 @@ public class ComponentTreeAction implements MeasuresWsAction {

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))
"<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);

@@ -278,11 +272,11 @@ public class ComponentTreeAction implements MeasuresWsAction {

action.createParam(PARAM_STRATEGY)
.setDescription("Strategy to search for base component descendants:" +
"<ul>" +
"<li>children: return the children components of the base component. Grandchildren components are not returned</li>" +
"<li>all: return all the descendants components of the base component. Grandchildren are returned.</li>" +
"<li>leaves: return all the descendant components (files, in general) which don't have other children. They are the leaves of the component tree.</li>" +
"</ul>")
"<ul>" +
"<li>children: return the children components of the base component. Grandchildren components are not returned</li>" +
"<li>all: return all the descendants components of the base component. Grandchildren are returned.</li>" +
"<li>leaves: return all the descendant components (files, in general) which don't have other children. They are the leaves of the component tree.</li>" +
"</ul>")
.setPossibleValues(STRATEGIES.keySet())
.setDefaultValue(ALL_STRATEGY);
}
@@ -686,15 +680,9 @@ public class ComponentTreeAction implements MeasuresWsAction {
INSTANCE;

static final Set<String> FORBIDDEN_METRIC_TYPES = Set.of(DISTRIB.name());
static final Map<String, Set<String>> PARTIALLY_SUPPORTED_METRICS= Map. of(
static final Map<String, Set<String>> PARTIALLY_SUPPORTED_METRICS = Map.of(
DATA.name(),
Set.of(
SECURITY_ISSUES_KEY,
MAINTAINABILITY_ISSUES_KEY,
RELIABILITY_ISSUES_KEY,
NEW_SECURITY_ISSUES_KEY,
NEW_MAINTAINABILITY_ISSUES_KEY,
NEW_RELIABILITY_ISSUES_KEY));
DataSupportedMetrics.IMPACTS_SUPPORTED_METRICS);

@Override
public boolean test(@Nonnull MetricDto input) {

+ 75
- 23
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java View File

@@ -25,16 +25,20 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Table;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.Metric.ValueType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.measure.ImpactMeasureBuilder;

import static java.lang.String.CASE_INSENSITIVE_ORDER;
import static java.lang.String.format;
@@ -62,7 +66,7 @@ public class ComponentTreeSort {
}

public static List<ComponentDto> sortComponents(List<ComponentDto> components, ComponentTreeRequest wsRequest, List<MetricDto> metrics,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
List<String> sortParameters = wsRequest.getSort();
if (sortParameters == null || sortParameters.isEmpty()) {
return components;
@@ -112,14 +116,14 @@ public class ComponentTreeSort {
}

private static Ordering<ComponentDto> metricValueOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
boolean isAscending = Optional.ofNullable(wsRequest.getAsc()).orElse(false);
if (wsRequest.getMetricSort() == null) {
return componentNameOrdering(wsRequest.getAsc());
return componentNameOrdering(isAscending);
}
Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());

boolean isAscending = wsRequest.getAsc();
ValueType metricValueType = ValueType.valueOf(metric.getValueType());
if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
@@ -127,22 +131,29 @@ public class ComponentTreeSort {
return stringOrdering(isAscending, new ComponentDtoToTextualMeasureValue(metric, measuresByComponentUuidAndMetric));
} else if (ValueType.LEVEL.equals(ValueType.valueOf(metric.getValueType()))) {
return levelMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
} else if (ValueType.DATA.equals(ValueType.valueOf(metric.getValueType()))
&& DataSupportedMetrics.IMPACTS_SUPPORTED_METRICS.contains(metric.getKey())) {
return totalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
}

throw new IllegalStateException("Unrecognized metric value type: " + metric.getValueType());
}

private static Ordering<ComponentDto> metricPeriodOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
boolean isAscending = Optional.ofNullable(wsRequest.getAsc()).orElse(false);
if (wsRequest.getMetricSort() == null || wsRequest.getMetricPeriodSort() == null) {
return componentNameOrdering(wsRequest.getAsc());
return componentNameOrdering(isAscending);
}
Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
MetricDto metric = metricsByKey.get(wsRequest.getMetricSort());

ValueType metricValueType = ValueType.valueOf(metric.getValueType());
if (NUMERIC_VALUE_TYPES.contains(metricValueType)) {
return numericalMetricPeriodOrdering(wsRequest, metric, measuresByComponentUuidAndMetric);
return numericalMetricPeriodOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
} else if (ValueType.DATA.equals(metricValueType)
&& DataSupportedMetrics.IMPACTS_SUPPORTED_METRICS.contains(metric.getKey())) {
return totalNewPeriodMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric);
}

throw BadRequestException.create(format("Impossible to sort metric '%s' by measure period.", metric.getKey()));
@@ -150,36 +161,47 @@ public class ComponentTreeSort {

private static Ordering<ComponentDto> numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Double> ordering = Ordering.natural();
Ordering<Double> ordering = getOrdering(isAscending);

return ordering.onResultOf(new ComponentDtoToNumericalMeasureValue(metric, measuresByComponentUuidAndMetric));
}

@NotNull
private static <T extends Comparable<T>> Ordering<T> getOrdering(boolean isAscending) {
Ordering<T> ordering = Ordering.natural();

if (!isAscending) {
ordering = ordering.reverse();
}
return ordering.nullsLast();
}

return ordering.nullsLast().onResultOf(new ComponentDtoToNumericalMeasureValue(metric, measuresByComponentUuidAndMetric));
private static Ordering<ComponentDto> totalMetricOrdering(boolean isAscending, MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Long> ordering = getOrdering(isAscending);
return ordering.onResultOf(new ComponentDtoToTotalImpactMeasureValue(metric, measuresByComponentUuidAndMetric, false));
}

private static Ordering<ComponentDto> numericalMetricPeriodOrdering(ComponentTreeRequest request, @Nullable MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Double> ordering = Ordering.natural();
private static Ordering<ComponentDto> totalNewPeriodMetricOrdering(boolean isAscending, MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Long> ordering = getOrdering(isAscending);
return ordering.onResultOf(new ComponentDtoToTotalImpactMeasureValue(metric, measuresByComponentUuidAndMetric, true));
}

if (!request.getAsc()) {
ordering = ordering.reverse();
}

return ordering.nullsLast().onResultOf(new ComponentDtoToMeasureVariationValue(metric, measuresByComponentUuidAndMetric));
private static Ordering<ComponentDto> numericalMetricPeriodOrdering(boolean isAscending, @Nullable MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Double> ordering = getOrdering(isAscending);

return ordering.onResultOf(new ComponentDtoToMeasureVariationValue(metric, measuresByComponentUuidAndMetric));
}

private static Ordering<ComponentDto> levelMetricOrdering(boolean isAscending, @Nullable MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) {
Ordering<Integer> ordering = Ordering.natural();

// inverse the order of org.sonar.api.measures.Metric.Level
if (isAscending) {
ordering = ordering.reverse();
}
// inverse the order of org.sonar.api.measures.Metric.Level enum
Ordering<Integer> ordering = getOrdering(!isAscending);

return ordering.nullsLast().onResultOf(new ComponentDtoToLevelIndex(metric, measuresByComponentUuidAndMetric));
return ordering.onResultOf(new ComponentDtoToLevelIndex(metric, measuresByComponentUuidAndMetric));
}

private static class ComponentDtoToNumericalMeasureValue implements Function<ComponentDto, Double> {
@@ -265,4 +287,34 @@ public class ComponentTreeSort {
}
}

private static class ComponentDtoToTotalImpactMeasureValue implements Function<ComponentDto, Long> {
private final MetricDto metric;
private final Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
private final boolean onlyNewPeriodMeasures;

//Store the total value for each component to avoid multiple deserialization of the same measure
Map<String, Long> totalByComponentUuid = new HashMap<>();

private ComponentDtoToTotalImpactMeasureValue(@Nullable MetricDto metric,
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric, boolean onlyNewPeriodMeasures) {
this.metric = metric;
this.measuresByComponentUuidAndMetric = measuresByComponentUuidAndMetric;
this.onlyNewPeriodMeasures = onlyNewPeriodMeasures;
}

@Override
public Long apply(@Nonnull ComponentDto input) {
if (onlyNewPeriodMeasures && metric != null && !metric.getKey().startsWith("new_")) {
return null;
}
return totalByComponentUuid.computeIfAbsent(input.uuid(),
k -> {
ComponentTreeData.Measure measure = measuresByComponentUuidAndMetric.get(input.uuid(), metric);
return Optional.ofNullable(measure).map(ComponentTreeData.Measure::getData)
.map(data -> ImpactMeasureBuilder.fromString(measure.getData()).getTotal())
.orElse(null);
});
}
}

}

+ 48
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/DataSupportedMetrics.java View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.measure.ws;

import java.util.Set;

import static org.sonar.api.measures.CoreMetrics.MAINTAINABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_ISSUES_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_ISSUES_KEY;


/**
* This class contains the list of metrics that are supported in the web-api for the data type.
*/
public class DataSupportedMetrics {

public static final Set<String> IMPACTS_SUPPORTED_METRICS = Set.of(
SECURITY_ISSUES_KEY,
MAINTAINABILITY_ISSUES_KEY,
RELIABILITY_ISSUES_KEY,
NEW_SECURITY_ISSUES_KEY,
NEW_MAINTAINABILITY_ISSUES_KEY,
NEW_RELIABILITY_ISSUES_KEY);

private DataSupportedMetrics() {
// only static methods
}
}

+ 85
- 19
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/ComponentTreeSortTest.java View File

@@ -23,8 +23,9 @@ import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import java.util.List;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric.ValueType;
import org.sonar.api.resources.Qualifiers;
@@ -32,6 +33,7 @@ import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.measure.LiveMeasureDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.server.measure.ImpactMeasureBuilder;

import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.singletonList;
@@ -44,17 +46,20 @@ import static org.sonar.server.measure.ws.ComponentTreeAction.PATH_SORT;
import static org.sonar.server.measure.ws.ComponentTreeAction.QUALIFIER_SORT;
import static org.sonar.server.measure.ws.ComponentTreeData.Measure.createFromMeasureDto;

public class ComponentTreeSortTest {
class ComponentTreeSortTest {
private static final String NUM_METRIC_KEY = "violations";
private static final String NEW_METRIC_KEY = "new_violations";
private static final String TEXT_METRIC_KEY = "sqale_index";

private static final String DATA_IMPACT_METRIC_KEY = "reliability_issues";
private static final String NEW_DATA_IMPACT_METRIC_KEY = "new_reliability_issues";

private List<MetricDto> metrics;
private Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric;
private List<ComponentDto> components;

@Before
public void setUp() {
@BeforeEach
void setUp() {
components = newArrayList(
newComponentWithoutSnapshotId("name-1", "qualifier-2", "path-9"),
newComponentWithoutSnapshotId("name-3", "qualifier-3", "path-8"),
@@ -75,8 +80,14 @@ public class ComponentTreeSortTest {
MetricDto sqaleIndexMetric = newMetricDto()
.setKey(TEXT_METRIC_KEY)
.setValueType(ValueType.STRING.name());
MetricDto reliabilityIssueMetric = newMetricDto()
.setKey(DATA_IMPACT_METRIC_KEY)
.setValueType(ValueType.DATA.name());
MetricDto newReliabilityIssueMetric = newMetricDto()
.setKey(NEW_DATA_IMPACT_METRIC_KEY)
.setValueType(ValueType.DATA.name());

metrics = newArrayList(violationsMetric, sqaleIndexMetric, newViolationsMetric);
metrics = newArrayList(violationsMetric, sqaleIndexMetric, newViolationsMetric, reliabilityIssueMetric, newReliabilityIssueMetric);

measuresByComponentUuidAndMetric = HashBasedTable.create(components.size(), 2);
// same number than path field
@@ -85,12 +96,23 @@ public class ComponentTreeSortTest {
measuresByComponentUuidAndMetric.put(component.uuid(), violationsMetric, createFromMeasureDto(new LiveMeasureDto().setValue(currentValue)));
measuresByComponentUuidAndMetric.put(component.uuid(), newViolationsMetric, createFromMeasureDto(new LiveMeasureDto().setValue(currentValue)));
measuresByComponentUuidAndMetric.put(component.uuid(), sqaleIndexMetric, createFromMeasureDto(new LiveMeasureDto().setData(String.valueOf(currentValue))));
measuresByComponentUuidAndMetric.put(component.uuid(), reliabilityIssueMetric, createFromMeasureDto(new LiveMeasureDto().setData(buildJsonImpact((int) currentValue))));
measuresByComponentUuidAndMetric.put(component.uuid(), newReliabilityIssueMetric, createFromMeasureDto(new LiveMeasureDto().setData(buildJsonImpact((int) currentValue))));
currentValue--;
}
}

private static String buildJsonImpact(int currentValue) {
return String.valueOf(ImpactMeasureBuilder.newInstance()
.setSeverity(Severity.HIGH, currentValue)
.setSeverity(Severity.MEDIUM, currentValue)
.setSeverity(Severity.LOW, currentValue)
.setTotal(currentValue)
.buildAsString());
}

@Test
public void sort_by_names() {
void sort_by_names() {
ComponentTreeRequest wsRequest = newRequest(singletonList(NAME_SORT), true, null);
List<ComponentDto> result = sortComponents(wsRequest);

@@ -99,7 +121,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_qualifier() {
void sort_by_qualifier() {
ComponentTreeRequest wsRequest = newRequest(singletonList(QUALIFIER_SORT), false, null);

List<ComponentDto> result = sortComponents(wsRequest);
@@ -109,7 +131,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_path() {
void sort_by_path() {
ComponentTreeRequest wsRequest = newRequest(singletonList(PATH_SORT), true, null);

List<ComponentDto> result = sortComponents(wsRequest);
@@ -119,7 +141,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_numerical_metric_key_ascending() {
void sort_by_numerical_metric_key_ascending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, NUM_METRIC_KEY);

@@ -130,7 +152,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_numerical_metric_key_descending() {
void sort_by_numerical_metric_key_descending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY);

@@ -141,7 +163,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_name_ascending_in_case_of_equality() {
void sort_by_name_ascending_in_case_of_equality() {
components = newArrayList(
newComponentWithoutSnapshotId("PROJECT 12", Qualifiers.PROJECT, "PROJECT_PATH_1"),
newComponentWithoutSnapshotId("PROJECT 11", Qualifiers.PROJECT, "PROJECT_PATH_1"),
@@ -155,7 +177,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_alert_status_ascending() {
void sort_by_alert_status_ascending() {
components = newArrayList(
newComponentWithoutSnapshotId("PROJECT OK 1", Qualifiers.PROJECT, "PROJECT_OK_PATH_1"),
newComponentWithoutSnapshotId("PROJECT ERROR 1", Qualifiers.PROJECT, "PROJECT_ERROR_PATH_1"),
@@ -181,7 +203,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_numerical_metric_period_1_key_descending() {
void sort_by_numerical_metric_period_1_key_descending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), false, NEW_METRIC_KEY).setMetricPeriodSort(1);

@@ -192,7 +214,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_numerical_metric_period_1_key_ascending() {
void sort_by_numerical_metric_period_1_key_ascending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), true, NEW_METRIC_KEY).setMetricPeriodSort(1);

@@ -203,7 +225,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_numerical_metric_period_5_key() {
void sort_by_numerical_metric_period_5_key() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY).setMetricPeriodSort(5);

@@ -214,7 +236,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_textual_metric_key_ascending() {
void sort_by_textual_metric_key_ascending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, TEXT_METRIC_KEY);

@@ -225,7 +247,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_by_textual_metric_key_descending() {
void sort_by_textual_metric_key_descending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, TEXT_METRIC_KEY);

@@ -236,7 +258,7 @@ public class ComponentTreeSortTest {
}

@Test
public void sort_on_multiple_fields() {
void sort_on_multiple_fields() {
components = newArrayList(
newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-2"),
newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-3"),
@@ -249,6 +271,50 @@ public class ComponentTreeSortTest {
.containsExactly("path-1", "path-2", "path-3");
}

@Test
void sortComponent_whenMetricIsImpactDataType_shouldOrderByTotalAscending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, DATA_IMPACT_METRIC_KEY);

List<ComponentDto> 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
void sortComponent_whenMetricIsImpactDataType_shouldOrderByTotalDescending() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, DATA_IMPACT_METRIC_KEY);

List<ComponentDto> 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
void sortComponent_whenMetricIsNewAndMetricPeriodSort_shouldOrderByTotal() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), false, NEW_DATA_IMPACT_METRIC_KEY).setMetricPeriodSort(1);

List<ComponentDto> 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
void sortComponent_whenMetricIsNotNewAndMetricPeriodSort_shouldNotOrder() {
components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure"));
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), false, DATA_IMPACT_METRIC_KEY).setMetricPeriodSort(1);

List<ComponentDto> result = sortComponents(wsRequest);

assertThat(result).extracting("path")
.containsExactly("path-9", "path-7", "path-8", "path-6", "path-3", "path-4", "path-5", "path-1", "path-2", "path-without-measure");
}

private List<ComponentDto> sortComponents(ComponentTreeRequest wsRequest) {
return ComponentTreeSort.sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric);
}

Loading…
Cancel
Save