]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11736 Reaturn overall issue count measures as new measures in SLBs and PRs
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 25 Feb 2019 16:40:16 +0000 (10:40 -0600)
committerSonarTech <sonartech@sonarsource.com>
Mon, 11 Mar 2019 19:21:03 +0000 (20:21 +0100)
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeData.java
server/sonar-server/src/main/java/org/sonar/server/measure/ws/SLBorPRMeasureFix.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/ws/MeasuresWsTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/ws/SLBorPRMeasureFixTest.java [new file with mode: 0644]

index c0ca9539afc11cc1c4c2b96ea395067f2eb67538..caa8b9599e65c59da90ae35f065785a76fe44a29 100644 (file)
@@ -30,11 +30,11 @@ import org.sonar.db.component.KeyType;
 public interface LiveMeasureMapper {
 
   List<LiveMeasureDto> selectByComponentUuidsAndMetricIds(
-    @Param("componentUuids") List<String> componentUuids,
+    @Param("componentUuids") Collection<String> componentUuids,
     @Param("metricIds") Collection<Integer> metricIds);
 
   List<LiveMeasureDto> selectByComponentUuidsAndMetricKeys(
-    @Param("componentUuids") List<String> componentUuids,
+    @Param("componentUuids") Collection<String> componentUuids,
     @Param("metricKeys") Collection<String> metricKeys);
 
   LiveMeasureDto selectByComponentUuidAndMetricKey(
index 042f746c35d040302ac60e52ef7db674b8a8453c..04b60be8adfa545702ba67e9880cbaca8287ad32 100644 (file)
@@ -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.<br>" +
-        "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<ComponentDto> 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<MetricDto> metrics = searchMetrics(dbSession, request);
-      Optional<Measures.Period> period = snapshotToWsPeriods(analysis);
+
+      boolean isSLBorPR = isSLBorPR(dbSession, component, branch, pullRequest);
+
+      Set<String> metricKeysToRequest = new HashSet<>(request.metricKeys);
+
+      if (isSLBorPR) {
+        SLBorPRMeasureFix.addReplacementMetricKeys(metricKeysToRequest);
+      }
+
+      List<MetricDto> metrics = searchMetrics(dbSession, metricKeysToRequest);
       List<LiveMeasureDto> measures = searchMeasures(dbSession, component, metrics);
+      Map<MetricDto, LiveMeasureDto> measuresByMetric = getMeasuresByMetric(measures, metrics);
+
+      if (isSLBorPR) {
+        Set<String> originalMetricKeys = new HashSet<>(request.metricKeys);
+        SLBorPRMeasureFix.createReplacementMeasures(metrics, measuresByMetric, originalMetricKeys);
+        SLBorPRMeasureFix.removeMetricsNotRequested(metrics, originalMetricKeys);
+      }
+
+      Optional<Measures.Period> period = snapshotToWsPeriods(analysis);
+      Optional<ComponentDto> refComponent = getReferenceComponent(dbSession, component);
+      return buildResponse(request, component, refComponent, measuresByMetric, metrics, period);
+    }
+  }
+
+  public List<MetricDto> searchMetrics(DbSession dbSession, Collection<String> metricKeys) {
+    List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys);
+    if (metrics.size() < metricKeys.size()) {
+      Set<String> foundMetricKeys = metrics.stream().map(MetricDto::getKey).collect(Collectors.toSet());
+      Set<String> 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<LiveMeasureDto> searchMeasures(DbSession dbSession, ComponentDto component, Collection<MetricDto> metrics) {
+    Set<Integer> metricIds = metrics.stream().map(MetricDto::getId).collect(Collectors.toSet());
+    List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds);
+    addBestValuesToMeasures(measures, component, metrics);
+    return measures;
+  }
+
+  private static Map<MetricDto, LiveMeasureDto> getMeasuresByMetric(List<LiveMeasureDto> measures, Collection<MetricDto> metrics) {
+    Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDto::getId);
+    Map<MetricDto, LiveMeasureDto> measuresByMetric = new HashMap<>();
+    for (LiveMeasureDto measure : measures) {
+      MetricDto metric = metricsById.get(measure.getMetricId());
+      measuresByMetric.put(metric, measure);
+    }
+    return measuresByMetric;
+  }
+
+  /**
+   * Conditions for best value measure:
+   * <ul>
+   * <li>component is a production file or test file</li>
+   * <li>metric is optimized for best value</li>
+   * </ul>
+   */
+  private static void addBestValuesToMeasures(List<LiveMeasureDto> measures, ComponentDto component, Collection<MetricDto> metrics) {
+    if (!QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier())) {
+      return;
+    }
+
+    List<MetricDtoWithBestValue> metricWithBestValueList = metrics.stream()
+      .filter(MetricDtoFunctions.isOptimizedForBestValue())
+      .map(MetricDtoWithBestValue::new)
+      .collect(MoreCollectors.toList(metrics.size()));
+    Map<Integer, LiveMeasureDto> 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<ComponentDto> refComponent,
-    List<LiveMeasureDto> measures, List<MetricDto> metrics, Optional<Measures.Period> period) {
+    Map<MetricDto, LiveMeasureDto> measuresByMetric, Collection<MetricDto> metrics, Optional<Measures.Period> period) {
     ComponentWsResponse.Builder response = ComponentWsResponse.newBuilder();
-    Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDto::getId);
-    Map<MetricDto, LiveMeasureDto> 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<MetricDto> searchMetrics(DbSession dbSession, ComponentRequest request) {
-    List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys());
-    if (metrics.size() < request.getMetricKeys().size()) {
-      List<String> foundMetricKeys = Lists.transform(metrics, MetricDto::getKey);
-      Set<String> 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<LiveMeasureDto> searchMeasures(DbSession dbSession, ComponentDto component, List<MetricDto> metrics) {
-    List<Integer> metricIds = Lists.transform(metrics, MetricDto::getId);
-    List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds);
-    addBestValuesToMeasures(measures, component, metrics);
-    return measures;
-  }
-
-  /**
-   * Conditions for best value measure:
-   * <ul>
-   * <li>component is a production file or test file</li>
-   * <li>metric is optimized for best value</li>
-   * </ul>
-   */
-  private static void addBestValuesToMeasures(List<LiveMeasureDto> measures, ComponentDto component, List<MetricDto> metrics) {
-    if (!QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier())) {
-      return;
-    }
-
-    List<MetricDtoWithBestValue> metricWithBestValueList = metrics.stream()
-      .filter(MetricDtoFunctions.isOptimizedForBestValue())
-      .map(MetricDtoWithBestValue::new)
-      .collect(MoreCollectors.toList(metrics.size()));
-    Map<Integer, LiveMeasureDto> 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))
index dd5a447e4c536a413765d19813dd716f2b3a1e5f..85c3929b2940f5872b98a13894af86dc31a9b043 100644 (file)
@@ -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<String> requestedMetricKeys = new HashSet<>(wsRequest.getMetricKeys());
+      Set<String> metricKeysToSearch = new HashSet<>(requestedMetricKeys);
+
+      boolean isSLBorPR = isSLBorPR(dbSession, baseComponent, wsRequest.getBranch(), wsRequest.getPullRequest());
+      if (isSLBorPR) {
+        SLBorPRMeasureFix.addReplacementMetricKeys(metricKeysToSearch);
+      }
+
       ComponentTreeQuery componentTreeQuery = toComponentTreeQuery(wsRequest, baseComponent);
       List<ComponentDto> components = searchComponents(dbSession, componentTreeQuery);
-      List<MetricDto> metrics = searchMetrics(dbSession, wsRequest);
+
+      List<MetricDto> metrics = searchMetrics(dbSession, metricKeysToSearch);
       Table<String, MetricDto, ComponentTreeData.Measure> 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<MetricDto> searchMetrics(DbSession dbSession, ComponentTreeRequest request) {
-    List<String> metricKeys = requireNonNull(request.getMetricKeys());
+  private List<MetricDto> searchMetrics(DbSession dbSession, Set<String> metricKeys) {
     List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys);
     if (metrics.size() < metricKeys.size()) {
       List<String> foundMetricKeys = Lists.transform(metrics, MetricDto::getKey);
index 6f01434455af77d40c6d535779ea1af9152ee316..d2747787f0c04bc19e2670098e32649827139883 100644 (file)
@@ -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 (file)
index 0000000..ddab46c
--- /dev/null
@@ -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<String, String> 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<String> metricKeys) {
+    Set<String> keysToAdd = metricKeys.stream()
+      .filter(METRICS::containsKey)
+      .map(METRICS::get)
+      .collect(Collectors.toSet());
+    metricKeys.addAll(keysToAdd);
+  }
+
+  static void removeMetricsNotRequested(List<MetricDto> metrics, Set<String> requestedMetricKeys) {
+    metrics.removeIf(m -> !requestedMetricKeys.contains(m.getKey()));
+  }
+
+  static void createReplacementMeasures(List<MetricDto> metrics, Table<String, MetricDto, ComponentTreeData.Measure> measuresByComponentUuidAndMetric,
+    Set<String> requestedMetricKeys) {
+    Map<String, MetricDto> metricByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
+
+    for (MetricDto metric : measuresByComponentUuidAndMetric.columnKeySet()) {
+      Map<String, ComponentTreeData.Measure> newEntries = new HashMap<>();
+
+      String originalKey = METRICS.inverse().get(metric.getKey());
+      if (originalKey != null && requestedMetricKeys.contains(originalKey)) {
+        for (Map.Entry<String, ComponentTreeData.Measure> 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<MetricDto> toRemove = measuresByComponentUuidAndMetric.columnKeySet().stream().filter(m -> !requestedMetricKeys.contains(m.getKey())).collect(Collectors.toList());
+    measuresByComponentUuidAndMetric.columnKeySet().removeAll(toRemove);
+  }
+
+  static void createReplacementMeasures(List<MetricDto> metrics, Map<MetricDto, LiveMeasureDto> measuresByMetric, Set<String> requestedMetricKeys) {
+    Map<String, MetricDto> metricByKey = Maps.uniqueIndex(metrics, MetricDto::getKey);
+    Map<MetricDto, LiveMeasureDto> newEntries = new HashMap<>();
+
+    for (Map.Entry<MetricDto, LiveMeasureDto> 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;
+  }
+}
index 23f35ea8dbabf6e3c4930db75c5f2a855cf1491f..780cfc9333decbd320995f5cb941804e8f9960f6 100644 (file)
@@ -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<Measures.Measure, Double> 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();
index aff9f504cb6ba4b85156faf2d0c22a8dc05c2844..57fd73c38d54140b228754236aac347cf22cdfa6 100644 (file)
@@ -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<Measure, Double> 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();
index 87df77301283e30950459147e31f793c898f7479..81e2ba59d65ac4a1464f3f13a44ceab81cba3724 100644 (file)
  */
 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 (file)
index 0000000..ca2c6a0
--- /dev/null
@@ -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<String> 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<String> 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<MetricDto> 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<String> 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<MetricDto> 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<MetricDto, LiveMeasureDto> 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);
+  }
+}