]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10875 Add 2 new measures for security hotspots (#394)
authorJanos Gyerik <janos.gyerik@sonarsource.com>
Mon, 18 Jun 2018 12:18:04 +0000 (14:18 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 4 Jul 2018 07:31:04 +0000 (09:31 +0200)
* Declare 2 new metrics

* Add 2 new metrics to live measures

* Add 2 new metrics in governance

* Add counts for 2 new metrics

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-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayMeasure.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java

index 56b777e762ecdbf8f62f80b3c6d573786d6c3062..5f78413704533926615c511fd8262db8e9bf442f 100644 (file)
@@ -97,11 +97,13 @@ public class IssueCounter extends IssueVisitor {
     .put(RuleType.CODE_SMELL, CoreMetrics.CODE_SMELLS_KEY)
     .put(RuleType.BUG, CoreMetrics.BUGS_KEY)
     .put(RuleType.VULNERABILITY, CoreMetrics.VULNERABILITIES_KEY)
+    .put(RuleType.SECURITY_HOTSPOT, CoreMetrics.SECURITY_HOTSPOTS_KEY)
     .build();
   private static final Map<RuleType, String> TYPE_TO_NEW_METRIC_KEY = ImmutableMap.<RuleType, String>builder()
     .put(RuleType.CODE_SMELL, CoreMetrics.NEW_CODE_SMELLS_KEY)
     .put(RuleType.BUG, CoreMetrics.NEW_BUGS_KEY)
     .put(RuleType.VULNERABILITY, CoreMetrics.NEW_VULNERABILITIES_KEY)
+    .put(RuleType.SECURITY_HOTSPOT, CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY)
     .build();
 
   private final PeriodHolder periodHolder;
@@ -262,6 +264,12 @@ public class IssueCounter extends IssueVisitor {
           // Other statuses are ignored
       }
     }
+
+    void addNewSecurityHotspot(DefaultIssue issue) {
+      if (issue.resolution() == null) {
+        typeBag.add(issue.type());
+      }
+    }
   }
 
   /**
@@ -279,7 +287,11 @@ public class IssueCounter extends IssueVisitor {
     }
 
     void addOnPeriod(DefaultIssue issue) {
-      counterForPeriod.add(issue);
+      if (issue.type() != RuleType.SECURITY_HOTSPOT) {
+        counterForPeriod.add(issue);
+      } else {
+        counterForPeriod.addNewSecurityHotspot(issue);
+      }
     }
 
     void add(DefaultIssue issue) {
index e3de14b416055998070dcaa4048028319390c7d4..45f2049e18765a5f5e3621f8865727e8499f7e28 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.ce.task.projectanalysis.issue;
 
+import java.util.Arrays;
 import java.util.Date;
 import javax.annotation.Nullable;
 import org.assertj.core.data.Offset;
@@ -100,6 +101,8 @@ public class IssueCounterTest {
   static final Metric NEW_CODE_SMELLS_METRIC = new MetricImpl(20, CoreMetrics.NEW_CODE_SMELLS_KEY, CoreMetrics.NEW_CODE_SMELLS_KEY, INT);
   static final Metric NEW_BUGS_METRIC = new MetricImpl(21, CoreMetrics.NEW_BUGS_KEY, CoreMetrics.NEW_BUGS_KEY, INT);
   static final Metric NEW_VULNERABILITIES_METRIC = new MetricImpl(22, CoreMetrics.NEW_VULNERABILITIES_KEY, CoreMetrics.NEW_VULNERABILITIES_KEY, INT);
+  static final Metric SECURITY_HOTSPOTS_METRIC = new MetricImpl(24, CoreMetrics.SECURITY_HOTSPOTS_KEY, CoreMetrics.SECURITY_HOTSPOTS_KEY, INT);
+  static final Metric NEW_SECURITY_HOTSPOTS_METRIC = new MetricImpl(25, CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY, CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY, INT);
 
   @Rule
   public BatchReportReaderRule reportReader = new BatchReportReaderRule();
@@ -134,7 +137,9 @@ public class IssueCounterTest {
     .add(VULNERABILITIES_METRIC)
     .add(NEW_CODE_SMELLS_METRIC)
     .add(NEW_BUGS_METRIC)
-    .add(NEW_VULNERABILITIES_METRIC);
+    .add(NEW_VULNERABILITIES_METRIC)
+    .add(SECURITY_HOTSPOTS_METRIC)
+    .add(NEW_SECURITY_HOTSPOTS_METRIC);
 
   @Rule
   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
@@ -287,13 +292,13 @@ public class IssueCounterTest {
 
     underTest.beforeComponent(FILE1);
     // created before -> existing issues (so ignored)
-    underTest.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L).setType(RuleType.CODE_SMELL));
+    underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate() - 1000000L).setType(RuleType.CODE_SMELL));
     // created during the first analysis starting the period -> existing issues (so ignored)
-    underTest.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate()).setType(RuleType.BUG));
+    underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER, period.getSnapshotDate()).setType(RuleType.BUG));
     // created after -> 3 new issues but 1 is closed
-    underTest.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.CODE_SMELL));
-    underTest.onIssue(FILE1, createIssueAt(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.BUG));
-    underTest.onIssue(FILE1, createIssueAt(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L).setType(RuleType.BUG));
+    underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.CODE_SMELL));
+    underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, CRITICAL, period.getSnapshotDate() + 100000L).setType(RuleType.BUG));
+    underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR, period.getSnapshotDate() + 200000L).setType(RuleType.BUG));
     underTest.afterComponent(FILE1);
 
     underTest.beforeComponent(FILE2);
@@ -319,27 +324,116 @@ public class IssueCounterTest {
     assertVariation(PROJECT, NEW_VULNERABILITIES_METRIC, 0);
   }
 
+  @Test
+  public void count_hotspots() {
+    periodsHolder.setPeriod(null);
+
+    // bottom-up traversal -> from files to project
+    underTest.beforeComponent(FILE1);
+    underTest.onIssue(FILE1, createSecurityHotspot());
+    underTest.onIssue(FILE1, createSecurityHotspot());
+    underTest.afterComponent(FILE1);
+
+    underTest.beforeComponent(FILE2);
+    underTest.onIssue(FILE1, createSecurityHotspot());
+    underTest.afterComponent(FILE2);
+
+    underTest.beforeComponent(FILE3);
+    underTest.afterComponent(FILE3);
+
+    underTest.beforeComponent(PROJECT);
+    underTest.afterComponent(PROJECT);
+
+    assertThat(measureRepository.getRawMeasure(FILE1, SECURITY_HOTSPOTS_METRIC).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getRawMeasure(FILE1, ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getRawMeasure(FILE1, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getRawMeasure(FILE1, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(FILE2, SECURITY_HOTSPOTS_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE2, ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE2, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getRawMeasure(FILE2, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(FILE3, SECURITY_HOTSPOTS_METRIC).get().getIntValue()).isEqualTo(0);
+    assertThat(measureRepository.getRawMeasure(FILE3, ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+
+    assertThat(measureRepository.getRawMeasure(PROJECT, SECURITY_HOTSPOTS_METRIC).get().getIntValue()).isEqualTo(3);
+    assertThat(measureRepository.getRawMeasure(PROJECT, ISSUES_METRIC).get().getIntValue()).isEqualTo(3);
+    assertThat(measureRepository.getRawMeasure(PROJECT, OPEN_ISSUES_METRIC).get().getIntValue()).isEqualTo(3);
+    assertThat(measureRepository.getRawMeasure(PROJECT, CONFIRMED_ISSUES_METRIC).get().getIntValue()).isEqualTo(0);
+  }
+
+  @Test
+  public void count_new_hotspots_excluded_from_other_raw_issue_counts() {
+    Period period = newPeriod(1500000000000L);
+    periodsHolder.setPeriod(period);
+
+    underTest.beforeComponent(FILE1);
+    // created before -> existing issues (so ignored)
+    underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() - 1000000L));
+    // created during the first analysis starting the period -> existing issues (so ignored)
+    underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate()));
+
+    // created after, but closed
+    underTest.onIssue(FILE1, createSecurityHotspot(period.getSnapshotDate() + 100000L).setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
+
+    for (String severity : Arrays.asList(CRITICAL, BLOCKER, MAJOR)) {
+      DefaultIssue issue = createSecurityHotspot(period.getSnapshotDate() + 100000L);
+      issue.setSeverity(severity);
+      underTest.onIssue(FILE1, issue);
+    }
+    underTest.afterComponent(FILE1);
+
+    underTest.beforeComponent(FILE2);
+    underTest.afterComponent(FILE2);
+
+    underTest.beforeComponent(PROJECT);
+    underTest.afterComponent(PROJECT);
+
+    assertVariation(FILE1, NEW_ISSUES_METRIC, 0);
+    assertVariation(FILE1, NEW_CRITICAL_ISSUES_METRIC, 0);
+    assertVariation(FILE1, NEW_BLOCKER_ISSUES_METRIC, 0);
+    assertVariation(FILE1, NEW_MAJOR_ISSUES_METRIC, 0);
+    assertVariation(FILE1, NEW_VULNERABILITIES_METRIC, 0);
+    assertVariation(FILE1, NEW_SECURITY_HOTSPOTS_METRIC, 3);
+
+    assertVariation(PROJECT, NEW_ISSUES_METRIC, 0);
+    assertVariation(PROJECT, NEW_CRITICAL_ISSUES_METRIC, 0);
+    assertVariation(PROJECT, NEW_BLOCKER_ISSUES_METRIC, 0);
+    assertVariation(PROJECT, NEW_MAJOR_ISSUES_METRIC, 0);
+    assertVariation(PROJECT, NEW_VULNERABILITIES_METRIC, 0);
+    assertVariation(PROJECT, NEW_SECURITY_HOTSPOTS_METRIC, 3);
+  }
+
   private void assertVariation(Component component, Metric metric, int expectedVariation) {
     Measure measure = measureRepository.getRawMeasure(component, metric).get();
     assertThat(measure.getVariation()).isEqualTo((double) expectedVariation, Offset.offset(0.01));
   }
 
   private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) {
-    return new DefaultIssue()
-      .setResolution(resolution).setStatus(status)
-      .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
-      .setType(RuleType.CODE_SMELL)
-      .setCreationDate(new Date());
+    return createIssue(resolution, status, severity, RuleType.CODE_SMELL, new Date().getTime());
+  }
+
+  private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, long creationDate) {
+    return createIssue(resolution, status, severity, RuleType.CODE_SMELL, creationDate);
   }
 
-  private static DefaultIssue createIssueAt(@Nullable String resolution, String status, String severity, long creationDate) {
+  private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, RuleType ruleType, long creationDate) {
     return new DefaultIssue()
       .setResolution(resolution).setStatus(status)
       .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
-      .setType(RuleType.CODE_SMELL)
+      .setType(ruleType)
       .setCreationDate(new Date(creationDate));
   }
 
+  private static DefaultIssue createSecurityHotspot() {
+    return createSecurityHotspot(new Date().getTime());
+  }
+
+  private static DefaultIssue createSecurityHotspot(long creationDate) {
+    return createIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT, creationDate);
+  }
+
   private static Period newPeriod(long date) {
     return new Period("mode", null, date, "U1");
   }
index def6ce168c056b2fa25e688ab814ef79dc5e0d50..756ecf804046cb8c1cf76925518a22eddc08c69c 100644 (file)
@@ -44,6 +44,9 @@ public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory
     new IssueMetricFormula(CoreMetrics.VULNERABILITIES, false,
       (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, false))),
 
+    new IssueMetricFormula(CoreMetrics.SECURITY_HOTSPOTS, false,
+      (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, false))),
+
     new IssueMetricFormula(CoreMetrics.VIOLATIONS, false,
       (context, issues) -> context.setValue(issues.countUnresolved(false))),
 
@@ -113,6 +116,9 @@ public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory
     new IssueMetricFormula(CoreMetrics.NEW_VULNERABILITIES, true,
       (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, true))),
 
+    new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS, true,
+      (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, true))),
+
     new IssueMetricFormula(CoreMetrics.NEW_VIOLATIONS, true,
       (context, issues) -> context.setLeakValue(issues.countUnresolved(true))),
 
index 7a41a9ef001638f7c390ac61b36f08409c799ed9..7e00a59b32d1c9a1e9e59898de4ba153a2f60ced 100644 (file)
@@ -39,7 +39,7 @@ export default function MeasuresOverlayMeasure({ measure }: Props) {
       data-metric={measure.metric.key}
       key={measure.metric.key}>
       <span className="measure-name">
-        {['bugs', 'vulnerabilities', 'code_smells'].includes(measure.metric.key) && (
+        {['bugs', 'vulnerabilities', 'code_smells', 'security_hotspots'].includes(measure.metric.key) && (
           <IssueTypeIcon className="little-spacer-right" query={measure.metric.key} />
         )}
         {getLocalizedMetricName(measure.metric)}
index 3e0f4420b35ae925a3ae4374a8a38ce3c1d4d813..60d21398eedb9acf6b9e32a2a9872c5d6aea65cd 100644 (file)
@@ -1805,6 +1805,8 @@ metric.new_reliability_rating.extra_short_name=Rating
 metric.new_reliability_remediation_effort.description=Reliability remediation effort on new code
 metric.new_reliability_remediation_effort.name=Reliability Remediation Effort on New Code
 metric.new_reliability_remediation_effort.extra_short_name=Remediation Effort
+metric.new_security_hotspots.description=New Security Hotspots
+metric.new_security_hotspots.name=New Security Hotspots
 metric.new_security_rating.description=Security rating on new code
 metric.new_security_rating.name=Security Rating on New Code
 metric.new_security_rating.extra_short_name=Rating
@@ -1905,6 +1907,8 @@ metric.rfc.description=Response for Class
 metric.rfc.name=Response for Class
 metric.rfc_distribution.description=Class distribution /RFC
 metric.rfc_distribution.name=Class Distribution / RFC
+metric.security_hotspots.description=Security Hotspots
+metric.security_hotspots.name=Security Hotspots
 metric.security_rating.description=Security rating
 metric.security_rating.name=Security Rating
 metric.security_rating.extra_short_name=Rating
index f746645a91b5ba6c811fd6b1030896f2ec0d7bf9..c3be7bb7d632a1a97a4dec367fb59e627464d030 100644 (file)
@@ -1978,6 +1978,45 @@ public final class CoreMetrics {
     .setDeleteHistoricalData(true)
     .create();
 
+  /**
+   * SonarQube Quality Model
+   * @since 7.3
+   */
+  public static final String SECURITY_HOTSPOTS_KEY = "security_hotspots";
+
+  /**
+   * SonarQube Quality Model
+   * @since 7.3
+   */
+  public static final Metric<Integer> SECURITY_HOTSPOTS = new Metric.Builder(SECURITY_HOTSPOTS_KEY, "Security Hotspots", Metric.ValueType.INT)
+    .setDescription("Security Hotspots")
+    .setDirection(Metric.DIRECTION_WORST)
+    .setQualitative(false)
+    .setDomain(DOMAIN_SECURITY)
+    .setBestValue(0.0)
+    .setOptimizedBestValue(true)
+    .create();
+
+  /**
+   * SonarQube Quality Model
+   * @since 7.3
+   */
+  public static final String NEW_SECURITY_HOTSPOTS_KEY = "new_security_hotspots";
+
+  /**
+   * SonarQube Quality Model
+   * @since 7.3
+   */
+  public static final Metric<Integer> NEW_SECURITY_HOTSPOTS = new Metric.Builder(NEW_SECURITY_HOTSPOTS_KEY, "New Security Hotspots", Metric.ValueType.INT)
+    .setDescription("New Security Hotspots")
+    .setDirection(Metric.DIRECTION_WORST)
+    .setQualitative(true)
+    .setDomain(DOMAIN_SECURITY)
+    .setBestValue(0.0)
+    .setOptimizedBestValue(true)
+    .setDeleteHistoricalData(true)
+    .create();
+
   // --------------------------------------------------------------------------------------------------------------------
   //
   // DESIGN