]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21273 Compute metrics new_accepted_issues and high_impact_accepted_issues
authorEric Giffon <eric.giffon@sonarsource.com>
Thu, 21 Dec 2023 11:53:35 +0000 (12:53 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 17 Jan 2024 20:02:44 +0000 (20:02 +0000)
gradle.properties
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/IssueGroupDto.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/MeasureUpdateFormulaFactoryImpl.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 923e9b7bda19701787a05885976faeeb92076c73..9d0fc0a31ec2a550151207921352840686ff2f30 100644 (file)
@@ -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
index 8f855709a18f2b7f822351e42c744e8c26d9e451..4cc9c893963854dc3ee0ebd292a4cf0954da1a0f 100644 (file)
@@ -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<String> severityBag = HashMultiset.create();
     private final EnumMultiset<RuleType> 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:
index cd8b385e34ac4c110518abb077387c85bb0fd05f..09ead8925652788fd722da5b71fc9b2df03f81df 100644 (file)
@@ -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);
   }
 }
index f35447534c8045b59a9b95da0b6c320df90ac9e7..7ae2f4eb5faf4c6ba0be35e42376dbec56f27749 100644 (file)
@@ -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<IssueGroupDto> 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<IssueGroupDto> 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);
 
index c33da2e12ee35adf4c65ff728da145ebe721a193..618a33187ffeb6d2c3a26eb8cab2533b63b61c4e 100644 (file)
@@ -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;
index 9e042bc90f794eb4cd7d93a11287ce939e994080..7a1773cb4d31030ad77052e07c383b23f96b959a 100644 (file)
   </select>
 
   <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
-    select i.issue_type as ruleType, i.severity as severity, i.resolution as resolution, i.status as status, sum(i.effort) as effort, count(i.issue_type) as "count",
-    <if test="leakPeriodBeginningDate &gt;= 0">
-      (i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak
-    </if>
-    <if test="leakPeriodBeginningDate &lt; 0">
-      CASE WHEN n.uuid is null THEN false ELSE true END as inLeak
-    </if>
+    <include refid="withHighImpactSeverityIssues"/>
+    select
+      i.issue_type as ruleType,
+      i.severity as severity,
+      exists(select 1 from high_impact_severity_issues hisi where hisi.kee = i.kee) as hasHighImpactSeverity,
+      i.resolution as resolution,
+      i.status as status, sum(i.effort) as effort,
+      count(i.issue_type) as "count",
+      <if test="leakPeriodBeginningDate &gt;= 0">
+        (i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT}) 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
     <if test="leakPeriodBeginningDate &lt; 0">
       left join new_code_reference_issues n on n.issue_key = i.kee
     </if>
-    where i.status !='CLOSED'
+    where i.status &lt;&gt; 'CLOSED'
     and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
-    group by i.issue_type, i.severity, i.resolution, i.status, inLeak
+    group by i.issue_type, i.severity, hasHighImpactSeverity, i.resolution, i.status, inLeak
   </select>
 
-  <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="oracle">
-    select i2.issue_type as ruleType, i2.severity as severity, 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,
-      <if test="leakPeriodBeginningDate &gt;= 0">
-        case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
-      </if>
-      <if test="leakPeriodBeginningDate &lt; 0">
-        case when n.uuid is null then 0 else 1 end as inLeak
-      </if>
+  <sql id="withHighImpactSeverityIssues">
+    with high_impact_severity_issues as (
+      select distinct kee
       from issues i
-      <if test="leakPeriodBeginningDate &lt; 0">
-        left join new_code_reference_issues n on n.issue_key = i.kee
-      </if>
-      where i.status !='CLOSED'
+      inner join issues_impacts ii on ii.issue_key = i.kee
+      where i.status &lt;&gt; 'CLOSED'
       and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
-    ) i2
-    group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
+      and ii.severity = 'HIGH'
+    )
+  </sql>
+
+  <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="oracle">
+    <include refid="selectIssueGroupsByComponentVendorSpecific"/>
   </select>
 
   <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="mssql">
-    select i2.issue_type as ruleType, i2.severity as severity, i2.resolution as resolution, i2.status as status, sum(i2.effort) as effort, count(i2.issue_type) as "count", i2.inLeak as inLeak
+    <include refid="selectIssueGroupsByComponentVendorSpecific"/>
+  </select>
+
+  <sql id="selectIssueGroupsByComponentVendorSpecific">
+    <include refid="withHighImpactSeverityIssues"/>
+    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,
-      <if test="leakPeriodBeginningDate &gt;= 0">
-      case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
-      </if>
-      <if test="leakPeriodBeginningDate &lt; 0">
-        case when n.uuid is null then 0 else 1 end as inLeak
-      </if>
+      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,
+        <if test="leakPeriodBeginningDate &gt;= 0">
+          case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+        </if>
+        <if test="leakPeriodBeginningDate &lt; 0">
+          case when n.uuid is null then 0 else 1 end as inLeak
+        </if>
       from issues i
       <if test="leakPeriodBeginningDate &lt; 0">
         left join new_code_reference_issues n on n.issue_key = i.kee
       </if>
-      where i.status !='CLOSED'
+      where i.status &lt;&gt; 'CLOSED'
       and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
     ) i2
-    group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
-  </select>
+    group by i2.issue_type, i2.severity, i2.hasHighImpactSeverity, i2.resolution, i2.status, i2.inLeak
+  </sql>
 
   <select id="selectIssueKeysByComponentUuid" parameterType="string" resultType="string">
     select
index d810cd88defd3e7191ebb65352ece1ffb28d6a91..4ea95fd5b1ecc42c6bd89553ce90426b2e2613da 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Optional;
 import javax.annotation.Nullable;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.status.IssueStatus;
 import org.sonar.db.issue.IssueGroupDto;
 import org.sonar.db.rule.SeverityUtil;
 
@@ -42,48 +43,60 @@ class IssueCounter {
   private final Map<String, Count> byStatus = new HashMap<>();
   private final Map<String, Count> hotspotsByStatus = new HashMap<>();
   private final Count unresolved = new Count();
+  private final Count highImpactAccepted = new Count();
 
   IssueCounter(Collection<IssueGroupDto> groups) {
     for (IssueGroupDto group : groups) {
-      RuleType ruleType = RuleType.valueOf(group.getRuleType());
-      if (ruleType.equals(SECURITY_HOTSPOT)) {
-        if (group.getResolution() == null) {
-          unresolvedByType
-            .computeIfAbsent(SECURITY_HOTSPOT, k -> new Count())
-            .add(group);
-        }
-        if (group.getStatus() != null) {
-          hotspotsByStatus
-            .computeIfAbsent(group.getStatus(), k -> new Count())
-            .add(group);
-        }
-        continue;
-      }
-      if (group.getResolution() == null) {
-        highestSeverityOfUnresolved
-          .computeIfAbsent(ruleType, k -> new HighestSeverity())
-          .add(group);
-        effortOfUnresolved
-          .computeIfAbsent(ruleType, k -> new Effort())
-          .add(group);
-        unresolvedBySeverity
-          .computeIfAbsent(group.getSeverity(), k -> new Count())
-          .add(group);
-        unresolvedByType
-          .computeIfAbsent(ruleType, k -> new Count())
-          .add(group);
-        unresolved.add(group);
+      if (RuleType.valueOf(group.getRuleType()).equals(SECURITY_HOTSPOT)) {
+        processHotspotGroup(group);
       } else {
-        byResolution
-          .computeIfAbsent(group.getResolution(), k -> new Count())
-          .add(group);
+        processGroup(group);
       }
-      if (group.getStatus() != null) {
-        byStatus
-          .computeIfAbsent(group.getStatus(), k -> new Count())
-          .add(group);
+    }
+  }
+
+  private void processHotspotGroup(IssueGroupDto group) {
+    if (group.getResolution() == null) {
+      unresolvedByType
+        .computeIfAbsent(SECURITY_HOTSPOT, k -> new Count())
+        .add(group);
+    }
+    if (group.getStatus() != null) {
+      hotspotsByStatus
+        .computeIfAbsent(group.getStatus(), k -> new Count())
+        .add(group);
+    }
+  }
+
+  private void processGroup(IssueGroupDto group) {
+    if (group.getResolution() == null) {
+      RuleType ruleType = RuleType.valueOf(group.getRuleType());
+      highestSeverityOfUnresolved
+        .computeIfAbsent(ruleType, k -> new HighestSeverity())
+        .add(group);
+      effortOfUnresolved
+        .computeIfAbsent(ruleType, k -> new Effort())
+        .add(group);
+      unresolvedBySeverity
+        .computeIfAbsent(group.getSeverity(), k -> new Count())
+        .add(group);
+      unresolvedByType
+        .computeIfAbsent(ruleType, k -> new Count())
+        .add(group);
+      unresolved.add(group);
+    } else {
+      byResolution
+        .computeIfAbsent(group.getResolution(), k -> new Count())
+        .add(group);
+      if (IssueStatus.ACCEPTED.equals(IssueStatus.of(group.getStatus(), group.getResolution())) && group.hasHighImpactSeverity()) {
+        highImpactAccepted.add(group);
       }
     }
+    if (group.getStatus() != null) {
+      byStatus
+        .computeIfAbsent(group.getStatus(), k -> new Count())
+        .add(group);
+    }
   }
 
   public Optional<String> getHighestSeverityOfUnresolved(RuleType ruleType, boolean onlyInLeak) {
@@ -119,6 +132,10 @@ class IssueCounter {
     return value(unresolved, onlyInLeak);
   }
 
+  public long countHighImpactAccepted(boolean onlyInLeak) {
+    return value(highImpactAccepted, onlyInLeak);
+  }
+
   public long countHotspotsByStatus(String status, boolean onlyInLeak) {
     return value(hotspotsByStatus.get(status), onlyInLeak);
   }
index e3a7d8a895650e12cc1ea50267ce7c7fb9bc8053..06954a8bd8470b640e2856fb4e0e4ebd0a2940c9 100644 (file)
@@ -81,6 +81,9 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact
     new MeasureUpdateFormula(CoreMetrics.ACCEPTED_ISSUES, false, new AddChildren(),
       (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_WONT_FIX, false))),
 
+    new MeasureUpdateFormula(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, false, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countHighImpactAccepted(false))),
+
     new MeasureUpdateFormula(CoreMetrics.OPEN_ISSUES, false, new AddChildren(),
       (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_OPEN, false))),
 
@@ -174,6 +177,9 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact
     new MeasureUpdateFormula(CoreMetrics.NEW_INFO_VIOLATIONS, true, new AddChildren(),
       (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.INFO, true))),
 
+    new MeasureUpdateFormula(CoreMetrics.NEW_ACCEPTED_ISSUES, true, new AddChildren(),
+      (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_WONT_FIX, true))),
+
     new MeasureUpdateFormula(CoreMetrics.NEW_TECHNICAL_DEBT, true, new AddChildren(),
       (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, true))),
 
index 4bf1df0e7ce0f972b733b48e70f00a9963fbc6af..9ca8ddc1774a02dbeaac4e6daa21daae132602e2 100644 (file)
@@ -320,6 +320,29 @@ public class MeasureUpdateFormulaFactoryImplTest {
       .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7);
   }
 
+  @Test
+  public void test_high_impact_accepted_issues() {
+    withNoIssues()
+      .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 0);
+
+    with(
+      newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
+        .setHasHighImpactSeverity(true).setCount(3),
+      newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX)
+        .setHasHighImpactSeverity(true).setCount(4),
+      newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX)
+        .setHasHighImpactSeverity(false).setCount(5),
+      newGroup(RuleType.BUG).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
+        .setHasHighImpactSeverity(true).setCount(30),
+      newGroup(RuleType.BUG).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX)
+        .setHasHighImpactSeverity(true).setCount(40),
+      newGroup(RuleType.BUG).setStatus(Issue.STATUS_RESOLVED).setResolution(Issue.RESOLUTION_WONT_FIX)
+        .setHasHighImpactSeverity(false).setCount(50),
+      // exclude security hotspot
+      newGroup(RuleType.SECURITY_HOTSPOT).setResolution(Issue.RESOLUTION_WONT_FIX).setHasHighImpactSeverity(true).setCount(40))
+      .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 40);
+  }
+
   @Test
   public void test_technical_debt() {
     withNoIssues().assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 0);
@@ -677,6 +700,22 @@ public class MeasureUpdateFormulaFactoryImplTest {
       .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7);
   }
 
+  @Test
+  public void test_new_accepted_issues() {
+    withNoIssues()
+      .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 0);
+
+    with(
+      newGroup(RuleType.CODE_SMELL).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setInLeak(true).setCount(3),
+      newGroup(RuleType.CODE_SMELL).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(true).setCount(4),
+      newGroup(RuleType.BUG).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setInLeak(true).setCount(30),
+      newGroup(RuleType.BUG).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(true).setCount(40),
+      // not in leak
+      newGroup(RuleType.CODE_SMELL).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(5),
+      newGroup(RuleType.BUG).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(50))
+      .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 4 + 40);
+  }
+
   @Test
   public void test_new_technical_debt() {
     withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0);
index 6651b1035ac3dfeb3e57230c26902d4f4168f82b..0b192e6af2d3c3a4a960b9d73ef536aaaff51274 100644 (file)
@@ -3266,12 +3266,13 @@ metric.wont_fix_issues.description=Won't fix issues
 metric.wont_fix_issues.name=Won't Fix Issues
 metric.accepted_issues.description=Accepted issues
 metric.accepted_issues.name=Accepted Issues
-metric.pull_request_fixed_issues.name=Fixed issues
-metric.pull_request_fixed_issues.description=Fixed issues
-metric.new_accepted_issues.name=Accepted issues
-metric.new_accepted_issues.description=Accepted issues
-metric.high_impact_accepted_issues.name=High impact accepted issues
-metric.high_impact_accepted_issues.description=High impact accepted issues
+metric.pull_request_fixed_issues.name=Pull request fixed issues
+metric.pull_request_fixed_issues.description=Count of issues that would be fixed by the pull request.
+metric.new_accepted_issues.name=New Accepted Issues
+metric.new_accepted_issues.description=New accepted issues
+metric.high_impact_accepted_issues.name=High Impact Accepted Issues
+metric.high_impact_accepted_issues.description=Accepted issues with high impact
+
 #------------------------------------------------------------------------------
 #
 # PERMISSIONS
@@ -5284,4 +5285,3 @@ guiding.issue_accept.3.content.1=Marking issues as Confirmed and Fixed is now de
 guiding.issue_accept.3.content.2=Consider Accepting issues, assigning the issue or using comments and tags instead.
 guiding.issue_accept.3.content.link=Learn more about issues statuses
 guiding.step_x_of_y={0} of {1}
-