From e0a4e3ba8a686e2ecfe8824b8aa6d40d50fd1cc7 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Mon, 25 Feb 2019 10:40:16 -0600 Subject: [PATCH] SONAR-11736 Reaturn overall issue count measures as new measures in SLBs and PRs --- .../sonar/db/measure/LiveMeasureMapper.java | 4 +- .../server/measure/ws/ComponentAction.java | 159 ++++++++++------- .../measure/ws/ComponentTreeAction.java | 29 ++- .../server/measure/ws/ComponentTreeData.java | 10 +- .../server/measure/ws/SLBorPRMeasureFix.java | 165 ++++++++++++++++++ .../measure/ws/ComponentActionTest.java | 61 +++++++ .../measure/ws/ComponentTreeActionTest.java | 84 +++++++++ .../server/measure/ws/MeasuresWsTest.java | 7 +- .../measure/ws/SLBorPRMeasureFixTest.java | 96 ++++++++++ 9 files changed, 536 insertions(+), 79 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/measure/ws/SLBorPRMeasureFix.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/measure/ws/SLBorPRMeasureFixTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java index c0ca9539afc..caa8b9599e6 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java @@ -30,11 +30,11 @@ import org.sonar.db.component.KeyType; public interface LiveMeasureMapper { List selectByComponentUuidsAndMetricIds( - @Param("componentUuids") List componentUuids, + @Param("componentUuids") Collection componentUuids, @Param("metricIds") Collection metricIds); List selectByComponentUuidsAndMetricKeys( - @Param("componentUuids") List componentUuids, + @Param("componentUuids") Collection componentUuids, @Param("metricKeys") Collection metricKeys); LiveMeasureDto selectByComponentUuidAndMetricKey( diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java index 042f746c35d..04b60be8adf 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java @@ -21,15 +21,15 @@ package org.sonar.server.measure.ws; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import java.util.Collection; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.resources.Qualifiers; @@ -41,6 +41,7 @@ import org.sonar.api.web.UserRole; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; import org.sonar.db.measure.LiveMeasureDto; @@ -100,7 +101,7 @@ public class ComponentAction implements MeasuresWsAction { public void define(WebService.NewController context) { WebService.NewAction action = context.createAction(ACTION_COMPONENT) .setDescription(format("Return component with specified measures. The %s or the %s parameter must be provided.
" + - "Requires the following permission: 'Browse' on the project of specified component.", + "Requires the following permission: 'Browse' on the project of specified component.", DEPRECATED_PARAM_COMPONENT_ID, PARAM_COMPONENT)) .setResponseExample(getClass().getResource("component-example.json")) .setSince("5.4") @@ -149,28 +150,107 @@ public class ComponentAction implements MeasuresWsAction { private ComponentWsResponse doHandle(ComponentRequest request) { try (DbSession dbSession = dbClient.openSession(false)) { - ComponentDto component = loadComponent(dbSession, request); - Optional refComponent = getReferenceComponent(dbSession, component); + String branch = request.getBranch(); + String pullRequest = request.getPullRequest(); + ComponentDto component = loadComponent(dbSession, request, branch, pullRequest); checkPermissions(component); SnapshotDto analysis = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, component.projectUuid()).orElse(null); - List metrics = searchMetrics(dbSession, request); - Optional period = snapshotToWsPeriods(analysis); + + boolean isSLBorPR = isSLBorPR(dbSession, component, branch, pullRequest); + + Set metricKeysToRequest = new HashSet<>(request.metricKeys); + + if (isSLBorPR) { + SLBorPRMeasureFix.addReplacementMetricKeys(metricKeysToRequest); + } + + List metrics = searchMetrics(dbSession, metricKeysToRequest); List measures = searchMeasures(dbSession, component, metrics); + Map measuresByMetric = getMeasuresByMetric(measures, metrics); + + if (isSLBorPR) { + Set originalMetricKeys = new HashSet<>(request.metricKeys); + SLBorPRMeasureFix.createReplacementMeasures(metrics, measuresByMetric, originalMetricKeys); + SLBorPRMeasureFix.removeMetricsNotRequested(metrics, originalMetricKeys); + } + + Optional period = snapshotToWsPeriods(analysis); + Optional refComponent = getReferenceComponent(dbSession, component); + return buildResponse(request, component, refComponent, measuresByMetric, metrics, period); + } + } + + public List searchMetrics(DbSession dbSession, Collection metricKeys) { + List metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys); + if (metrics.size() < metricKeys.size()) { + Set foundMetricKeys = metrics.stream().map(MetricDto::getKey).collect(Collectors.toSet()); + Set missingMetricKeys = metricKeys.stream().filter(m -> !foundMetricKeys.contains(m)).collect(Collectors.toSet()); + throw new NotFoundException(format("The following metric keys are not found: %s", Joiner.on(", ").join(missingMetricKeys))); + } + + return metrics; + } + + private List searchMeasures(DbSession dbSession, ComponentDto component, Collection metrics) { + Set metricIds = metrics.stream().map(MetricDto::getId).collect(Collectors.toSet()); + List measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds); + addBestValuesToMeasures(measures, component, metrics); + return measures; + } + + private static Map getMeasuresByMetric(List measures, Collection metrics) { + Map metricsById = Maps.uniqueIndex(metrics, MetricDto::getId); + Map measuresByMetric = new HashMap<>(); + for (LiveMeasureDto measure : measures) { + MetricDto metric = metricsById.get(measure.getMetricId()); + measuresByMetric.put(metric, measure); + } + return measuresByMetric; + } + + /** + * Conditions for best value measure: + *
    + *
  • component is a production file or test file
  • + *
  • metric is optimized for best value
  • + *
+ */ + private static void addBestValuesToMeasures(List measures, ComponentDto component, Collection metrics) { + if (!QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier())) { + return; + } + + List metricWithBestValueList = metrics.stream() + .filter(MetricDtoFunctions.isOptimizedForBestValue()) + .map(MetricDtoWithBestValue::new) + .collect(MoreCollectors.toList(metrics.size())); + Map measuresByMetricId = Maps.uniqueIndex(measures, LiveMeasureDto::getMetricId); + + for (MetricDtoWithBestValue metricWithBestValue : metricWithBestValueList) { + if (measuresByMetricId.get(metricWithBestValue.getMetric().getId()) == null) { + measures.add(metricWithBestValue.getBestValue()); + } + } + } - return buildResponse(request, component, refComponent, measures, metrics, period); + private boolean isSLBorPR(DbSession dbSession, ComponentDto component, @Nullable String branch, @Nullable String pullRequest) { + if (branch != null) { + return dbClient.branchDao().selectByUuid(dbSession, component.projectUuid()) + .map(b -> b.getBranchType() == BranchType.SHORT).orElse(false); } + return pullRequest != null; } - private ComponentDto loadComponent(DbSession dbSession, ComponentRequest request) { + private ComponentDto loadComponent(DbSession dbSession, ComponentRequest request, @Nullable String branch, @Nullable String pullRequest) { String componentKey = request.getComponent(); String componentId = request.getComponentId(); - String branch = request.getBranch(); - String pullRequest = request.getPullRequest(); checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'", DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST); + if (branch == null && pullRequest == null) { return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_KEY); } + checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT); return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } @@ -184,14 +264,9 @@ public class ComponentAction implements MeasuresWsAction { } private static ComponentWsResponse buildResponse(ComponentRequest request, ComponentDto component, Optional refComponent, - List measures, List metrics, Optional period) { + Map measuresByMetric, Collection metrics, Optional period) { ComponentWsResponse.Builder response = ComponentWsResponse.newBuilder(); - Map metricsById = Maps.uniqueIndex(metrics, MetricDto::getId); - Map measuresByMetric = new HashMap<>(); - for (LiveMeasureDto measure : measures) { - MetricDto metric = metricsById.get(measure.getMetricId()); - measuresByMetric.put(metric, measure); - } + if (refComponent.isPresent()) { response.setComponent(componentDtoToWsComponent(component, measuresByMetric, singletonMap(refComponent.get().uuid(), refComponent.get()))); } else { @@ -213,52 +288,6 @@ public class ComponentAction implements MeasuresWsAction { return response.build(); } - private List searchMetrics(DbSession dbSession, ComponentRequest request) { - List metrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys()); - if (metrics.size() < request.getMetricKeys().size()) { - List foundMetricKeys = Lists.transform(metrics, MetricDto::getKey); - Set missingMetricKeys = Sets.difference( - new LinkedHashSet<>(request.getMetricKeys()), - new LinkedHashSet<>(foundMetricKeys)); - - throw new NotFoundException(format("The following metric keys are not found: %s", Joiner.on(", ").join(missingMetricKeys))); - } - - return metrics; - } - - private List searchMeasures(DbSession dbSession, ComponentDto component, List metrics) { - List metricIds = Lists.transform(metrics, MetricDto::getId); - List measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds); - addBestValuesToMeasures(measures, component, metrics); - return measures; - } - - /** - * Conditions for best value measure: - *
    - *
  • component is a production file or test file
  • - *
  • metric is optimized for best value
  • - *
- */ - private static void addBestValuesToMeasures(List measures, ComponentDto component, List metrics) { - if (!QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier())) { - return; - } - - List metricWithBestValueList = metrics.stream() - .filter(MetricDtoFunctions.isOptimizedForBestValue()) - .map(MetricDtoWithBestValue::new) - .collect(MoreCollectors.toList(metrics.size())); - Map measuresByMetricId = Maps.uniqueIndex(measures, LiveMeasureDto::getMetricId); - - for (MetricDtoWithBestValue metricWithBestValue : metricWithBestValueList) { - if (measuresByMetricId.get(metricWithBestValue.getMetric().getId()) == null) { - measures.add(metricWithBestValue.getBestValue()); - } - } - } - private static ComponentRequest toComponentWsRequest(Request request) { ComponentRequest componentRequest = new ComponentRequest() .setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java index dd5a447e4c5..85c3929b294 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java @@ -57,6 +57,7 @@ import org.sonar.api.web.UserRole; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTreeQuery; import org.sonar.db.component.ComponentTreeQuery.Strategy; @@ -76,7 +77,6 @@ import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -import static java.util.Objects.requireNonNull; 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; @@ -412,13 +412,27 @@ public class ComponentTreeAction implements MeasuresWsAction { .build(); } + Set requestedMetricKeys = new HashSet<>(wsRequest.getMetricKeys()); + Set metricKeysToSearch = new HashSet<>(requestedMetricKeys); + + boolean isSLBorPR = isSLBorPR(dbSession, baseComponent, wsRequest.getBranch(), wsRequest.getPullRequest()); + if (isSLBorPR) { + SLBorPRMeasureFix.addReplacementMetricKeys(metricKeysToSearch); + } + ComponentTreeQuery componentTreeQuery = toComponentTreeQuery(wsRequest, baseComponent); List components = searchComponents(dbSession, componentTreeQuery); - List metrics = searchMetrics(dbSession, wsRequest); + + List metrics = searchMetrics(dbSession, metricKeysToSearch); Table measuresByComponentUuidAndMetric = searchMeasuresByComponentUuidAndMetric(dbSession, baseComponent, componentTreeQuery, components, metrics); + if (isSLBorPR) { + SLBorPRMeasureFix.removeMetricsNotRequested(metrics, requestedMetricKeys); + SLBorPRMeasureFix.createReplacementMeasures(metrics, measuresByComponentUuidAndMetric, requestedMetricKeys); + } + components = filterComponents(components, measuresByComponentUuidAndMetric, metrics, wsRequest); components = sortComponents(components, wsRequest, metrics, measuresByComponentUuidAndMetric); @@ -437,6 +451,14 @@ public class ComponentTreeAction implements MeasuresWsAction { } } + private boolean isSLBorPR(DbSession dbSession, ComponentDto component, @Nullable String branch, @Nullable String pullRequest) { + if (branch != null) { + return dbClient.branchDao().selectByUuid(dbSession, component.projectUuid()) + .map(b -> b.getBranchType() == BranchType.SHORT).orElse(false); + } + return pullRequest != null; + } + private ComponentDto loadComponent(DbSession dbSession, ComponentTreeRequest request) { String componentId = request.getBaseComponentId(); String componentKey = request.getComponent(); @@ -472,8 +494,7 @@ public class ComponentTreeAction implements MeasuresWsAction { return dbClient.componentDao().selectDescendants(dbSession, componentTreeQuery); } - private List searchMetrics(DbSession dbSession, ComponentTreeRequest request) { - List metricKeys = requireNonNull(request.getMetricKeys()); + private List searchMetrics(DbSession dbSession, Set metricKeys) { List metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys); if (metrics.size() < metricKeys.size()) { List foundMetricKeys = Lists.transform(metrics, MetricDto::getKey); diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeData.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeData.java index 6f01434455a..d2747787f0c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeData.java @@ -149,10 +149,14 @@ class ComponentTreeData { private String data; private double variation; + public Measure(@Nullable String data, @Nullable Double value, @Nullable Double variation) { + this.data = data; + this.value = toPrimitive(value); + this.variation = toPrimitive(variation); + } + private Measure(LiveMeasureDto measureDto) { - this.value = toPrimitive(measureDto.getValue()); - this.data = measureDto.getDataAsString(); - this.variation = toPrimitive(measureDto.getVariation()); + this(measureDto.getDataAsString(), measureDto.getValue(), measureDto.getVariation()); } public double getValue() { diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SLBorPRMeasureFix.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SLBorPRMeasureFix.java new file mode 100644 index 00000000000..ddab46c9800 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SLBorPRMeasureFix.java @@ -0,0 +1,165 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.sonar.db.measure.LiveMeasureDto; +import org.sonar.db.metric.MetricDto; + +import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.BUGS_KEY; +import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY; +import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES_KEY; +import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.RELIABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY; +import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY; + +/** + * See SONAR-11736 + * This class should be removed in 8.0. + */ +class SLBorPRMeasureFix { + static final BiMap METRICS; + + static { + METRICS = HashBiMap.create(); + + METRICS.put(NEW_VIOLATIONS_KEY, VIOLATIONS_KEY); + + // issue severities + METRICS.put(NEW_BLOCKER_VIOLATIONS_KEY, BLOCKER_VIOLATIONS_KEY); + METRICS.put(NEW_CRITICAL_VIOLATIONS_KEY, CRITICAL_VIOLATIONS_KEY); + METRICS.put(NEW_MAJOR_VIOLATIONS_KEY, MAJOR_VIOLATIONS_KEY); + METRICS.put(NEW_MINOR_VIOLATIONS_KEY, MINOR_VIOLATIONS_KEY); + METRICS.put(NEW_INFO_VIOLATIONS_KEY, INFO_VIOLATIONS_KEY); + + // issue types + METRICS.put(NEW_BUGS_KEY, BUGS_KEY); + METRICS.put(NEW_CODE_SMELLS_KEY, CODE_SMELLS_KEY); + METRICS.put(NEW_VULNERABILITIES_KEY, VULNERABILITIES_KEY); + + // ratings + METRICS.put(NEW_SECURITY_RATING_KEY, SECURITY_RATING_KEY); + METRICS.put(NEW_RELIABILITY_RATING_KEY, RELIABILITY_RATING_KEY); + + // effort + METRICS.put(NEW_TECHNICAL_DEBT_KEY, TECHNICAL_DEBT_KEY); + METRICS.put(NEW_SECURITY_REMEDIATION_EFFORT_KEY, SECURITY_REMEDIATION_EFFORT_KEY); + METRICS.put(NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, RELIABILITY_REMEDIATION_EFFORT_KEY); + } + + private SLBorPRMeasureFix() { + // static only + } + + static void addReplacementMetricKeys(Collection metricKeys) { + Set keysToAdd = metricKeys.stream() + .filter(METRICS::containsKey) + .map(METRICS::get) + .collect(Collectors.toSet()); + metricKeys.addAll(keysToAdd); + } + + static void removeMetricsNotRequested(List metrics, Set requestedMetricKeys) { + metrics.removeIf(m -> !requestedMetricKeys.contains(m.getKey())); + } + + static void createReplacementMeasures(List metrics, Table measuresByComponentUuidAndMetric, + Set requestedMetricKeys) { + Map metricByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); + + for (MetricDto metric : measuresByComponentUuidAndMetric.columnKeySet()) { + Map newEntries = new HashMap<>(); + + String originalKey = METRICS.inverse().get(metric.getKey()); + if (originalKey != null && requestedMetricKeys.contains(originalKey)) { + for (Map.Entry e : measuresByComponentUuidAndMetric.column(metric).entrySet()) { + newEntries.put(e.getKey(), copyMeasureToVariation(e.getValue())); + } + + MetricDto originalMetric = metricByKey.get(originalKey); + newEntries.forEach((k, v) -> measuresByComponentUuidAndMetric.put(k, originalMetric, v)); + } + } + + List toRemove = measuresByComponentUuidAndMetric.columnKeySet().stream().filter(m -> !requestedMetricKeys.contains(m.getKey())).collect(Collectors.toList()); + measuresByComponentUuidAndMetric.columnKeySet().removeAll(toRemove); + } + + static void createReplacementMeasures(List metrics, Map measuresByMetric, Set requestedMetricKeys) { + Map metricByKey = Maps.uniqueIndex(metrics, MetricDto::getKey); + Map newEntries = new HashMap<>(); + + for (Map.Entry e : measuresByMetric.entrySet()) { + String originalKey = METRICS.inverse().get(e.getKey().getKey()); + + if (originalKey != null && requestedMetricKeys.contains(originalKey)) { + MetricDto metricDto = metricByKey.get(originalKey); + newEntries.put(metricDto, copyMeasureToVariation(e.getValue(), metricDto.getId())); + } + } + + measuresByMetric.entrySet().removeIf(e -> !requestedMetricKeys.contains(e.getKey().getKey())); + measuresByMetric.putAll(newEntries); + } + + private static ComponentTreeData.Measure copyMeasureToVariation(ComponentTreeData.Measure measure) { + return new ComponentTreeData.Measure(null, null, measure.getValue()); + } + + private static LiveMeasureDto copyMeasureToVariation(LiveMeasureDto dto, Integer metricId) { + LiveMeasureDto copy = new LiveMeasureDto(); + copy.setVariation(dto.getValue()); + copy.setProjectUuid(dto.getProjectUuid()); + copy.setComponentUuid(dto.getComponentUuid()); + copy.setMetricId(metricId); + return copy; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java index 23f35ea8dba..780cfc9333d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.measure.ws; +import java.util.function.Function; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -165,6 +166,66 @@ public class ComponentActionTest { .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue())); } + @Test + public void new_issue_count_measures_are_transformed_in_pr() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto bugs = db.measures().insertMetric(m1 -> m1.setKey("bugs").setValueType("INT")); + MetricDto newBugs = db.measures().insertMetric(m1 -> m1.setKey("new_bugs").setValueType("INT")); + MetricDto violations = db.measures().insertMetric(m1 -> m1.setKey("violations").setValueType("INT")); + MetricDto newViolations = db.measures().insertMetric(m1 -> m1.setKey("new_violations").setValueType("INT")); + LiveMeasureDto bugMeasure = db.measures().insertLiveMeasure(file, bugs, m -> m.setValue(12.0d).setVariation(null)); + LiveMeasureDto newBugMeasure = db.measures().insertLiveMeasure(file, newBugs, m -> m.setVariation(1d).setValue(null)); + LiveMeasureDto violationMeasure = db.measures().insertLiveMeasure(file, violations, m -> m.setValue(20.0d).setVariation(null)); + + ComponentWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRIC_KEYS, newBugs.getKey() + "," + bugs.getKey() + "," + newViolations.getKey()) + .executeProtobuf(ComponentWsResponse.class); + + assertThat(response.getComponent()).extracting(Component::getKey, Component::getPullRequest) + .containsExactlyInAnyOrder(file.getKey(), "pr-123"); + + Function extractVariation = m -> { + if (m.getPeriods().getPeriodsValueCount() > 0) { + return parseDouble(m.getPeriods().getPeriodsValue(0).getValue()); + } + return null; + }; + assertThat(response.getComponent().getMeasuresList()) + .extracting(Measures.Measure::getMetric, extractVariation, m -> m.getValue().isEmpty() ? null : parseDouble(m.getValue())) + .containsExactlyInAnyOrder( + tuple(newBugs.getKey(), bugMeasure.getValue(), null), + tuple(bugs.getKey(), null, bugMeasure.getValue()), + tuple(newViolations.getKey(), violationMeasure.getValue(), null)); + } + + @Test + public void new_issue_count_measures_are_not_transformed_if_they_dont_exist_in_pr() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto bugs = db.measures().insertMetric(m1 -> m1.setKey("bugs").setOptimizedBestValue(false).setValueType("INT")); + MetricDto newBugs = db.measures().insertMetric(m1 -> m1.setKey("new_bugs").setOptimizedBestValue(false).setValueType("INT")); + + ComponentWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRIC_KEYS, newBugs.getKey() + "," + bugs.getKey()) + .executeProtobuf(ComponentWsResponse.class); + + assertThat(response.getComponent()).extracting(Component::getKey, Component::getPullRequest) + .containsExactlyInAnyOrder(file.getKey(), "pr-123"); + + assertThat(response.getComponent().getMeasuresList()).isEmpty(); + } + @Test public void reference_uuid_in_the_response() { userSession.logIn().setRoot(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java index aff9f504cb6..57fd73c38d5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java @@ -21,10 +21,12 @@ package org.sonar.server.measure.ws; import com.google.common.base.Joiner; import java.util.List; +import java.util.function.Function; import java.util.stream.IntStream; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; @@ -70,6 +72,7 @@ import static org.sonar.api.server.ws.WebService.Param.SORT; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.component.BranchType.PULL_REQUEST; +import static org.sonar.db.component.BranchType.SHORT; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.component.ComponentTesting.newProjectCopy; @@ -558,6 +561,87 @@ public class ComponentTreeActionTest { .containsExactlyInAnyOrder(tuple(complexity.getKey(), measure.getValue())); } + @Test + public void fix_pull_request_new_issue_count_metrics() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto bug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.BUGS_KEY)); + MetricDto newBug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.NEW_BUGS_KEY)); + + LiveMeasureDto measure = db.measures().insertLiveMeasure(file, bug, m -> m.setValue(12.0d)); + + ComponentTreeWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_PULL_REQUEST, "pr-123") + .setParam(PARAM_METRIC_KEYS, newBug.getKey()) + .executeProtobuf(ComponentTreeWsResponse.class); + + assertThat(response.getBaseComponent()).extracting(Component::getKey, Component::getPullRequest) + .containsExactlyInAnyOrder(file.getKey(), "pr-123"); + assertThat(response.getBaseComponent().getMeasuresList()) + .extracting(Measure::getMetric, m -> parseDouble(m.getPeriods().getPeriodsValue(0).getValue()), Measure::getValue) + .containsExactlyInAnyOrder(tuple(newBug.getKey(), measure.getValue(), "")); + } + + @Test + public void new_issue_count_measures_are_transformed_in_slb() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("slb").setBranchType(SHORT)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto bug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.BUGS_KEY)); + MetricDto newBug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.NEW_BUGS_KEY)); + + LiveMeasureDto measure = db.measures().insertLiveMeasure(file, bug, m -> m.setValue(12.0d).setVariation(null)); + + ComponentTreeWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_BRANCH, "slb") + .setParam(PARAM_METRIC_KEYS, newBug.getKey() + "," + bug.getKey()) + .executeProtobuf(ComponentTreeWsResponse.class); + + Function extractVariation = m -> { + if (m.getPeriods().getPeriodsValueCount() > 0) { + return parseDouble(m.getPeriods().getPeriodsValue(0).getValue()); + } + return null; + }; + + assertThat(response.getBaseComponent()).extracting(Component::getKey, Component::getBranch) + .containsExactlyInAnyOrder(file.getKey(), "slb"); + assertThat(response.getBaseComponent().getMeasuresList()) + .extracting(Measure::getMetric, extractVariation, m -> m.getValue().isEmpty() ? null : parseDouble(m.getValue())) + .containsExactlyInAnyOrder( + tuple(newBug.getKey(), measure.getValue(), null), + tuple(bug.getKey(), null, measure.getValue())); + } + + @Test + public void new_issue_count_measures_are_not_transformed_if_they_dont_exist_in_slb() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("slb").setBranchType(SHORT)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + ComponentDto file = db.components().insertComponent(newFileDto(branch)); + MetricDto bug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.BUGS_KEY)); + MetricDto newBug = db.measures().insertMetric(m -> m.setValueType(INT.name()).setKey(CoreMetrics.NEW_BUGS_KEY)); + + ComponentTreeWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, file.getKey()) + .setParam(PARAM_BRANCH, "slb") + .setParam(PARAM_METRIC_KEYS, newBug.getKey() + "," + bug.getKey()) + .executeProtobuf(ComponentTreeWsResponse.class); + + assertThat(response.getBaseComponent()).extracting(Component::getKey, Component::getBranch) + .containsExactlyInAnyOrder(file.getKey(), "slb"); + assertThat(response.getBaseComponent().getMeasuresList()) + .isEmpty(); + } + @Test public void return_deprecated_id_in_the_response() { ComponentDto project = db.components().insertPrivateProject(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsTest.java index 87df7730128..81e2ba59d65 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsTest.java @@ -19,15 +19,12 @@ */ package org.sonar.server.measure.ws; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - import org.junit.Test; -import org.sonar.api.i18n.I18n; -import org.sonar.api.resources.ResourceTypes; import org.sonar.api.server.ws.WebService; import org.sonar.server.ws.WsTester; +import static org.assertj.core.api.Assertions.assertThat; + public class MeasuresWsTest { WsTester ws = new WsTester( new MeasuresWs( diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SLBorPRMeasureFixTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SLBorPRMeasureFixTest.java new file mode 100644 index 00000000000..ca2c6a07898 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/SLBorPRMeasureFixTest.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.junit.Test; +import org.sonar.db.measure.LiveMeasureDto; +import org.sonar.db.metric.MetricDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.measures.CoreMetrics.BUGS_KEY; +import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY; + +public class SLBorPRMeasureFixTest { + + @Test + public void should_add_replacement_metrics() { + List metricList = new ArrayList<>(Arrays.asList(NEW_BUGS_KEY, NEW_MINOR_VIOLATIONS_KEY)); + SLBorPRMeasureFix.addReplacementMetricKeys(metricList); + assertThat(metricList).contains(BUGS_KEY, NEW_BUGS_KEY, MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY); + } + + @Test + public void should_remove_metrics_not_initially_requested() { + Set originalMetricList = new HashSet<>(Arrays.asList(NEW_BUGS_KEY, MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY)); + MetricDto dto1 = new MetricDto().setKey(BUGS_KEY).setId(1); + MetricDto dto2 = new MetricDto().setKey(NEW_BUGS_KEY).setId(2); + MetricDto dto3 = new MetricDto().setKey(MINOR_VIOLATIONS_KEY).setId(3); + MetricDto dto4 = new MetricDto().setKey(NEW_MINOR_VIOLATIONS_KEY).setId(4); + + List metricList = new ArrayList<>(Arrays.asList(dto1, dto2, dto3, dto4)); + + SLBorPRMeasureFix.removeMetricsNotRequested(metricList, originalMetricList); + assertThat(metricList).containsOnly(dto2, dto3, dto4); + } + + @Test + public void should_transform_measures() { + Set requestedKeys = new HashSet<>(Arrays.asList(NEW_BUGS_KEY, MINOR_VIOLATIONS_KEY, NEW_MINOR_VIOLATIONS_KEY)); + + MetricDto bugsMetric = new MetricDto().setKey(BUGS_KEY).setId(1); + MetricDto newBugsMetric = new MetricDto().setKey(NEW_BUGS_KEY).setId(2); + MetricDto violationsMetric = new MetricDto().setKey(MINOR_VIOLATIONS_KEY).setId(3); + MetricDto newViolationsMetric = new MetricDto().setKey(NEW_MINOR_VIOLATIONS_KEY).setId(4); + + List metricList = Arrays.asList(bugsMetric, newBugsMetric, violationsMetric, newViolationsMetric); + + LiveMeasureDto bugs = createLiveMeasure(bugsMetric.getId(), 10.0, null); + LiveMeasureDto newBugs = createLiveMeasure(newBugsMetric.getId(), null, 5.0); + LiveMeasureDto violations = createLiveMeasure(violationsMetric.getId(), 20.0, null); + LiveMeasureDto newViolations = createLiveMeasure(newViolationsMetric.getId(), null, 3.0); + + Map measureByMetric = new HashMap<>(); + measureByMetric.put(bugsMetric, bugs); + measureByMetric.put(newBugsMetric, newBugs); + measureByMetric.put(violationsMetric, violations); + measureByMetric.put(newViolationsMetric, newViolations); + + SLBorPRMeasureFix.createReplacementMeasures(metricList, measureByMetric, requestedKeys); + assertThat(measureByMetric.entrySet()).extracting(e -> e.getKey().getKey(), e -> e.getValue().getValue(), e -> e.getValue().getVariation()) + .containsOnly(tuple(NEW_BUGS_KEY, null, 10.0), + tuple(MINOR_VIOLATIONS_KEY, 20.0, null), + tuple(NEW_MINOR_VIOLATIONS_KEY, null, 20.0)); + } + + private static LiveMeasureDto createLiveMeasure(int metricId, @Nullable Double value, @Nullable Double variation) { + return new LiveMeasureDto().setMetricId(metricId).setVariation(variation).setValue(value); + } +} -- 2.39.5