diff options
8 files changed, 633 insertions, 725 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index 2f72a4ac5fa..878eb4e66c9 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -21,31 +21,18 @@ package org.sonar.plugins.core; import com.google.common.collect.ImmutableList; -import org.sonar.api.CoreProperties; -import org.sonar.api.Properties; -import org.sonar.api.Property; -import org.sonar.api.PropertyType; -import org.sonar.api.SonarPlugin; +import org.sonar.api.*; import org.sonar.api.checks.NoSonarFilter; import org.sonar.api.notifications.NotificationDispatcherMetadata; import org.sonar.api.resources.Java; -import org.sonar.batch.issue.InitialOpenIssuesSensor; -import org.sonar.batch.issue.InitialOpenIssuesStack; -import org.sonar.batch.issue.IssuesDecorator; -import org.sonar.batch.issue.IssuesDensityDecorator; -import org.sonar.batch.issue.NewIssuesDecorator; -import org.sonar.batch.issue.WeightedIssuesDecorator; +import org.sonar.batch.issue.*; import org.sonar.core.timemachine.Periods; import org.sonar.plugins.core.batch.IndexProjectPostJob; import org.sonar.plugins.core.charts.DistributionAreaChart; import org.sonar.plugins.core.charts.DistributionBarChart; import org.sonar.plugins.core.charts.XradarChart; import org.sonar.plugins.core.colorizers.JavaColorizerFormat; -import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard; -import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard; -import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard; -import org.sonar.plugins.core.dashboards.ProjectReviewsDashboard; -import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard; +import org.sonar.plugins.core.dashboards.*; import org.sonar.plugins.core.issue.IssueTracking; import org.sonar.plugins.core.issue.IssuesWorkflowDecorator; import org.sonar.plugins.core.measurefilters.MyFavouritesFilter; @@ -55,73 +42,12 @@ import org.sonar.plugins.core.notifications.reviews.ChangesInReviewAssignedToMeO import org.sonar.plugins.core.notifications.reviews.NewFalsePositiveReview; import org.sonar.plugins.core.notifications.violations.NewViolationsOnFirstDifferentialPeriod; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; -import org.sonar.plugins.core.sensors.BranchCoverageDecorator; -import org.sonar.plugins.core.sensors.CheckAlertThresholds; -import org.sonar.plugins.core.sensors.CommentDensityDecorator; -import org.sonar.plugins.core.sensors.CoverageDecorator; -import org.sonar.plugins.core.sensors.DirectoriesDecorator; -import org.sonar.plugins.core.sensors.FilesDecorator; -import org.sonar.plugins.core.sensors.GenerateAlertEvents; -import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator; -import org.sonar.plugins.core.sensors.ItCoverageDecorator; -import org.sonar.plugins.core.sensors.ItLineCoverageDecorator; -import org.sonar.plugins.core.sensors.LineCoverageDecorator; -import org.sonar.plugins.core.sensors.ManualMeasureDecorator; -import org.sonar.plugins.core.sensors.ManualViolationInjector; -import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator; -import org.sonar.plugins.core.sensors.OverallCoverageDecorator; -import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator; -import org.sonar.plugins.core.sensors.ProfileEventsSensor; -import org.sonar.plugins.core.sensors.ProfileSensor; -import org.sonar.plugins.core.sensors.ProjectLinksSensor; -import org.sonar.plugins.core.sensors.ReviewNotifications; -import org.sonar.plugins.core.sensors.ReviewWorkflowDecorator; -import org.sonar.plugins.core.sensors.ReviewsMeasuresDecorator; -import org.sonar.plugins.core.sensors.UnitTestDecorator; -import org.sonar.plugins.core.sensors.VersionEventsSensor; -import org.sonar.plugins.core.sensors.ViolationSeverityUpdater; -import org.sonar.plugins.core.sensors.ViolationsDecorator; -import org.sonar.plugins.core.sensors.ViolationsDensityDecorator; -import org.sonar.plugins.core.sensors.WeightedViolationsDecorator; -import org.sonar.plugins.core.timemachine.NewCoverageAggregator; -import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer; -import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer; -import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer; -import org.sonar.plugins.core.timemachine.NewViolationsDecorator; -import org.sonar.plugins.core.timemachine.TendencyDecorator; -import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister; -import org.sonar.plugins.core.timemachine.VariationDecorator; -import org.sonar.plugins.core.timemachine.ViolationPersisterDecorator; -import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator; +import org.sonar.plugins.core.sensors.*; +import org.sonar.plugins.core.timemachine.*; import org.sonar.plugins.core.web.Lcom4Viewer; import org.sonar.plugins.core.web.TestsViewer; -import org.sonar.plugins.core.widgets.ActionPlansWidget; -import org.sonar.plugins.core.widgets.AlertsWidget; -import org.sonar.plugins.core.widgets.CommentsDuplicationsWidget; -import org.sonar.plugins.core.widgets.ComplexityWidget; -import org.sonar.plugins.core.widgets.CoverageWidget; -import org.sonar.plugins.core.widgets.CustomMeasuresWidget; -import org.sonar.plugins.core.widgets.DescriptionWidget; -import org.sonar.plugins.core.widgets.EventsWidget; -import org.sonar.plugins.core.widgets.HotspotMetricWidget; -import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget; -import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget; -import org.sonar.plugins.core.widgets.ItCoverageWidget; -import org.sonar.plugins.core.widgets.MeasureFilterListWidget; -import org.sonar.plugins.core.widgets.MeasureFilterTreemapWidget; -import org.sonar.plugins.core.widgets.RulesWidget; -import org.sonar.plugins.core.widgets.SizeWidget; -import org.sonar.plugins.core.widgets.TimeMachineWidget; -import org.sonar.plugins.core.widgets.TimelineWidget; -import org.sonar.plugins.core.widgets.TreemapWidget; -import org.sonar.plugins.core.widgets.WelcomeWidget; -import org.sonar.plugins.core.widgets.reviews.FalsePositiveReviewsWidget; -import org.sonar.plugins.core.widgets.reviews.MyReviewsWidget; -import org.sonar.plugins.core.widgets.reviews.PlannedReviewsWidget; -import org.sonar.plugins.core.widgets.reviews.ProjectReviewsWidget; -import org.sonar.plugins.core.widgets.reviews.ReviewsMetricsWidget; -import org.sonar.plugins.core.widgets.reviews.ReviewsPerDeveloperWidget; -import org.sonar.plugins.core.widgets.reviews.UnplannedReviewsWidget; +import org.sonar.plugins.core.widgets.*; +import org.sonar.plugins.core.widgets.reviews.*; import java.util.List; @@ -515,7 +441,6 @@ public final class CorePlugin extends SonarPlugin { IssueTracking.class, ViolationPersisterDecorator.class, NewViolationsDecorator.class, - NewIssuesDecorator.class, TimeMachineConfigurationPersister.class, NewCoverageFileAnalyzer.class, NewItCoverageFileAnalyzer.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java index 803ed44381e..65286c6d124 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java @@ -19,9 +19,9 @@ */ package org.sonar.batch.issue; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Maps; -import com.google.common.collect.Multiset; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Predicate; +import com.google.common.collect.*; import org.sonar.api.batch.*; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.issue.Issuable; @@ -29,24 +29,31 @@ import org.sonar.api.issue.Issue; import org.sonar.api.measures.*; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RulePriority; +import org.sonar.batch.components.PastSnapshot; +import org.sonar.batch.components.TimeMachineConfiguration; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; +import static com.google.common.collect.Lists.newArrayList; + +/** + * @since 3.6 + */ @DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES) public class IssuesDecorator implements Decorator { private final ResourcePerspectives perspectives; private final RuleFinder rulefinder; + private final TimeMachineConfiguration timeMachineConfiguration; - public IssuesDecorator(ResourcePerspectives perspectives, RuleFinder rulefinder) { + public IssuesDecorator(ResourcePerspectives perspectives, RuleFinder rulefinder, TimeMachineConfiguration timeMachineConfiguration) { this.perspectives = perspectives; this.rulefinder = rulefinder; + this.timeMachineConfiguration = timeMachineConfiguration; } public boolean shouldExecuteOnProject(Project project) { @@ -55,25 +62,65 @@ public class IssuesDecorator implements Decorator { @DependedUpon public List<Metric> generatesIssuesMetrics() { - return Arrays.asList(CoreMetrics.ISSUES, + return newArrayList( + CoreMetrics.ISSUES, CoreMetrics.BLOCKER_ISSUES, CoreMetrics.CRITICAL_ISSUES, CoreMetrics.MAJOR_ISSUES, CoreMetrics.MINOR_ISSUES, - CoreMetrics.INFO_ISSUES); + CoreMetrics.INFO_ISSUES, + CoreMetrics.NEW_ISSUES, + CoreMetrics.NEW_BLOCKER_ISSUES, + CoreMetrics.NEW_CRITICAL_ISSUES, + CoreMetrics.NEW_MAJOR_ISSUES, + CoreMetrics.NEW_MINOR_ISSUES, + CoreMetrics.NEW_INFO_ISSUES, + CoreMetrics.FALSE_POSITIVE_ISSUES, + CoreMetrics.UNASSIGNED_ISSUES + ); } public void decorate(Resource resource, DecoratorContext context) { Issuable issuable = perspectives.as(Issuable.class, resource); if (issuable != null) { - Collection<Issue> issues = issuable.issues(); - computeTotalIssues(context, issues); - computeIssuesPerSeverities(context, issues); - computeIssuesPerRules(context, issues); + Collection<Issue> issues = getOpenIssues(issuable.issues()); + boolean shouldSaveNewMetrics = shouldSaveNewMetrics(context); + + Multiset<RulePriority> severitiesBag = HashMultiset.create(); + Map<RulePriority, Multiset<Rule>> rulesPerSeverity = Maps.newHashMap(); + ListMultimap<RulePriority, Issue> issuesPerSeverities = ArrayListMultimap.create(); + int countUnassigned = 0; + int falsePositives = 0; + + for (Issue issue : issues) { + severitiesBag.add(RulePriority.valueOf(issue.severity())); + Multiset<Rule> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity())); + rulesBag.add(rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule())); + issuesPerSeverities.put(RulePriority.valueOf(issue.severity()), issue); + + if (issue.assigneeLogin() == null) { + countUnassigned++; + } + if (Issue.RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) { + falsePositives++; + } + } + + for (RulePriority ruleSeverity : RulePriority.values()) { + saveIssuesForSeverity(context, ruleSeverity, severitiesBag); + saveIssuesPerRules(context, ruleSeverity, rulesPerSeverity); + saveNewIssuesForSeverity(context, ruleSeverity, issuesPerSeverities, shouldSaveNewMetrics); + saveNewIssuesPerRule(context, ruleSeverity, issues, shouldSaveNewMetrics); + } + + saveTotalIssues(context, issues); + saveNewIssues(context, issues, shouldSaveNewMetrics); + saveUnassignedIssues(context, countUnassigned); + saveFalsePositiveIssues(context, falsePositives); } } - private void computeTotalIssues(DecoratorContext context, Collection<Issue> issues) { + private void saveTotalIssues(DecoratorContext context, Collection<Issue> issues) { if (context.getMeasure(CoreMetrics.ISSUES) == null) { Collection<Measure> childrenIssues = context.getChildrenMeasures(CoreMetrics.ISSUES); Double sum = MeasureUtils.sum(true, childrenIssues); @@ -81,53 +128,119 @@ public class IssuesDecorator implements Decorator { } } - private void computeIssuesPerSeverities(DecoratorContext context, Collection<Issue> issues) { - Multiset<RulePriority> severitiesBag = HashMultiset.create(); - for (Issue issue : issues) { - severitiesBag.add(RulePriority.valueOf(issue.severity())); + private void saveNewIssues(DecoratorContext context, Collection<Issue> issues, boolean shouldSaveNewMetrics) { + if (shouldSaveNewMetrics) { + Measure measure = new Measure(CoreMetrics.NEW_ISSUES); + saveNewIssues(context, measure, issues); + } + } + + private void saveIssuesForSeverity(DecoratorContext context, RulePriority ruleSeverity, Multiset<RulePriority> severitiesBag) { + Metric metric = SeverityUtils.severityToIssueMetric(ruleSeverity); + if (context.getMeasure(metric) == null) { + Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric)); + int sum = MeasureUtils.sum(true, children).intValue() + severitiesBag.count(ruleSeverity); + context.saveMeasure(metric, (double) sum); } + } - for (RulePriority ruleSeverity : RulePriority.values()) { - Metric metric = SeverityUtils.severityToIssueMetric(ruleSeverity); - if (context.getMeasure(metric) == null) { - Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric)); - int sum = MeasureUtils.sum(true, children).intValue() + severitiesBag.count(ruleSeverity); - context.saveMeasure(metric, (double) sum); - } + private void saveNewIssuesForSeverity(DecoratorContext context, RulePriority severity, ListMultimap<RulePriority, Issue> issuesPerSeverities, boolean shouldSaveNewMetrics) { + if (shouldSaveNewMetrics) { + Metric metric = SeverityUtils.severityToNewMetricIssue(severity); + Measure measure = new Measure(metric); + saveNewIssues(context, measure, issuesPerSeverities.get(severity)); } } - private void computeIssuesPerRules(DecoratorContext context, Collection<Issue> issues) { - Map<RulePriority, Multiset<Rule>> rulesPerSeverity = Maps.newHashMap(); - for (Issue issue : issues) { - Multiset<Rule> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity())); - rulesBag.add(rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule())); + private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<Rule>> rulesPerSeverity) { + Metric metric = SeverityUtils.severityToIssueMetric(severity); + + Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric)); + for (Measure child : children) { + RuleMeasure childRuleMeasure = (RuleMeasure) child; + Rule rule = childRuleMeasure.getRule(); + if (rule != null && MeasureUtils.hasValue(childRuleMeasure)) { + Multiset<Rule> rulesBag = initRules(rulesPerSeverity, severity); + rulesBag.add(rule, childRuleMeasure.getIntValue()); + } } - for (RulePriority severity : RulePriority.values()) { - Metric metric = SeverityUtils.severityToIssueMetric(severity); + Multiset<Rule> rulesBag = rulesPerSeverity.get(severity); + if (rulesBag != null) { + for (Multiset.Entry<Rule> entry : rulesBag.entrySet()) { + RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount()); + measure.setSeverity(severity); + context.saveMeasure(measure); + } + } + } + + private void saveNewIssuesPerRule(DecoratorContext context, RulePriority severity, Collection<Issue> issues, boolean shouldSaveNewMetrics) { + if (shouldSaveNewMetrics) { + Metric metric = SeverityUtils.severityToNewMetricIssue(severity); + ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create(); + ListMultimap<Rule, Issue> issuesPerRule = ArrayListMultimap.create(); + Set<Rule> rules = Sets.newHashSet(); Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric)); for (Measure child : children) { RuleMeasure childRuleMeasure = (RuleMeasure) child; Rule rule = childRuleMeasure.getRule(); - if (rule != null && MeasureUtils.hasValue(childRuleMeasure)) { - Multiset<Rule> rulesBag = initRules(rulesPerSeverity, severity); - rulesBag.add(rule, childRuleMeasure.getIntValue()); + if (rule != null) { + childMeasuresPerRule.put(rule, childRuleMeasure); + rules.add(rule); + } + } + + for (Issue issue : issues) { + if (RulePriority.valueOf(issue.severity()).equals(severity)) { + Rule rule = rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule()); + rules.add(rule); + issuesPerRule.put(rule, issue); } } - Multiset<Rule> rulesBag = rulesPerSeverity.get(severity); - if (rulesBag != null) { - for (Multiset.Entry<Rule> entry : rulesBag.entrySet()) { - RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount()); - measure.setSeverity(severity); - context.saveMeasure(measure); + for (Rule rule : rules) { + RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null); + measure.setSeverity(severity); + for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { + int variationIndex = pastSnapshot.getIndex(); + int count = countIssuesAfterDate(issuesPerRule.get(rule), pastSnapshot.getTargetDate()); + double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count; + measure.setVariation(variationIndex, sum); } + context.saveMeasure(measure); } } } + private void saveNewIssues(DecoratorContext context, Measure measure, Collection<Issue> issues) { + for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { + int variationIndex = pastSnapshot.getIndex(); + Collection<Measure> children = context.getChildrenMeasures(measure.getMetric()); + int count = countIssuesAfterDate(issues, pastSnapshot.getTargetDate()); + double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count; + measure.setVariation(variationIndex, sum); + } + context.saveMeasure(measure); + } + + private void saveUnassignedIssues(DecoratorContext context, int countUnassigned) { + context.saveMeasure(CoreMetrics.UNASSIGNED_ISSUES, (double) (countUnassigned + sumChildren(context, CoreMetrics.UNASSIGNED_ISSUES))); + } + + private void saveFalsePositiveIssues(DecoratorContext context, int falsePositives) { + context.saveMeasure(CoreMetrics.FALSE_POSITIVE_ISSUES, (double) (falsePositives + sumChildren(context, CoreMetrics.FALSE_POSITIVE_ISSUES))); + } + + private int sumChildren(DecoratorContext context, Metric metric) { + int sum = 0; + if (!ResourceUtils.isFile(context.getResource())) { + sum = MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue(); + } + return sum; + } + private Multiset<Rule> initRules(Map<RulePriority, Multiset<Rule>> rulesPerSeverity, RulePriority severity) { Multiset<Rule> rulesBag = rulesPerSeverity.get(severity); if (rulesBag == null) { @@ -137,6 +250,40 @@ public class IssuesDecorator implements Decorator { return rulesBag; } + @VisibleForTesting + int countIssuesAfterDate(Collection<Issue> issues, Date targetDate) { + if (issues == null) { + return 0; + } + int count = 0; + for (Issue issue : issues) { + if (isAfter(issue, targetDate)) { + count++; + } + } + return count; + } + + private boolean isAfter(Issue issue, Date date) { + if (date == null) { + return true; + } + return issue.createdAt() != null && issue.createdAt().after(date); + } + + private boolean shouldSaveNewMetrics(DecoratorContext context) { + return context.getProject().isLatestAnalysis() && context.getMeasure(CoreMetrics.NEW_ISSUES) == null; + } + + private Collection<Issue> getOpenIssues(Collection<Issue> issues) { + return newArrayList(Iterables.filter(issues, new Predicate<Issue>() { + @Override + public boolean apply(final Issue issue) { + return !Issue.STATUS_CLOSED.equals(issue.status()); + } + })); + } + @Override public String toString() { return getClass().getSimpleName(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDensityDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDensityDecorator.java index ae6e370d89d..47199681cca 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDensityDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDensityDecorator.java @@ -19,10 +19,7 @@ */ package org.sonar.batch.issue; -import org.sonar.api.batch.Decorator; -import org.sonar.api.batch.DecoratorContext; -import org.sonar.api.batch.DependedUpon; -import org.sonar.api.batch.DependsUpon; +import org.sonar.api.batch.*; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.MeasureUtils; @@ -33,6 +30,10 @@ import org.sonar.api.resources.Resource; import java.util.Arrays; import java.util.List; +/** + * @since 3.6 + */ +@DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES) public class IssuesDensityDecorator implements Decorator { public boolean shouldExecuteOnProject(Project project) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/NewIssuesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/issue/NewIssuesDecorator.java deleted file mode 100644 index b905408625b..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/NewIssuesDecorator.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.batch.issue; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Sets; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.*; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.measures.*; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationManager; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; -import org.sonar.api.resources.ResourceUtils; -import org.sonar.api.resources.Scopes; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.RulePriority; -import org.sonar.batch.components.PastSnapshot; -import org.sonar.batch.components.TimeMachineConfiguration; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - -@DependsUpon(DecoratorBarriers.END_OF_ISSUES_UPDATES) -public class NewIssuesDecorator implements Decorator { - - private final ResourcePerspectives perspectives; - private final RuleFinder rulefinder; - private TimeMachineConfiguration timeMachineConfiguration; - private NotificationManager notificationManager; - - public NewIssuesDecorator(TimeMachineConfiguration timeMachineConfiguration, NotificationManager notificationManager, ResourcePerspectives perspectives, RuleFinder rulefinder) { - this.timeMachineConfiguration = timeMachineConfiguration; - this.notificationManager = notificationManager; - this.perspectives = perspectives; - this.rulefinder = rulefinder; - } - - public boolean shouldExecuteOnProject(Project project) { - return project.isLatestAnalysis(); - } - - @DependedUpon - public List<Metric> generatesMetric() { - return Arrays.asList( - CoreMetrics.NEW_ISSUES, - CoreMetrics.NEW_BLOCKER_ISSUES, - CoreMetrics.NEW_CRITICAL_ISSUES, - CoreMetrics.NEW_MAJOR_ISSUES, - CoreMetrics.NEW_MINOR_ISSUES, - CoreMetrics.NEW_INFO_ISSUES); - } - - @SuppressWarnings("rawtypes") - public void decorate(Resource resource, DecoratorContext context) { - if (shouldDecorateResource(resource, context)) { - Issuable issuable = perspectives.as(Issuable.class, context.getResource()); - if (issuable != null) { - Collection<Issue> issues = issuable.issues(); - - computeNewIssues(context, issues); - computeNewIssuesPerSeverity(context, issues); - computeNewIssuesPerRule(context, issues); - } - } - if (ResourceUtils.isRootProject(resource)) { - notifyNewIssues((Project) resource, context); - } - } - - private boolean shouldDecorateResource(Resource<?> resource, DecoratorContext context) { - return (StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope()) || StringUtils - .equals(Scopes.FILE, resource.getScope())) - && (context.getMeasure(CoreMetrics.NEW_ISSUES) == null); - } - - private void computeNewIssues(DecoratorContext context, Collection<Issue> issues) { - Measure measure = new Measure(CoreMetrics.NEW_ISSUES); - for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { - int variationIndex = pastSnapshot.getIndex(); - Collection<Measure> children = context.getChildrenMeasures(CoreMetrics.NEW_ISSUES); - int count = countIssues(issues, pastSnapshot.getTargetDate()); - double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count; - measure.setVariation(variationIndex, sum); - } - context.saveMeasure(measure); - } - - private void computeNewIssuesPerSeverity(DecoratorContext context, Collection<Issue> issues) { - ListMultimap<RulePriority, Issue> issuesPerSeverities = ArrayListMultimap.create(); - for (Issue issue : issues) { - issuesPerSeverities.put(RulePriority.valueOf(issue.severity()), issue); - } - - for (RulePriority severity : RulePriority.values()) { - Metric metric = severityToMetric(severity); - Measure measure = new Measure(metric); - for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { - int variationIndex = pastSnapshot.getIndex(); - int count = countIssues(issuesPerSeverities.get(severity), pastSnapshot.getTargetDate()); - Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric)); - double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + count; - measure.setVariation(variationIndex, sum); - } - context.saveMeasure(measure); - } - } - - private void computeNewIssuesPerRule(DecoratorContext context, Collection<Issue> issues) { - for (RulePriority severity : RulePriority.values()) { - Metric metric = severityToMetric(severity); - ListMultimap<Rule, Measure> childMeasuresPerRule = ArrayListMultimap.create(); - ListMultimap<Rule, Issue> issuesPerRule = ArrayListMultimap.create(); - Set<Rule> rules = Sets.newHashSet(); - - Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric)); - for (Measure child : children) { - RuleMeasure childRuleMeasure = (RuleMeasure) child; - Rule rule = childRuleMeasure.getRule(); - if (rule != null) { - childMeasuresPerRule.put(rule, childRuleMeasure); - rules.add(rule); - } - } - - for (Issue issue : issues) { - if (RulePriority.valueOf(issue.severity()).equals(severity)) { - Rule rule = rulefinder.findByKey(issue.ruleKey().repository(), issue.ruleKey().rule()); - rules.add(rule); - issuesPerRule.put(rule, issue); - } - } - - for (Rule rule : rules) { - RuleMeasure measure = RuleMeasure.createForRule(metric, rule, null); - measure.setSeverity(severity); - for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { - int variationIndex = pastSnapshot.getIndex(); - int count = countIssues(issuesPerRule.get(rule), pastSnapshot.getTargetDate()); - double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRule.get(rule)) + count; - measure.setVariation(variationIndex, sum); - } - context.saveMeasure(measure); - } - } - } - - int countIssues(Collection<Issue> issues, Date targetDate) { - if (issues == null) { - return 0; - } - int count = 0; - for (Issue issue : issues) { - if (isAfter(issue, targetDate)) { - count++; - } - } - return count; - } - - private boolean isAfter(Issue issue, Date date) { - if (date == null) { - return true; - } - return issue.createdAt() != null && issue.createdAt().after(date); - } - - private Metric severityToMetric(RulePriority severity) { - Metric metric; - if (severity.equals(RulePriority.BLOCKER)) { - metric = CoreMetrics.NEW_BLOCKER_ISSUES; - } else if (severity.equals(RulePriority.CRITICAL)) { - metric = CoreMetrics.NEW_CRITICAL_ISSUES; - } else if (severity.equals(RulePriority.MAJOR)) { - metric = CoreMetrics.NEW_MAJOR_ISSUES; - } else if (severity.equals(RulePriority.MINOR)) { - metric = CoreMetrics.NEW_MINOR_ISSUES; - } else if (severity.equals(RulePriority.INFO)) { - metric = CoreMetrics.NEW_INFO_ISSUES; - } else { - throw new IllegalArgumentException("Unsupported severity: " + severity); - } - return metric; - } - - protected void notifyNewIssues(Project project, DecoratorContext context) { - List<PastSnapshot> projectPastSnapshots = timeMachineConfiguration.getProjectPastSnapshots(); - if (projectPastSnapshots.size() >= 1) { - // we always check new issues against period1 - PastSnapshot pastSnapshot = projectPastSnapshots.get(0); - Double newIssuesCount = context.getMeasure(CoreMetrics.NEW_ISSUES).getVariation1(); - // Do not send notification if this is the first analysis or if there's no violation - if (pastSnapshot.getTargetDate() != null && newIssuesCount != null && newIssuesCount > 0) { - // Maybe we should check if this is the first analysis or not? - DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd"); - Notification notification = new Notification("new-issues") - .setDefaultMessage(newIssuesCount.intValue() + " new issues on " + project.getLongName() + ".") - .setFieldValue("count", String.valueOf(newIssuesCount.intValue())) - .setFieldValue("projectName", project.getLongName()) - .setFieldValue("projectKey", project.getKey()) - .setFieldValue("projectId", String.valueOf(project.getId())) - .setFieldValue("fromDate", dateformat.format(pastSnapshot.getTargetDate())); - notificationManager.scheduleForSending(notification); - } - } - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/SeverityUtils.java b/sonar-batch/src/main/java/org/sonar/batch/issue/SeverityUtils.java index 7377089dfaf..c90516eaaef 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/SeverityUtils.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/SeverityUtils.java @@ -45,4 +45,22 @@ final class SeverityUtils { } return metric; } + + static Metric severityToNewMetricIssue(RulePriority severity) { + Metric metric; + if (severity.equals(RulePriority.BLOCKER)) { + metric = CoreMetrics.NEW_BLOCKER_ISSUES; + } else if (severity.equals(RulePriority.CRITICAL)) { + metric = CoreMetrics.NEW_CRITICAL_ISSUES; + } else if (severity.equals(RulePriority.MAJOR)) { + metric = CoreMetrics.NEW_MAJOR_ISSUES; + } else if (severity.equals(RulePriority.MINOR)) { + metric = CoreMetrics.NEW_MINOR_ISSUES; + } else if (severity.equals(RulePriority.INFO)) { + metric = CoreMetrics.NEW_INFO_ISSUES; + } else { + throw new IllegalArgumentException("Unsupported severity: " + severity); + } + return metric; + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuesDecoratorTest.java index 092fc2de768..4a331b8ea72 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuesDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuesDecoratorTest.java @@ -17,45 +17,57 @@ * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ + package org.sonar.batch.issue; import com.google.common.collect.Lists; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.time.DateUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.issue.Issuable; import org.sonar.api.issue.Issue; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.measures.*; +import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.Scopes; +import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.RulePriority; import org.sonar.api.test.IsRuleMeasure; +import org.sonar.batch.components.PastSnapshot; +import org.sonar.batch.components.TimeMachineConfiguration; import org.sonar.core.issue.DefaultIssue; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.List; import static com.google.common.collect.Lists.newArrayList; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyDouble; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.*; public class IssuesDecoratorTest { + private IssuesDecorator decorator; + private TimeMachineConfiguration timeMachineConfiguration; + private RuleFinder rulefinder; + private Issuable issuable; + private DecoratorContext context; + private Resource resource; + private Project project; private Rule ruleA1; private Rule ruleA2; private Rule ruleB1; - private IssuesDecorator decorator; - private Resource resource; - private DecoratorContext context; - private Issuable issuable; - private RuleFinder rulefinder; + private Date rightNow; + private Date tenDaysAgo; + private Date fiveDaysAgo; @Before public void before() { @@ -68,21 +80,46 @@ public class IssuesDecoratorTest { when(rulefinder.findByKey(ruleA2.getRepositoryKey(), ruleA2.getKey())).thenReturn(ruleA2); when(rulefinder.findByKey(ruleB1.getRepositoryKey(), ruleB1.getKey())).thenReturn(ruleB1); + rightNow = new Date(); + tenDaysAgo = DateUtils.addDays(rightNow, -10); + fiveDaysAgo = DateUtils.addDays(rightNow, -5); + + PastSnapshot pastSnapshot = mock(PastSnapshot.class); + when(pastSnapshot.getIndex()).thenReturn(1); + when(pastSnapshot.getTargetDate()).thenReturn(fiveDaysAgo); + + PastSnapshot pastSnapshot2 = mock(PastSnapshot.class); + when(pastSnapshot2.getIndex()).thenReturn(2); + when(pastSnapshot2.getTargetDate()).thenReturn(tenDaysAgo); + + timeMachineConfiguration = mock(TimeMachineConfiguration.class); + when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(pastSnapshot, pastSnapshot2)); + + project = mock(Project.class); + when(project.isLatestAnalysis()).thenReturn(true); + resource = mock(Resource.class); context = mock(DecoratorContext.class); when(context.getResource()).thenReturn(resource); + when(context.getProject()).thenReturn(project); + when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(null); issuable = mock(Issuable.class); ResourcePerspectives perspectives = mock(ResourcePerspectives.class); when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); - decorator = new IssuesDecorator(perspectives, rulefinder); + decorator = new IssuesDecorator(perspectives, rulefinder, timeMachineConfiguration); + } + + @Test + public void should_be_depended_upon_metric() { + assertThat(decorator.generatesIssuesMetrics()).hasSize(14); } @Test public void should_count_issues() { when(resource.getScope()).thenReturn(Scopes.PROJECT); when(issuable.issues()).thenReturn(createIssues()); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList()); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList()); decorator.decorate(resource, context); @@ -93,7 +130,7 @@ public class IssuesDecoratorTest { public void should_do_nothing_when_issuable_is_null() { ResourcePerspectives perspectives = mock(ResourcePerspectives.class); when(perspectives.as(Issuable.class, resource)).thenReturn(null); - IssuesDecorator decorator = new IssuesDecorator(perspectives, rulefinder); + IssuesDecorator decorator = new IssuesDecorator(perspectives, rulefinder, timeMachineConfiguration); decorator.decorate(resource, context); @@ -107,7 +144,7 @@ public class IssuesDecoratorTest { public void should_not_count_issues_if_measure_already_exists() { when(resource.getScope()).thenReturn(Scopes.PROJECT); when(issuable.issues()).thenReturn(createIssues()); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList()); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList()); when(context.getMeasure(CoreMetrics.ISSUES)).thenReturn(new Measure(CoreMetrics.ISSUES, 3000.0)); when(context.getMeasure(CoreMetrics.MAJOR_ISSUES)).thenReturn(new Measure(CoreMetrics.MAJOR_ISSUES, 500.0)); @@ -122,7 +159,7 @@ public class IssuesDecoratorTest { public void should_save_zero_on_projects() { when(resource.getScope()).thenReturn(Scopes.PROJECT); when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList()); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList()); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList()); decorator.decorate(resource, context); @@ -133,7 +170,7 @@ public class IssuesDecoratorTest { public void should_save_zero_on_directories() { when(resource.getScope()).thenReturn(Scopes.DIRECTORY); when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList()); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList()); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList()); decorator.decorate(resource, context); @@ -144,7 +181,7 @@ public class IssuesDecoratorTest { public void should_count_issues_by_severity() { when(resource.getScope()).thenReturn(Scopes.PROJECT); when(issuable.issues()).thenReturn(createIssues()); - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.<Measure>newArrayList()); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList()); decorator.decorate(resource, context); @@ -171,6 +208,31 @@ public class IssuesDecoratorTest { } @Test + public void should_save_unassigned_issues() { + List<Issue> issues = newArrayList(); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name())); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setStatus(Issue.STATUS_REOPENED).setSeverity(RulePriority.CRITICAL.name())); + issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setStatus(Issue.STATUS_OPEN).setAssigneeLogin("arthur").setSeverity(RulePriority.CRITICAL.name())); + when(issuable.issues()).thenReturn(issues); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.UNASSIGNED_ISSUES, 2.0); + } + + @Test + public void should_save_false_positive_issues() { + List<Issue> issues = newArrayList(); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name())); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setResolution(Issue.RESOLUTION_FIXED).setStatus(Issue.STATUS_OPEN).setSeverity(RulePriority.CRITICAL.name())); + when(issuable.issues()).thenReturn(issues); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.FALSE_POSITIVE_ISSUES, 1.0); + } + + @Test public void same_rule_should_have_different_severities() { List<Issue> issues = newArrayList(); issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name())); @@ -184,12 +246,159 @@ public class IssuesDecoratorTest { verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MINOR_ISSUES, ruleA1, 1.0))); } + @Test + public void should_count_issues_after_date() { + List<Issue> issues = createIssuesForNewMetrics(); + + assertThat(decorator.countIssuesAfterDate(null, fiveDaysAgo)).isEqualTo(0); + assertThat(decorator.countIssuesAfterDate(issues, fiveDaysAgo)).isEqualTo(1); // 1 rightNow + assertThat(decorator.countIssuesAfterDate(issues, tenDaysAgo)).isEqualTo(3); // 1 rightNow + 2 fiveDaysAgo + } + + @Test + public void should_clear_cache_after_execution() { + Issue issue1 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA1.getRepositoryKey(), ruleA1.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow); + Issue issue2 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA2.getRepositoryKey(), ruleA2.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow); + when(issuable.issues()).thenReturn(newArrayList(issue1)).thenReturn(newArrayList(issue2)); + + decorator.decorate(resource, context); + decorator.decorate(resource, context); + + verify(context, times(2)).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0))); + verify(context, never()).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 2.0, 2.0))); + } + + @Test + public void should_save_severity_new_issues() { + when(issuable.issues()).thenReturn(createIssuesForNewMetrics()); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_BLOCKER_ISSUES, 0.0, 0.0))); + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0))); + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MAJOR_ISSUES, 0.0, 1.0))); + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MINOR_ISSUES, 0.0, 1.0))); + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_INFO_ISSUES, 0.0, 0.0))); + } + + @Test + public void should_save_rule_new_issues() { + when(issuable.issues()).thenReturn(createIssuesForNewMetrics()); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, ruleA1, 1.0, 1.0))); + verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MAJOR_ISSUES, ruleA2, 0.0, 1.0))); + verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_ISSUES, ruleB1, 0.0, 1.0))); + } + + @Test + public void should_not_save_new_issues_if_not_last_analysis() { + when(project.isLatestAnalysis()).thenReturn(false); + when(issuable.issues()).thenReturn(createIssuesForNewMetrics()); + + decorator.decorate(resource, context); + + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_BLOCKER_ISSUES))); + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES))); + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MAJOR_ISSUES))); + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MINOR_ISSUES))); + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_INFO_ISSUES))); + verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_ISSUES))); + } + private List<Issue> createIssues() { List<Issue> issues = newArrayList(); - issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name())); - issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name())); - issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name())); - issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name())); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_OPEN)); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setStatus(Issue.STATUS_REOPENED)); + issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setStatus(Issue.STATUS_REOPENED)); + issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setStatus(Issue.STATUS_OPEN)); + return issues; + } + + private List<Issue> createIssuesForNewMetrics() { + List<Issue> issues = newArrayList(); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow).setStatus(Issue.STATUS_OPEN)); + issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(tenDaysAgo).setStatus(Issue.STATUS_OPEN)); + issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreatedAt(fiveDaysAgo).setStatus(Issue.STATUS_REOPENED)); + issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreatedAt(tenDaysAgo).setStatus(Issue.STATUS_REOPENED)); + issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreatedAt(fiveDaysAgo).setStatus(Issue.STATUS_OPEN)); + issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreatedAt(tenDaysAgo).setStatus(Issue.STATUS_OPEN)); return issues; } + + private class IsVariationRuleMeasure extends BaseMatcher<Measure> { + private Metric metric = null; + private Rule rule = null; + private Double var1 = null; + private Double var2 = null; + + public IsVariationRuleMeasure(Metric metric, Rule rule, Double var1, Double var2) { + this.metric = metric; + this.rule = rule; + this.var1 = var1; + this.var2 = var2; + } + + public boolean matches(Object o) { + if (!(o instanceof RuleMeasure)) { + return false; + } + RuleMeasure m = (RuleMeasure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(rule, m.getRule()) && + ObjectUtils.equals(var1, m.getVariation1()) && + ObjectUtils.equals(var2, m.getVariation2()); + } + + public void describeTo(Description arg0) { + } + } + + private class IsVariationMeasure extends BaseMatcher<Measure> { + private Metric metric = null; + private Double var1 = null; + private Double var2 = null; + + public IsVariationMeasure(Metric metric, Double var1, Double var2) { + this.metric = metric; + this.var1 = var1; + this.var2 = var2; + } + + public boolean matches(Object o) { + if (!(o instanceof Measure)) { + return false; + } + Measure m = (Measure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(var1, m.getVariation1()) && + ObjectUtils.equals(var2, m.getVariation2()) && + !(m instanceof RuleMeasure); + } + + public void describeTo(Description o) { + } + } + + private class IsMetricMeasure extends BaseMatcher<Measure> { + private Metric metric = null; + + public IsMetricMeasure(Metric metric) { + this.metric = metric; + } + + public boolean matches(Object o) { + if (!(o instanceof Measure)) { + return false; + } + Measure m = (Measure) o; + return ObjectUtils.equals(metric, m.getMetric()); + } + + public void describeTo(Description o) { + } + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/NewIssuesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/NewIssuesDecoratorTest.java deleted file mode 100644 index 3bc4f7a32b7..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/NewIssuesDecoratorTest.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.batch.issue; - -import com.google.common.collect.Lists; -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.time.DateUtils; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.batch.DecoratorContext; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.Metric; -import org.sonar.api.measures.RuleMeasure; -import org.sonar.api.notifications.Notification; -import org.sonar.api.notifications.NotificationManager; -import org.sonar.api.resources.File; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Resource; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.rules.RulePriority; -import org.sonar.batch.components.PastSnapshot; -import org.sonar.batch.components.TimeMachineConfiguration; -import org.sonar.core.issue.DefaultIssue; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - -import static com.google.common.collect.Lists.newArrayList; -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.*; - -public class NewIssuesDecoratorTest { - private Rule rule1; - private Rule rule2; - private Rule rule3; - private NewIssuesDecorator decorator; - private Issuable issuable; - private RuleFinder rulefinder; - private DecoratorContext context; - private Resource<?> resource; - private NotificationManager notificationManager; - private Date rightNow; - private Date tenDaysAgo; - private Date fiveDaysAgo; - private TimeMachineConfiguration timeMachineConfiguration; - - @Before - public void before() { - rightNow = new Date(); - tenDaysAgo = DateUtils.addDays(rightNow, -10); - fiveDaysAgo = DateUtils.addDays(rightNow, -5); - - PastSnapshot pastSnapshot = mock(PastSnapshot.class); - when(pastSnapshot.getIndex()).thenReturn(1); - when(pastSnapshot.getTargetDate()).thenReturn(fiveDaysAgo); - - PastSnapshot pastSnapshot2 = mock(PastSnapshot.class); - when(pastSnapshot2.getIndex()).thenReturn(2); - when(pastSnapshot2.getTargetDate()).thenReturn(tenDaysAgo); - - timeMachineConfiguration = mock(TimeMachineConfiguration.class); - when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(pastSnapshot, pastSnapshot2)); - - context = mock(DecoratorContext.class); - resource = new File("com/foo/bar"); - when(context.getResource()).thenReturn(resource); - - notificationManager = mock(NotificationManager.class); - - rule1 = Rule.create().setRepositoryKey("rule1").setKey("rule1").setName("name1"); - rule2 = Rule.create().setRepositoryKey("rule2").setKey("rule2").setName("name2"); - rule3 = Rule.create().setRepositoryKey("rule3").setKey("rule3").setName("name3"); - - rulefinder = mock(RuleFinder.class); - when(rulefinder.findByKey(rule1.getRepositoryKey(), rule1.getKey())).thenReturn(rule1); - when(rulefinder.findByKey(rule2.getRepositoryKey(), rule2.getKey())).thenReturn(rule2); - when(rulefinder.findByKey(rule3.getRepositoryKey(), rule3.getKey())).thenReturn(rule3); - - issuable = mock(Issuable.class); - ResourcePerspectives perspectives = mock(ResourcePerspectives.class); - when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); - decorator = new NewIssuesDecorator(timeMachineConfiguration, notificationManager, perspectives, rulefinder); - } - - @Test - public void should_execute_if_last_analysis() { - Project project = mock(Project.class); - - when(project.isLatestAnalysis()).thenReturn(false); - assertThat(decorator.shouldExecuteOnProject(project)).isFalse(); - - when(project.isLatestAnalysis()).thenReturn(true); - assertThat(decorator.shouldExecuteOnProject(project)).isTrue(); - } - - @Test - public void should_be_depended_upon_metric() { - assertThat(decorator.generatesMetric()).hasSize(6); - } - - @Test - public void should_count_issues_after_date() { - List<Issue> issues = createIssues(); - - assertThat(decorator.countIssues(null, fiveDaysAgo)).isEqualTo(0); - assertThat(decorator.countIssues(issues, fiveDaysAgo)).isEqualTo(1); // 1 rightNow - assertThat(decorator.countIssues(issues, tenDaysAgo)).isEqualTo(3); // 1 rightNow + 2 fiveDaysAgo - } - - @Test - public void should_clear_cache_after_execution() { - Issue issue1 = new DefaultIssue().setRuleKey(RuleKey.of(rule1.getRepositoryKey(), rule1.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow); - Issue issue2 = new DefaultIssue().setRuleKey(RuleKey.of(rule2.getRepositoryKey(), rule2.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow); - when(issuable.issues()).thenReturn(newArrayList(issue1)).thenReturn(newArrayList(issue2)); - - decorator.decorate(resource, context); - decorator.decorate(resource, context); - - verify(context, times(2)).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0))); - verify(context, never()).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 2.0, 2.0))); - } - - @Test - public void severity_issues() { - when(issuable.issues()).thenReturn(createIssues()); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_BLOCKER_ISSUES, 0.0, 0.0))); - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, 1.0, 1.0))); - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MAJOR_ISSUES, 0.0, 1.0))); - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MINOR_ISSUES, 0.0, 1.0))); - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_INFO_ISSUES, 0.0, 0.0))); - } - - @Test - public void rule_issues() { - when(issuable.issues()).thenReturn(createIssues()); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_CRITICAL_ISSUES, rule1, 1.0, 1.0))); - verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MAJOR_ISSUES, rule2, 0.0, 1.0))); - verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_ISSUES, rule3, 0.0, 1.0))); - } - - @Test - public void should_not_notify_if_not_lastest_analysis() { - Project project = mock(Project.class); - when(project.isLatestAnalysis()).thenReturn(false); - assertThat(decorator.shouldExecuteOnProject(project)).isFalse(); - } - - @Test - public void should_not_notify_if_not_root_project() throws Exception { - Project project = mock(Project.class); - when(project.getQualifier()).thenReturn(Qualifiers.MODULE); - - decorator.decorate(project, context); - - verify(notificationManager, never()).scheduleForSending(any(Notification.class)); - } - - @Test - public void should_not_notify_if_no_not_enough_past_snapshots() throws Exception { - Project project = new Project("key"); - // the #setUp method adds 2 snapshots: if last period analysis is 3, then it's not enough - when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(new ArrayList<PastSnapshot>()); - - decorator.notifyNewIssues(project, context); - verify(notificationManager, never()).scheduleForSending(any(Notification.class)); - } - - @Test - public void should_not_notify_if_no_new_issues() throws Exception { - Project project = new Project("key"); - Measure m = new Measure(CoreMetrics.NEW_ISSUES); - when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(m); - - // NULL is returned here - decorator.notifyNewIssues(project, context); - verify(notificationManager, never()).scheduleForSending(any(Notification.class)); - - // 0 will be returned now - m.setVariation1(0.0); - decorator.notifyNewIssues(project, context); - verify(notificationManager, never()).scheduleForSending(any(Notification.class)); - } - - @Test - public void should_not_notify_user_if_first_analysis() throws Exception { - Project project = new Project("key").setName("LongName"); - project.setId(45); - // PastSnapshot with targetDate==null means first analysis - PastSnapshot pastSnapshot = new PastSnapshot("", null); - when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Lists.newArrayList(pastSnapshot)); - Measure m = new Measure(CoreMetrics.NEW_ISSUES).setVariation1(0.0); - when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(m); - - decorator.decorate(project, context); - verify(notificationManager, never()).scheduleForSending(any(Notification.class)); - } - - @Test - public void should_notify_user_about_new_issues() throws Exception { - Project project = new Project("key").setName("LongName"); - project.setId(45); - Calendar pastDate = new GregorianCalendar(2011, 10, 25); - PastSnapshot pastSnapshot = new PastSnapshot("", pastDate.getTime()); - when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Lists.newArrayList(pastSnapshot, pastSnapshot)); - Measure m = new Measure(CoreMetrics.NEW_ISSUES).setVariation1(32.0); - when(context.getMeasure(CoreMetrics.NEW_ISSUES)).thenReturn(m); - - decorator.decorate(project, context); - - DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd"); - Notification notification = new Notification("new-issues") - .setDefaultMessage("32 new issues on LongName.") - .setFieldValue("count", "32") - .setFieldValue("projectName", "LongName") - .setFieldValue("projectKey", "key") - .setFieldValue("projectId", "45") - .setFieldValue("fromDate", dateformat.format(pastDate.getTime())); - verify(notificationManager, times(1)).scheduleForSending(eq(notification)); - } - - private List<Issue> createIssues() { - List<Issue> issues = newArrayList(); - issues.add(new DefaultIssue().setRuleKey(rule1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(rightNow)); - issues.add(new DefaultIssue().setRuleKey(rule1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreatedAt(tenDaysAgo)); - issues.add(new DefaultIssue().setRuleKey(rule2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreatedAt(fiveDaysAgo)); - issues.add(new DefaultIssue().setRuleKey(rule2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreatedAt(tenDaysAgo)); - issues.add(new DefaultIssue().setRuleKey(rule3.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreatedAt(fiveDaysAgo)); - issues.add(new DefaultIssue().setRuleKey(rule3.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreatedAt(tenDaysAgo)); - return issues; - } - - private class IsVariationRuleMeasure extends BaseMatcher<Measure> { - private Metric metric = null; - private Rule rule = null; - private Double var1 = null; - private Double var2 = null; - - public IsVariationRuleMeasure(Metric metric, Rule rule, Double var1, Double var2) { - this.metric = metric; - this.rule = rule; - this.var1 = var1; - this.var2 = var2; - } - - public boolean matches(Object o) { - if (!(o instanceof RuleMeasure)) { - return false; - } - RuleMeasure m = (RuleMeasure) o; - return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(rule, m.getRule()) && - ObjectUtils.equals(var1, m.getVariation1()) && - ObjectUtils.equals(var2, m.getVariation2()); - } - - public void describeTo(Description arg0) { - } - } - - private class IsVariationMeasure extends BaseMatcher<Measure> { - private Metric metric = null; - private Double var1 = null; - private Double var2 = null; - - public IsVariationMeasure(Metric metric, Double var1, Double var2) { - this.metric = metric; - this.var1 = var1; - this.var2 = var2; - } - - public boolean matches(Object o) { - if (!(o instanceof Measure)) { - return false; - } - Measure m = (Measure) o; - return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(var1, m.getVariation1()) && - ObjectUtils.equals(var2, m.getVariation2()) && - !(m instanceof RuleMeasure); - } - - public void describeTo(Description o) { - } - } -} 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 ef463a683ff..aafff9113b1 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 @@ -47,6 +47,7 @@ public final class CoreMetrics { public static String DOMAIN_RULES = "Rules"; public static String DOMAIN_SCM = "SCM"; public static String DOMAIN_REVIEWS = "Reviews"; + public static String DOMAIN_ISSUES = "Issues"; /** * @deprecated since 2.5 See SONAR-2007 @@ -1532,150 +1533,315 @@ public final class CoreMetrics { // // -------------------------------------------------------------------------------------------------------------------- + /** + * @since 3.6 + */ public static final String ISSUES_KEY = "issues"; + + /** + * @since 3.6 + */ public static final Metric ISSUES = new Metric.Builder(ISSUES_KEY, "Issues", Metric.ValueType.INT) .setDescription("Issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String WEIGHTED_ISSUES_KEY = "weighted_issues"; + + /** + * @since 3.6 + */ public static final Metric WEIGHTED_ISSUES = new Metric.Builder(WEIGHTED_ISSUES_KEY, "Weighted issues", Metric.ValueType.INT) .setDescription("Weighted issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String ISSUES_DENSITY_KEY = "issues_density"; + + /** + * @since 3.6 + */ public static final Metric ISSUES_DENSITY = new Metric.Builder(ISSUES_DENSITY_KEY, "Rules compliance", Metric.ValueType.PERCENT) .setDescription("Rules compliance") .setDirection(Metric.DIRECTION_BETTER) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .create(); + /** + * @since 3.6 + */ public static final String BLOCKER_ISSUES_KEY = "blocker_issues"; + + /** + * @since 3.6 + */ public static final Metric BLOCKER_ISSUES = new Metric.Builder(BLOCKER_ISSUES_KEY, "Blocker issues", Metric.ValueType.INT) .setDescription("Blocker issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String CRITICAL_ISSUES_KEY = "critical_issues"; + + /** + * @since 3.6 + */ public static final Metric CRITICAL_ISSUES = new Metric.Builder(CRITICAL_ISSUES_KEY, "Critical issues", Metric.ValueType.INT) .setDescription("Critical issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String MAJOR_ISSUES_KEY = "major_issues"; + + /** + * @since 3.6 + */ public static final Metric MAJOR_ISSUES = new Metric.Builder(MAJOR_ISSUES_KEY, "Major issues", Metric.ValueType.INT) .setDescription("Major issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String MINOR_ISSUES_KEY = "minor_issues"; + + /** + * @since 3.6 + */ public static final Metric MINOR_ISSUES = new Metric.Builder(MINOR_ISSUES_KEY, "Minor issues", Metric.ValueType.INT) .setDescription("Minor issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String INFO_ISSUES_KEY = "info_issues"; + + /** + * @since 3.6 + */ public static final Metric INFO_ISSUES = new Metric.Builder(INFO_ISSUES_KEY, "Info issues", Metric.ValueType.INT) .setDescription("Info issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_ISSUES_KEY = "new_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_ISSUES = new Metric.Builder(NEW_ISSUES_KEY, "New issues", Metric.ValueType.INT) .setDescription("New issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_BLOCKER_ISSUES_KEY = "new_blocker_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_BLOCKER_ISSUES = new Metric.Builder(NEW_BLOCKER_ISSUES_KEY, "New Blocker issues", Metric.ValueType.INT) .setDescription("New Blocker issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_CRITICAL_ISSUES_KEY = "new_critical_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_CRITICAL_ISSUES = new Metric.Builder(NEW_CRITICAL_ISSUES_KEY, "New Critical issues", Metric.ValueType.INT) .setDescription("New Critical issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_MAJOR_ISSUES_KEY = "new_major_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_MAJOR_ISSUES = new Metric.Builder(NEW_MAJOR_ISSUES_KEY, "New Major issues", Metric.ValueType.INT) .setDescription("New Major issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_MINOR_ISSUES_KEY = "new_minor_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_MINOR_ISSUES = new Metric.Builder(NEW_MINOR_ISSUES_KEY, "New Minor issues", Metric.ValueType.INT) .setDescription("New Minor issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) .create(); + /** + * @since 3.6 + */ public static final String NEW_INFO_ISSUES_KEY = "new_info_issues"; + + /** + * @since 3.6 + */ public static final Metric NEW_INFO_ISSUES = new Metric.Builder(NEW_INFO_ISSUES_KEY, "New Info issues", Metric.ValueType.INT) .setDescription("New Info issues") .setDirection(Metric.DIRECTION_WORST) .setQualitative(true) - .setDomain(DOMAIN_RULES) + .setDomain(DOMAIN_ISSUES) .setBestValue(0.0) .setOptimizedBestValue(true) .setDeleteHistoricalData(true) - .create(); - + .create(); + + /** + * @since 3.6 + */ + public static final String FALSE_POSITIVE_ISSUES_KEY = "false_positive_issues"; + + /** + * @since 3.6 + */ + public static final Metric FALSE_POSITIVE_ISSUES = new Metric.Builder(FALSE_POSITIVE_ISSUES_KEY, "False-positive issues", Metric.ValueType.INT) + .setDescription("Active false-positive issues") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_ISSUES) + .setBestValue(0.0) + .setOptimizedBestValue(true) + .create(); + + /** + * @since 3.6 + */ + public static final String UNASSIGNED_ISSUES_KEY = "unassigned_issues"; + + /** + * @since 3.6 + */ + public static final Metric UNASSIGNED_ISSUES = new Metric.Builder(UNASSIGNED_ISSUES_KEY, "Unassigned issues", Metric.ValueType.INT) + .setDescription("Active unassigned issues") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_ISSUES) + .setBestValue(0.0) + .setOptimizedBestValue(true) + .create(); + + /** + * @since 3.6 + */ + public static final String UNPLANNED_ISSUES_KEY = "unplanned_issues"; + + /** + * @since 3.6 + */ + public static final Metric UNPLANNED_ISSUES = new Metric.Builder(UNPLANNED_ISSUES_KEY, "Unplanned issues", Metric.ValueType.INT) + .setDescription("Active unplanned issues") + .setDirection(Metric.DIRECTION_WORST) + .setDomain(DOMAIN_ISSUES) + .setBestValue(0.0) + .setOptimizedBestValue(true) + .create(); + + /** + * @since 3.6 + */ + public static final String NEW_UNPLANNED_ISSUES_KEY = "new_unplanned_issues"; + + /** + * @since 3.6 + */ + public static final Metric NEW_UNPLANNED_ISSUES = new Metric.Builder(NEW_UNPLANNED_ISSUES_KEY, "New unplanned issues", Metric.ValueType.INT) + .setDescription("New issues that have not been planned yet") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(true) + .setDomain(DOMAIN_ISSUES) + .setBestValue(0.0) + .setOptimizedBestValue(true) + .setDeleteHistoricalData(true) + .create(); + + // -------------------------------------------------------------------------------------------------------------------- // // DESIGN |