From 969c7f691192a9f3af6220ef85de973b303ed473 Mon Sep 17 00:00:00 2001 From: Dejan Milisavljevic Date: Mon, 28 Oct 2024 10:40:27 +0100 Subject: [PATCH] SONAR-23298 Compute new measures for project and live update --- .../projectanalysis/issue/IssueCounter.java | 83 +++++-- .../issue/IssueCounterTest.java | 223 +++++++++++++++--- .../java/org/sonar/db/issue/IssueDaoIT.java | 54 +++++ .../java/org/sonar/db/issue/IssueDao.java | 5 + .../db/issue/IssueImpactSeverityGroupDto.java | 70 ++++++ .../java/org/sonar/db/issue/IssueMapper.java | 3 + .../org/sonar/db/issue/IssueMapper.xml | 33 +++ .../server/measure/live/IssueCounter.java | 37 ++- .../live/LiveMeasureTreeUpdaterImpl.java | 7 +- .../live/MeasureUpdateFormulaFactoryImpl.java | 61 ++++- .../MeasureUpdateFormulaFactoryImplTest.java | 157 +++++++++++- 11 files changed, 665 insertions(+), 68 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactSeverityGroupDto.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java index 6732c26d0ae..d0111290f1d 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCounter.java @@ -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; *
  • issues per status (open, reopen, confirmed)
  • *
  • issues per resolution (unresolved, false-positives, won't fix)
  • *
  • issues per severity (from info to blocker)
  • + *
  • issues per impact severity (from info to blocker)
  • *
  • issues per type (code smell, bug, vulnerability, security hotspots)
  • *
  • issues per impact
  • * @@ -111,16 +113,40 @@ public class IssueCounter extends IssueVisitor { MINOR, NEW_MINOR_VIOLATIONS_KEY, INFO, NEW_INFO_VIOLATIONS_KEY); - static final Map IMPACT_TO_METRIC_KEY = Map.of( + private static final Map 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 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 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 IMPACT_TO_NEW_METRIC_KEY = Map.of( + static final Map 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 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 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 TYPE_TO_METRIC_KEY = ImmutableMap.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 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> 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 void addMeasures(Component component, Map metrics, Multiset countPerMetric) { + for (Map.Entry 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 entry : SEVERITY_TO_NEW_METRIC_KEY.entrySet()) { - String severity = entry.getKey(); - String metricKey = entry.getValue(); - Multiset 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 entry : TYPE_TO_NEW_METRIC_KEY.entrySet()) { - RuleType type = entry.getKey(); - String metricKey = entry.getValue(); - Multiset 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> 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 severityBag = HashMultiset.create(); + private final Multiset 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 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); } } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java index e975fbffcd5..224596dd769 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCounterTest.java @@ -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> 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> 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 getImpactMeasure(long total, long high, long medium, long low, long info, long blocker) { Map 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 expectedMap, Set> actualRaw) { - assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_METRIC_KEY); + assertSoftwareQualityMeasures(softwareQuality, expectedMap, actualRaw, IMPACT_TO_JSON_METRIC_KEY); } private void assertNewSoftwareQualityMeasures(SoftwareQuality softwareQuality, Map expectedMap, Set> 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 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 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 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)) diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java index 13195fb952e..ef3a44abbb1 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java @@ -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 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() { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java index e533bf62a1b..a292a26d1f9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java @@ -94,6 +94,11 @@ public class IssueDao implements Dao { return mapper(dbSession).selectIssueImpactGroupsByComponent(component, leakPeriodBeginningDate); } + public Collection selectIssueImpactSeverityGroupsByComponent(DbSession dbSession, ComponentDto component, + long leakPeriodBeginningDate) { + return mapper(dbSession).selectIssueImpactSeverityGroupsByComponent(component, leakPeriodBeginningDate); + } + public Cursor scrollIssuesForIndexation(DbSession dbSession, @Nullable @Param("branchUuid") String branchUuid, @Nullable @Param("issueKeys") Collection 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 index 00000000000..9cfa17c65fa --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactSeverityGroupDto.java @@ -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; + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java index b1b3d7f760d..c1c5b9726ce 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java @@ -76,6 +76,9 @@ public interface IssueMapper { Collection selectIssueImpactGroupsByComponent(@Param("component") ComponentDto component, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate); + Collection selectIssueImpactSeverityGroupsByComponent(@Param("component") ComponentDto component, + @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate); + List selectByBranch(@Param("keys") Set keys, @Nullable @Param("changedSince") Long changedSince); List selectRecentlyClosedIssues(@Param("queryParams") IssueQueryParams issueQueryParams); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml index b8f3a77b8d6..0dbe81a387c 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml @@ -576,6 +576,39 @@ group by i2.status, i2.resolution, i2.software_quality, i2.severity, i2.inLeak + +