From aa47246ac578b6886649fc173450073920dcb216 Mon Sep 17 00:00:00 2001 From: Eric Giffon Date: Thu, 21 Dec 2023 12:53:35 +0100 Subject: [PATCH] SONAR-21273 Compute metrics new_accepted_issues and high_impact_accepted_issues --- gradle.properties | 2 +- .../projectanalysis/issue/IssueCounter.java | 11 ++ .../issue/IssueCounterTest.java | 103 +++++++++++++++--- .../java/org/sonar/db/issue/IssueDaoIT.java | 37 +++++-- .../org/sonar/db/issue/IssueGroupDto.java | 14 +++ .../org/sonar/db/issue/IssueMapper.xml | 93 ++++++++++------ .../server/measure/live/IssueCounter.java | 87 +++++++++------ .../live/MeasureUpdateFormulaFactoryImpl.java | 6 + .../MeasureUpdateFormulaFactoryImplTest.java | 39 +++++++ .../resources/org/sonar/l10n/core.properties | 14 +-- 10 files changed, 301 insertions(+), 105 deletions(-) diff --git a/gradle.properties b/gradle.properties index 923e9b7bda1..9d0fc0a31ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group=org.sonarsource.sonarqube version=10.4 -pluginApiVersion=10.4.0.2040 +pluginApiVersion=10.4.0.2048 description=Open source platform for continuous inspection of code quality projectTitle=SonarQube org.gradle.jvmargs=-Xmx2048m 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 8f855709a18..4cc9c893963 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 @@ -26,6 +26,7 @@ import com.google.common.collect.Multiset; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; +import org.sonar.api.issue.impact.Severity; import org.sonar.api.rules.RuleType; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.measure.Measure; @@ -45,9 +46,11 @@ import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY; import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY; import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES_KEY; import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_ACCEPTED_ISSUES_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY; @@ -169,6 +172,7 @@ public class IssueCounter extends IssueVisitor { addMeasure(component, CONFIRMED_ISSUES_KEY, currentCounters.counter().confirmed); addMeasure(component, FALSE_POSITIVE_ISSUES_KEY, currentCounters.counter().falsePositives); addMeasure(component, ACCEPTED_ISSUES_KEY, currentCounters.counter().accepted); + addMeasure(component, HIGH_IMPACT_ACCEPTED_ISSUES_KEY, currentCounters.counter().highImpactAccepted); } private void addMeasuresByType(Component component) { @@ -209,6 +213,8 @@ public class IssueCounter extends IssueVisitor { measureRepository.add(component, metric, Measure.newMeasureBuilder() .create(bag.count(type))); } + + addMeasure(component, NEW_ACCEPTED_ISSUES_KEY, currentCounters.counterForPeriod().accepted); } /** @@ -221,6 +227,7 @@ public class IssueCounter extends IssueVisitor { private int confirmed = 0; private int falsePositives = 0; private int accepted = 0; + private int highImpactAccepted = 0; private final Multiset severityBag = HashMultiset.create(); private final EnumMultiset typeBag = EnumMultiset.create(RuleType.class); @@ -231,6 +238,7 @@ public class IssueCounter extends IssueVisitor { confirmed += counter.confirmed; falsePositives += counter.falsePositives; accepted += counter.accepted; + highImpactAccepted += counter.highImpactAccepted; severityBag.addAll(counter.severityBag); typeBag.addAll(counter.typeBag); } @@ -250,6 +258,9 @@ public class IssueCounter extends IssueVisitor { falsePositives++; } else if (IssueStatus.ACCEPTED.equals(issue.getIssueStatus())) { accepted++; + if (issue.impacts().values().stream().anyMatch(severity -> severity == Severity.HIGH)) { + highImpactAccepted++; + } } switch (issue.status()) { case STATUS_OPEN: 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 cd8b385e34a..09ead892565 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 @@ -26,6 +26,8 @@ import javax.annotation.Nullable; import org.assertj.core.data.MapEntry; import org.junit.Rule; import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; import org.sonar.ce.task.projectanalysis.component.Component; @@ -50,6 +52,8 @@ 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.HIGH; +import static org.sonar.api.issue.impact.Severity.MEDIUM; 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; @@ -64,10 +68,14 @@ import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES; import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY; +import static org.sonar.api.measures.CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES; +import static org.sonar.api.measures.CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES_KEY; import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS; +import static org.sonar.api.measures.CoreMetrics.NEW_ACCEPTED_ISSUES; +import static org.sonar.api.measures.CoreMetrics.NEW_ACCEPTED_ISSUES_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_BUGS; @@ -95,11 +103,12 @@ import static org.sonar.api.measures.CoreMetrics.VIOLATIONS; import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY; import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES; import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY; -import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES; -import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES_KEY; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.api.rule.Severity.CRITICAL; import static org.sonar.api.rule.Severity.MAJOR; +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.measure.Measure.newMeasureBuilder; import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf; @@ -143,7 +152,9 @@ public class IssueCounterTest { .add(NEW_CODE_SMELLS) .add(NEW_BUGS) .add(NEW_VULNERABILITIES) - .add(NEW_SECURITY_HOTSPOTS); + .add(NEW_SECURITY_HOTSPOTS) + .add(NEW_ACCEPTED_ISSUES) + .add(HIGH_IMPACT_ACCEPTED_ISSUES); @Rule public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); @@ -237,13 +248,13 @@ public class IssueCounterTest { // bottom-up traversal -> from files to project // file1 : one open code smell, one closed code smell (which will be excluded from metric) underTest.beforeComponent(FILE1); - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL)); - underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.CODE_SMELL)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(CODE_SMELL)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(CODE_SMELL)); underTest.afterComponent(FILE1); // file2 : one bug underTest.beforeComponent(FILE2); - underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER).setType(RuleType.BUG)); + underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER).setType(BUG)); underTest.afterComponent(FILE2); // file3 : one unresolved security hotspot @@ -267,13 +278,13 @@ public class IssueCounterTest { underTest.beforeComponent(FILE1); // created before -> existing issues (so ignored) - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL)); - underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.BUG)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(CODE_SMELL)); + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(BUG)); // created after -> 4 new issues but 1 is closed - underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.CODE_SMELL)); - underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.BUG)); - underTest.onIssue(FILE1, createNewIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.BUG)); + underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(CODE_SMELL)); + underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(BUG)); + underTest.onIssue(FILE1, createNewIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(BUG)); underTest.onIssue(FILE1, createNewSecurityHotspot()); underTest.onIssue(FILE1, createNewSecurityHotspot().setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED)); underTest.afterComponent(FILE1); @@ -290,6 +301,56 @@ public class IssueCounterTest { entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1)); } + @Test + public void count_new_accepted_issues() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + + underTest.beforeComponent(FILE1); + // created before -> existing issues (so ignored) + underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL)); + underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, CRITICAL)); + + // created after -> 2 accepted, 1 open, 1 hotspot + underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL)); + underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, CRITICAL)); + underTest.onIssue(FILE1, createNewIssue(RESOLUTION_WONT_FIX, STATUS_RESOLVED, CRITICAL)); + underTest.onIssue(FILE1, createNewSecurityHotspot()); + underTest.afterComponent(FILE1); + + underTest.beforeComponent(PROJECT); + underTest.afterComponent(PROJECT); + + assertValues(FILE1, entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 2), entry(NEW_SECURITY_HOTSPOTS_KEY, 1)); + assertValues(PROJECT, entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 2), entry(NEW_SECURITY_HOTSPOTS_KEY, 1)); + } + + @Test + public void count_high_impact_accepted_issues() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + + underTest.beforeComponent(FILE1); + // created before -> existing issues with 1 high impact accepted + 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, 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, createNewSecurityHotspot()); + underTest.afterComponent(FILE1); + + underTest.beforeComponent(PROJECT); + underTest.afterComponent(PROJECT); + + assertValues(FILE1, entry(VIOLATIONS_KEY, 2), entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 3), + entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 3)); + assertValues(PROJECT, entry(VIOLATIONS_KEY, 2), entry(NEW_VIOLATIONS_KEY, 1), entry(NEW_ACCEPTED_ISSUES_KEY, 3), + entry(HIGH_IMPACT_ACCEPTED_ISSUES_KEY, 3)); + } + @Test public void exclude_hotspots_from_issue_counts() { // bottom-up traversal -> from files to project @@ -364,7 +425,13 @@ public class IssueCounterTest { } private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity) { - return createNewIssue(resolution, status, severity, RuleType.CODE_SMELL); + return createNewIssue(resolution, status, severity, CODE_SMELL); + } + + private DefaultIssue createNewIssue(@Nullable String resolution, String status, Severity impactSeverity) { + DefaultIssue issue = createNewIssue(resolution, status, MAJOR, CODE_SMELL); + issue.addImpact(SoftwareQuality.MAINTAINABILITY, impactSeverity); + return issue; } private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) { @@ -374,7 +441,13 @@ public class IssueCounterTest { } private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) { - return createIssue(resolution, status, severity, RuleType.CODE_SMELL); + return createIssue(resolution, status, severity, CODE_SMELL); + } + + private static DefaultIssue createIssue(@Nullable String resolution, String status, Severity impactSeverity) { + DefaultIssue issue = createIssue(resolution, status, MAJOR, CODE_SMELL); + issue.addImpact(SoftwareQuality.MAINTAINABILITY, impactSeverity); + return issue; } private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) { @@ -385,10 +458,10 @@ public class IssueCounterTest { } private static DefaultIssue createSecurityHotspot() { - return createIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT); + return createIssue(null, STATUS_OPEN, "MAJOR", SECURITY_HOTSPOT); } private DefaultIssue createNewSecurityHotspot() { - return createNewIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT); + return createNewIssue(null, STATUS_OPEN, "MAJOR", SECURITY_HOTSPOT); } } 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 f35447534c8..7ae2f4eb5fa 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 @@ -30,6 +30,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.ibatis.cursor.Cursor; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -39,7 +40,6 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactoryFast; -import org.sonar.core.util.Uuids; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.Pagination; @@ -465,17 +465,22 @@ public class IssueDaoIT { ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project)); RuleDto rule = db.rules().insert(); db.issues().insert(rule, project, file, - i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG).setIssueCreationTime(1_500L)); + i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG).setIssueCreationTime(1_500L) + .replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW)))); db.issues().insert(rule, project, file, - i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_600L)); + i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_600L) + .replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, HIGH)))); IssueDto criticalBug2 = db.issues().insert(rule, project, file, - i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L)); + i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L) + .replaceAllImpacts(List.of(createImpact(SECURITY, MEDIUM), createImpact(MAINTAINABILITY, LOW)))); // closed issues are ignored db.issues().insert(rule, project, file, - i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L)); + i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L) + .replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); Collection result = underTest.selectIssueGroupsByComponent(db.getSession(), file, 1_000L); + assertThat(result).hasSize(3); assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); @@ -485,6 +490,7 @@ public class IssueDaoIT { assertThat(result.stream().filter(g -> g.getSeverity().equals("CRITICAL")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isOne(); assertThat(result.stream().filter(g -> g.getSeverity().equals("MINOR")).mapToLong(IssueGroupDto::getCount).sum()).isZero(); + assertThat(result.stream().filter(IssueGroupDto::hasHighImpactSeverity).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getStatus().equals("OPEN")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isOne(); @@ -507,6 +513,11 @@ public class IssueDaoIT { assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); } + @NotNull + private static ImpactDto createImpact(SoftwareQuality softwareQuality, Severity high) { + return new ImpactDto().setUuid(UuidFactoryFast.getInstance().create()).setSoftwareQuality(softwareQuality).setSeverity(high); + } + @Test public void selectGroupsOfComponentTreeOnLeak_on_file_new_code_reference_branch() { ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); @@ -514,6 +525,8 @@ public class IssueDaoIT { RuleDto rule = db.rules().insert(); IssueDto fpBug = db.issues().insert(rule, project, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG)); + IssueDto acceptedBug = db.issues().insert(rule, project, file, + i -> i.setStatus("RESOLVED").setResolution("WONTFIX").setSeverity("MAJOR").setType(RuleType.BUG)); IssueDto criticalBug1 = db.issues().insert(rule, project, file, i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG)); IssueDto criticalBug2 = db.issues().insert(rule, project, file, @@ -524,29 +537,31 @@ public class IssueDaoIT { // two issues part of new code period on reference branch db.issues().insertNewCodeReferenceIssue(fpBug); + db.issues().insertNewCodeReferenceIssue(acceptedBug); db.issues().insertNewCodeReferenceIssue(criticalBug1); db.issues().insertNewCodeReferenceIssue(criticalBug2); Collection result = underTest.selectIssueGroupsByComponent(db.getSession(), file, -1); - assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4); + assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(5); - assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4); + assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(5); assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.CODE_SMELL.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero(); assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.VULNERABILITY.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero(); assertThat(result.stream().filter(g -> g.getSeverity().equals("CRITICAL")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); - assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isOne(); + assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getSeverity().equals("MINOR")).mapToLong(IssueGroupDto::getCount).sum()).isZero(); assertThat(result.stream().filter(g -> g.getStatus().equals("OPEN")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); - assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isOne(); + assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(IssueGroupDto::getCount).sum()).isZero(); assertThat(result.stream().filter(g -> "FALSE-POSITIVE".equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isOne(); + assertThat(result.stream().filter(g -> "WONTFIX".equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isOne(); assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); - assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); + assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4); assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isOne(); } @@ -903,7 +918,7 @@ public class IssueDaoIT { prepareTables(); IssueDto issueDto = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1) .setSelectedAt(1_400_000_000_000L) - .replaceAllImpacts(List.of(new ImpactDto().setUuid(Uuids.createFast()).setSoftwareQuality(RELIABILITY).setSeverity(LOW))); + .replaceAllImpacts(List.of(createImpact(RELIABILITY, LOW))); underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java index c33da2e12ee..618a33187ff 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; public class IssueGroupDto { private int ruleType; private String severity; + private boolean hasHighImpactSeverity; @Nullable private String resolution; private String status; @@ -32,6 +33,10 @@ public class IssueGroupDto { private long count; private boolean inLeak; + public IssueGroupDto() { + // nothing to do + } + public int getRuleType() { return ruleType; } @@ -40,6 +45,10 @@ public class IssueGroupDto { return severity; } + public boolean hasHighImpactSeverity() { + return hasHighImpactSeverity; + } + @CheckForNull public String getResolution() { return resolution; @@ -71,6 +80,11 @@ public class IssueGroupDto { return this; } + public IssueGroupDto setHasHighImpactSeverity(boolean hasHighImpactSeverity) { + this.hasHighImpactSeverity = hasHighImpactSeverity; + return this; + } + public IssueGroupDto setResolution(@Nullable String resolution) { this.resolution = resolution; return this; 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 9e042bc90f7..7a1773cb4d3 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 @@ -458,61 +458,82 @@ - + + + + + select + i2.issue_type as ruleType, + i2.severity as severity, + i2.hasHighImpactSeverity as hasHighImpactSeverity, + i2.resolution as resolution, + i2.status as status, + sum(i2.effort) as effort, + count(i2.issue_type) as "count", + i2.inLeak as inLeak from ( - select i.issue_type, i.severity, i.resolution, i.status, i.effort, - - case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak - - - case when n.uuid is null then 0 else 1 end as inLeak - + select + i.issue_type, + i.severity, + case when exists(select 1 from high_impact_severity_issues hisi where hisi.kee = i.kee) then 1 else 0 end as hasHighImpactSeverity, + i.resolution, + i.status, + i.effort, + + case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak + + + case when n.uuid is null then 0 else 1 end as inLeak + from issues i left join new_code_reference_issues n on n.issue_key = i.kee - where i.status !='CLOSED' + where i.status <> 'CLOSED' and i.component_uuid = #{component.uuid,jdbcType=VARCHAR} ) i2 - group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak - + group by i2.issue_type, i2.severity, i2.hasHighImpactSeverity, i2.resolution, i2.status, i2.inLeak +