From: Janos Gyerik Date: Mon, 18 Jun 2018 12:18:04 +0000 (+0200) Subject: SONAR-10875 Add 2 new measures for security hotspots (#394) X-Git-Tag: 7.5~885 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6d08db0105df9a66b9598b79a88c19970980e230;p=sonarqube.git SONAR-10875 Add 2 new measures for security hotspots (#394) * Declare 2 new metrics * Add 2 new metrics to live measures * Add 2 new metrics in governance * Add counts for 2 new metrics --- 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 56b777e762e..5f784137045 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 @@ -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 TYPE_TO_NEW_METRIC_KEY = ImmutableMap.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) { 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 e3de14b4160..45f2049e187 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 @@ -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"); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java index def6ce168c0..756ecf80404 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java @@ -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))), diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayMeasure.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayMeasure.tsx index 7a41a9ef001..7e00a59b32d 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayMeasure.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlayMeasure.tsx @@ -39,7 +39,7 @@ export default function MeasuresOverlayMeasure({ measure }: Props) { data-metric={measure.metric.key} key={measure.metric.key}> - {['bugs', 'vulnerabilities', 'code_smells'].includes(measure.metric.key) && ( + {['bugs', 'vulnerabilities', 'code_smells', 'security_hotspots'].includes(measure.metric.key) && ( )} {getLocalizedMetricName(measure.metric)} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 3e0f4420b35..60d21398eed 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index f746645a91b..c3be7bb7d63 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -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 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 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