diff options
53 files changed, 343 insertions, 147 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java index 86374222608..fcc2deb96c6 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java @@ -30,6 +30,7 @@ import org.sonar.api.measures.PersistenceMode; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; +import org.sonar.core.technicaldebt.TechnicalDebtCalculator; import org.sonar.core.technicaldebt.TechnicalDebtCharacteristic; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.WorkUnitConverter; diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java index 65bfafefa10..96988f33f0d 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java @@ -28,6 +28,7 @@ import org.sonar.api.resources.File; import org.sonar.api.resources.Project; import org.sonar.api.resources.Qualifiers; import org.sonar.api.test.IsMeasure; +import org.sonar.core.technicaldebt.TechnicalDebtCalculator; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java index 7bef92697a9..a3f695151f0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java @@ -26,6 +26,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.ActiveRule; import org.sonar.api.rules.Violation; import org.sonar.core.issue.DefaultIssueBuilder; +import org.sonar.core.technicaldebt.TechnicalDebtCalculator; import javax.annotation.Nullable; @@ -38,12 +39,14 @@ public class ModuleIssues { private final IssueCache cache; private final Project project; private final IssueFilters filters; + private final TechnicalDebtCalculator technicalDebtCalculator; - public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters) { + public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters, TechnicalDebtCalculator technicalDebtCalculator) { this.qProfile = qProfile; this.cache = cache; this.project = project; this.filters = filters; + this.technicalDebtCalculator = technicalDebtCalculator; } public boolean initAndAddIssue(DefaultIssue issue) { @@ -79,6 +82,7 @@ public class ModuleIssues { if (issue.severity() == null) { issue.setSeverity(activeRule.getSeverity().name()); } + issue.setRemediationCost(technicalDebtCalculator.cost(issue)); if (filters.accept(issue, violation)) { cache.put(issue); diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java index 2fc2f8d2543..0097856a211 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java @@ -34,6 +34,7 @@ import org.sonar.api.rules.ActiveRule; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RulePriority; import org.sonar.api.rules.Violation; +import org.sonar.core.technicaldebt.TechnicalDebtCalculator; import java.util.Calendar; import java.util.Date; @@ -42,10 +43,7 @@ import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class ModuleIssuesTest { @@ -55,7 +53,8 @@ public class ModuleIssuesTest { RulesProfile qProfile = mock(RulesProfile.class); Project project = mock(Project.class); IssueFilters filters = mock(IssueFilters.class); - ModuleIssues moduleIssues = new ModuleIssues(qProfile, cache, project, filters); + TechnicalDebtCalculator technicalDebtCalculator = mock(TechnicalDebtCalculator.class); + ModuleIssues moduleIssues = new ModuleIssues(qProfile, cache, project, filters, technicalDebtCalculator); @Before public void setUp() { @@ -64,7 +63,7 @@ public class ModuleIssuesTest { } @Test - public void should_ignore_null_active_rule() throws Exception { + public void ignore_null_active_rule() throws Exception { when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null); DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); @@ -75,7 +74,7 @@ public class ModuleIssuesTest { } @Test - public void should_ignore_null_rule_of_active_rule() throws Exception { + public void ignore_null_rule_of_active_rule() throws Exception { ActiveRule activeRule = mock(ActiveRule.class); when(activeRule.getRule()).thenReturn(null); when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule); @@ -88,7 +87,7 @@ public class ModuleIssuesTest { } @Test - public void should_add_issue_to_cache() throws Exception { + public void add_issue_to_cache() throws Exception { Rule rule = Rule.create("squid", "AvoidCycle"); ActiveRule activeRule = mock(ActiveRule.class); when(activeRule.getRule()).thenReturn(rule); @@ -114,7 +113,7 @@ public class ModuleIssuesTest { } @Test - public void should_use_severity_from_active_rule_if_no_severity() throws Exception { + public void use_severity_from_active_rule_if_no_severity() throws Exception { Rule rule = Rule.create("squid", "AvoidCycle"); ActiveRule activeRule = mock(ActiveRule.class); when(activeRule.getRule()).thenReturn(rule); @@ -135,7 +134,7 @@ public class ModuleIssuesTest { } @Test - public void should_add_deprecated_violation() throws Exception { + public void add_deprecated_violation() throws Exception { Rule rule = Rule.create("squid", "AvoidCycle"); Resource resource = new JavaFile("org.struts.Action").setEffectiveKey("struts:org.struts.Action"); Violation violation = new Violation(rule, resource); @@ -164,7 +163,7 @@ public class ModuleIssuesTest { } @Test - public void should_filter_issue() throws Exception { + public void filter_issue() throws Exception { Rule rule = Rule.create("squid", "AvoidCycle"); ActiveRule activeRule = mock(ActiveRule.class); when(activeRule.getRule()).thenReturn(rule); @@ -184,4 +183,31 @@ public class ModuleIssuesTest { verifyZeroInteractions(cache); } + @Test + public void set_remediation_cost() throws Exception { + Rule rule = Rule.create("squid", "AvoidCycle"); + ActiveRule activeRule = mock(ActiveRule.class); + when(activeRule.getRule()).thenReturn(rule); + when(activeRule.getSeverity()).thenReturn(RulePriority.INFO); + when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); + + Date analysisDate = new Date(); + when(project.getAnalysisDate()).thenReturn(analysisDate); + + + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setRuleKey(SQUID_RULE_KEY) + .setSeverity(Severity.CRITICAL); + + when(technicalDebtCalculator.cost(issue)).thenReturn(10L); + when(filters.accept(issue, null)).thenReturn(true); + + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class); + verify(cache).put(argument.capture()); + assertThat(argument.getValue().remediationCost()).isEqualTo(10L); + } + } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java index b86a7b6a15a..e251095ede5 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java @@ -29,7 +29,6 @@ import org.sonar.api.utils.KeyValueFormat; import javax.annotation.CheckForNull; import javax.annotation.Nullable; - import java.io.Serializable; import java.util.Date; @@ -48,6 +47,7 @@ public final class IssueDto implements Serializable { private String message; private Integer line; private Double effortToFix; + private Long remediationCost; private String status; private String resolution; private String checksum; @@ -181,6 +181,16 @@ public final class IssueDto implements Serializable { return this; } + @CheckForNull + public Long getRemediationCost() { + return remediationCost; + } + + public IssueDto setRemediationCost(@Nullable Long remediationCost) { + this.remediationCost = remediationCost; + return this; + } + public String getStatus() { return status; } @@ -408,6 +418,7 @@ public final class IssueDto implements Serializable { issue.setResolution(resolution); issue.setMessage(message); issue.setEffortToFix(effortToFix); + issue.setRemediationCost(remediationCost); issue.setLine(line); issue.setSeverity(severity); issue.setReporter(reporter); diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index e49c6615aed..f8c8211981e 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -33,7 +33,7 @@ import java.util.List; */ public class DatabaseVersion implements BatchComponent, ServerComponent { - public static final int LAST_VERSION = 441; + public static final int LAST_VERSION = 442; public static enum Status { UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java index 6c188e93584..fb540c66419 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtCalculator.java @@ -17,7 +17,7 @@ * 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.plugins.core.technicaldebt; +package org.sonar.core.technicaldebt; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; @@ -26,14 +26,12 @@ import com.google.common.collect.Maps; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.issue.Issue; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.MeasuresFilters; import org.sonar.api.measures.Metric; import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtCharacteristic; -import org.sonar.core.technicaldebt.TechnicalDebtModel; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.functions.Functions; import java.util.Collection; @@ -57,6 +55,14 @@ public class TechnicalDebtCalculator implements BatchExtension { this.functions = functions; } + public Long cost(Issue issue) { + TechnicalDebtRequirement requirement = technicalDebtModel.getRequirementByRule(issue.ruleKey().repository(), issue.ruleKey().rule()); + if (requirement != null) { + return functions.costInMinutes(requirement, issue); + } + return null; + } + public void compute(DecoratorContext context) { reset(); @@ -109,7 +115,7 @@ public class TechnicalDebtCalculator implements BatchExtension { private double computeRemediationCost(Metric metric, DecoratorContext context, TechnicalDebtRequirement requirement, Collection<Violation> violations) { double cost = 0.0; if (violations != null) { - cost = functions.calculateCost(requirement, violations); + cost = functions.costInHours(requirement, violations); } for (Measure measure : context.getChildrenMeasures(MeasuresFilters.characteristic(metric, requirement.toCharacteristic()))) { diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModel.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModel.java index fbcee9db691..bcc58262889 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModel.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtModel.java @@ -19,8 +19,6 @@ */ package org.sonar.core.technicaldebt; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; @@ -30,10 +28,14 @@ import org.sonar.api.rules.Rule; import org.sonar.api.utils.SonarException; import org.sonar.api.utils.TimeProfiler; +import javax.annotation.CheckForNull; import java.util.Collection; import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; + public class TechnicalDebtModel implements BatchComponent { private static final Logger LOGGER = LoggerFactory.getLogger(TechnicalDebtModel.class); @@ -41,8 +43,8 @@ public class TechnicalDebtModel implements BatchComponent { // FIXME Use the same as in RegisterTechnicalDebtModel public static final String MODEL_NAME = "TECHNICAL_DEBT"; - private List<TechnicalDebtCharacteristic> characteristics = Lists.newArrayList(); - private Map<Rule, TechnicalDebtRequirement> requirementsByRule = Maps.newHashMap(); + private List<TechnicalDebtCharacteristic> characteristics = newArrayList(); + private Map<Rule, TechnicalDebtRequirement> requirementsByRule = newHashMap(); public TechnicalDebtModel(ModelFinder modelFinder) { TimeProfiler profiler = new TimeProfiler(LOGGER).start("Loading technical debt model"); @@ -98,6 +100,7 @@ public class TechnicalDebtModel implements BatchComponent { return requirementsByRule.values(); } + @CheckForNull public TechnicalDebtRequirement getRequirementByRule(String repositoryKey, String key) { return requirementsByRule.get(Rule.create().setUniqueKey(repositoryKey, key)); } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java index 93799c5878d..b497b78b9f0 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/TechnicalDebtRequirement.java @@ -19,6 +19,7 @@ */ package org.sonar.core.technicaldebt; +import org.sonar.api.qualitymodel.Characteristic; import org.sonar.api.rules.Rule; import org.sonar.core.technicaldebt.functions.LinearFunction; import org.sonar.core.technicaldebt.functions.LinearWithOffsetFunction; @@ -37,7 +38,7 @@ public class TechnicalDebtRequirement implements Characteristicable { private WorkUnit factor; private WorkUnit offset; - public TechnicalDebtRequirement(org.sonar.api.qualitymodel.Characteristic requirement, TechnicalDebtCharacteristic parent) { + public TechnicalDebtRequirement(Characteristic requirement, TechnicalDebtCharacteristic parent) { this.characteristic = requirement; this.rule = requirement.getRule(); this.parent = parent; diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/WorkUnitConverter.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/WorkUnitConverter.java index b83dc4cbfd7..8523b9a3ecb 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/WorkUnitConverter.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/WorkUnitConverter.java @@ -55,4 +55,21 @@ public final class WorkUnitConverter implements BatchComponent { } return result; } + + public long toMinutes(WorkUnit factor) { + long result; + if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) { + result = Double.valueOf(factor.getValue() * hoursInDay * 60d).longValue(); + + } else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) { + result = Double.valueOf(factor.getValue() * 60d).longValue(); + + } else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) { + result = Double.valueOf(factor.getValue()).longValue(); + + } else { + throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit()); + } + return result; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java index dd2ea2d8dfe..c07a7c4b181 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/AbstractFunction.java @@ -19,6 +19,7 @@ */ package org.sonar.core.technicaldebt.functions; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.WorkUnitConverter; @@ -39,6 +40,12 @@ public abstract class AbstractFunction implements Function { public abstract String getKey(); - public abstract double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations); + public abstract double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations); + + public abstract long costInMinutes(TechnicalDebtRequirement requirement, Issue issue); + + protected long factorInMinutes(TechnicalDebtRequirement requirement) { + return getConverter().toMinutes(requirement.getRemediationFactor()); + } } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java index 4c54129258d..b347c6838bd 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/ConstantFunction.java @@ -19,6 +19,7 @@ */ package org.sonar.core.technicaldebt.functions; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.WorkUnitConverter; @@ -37,7 +38,7 @@ public final class ConstantFunction extends AbstractFunction { return FUNCTION_CONSTANT_RESOURCE; } - public double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations) { + public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { double cost = 0.0; if (!violations.isEmpty()) { cost = getConverter().toDays(requirement.getRemediationFactor()); @@ -45,4 +46,8 @@ public final class ConstantFunction extends AbstractFunction { return cost; } + public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { + return factorInMinutes(requirement); + } + } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java index cb54e529e4e..65a37918499 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Function.java @@ -20,6 +20,7 @@ package org.sonar.core.technicaldebt.functions; import org.sonar.api.BatchComponent; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; @@ -29,6 +30,8 @@ public interface Function extends BatchComponent { String getKey(); - double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations); + double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations); + + long costInMinutes(TechnicalDebtRequirement requirement, Issue issue); } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java index 99263df6ef1..da43d06a508 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/Functions.java @@ -21,6 +21,7 @@ package org.sonar.core.technicaldebt.functions; import com.google.common.collect.Maps; import org.sonar.api.BatchComponent; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; @@ -45,8 +46,12 @@ public class Functions implements BatchComponent { return getFunction(requirement.getRemediationFunction()); } - public double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations) { - return getFunction(requirement).calculateCost(requirement, violations); + public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { + return getFunction(requirement).costInHours(requirement, violations); + } + + public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { + return getFunction(requirement).costInMinutes(requirement, issue); } } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java index 727168c8dd9..cac450e9509 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearFunction.java @@ -19,6 +19,7 @@ */ package org.sonar.core.technicaldebt.functions; +import org.sonar.api.issue.Issue; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.WorkUnitConverter; @@ -39,11 +40,16 @@ public class LinearFunction extends AbstractFunction { return FUNCTION_LINEAR; } - public double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations) { + public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { double points = 0.0; for (Violation violation : violations) { points += (violation.getCost() != null ? violation.getCost() : DEFAULT_VIOLATION_COST); } return points * getConverter().toDays(requirement.getRemediationFactor()); } + + public long costInMinutes(TechnicalDebtRequirement requirement, Issue issue) { + double points = (issue.effortToFix() != null ? issue.effortToFix() : DEFAULT_VIOLATION_COST); + return Double.valueOf(points * factorInMinutes(requirement)).longValue(); + } } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java index 02b3f22d1e0..931ff24798e 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunction.java @@ -37,12 +37,12 @@ public final class LinearWithOffsetFunction extends LinearFunction { return FUNCTION_LINEAR_WITH_OFFSET; } - public double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations) { + public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { if (violations.isEmpty()) { return 0.0; } double minimunCost = getConverter().toDays(requirement.getOffset()); - return minimunCost + super.calculateCost(requirement, violations); + return minimunCost + super.costInHours(requirement, violations); } } diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java index cac84ce45c7..9530b11843f 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunction.java @@ -37,12 +37,12 @@ public final class LinearWithThresholdFunction extends LinearFunction { return FUNCTION_LINEAR_WITH_THRESHOLD; } - public double calculateCost(TechnicalDebtRequirement requirement, Collection<Violation> violations) { + public double costInHours(TechnicalDebtRequirement requirement, Collection<Violation> violations) { if (violations.isEmpty()) { return 0.0; } double thresholdCost = getConverter().toDays(requirement.getOffset()); - double violationsCost = super.calculateCost(requirement, violations); + double violationsCost = super.costInHours(requirement, violations); return violationsCost > thresholdCost ? violationsCost : thresholdCost; } diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml index 2aca03357aa..80e9e375cb1 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml @@ -16,6 +16,7 @@ i.message as message, i.line as line, i.effort_to_fix as effortToFix, + i.remediation_cost as remediationCost, i.status as status, i.resolution as resolution, i.checksum as checksum, @@ -61,11 +62,11 @@ <insert id="insert" parameterType="Issue" useGeneratedKeys="false" keyProperty="id"> INSERT INTO issues (kee, component_id, root_component_id, rule_id, action_plan_key, severity, manual_severity, - message, line, effort_to_fix, status, + message, line, effort_to_fix, remediation_cost, status, resolution, checksum, reporter, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date, issue_close_date, created_at, updated_at) VALUES (#{kee}, #{componentId}, #{rootComponentId}, #{ruleId}, #{actionPlanKey}, #{severity}, #{manualSeverity}, - #{message}, #{line}, #{effortToFix}, #{status}, + #{message}, #{line}, #{effortToFix}, #{remediationCost}, #{status}, #{resolution}, #{checksum}, #{reporter}, #{assignee}, #{authorLogin}, #{issueAttributes}, #{issueCreationDate}, #{issueUpdateDate}, #{issueCloseDate}, #{createdAt}, #{updatedAt}) </insert> @@ -81,6 +82,7 @@ message=#{message}, line=#{line}, effort_to_fix=#{effortToFix}, + remediation_cost=#{remediationCost}, status=#{status}, resolution=#{resolution}, checksum=#{checksum}, @@ -106,6 +108,7 @@ message=#{message}, line=#{line}, effort_to_fix=#{effortToFix}, + remediation_cost=#{remediationCost}, status=#{status}, resolution=#{resolution}, checksum=#{checksum}, @@ -145,6 +148,7 @@ i.message as message, i.line as line, i.effort_to_fix as effortToFix, + i.remediation_cost as remediationCost, i.status as status, i.resolution as resolution, i.checksum as checksum, diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql index 2ee8f0fd611..709c308c6d6 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql @@ -179,6 +179,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('432'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('433'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('440'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('441'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('442'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl index 52355a26659..4f201802db7 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/schema-h2.ddl @@ -477,6 +477,7 @@ CREATE TABLE "ISSUES" ( "MESSAGE" VARCHAR(4000), "LINE" INTEGER, "EFFORT_TO_FIX" DOUBLE, + "REMEDIATION_COST" INTEGER, "STATUS" VARCHAR(20), "RESOLUTION" VARCHAR(20), "CHECKSUM" VARCHAR(1000), diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java index 178161dae4a..e97d7cdd53d 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java @@ -30,7 +30,7 @@ import static org.fest.assertions.Assertions.assertThat; public class DefaultIssueBuilderTest { @Test - public void should_build_new_issue() throws Exception { + public void build_new_issue() throws Exception { String componentKey = "org.apache.struts:struts-core:Action.java"; DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder() .componentKey(componentKey) @@ -62,7 +62,7 @@ public class DefaultIssueBuilderTest { } @Test - public void should_not_set_default_severity() { + public void not_set_default_severity() { DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder() .componentKey("Action.java") .ruleKey(RuleKey.of("squid", "NullDereference")) diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java index 91ffce8461d..4add1b46497 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDtoTest.java @@ -49,7 +49,7 @@ public class IssueDtoTest { } @Test - public void should_set_issue_fields() { + public void set_issue_fields() { Date createdAt = DateUtils.addDays(new Date(), -5); Date updatedAt = DateUtils.addDays(new Date(), -3); Date closedAt = DateUtils.addDays(new Date(), -1); @@ -65,6 +65,7 @@ public class IssueDtoTest { .setStatus(Issue.STATUS_CLOSED) .setResolution(Issue.RESOLUTION_FALSE_POSITIVE) .setEffortToFix(15.0) + .setRemediationCost(500L) .setLine(6) .setSeverity("BLOCKER") .setMessage("message") @@ -85,6 +86,7 @@ public class IssueDtoTest { assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FALSE_POSITIVE); assertThat(issue.effortToFix()).isEqualTo(15.0); + assertThat(issue.remediationCost()).isEqualTo(500L); assertThat(issue.line()).isEqualTo(6); assertThat(issue.severity()).isEqualTo("BLOCKER"); assertThat(issue.message()).isEqualTo("message"); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java index 199c198737b..3397cd4e4a2 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java @@ -54,6 +54,7 @@ public class IssueMapperTest extends AbstractDaoTestCase { dto.setKee("ABCDE"); dto.setLine(500); dto.setEffortToFix(3.14); + dto.setRemediationCost(10L); dto.setResolution("FIXED"); dto.setStatus("RESOLVED"); dto.setSeverity("BLOCKER"); @@ -88,6 +89,7 @@ public class IssueMapperTest extends AbstractDaoTestCase { dto.setKee("ABCDE"); dto.setLine(500); dto.setEffortToFix(3.14); + dto.setRemediationCost(10L); dto.setResolution("FIXED"); dto.setStatus("RESOLVED"); dto.setSeverity("BLOCKER"); @@ -122,6 +124,7 @@ public class IssueMapperTest extends AbstractDaoTestCase { dto.setKee("ABCDE"); dto.setLine(500); dto.setEffortToFix(3.14); + dto.setRemediationCost(10L); dto.setResolution("FIXED"); dto.setStatus("RESOLVED"); dto.setSeverity("BLOCKER"); @@ -159,6 +162,7 @@ public class IssueMapperTest extends AbstractDaoTestCase { dto.setKee("ABCDE"); dto.setLine(500); dto.setEffortToFix(3.14); + dto.setRemediationCost(10L); dto.setResolution("FIXED"); dto.setStatus("RESOLVED"); dto.setSeverity("BLOCKER"); diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java index 7eea344975d..eed0940f08c 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/TechnicalDebtCalculatorTest.java @@ -17,7 +17,7 @@ * 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.plugins.core.technicaldebt; +package org.sonar.core.technicaldebt; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; @@ -25,13 +25,13 @@ import org.apache.commons.lang.time.DateUtils; import org.junit.Before; import org.junit.Test; import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.measures.MeasuresFilter; import org.sonar.api.qualitymodel.Characteristic; +import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.Rule; import org.sonar.api.rules.Violation; -import org.sonar.core.technicaldebt.TechnicalDebtCharacteristic; -import org.sonar.core.technicaldebt.TechnicalDebtModel; -import org.sonar.core.technicaldebt.TechnicalDebtRequirement; import org.sonar.core.technicaldebt.functions.Functions; import java.util.Collection; @@ -73,8 +73,8 @@ public class TechnicalDebtCalculatorTest { List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4); - stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1); - stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2); + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); + when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(requirement2); DecoratorContext context = mock(DecoratorContext.class); when(context.getViolations()).thenReturn(violations); @@ -132,23 +132,43 @@ public class TechnicalDebtCalculatorTest { List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4); - stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1); - stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2); - stub(technicalDebtModel.getAllRequirements()).toReturn(Lists.newArrayList(requirement1, requirement2)); + when(technicalDebtModel.getRequirementByRule("repo1", "rule1")).thenReturn(requirement1); + when(technicalDebtModel.getRequirementByRule("repo2", "rule2")).thenReturn(requirement2); + when(technicalDebtModel.getAllRequirements()).thenReturn(Lists.newArrayList(requirement1, requirement2)); - stub(functions.calculateCost(any(TechnicalDebtRequirement.class), any(Collection.class))).toReturn(1.0); + when(functions.costInHours(any(TechnicalDebtRequirement.class), any(Collection.class))).thenReturn(1.0); DecoratorContext context = mock(DecoratorContext.class); - stub(context.getViolations()).toReturn(violations); - stub(context.getChildrenMeasures(any(MeasuresFilter.class))).toReturn(Collections.EMPTY_LIST); + when(context.getViolations()).thenReturn(violations); + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.EMPTY_LIST); remediationCostCalculator.compute(context); -// assertThat(remediationCostCalculator.getTotal()).isEqualTo(2.0); + assertThat(remediationCostCalculator.getTotal()).isEqualTo(2.0); assertThat(remediationCostCalculator.getRequirementCosts().get(requirement1)).isEqualTo(1.0); assertThat(remediationCostCalculator.getRequirementCosts().get(requirement2)).isEqualTo(1.0); } + @Test + public void cost_from_one_issue() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")); + TechnicalDebtRequirement requirement = mock(TechnicalDebtRequirement.class); + stub(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).toReturn(requirement); + + when(functions.costInMinutes(eq(requirement), eq(issue))).thenReturn(10L); + + assertThat(remediationCostCalculator.cost(issue)).isEqualTo(10L); + } + + @Test + public void no_cost_from_one_issue_if_reauirement_not_found() throws Exception { + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(RuleKey.of("squid", "AvoidCycle")); + stub(technicalDebtModel.getRequirementByRule("squid", "AvoidCycle")).toReturn(null); + + assertThat(remediationCostCalculator.cost(issue)).isNull(); + verify(functions, never()).costInMinutes(any(TechnicalDebtRequirement.class), any(Issue.class)); + } + private Violation buildViolation(String ruleKey, String repositoryKey, Date creationDate) { Violation violation = mock(Violation.class); stub(violation.getRule()).toReturn(Rule.create(repositoryKey, ruleKey)); diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/WorkUnitConverterTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/WorkUnitConverterTest.java index 9f5473dfa91..be225db71fc 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/WorkUnitConverterTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/WorkUnitConverterTest.java @@ -28,7 +28,7 @@ import static org.junit.Assert.assertThat; public class WorkUnitConverterTest { @Test - public void concert_value_to_days() { + public void concert_to_days() { Settings settings = new Settings(); settings.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, "12"); @@ -39,4 +39,16 @@ public class WorkUnitConverterTest { assertThat(converter.toDays(WorkUnit.create(60.0, WorkUnit.MINUTES)), is(1.0 / 12.0)); } + @Test + public void concert_to_minutes() { + Settings settings = new Settings(); + settings.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, "12"); + + WorkUnitConverter converter = new WorkUnitConverter(settings); + + assertThat(converter.toMinutes(WorkUnit.create(2.0, WorkUnit.DAYS)), is(2 * 12 * 60L)); + assertThat(converter.toMinutes(WorkUnit.create(6.0, WorkUnit.HOURS)), is(6 * 60L)); + assertThat(converter.toMinutes(WorkUnit.create(60.0, WorkUnit.MINUTES)), is(60L)); + } + } diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java index 2a46394aa21..9bd97a1a6a3 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/ConstantFunctionTest.java @@ -26,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.sonar.api.config.Settings; +import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.rules.Rule; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; @@ -35,6 +36,9 @@ import org.sonar.core.technicaldebt.WorkUnitConverter; import java.util.Collection; import java.util.Collections; +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.when; + public class ConstantFunctionTest { private TechnicalDebtRequirement requirement; @@ -48,19 +52,26 @@ public class ConstantFunctionTest { } @Test - public void zeroIfNoViolations() { - Assert.assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); + public void zero_if_no_violations() { + Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); } @Test - public void countAsIfSingleViolation() { + public void count_as_if_single_violation() { Collection<Violation> violations = Lists.newArrayList(); Rule rule = Rule.create("checkstyle", "foo", "Foo"); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(3.14)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(3.14)); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(3.14)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(3.14)); + } + + @Test + public void cost_in_minutes() { + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); + DefaultIssue issue = new DefaultIssue().setKey("ABCDE"); + assertThat(function.costInMinutes(requirement, issue)).isEqualTo(10L); } } diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java index ada1862aa87..e829e8e15e8 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/FunctionsTest.java @@ -26,7 +26,7 @@ import static org.fest.assertions.Assertions.assertThat; public class FunctionsTest { @Test - public void registerFunctions() { + public void register_functions() { Functions functions = new Functions(new Function[]{new LinearFunction(null), new LinearWithOffsetFunction(null), new ConstantFunction(null)}); assertThat(functions.getFunction(LinearFunction.FUNCTION_LINEAR)).isInstanceOf(LinearFunction.class); diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java index 6f3e2cb9106..077ebe8f6aa 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearFunctionTest.java @@ -20,11 +20,10 @@ package org.sonar.core.technicaldebt.functions; import com.google.common.collect.Lists; -import org.hamcrest.core.Is; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.sonar.api.config.Settings; +import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.rules.Rule; import org.sonar.api.rules.Violation; import org.sonar.core.technicaldebt.TechnicalDebtRequirement; @@ -34,45 +33,64 @@ import org.sonar.core.technicaldebt.WorkUnitConverter; import java.util.Collection; import java.util.Collections; +import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class LinearFunctionTest { private TechnicalDebtRequirement requirement; - private Function function = new LinearFunction(new WorkUnitConverter(new Settings())); + private Function function; @Before public void before() { + Settings settings = new Settings(); + settings.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, 8); + function = new LinearFunction(new WorkUnitConverter(settings)); + requirement = mock(TechnicalDebtRequirement.class); when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14)); } @Test - public void zeroIfNoViolations() { - Assert.assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); + public void zero_if_no_violations() { + assertThat(function.costInHours(requirement, Collections.<Violation>emptyList())).isEqualTo(0.0); } @Test - public void countEveryViolation() { + public void count_every_violation() { Collection<Violation> violations = Lists.newArrayList(); Rule rule = Rule.create("checkstyle", "foo", "Foo"); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(3.14)); + assertThat(function.costInHours(requirement, violations)).isEqualTo(3.14); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(3.14 * 2)); + assertThat(function.costInHours(requirement, violations)).isEqualTo(3.14 * 2); } @Test - public void usePointsWhenAvailable() { + public void use_points_when_available() { Collection<Violation> violations = Lists.newArrayList(); Rule rule = Rule.create("checkstyle", "foo", "Foo"); violations.add(new Violation(rule).setCost(20.5)); violations.add(new Violation(rule).setCost(3.8)); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(3.14 * (20.5 + 3.8 + 1))); + assertThat(function.costInHours(requirement, violations)).isEqualTo(3.14 * (20.5 + 3.8 + 1)); + } + + @Test + public void cost_in_minutes() { + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setEffortToFix(2.0); + assertThat(function.costInMinutes(requirement, issue)).isEqualTo(20L); + } + + @Test + public void cost_in_minutes_use_default_cost_when_no_effort_to_fix_on_issue() { + when(requirement.getRemediationFactor()).thenReturn(WorkUnit.create(10d, WorkUnit.MINUTES)); + DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setEffortToFix(null); + assertThat(function.costInMinutes(requirement, issue)).isEqualTo(10L); } } diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java index 0bcaeca8043..b4aa70153a2 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java @@ -49,7 +49,7 @@ public class LinearWithOffsetFunctionTest { @Test public void zeroIfNoViolations() { - Assert.assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); + Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); } @Test @@ -58,9 +58,9 @@ public class LinearWithOffsetFunctionTest { Rule rule = Rule.create("checkstyle", "foo", "Foo"); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(2.12 + 3.14)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(2.12 + 3.14)); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(2.12 + 3.14 * 2)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(2.12 + 3.14 * 2)); } } diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java index 0248de5072f..7445fad7f92 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java @@ -49,7 +49,7 @@ public class LinearWithThresholdFunctionTest { @Test public void zeroIfNoViolations() { - Assert.assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); + Assert.assertThat(function.costInHours(requirement, Collections.<Violation>emptyList()), Is.is(0.0)); } @Test @@ -58,12 +58,12 @@ public class LinearWithThresholdFunctionTest { Rule rule = Rule.create("checkstyle", "foo", "Foo"); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(5.0)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(5.0)); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(5.0)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(5.0)); violations.add(new Violation(rule)); - Assert.assertThat(function.calculateCost(requirement, violations), Is.is(6.0)); + Assert.assertThat(function.costInHours(requirement, violations), Is.is(6.0)); } } diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testInsert-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testInsert-result.xml index 34e96a79ae9..4e0b18c0de6 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testInsert-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testInsert-result.xml @@ -10,6 +10,7 @@ message="the message" line="500" effort_to_fix="3.14" + remediation_cost="10" status="RESOLVED" resolution="FIXED" checksum="123456789" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml index 171c7a3b4b4..4dbc84fdd59 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml @@ -10,6 +10,7 @@ message="the message" line="500" effort_to_fix="3.14" + remediation_cost="10" status="RESOLVED" resolution="FIXED" checksum="123456789" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate.xml index a4df3d603dd..00a1b052c40 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate.xml @@ -10,6 +10,7 @@ message="old" line="[null]" effort_to_fix="[null]" + remediation_cost="[null]" status="OPEN" resolution="[null]" checksum="[null]" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml index 3bd12af6cf8..027e5f65c5b 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict-result.xml @@ -11,6 +11,7 @@ message="old" line="[null]" effort_to_fix="[null]" + remediation_cost="[null]" status="OPEN" resolution="[null]" checksum="[null]" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml index d598d02536e..fb218f4ab13 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/updateBeforeSelectedDate_with_conflict.xml @@ -10,6 +10,7 @@ message="old" line="[null]" effort_to_fix="[null]" + remediation_cost="[null]" status="OPEN" resolution="[null]" checksum="[null]" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml index 6ae5005cdf9..af3b4371b49 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_insert_new_issues-result.xml @@ -4,6 +4,7 @@ author_login="[null]" checksum="[null]" effort_to_fix="[null]" + remediation_cost="[null]" message="[null]" line="5000" component_id="100" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates-result.xml index 0564fd93ae4..9c2a8fcbee9 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates-result.xml @@ -1,22 +1,3 @@ -<!-- - ~ SonarQube, open source software quality management tool. - ~ Copyright (C) 2008-2013 SonarSource - ~ mailto:contact AT sonarsource DOT com - ~ - ~ SonarQube 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. - ~ - ~ SonarQube 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. - --> <dataset> <rules id="200" name="Avoid Cycles" plugin_rule_key="AvoidCycles" plugin_config_key="[null]" plugin_name="squid"/> @@ -34,6 +15,7 @@ author_login="[null]" checksum="FFFFF" effort_to_fix="[null]" + remediation_cost="[null]" message="[null]" line="444" component_id="100" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates.xml index c37f8367898..a31dd9baa6e 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_resolve_conflicts_on_updates.xml @@ -1,22 +1,3 @@ -<!-- - ~ SonarQube, open source software quality management tool. - ~ Copyright (C) 2008-2013 SonarSource - ~ mailto:contact AT sonarsource DOT com - ~ - ~ SonarQube 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. - ~ - ~ SonarQube 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. - --> <dataset> <rules id="200" name="Avoid Cycles" plugin_rule_key="AvoidCycles" @@ -36,6 +17,7 @@ author_login="[null]" checksum="FFFFF" effort_to_fix="[null]" + remediation_cost="[null]" message="[null]" line="1" component_id="100" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues-result.xml index d03b9cd7523..8d700c2f865 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues-result.xml @@ -9,6 +9,7 @@ author_login="simon" checksum="FFFFF" effort_to_fix="[null]" + remediation_cost="[null]" message="[null]" line="5000" component_id="100" diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues.xml index 6d3ac5aa2d8..8f73107f0e9 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueStorageTest/should_update_issues.xml @@ -9,6 +9,7 @@ author_login="simon" checksum="FFFFF" effort_to_fix="[null]" + remediation_cost="[null]" message="[null]" line="3000" component_id="100" diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml index 49fe2baa8ed..354b35fcf13 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues-result.xml @@ -18,7 +18,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -28,7 +28,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> --> @@ -40,7 +40,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -51,7 +51,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -62,7 +62,7 @@ status="CLOSED" issue_close_date="2025-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> --> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues.xml index ba7573a6080..9ab34cc61fe 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_all_closed_issues.xml @@ -12,7 +12,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -22,7 +22,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -34,7 +34,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -45,7 +45,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -55,7 +55,7 @@ status="CLOSED" issue_close_date="2025-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml index ce95f4372ee..d931b3848fd 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues-result.xml @@ -13,7 +13,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -23,7 +23,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> --> @@ -35,7 +35,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -46,7 +46,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -56,7 +56,7 @@ status="CLOSED" issue_close_date="2025-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> diff --git a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues.xml b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues.xml index a50ca73937f..27af8b156b0 100644 --- a/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues.xml +++ b/sonar-core/src/test/resources/org/sonar/core/purge/PurgeDaoTest/should_delete_old_closed_issues.xml @@ -12,7 +12,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="1" kee="[null]" issue_key="ISSUE-1" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -22,7 +22,7 @@ status="CLOSED" issue_close_date="2010-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="2" kee="[null]" issue_key="ISSUE-2" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -34,7 +34,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="3" kee="[null]" issue_key="ISSUE-3" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -45,7 +45,7 @@ status="OPEN" issue_close_date="[null]" resolution="[null]" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="4" kee="[null]" issue_key="ISSUE-4" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> @@ -55,7 +55,7 @@ status="CLOSED" issue_close_date="2025-01-01" resolution="FIXED" line="200" severity="BLOCKER" reporter="perceval" assignee="arthur" rule_id="500" manual_severity="[false]" - message="[null]" action_plan_key="[null]" effort_to_fix="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" + message="[null]" action_plan_key="[null]" effort_to_fix="[null]" remediation_cost="[null]" issue_attributes="[null]" checksum="[null]" author_login="[null]" updated_at="[null]" issue_creation_date="2013-04-16" issue_update_date="2013-04-16" created_at="2013-04-16"/> <issue_changes id="5" kee="[null]" issue_key="ISSUE-5" created_at="[null]" updated_at="[null]" user_login="admin" change_type="comment" change_data="abc"/> diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java index 177e8eca2d4..847d1d65eca 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableList; import org.sonar.api.rule.RuleKey; import javax.annotation.CheckForNull; - import java.io.Serializable; import java.util.Date; import java.util.List; @@ -103,6 +102,12 @@ public interface Issue extends Serializable { Double effortToFix(); /** + * Elapsed time in minutes to fix the issue + */ + @CheckForNull + Long remediationCost(); + + /** * See constant values in {@link Issue}. */ String status(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java index 2214acfe5f5..a195ab4ea80 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java @@ -37,13 +37,8 @@ import org.sonar.api.rule.Severity; import javax.annotation.CheckForNull; import javax.annotation.Nullable; - import java.io.Serializable; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; /** * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING. @@ -61,6 +56,7 @@ public class DefaultIssue implements Issue { private String message; private Integer line; private Double effortToFix; + private Long remediationCost; private String status; private String resolution; private String reporter; @@ -190,6 +186,16 @@ public class DefaultIssue implements Issue { return this; } + @CheckForNull + public Long remediationCost() { + return remediationCost; + } + + public DefaultIssue setRemediationCost(@Nullable Long r) { + this.remediationCost = r; + return this; + } + public String status() { return status; } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java index 40646371131..32f164eb4e7 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/issue/internal/DefaultIssueTest.java @@ -48,6 +48,7 @@ public class DefaultIssueTest { .setMessage("a message") .setLine(7) .setEffortToFix(1.2d) + .setRemediationCost(1L) .setActionPlanKey("BCDE") .setStatus(Issue.STATUS_CLOSED) .setResolution(Issue.RESOLUTION_FIXED) @@ -75,6 +76,7 @@ public class DefaultIssueTest { assertThat(issue.message()).isEqualTo("a message"); assertThat(issue.line()).isEqualTo(7); assertThat(issue.effortToFix()).isEqualTo(1.2d); + assertThat(issue.remediationCost()).isEqualTo(1L); assertThat(issue.actionPlanKey()).isEqualTo("BCDE"); assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED); @@ -94,7 +96,7 @@ public class DefaultIssueTest { } @Test - public void should_set_empty_dates() throws Exception { + public void set_empty_dates() throws Exception { issue .setCreationDate(null) .setUpdateDate(null) @@ -134,7 +136,7 @@ public class DefaultIssueTest { } @Test - public void should_fail_on_empty_status() { + public void fail_on_empty_status() { try { issue.setStatus(""); fail(); @@ -144,7 +146,7 @@ public class DefaultIssueTest { } @Test - public void should_fail_on_bad_severity() { + public void fail_on_bad_severity() { try { issue.setSeverity("FOO"); fail(); diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb index 49436c1582e..4b908bcc8e9 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/issue.rb @@ -32,6 +32,7 @@ class Issue hash[:message] = issue.message if issue.message hash[:line] = issue.line.to_i if issue.line hash[:effortToFix] = issue.effortToFix.to_f if issue.effortToFix + hash[:remediationCost] = issue.remediationCost.to_i if issue.remediationCost hash[:reporter] = issue.reporter if issue.reporter hash[:assignee] = issue.assignee if issue.assignee hash[:author] = issue.authorLogin if issue.authorLogin @@ -55,4 +56,4 @@ class Issue } end -end
\ No newline at end of file +end diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/442_add_remediation_cost_to_issue.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/442_add_remediation_cost_to_issue.rb new file mode 100644 index 00000000000..2a5825554f4 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/442_add_remediation_cost_to_issue.rb @@ -0,0 +1,32 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2013 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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. +# + +# +# Sonar 4.0 +# SONAR-4716 +# + +class AddRemediationCostToIssue < ActiveRecord::Migration + + def self.up + add_column 'issues', 'remediation_cost', :integer, :null => true + end + +end diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java index 93aec19c148..c88a9a228e0 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java @@ -20,7 +20,6 @@ package org.sonar.wsclient.issue; import javax.annotation.CheckForNull; - import java.util.Date; import java.util.List; import java.util.Map; @@ -52,6 +51,9 @@ public interface Issue { @CheckForNull Double effortToFix(); + @CheckForNull + Long remediationCost(); + String status(); /** diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java index 36f25bc8d30..2f83632ab17 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java @@ -75,6 +75,11 @@ public class DefaultIssue implements Issue { return JsonUtils.getDouble(json, "effortToFix"); } + @CheckForNull + public Long remediationCost() { + return JsonUtils.getLong(json, "remediationCost"); + } + public String status() { return JsonUtils.getString(json, "status"); } diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java index 725405444ab..4e95e9dcbe0 100644 --- a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java +++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java @@ -27,7 +27,6 @@ import org.sonar.wsclient.issue.*; import org.sonar.wsclient.user.User; import java.util.List; -import java.util.TimeZone; import static org.fest.assertions.Assertions.assertThat; @@ -52,6 +51,7 @@ public class IssueJsonParserTest { assertThat(first.assignee()).isEqualTo("karadoc"); assertThat(first.message()).isEqualTo("the message"); assertThat(first.effortToFix()).isEqualTo(4.2); + assertThat(first.remediationCost()).isEqualTo(10L); assertThat(first.reporter()).isEqualTo("perceval"); assertThat(first.author()).isEqualTo("pirlouis"); assertThat(first.actionPlan()).isEqualTo("9450b10c-e725-48b8-bf01-acdec751c491"); @@ -67,6 +67,7 @@ public class IssueJsonParserTest { assertThat(second.key()).isEqualTo("FGHIJ"); assertThat(second.line()).isNull(); assertThat(second.effortToFix()).isNull(); + assertThat(second.remediationCost()).isNull(); assertThat(second.reporter()).isNull(); assertThat(second.author()).isNull(); assertThat(second.attribute("JIRA")).isNull(); diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json index 8c8505f5d73..e1f131c2f96 100644 --- a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json +++ b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json @@ -11,6 +11,7 @@ "status": "OPEN", "assignee": "karadoc", "effortToFix": 4.2, + "remediationCost": 10, "message": "the message", "title": "the title", "reporter": "perceval", |