@@ -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; |
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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); | |||
}); | |||
} | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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); | |||
} |