import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
import javax.annotation.CheckForNull; | |||||
import org.sonar.api.issue.impact.Severity; | import org.sonar.api.issue.impact.Severity; | ||||
import static org.sonar.api.utils.Preconditions.checkArgument; | import static org.sonar.api.utils.Preconditions.checkArgument; | ||||
return this; | return this; | ||||
} | } | ||||
@CheckForNull | |||||
public Long getTotal() { | |||||
return map.get(TOTAL_KEY); | |||||
} | |||||
public ImpactMeasureBuilder add(ImpactMeasureBuilder other) { | public ImpactMeasureBuilder add(ImpactMeasureBuilder other) { | ||||
other.buildAsMap().forEach((key, val) -> map.merge(key, val, Long::sum)); | other.buildAsMap().forEach((key, val) -> map.merge(key, val, Long::sum)); | ||||
return this; | return this; |
package org.sonar.server.measure; | package org.sonar.server.measure; | ||||
import java.util.Map; | import java.util.Map; | ||||
import org.junit.Test; | |||||
import org.junit.jupiter.api.Test; | |||||
import org.sonar.api.issue.impact.Severity; | import org.sonar.api.issue.impact.Severity; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
public class ImpactMeasureBuilderTest { | |||||
class ImpactMeasureBuilderTest { | |||||
@Test | @Test | ||||
public void createEmptyMeasure_shouldReturnMeasureWithAllFields() { | |||||
void createEmptyMeasure_shouldReturnMeasureWithAllFields() { | |||||
ImpactMeasureBuilder builder = ImpactMeasureBuilder.createEmpty(); | ImpactMeasureBuilder builder = ImpactMeasureBuilder.createEmpty(); | ||||
assertThat(builder.buildAsMap()) | assertThat(builder.buildAsMap()) | ||||
.containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L)); | .containsAllEntriesOf(getImpactMap(0L, 0L, 0L, 0L)); | ||||
} | } | ||||
@Test | @Test | ||||
public void fromMap_shouldInitializeCorrectlyTheBuilder() { | |||||
void fromMap_shouldInitializeCorrectlyTheBuilder() { | |||||
Map<String, Long> map = getImpactMap(6L, 3L, 2L, 1L); | Map<String, Long> map = getImpactMap(6L, 3L, 2L, 1L); | ||||
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(map); | ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(map); | ||||
assertThat(builder.buildAsMap()) | assertThat(builder.buildAsMap()) | ||||
} | } | ||||
@Test | @Test | ||||
public void fromMap_whenMissingField_shouldThrowException() { | |||||
void fromMap_whenMissingField_shouldThrowException() { | |||||
Map<String, Long> map = Map.of(); | Map<String, Long> map = Map.of(); | ||||
assertThatThrownBy(() -> ImpactMeasureBuilder.fromMap(map)) | assertThatThrownBy(() -> ImpactMeasureBuilder.fromMap(map)) | ||||
.isInstanceOf(IllegalArgumentException.class) | .isInstanceOf(IllegalArgumentException.class) | ||||
} | } | ||||
@Test | @Test | ||||
public void toString_shouldInitializeCorrectlyTheBuilder() { | |||||
void toString_shouldInitializeCorrectlyTheBuilder() { | |||||
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromString(""" | ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromString(""" | ||||
{ | { | ||||
total: 6, | total: 6, | ||||
} | } | ||||
@Test | @Test | ||||
public void buildAsMap_whenIsEmpty_shouldThrowException() { | |||||
void buildAsMap_whenIsEmpty_shouldThrowException() { | |||||
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); | ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); | ||||
assertThatThrownBy(impactMeasureBuilder::buildAsMap) | assertThatThrownBy(impactMeasureBuilder::buildAsMap) | ||||
.isInstanceOf(IllegalArgumentException.class) | .isInstanceOf(IllegalArgumentException.class) | ||||
} | } | ||||
@Test | @Test | ||||
public void buildAsMap_whenMissingSeverity_shouldThrowException() { | |||||
void buildAsMap_whenMissingSeverity_shouldThrowException() { | |||||
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() | ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() | ||||
.setTotal(1L) | .setTotal(1L) | ||||
.setSeverity(Severity.HIGH, 1L) | .setSeverity(Severity.HIGH, 1L) | ||||
} | } | ||||
@Test | @Test | ||||
public void buildAsString_whenMissingSeverity_shouldThrowException() { | |||||
void buildAsString_whenMissingSeverity_shouldThrowException() { | |||||
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() | ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance() | ||||
.setTotal(1L) | .setTotal(1L) | ||||
.setSeverity(Severity.HIGH, 1L) | .setSeverity(Severity.HIGH, 1L) | ||||
} | } | ||||
@Test | @Test | ||||
public void setSeverity_shouldInitializeSeverityValues() { | |||||
void setSeverity_shouldInitializeSeverityValues() { | |||||
ImpactMeasureBuilder builder = ImpactMeasureBuilder.newInstance() | ImpactMeasureBuilder builder = ImpactMeasureBuilder.newInstance() | ||||
.setSeverity(Severity.HIGH, 3L) | .setSeverity(Severity.HIGH, 3L) | ||||
.setSeverity(Severity.MEDIUM, 2L) | .setSeverity(Severity.MEDIUM, 2L) | ||||
} | } | ||||
@Test | @Test | ||||
public void add_shouldSumImpactsAndTotal() { | |||||
void add_shouldSumImpactsAndTotal() { | |||||
ImpactMeasureBuilder builder = ImpactMeasureBuilder.fromMap(getImpactMap(6L, 3L, 2L, 1L)) | 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)); | .add(ImpactMeasureBuilder.newInstance().setTotal(6L).setSeverity(Severity.HIGH, 3L).setSeverity(Severity.MEDIUM, 2L).setSeverity(Severity.LOW, 1L)); | ||||
assertThat(builder.buildAsMap()) | assertThat(builder.buildAsMap()) | ||||
} | } | ||||
@Test | @Test | ||||
public void add_whenOtherMapHasMissingField_shouldThrowException() { | |||||
void add_whenOtherMapHasMissingField_shouldThrowException() { | |||||
ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); | ImpactMeasureBuilder impactMeasureBuilder = ImpactMeasureBuilder.newInstance(); | ||||
ImpactMeasureBuilder otherBuilder = ImpactMeasureBuilder.newInstance(); | ImpactMeasureBuilder otherBuilder = ImpactMeasureBuilder.newInstance(); | ||||
assertThatThrownBy(() -> impactMeasureBuilder.add(otherBuilder)) | assertThatThrownBy(() -> impactMeasureBuilder.add(otherBuilder)) | ||||
.hasMessage("Map must contain a total key"); | .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); | |||||
} | |||||
} | } |
import static java.util.Collections.emptyList; | import static java.util.Collections.emptyList; | ||||
import static java.util.Collections.emptyMap; | import static java.util.Collections.emptyMap; | ||||
import static java.util.Optional.ofNullable; | 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.DATA; | ||||
import static org.sonar.api.measures.Metric.ValueType.DISTRIB; | import static org.sonar.api.measures.Metric.ValueType.DISTRIB; | ||||
import static org.sonar.api.utils.Paging.offset; | import static org.sonar.api.utils.Paging.offset; | ||||
public void define(WebService.NewController context) { | public void define(WebService.NewController context) { | ||||
WebService.NewAction action = context.createAction(ACTION_COMPONENT_TREE) | WebService.NewAction action = context.createAction(ACTION_COMPONENT_TREE) | ||||
.setDescription(format("Navigate through components based on the chosen strategy with specified measures.<br>" + | .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")) | .setResponseExample(getClass().getResource("component_tree-example.json")) | ||||
.setSince("5.4") | .setSince("5.4") | ||||
.setHandler(this) | .setHandler(this) | ||||
MeasuresWsModule.getDeprecatedMetricsInSonarQube105())), | MeasuresWsModule.getDeprecatedMetricsInSonarQube105())), | ||||
new Change("10.5", "Added new accepted values for the 'metricKeys' param: 'maintainability_issues', 'reliability_issues', 'security_issues'"), | 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 " + | 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())), | 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 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."), | new Change("10.4", "The use of 'open_issues', 'reopened_issues' and 'confirmed_issues' values in 'metricKeys' param are now deprecated. Use 'violations' instead."), | ||||
action.createParam(Param.TEXT_QUERY) | action.createParam(Param.TEXT_QUERY) | ||||
.setDescription("Limit search to: <ul>" + | .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) | .setMinimumLength(QUERY_MINIMUM_LENGTH) | ||||
.setExampleValue("FILE_NAM"); | .setExampleValue("FILE_NAM"); | ||||
action.createParam(PARAM_METRIC_SORT_FILTER) | action.createParam(PARAM_METRIC_SORT_FILTER) | ||||
.setDescription(format("Filter components. Sort must be on a metric. Possible values are: " + | .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) | .setDefaultValue(ALL_METRIC_SORT_FILTER) | ||||
.setPossibleValues(METRIC_SORT_FILTERS); | .setPossibleValues(METRIC_SORT_FILTERS); | ||||
action.createParam(PARAM_STRATEGY) | action.createParam(PARAM_STRATEGY) | ||||
.setDescription("Strategy to search for base component descendants:" + | .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()) | .setPossibleValues(STRATEGIES.keySet()) | ||||
.setDefaultValue(ALL_STRATEGY); | .setDefaultValue(ALL_STRATEGY); | ||||
} | } | ||||
INSTANCE; | INSTANCE; | ||||
static final Set<String> FORBIDDEN_METRIC_TYPES = Set.of(DISTRIB.name()); | 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(), | 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 | @Override | ||||
public boolean test(@Nonnull MetricDto input) { | public boolean test(@Nonnull MetricDto input) { |
import com.google.common.collect.Ordering; | import com.google.common.collect.Ordering; | ||||
import com.google.common.collect.Table; | import com.google.common.collect.Table; | ||||
import java.util.EnumSet; | import java.util.EnumSet; | ||||
import java.util.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | |||||
import java.util.Set; | import java.util.Set; | ||||
import javax.annotation.Nonnull; | import javax.annotation.Nonnull; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.jetbrains.annotations.NotNull; | |||||
import org.sonar.api.measures.Metric; | import org.sonar.api.measures.Metric; | ||||
import org.sonar.api.measures.Metric.ValueType; | import org.sonar.api.measures.Metric.ValueType; | ||||
import org.sonar.db.component.ComponentDto; | import org.sonar.db.component.ComponentDto; | ||||
import org.sonar.db.metric.MetricDto; | import org.sonar.db.metric.MetricDto; | ||||
import org.sonar.server.exceptions.BadRequestException; | 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.CASE_INSENSITIVE_ORDER; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
} | } | ||||
public static List<ComponentDto> sortComponents(List<ComponentDto> components, ComponentTreeRequest wsRequest, List<MetricDto> metrics, | 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(); | List<String> sortParameters = wsRequest.getSort(); | ||||
if (sortParameters == null || sortParameters.isEmpty()) { | if (sortParameters == null || sortParameters.isEmpty()) { | ||||
return components; | return components; | ||||
} | } | ||||
private static Ordering<ComponentDto> metricValueOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics, | 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) { | if (wsRequest.getMetricSort() == null) { | ||||
return componentNameOrdering(wsRequest.getAsc()); | |||||
return componentNameOrdering(isAscending); | |||||
} | } | ||||
Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); | Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); | ||||
MetricDto metric = metricsByKey.get(wsRequest.getMetricSort()); | MetricDto metric = metricsByKey.get(wsRequest.getMetricSort()); | ||||
boolean isAscending = wsRequest.getAsc(); | |||||
ValueType metricValueType = ValueType.valueOf(metric.getValueType()); | ValueType metricValueType = ValueType.valueOf(metric.getValueType()); | ||||
if (NUMERIC_VALUE_TYPES.contains(metricValueType)) { | if (NUMERIC_VALUE_TYPES.contains(metricValueType)) { | ||||
return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric); | return numericalMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric); | ||||
return stringOrdering(isAscending, new ComponentDtoToTextualMeasureValue(metric, measuresByComponentUuidAndMetric)); | return stringOrdering(isAscending, new ComponentDtoToTextualMeasureValue(metric, measuresByComponentUuidAndMetric)); | ||||
} else if (ValueType.LEVEL.equals(ValueType.valueOf(metric.getValueType()))) { | } else if (ValueType.LEVEL.equals(ValueType.valueOf(metric.getValueType()))) { | ||||
return levelMetricOrdering(isAscending, metric, measuresByComponentUuidAndMetric); | 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()); | throw new IllegalStateException("Unrecognized metric value type: " + metric.getValueType()); | ||||
} | } | ||||
private static Ordering<ComponentDto> metricPeriodOrdering(ComponentTreeRequest wsRequest, List<MetricDto> metrics, | 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) { | if (wsRequest.getMetricSort() == null || wsRequest.getMetricPeriodSort() == null) { | ||||
return componentNameOrdering(wsRequest.getAsc()); | |||||
return componentNameOrdering(isAscending); | |||||
} | } | ||||
Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); | Map<String, MetricDto> metricsByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); | ||||
MetricDto metric = metricsByKey.get(wsRequest.getMetricSort()); | MetricDto metric = metricsByKey.get(wsRequest.getMetricSort()); | ||||
ValueType metricValueType = ValueType.valueOf(metric.getValueType()); | ValueType metricValueType = ValueType.valueOf(metric.getValueType()); | ||||
if (NUMERIC_VALUE_TYPES.contains(metricValueType)) { | 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())); | throw BadRequestException.create(format("Impossible to sort metric '%s' by measure period.", metric.getKey())); | ||||
private static Ordering<ComponentDto> numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric, | private static Ordering<ComponentDto> numericalMetricOrdering(boolean isAscending, @Nullable MetricDto metric, | ||||
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) { | 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) { | if (!isAscending) { | ||||
ordering = ordering.reverse(); | 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, | private static Ordering<ComponentDto> levelMetricOrdering(boolean isAscending, @Nullable MetricDto metric, | ||||
Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric) { | 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> { | private static class ComponentDtoToNumericalMeasureValue implements Function<ComponentDto, Double> { | ||||
} | } | ||||
} | } | ||||
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); | |||||
}); | |||||
} | |||||
} | |||||
} | } |
/* | |||||
* 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 | |||||
} | |||||
} |
import com.google.common.collect.Table; | import com.google.common.collect.Table; | ||||
import java.util.List; | import java.util.List; | ||||
import javax.annotation.Nullable; | 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.CoreMetrics; | ||||
import org.sonar.api.measures.Metric.ValueType; | import org.sonar.api.measures.Metric.ValueType; | ||||
import org.sonar.api.resources.Qualifiers; | import org.sonar.api.resources.Qualifiers; | ||||
import org.sonar.db.component.ComponentDto; | import org.sonar.db.component.ComponentDto; | ||||
import org.sonar.db.measure.LiveMeasureDto; | import org.sonar.db.measure.LiveMeasureDto; | ||||
import org.sonar.db.metric.MetricDto; | import org.sonar.db.metric.MetricDto; | ||||
import org.sonar.server.measure.ImpactMeasureBuilder; | |||||
import static com.google.common.collect.Lists.newArrayList; | import static com.google.common.collect.Lists.newArrayList; | ||||
import static java.util.Collections.singletonList; | import static java.util.Collections.singletonList; | ||||
import static org.sonar.server.measure.ws.ComponentTreeAction.QUALIFIER_SORT; | import static org.sonar.server.measure.ws.ComponentTreeAction.QUALIFIER_SORT; | ||||
import static org.sonar.server.measure.ws.ComponentTreeData.Measure.createFromMeasureDto; | 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 NUM_METRIC_KEY = "violations"; | ||||
private static final String NEW_METRIC_KEY = "new_violations"; | private static final String NEW_METRIC_KEY = "new_violations"; | ||||
private static final String TEXT_METRIC_KEY = "sqale_index"; | 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 List<MetricDto> metrics; | ||||
private Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric; | private Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric; | ||||
private List<ComponentDto> components; | private List<ComponentDto> components; | ||||
@Before | |||||
public void setUp() { | |||||
@BeforeEach | |||||
void setUp() { | |||||
components = newArrayList( | components = newArrayList( | ||||
newComponentWithoutSnapshotId("name-1", "qualifier-2", "path-9"), | newComponentWithoutSnapshotId("name-1", "qualifier-2", "path-9"), | ||||
newComponentWithoutSnapshotId("name-3", "qualifier-3", "path-8"), | newComponentWithoutSnapshotId("name-3", "qualifier-3", "path-8"), | ||||
MetricDto sqaleIndexMetric = newMetricDto() | MetricDto sqaleIndexMetric = newMetricDto() | ||||
.setKey(TEXT_METRIC_KEY) | .setKey(TEXT_METRIC_KEY) | ||||
.setValueType(ValueType.STRING.name()); | .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); | measuresByComponentUuidAndMetric = HashBasedTable.create(components.size(), 2); | ||||
// same number than path field | // same number than path field | ||||
measuresByComponentUuidAndMetric.put(component.uuid(), violationsMetric, createFromMeasureDto(new LiveMeasureDto().setValue(currentValue))); | measuresByComponentUuidAndMetric.put(component.uuid(), violationsMetric, createFromMeasureDto(new LiveMeasureDto().setValue(currentValue))); | ||||
measuresByComponentUuidAndMetric.put(component.uuid(), newViolationsMetric, 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(), 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--; | 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 | @Test | ||||
public void sort_by_names() { | |||||
void sort_by_names() { | |||||
ComponentTreeRequest wsRequest = newRequest(singletonList(NAME_SORT), true, null); | ComponentTreeRequest wsRequest = newRequest(singletonList(NAME_SORT), true, null); | ||||
List<ComponentDto> result = sortComponents(wsRequest); | List<ComponentDto> result = sortComponents(wsRequest); | ||||
} | } | ||||
@Test | @Test | ||||
public void sort_by_qualifier() { | |||||
void sort_by_qualifier() { | |||||
ComponentTreeRequest wsRequest = newRequest(singletonList(QUALIFIER_SORT), false, null); | ComponentTreeRequest wsRequest = newRequest(singletonList(QUALIFIER_SORT), false, null); | ||||
List<ComponentDto> result = sortComponents(wsRequest); | List<ComponentDto> result = sortComponents(wsRequest); | ||||
} | } | ||||
@Test | @Test | ||||
public void sort_by_path() { | |||||
void sort_by_path() { | |||||
ComponentTreeRequest wsRequest = newRequest(singletonList(PATH_SORT), true, null); | ComponentTreeRequest wsRequest = newRequest(singletonList(PATH_SORT), true, null); | ||||
List<ComponentDto> result = sortComponents(wsRequest); | List<ComponentDto> result = sortComponents(wsRequest); | ||||
} | } | ||||
@Test | @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")); | components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure")); | ||||
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, NUM_METRIC_KEY); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, NUM_METRIC_KEY); | ||||
} | } | ||||
@Test | @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")); | components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure")); | ||||
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY); | ||||
} | } | ||||
@Test | @Test | ||||
public void sort_by_name_ascending_in_case_of_equality() { | |||||
void sort_by_name_ascending_in_case_of_equality() { | |||||
components = newArrayList( | components = newArrayList( | ||||
newComponentWithoutSnapshotId("PROJECT 12", Qualifiers.PROJECT, "PROJECT_PATH_1"), | newComponentWithoutSnapshotId("PROJECT 12", Qualifiers.PROJECT, "PROJECT_PATH_1"), | ||||
newComponentWithoutSnapshotId("PROJECT 11", Qualifiers.PROJECT, "PROJECT_PATH_1"), | newComponentWithoutSnapshotId("PROJECT 11", Qualifiers.PROJECT, "PROJECT_PATH_1"), | ||||
} | } | ||||
@Test | @Test | ||||
public void sort_by_alert_status_ascending() { | |||||
void sort_by_alert_status_ascending() { | |||||
components = newArrayList( | components = newArrayList( | ||||
newComponentWithoutSnapshotId("PROJECT OK 1", Qualifiers.PROJECT, "PROJECT_OK_PATH_1"), | newComponentWithoutSnapshotId("PROJECT OK 1", Qualifiers.PROJECT, "PROJECT_OK_PATH_1"), | ||||
newComponentWithoutSnapshotId("PROJECT ERROR 1", Qualifiers.PROJECT, "PROJECT_ERROR_PATH_1"), | newComponentWithoutSnapshotId("PROJECT ERROR 1", Qualifiers.PROJECT, "PROJECT_ERROR_PATH_1"), | ||||
} | } | ||||
@Test | @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")); | 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); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), false, NEW_METRIC_KEY).setMetricPeriodSort(1); | ||||
} | } | ||||
@Test | @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")); | 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); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_PERIOD_SORT), true, NEW_METRIC_KEY).setMetricPeriodSort(1); | ||||
} | } | ||||
@Test | @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")); | components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure")); | ||||
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY).setMetricPeriodSort(5); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, NUM_METRIC_KEY).setMetricPeriodSort(5); | ||||
} | } | ||||
@Test | @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")); | components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure")); | ||||
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, TEXT_METRIC_KEY); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), true, TEXT_METRIC_KEY); | ||||
} | } | ||||
@Test | @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")); | components.add(newComponentWithoutSnapshotId("name-without-measure", "qualifier-without-measure", "path-without-measure")); | ||||
ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, TEXT_METRIC_KEY); | ComponentTreeRequest wsRequest = newRequest(singletonList(METRIC_SORT), false, TEXT_METRIC_KEY); | ||||
} | } | ||||
@Test | @Test | ||||
public void sort_on_multiple_fields() { | |||||
void sort_on_multiple_fields() { | |||||
components = newArrayList( | components = newArrayList( | ||||
newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-2"), | newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-2"), | ||||
newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-3"), | newComponentWithoutSnapshotId("name-1", "qualifier-1", "path-3"), | ||||
.containsExactly("path-1", "path-2", "path-3"); | .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) { | private List<ComponentDto> sortComponents(ComponentTreeRequest wsRequest) { | ||||
return ComponentTreeSort.sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric); | return ComponentTreeSort.sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric); | ||||
} | } |