diff options
16 files changed, 700 insertions, 113 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index c5dc824f950..ed5b4fd315c 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -111,6 +111,7 @@ import org.sonar.ce.task.projectanalysis.qualitygate.QualityGateStatusHolderImpl import org.sonar.ce.task.projectanalysis.qualitymodel.MaintainabilityMeasuresVisitor; import org.sonar.ce.task.projectanalysis.qualitymodel.NewMaintainabilityMeasuresVisitor; import org.sonar.ce.task.projectanalysis.qualitymodel.NewReliabilityAndSecurityRatingMeasuresVisitor; +import org.sonar.ce.task.projectanalysis.qualitymodel.NewSecurityReviewMeasuresVisitor; import org.sonar.ce.task.projectanalysis.qualitymodel.RatingSettings; import org.sonar.ce.task.projectanalysis.qualitymodel.ReliabilityAndSecurityRatingMeasuresVisitor; import org.sonar.ce.task.projectanalysis.qualitymodel.SecurityReviewMeasuresVisitor; @@ -269,6 +270,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop ReliabilityAndSecurityRatingMeasuresVisitor.class, NewReliabilityAndSecurityRatingMeasuresVisitor.class, SecurityReviewMeasuresVisitor.class, + NewSecurityReviewMeasuresVisitor.class, LastCommitVisitor.class, MeasureComputersVisitor.class, 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 057d5bb00ca..91fadde60ca 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 @@ -76,7 +76,6 @@ 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.api.rules.RuleType.VULNERABILITY; -import static org.sonar.api.utils.DateUtils.truncateToSeconds; /** * For each component, computes the measures related to number of issues: @@ -153,7 +152,7 @@ public class IssueCounter extends IssueVisitor { currentCounters.addOnPeriod(issue); } else if (periodHolder.hasPeriod()) { Period period = periodHolder.getPeriod(); - if (issue.creationDate().getTime() > truncateToSeconds(period.getSnapshotDate())) { + if (period.isOnPeriod(issue.creationDate())){ currentCounters.addOnPeriod(issue); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java index ae7c9c28ac3..1bd97ca7542 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java @@ -22,6 +22,7 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.common.base.MoreObjects; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; import org.sonar.api.measures.CoreMetrics; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.component.Component; @@ -29,13 +30,13 @@ import org.sonar.ce.task.projectanalysis.measure.Measure; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; +import org.sonar.ce.task.projectanalysis.period.Period; import org.sonar.ce.task.projectanalysis.period.PeriodHolder; import org.sonar.core.issue.DefaultIssue; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; -import static org.sonar.api.utils.DateUtils.truncateToSeconds; /** * Compute new effort related measures : @@ -82,9 +83,9 @@ public class NewEffortAggregator extends IssueVisitor { public void onIssue(Component component, DefaultIssue issue) { if (issue.resolution() == null && issue.effortInMinutes() != null) { if (analysisMetadataHolder.isPullRequest()) { - counter.add(issue, 0L); + counter.add(issue, null); } else if (periodHolder.hasPeriod()) { - counter.add(issue, periodHolder.getPeriod().getSnapshotDate()); + counter.add(issue, periodHolder.getPeriod()); } } } @@ -115,8 +116,8 @@ public class NewEffortAggregator extends IssueVisitor { securitySum.add(otherCounter.securitySum); } - void add(DefaultIssue issue, long startDate) { - long newEffort = calculate(issue, startDate); + void add(DefaultIssue issue, @Nullable Period period) { + long newEffort = calculate(issue, period); switch (issue.type()) { case CODE_SMELL: maintainabilitySum.add(newEffort); @@ -135,8 +136,8 @@ public class NewEffortAggregator extends IssueVisitor { } } - long calculate(DefaultIssue issue, long startDate) { - if (issue.creationDate().getTime() > truncateToSeconds(startDate)) { + long calculate(DefaultIssue issue, @Nullable Period period) { + if (period == null || period.isOnPeriod(issue.creationDate())) { return MoreObjects.firstNonNull(issue.effortInMinutes(), 0L); } return 0L; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java index 3a44185505f..474adf14536 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/Period.java @@ -19,6 +19,7 @@ */ package org.sonar.ce.task.projectanalysis.period; +import java.util.Date; import java.util.Objects; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -27,6 +28,7 @@ import javax.annotation.concurrent.Immutable; import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.hash; import static java.util.Objects.requireNonNull; +import static org.sonar.api.utils.DateUtils.truncateToSeconds; @Immutable public class Period { @@ -68,6 +70,10 @@ public class Period { && Objects.equals(modeParameter, period.modeParameter); } + public boolean isOnPeriod(Date date) { + return date.getTime() > truncateToSeconds(snapshotDate); + } + @Override public int hashCode() { return hash(mode, modeParameter, snapshotDate); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java index 5fc877f4ee1..c9d70906bbf 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java @@ -31,7 +31,6 @@ import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepository; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; -import org.sonar.ce.task.projectanalysis.period.Period; import org.sonar.ce.task.projectanalysis.period.PeriodHolder; import org.sonar.core.issue.DefaultIssue; import org.sonar.server.measure.Rating; @@ -45,7 +44,6 @@ import static org.sonar.api.rule.Severity.MAJOR; import static org.sonar.api.rule.Severity.MINOR; import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.VULNERABILITY; -import static org.sonar.api.utils.DateUtils.truncateToSeconds; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit.LEAVES; import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder; @@ -154,7 +152,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVis } void processIssue(Issue issue, boolean isPR, PeriodHolder periodHolder) { - if (isPR || isOnPeriod((DefaultIssue) issue, periodHolder.getPeriod())) { + if (isPR || periodHolder.getPeriod().isOnPeriod(((DefaultIssue) issue).creationDate())) { Rating rating = RATING_BY_SEVERITY.get(issue.severity()); if (issue.type().equals(BUG)) { newRatingValueByMetric.get(NEW_RELIABILITY_RATING_KEY).increment(rating); @@ -163,11 +161,6 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVis } } } - - private static boolean isOnPeriod(DefaultIssue issue, Period period) { - // Add one second to not take into account issues created during current analysis - return issue.creationDate().getTime() > truncateToSeconds(period.getSnapshotDate()); - } } private static final class CounterFactory extends SimpleStackElementFactory<NewReliabilityAndSecurityRatingMeasuresVisitor.Counter> { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java new file mode 100644 index 00000000000..5c48a164805 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitor.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.qualitymodel; + +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter; +import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepository; +import org.sonar.ce.task.projectanalysis.measure.Measure; +import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; +import org.sonar.ce.task.projectanalysis.metric.Metric; +import org.sonar.ce.task.projectanalysis.metric.MetricRepository; +import org.sonar.ce.task.projectanalysis.period.PeriodHolder; + +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY; +import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; +import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; +import static org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit.FILE; +import static org.sonar.server.security.SecurityReviewRating.computePercent; +import static org.sonar.server.security.SecurityReviewRating.computeRating; + +public class NewSecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<SecurityReviewCounter> { + + private final ComponentIssuesRepository componentIssuesRepository; + private final MeasureRepository measureRepository; + private final PeriodHolder periodHolder; + private final AnalysisMetadataHolder analysisMetadataHolder; + private final Metric newSecurityReviewRatingMetric; + private final Metric newSecurityHotspotsReviewedMetric; + + public NewSecurityReviewMeasuresVisitor(ComponentIssuesRepository componentIssuesRepository, MeasureRepository measureRepository, PeriodHolder periodHolder, + AnalysisMetadataHolder analysisMetadataHolder, MetricRepository metricRepository) { + super(FILE, POST_ORDER, NewSecurityReviewMeasuresVisitor.CounterFactory.INSTANCE); + this.componentIssuesRepository = componentIssuesRepository; + this.measureRepository = measureRepository; + this.periodHolder = periodHolder; + this.analysisMetadataHolder = analysisMetadataHolder; + this.newSecurityReviewRatingMetric = metricRepository.getByKey(NEW_SECURITY_REVIEW_RATING_KEY); + this.newSecurityHotspotsReviewedMetric = metricRepository.getByKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY); + } + + @Override + public void visitProject(Component project, Path<SecurityReviewCounter> path) { + computeMeasure(project, path); + } + + @Override + public void visitDirectory(Component directory, Path<SecurityReviewCounter> path) { + computeMeasure(directory, path); + } + + @Override + public void visitFile(Component file, Path<SecurityReviewCounter> path) { + computeMeasure(file, path); + } + + private void computeMeasure(Component component, Path<SecurityReviewCounter> path) { + if (!periodHolder.hasPeriod() && !analysisMetadataHolder.isPullRequest()) { + return; + } + componentIssuesRepository.getIssues(component) + .stream() + .filter(issue -> issue.type().equals(SECURITY_HOTSPOT)) + .filter(issue -> analysisMetadataHolder.isPullRequest() || periodHolder.getPeriod().isOnPeriod(issue.creationDate()) ) + .forEach(issue -> path.current().processHotspot(issue)); + + Double percent = computePercent(path.current().getHotspotsToReview(), path.current().getHotspotsReviewed()); + measureRepository.add(component, newSecurityHotspotsReviewedMetric, Measure.newMeasureBuilder().setVariation(percent).createNoValue()); + measureRepository.add(component, newSecurityReviewRatingMetric, Measure.newMeasureBuilder().setVariation(computeRating(percent).getIndex()).createNoValue()); + + if (!path.isRoot()) { + path.parent().add(path.current()); + } + } + + private static final class CounterFactory extends SimpleStackElementFactory<SecurityReviewCounter> { + public static final NewSecurityReviewMeasuresVisitor.CounterFactory INSTANCE = new NewSecurityReviewMeasuresVisitor.CounterFactory(); + + private CounterFactory() { + // prevents instantiation + } + + @Override + public SecurityReviewCounter createForAny(Component component) { + return new SecurityReviewCounter(); + } + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewCounter.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewCounter.java new file mode 100644 index 00000000000..07d35e3a98c --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewCounter.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.qualitymodel; + +import org.sonar.api.ce.measure.Issue; + +import static org.sonar.api.issue.Issue.STATUS_REVIEWED; +import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; + +final class SecurityReviewCounter { + private long hotspotsReviewed; + private long hotspotsToReview; + + SecurityReviewCounter() { + // prevents instantiation + } + + void processHotspot(Issue issue) { + if (issue.status().equals(STATUS_REVIEWED)) { + hotspotsReviewed++; + } else if (issue.status().equals(STATUS_TO_REVIEW)) { + hotspotsToReview++; + } + } + + void add(SecurityReviewCounter otherCounter) { + hotspotsReviewed += otherCounter.hotspotsReviewed; + hotspotsToReview += otherCounter.hotspotsToReview; + } + + public long getHotspotsReviewed() { + return hotspotsReviewed; + } + + public long getHotspotsToReview() { + return hotspotsToReview; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java index 0f7c84b45bf..2bb1fdc3d3f 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewMeasuresVisitor.java @@ -19,7 +19,6 @@ */ package org.sonar.ce.task.projectanalysis.qualitymodel; -import org.sonar.api.ce.measure.Issue; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.PathAwareVisitor; import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter; @@ -30,17 +29,15 @@ import org.sonar.ce.task.projectanalysis.measure.RatingMeasures; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; -import static org.sonar.api.issue.Issue.STATUS_REVIEWED; import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY; import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY; import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; import static org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit.FILE; -import static org.sonar.core.issue.DefaultIssue.STATUS_TO_REVIEW; import static org.sonar.server.security.SecurityReviewRating.computePercent; import static org.sonar.server.security.SecurityReviewRating.computeRating; -public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<SecurityReviewMeasuresVisitor.Counter> { +public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<SecurityReviewCounter> { private final ComponentIssuesRepository componentIssuesRepository; private final MeasureRepository measureRepository; @@ -56,27 +53,27 @@ public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<Secur } @Override - public void visitProject(Component project, Path<SecurityReviewMeasuresVisitor.Counter> path) { + public void visitProject(Component project, Path<SecurityReviewCounter> path) { computeMeasure(project, path); } @Override - public void visitDirectory(Component directory, PathAwareVisitor.Path<SecurityReviewMeasuresVisitor.Counter> path) { + public void visitDirectory(Component directory, PathAwareVisitor.Path<SecurityReviewCounter> path) { computeMeasure(directory, path); } @Override - public void visitFile(Component file, PathAwareVisitor.Path<SecurityReviewMeasuresVisitor.Counter> path) { + public void visitFile(Component file, PathAwareVisitor.Path<SecurityReviewCounter> path) { computeMeasure(file, path); } - private void computeMeasure(Component component, PathAwareVisitor.Path<SecurityReviewMeasuresVisitor.Counter> path) { + private void computeMeasure(Component component, PathAwareVisitor.Path<SecurityReviewCounter> path) { componentIssuesRepository.getIssues(component) .stream() .filter(issue -> issue.type().equals(SECURITY_HOTSPOT)) .forEach(issue -> path.current().processHotspot(issue)); - Double percent = computePercent(path.current().hotspotsToReview, path.current().hotspotsReviewed); + Double percent = computePercent(path.current().getHotspotsToReview(), path.current().getHotspotsReviewed()); measureRepository.add(component, securityHotspotsReviewedMetric, Measure.newMeasureBuilder().create(percent, securityHotspotsReviewedMetric.getDecimalScale())); measureRepository.add(component, securityReviewRatingMetric, RatingMeasures.get(computeRating(percent))); @@ -85,29 +82,7 @@ public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<Secur } } - static final class Counter { - private long hotspotsReviewed; - private long hotspotsToReview; - - private Counter() { - // prevents instantiation - } - - void processHotspot(Issue issue) { - if (issue.status().equals(STATUS_REVIEWED)) { - hotspotsReviewed++; - } else if (issue.status().equals(STATUS_TO_REVIEW)) { - hotspotsToReview++; - } - } - - void add(Counter otherCounter) { - hotspotsReviewed += otherCounter.hotspotsReviewed; - hotspotsToReview += otherCounter.hotspotsToReview; - } - } - - private static final class CounterFactory extends PathAwareVisitorAdapter.SimpleStackElementFactory<SecurityReviewMeasuresVisitor.Counter> { + private static final class CounterFactory extends PathAwareVisitorAdapter.SimpleStackElementFactory<SecurityReviewCounter> { public static final SecurityReviewMeasuresVisitor.CounterFactory INSTANCE = new SecurityReviewMeasuresVisitor.CounterFactory(); private CounterFactory() { @@ -115,8 +90,8 @@ public class SecurityReviewMeasuresVisitor extends PathAwareVisitorAdapter<Secur } @Override - public SecurityReviewMeasuresVisitor.Counter createForAny(Component component) { - return new SecurityReviewMeasuresVisitor.Counter(); + public SecurityReviewCounter createForAny(Component component) { + return new SecurityReviewCounter(); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java index c9c1b2c9bf5..f79b13030bf 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java @@ -80,14 +80,14 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { static final String LANGUAGE_KEY_1 = "lKey1"; static final int PROJECT_REF = 1; - static final int DIR_REF = 12; + static final int ROOT_DIR_REF = 12; static final int DIRECTORY_REF = 123; static final int FILE_1_REF = 1231; static final int FILE_2_REF = 1232; static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project") .addChildren( - builder(DIRECTORY, DIR_REF).setKey("dir") + builder(DIRECTORY, ROOT_DIR_REF).setKey("dir") .addChildren( builder(DIRECTORY, DIRECTORY_REF).setKey("directory") .addChildren( @@ -157,7 +157,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { newVulnerabilityIssue(3L, MINOR).setCreationDate(AFTER_LEAK_PERIOD_DATE), // Should not be taken into account newVulnerabilityIssue(10L, BLOCKER).setCreationDate(AFTER_LEAK_PERIOD_DATE).setResolution(RESOLUTION_FIXED)); - fillComponentIssuesVisitorRule.setIssues(DIR_REF, + fillComponentIssuesVisitorRule.setIssues(ROOT_DIR_REF, newVulnerabilityIssue(7L, BLOCKER).setCreationDate(AFTER_LEAK_PERIOD_DATE)); underTest.visit(ROOT_PROJECT); @@ -165,7 +165,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_SECURITY_RATING_KEY, C); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_SECURITY_RATING_KEY, D); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_SECURITY_RATING_KEY, D); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_SECURITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_SECURITY_RATING_KEY, E); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SECURITY_RATING_KEY, E); } @@ -179,7 +179,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_SECURITY_RATING_KEY, A); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_SECURITY_RATING_KEY, A); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SECURITY_RATING_KEY, A); } @@ -194,7 +194,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_SECURITY_RATING_KEY, A); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_SECURITY_RATING_KEY, A); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_SECURITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_SECURITY_RATING_KEY, A); } @@ -211,7 +211,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { newBugIssue(3L, MINOR).setCreationDate(AFTER_LEAK_PERIOD_DATE), // Should not be taken into account newBugIssue(10L, BLOCKER).setCreationDate(AFTER_LEAK_PERIOD_DATE).setResolution(RESOLUTION_FIXED)); - fillComponentIssuesVisitorRule.setIssues(DIR_REF, + fillComponentIssuesVisitorRule.setIssues(ROOT_DIR_REF, newBugIssue(7L, BLOCKER).setCreationDate(AFTER_LEAK_PERIOD_DATE)); underTest.visit(ROOT_PROJECT); @@ -219,7 +219,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_RELIABILITY_RATING_KEY, C); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_RELIABILITY_RATING_KEY, D); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_RELIABILITY_RATING_KEY, D); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_RELIABILITY_RATING_KEY, E); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_RELIABILITY_RATING_KEY, E); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_RELIABILITY_RATING_KEY, E); } @@ -233,7 +233,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_RELIABILITY_RATING_KEY, A); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_RELIABILITY_RATING_KEY, A); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_RELIABILITY_RATING_KEY, A); } @@ -248,7 +248,7 @@ public class NewReliabilityAndSecurityRatingMeasuresVisitorTest { verifyAddedRawMeasureOnLeakPeriod(FILE_1_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(FILE_2_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(DIRECTORY_REF, NEW_RELIABILITY_RATING_KEY, A); - verifyAddedRawMeasureOnLeakPeriod(DIR_REF, NEW_RELIABILITY_RATING_KEY, A); + verifyAddedRawMeasureOnLeakPeriod(ROOT_DIR_REF, NEW_RELIABILITY_RATING_KEY, A); verifyAddedRawMeasureOnLeakPeriod(PROJECT_REF, NEW_RELIABILITY_RATING_KEY, A); } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java new file mode 100644 index 00000000000..08b85755a8d --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/NewSecurityReviewMeasuresVisitorTest.java @@ -0,0 +1,364 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.qualitymodel; + +import java.util.Arrays; +import java.util.Date; +import javax.annotation.Nullable; +import org.assertj.core.data.Offset; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.rules.RuleType; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.FileAttributes; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler; +import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepositoryRule; +import org.sonar.ce.task.projectanalysis.issue.FillComponentIssuesVisitorRule; +import org.sonar.ce.task.projectanalysis.measure.MeasureAssert; +import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule; +import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule; +import org.sonar.ce.task.projectanalysis.period.Period; +import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.core.util.Uuids; +import org.sonar.db.component.BranchType; +import org.sonar.server.measure.Rating; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.issue.Issue.STATUS_REVIEWED; +import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY; +import static org.sonar.api.rule.Severity.MAJOR; +import static org.sonar.api.rule.Severity.MINOR; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.server.measure.Rating.A; +import static org.sonar.server.measure.Rating.B; +import static org.sonar.server.measure.Rating.C; +import static org.sonar.server.measure.Rating.D; +import static org.sonar.server.measure.Rating.E; + +public class NewSecurityReviewMeasuresVisitorTest { + + private static final Offset<Double> VARIATION_COMPARISON_OFFSET = Offset.offset(0.01); + + private static final long LEAK_PERIOD_SNAPSHOT_IN_MILLISEC = 12323l; + private static final Date DEFAULT_CREATION_DATE = new Date(1000l); + private static final Date BEFORE_LEAK_PERIOD_DATE = new Date(LEAK_PERIOD_SNAPSHOT_IN_MILLISEC - 5000L); + private static final Date AFTER_LEAK_PERIOD_DATE = new Date(LEAK_PERIOD_SNAPSHOT_IN_MILLISEC + 5000L); + + private static final String LANGUAGE_KEY_1 = "lKey1"; + + private static final int PROJECT_REF = 1; + private static final int ROOT_DIR_REF = 12; + private static final int DIRECTORY_REF = 123; + private static final int FILE_1_REF = 1231; + private static final int FILE_2_REF = 1232; + + private static final Component ROOT_PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project") + .addChildren( + builder(DIRECTORY, ROOT_DIR_REF).setKey("dir") + .addChildren( + builder(DIRECTORY, DIRECTORY_REF).setKey("directory") + .addChildren( + builder(FILE, FILE_1_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file1").build(), + builder(FILE, FILE_2_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_KEY_1, 1)).setKey("file2").build()) + .build()) + .build()) + .build(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(NEW_SECURITY_REVIEW_RATING) + .add(NEW_SECURITY_HOTSPOTS_REVIEWED); + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + @Rule + public PeriodHolderRule periodsHolder = new PeriodHolderRule().setPeriod(new Period("mode", null, LEAK_PERIOD_SNAPSHOT_IN_MILLISEC)); + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + @Rule + public ComponentIssuesRepositoryRule componentIssuesRepositoryRule = new ComponentIssuesRepositoryRule(treeRootHolder); + @Rule + public FillComponentIssuesVisitorRule fillComponentIssuesVisitorRule = new FillComponentIssuesVisitorRule(componentIssuesRepositoryRule, treeRootHolder); + + private VisitorsCrawler underTest = new VisitorsCrawler(Arrays.asList(fillComponentIssuesVisitorRule, + new NewSecurityReviewMeasuresVisitor(componentIssuesRepositoryRule, measureRepository, periodsHolder, analysisMetadataHolder, metricRepository))); + + @Test + public void compute_measures_when_100_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue().setCreationDate(AFTER_LEAK_PERIOD_DATE)); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE)); + fillComponentIssuesVisitorRule.setIssues(ROOT_DIR_REF, + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE)); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, A, 100.0); + verifyMeasures(FILE_2_REF, A, 100.0); + verifyMeasures(DIRECTORY_REF, A, 100.0); + verifyMeasures(ROOT_DIR_REF, A, 100.0); + verifyMeasures(PROJECT_REF, A, 100.0); + } + + @Test + public void compute_measures_when_more_than_80_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue().setCreationDate(AFTER_LEAK_PERIOD_DATE)); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, A, 100.0); + verifyMeasures(FILE_2_REF, A, 80.0); + verifyMeasures(DIRECTORY_REF, A, 87.5); + verifyMeasures(ROOT_DIR_REF, A, 87.5); + verifyMeasures(PROJECT_REF, A, 87.5); + } + + @Test + public void compute_measures_when_more_than_70_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue().setCreationDate(AFTER_LEAK_PERIOD_DATE)); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, A, 100.0); + verifyMeasures(FILE_2_REF, B, 71.42); + verifyMeasures(DIRECTORY_REF, B, 75.0); + verifyMeasures(ROOT_DIR_REF, B, 75.0); + verifyMeasures(PROJECT_REF, B, 75.0); + } + + @Test + public void compute_measures_when_more_than_50_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue()); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, C, 50.0); + verifyMeasures(FILE_2_REF, C, 60.0); + verifyMeasures(DIRECTORY_REF, C, 57.14); + verifyMeasures(ROOT_DIR_REF, C, 57.14); + verifyMeasures(PROJECT_REF, C, 57.14); + } + + @Test + public void compute_measures_when_more_30_than_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue()); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, D, 33.33); + verifyMeasures(FILE_2_REF, D, 40.0); + verifyMeasures(DIRECTORY_REF, D, 37.5); + verifyMeasures(ROOT_DIR_REF, D, 37.5); + verifyMeasures(PROJECT_REF, D, 37.5); + } + + @Test + public void compute_measures_when_less_than_30_percent_hotspots_reviewed() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue()); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, D, 33.33); + verifyMeasures(FILE_2_REF, E, 0.0); + verifyMeasures(DIRECTORY_REF, E, 16.66); + verifyMeasures(ROOT_DIR_REF, E, 16.66); + verifyMeasures(PROJECT_REF, E, 16.66); + } + + @Test + public void compute_A_rating_and_100_percent_when_no_new_hotspot_on_new_code() { + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newIssue()); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(PROJECT_REF, A, 100.0); + } + + @Test + public void compute_measures_on_pr() { + periodsHolder.setPeriod(null); + Branch b = mock(Branch.class); + when(b.getType()).thenReturn(BranchType.PULL_REQUEST); + analysisMetadataHolder.setBranch(b); + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Should not be taken into account + newIssue()); + fillComponentIssuesVisitorRule.setIssues(FILE_2_REF, + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(AFTER_LEAK_PERIOD_DATE), + // Dates is not taken into account on PR + newHotspot(STATUS_TO_REVIEW, null).setCreationDate(BEFORE_LEAK_PERIOD_DATE), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED).setCreationDate(BEFORE_LEAK_PERIOD_DATE)); + + underTest.visit(ROOT_PROJECT); + + verifyMeasures(FILE_1_REF, C, 50.0); + verifyMeasures(FILE_2_REF, C, 57.14); + verifyMeasures(DIRECTORY_REF, C, 55.55); + verifyMeasures(ROOT_DIR_REF, C, 55.55); + verifyMeasures(PROJECT_REF, C, 55.55); + } + + @Test + public void no_measure_if_there_is_no_period() { + periodsHolder.setPeriod(null); + treeRootHolder.setRoot(ROOT_PROJECT); + fillComponentIssuesVisitorRule.setIssues(FILE_1_REF, + newHotspot(STATUS_TO_REVIEW, null), + newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED)); + + underTest.visit(ROOT_PROJECT); + + assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).values()).isEmpty(); + } + + private void verifyMeasures(int componentRef, Rating expectedReviewRating, double expectedHotspotsReviewed) { + MeasureAssert.assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_REVIEW_RATING_KEY)).hasVariation(expectedReviewRating.getIndex()); + MeasureAssert.assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).hasVariation(expectedHotspotsReviewed, + VARIATION_COMPARISON_OFFSET); + } + + private static DefaultIssue newHotspot(String status, @Nullable String resolution) { + return new DefaultIssue() + .setKey(Uuids.create()) + .setSeverity(MINOR) + .setStatus(status) + .setResolution(resolution) + .setType(RuleType.SECURITY_HOTSPOT) + .setCreationDate(DEFAULT_CREATION_DATE); + } + + private static DefaultIssue newIssue() { + return new DefaultIssue() + .setKey(Uuids.create()) + .setSeverity(MAJOR) + .setType(RuleType.BUG) + .setCreationDate(DEFAULT_CREATION_DATE); + } + +} 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 368414c60a7..cf6c1b87322 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 @@ -322,7 +322,7 @@ </select> <select id="selectIssueGroupsByBaseComponent" 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", (i.issue_creation_date >= #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak + 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", (i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak from issues i inner join components p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid where i.status !='CLOSED' diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java index 04bf2a024f0..df9236be1da 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java @@ -293,6 +293,11 @@ public class IssueDaoTest { result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 999_999_999L); assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0); assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); + + // test leak using exact creation time of criticalBug2 issue + result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, criticalBug2.getIssueCreationTime()); + assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0); + assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java index 8eaec8e8779..f65b6bad7f0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java @@ -168,6 +168,18 @@ public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity)); }), + new IssueMetricFormula(CoreMetrics.NEW_SECURITY_REVIEW_RATING, true, + (context, issues) -> { + Rating rating = computeRating(computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))); + context.setLeakValue(rating); + }), + + new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, true, + (context, issues) -> { + double percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true)); + context.setLeakValue(percent); + }), + new IssueMetricFormula(CoreMetrics.NEW_SQALE_DEBT_RATIO, true, (context, issues) -> context.setLeakValue(100.0 * newDebtDensity(context)), asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)), diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java index 4e626e4f7ce..f23aa1c8cc1 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java @@ -139,7 +139,7 @@ public class IssueMetricFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, 75.0); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, 75.0); withNoIssues() .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, 100.0); @@ -653,6 +653,32 @@ public class IssueMetricFormulaFactoryImplTest { } @Test + public void test_new_security_review_rating() { + with( + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true), + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), + // not in leak + newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false)) + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REVIEW_RATING, Rating.B); + + withNoIssues() + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REVIEW_RATING, Rating.A); + } + + @Test + public void test_new_security_hotspots_reviewed() { + with( + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true), + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), + // not in leak + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0); + + withNoIssues() + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 100.0); + } + + @Test public void test_new_sqale_debt_ratio_and_new_maintainability_rating() { withNoIssues() .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) 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 38c62460413..c8d6c48cc61 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2109,12 +2109,16 @@ 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_hotspots.short_name=Security Hotspots +metric.new_security_hotspots_reviewed.description=Security Hotspots Reviewed on New Code +metric.new_security_hotspots_reviewed.name=Security Hotspots Reviewed on New Code 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 metric.new_security_remediation_effort.description=Security remediation effort on new code metric.new_security_remediation_effort.name=Security Remediation Effort on New Code metric.new_security_remediation_effort.extra_short_name=Remediation Effort +metric.new_security_review_rating.description=Security Review Rating on New Code +metric.new_security_review_rating.name=Security Review Rating on New Code metric.new_sqale_debt_ratio.description=Technical Debt Ratio of new/changed code. metric.new_sqale_debt_ratio.name=Technical Debt Ratio on New Code metric.new_sqale_debt_ratio.short_name=Debt Ratio on new code 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 4e27d4c4ae2..e31e6e85d3d 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 @@ -423,12 +423,12 @@ public final class CoreMetrics { @Deprecated public static final Metric<String> FUNCTION_COMPLEXITY_DISTRIBUTION = new Metric.Builder(FUNCTION_COMPLEXITY_DISTRIBUTION_KEY, "Function Distribution / Complexity", Metric.ValueType.DISTRIB) - .setDescription("Functions distribution /complexity") - .setDirection(Metric.DIRECTION_NONE) - .setQualitative(true) - .setDomain(DOMAIN_COMPLEXITY) - .setHidden(true) - .create(); + .setDescription("Functions distribution /complexity") + .setDirection(Metric.DIRECTION_NONE) + .setQualitative(true) + .setDomain(DOMAIN_COMPLEXITY) + .setHidden(true) + .create(); /** * @deprecated since 6.7 @@ -441,12 +441,12 @@ public final class CoreMetrics { @Deprecated public static final Metric<String> FILE_COMPLEXITY_DISTRIBUTION = new Metric.Builder(FILE_COMPLEXITY_DISTRIBUTION_KEY, "File Distribution / Complexity", Metric.ValueType.DISTRIB) - .setDescription("Files distribution /complexity") - .setDirection(Metric.DIRECTION_NONE) - .setQualitative(true) - .setDomain(DOMAIN_COMPLEXITY) - .setHidden(true) - .create(); + .setDescription("Files distribution /complexity") + .setDirection(Metric.DIRECTION_NONE) + .setQualitative(true) + .setDomain(DOMAIN_COMPLEXITY) + .setHidden(true) + .create(); public static final String COGNITIVE_COMPLEXITY_KEY = "cognitive_complexity"; public static final Metric<Integer> COGNITIVE_COMPLEXITY = new Metric.Builder(COGNITIVE_COMPLEXITY_KEY, "Cognitive Complexity", Metric.ValueType.INT) @@ -758,13 +758,13 @@ public final class CoreMetrics { */ public static final Metric<Integer> NEW_DUPLICATED_LINES_DENSITY = new Metric.Builder(NEW_DUPLICATED_LINES_DENSITY_KEY, "Duplicated Lines on New Code", Metric.ValueType.PERCENT) - .setDescription("Duplicated lines on new code balanced by statements") - .setDirection(Metric.DIRECTION_WORST) - .setQualitative(true) - .setDomain(DOMAIN_DUPLICATIONS) - .setBestValue(0.0) - .setDeleteHistoricalData(true) - .create(); + .setDescription("Duplicated lines on new code balanced by statements") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(true) + .setDomain(DOMAIN_DUPLICATIONS) + .setBestValue(0.0) + .setDeleteHistoricalData(true) + .create(); /** * @deprecated since 4.5. Internal storage of duplication is not an API. @@ -1346,13 +1346,13 @@ public final class CoreMetrics { */ public static final Metric<Long> EFFORT_TO_REACH_MAINTAINABILITY_RATING_A = new Metric.Builder(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, "Effort to Reach Maintainability Rating A", Metric.ValueType.WORK_DUR) - .setDescription("Effort to reach maintainability rating A") - .setDomain(DOMAIN_MAINTAINABILITY) - .setDirection(Metric.DIRECTION_WORST) - .setQualitative(true) - .setBestValue(0.0) - .setOptimizedBestValue(true) - .create(); + .setDescription("Effort to reach maintainability rating A") + .setDomain(DOMAIN_MAINTAINABILITY) + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(true) + .setBestValue(0.0) + .setOptimizedBestValue(true) + .create(); // -------------------------------------------------------------------------------------------------------------------- // @@ -1370,13 +1370,13 @@ public final class CoreMetrics { */ public static final Metric<Long> RELIABILITY_REMEDIATION_EFFORT = new Metric.Builder(RELIABILITY_REMEDIATION_EFFORT_KEY, "Reliability Remediation Effort", Metric.ValueType.WORK_DUR) - .setDescription("Reliability Remediation Effort") - .setDomain(DOMAIN_RELIABILITY) - .setDirection(Metric.DIRECTION_WORST) - .setOptimizedBestValue(true) - .setBestValue(0.0) - .setQualitative(true) - .create(); + .setDescription("Reliability Remediation Effort") + .setDomain(DOMAIN_RELIABILITY) + .setDirection(Metric.DIRECTION_WORST) + .setOptimizedBestValue(true) + .setBestValue(0.0) + .setQualitative(true) + .create(); /** * @since 5.5 @@ -1388,14 +1388,14 @@ public final class CoreMetrics { */ public static final Metric<Long> NEW_RELIABILITY_REMEDIATION_EFFORT = new Metric.Builder(NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, "Reliability Remediation Effort on New Code", Metric.ValueType.WORK_DUR) - .setDescription("Reliability remediation effort on new code") - .setDomain(DOMAIN_RELIABILITY) - .setDirection(Metric.DIRECTION_WORST) - .setOptimizedBestValue(true) - .setBestValue(0.0) - .setQualitative(true) - .setDeleteHistoricalData(true) - .create(); + .setDescription("Reliability remediation effort on new code") + .setDomain(DOMAIN_RELIABILITY) + .setDirection(Metric.DIRECTION_WORST) + .setOptimizedBestValue(true) + .setBestValue(0.0) + .setQualitative(true) + .setDeleteHistoricalData(true) + .create(); /** * @since 5.5 @@ -1466,14 +1466,14 @@ public final class CoreMetrics { */ public static final Metric<Long> NEW_SECURITY_REMEDIATION_EFFORT = new Metric.Builder(NEW_SECURITY_REMEDIATION_EFFORT_KEY, "Security Remediation Effort on New Code", Metric.ValueType.WORK_DUR) - .setDescription("Security remediation effort on new code") - .setDomain(DOMAIN_SECURITY) - .setDirection(Metric.DIRECTION_WORST) - .setOptimizedBestValue(true) - .setBestValue(0.0) - .setQualitative(true) - .setDeleteHistoricalData(true) - .create(); + .setDescription("Security remediation effort on new code") + .setDomain(DOMAIN_SECURITY) + .setDirection(Metric.DIRECTION_WORST) + .setOptimizedBestValue(true) + .setBestValue(0.0) + .setQualitative(true) + .setDeleteHistoricalData(true) + .create(); /** * @since 5.5 @@ -1511,7 +1511,6 @@ public final class CoreMetrics { .setWorstValue(5.0) .create(); - // -------------------------------------------------------------------------------------------------------------------- // // SECURITY REVIEW @@ -1538,6 +1537,26 @@ public final class CoreMetrics { /** * @since 8.2 */ + public static final String NEW_SECURITY_REVIEW_RATING_KEY = "new_security_review_rating"; + + /** + * @since 8.2 + */ + public static final Metric<Integer> NEW_SECURITY_REVIEW_RATING = new Metric.Builder(NEW_SECURITY_REVIEW_RATING_KEY, "Security Review Rating on New Code", + Metric.ValueType.RATING) + .setDescription("Security Review Rating on New Code") + .setDomain(DOMAIN_SECURITY_REVIEW) + .setDirection(Metric.DIRECTION_WORST) + .setDeleteHistoricalData(true) + .setOptimizedBestValue(true) + .setQualitative(true) + .setBestValue(1.0) + .setWorstValue(5.0) + .create(); + + /** + * @since 8.2 + */ public static final String SECURITY_HOTSPOTS_REVIEWED_KEY = "security_hotspots_reviewed"; /** @@ -1552,6 +1571,25 @@ public final class CoreMetrics { .setBestValue(100.0) .create(); + /** + * @since 8.2 + */ + public static final String NEW_SECURITY_HOTSPOTS_REVIEWED_KEY = "new_security_hotspots_reviewed"; + + /** + * @since 8.2 + */ + public static final Metric<Integer> NEW_SECURITY_HOTSPOTS_REVIEWED = new Metric.Builder(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, "Security Hotspots Reviewed on New Code", + Metric.ValueType.PERCENT) + .setDescription("Security Hotspots Reviewed on New Code") + .setDomain(DOMAIN_SECURITY_REVIEW) + .setDirection(Metric.DIRECTION_BETTER) + .setDeleteHistoricalData(true) + .setQualitative(true) + .setWorstValue(0.0) + .setBestValue(100.0) + .create(); + // -------------------------------------------------------------------------------------------------------------------- // // FILE DATA |