]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23298 Compute new measures for project and live update
authorDejan Milisavljevic <dejan.milisavljevic@sonarsource.com>
Mon, 28 Oct 2024 09:40:27 +0000 (10:40 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Nov 2024 20:03:01 +0000 (20:03 +0000)
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactSeverityGroupDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImpl.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java

index 6732c26d0ae1060a52d462d3e46908980e72510d..d0111290f1d7f1cc6cb2e78f79fc92c7662271de 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
 import org.sonar.ce.task.projectanalysis.metric.Metric;
 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
 import org.sonar.server.measure.ImpactMeasureBuilder;
 
 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
@@ -90,6 +91,7 @@ import static org.sonar.api.rules.RuleType.VULNERABILITY;
  * <li>issues per status (open, reopen, confirmed)</li>
  * <li>issues per resolution (unresolved, false-positives, won't fix)</li>
  * <li>issues per severity (from info to blocker)</li>
+ * <li>issues per impact severity (from info to blocker)</li>
  * <li>issues per type (code smell, bug, vulnerability, security hotspots)</li>
  * <li>issues per impact</li>
  * </ul>
@@ -111,16 +113,40 @@ public class IssueCounter extends IssueVisitor {
     MINOR, NEW_MINOR_VIOLATIONS_KEY,
     INFO, NEW_INFO_VIOLATIONS_KEY);
 
-  static final Map<String, String> IMPACT_TO_METRIC_KEY = Map.of(
+  private static final Map<Severity, String> IMPACT_SEVERITY_TO_METRIC_KEY = ImmutableMap.of(
+    Severity.BLOCKER, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY,
+    Severity.HIGH, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY,
+    Severity.MEDIUM, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY,
+    Severity.LOW, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES_KEY,
+    Severity.INFO, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES_KEY);
+
+  private static final Map<Severity, String> IMPACT_SEVERITY_TO_NEW_METRIC_KEY = ImmutableMap.of(
+    Severity.BLOCKER, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY,
+    Severity.HIGH, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY,
+    Severity.MEDIUM, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY,
+    Severity.LOW, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY,
+    Severity.INFO, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY);
+
+  static final Map<String, String> IMPACT_TO_JSON_METRIC_KEY = Map.of(
     SoftwareQuality.SECURITY.name(), SECURITY_ISSUES_KEY,
     SoftwareQuality.RELIABILITY.name(), RELIABILITY_ISSUES_KEY,
     SoftwareQuality.MAINTAINABILITY.name(), MAINTAINABILITY_ISSUES_KEY);
 
-  static final Map<String, String> IMPACT_TO_NEW_METRIC_KEY = Map.of(
+  static final Map<String, String> IMPACT_TO_NEW_JSON_METRIC_KEY = Map.of(
     SoftwareQuality.SECURITY.name(), NEW_SECURITY_ISSUES_KEY,
     SoftwareQuality.RELIABILITY.name(), NEW_RELIABILITY_ISSUES_KEY,
     SoftwareQuality.MAINTAINABILITY.name(), NEW_MAINTAINABILITY_ISSUES_KEY);
 
+  static final Map<String, String> IMPACT_TO_METRIC_KEY = Map.of(
+    SoftwareQuality.SECURITY.name(), SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY,
+    SoftwareQuality.RELIABILITY.name(), SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY,
+    SoftwareQuality.MAINTAINABILITY.name(), SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY);
+
+  static final Map<String, String> IMPACT_TO_NEW_METRIC_KEY = Map.of(
+    SoftwareQuality.SECURITY.name(), SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY,
+    SoftwareQuality.RELIABILITY.name(), SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY,
+    SoftwareQuality.MAINTAINABILITY.name(), SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY);
+
   private static final Map<RuleType, String> TYPE_TO_METRIC_KEY = ImmutableMap.<RuleType, String>builder()
     .put(CODE_SMELL, CODE_SMELLS_KEY)
     .put(BUG, BUGS_KEY)
@@ -170,6 +196,7 @@ public class IssueCounter extends IssueVisitor {
   @Override
   public void afterComponent(Component component) {
     addMeasuresBySeverity(component);
+    addMeasuresByImpactSeverity(component);
     addMeasuresByStatus(component);
     addMeasuresByType(component);
     addMeasuresByImpact(component);
@@ -178,11 +205,11 @@ public class IssueCounter extends IssueVisitor {
   }
 
   private void addMeasuresBySeverity(Component component) {
-    for (Map.Entry<String, String> entry : SEVERITY_TO_METRIC_KEY.entrySet()) {
-      String severity = entry.getKey();
-      String metricKey = entry.getValue();
-      addMeasure(component, metricKey, currentCounters.counter().severityBag.count(severity));
-    }
+    addMeasures(component, SEVERITY_TO_METRIC_KEY, currentCounters.counter().severityBag);
+  }
+
+  private void addMeasuresByImpactSeverity(Component component) {
+    addMeasures(component, IMPACT_SEVERITY_TO_METRIC_KEY, currentCounters.counter().impactSeverityBag);
   }
 
   private void addMeasuresByStatus(Component component) {
@@ -198,7 +225,9 @@ public class IssueCounter extends IssueVisitor {
   private void addMeasuresByImpact(Component component) {
     for (Map.Entry<String, Map<String, Long>> impactEntry : currentCounters.counter().impactsBag.entrySet()) {
       String json = ImpactMeasureBuilder.fromMap(impactEntry.getValue()).buildAsString();
-      addMeasure(component, IMPACT_TO_METRIC_KEY.get(impactEntry.getKey()), json);
+      addMeasure(component, IMPACT_TO_JSON_METRIC_KEY.get(impactEntry.getKey()), json);
+      addMeasure(component, IMPACT_TO_METRIC_KEY.get(impactEntry.getKey()),
+        impactEntry.getValue().get(ImpactMeasureBuilder.TOTAL_KEY).intValue());
     }
   }
 
@@ -218,6 +247,15 @@ public class IssueCounter extends IssueVisitor {
     measureRepository.add(component, metric, Measure.newMeasureBuilder().create(data));
   }
 
+  private <M> void addMeasures(Component component, Map<M, String> metrics, Multiset<M> countPerMetric) {
+    for (Map.Entry<M, String> entry : metrics.entrySet()) {
+      M entryKey = entry.getKey();
+      String metricKey = entry.getValue();
+      addMeasure(component, metricKey, countPerMetric.count(entryKey));
+    }
+  }
+
+
   private void addNewMeasures(Component component) {
     if (!newIssueClassifier.isEnabled()) {
       return;
@@ -226,25 +264,17 @@ public class IssueCounter extends IssueVisitor {
     measureRepository.add(component, metricRepository.getByKey(NEW_VIOLATIONS_KEY), Measure.newMeasureBuilder()
       .create(unresolved));
 
-    for (Map.Entry<String, String> entry : SEVERITY_TO_NEW_METRIC_KEY.entrySet()) {
-      String severity = entry.getKey();
-      String metricKey = entry.getValue();
-      Multiset<String> bag = currentCounters.counterForPeriod().severityBag;
-      addMeasure(component, metricKey, bag.count(severity));
-    }
+    addMeasures(component, SEVERITY_TO_NEW_METRIC_KEY, currentCounters.counterForPeriod().severityBag);
 
-    // waiting for Java 8 lambda in order to factor this loop with the previous one
-    // (see call currentCounters.counterForPeriod(period.getIndex()).xxx with xxx as severityBag or typeBag)
-    for (Map.Entry<RuleType, String> entry : TYPE_TO_NEW_METRIC_KEY.entrySet()) {
-      RuleType type = entry.getKey();
-      String metricKey = entry.getValue();
-      Multiset<RuleType> bag = currentCounters.counterForPeriod().typeBag;
-      addMeasure(component, metricKey, bag.count(type));
-    }
+    addMeasures(component, IMPACT_SEVERITY_TO_NEW_METRIC_KEY, currentCounters.counterForPeriod().impactSeverityBag);
+
+    addMeasures(component, TYPE_TO_NEW_METRIC_KEY, currentCounters.counterForPeriod().typeBag);
 
     for (Map.Entry<String, Map<String, Long>> impactEntry : currentCounters.counterForPeriod().impactsBag.entrySet()) {
       String json = ImpactMeasureBuilder.fromMap(impactEntry.getValue()).buildAsString();
-      addMeasure(component, IMPACT_TO_NEW_METRIC_KEY.get(impactEntry.getKey()), json);
+      addMeasure(component, IMPACT_TO_NEW_JSON_METRIC_KEY.get(impactEntry.getKey()), json);
+      addMeasure(component, IMPACT_TO_NEW_METRIC_KEY.get(impactEntry.getKey()),
+        impactEntry.getValue().get(ImpactMeasureBuilder.TOTAL_KEY).intValue());
     }
 
     addMeasure(component, NEW_ACCEPTED_ISSUES_KEY, currentCounters.counterForPeriod().accepted);
@@ -262,6 +292,7 @@ public class IssueCounter extends IssueVisitor {
     private int accepted = 0;
     private int highImpactAccepted = 0;
     private final Multiset<String> severityBag = HashMultiset.create();
+    private final Multiset<Severity> impactSeverityBag = HashMultiset.create();
     /**
      * This map contains the number of issues per software quality along with their distribution based on (new) severity.
      */
@@ -287,6 +318,7 @@ public class IssueCounter extends IssueVisitor {
       accepted += counter.accepted;
       highImpactAccepted += counter.highImpactAccepted;
       severityBag.addAll(counter.severityBag);
+      impactSeverityBag.addAll(counter.impactSeverityBag);
       typeBag.addAll(counter.typeBag);
 
       // Add impacts
@@ -330,10 +362,10 @@ public class IssueCounter extends IssueVisitor {
         default:
           // Other statuses are ignored
       }
-      addIssueToImpactsBag(issue);
+      countIssueImpacts(issue);
     }
 
-    private void addIssueToImpactsBag(DefaultIssue issue) {
+    private void countIssueImpacts(DefaultIssue issue) {
       if (IssueStatus.OPEN == issue.issueStatus() || IssueStatus.CONFIRMED == issue.issueStatus()) {
         for (Map.Entry<SoftwareQuality, Severity> impact : issue.impacts().entrySet()) {
           impactsBag.compute(impact.getKey().name(), (key, value) -> {
@@ -342,6 +374,7 @@ public class IssueCounter extends IssueVisitor {
             return value;
           });
         }
+        issue.impacts().values().stream().distinct().forEach(impactSeverityBag::add);
       }
     }
   }
index e975fbffcd506a0779748b2a7a64a4794b436aa2..224596dd7692b3c7460e67c08a593604cbb83bab 100644 (file)
@@ -58,10 +58,14 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED;
 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
 import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+import static org.sonar.api.issue.impact.Severity.BLOCKER;
 import static org.sonar.api.issue.impact.Severity.HIGH;
 import static org.sonar.api.issue.impact.Severity.INFO;
 import static org.sonar.api.issue.impact.Severity.LOW;
 import static org.sonar.api.issue.impact.Severity.MEDIUM;
+import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
+import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
 import static org.sonar.api.measures.CoreMetrics.ACCEPTED_ISSUES;
 import static org.sonar.api.measures.CoreMetrics.ACCEPTED_ISSUES_KEY;
 import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS;
@@ -123,10 +127,42 @@ import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_METRIC_KEY;
-import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_NEW_METRIC_KEY;
+import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_JSON_METRIC_KEY;
+import static org.sonar.ce.task.projectanalysis.issue.IssueCounter.IMPACT_TO_NEW_JSON_METRIC_KEY;
 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY;
 import static org.sonar.test.JsonAssert.assertJson;
 
 class IssueCounterTest {
@@ -176,7 +212,23 @@ class IssueCounterTest {
     .add(SECURITY_ISSUES)
     .add(NEW_RELIABILITY_ISSUES)
     .add(NEW_MAINTAINABILITY_ISSUES)
-    .add(NEW_SECURITY_ISSUES);
+    .add(NEW_SECURITY_ISSUES)
+    .add(SOFTWARE_QUALITY_BLOCKER_ISSUES)
+    .add(SOFTWARE_QUALITY_HIGH_ISSUES)
+    .add(SOFTWARE_QUALITY_MEDIUM_ISSUES)
+    .add(SOFTWARE_QUALITY_LOW_ISSUES)
+    .add(SOFTWARE_QUALITY_INFO_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_HIGH_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_LOW_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_INFO_ISSUES)
+    .add(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES)
+    .add(SOFTWARE_QUALITY_RELIABILITY_ISSUES)
+    .add(SOFTWARE_QUALITY_SECURITY_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES)
+    .add(NEW_SOFTWARE_QUALITY_SECURITY_ISSUES);
 
   @RegisterExtension
   private final MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
@@ -354,12 +406,12 @@ class IssueCounterTest {
     when(newIssueClassifier.isEnabled()).thenReturn(true);
 
     underTest.beforeComponent(FILE1);
-    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, MEDIUM));
 
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, MEDIUM));
 
     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, HIGH));
     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, MEDIUM));
@@ -372,9 +424,110 @@ class IssueCounterTest {
 
     Set<Map.Entry<String, Measure>> entries = measureRepository.getRawMeasures(FILE1).entrySet();
 
-    assertOverallSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(4, 2, 2, 0, 0, 0), entries);
+    assertOverallSoftwareQualityMeasures(MAINTAINABILITY, getImpactMeasure(4, 2, 2, 0, 0, 0), entries);
     assertOverallSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(2, 1, 1, 0, 0, 0), entries);
-    assertOverallSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(0, 0, 0, 0, 0, 0), entries);
+    assertOverallSoftwareQualityMeasures(RELIABILITY, getImpactMeasure(0, 0, 0, 0, 0, 0), entries);
+  }
+
+  @Test
+  void onIssue_shouldCountByImpactSeverity() {
+    when(newIssueClassifier.isEnabled()).thenReturn(true);
+
+    underTest.beforeComponent(FILE1);
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(MAINTAINABILITY, BLOCKER, SECURITY, BLOCKER)));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(MAINTAINABILITY, HIGH, SECURITY, BLOCKER)));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, RELIABILITY, HIGH));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+    // Should not count because it is resolved
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, Map.of(SECURITY, BLOCKER, MAINTAINABILITY, INFO)));
+
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(MAINTAINABILITY, INFO, SECURITY, INFO)));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(MAINTAINABILITY, INFO, SECURITY, INFO)));
+
+    underTest.afterComponent(FILE1);
+
+    underTest.beforeComponent(PROJECT);
+    underTest.onIssue(PROJECT, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+    underTest.afterComponent(PROJECT);
+
+    assertIntValue(FILE1,
+      entry(SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY, 2),
+      entry(SOFTWARE_QUALITY_HIGH_ISSUES_KEY, 3),
+      entry(SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY, 2),
+      entry(SOFTWARE_QUALITY_LOW_ISSUES_KEY, 2),
+      entry(SOFTWARE_QUALITY_INFO_ISSUES_KEY, 2)
+    );
+
+    assertIntValue(FILE1,
+      entry(NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY, 0),
+      entry(NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY, 1),
+      entry(NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY, 1),
+      entry(NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY, 1),
+      entry(NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY, 2)
+    );
+
+    assertIntValue(PROJECT,
+      entry(SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY, 2),
+      entry(SOFTWARE_QUALITY_HIGH_ISSUES_KEY, 3),
+      entry(SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY, 3),
+      entry(SOFTWARE_QUALITY_LOW_ISSUES_KEY, 3),
+      entry(SOFTWARE_QUALITY_INFO_ISSUES_KEY, 2)
+    );
+
+    assertIntValue(PROJECT,
+      entry(NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY, 0),
+      entry(NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY, 1),
+      entry(NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY, 2),
+      entry(NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY, 2),
+      entry(NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY, 2)
+    );
+  }
+
+  @Test
+  void onIssue_shouldCountBySoftwareQuality() {
+    when(newIssueClassifier.isEnabled()).thenReturn(true);
+
+    underTest.beforeComponent(FILE1);
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(MAINTAINABILITY, HIGH, SECURITY, BLOCKER)));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, RELIABILITY, HIGH));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+    // Should not count because it is resolved
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, Map.of(SECURITY, BLOCKER, MAINTAINABILITY, INFO)));
+
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+
+    underTest.afterComponent(FILE1);
+
+    underTest.beforeComponent(PROJECT);
+    underTest.onIssue(PROJECT, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, Map.of(SECURITY, MEDIUM, MAINTAINABILITY, LOW)));
+    underTest.afterComponent(PROJECT);
+
+    assertIntValue(FILE1,
+      entry(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY, 4),
+      entry(SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY, 1),
+      entry(SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 3)
+    );
+
+    assertIntValue(FILE1,
+      entry(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY, 2),
+      entry(NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY, 0),
+      entry(NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 1)
+    );
+
+    assertIntValue(PROJECT,
+      entry(SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY, 5),
+      entry(SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY, 1),
+      entry(SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 4)
+    );
+
+    assertIntValue(PROJECT,
+      entry(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY, 3),
+      entry(NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY, 0),
+      entry(NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 2)
+    );
   }
 
   @Test
@@ -382,15 +535,15 @@ class IssueCounterTest {
     when(newIssueClassifier.isEnabled()).thenReturn(true);
 
     underTest.beforeComponent(FILE1);
-    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.MAINTAINABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.MAINTAINABILITY, MEDIUM));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, MAINTAINABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, MAINTAINABILITY, MEDIUM));
 
-    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, LOW));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, SoftwareQuality.RELIABILITY, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.RELIABILITY, MEDIUM));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, RELIABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, RELIABILITY, LOW));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, RELIABILITY, HIGH));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, RELIABILITY, MEDIUM));
 
     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, MEDIUM));
     underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_OPEN, SoftwareQuality.SECURITY, LOW));
@@ -406,8 +559,8 @@ class IssueCounterTest {
 
     Set<Map.Entry<String, Measure>> entries = measureRepository.getRawMeasures(FILE1).entrySet();
 
-    assertNewSoftwareQualityMeasures(SoftwareQuality.MAINTAINABILITY, getImpactMeasure(2, 1, 1, 0, 0, 0), entries);
-    assertNewSoftwareQualityMeasures(SoftwareQuality.RELIABILITY, getImpactMeasure(2, 0, 1, 1, 0, 0), entries);
+    assertNewSoftwareQualityMeasures(MAINTAINABILITY, getImpactMeasure(2, 1, 1, 0, 0, 0), entries);
+    assertNewSoftwareQualityMeasures(RELIABILITY, getImpactMeasure(2, 0, 1, 1, 0, 0), entries);
     assertNewSoftwareQualityMeasures(SoftwareQuality.SECURITY, getImpactMeasure(4, 2, 1, 1, 0, 0), entries);
   }
 
@@ -423,18 +576,18 @@ class IssueCounterTest {
   private static Map<String, Long> getImpactMeasure(long total, long high, long medium, long low, long info, long blocker) {
     Map<String, Long> map = getImpactMeasure(total, high, medium, low);
     map.put(INFO.name(), info);
-    map.put(Severity.BLOCKER.name(), blocker);
+    map.put(BLOCKER.name(), blocker);
     return map;
   }
 
   private void assertOverallSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map<? extends String, Long> expectedMap,
     Set<Map.Entry<String, Measure>> actualRaw) {
-    assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_METRIC_KEY);
+    assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_JSON_METRIC_KEY);
   }
 
   private void assertNewSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map<? extends String, Long> expectedMap,
     Set<Map.Entry<String, Measure>> actualRaw) {
-    assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_NEW_METRIC_KEY);
+    assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_NEW_JSON_METRIC_KEY);
   }
 
   private void assertSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map<? extends String, Long> expectedMap,
@@ -456,14 +609,14 @@ class IssueCounterTest {
     // created before -> existing issues with 2 high impact accepted (High and Blocker)
     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, HIGH));
     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, HIGH));
-    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, Severity.BLOCKER));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, Map.of(MAINTAINABILITY, BLOCKER, RELIABILITY, HIGH)));
     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, MEDIUM));
 
     // created after -> 2 high impact accepted
     underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, HIGH));
     underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, HIGH));
     underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, HIGH));
-    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, MEDIUM));
+    underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, Map.of(MAINTAINABILITY, MEDIUM, RELIABILITY, HIGH)));
     underTest.onIssue(FILE1, createNewSecurityHotspot());
     underTest.afterComponent(FILE1);
 
@@ -471,9 +624,9 @@ class IssueCounterTest {
     underTest.afterComponent(PROJECT);
 
     assertIntValue(FILE1, entry(VIOLATIONS_KEY, 2), entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 3),
-      entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 4));
+      entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 5));
     assertIntValue(PROJECT, entry(VIOLATIONS_KEY, 2), entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 3),
-      entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 4));
+      entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 5));
   }
 
   @Test
@@ -557,7 +710,7 @@ class IssueCounterTest {
   }
 
   private DefaultIssue createNewIssue(@Nullable String resolution, String status, Severity impactSeverity) {
-    return createNewIssue(resolution, status, SoftwareQuality.MAINTAINABILITY, impactSeverity);
+    return createNewIssue(resolution, status, MAINTAINABILITY, impactSeverity);
   }
 
   private DefaultIssue createNewIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality,
@@ -567,6 +720,12 @@ class IssueCounterTest {
     return issue;
   }
 
+  private DefaultIssue createNewIssue(@Nullable String resolution, String status, Map<SoftwareQuality, Severity> impaxts) {
+    DefaultIssue issue = createNewIssue(resolution, status, MAJOR, CODE_SMELL);
+    issue.replaceImpacts(impaxts);
+    return issue;
+  }
+
   private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
     DefaultIssue issue = createIssue(resolution, status, severity, ruleType);
     when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(true);
@@ -578,7 +737,7 @@ class IssueCounterTest {
   }
 
   private static DefaultIssue createIssue(@Nullable String resolution, String status, Severity impactSeverity) {
-    return createIssue(resolution, status, SoftwareQuality.MAINTAINABILITY, impactSeverity);
+    return createIssue(resolution, status, MAINTAINABILITY, impactSeverity);
   }
 
   private static DefaultIssue createIssue(@Nullable String resolution, String status, SoftwareQuality softwareQuality,
@@ -588,6 +747,12 @@ class IssueCounterTest {
     return issue;
   }
 
+  private static DefaultIssue createIssue(@Nullable String resolution, String status, Map<SoftwareQuality, Severity> impacts) {
+    DefaultIssue issue = createIssue(resolution, status, MAJOR, CODE_SMELL);
+    issue.replaceImpacts(impacts);
+    return issue;
+  }
+
   private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
     return new DefaultIssue()
       .setKey(String.valueOf(++issueCounter))
index 13195fb952eaf036e2ff9394f6443b1e1ccd9605..ef3a44abbb1e50c703cb88cc49771a0bf84f1ef6 100644 (file)
@@ -75,7 +75,9 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN;
 import static org.sonar.api.issue.Issue.STATUS_REOPENED;
 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
+import static org.sonar.api.issue.impact.Severity.BLOCKER;
 import static org.sonar.api.issue.impact.Severity.HIGH;
+import static org.sonar.api.issue.impact.Severity.INFO;
 import static org.sonar.api.issue.impact.Severity.LOW;
 import static org.sonar.api.issue.impact.Severity.MEDIUM;
 import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
@@ -631,6 +633,58 @@ class IssueDaoIT {
 
   }
 
+  @ParameterizedTest
+  @ValueSource(booleans = {true, false})
+  void selectIssueImpactSeverityGroupsByComponent_shouldReturnImpactSeverityGroups(boolean inLeak) {
+
+    ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN).setEffort(60L).replaceAllImpacts(List.of(createImpact(RELIABILITY, BLOCKER), createImpact(SECURITY,
+        BLOCKER))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN).setEffort(60L).replaceAllImpacts(List.of(createImpact(RELIABILITY, BLOCKER))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_REOPENED).setEffort(60L).replaceAllImpacts(List.of(createImpact(RELIABILITY, INFO))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_REOPENED).setEffort(60L).replaceAllImpacts(List.of(createImpact(RELIABILITY, INFO), createImpact(SECURITY,
+        BLOCKER))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_RESOLVED).setEffort(60L).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(MAINTAINABILITY, HIGH), createImpact(RELIABILITY, INFO), createImpact(SECURITY, BLOCKER))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_RESOLVED).setEffort(60L).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY
+        , HIGH))));
+    // issues in ignored status
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(Issue.STATUS_CLOSED).setEffort(60L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
+    db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_RESOLVED).setEffort(60L).setResolution(RESOLUTION_FALSE_POSITIVE).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
+
+    Collection<IssueImpactSeverityGroupDto> result = underTest.selectIssueImpactSeverityGroupsByComponent(db.getSession(), file, inLeak ?
+      1L : Long.MAX_VALUE);
+
+    assertThat(result).hasSize(6);
+    assertThat(result.stream().filter(IssueImpactSeverityGroupDto::isInLeak)).hasSize(inLeak ? 6 : 0);
+    // 6 issues, but 1 has 2 different severity impact, and 1 has 3 different severity impact
+    // The total count should then be 6 + 1 (1 additional severity for 1 issue) + 2 (2 additional severities from 1 issue)
+    assertThat(result.stream().mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(6 + 1 + 2);
+
+    assertThat(result.stream().filter(g -> g.getSeverity() == BLOCKER).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(4);
+    assertThat(result.stream().filter(g -> g.getSeverity() == HIGH).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(2);
+    assertThat(result.stream().filter(g -> g.getSeverity() == MEDIUM).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isZero();
+    assertThat(result.stream().filter(g -> g.getSeverity() == LOW).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isZero();
+    assertThat(result.stream().filter(g -> g.getSeverity() == INFO).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(3);
+
+    assertThat(result.stream().filter(g -> RESOLUTION_WONT_FIX.equals(g.getResolution())).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(4);
+    assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueImpactSeverityGroupDto::getCount).sum()).isEqualTo(5);
+
+    assertThat(result.stream().noneMatch(g -> STATUS_CLOSED.equals(g.getResolution()))).isTrue();
+    assertThat(result.stream().noneMatch(g -> RESOLUTION_FALSE_POSITIVE.equals(g.getResolution()))).isTrue();
+    assertThat(result.stream().noneMatch(g -> MEDIUM == g.getSeverity())).isTrue();
+
+  }
+
   @Test
   void selectIssueImpactGroupsByComponent_whenNewCodeFromReferenceBranch_shouldReturnImpactGroups() {
 
index e533bf62a1b33033e3be554f8751eb58dfd79182..a292a26d1f973e2068e9032c9516d5da311b8d20 100644 (file)
@@ -94,6 +94,11 @@ public class IssueDao implements Dao {
     return mapper(dbSession).selectIssueImpactGroupsByComponent(component, leakPeriodBeginningDate);
   }
 
+  public Collection<IssueImpactSeverityGroupDto> selectIssueImpactSeverityGroupsByComponent(DbSession dbSession, ComponentDto component,
+    long leakPeriodBeginningDate) {
+    return mapper(dbSession).selectIssueImpactSeverityGroupsByComponent(component, leakPeriodBeginningDate);
+  }
+
   public Cursor<IndexedIssueDto> scrollIssuesForIndexation(DbSession dbSession, @Nullable @Param("branchUuid") String branchUuid,
     @Nullable @Param("issueKeys") Collection<String> issueKeys) {
     return mapper(dbSession).scrollIssuesForIndexation(branchUuid, issueKeys);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactSeverityGroupDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactSeverityGroupDto.java
new file mode 100644 (file)
index 0000000..9cfa17c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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.db.issue;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.issue.impact.Severity;
+
+public class IssueImpactSeverityGroupDto {
+
+  private String resolution;
+  private Severity severity;
+  private long count;
+  private boolean inLeak;
+
+  public IssueImpactSeverityGroupDto() {
+    // nothing to do
+  }
+
+  public boolean isInLeak() {
+    return inLeak;
+  }
+
+  public void setInLeak(boolean inLeak) {
+    this.inLeak = inLeak;
+  }
+
+  @CheckForNull
+  public String getResolution() {
+    return resolution;
+  }
+
+  public void setResolution(@Nullable String resolution) {
+    this.resolution = resolution;
+  }
+
+  public long getCount() {
+    return count;
+  }
+
+  public void setCount(long count) {
+    this.count = count;
+  }
+
+  public Severity getSeverity() {
+    return severity;
+  }
+
+  public void setSeverity(Severity severity) {
+    this.severity = severity;
+  }
+
+}
index b1b3d7f760d8fc0c1a6f66af83fd79be1a8c22ea..c1c5b9726ce082db4526d0c12b12aae14b6d980f 100644 (file)
@@ -76,6 +76,9 @@ public interface IssueMapper {
 
   Collection<IssueImpactGroupDto> selectIssueImpactGroupsByComponent(@Param("component") ComponentDto component, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
 
+  Collection<IssueImpactSeverityGroupDto> selectIssueImpactSeverityGroupsByComponent(@Param("component") ComponentDto component,
+    @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
+
   List<IssueDto> selectByBranch(@Param("keys") Set<String> keys, @Nullable @Param("changedSince") Long changedSince);
 
   List<String> selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams);
index b8f3a77b8d67ae3cae8c21e092f4e8bd6b385ca6..0dbe81a387c4f9e63843361c4a78e71d6c1c6480 100644 (file)
     group by i2.status, i2.resolution, i2.software_quality, i2.severity, i2.inLeak
   </select>
 
+  <select id="selectIssueImpactSeverityGroupsByComponent" resultType="org.sonar.db.issue.IssueImpactSeverityGroupDto" parameterType="map">
+    select
+      i2.status as status,
+      i2.resolution as resolution,
+      i2.severity as severity,
+      count(i2.kee) as "count",
+      i2.inLeak as inLeak
+      from (
+        <!-- We need to use distinct keyword to not count twice issues with same impact severity on multiple software qualities -->
+        <!-- Sample RELIABILITY=BLOCKER and SECURITY=BLOCKER will be counted as 2 without the distinct keyword -->
+        select distinct
+          i.status,
+          i.resolution,
+          i.kee,
+          ii.severity,
+          <if test="leakPeriodBeginningDate &gt;= 0">
+            case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then ${_true} else ${_false} end as inLeak
+          </if>
+          <if test="leakPeriodBeginningDate &lt; 0">
+            case when n.uuid is null then  ${_false} else ${_true} end as inLeak
+          </if>
+          from issues i
+            inner join issues_impacts ii on i.kee = ii.issue_key
+          <if test="leakPeriodBeginningDate &lt; 0">
+            left join new_code_reference_issues n on n.issue_key = i.kee
+          </if>
+          where i.status &lt;&gt; 'CLOSED'
+          and (i.resolution is null or i.resolution = 'WONTFIX')
+          and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
+      ) i2
+    group by i2.status, i2.resolution, i2.severity, i2.inLeak
+  </select>
+
   <select id="selectIssueKeysByComponentUuid" parameterType="string" resultType="string">
     select
       i.kee
index d148936d19224a76f4e0daad9c3feb4ef66e3481..a4372cf8fbb4c3548df066aa55c6f0a00ad9dace 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.api.issue.impact.SoftwareQuality;
 import org.sonar.api.rules.RuleType;
 import org.sonar.db.issue.IssueGroupDto;
 import org.sonar.db.issue.IssueImpactGroupDto;
+import org.sonar.db.issue.IssueImpactSeverityGroupDto;
 import org.sonar.db.rule.SeverityUtil;
 import org.sonar.server.measure.ImpactMeasureBuilder;
 
@@ -43,6 +44,7 @@ class IssueCounter {
   private final Map<RuleType, HighestSeverity> highestSeverityOfUnresolved = new EnumMap<>(RuleType.class);
   private final Map<RuleType, Effort> effortOfUnresolved = new EnumMap<>(RuleType.class);
   private final Map<String, Count> unresolvedBySeverity = new HashMap<>();
+  private final Map<Severity, Count> unresolvedByImpactSeverity = new EnumMap<>(Severity.class);
   private final Map<RuleType, Count> unresolvedByType = new EnumMap<>(RuleType.class);
   private final Map<String, Count> byResolution = new HashMap<>();
   private final Map<String, Count> byStatus = new HashMap<>();
@@ -54,7 +56,8 @@ class IssueCounter {
   private final Map<SoftwareQuality, Effort> effortOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class);
   private final Map<SoftwareQuality, HighestImpactSeverity> highestSeverityOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class);
 
-  IssueCounter(Collection<IssueGroupDto> groups, Collection<IssueImpactGroupDto> impactGroups) {
+  IssueCounter(Collection<IssueGroupDto> groups, Collection<IssueImpactGroupDto> impactGroups,
+    Collection<IssueImpactSeverityGroupDto> impactSeverityGroups) {
     for (IssueGroupDto group : groups) {
       if (RuleType.valueOf(group.getRuleType()).equals(SECURITY_HOTSPOT)) {
         processHotspotGroup(group);
@@ -65,6 +68,10 @@ class IssueCounter {
     for (IssueImpactGroupDto group : impactGroups) {
       processImpactGroup(group);
     }
+
+    for (IssueImpactSeverityGroupDto group : impactSeverityGroups) {
+      processImpactSeverityGroup(group);
+    }
   }
 
   private void processHotspotGroup(IssueGroupDto group) {
@@ -135,7 +142,12 @@ class IssueCounter {
     }
   }
 
-  public Optional<String> getHighestSeverityOfUnresolved(RuleType ruleType, boolean onlyInLeak) {
+  private void processImpactSeverityGroup(IssueImpactSeverityGroupDto group) {
+    if (group.getResolution() == null) {
+      unresolvedByImpactSeverity.computeIfAbsent(group.getSeverity(), k -> new Count()).add(group);
+    }
+  }
+    public Optional<String> getHighestSeverityOfUnresolved(RuleType ruleType, boolean onlyInLeak) {
     return Optional.ofNullable(highestSeverityOfUnresolved.get(ruleType))
       .map(hs -> hs.severity(onlyInLeak));
   }
@@ -165,6 +177,10 @@ class IssueCounter {
     return value(unresolvedBySeverity.get(severity), onlyInLeak);
   }
 
+  public long countUnresolvedByImpactSeverity(Severity severity, boolean onlyInLeak) {
+    return value(unresolvedByImpactSeverity.get(severity), onlyInLeak);
+  }
+
   public long countByResolution(String resolution, boolean onlyInLeak) {
     return value(byResolution.get(resolution), onlyInLeak);
   }
@@ -200,7 +216,15 @@ class IssueCounter {
     return onlyInLeak ? count.leak : count.absolute;
   }
 
-  public String getBySoftwareQuality(SoftwareQuality softwareQuality, boolean onlyInLeak) {
+  public long countBySoftwareQuality(SoftwareQuality softwareQuality, boolean onlyInLeak) {
+    Map<Severity, Count> severityToCount = bySoftwareQualityAndSeverity.get(softwareQuality);
+    if (severityToCount == null) {
+      return 0;
+    }
+    return severityToCount.values().stream().mapToLong(count -> value(count, onlyInLeak)).sum();
+  }
+
+  public String getImpactJsonBySoftwareQuality(SoftwareQuality softwareQuality, boolean onlyInLeak) {
     Map<Severity, Count> severityToCount = bySoftwareQualityAndSeverity.get(softwareQuality);
 
     ImpactMeasureBuilder impactMeasureBuilder;
@@ -234,6 +258,13 @@ class IssueCounter {
         leak += group.getCount();
       }
     }
+
+    public void add(IssueImpactSeverityGroupDto group) {
+      absolute += group.getCount();
+      if (group.isInLeak()) {
+        leak += group.getCount();
+      }
+    }
   }
 
   private static class Effort {
index f8b8c0aa9b5ded209aae19d9dc221da55cb7ff80..9f250e4b24ed08b969391c51dfb9aee340f076b5 100644 (file)
@@ -87,8 +87,11 @@ public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater {
     FormulaContextImpl context = new FormulaContextImpl(matrix, components, debtRatingGrid);
 
     components.getSortedTree().forEach(c -> {
-      IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByComponent(dbSession, c, beginningOfLeak),
-        dbClient.issueDao().selectIssueImpactGroupsByComponent(dbSession, c, beginningOfLeak));
+      IssueCounter issueCounter = new IssueCounter(
+        dbClient.issueDao().selectIssueGroupsByComponent(dbSession, c, beginningOfLeak),
+        dbClient.issueDao().selectIssueImpactGroupsByComponent(dbSession, c, beginningOfLeak),
+        dbClient.issueDao().selectIssueImpactSeverityGroupsByComponent(dbSession, c, beginningOfLeak)
+        );
       for (MeasureUpdateFormula formula : formulaFactory.getFormulas()) {
         if (shouldComputeMetric(formula, useLeakFormulas, components.getBranch(), matrix)) {
           context.change(c, formula);
index b857f21915dae0d6c7a21f020339e5a1d9bf9a41..0ca55ba42f3f03561869488c03163b0037b6c939 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
 import org.sonar.server.measure.ImpactMeasureBuilder;
 import org.sonar.server.measure.Rating;
 
@@ -81,22 +82,22 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact
       (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, false))),
 
     new MeasureUpdateFormula(CoreMetrics.RELIABILITY_ISSUES, false, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.RELIABILITY, false))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.RELIABILITY, false))),
 
     new MeasureUpdateFormula(CoreMetrics.MAINTAINABILITY_ISSUES, false, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, false))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, false))),
 
     new MeasureUpdateFormula(CoreMetrics.SECURITY_ISSUES, false, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.SECURITY, false))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.SECURITY, false))),
 
     new MeasureUpdateFormula(CoreMetrics.NEW_RELIABILITY_ISSUES, true, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.RELIABILITY, true))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.RELIABILITY, true))),
 
     new MeasureUpdateFormula(CoreMetrics.NEW_MAINTAINABILITY_ISSUES, true, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, true))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, true))),
 
     new MeasureUpdateFormula(CoreMetrics.NEW_SECURITY_ISSUES, true, true, new ImpactAddChildren(),
-      (context, issues) -> context.setValue(issues.getBySoftwareQuality(SoftwareQuality.SECURITY, true))),
+      (context, issues) -> context.setValue(issues.getImpactJsonBySoftwareQuality(SoftwareQuality.SECURITY, true))),
 
     new MeasureUpdateFormula(CoreMetrics.VIOLATIONS, false, new AddChildren(),
       (context, issues) -> context.setValue(issues.countUnresolved(false))),
@@ -279,6 +280,54 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact
       asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)),
 
     // Metrics based on Software Qualities
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.BLOCKER, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.HIGH, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.MEDIUM, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.LOW, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.INFO, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.BLOCKER, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.HIGH, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.MEDIUM, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.LOW, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countUnresolvedByImpactSeverity(org.sonar.api.issue.impact.Severity.INFO, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.RELIABILITY, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.SECURITY, false))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.RELIABILITY, true))),
+
+    new MeasureUpdateFormula(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countBySoftwareQuality(SoftwareQuality.SECURITY, true))),
+
     new MeasureUpdateFormula(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, false, true, new AddChildren(),
       (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, false))),
 
index 43db7c44d63fce17f8500d41d146f3ca145a8157..55a82844cc90692cd3e99637414f45cd3f24b521 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.core.metric.SoftwareQualitiesMetrics;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.IssueGroupDto;
 import org.sonar.db.issue.IssueImpactGroupDto;
+import org.sonar.db.issue.IssueImpactSeverityGroupDto;
 import org.sonar.server.measure.DebtRatingGrid;
 import org.sonar.server.measure.Rating;
 
@@ -1200,6 +1201,122 @@ class MeasureUpdateFormulaFactoryImplTest {
         .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 8 + 2);
   }
 
+  @Test
+  void compute_shouldComputeCountPerImpactSeverityOnOverallCode() {
+    withNoIssues()
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES, 0);
+
+    with(
+      newImpactSeverityGroup(HIGH, 3),
+      newImpactSeverityGroup(MEDIUM, 4),
+      newImpactSeverityGroup(LOW, 1),
+      newImpactSeverityGroup(LOW, 1),
+      newImpactSeverityGroup(BLOCKER, 7),
+      newImpactSeverityGroup(INFO, 12),
+      newImpactSeverityGroup(INFO, 3),
+
+      // Should not be counted due to status
+      newImpactSeverityGroup(HIGH, Issue.STATUS_RESOLVED, 4),
+      newImpactSeverityGroup(BLOCKER, Issue.STATUS_RESOLVED, 4),
+      newImpactSeverityGroup(BLOCKER, Issue.STATUS_RESOLVED, 4))
+
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES, 7)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES, 3)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES, 4)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES, 1 + 1)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES, 12 + 3)
+    ;
+  }
+
+  @Test
+  void compute_shouldComputeCountPerImpactSeverityOnNewCode() {
+    withNoIssues()
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES, 0);
+
+    with(
+      newImpactSeverityGroup(HIGH, 3, true),
+      newImpactSeverityGroup(MEDIUM, 4, true),
+      newImpactSeverityGroup(LOW, 1, true),
+      newImpactSeverityGroup(LOW, 1, true),
+      newImpactSeverityGroup(BLOCKER, 7, true),
+      newImpactSeverityGroup(INFO, 12, true),
+      newImpactSeverityGroup(INFO, 3, true),
+
+      // Should not be counted due to status
+      newImpactSeverityGroup(HIGH, Issue.RESOLUTION_WONT_FIX, 4, true),
+      newImpactSeverityGroup(BLOCKER, Issue.RESOLUTION_WONT_FIX, 4, true),
+      newImpactSeverityGroup(BLOCKER, Issue.RESOLUTION_FALSE_POSITIVE, 4, true),
+
+      //Should not be counted because on overall code
+      newImpactSeverityGroup(BLOCKER, 7, false))
+
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES, 7)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES, 3)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES, 4)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES, 1 + 1)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES, 12 + 3)
+    ;
+  }
+
+  @Test
+  void compute_shouldComputeCountPerSoftwareQualityOnOverallCode() {
+    withNoIssues()
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES, 0)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES, 0);
+
+    with(
+      newImpactGroup(RELIABILITY, HIGH, 3),
+      newImpactGroup(RELIABILITY, MEDIUM, 1, true),
+      newImpactGroup(SECURITY, MEDIUM, 1),
+      newImpactGroup(MAINTAINABILITY, MEDIUM, 1),
+      newImpactGroup(MAINTAINABILITY, HIGH, 1, true),
+      // Should not count due to status
+      newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 4, 1d, false),
+      newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false),
+      newImpactGroup(MAINTAINABILITY, MEDIUM, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false))
+
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, 1 + 1)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES, 3 + 1)
+      .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES, 1)
+    ;
+  }
+
+  @Test
+  void compute_shouldComputeCountPerSoftwareQualityOnNewCode() {
+    withNoIssues()
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES, 0)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES, 0);
+
+    with(
+      newImpactGroup(RELIABILITY, HIGH, 3, true),
+      newImpactGroup(RELIABILITY, MEDIUM, 1, true),
+      newImpactGroup(SECURITY, MEDIUM, 1, true),
+      newImpactGroup(SECURITY, MEDIUM, 12, true),
+      newImpactGroup(MAINTAINABILITY, MEDIUM, 1, true),
+      newImpactGroup(MAINTAINABILITY, HIGH, 1, true),
+      // Should not count due to status
+      newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 4, 1d, false),
+      newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false),
+      newImpactGroup(MAINTAINABILITY, MEDIUM, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false),
+      // Should not be counted because on overall code
+      newImpactGroup(MAINTAINABILITY, MEDIUM, 1, false))
+
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES, 1 + 1)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES, 3 + 1)
+      .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES, 1 + 12)
+    ;
+  }
+
   @Test
   void compute_shouldComputeRemediationEffortBasedOnSoftwareQuality() {
     withNoIssues()
@@ -1276,6 +1393,10 @@ class MeasureUpdateFormulaFactoryImplTest {
     return new Verifier(groups);
   }
 
+  private Verifier with(IssueImpactSeverityGroupDto... groups) {
+    return new Verifier(groups);
+  }
+
   private Verifier withNoIssues() {
     return new Verifier(new IssueGroupDto[0]);
   }
@@ -1291,6 +1412,7 @@ class MeasureUpdateFormulaFactoryImplTest {
   private class Verifier {
     private IssueGroupDto[] groups = {};
     private IssueImpactGroupDto[] impactGroups = {};
+    private IssueImpactSeverityGroupDto[] impactSeverityGroups = {};
     private final InitialValues initialValues = new InitialValues();
 
     private Verifier(IssueGroupDto[] groups) {
@@ -1301,6 +1423,10 @@ class MeasureUpdateFormulaFactoryImplTest {
       this.impactGroups = impactGroups;
     }
 
+    private Verifier(IssueImpactSeverityGroupDto[] impactSeverityGroups) {
+      this.impactSeverityGroups = impactSeverityGroups;
+    }
+
     Verifier and(Metric metric, double value) {
       this.initialValues.values.put(metric, value);
       return this;
@@ -1366,13 +1492,14 @@ class MeasureUpdateFormulaFactoryImplTest {
         .get();
       assertThat(formula.isOnLeak()).isEqualTo(expectLeakFormula);
       TestContext context = new TestContext(formula.getDependentMetrics(), initialValues);
-      formula.compute(context, newIssueCounter(groups, impactGroups));
+      formula.compute(context, newIssueCounter(groups, impactGroups, impactSeverityGroups));
       return context;
     }
   }
 
-  private static IssueCounter newIssueCounter(IssueGroupDto[] groups, IssueImpactGroupDto[] impactGroups) {
-    return new IssueCounter(asList(groups), asList(impactGroups));
+  private static IssueCounter newIssueCounter(IssueGroupDto[] groups, IssueImpactGroupDto[] impactGroups,
+    IssueImpactSeverityGroupDto[] impactSeverityGroups) {
+    return new IssueCounter(asList(groups), asList(impactGroups), asList(impactSeverityGroups));
   }
 
   private static IssueGroupDto newGroup() {
@@ -1404,6 +1531,16 @@ class MeasureUpdateFormulaFactoryImplTest {
     return dto;
   }
 
+  private static IssueImpactSeverityGroupDto newImpactSeverityGroup(org.sonar.api.issue.impact.Severity severity,
+    @Nullable String resolution, long count, boolean inLeak) {
+    IssueImpactSeverityGroupDto dto = new IssueImpactSeverityGroupDto();
+    dto.setSeverity(severity);
+    dto.setResolution(resolution);
+    dto.setCount(count);
+    dto.setInLeak(inLeak);
+    return dto;
+  }
+
   private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity,
     String status, @Nullable String resolution, long count) {
     return newImpactGroup(softwareQuality, severity, status, resolution, count, 0, false);
@@ -1429,6 +1566,20 @@ class MeasureUpdateFormulaFactoryImplTest {
     return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, effort, inLeak);
   }
 
+  private static IssueImpactSeverityGroupDto newImpactSeverityGroup(org.sonar.api.issue.impact.Severity severity, long count) {
+    return newImpactSeverityGroup(severity, null, count, false);
+  }
+
+  private static IssueImpactSeverityGroupDto newImpactSeverityGroup(org.sonar.api.issue.impact.Severity severity, long count,
+    boolean inLeak) {
+    return newImpactSeverityGroup(severity, null, count, inLeak);
+  }
+
+  private static IssueImpactSeverityGroupDto newImpactSeverityGroup(org.sonar.api.issue.impact.Severity severity,
+    @Nullable String resolution, long count) {
+    return newImpactSeverityGroup(severity, resolution, count, false);
+  }
+
   private static IssueGroupDto newResolvedGroup(RuleType ruleType) {
     return newGroup(ruleType).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_CLOSED);
   }