import org.sonar.api.checks.NoSonarFilter;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
import org.sonar.core.timemachine.Periods;
import org.sonar.plugins.core.batch.IndexProjectPostJob;
import org.sonar.plugins.core.charts.DistributionAreaChart;
// technical debt
TechnicalDebtDecorator.class,
NewTechnicalDebtDecorator.class,
+ IssueChangelogDebtCalculator.class,
+ WorkDurationFactory.class,
// batch
ProfileEventsSensor.class,
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.utils.KeyValueFormat;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.issue.IssueCache;
import org.sonar.batch.scan.LastSnapshots;
import org.sonar.core.issue.IssueUpdater;
private final ResourcePerspectives perspectives;
private final RulesProfile rulesProfile;
private final RuleFinder ruleFinder;
+ private final WorkDurationFactory workDurationFactory;
public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
LastSnapshots lastSnapshots, SonarIndex index,
Project project,
ResourcePerspectives perspectives,
RulesProfile rulesProfile,
- RuleFinder ruleFinder) {
+ RuleFinder ruleFinder, WorkDurationFactory workDurationFactory) {
this.issueCache = issueCache;
this.initialOpenIssues = initialOpenIssues;
this.tracking = tracking;
this.handlers = handlers;
this.workflow = workflow;
this.updater = updater;
+ this.workDurationFactory = workDurationFactory;
this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
this.perspectives = perspectives;
this.rulesProfile = rulesProfile;
updater.setPastMessage(issue, ref.getMessage(), changeContext);
updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
Long technicalDebt = ref.getTechnicalDebt();
- WorkUnit previousTechnicalDebt = technicalDebt != null ? WorkUnit.fromLong(technicalDebt) : null;
+ WorkDuration previousTechnicalDebt = technicalDebt != null ? workDurationFactory.createFromWorkingLong(technicalDebt) : null;
updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
}
}
private void addUnmatched(Collection<IssueDto> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
for (IssueDto unmatchedDto : unmatchedIssues) {
- DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
+ DefaultIssue unmatched = unmatchedDto.toDefaultIssue(workDurationFactory.createFromWorkingLong(unmatchedDto.getTechnicalDebt()));
if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
}
private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
- DefaultIssue dead = deadDto.toDefaultIssue();
+ DefaultIssue dead = deadDto.toDefaultIssue(workDurationFactory.createFromWorkingLong(deadDto.getTechnicalDebt()));
updateUnmatchedIssue(dead, true);
issues.add(dead);
}
package org.sonar.plugins.core.technicaldebt;
-import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Ordering;
-import org.apache.commons.lang.time.DateUtils;
-import org.sonar.api.CoreProperties;
import org.sonar.api.batch.*;
import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.MeasureUtils;
import org.sonar.api.measures.Metric;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.components.Period;
import org.sonar.batch.components.TimeMachineConfiguration;
-import org.sonar.core.issue.IssueUpdater;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
import javax.annotation.Nullable;
-import java.util.*;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
private final ResourcePerspectives perspectives;
private final TimeMachineConfiguration timeMachineConfiguration;
+ private final IssueChangelogDebtCalculator issueChangelogDebtCalculator;
+ private final WorkDurationFactory workDurationFactory;
- private final int hoursInDay;
-
- public NewTechnicalDebtDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration, Settings settings) {
+ public NewTechnicalDebtDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration,
+ IssueChangelogDebtCalculator issueChangelogDebtCalculator, WorkDurationFactory workDurationFactory) {
this.perspectives = perspectives;
this.timeMachineConfiguration = timeMachineConfiguration;
- this.hoursInDay = settings.getInt(CoreProperties.HOURS_IN_DAY);
+ this.issueChangelogDebtCalculator = issueChangelogDebtCalculator;
+ this.workDurationFactory = workDurationFactory;
}
public boolean shouldExecuteOnProject(Project project) {
}
private Double calculateNewTechnicalDebtValue(Collection<Issue> issues, @Nullable Date periodDate) {
- double value = 0;
+ WorkDuration duration = workDurationFactory.createFromWorkingLong(0l);
for (Issue issue : issues) {
- double currentTechnicalDebtValue = 0d;
- WorkUnit currentTechnicalDebt = ((DefaultIssue) issue).technicalDebt();
- if (currentTechnicalDebt != null) {
- currentTechnicalDebtValue = currentTechnicalDebt.toDays(hoursInDay);
- }
-
- Date periodDatePlusOneSecond = periodDate != null ? DateUtils.addSeconds(periodDate, 1) : null;
- if (isAfter(issue.creationDate(), periodDatePlusOneSecond)) {
- value += currentTechnicalDebtValue;
- } else {
- value += calculateNewTechnicalDebtValueFromChangelog(currentTechnicalDebtValue, issue, periodDate);
- }
- }
- return value;
- }
-
- private double calculateNewTechnicalDebtValueFromChangelog(double currentTechnicalDebtValue, Issue issue, Date periodDate) {
- List<FieldDiffs> changelog = technicalDebtHistory(issue);
- for (Iterator<FieldDiffs> iterator = changelog.iterator(); iterator.hasNext(); ) {
- FieldDiffs diff = iterator.next();
- Date date = diff.creationDate();
- if (isLesserOrEqual(date, periodDate)) {
- // return new value from the change that is just before the period date
- return currentTechnicalDebtValue - newValue(diff).toDays(hoursInDay);
+ WorkDuration debt = issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, periodDate);
+ if (debt != null) {
+ duration = duration.add(debt);
}
- if (!iterator.hasNext()) {
- // return old value from the change that is just after the period date when there's no more element in changelog
- return currentTechnicalDebtValue - oldValue(diff).toDays(hoursInDay);
- }
- }
- // Return 0 when no changelog
- return 0d;
- }
-
- private List<FieldDiffs> technicalDebtHistory(Issue issue) {
- List<FieldDiffs> technicalDebtChangelog = changesOnField(((DefaultIssue) issue).changes());
- if (!technicalDebtChangelog.isEmpty()) {
- // Changelog have to be sorted from newest to oldest.
- // Null date should be the latest as this happen when technical debt has changed since previous analysis.
- Ordering<FieldDiffs> ordering = Ordering.natural().reverse().nullsFirst().onResultOf(new Function<FieldDiffs, Date>() {
- public Date apply(FieldDiffs diff) {
- return diff.creationDate();
- }
- });
- return ordering.immutableSortedCopy(technicalDebtChangelog);
}
- return Collections.emptyList();
- }
-
- private List<FieldDiffs> changesOnField(Collection<FieldDiffs> fieldDiffs) {
- List<FieldDiffs> diffs = newArrayList();
- for (FieldDiffs fieldDiff : fieldDiffs) {
- if (fieldDiff.diffs().containsKey(IssueUpdater.TECHNICAL_DEBT)) {
- diffs.add(fieldDiff);
- }
- }
- return diffs;
- }
-
- private WorkUnit newValue(FieldDiffs fieldDiffs) {
- for (Map.Entry<String, FieldDiffs.Diff> entry : fieldDiffs.diffs().entrySet()) {
- if (entry.getKey().equals(IssueUpdater.TECHNICAL_DEBT)) {
- Long newValue = entry.getValue().newValueLong();
- return newValue != null ? WorkUnit.fromLong(newValue) : WorkUnit.fromLong(0);
- }
- }
- return WorkUnit.fromLong(0);
- }
-
- private WorkUnit oldValue(FieldDiffs fieldDiffs) {
- for (Map.Entry<String, FieldDiffs.Diff> entry : fieldDiffs.diffs().entrySet()) {
- if (entry.getKey().equals(IssueUpdater.TECHNICAL_DEBT)) {
- Long value = entry.getValue().oldValueLong();
- return value != null ? WorkUnit.fromLong(value) : WorkUnit.fromLong(0);
- }
- }
- return WorkUnit.fromLong(0);
- }
-
- private boolean isAfter(@Nullable Date currentDate, @Nullable Date pastDate) {
- return pastDate == null || (currentDate != null && DateUtils.truncatedCompareTo(currentDate, pastDate, Calendar.SECOND) > 0);
- }
-
- private boolean isLesserOrEqual(@Nullable Date currentDate, @Nullable Date pastDate) {
- return (currentDate != null) && (pastDate == null || (DateUtils.truncatedCompareTo(currentDate, pastDate, Calendar.SECOND) <= 0));
+ return duration.toWorkingDays();
}
private boolean shouldSaveNewMetrics(DecoratorContext context) {
import org.sonar.api.batch.*;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.technicaldebt.batch.Characteristic;
import org.sonar.api.technicaldebt.batch.Requirement;
import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import java.util.Arrays;
import java.util.Collection;
private final ResourcePerspectives perspectives;
private final TechnicalDebtModel model;
+ private final WorkDurationFactory workDurationFactory;
- private final int hoursInDay;
-
- public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, Settings settings) {
+ public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, WorkDurationFactory workDurationFactory) {
this.perspectives = perspectives;
this.model = model;
- this.hoursInDay = settings.getInt(CoreProperties.HOURS_IN_DAY);
+ this.workDurationFactory = workDurationFactory;
}
public boolean shouldExecuteOnProject(Project project) {
}
private double computeTechnicalDebt(Metric metric, DecoratorContext context, Requirement requirement, Collection<Issue> issues) {
- double value = 0.0;
+ WorkDuration debt = workDurationFactory.createFromWorkingLong(0l);
+// double value = 0d;
if (issues != null) {
for (Issue issue : issues) {
- WorkUnit debt = ((DefaultIssue) issue).technicalDebt();
- if (debt != null) {
- value += debt.toDays(hoursInDay);
- } else {
- value += 0d;
- }
+// if (debt != null) {
+// value += debt.toWorkingDays();
+// }
+ debt = debt.add(((DefaultIssue) issue).technicalDebt());
}
}
+ double value = debt.toWorkingDays();
for (Measure measure : context.getChildrenMeasures(MeasuresFilters.requirement(metric, requirement))) {
Requirement measureRequirement = measure.getRequirement();
if (measureRequirement != null && measureRequirement.equals(requirement) && measure.getValue() != null) {
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
+import org.sonar.api.CoreProperties;
import org.sonar.api.batch.DecoratorContext;
import org.sonar.api.batch.SonarIndex;
import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.issue.IssueCache;
import org.sonar.batch.scan.LastSnapshots;
import org.sonar.core.issue.IssueUpdater;
@Before
public void init() {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, 8);
+
decorator = new IssueTrackingDecorator(
issueCache,
initialOpenIssues,
mock(Project.class),
perspectives,
profile,
- ruleFinder);
+ ruleFinder, new WorkDurationFactory(settings));
}
@Test
verify(updater).setPastLine(eq(issue), eq(10));
verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
- verify(updater).setPastTechnicalDebt(eq(issue), eq(new WorkUnit.Builder().setMinutes(1).build()), any(IssueChangeContext.class));
+ verify(updater).setPastTechnicalDebt(eq(issue), eq(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.MINUTES, 8)), any(IssueChangeContext.class));
}
@Test
import org.sonar.api.measures.Metric;
import org.sonar.api.resources.Resource;
import org.sonar.api.test.IsMeasure;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.components.Period;
import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
import java.util.Date;
Date fiveDaysAgo;
Date fourDaysAgo;
- WorkUnit oneDaysDebt = new WorkUnit.Builder().setDays(1).build();
- WorkUnit twoDaysDebt = new WorkUnit.Builder().setDays(2).build();
- WorkUnit fiveDaysDebt = new WorkUnit.Builder().setDays(5).build();
+ WorkDuration oneDaysDebt = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8);
+ WorkDuration twoDaysDebt = WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.DAYS, 8);
+ WorkDuration fiveDaysDebt = WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.DAYS, 8);
@Before
public void setup() {
when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, fiveDaysAgo), new Period(2, tenDaysAgo)));
- decorator = new NewTechnicalDebtDecorator(perspectives, timeMachineConfiguration, settings);
+ WorkDurationFactory workDurationFactory = new WorkDurationFactory(settings);
+ decorator = new NewTechnicalDebtDecorator(perspectives, timeMachineConfiguration, new IssueChangelogDebtCalculator(workDurationFactory), workDurationFactory);
}
@Test
verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)));
}
- private Long fromWorkDayDuration(WorkUnit workDayDuration) {
+ private Long fromWorkDayDuration(WorkDuration workDayDuration) {
return workDayDuration.toLong();
}
import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
import org.sonar.api.test.IsMeasure;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import java.util.List;
Settings settings = new Settings();
settings.setProperty(CoreProperties.HOURS_IN_DAY, "8");
- decorator = new TechnicalDebtDecorator(perspectives, defaultTechnicalDebtModel, settings);
+ decorator = new TechnicalDebtDecorator(perspectives, defaultTechnicalDebtModel, new WorkDurationFactory(settings));
}
@Test
@Test
public void add_technical_debt_from_one_issue_and_no_parent() throws Exception {
- WorkUnit technicalDebt = new WorkUnit.Builder().setDays(1).build();
+ WorkDuration technicalDebt = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8);
Issue issue = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt);
when(issuable.issues()).thenReturn(newArrayList(issue));
@Test
public void add_technical_debt_from_one_issue_and_propagate_to_parents() throws Exception {
- WorkUnit technicalDebt = new WorkUnit.Builder().setDays(1).build();
+ WorkDuration technicalDebt = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8);
Issue issue = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt);
when(issuable.issues()).thenReturn(newArrayList(issue));
@Test
public void add_technical_debt_from_issues() throws Exception {
- WorkUnit technicalDebt1 = new WorkUnit.Builder().setDays(1).build();
- WorkUnit technicalDebt2 = new WorkUnit.Builder().setDays(2).build();
+ WorkDuration technicalDebt1 = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8);
+ WorkDuration technicalDebt2 = WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.DAYS, 8);
Issue issue1 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1);
Issue issue2 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt1);
@Test
public void add_technical_debt_from_children_measures() throws Exception {
- WorkUnit technicalDebt = new WorkUnit.Builder().setDays(1).build();
+ WorkDuration technicalDebt = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8);
Issue issue1 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt);
Issue issue2 = createIssue("rule1", "repo1").setTechnicalDebt(technicalDebt);
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.batch.debt;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
+
+public class DebtModelLoader implements BatchComponent {
+
+ private final CharacteristicDao dao;
+ private final RuleFinder ruleFinder;
+
+ public DebtModelLoader(CharacteristicDao dao, RuleFinder ruleFinder) {
+ this.dao = dao;
+ this.ruleFinder = ruleFinder;
+ }
+
+ public TechnicalDebtModel load() {
+ DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel();
+ List<CharacteristicDto> dtos = dao.selectEnabledCharacteristics();
+ Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap();
+
+ addRootCharacteristics(model, dtos, characteristicsById);
+ addCharacteristics(dtos, characteristicsById);
+ addRequirements(dtos, characteristicsById);
+ return model;
+ }
+
+ private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
+ for (CharacteristicDto dto : dtos) {
+ if (dto.getParentId() == null) {
+ DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null);
+ model.addRootCharacteristic(rootCharacteristic);
+ characteristicsById.put(dto.getId(), rootCharacteristic);
+ }
+ }
+ }
+
+ private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
+ for (CharacteristicDto dto : dtos) {
+ if (dto.getParentId() != null && dto.getRuleId() == null) {
+ DefaultCharacteristic parent = characteristicsById.get(dto.getParentId());
+ DefaultCharacteristic characteristic = dto.toCharacteristic(parent);
+ characteristicsById.put(dto.getId(), characteristic);
+ }
+ }
+ }
+
+ private void addRequirements(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
+ Map<Integer, Rule> rulesById = rulesById(ruleFinder.findAll(RuleQuery.create()));
+ for (CharacteristicDto dto : dtos) {
+ Integer ruleId = dto.getRuleId();
+ if (ruleId != null) {
+ DefaultCharacteristic characteristic = characteristicsById.get(dto.getParentId());
+ DefaultCharacteristic rootCharacteristic = characteristicsById.get(dto.getRootId());
+ Rule rule = rulesById.get(ruleId);
+ RuleKey ruleKey = RuleKey.of(rule.getRepositoryKey(), rule.getKey());
+ dto.toRequirement(ruleKey, characteristic, rootCharacteristic);
+ }
+ }
+ }
+
+ private Map<Integer, Rule> rulesById(Collection<Rule> rules) {
+ Map<Integer, Rule> rulesById = newHashMap();
+ for (Rule rule : rules) {
+ rulesById.put(rule.getId(), rule);
+ }
+ return rulesById;
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.batch.debt;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.utils.TimeProfiler;
+
+public class DebtModelProvider extends ProviderAdapter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DebtModelProvider.class);
+
+ private TechnicalDebtModel model;
+
+ public TechnicalDebtModel provide(DebtModelLoader loader) {
+ if (model == null) {
+ TimeProfiler profiler = new TimeProfiler(LOG).start("Loading technical debt model");
+ model = loader.load();
+ profiler.stop();
+ }
+ return model;
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.batch.debt;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Ordering;
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.FieldDiffs;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
+import org.sonar.core.issue.IssueUpdater;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.*;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class IssueChangelogDebtCalculator implements BatchComponent {
+
+ private final WorkDurationFactory workDurationFactory;
+
+ public IssueChangelogDebtCalculator(WorkDurationFactory workDurationFactory) {
+ this.workDurationFactory = workDurationFactory;
+ }
+
+ @CheckForNull
+ public WorkDuration calculateNewTechnicalDebt(Issue issue, @Nullable Date periodDate) {
+ WorkDuration currentTechnicalDebt = ((DefaultIssue) issue).technicalDebt();
+ Date periodDatePlusOneSecond = periodDate != null ? DateUtils.addSeconds(periodDate, 1) : null;
+ if (isAfter(issue.creationDate(), periodDatePlusOneSecond)) {
+ return currentTechnicalDebt;
+ } else {
+ return calculateNewTechnicalDebtValueFromChangelog(currentTechnicalDebt, issue, periodDate);
+ }
+ }
+
+ @CheckForNull
+ private WorkDuration calculateNewTechnicalDebtValueFromChangelog(WorkDuration currentTechnicalDebtValue, Issue issue, Date periodDate) {
+ List<FieldDiffs> changelog = technicalDebtHistory(issue);
+ for (Iterator<FieldDiffs> iterator = changelog.iterator(); iterator.hasNext(); ) {
+ FieldDiffs diff = iterator.next();
+ Date date = diff.creationDate();
+ if (isLesserOrEqual(date, periodDate)) {
+ // return new value from the change that is just before the period date
+ return currentTechnicalDebtValue.subtract(newValue(diff));
+ }
+ if (!iterator.hasNext()) {
+ // return old value from the change that is just after the period date when there's no more element in changelog
+ return currentTechnicalDebtValue.subtract(oldValue(diff));
+ }
+ }
+ // Return null when no changelog
+ return null;
+ }
+
+ private List<FieldDiffs> technicalDebtHistory(Issue issue) {
+ List<FieldDiffs> technicalDebtChangelog = changesOnField(((DefaultIssue) issue).changes());
+ if (!technicalDebtChangelog.isEmpty()) {
+ // Changelog have to be sorted from newest to oldest.
+ // Null date should be the first as this happen when technical debt has changed since previous analysis.
+ Ordering<FieldDiffs> ordering = Ordering.natural().reverse().nullsFirst().onResultOf(new Function<FieldDiffs, Date>() {
+ public Date apply(FieldDiffs diff) {
+ return diff.creationDate();
+ }
+ });
+ return ordering.immutableSortedCopy(technicalDebtChangelog);
+ }
+ return Collections.emptyList();
+ }
+
+ private List<FieldDiffs> changesOnField(Collection<FieldDiffs> fieldDiffs) {
+ List<FieldDiffs> diffs = newArrayList();
+ for (FieldDiffs fieldDiff : fieldDiffs) {
+ if (fieldDiff.diffs().containsKey(IssueUpdater.TECHNICAL_DEBT)) {
+ diffs.add(fieldDiff);
+ }
+ }
+ return diffs;
+ }
+
+ private WorkDuration newValue(FieldDiffs fieldDiffs) {
+ for (Map.Entry<String, FieldDiffs.Diff> entry : fieldDiffs.diffs().entrySet()) {
+ if (entry.getKey().equals(IssueUpdater.TECHNICAL_DEBT)) {
+ Long newValue = entry.getValue().newValueLong();
+ return newValue != null ? workDurationFactory.createFromWorkingLong(newValue) : workDurationFactory.createFromWorkingLong(0l);
+ }
+ }
+ return workDurationFactory.createFromWorkingLong(0l);
+ }
+
+ private WorkDuration oldValue(FieldDiffs fieldDiffs) {
+ for (Map.Entry<String, FieldDiffs.Diff> entry : fieldDiffs.diffs().entrySet()) {
+ if (entry.getKey().equals(IssueUpdater.TECHNICAL_DEBT)) {
+ Long value = entry.getValue().oldValueLong();
+ return value != null ? workDurationFactory.createFromWorkingLong(value) : workDurationFactory.createFromWorkingLong(0l);
+ }
+ }
+ return workDurationFactory.createFromWorkingLong(0l);
+ }
+
+ private boolean isAfter(@Nullable Date currentDate, @Nullable Date pastDate) {
+ return pastDate == null || (currentDate != null && DateUtils.truncatedCompareTo(currentDate, pastDate, Calendar.SECOND) > 0);
+ }
+
+ private boolean isLesserOrEqual(@Nullable Date currentDate, @Nullable Date pastDate) {
+ return (currentDate != null) && (pastDate == null || (DateUtils.truncatedCompareTo(currentDate, pastDate, Calendar.SECOND) <= 0));
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.batch.debt;
+
+import com.google.common.base.Objects;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.technicaldebt.batch.Requirement;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
+import org.sonar.api.utils.WorkUnit;
+
+import javax.annotation.Nullable;
+
+/**
+ * Computes the remediation cost based on the quality and analysis models.
+ */
+public class RuleDebtCalculator implements BatchExtension {
+
+ private TechnicalDebtModel model;
+ private final WorkDurationFactory workDurationFactory;
+
+ public RuleDebtCalculator(TechnicalDebtModel model, WorkDurationFactory workDurationFactory) {
+ this.model = model;
+ this.workDurationFactory = workDurationFactory;
+ }
+
+ /**
+ * Calculate the technical debt from a requirement
+ */
+ public WorkDuration calculateTechnicalDebt(RuleKey ruleKey, Double effortToFix) {
+ Requirement requirement = model.requirementsByRule(ruleKey);
+ if (requirement != null) {
+ if (requirement.function().equals(DefaultRequirement.CONSTANT_ISSUE) && effortToFix != null) {
+ throw new IllegalArgumentException("Requirement for '" + ruleKey + "' can not use 'Constant/issue' remediation function " +
+ "because this rule does not have a fixed remediation cost.");
+ }
+ return calculateTechnicalDebt(requirement, effortToFix);
+ }
+ return null;
+ }
+
+ private WorkDuration calculateTechnicalDebt(Requirement requirement, @Nullable Double effortToFix) {
+ WorkDuration result = workDurationFactory.createFromWorkingValue(0, WorkDuration.UNIT.DAYS);
+
+ WorkUnit factorUnit = requirement.factor();
+ if (factorUnit != null) {
+ int factorValue = (int) requirement.factor().getValue();
+ int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue();
+ result = workDurationFactory.createFromWorkingValue(factorValue, toUnit(requirement.factor().getUnit())).multiply(effortToFixValue);
+ }
+
+ WorkUnit offsetUnit = requirement.offset();
+ if (offsetUnit != null) {
+ int offsetValue = (int) requirement.offset().getValue();
+ result = result.add(workDurationFactory.createFromWorkingValue(offsetValue, toUnit(requirement.offset().getUnit())));
+ }
+
+ return result;
+ }
+
+ private static WorkDuration.UNIT toUnit(String requirementUnit){
+ if (requirementUnit.equals(WorkUnit.DAYS)) {
+ return WorkDuration.UNIT.DAYS;
+ } else if (requirementUnit.equals(WorkUnit.HOURS)) {
+ return WorkDuration.UNIT.HOURS;
+ } else if (requirementUnit.equals(WorkUnit.MINUTES)) {
+ return WorkDuration.UNIT.MINUTES;
+ }
+ throw new IllegalStateException("Invalid unit : " + requirementUnit);
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.debt;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.Violation;
import org.sonar.api.utils.MessageException;
-import org.sonar.batch.technicaldebt.TechnicalDebtCalculator;
+import org.sonar.batch.debt.RuleDebtCalculator;
import org.sonar.core.issue.DefaultIssueBuilder;
import javax.annotation.Nullable;
private final IssueCache cache;
private final Project project;
private final IssueFilters filters;
- private final TechnicalDebtCalculator technicalDebtCalculator;
+ private final RuleDebtCalculator technicalDebtCalculator;
private final RuleFinder ruleFinder;
- public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters, TechnicalDebtCalculator technicalDebtCalculator, RuleFinder ruleFinder) {
+ public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters, RuleDebtCalculator technicalDebtCalculator, RuleFinder ruleFinder) {
this.qProfile = qProfile;
this.cache = cache;
this.project = project;
if (issue.severity() == null) {
issue.setSeverity(activeRule.getSeverity().name());
}
- issue.setTechnicalDebt(technicalDebtCalculator.calculTechnicalDebt(issue));
+ issue.setTechnicalDebt(technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix()));
}
}
import org.sonar.api.resources.Project;
import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.batch.DefaultFileLinesContextFactory;
import org.sonar.batch.DefaultResourceCreationLock;
import org.sonar.batch.ProjectConfigurator;
import org.sonar.batch.ProjectTree;
import org.sonar.batch.bootstrap.*;
import org.sonar.batch.components.PeriodsDefinition;
+import org.sonar.batch.debt.DebtModelLoader;
+import org.sonar.batch.debt.DebtModelProvider;
+import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+import org.sonar.batch.debt.RuleDebtCalculator;
import org.sonar.batch.index.*;
import org.sonar.batch.issue.*;
import org.sonar.batch.phases.GraphPersister;
import org.sonar.batch.scan.maven.MavenPluginExecutor;
import org.sonar.batch.source.HighlightableBuilder;
import org.sonar.batch.source.SymbolizableBuilder;
-import org.sonar.batch.technicaldebt.TechnicalDebtCalculator;
-import org.sonar.batch.technicaldebt.TechnicalDebtModelLoader;
-import org.sonar.batch.technicaldebt.TechnicalDebtModelProvider;
import org.sonar.core.component.ScanGraph;
import org.sonar.core.issue.IssueNotifications;
import org.sonar.core.issue.IssueUpdater;
IssuePersister.class,
IssueNotifications.class,
DefaultProjectIssues.class,
+ IssueChangelogDebtCalculator.class,
// tests
TestPlanPerspectiveLoader.class,
SymbolizableBuilder.class,
// technical debt
- TechnicalDebtModelLoader.class,
- TechnicalDebtCalculator.class,
- new TechnicalDebtModelProvider(),
+ DebtModelLoader.class,
+ RuleDebtCalculator.class,
+ WorkDurationFactory.class,
+ new DebtModelProvider(),
// Differential periods
PeriodsDefinition.class,
+++ /dev/null
-/*
- * 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.
- */
-package org.sonar.batch.technicaldebt;
-
-import com.google.common.base.Objects;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.technicaldebt.batch.Requirement;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.WorkUnit;
-
-/**
- * Computes the remediation cost based on the quality and analysis models.
- */
-public class TechnicalDebtCalculator implements BatchExtension {
-
- private int hoursInDay;
-
- private TechnicalDebtModel model;
-
- public TechnicalDebtCalculator(TechnicalDebtModel model, Settings settings) {
- this.model = model;
- this.hoursInDay = settings.getInt(CoreProperties.HOURS_IN_DAY);
- }
-
- /**
- * Get the technical debt from the requirement
- */
- public WorkUnit calculTechnicalDebt(Issue issue) {
- Requirement requirement = model.requirementsByRule(issue.ruleKey());
- if (requirement != null) {
- if (requirement.function().equals(DefaultRequirement.CONSTANT_ISSUE) && issue.effortToFix() != null) {
- throw new IllegalArgumentException("Requirement for '" + issue.ruleKey() + "' can not use 'Constant/issue' remediation function " +
- "because this rule does not have a fixed remediation cost.");
- }
- return fromMinutes(calculTechnicalDebt(requirement, issue));
- }
- return null;
- }
-
- private long calculTechnicalDebt(Requirement requirement, Issue issue) {
- long effortToFix = Objects.firstNonNull(issue.effortToFix(), 1L).longValue();
-
- WorkUnit factorUnit = requirement.factor();
- long factor = factorUnit != null ? toMinutes(factorUnit) : 0L;
-
- WorkUnit offsetUnit = requirement.offset();
- long offset = offsetUnit != null ? toMinutes(offsetUnit) : 0L;
-
- return effortToFix * factor + offset;
- }
-
- private long toMinutes(WorkUnit factor) {
- if (factor.days() > 0) {
- return Double.valueOf(factor.days() * hoursInDay * 60d).longValue();
- } else if (factor.hours() > 0) {
- return Double.valueOf(factor.hours() * 60d).longValue();
- } else {
- return Double.valueOf(factor.minutes()).longValue();
- }
- }
-
- private WorkUnit fromMinutes(Long inMinutes) {
- int oneHourInMinute = 60;
- int days = 0;
- int hours = 0;
- int minutes = 0;
-
- int oneWorkingDay = hoursInDay * oneHourInMinute;
- if (inMinutes >= oneWorkingDay) {
- Long nbDays = inMinutes / oneWorkingDay;
- days = nbDays.shortValue();
- inMinutes = inMinutes - (nbDays * oneWorkingDay);
- }
-
- if (inMinutes >= oneHourInMinute) {
- Long nbHours = inMinutes / oneHourInMinute;
- hours = nbHours.shortValue();
- inMinutes = inMinutes - (nbHours * oneHourInMinute);
- }
-
- minutes = inMinutes.shortValue();
-
- return new WorkUnit.Builder().setDays(days).setHours(hours).setMinutes(minutes).build();
- }
-}
+++ /dev/null
-/*
- * 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.
- */
-
-package org.sonar.batch.technicaldebt;
-
-import org.sonar.api.BatchComponent;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.collect.Maps.newHashMap;
-
-public class TechnicalDebtModelLoader implements BatchComponent {
-
- private final CharacteristicDao dao;
- private final RuleFinder ruleFinder;
-
- public TechnicalDebtModelLoader(CharacteristicDao dao, RuleFinder ruleFinder) {
- this.dao = dao;
- this.ruleFinder = ruleFinder;
- }
-
- public TechnicalDebtModel load() {
- DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel();
- List<CharacteristicDto> dtos = dao.selectEnabledCharacteristics();
- Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap();
-
- addRootCharacteristics(model, dtos, characteristicsById);
- addCharacteristics(dtos, characteristicsById);
- addRequirements(dtos, characteristicsById);
- return model;
- }
-
- private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
- for (CharacteristicDto dto : dtos) {
- if (dto.getParentId() == null) {
- DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null);
- model.addRootCharacteristic(rootCharacteristic);
- characteristicsById.put(dto.getId(), rootCharacteristic);
- }
- }
- }
-
- private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
- for (CharacteristicDto dto : dtos) {
- if (dto.getParentId() != null && dto.getRuleId() == null) {
- DefaultCharacteristic parent = characteristicsById.get(dto.getParentId());
- DefaultCharacteristic characteristic = dto.toCharacteristic(parent);
- characteristicsById.put(dto.getId(), characteristic);
- }
- }
- }
-
- private void addRequirements(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
- Map<Integer, Rule> rulesById = rulesById(ruleFinder.findAll(RuleQuery.create()));
- for (CharacteristicDto dto : dtos) {
- Integer ruleId = dto.getRuleId();
- if (ruleId != null) {
- DefaultCharacteristic characteristic = characteristicsById.get(dto.getParentId());
- DefaultCharacteristic rootCharacteristic = characteristicsById.get(dto.getRootId());
- Rule rule = rulesById.get(ruleId);
- RuleKey ruleKey = RuleKey.of(rule.getRepositoryKey(), rule.getKey());
- dto.toRequirement(ruleKey, characteristic, rootCharacteristic);
- }
- }
- }
-
- private Map<Integer, Rule> rulesById(Collection<Rule> rules) {
- Map<Integer, Rule> rulesById = newHashMap();
- for (Rule rule : rules) {
- rulesById.put(rule.getId(), rule);
- }
- return rulesById;
- }
-
-}
+++ /dev/null
-/*
- * 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.
- */
-
-package org.sonar.batch.technicaldebt;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.utils.TimeProfiler;
-
-public class TechnicalDebtModelProvider extends ProviderAdapter {
-
- private static final Logger LOG = LoggerFactory.getLogger(TechnicalDebtModelProvider.class);
-
- private TechnicalDebtModel model;
-
- public TechnicalDebtModel provide(TechnicalDebtModelLoader loader) {
- if (model == null) {
- TimeProfiler profiler = new TimeProfiler(LOG).start("Loading technical debt model");
- model = loader.load();
- profiler.stop();
- }
- return model;
- }
-}
+++ /dev/null
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.batch.technicaldebt;
-
-import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.batch.debt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
+import org.sonar.api.utils.WorkUnit;
+import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+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.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelLoaderTest {
+
+ @Mock
+ CharacteristicDao dao;
+
+ @Mock
+ RuleFinder ruleFinder;
+
+ DebtModelLoader loader;
+
+ @Before
+ public void before() {
+ loader = new DebtModelLoader(dao, ruleFinder);
+ }
+
+ @Test
+ public void find_all() throws Exception {
+ CharacteristicDto rootCharacteristicDto = new CharacteristicDto()
+ .setId(1)
+ .setKey("MEMORY_EFFICIENCY")
+ .setName("Memory use");
+
+ CharacteristicDto characteristicDto = new CharacteristicDto()
+ .setId(2)
+ .setKey("EFFICIENCY")
+ .setName("Efficiency")
+ .setParentId(1);
+
+ CharacteristicDto requirementDto = new CharacteristicDto()
+ .setId(3)
+ .setParentId(2)
+ .setRuleId(100)
+ .setFunction("linear")
+ .setFactorValue(2d)
+ .setFactorUnit(WorkUnit.DAYS)
+ .setOffsetValue(0d)
+ .setOffsetUnit(WorkUnit.DEFAULT_UNIT);
+
+ RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp");
+ Rule rule = Rule.create(ruleKey.repository(), ruleKey.rule());
+ rule.setId(100);
+ when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(rule));
+ when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto));
+
+ DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) loader.load();
+ assertThat(result.rootCharacteristics()).hasSize(1);
+
+ DefaultCharacteristic rootCharacteristic = result.characteristicByKey("MEMORY_EFFICIENCY");
+ assertThat(rootCharacteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
+ assertThat(rootCharacteristic.name()).isEqualTo("Memory use");
+ assertThat(rootCharacteristic.parent()).isNull();
+ assertThat(rootCharacteristic.requirements()).isEmpty();
+ assertThat(rootCharacteristic.children()).hasSize(1);
+ assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY");
+
+ DefaultCharacteristic characteristic = result.characteristicByKey("EFFICIENCY");
+ assertThat(characteristic.key()).isEqualTo("EFFICIENCY");
+ assertThat(characteristic.name()).isEqualTo("Efficiency");
+ assertThat(characteristic.parent().key()).isEqualTo("MEMORY_EFFICIENCY");
+ assertThat(characteristic.children()).isEmpty();
+ assertThat(characteristic.requirements()).hasSize(1);
+ assertThat(characteristic.requirements().get(0).ruleKey()).isEqualTo(ruleKey);
+
+ DefaultRequirement requirement = result.requirementsByRule(ruleKey);
+ assertThat(requirement.ruleKey()).isEqualTo(ruleKey);
+ assertThat(requirement.function()).isEqualTo("linear");
+ assertThat(requirement.factor()).isEqualTo(WorkUnit.create(2d, WorkUnit.DAYS));
+ assertThat(requirement.offset()).isEqualTo(WorkUnit.create(0d, WorkUnit.DAYS));
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.batch.debt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
+import org.sonar.api.utils.WorkUnit;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RuleDebtCalculatorTest {
+
+ private static final int HOURS_IN_DAY = 8;
+ @Mock
+ TechnicalDebtModel model;
+
+ WorkUnit tenMinutes = WorkUnit.create(10d, WorkUnit.MINUTES);
+ WorkUnit fiveMinutes = WorkUnit.create(5d, WorkUnit.MINUTES);
+
+ RuleDebtCalculator calculator;
+
+ @Before
+ public void before() {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
+ calculator = new RuleDebtCalculator(model, new WorkDurationFactory(settings));
+ }
+
+ @Test
+ public void calculate_technical_debt() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
+
+ DefaultRequirement requirement = mock(DefaultRequirement.class);
+ Mockito.when(requirement.function()).thenReturn("constant_issue");
+ Mockito.when(requirement.factor()).thenReturn(tenMinutes);
+ Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
+ when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
+
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(
+ WorkDuration.createFromValueAndUnit(15, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ }
+
+ @Test
+ public void calculate_technical_debt_with_effort_to_fix() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
+
+ DefaultRequirement requirement = mock(DefaultRequirement.class);
+ Mockito.when(requirement.function()).thenReturn("linear_offset");
+ Mockito.when(requirement.factor()).thenReturn(tenMinutes);
+ Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
+ when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
+
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(
+ WorkDuration.createFromValueAndUnit((10 * 2) + 5, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ }
+
+ @Test
+ public void calculate_technical_debt_with_no_offset() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
+
+ DefaultRequirement requirement = mock(DefaultRequirement.class);
+ Mockito.when(requirement.function()).thenReturn("linear");
+ Mockito.when(requirement.factor()).thenReturn(tenMinutes);
+ Mockito.when(requirement.offset()).thenReturn(null);
+ when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
+
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(
+ WorkDuration.createFromValueAndUnit((10 * 2), WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ }
+
+ @Test
+ public void calculate_technical_debt_with_no_factor() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
+
+ DefaultRequirement requirement = mock(DefaultRequirement.class);
+ Mockito.when(requirement.function()).thenReturn("constant_issue");
+ Mockito.when(requirement.factor()).thenReturn(null);
+ Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
+ when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
+
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(
+ WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ }
+
+ @Test
+ public void no_technical_debt_if_requirement_not_found() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
+ when(model.requirementsByRule(ruleKey)).thenReturn(null);
+
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isNull();
+ }
+
+ @Test
+ public void fail_to_calculate_technical_debt_on_constant_issue_function_with_effort_to_fix() throws Exception {
+ RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
+ DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
+
+ DefaultRequirement requirement = mock(DefaultRequirement.class);
+ Mockito.when(requirement.function()).thenReturn("constant_issue");
+ Mockito.when(requirement.factor()).thenReturn(null);
+ Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
+ when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
+
+ try {
+ assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(
+ WorkDuration.createFromValueAndUnit(15, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Requirement for 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost.");
+ }
+ }
+
+}
+
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.batch.debt;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TechnicalDebtModelProviderTest {
+
+ @Mock
+ DebtModelLoader loader;
+
+ @Test
+ public void load_model() {
+ TechnicalDebtModel model = mock(TechnicalDebtModel.class);
+ when(loader.load()).thenReturn(model);
+
+ DebtModelProvider provider = new DebtModelProvider();
+ TechnicalDebtModel result = provider.provide(loader);
+ assertThat(result).isNotNull();
+ }
+
+ @Test
+ public void load_model_only_once() {
+ TechnicalDebtModel model = mock(TechnicalDebtModel.class);
+ when(loader.load()).thenReturn(model);
+
+ DebtModelProvider provider = new DebtModelProvider();
+ provider.provide(loader);
+ verify(loader).load();
+
+ provider.provide(loader);
+ verifyZeroInteractions(loader);
+ }
+}
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.*;
import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.WorkUnit;
-import org.sonar.batch.technicaldebt.TechnicalDebtCalculator;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.batch.debt.RuleDebtCalculator;
import java.util.Calendar;
import java.util.Date;
IssueFilters filters;
@Mock
- TechnicalDebtCalculator technicalDebtCalculator;
+ RuleDebtCalculator technicalDebtCalculator;
@Mock
RuleFinder ruleFinder;
.setRuleKey(SQUID_RULE_KEY)
.setSeverity(Severity.CRITICAL);
- when(technicalDebtCalculator.calculTechnicalDebt(issue)).thenReturn(new WorkUnit.Builder().setDays(10).build());
+ WorkDuration debt = WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.DAYS, 8);
+ when(technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).thenReturn(debt);
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().technicalDebt()).isEqualTo(new WorkUnit.Builder().setDays(10).build());
+ assertThat(argument.getValue().technicalDebt()).isEqualTo(debt);
}
}
+++ /dev/null
-/*
- * 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.
- */
-package org.sonar.batch.technicaldebt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.WorkUnit;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TechnicalDebtCalculatorTest {
-
- @Mock
- TechnicalDebtModel model;
-
- WorkUnit tenMinutes = new WorkUnit.Builder().setMinutes(10).build();
- WorkUnit fiveMinutes = new WorkUnit.Builder().setMinutes(5).build();
-
- TechnicalDebtCalculator remediationCostCalculator;
-
- @Before
- public void before() {
- Settings settings = new Settings();
- settings.setProperty(CoreProperties.HOURS_IN_DAY, 8);
-
- remediationCostCalculator = new TechnicalDebtCalculator(model, settings);
- }
-
- @Test
- public void calcul_technical_debt() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
-
- DefaultRequirement requirement = mock(DefaultRequirement.class);
- Mockito.when(requirement.function()).thenReturn("constant_issue");
- Mockito.when(requirement.factor()).thenReturn(tenMinutes);
- Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
- when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isEqualTo(new WorkUnit.Builder().setMinutes(15).build());
- }
-
- @Test
- public void calcul_technical_debt_with_effort_to_fix() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
- DefaultRequirement requirement = mock(DefaultRequirement.class);
- Mockito.when(requirement.function()).thenReturn("linear_offset");
- Mockito.when(requirement.factor()).thenReturn(tenMinutes);
- Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
- when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isEqualTo(new WorkUnit.Builder().setMinutes((10 * 2) + 5).build());
- }
-
- @Test
- public void calcul_technical_debt_with_no_offset() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
- DefaultRequirement requirement = mock(DefaultRequirement.class);
- Mockito.when(requirement.function()).thenReturn("linear");
- Mockito.when(requirement.factor()).thenReturn(tenMinutes);
- Mockito.when(requirement.offset()).thenReturn(null);
- when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isEqualTo(new WorkUnit.Builder().setMinutes(10 * 2).build());
- }
-
- @Test
- public void calcul_technical_debt_with_no_factor() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
-
- DefaultRequirement requirement = mock(DefaultRequirement.class);
- Mockito.when(requirement.function()).thenReturn("constant_issue");
- Mockito.when(requirement.factor()).thenReturn(null);
- Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
- when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isEqualTo(new WorkUnit.Builder().setMinutes(5).build());
- }
-
- @Test
- public void no_technical_debt_if_requirement_not_found() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
- when(model.requirementsByRule(ruleKey)).thenReturn(null);
-
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isNull();
- }
-
- @Test
- public void fail_to_calcul_technical_debt_on_constant_issue_function_with_effort_to_fix() throws Exception {
- RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
- DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
- DefaultRequirement requirement = mock(DefaultRequirement.class);
- Mockito.when(requirement.function()).thenReturn("constant_issue");
- Mockito.when(requirement.factor()).thenReturn(null);
- Mockito.when(requirement.offset()).thenReturn(fiveMinutes);
- when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
- try {
- assertThat(remediationCostCalculator.calculTechnicalDebt(issue)).isEqualTo(new WorkUnit.Builder().setMinutes(15).build());
- fail();
- } catch (Exception e) {
- assertThat(e).isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Requirement for 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost.");
- }
- }
-
-}
-
+++ /dev/null
-/*
- * 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.
- */
-
-package org.sonar.batch.technicaldebt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.WorkUnit;
-import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-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.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TechnicalDebtModelLoaderTest {
-
- @Mock
- CharacteristicDao dao;
-
- @Mock
- RuleFinder ruleFinder;
-
- TechnicalDebtModelLoader loader;
-
- @Before
- public void before() {
- loader = new TechnicalDebtModelLoader(dao, ruleFinder);
- }
-
- @Test
- public void find_all() throws Exception {
- CharacteristicDto rootCharacteristicDto = new CharacteristicDto()
- .setId(1)
- .setKey("MEMORY_EFFICIENCY")
- .setName("Memory use");
-
- CharacteristicDto characteristicDto = new CharacteristicDto()
- .setId(2)
- .setKey("EFFICIENCY")
- .setName("Efficiency")
- .setParentId(1);
-
- CharacteristicDto requirementDto = new CharacteristicDto()
- .setId(3)
- .setParentId(2)
- .setRuleId(100)
- .setFunction("linear")
- .setFactorValue(2d)
- .setFactorUnit(WorkUnit.DAYS)
- .setOffsetValue(0d)
- .setOffsetUnit(WorkUnit.DEFAULT_UNIT);
-
- RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp");
- Rule rule = Rule.create(ruleKey.repository(), ruleKey.rule());
- rule.setId(100);
- when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(rule));
- when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto));
-
- DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) loader.load();
- assertThat(result.rootCharacteristics()).hasSize(1);
-
- DefaultCharacteristic rootCharacteristic = result.characteristicByKey("MEMORY_EFFICIENCY");
- assertThat(rootCharacteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
- assertThat(rootCharacteristic.name()).isEqualTo("Memory use");
- assertThat(rootCharacteristic.parent()).isNull();
- assertThat(rootCharacteristic.requirements()).isEmpty();
- assertThat(rootCharacteristic.children()).hasSize(1);
- assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY");
-
- DefaultCharacteristic characteristic = result.characteristicByKey("EFFICIENCY");
- assertThat(characteristic.key()).isEqualTo("EFFICIENCY");
- assertThat(characteristic.name()).isEqualTo("Efficiency");
- assertThat(characteristic.parent().key()).isEqualTo("MEMORY_EFFICIENCY");
- assertThat(characteristic.children()).isEmpty();
- assertThat(characteristic.requirements()).hasSize(1);
- assertThat(characteristic.requirements().get(0).ruleKey()).isEqualTo(ruleKey);
-
- DefaultRequirement requirement = result.requirementsByRule(ruleKey);
- assertThat(requirement.ruleKey()).isEqualTo(ruleKey);
- assertThat(requirement.function()).isEqualTo("linear");
- assertThat(requirement.factor()).isEqualTo(new WorkUnit.Builder().setDays(2).build());
- assertThat(requirement.offset()).isEqualTo(new WorkUnit.Builder().setDays(0).build());
- }
-
-}
+++ /dev/null
-/*
- * 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.
- */
-
-package org.sonar.batch.technicaldebt;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TechnicalDebtModelProviderTest {
-
- @Mock
- TechnicalDebtModelLoader loader;
-
- @Test
- public void load_model() {
- TechnicalDebtModel model = mock(TechnicalDebtModel.class);
- when(loader.load()).thenReturn(model);
-
- TechnicalDebtModelProvider provider = new TechnicalDebtModelProvider();
- TechnicalDebtModel result = provider.provide(loader);
- assertThat(result).isNotNull();
- }
-
- @Test
- public void load_model_only_once() {
- TechnicalDebtModel model = mock(TechnicalDebtModel.class);
- when(loader.load()).thenReturn(model);
-
- TechnicalDebtModelProvider provider = new TechnicalDebtModelProvider();
- provider.provide(loader);
- verify(loader).load();
-
- provider.provide(loader);
- verifyZeroInteractions(loader);
- }
-}
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.user.User;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import javax.annotation.Nullable;
return setEffortToFix(issue, currentEffort, context);
}
- public boolean setTechnicalDebt(DefaultIssue issue, @Nullable WorkUnit value, IssueChangeContext context) {
- WorkUnit oldValue = issue.technicalDebt();
+ public boolean setTechnicalDebt(DefaultIssue issue, @Nullable WorkDuration value, IssueChangeContext context) {
+ WorkDuration oldValue = issue.technicalDebt();
if (!Objects.equal(value, oldValue)) {
issue.setTechnicalDebt(value);
issue.setFieldChange(context, TECHNICAL_DEBT, oldValue != null ? oldValue.toLong() : null, value != null ? value.toLong() : null);
return false;
}
- public boolean setPastTechnicalDebt(DefaultIssue issue, @Nullable WorkUnit previousTechnicalDebt, IssueChangeContext context) {
- WorkUnit currentTechnicalDebt = issue.technicalDebt();
+ public boolean setPastTechnicalDebt(DefaultIssue issue, @Nullable WorkDuration previousTechnicalDebt, IssueChangeContext context) {
+ WorkDuration currentTechnicalDebt = issue.technicalDebt();
issue.setTechnicalDebt(previousTechnicalDebt);
return setTechnicalDebt(issue, currentTechnicalDebt, context);
}
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.KeyValueFormat;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
}
public static IssueDto toDtoForInsert(DefaultIssue issue, Long componentId, Long rootComponentId, Integer ruleId, Date now) {
+ WorkDuration debt = issue.technicalDebt();
return new IssueDto()
.setKee(issue.key())
.setLine(issue.line())
.setMessage(issue.message())
.setEffortToFix(issue.effortToFix())
- .setTechnicalDebt(issue.technicalDebt() != null ? issue.technicalDebt().toLong() : null)
+ .setTechnicalDebt(debt != null ? debt.toLong() : null)
.setResolution(issue.resolution())
.setStatus(issue.status())
.setSeverity(issue.severity())
public static IssueDto toDtoForUpdate(DefaultIssue issue, Date now) {
// Invariant fields, like key and rule, can't be updated
+ WorkDuration debt = issue.technicalDebt();
return new IssueDto()
.setKee(issue.key())
.setLine(issue.line())
.setMessage(issue.message())
.setEffortToFix(issue.effortToFix())
- .setTechnicalDebt(issue.technicalDebt() != null ? issue.technicalDebt().toLong() : null)
+ .setTechnicalDebt(debt != null ? debt.toLong() : null)
.setResolution(issue.resolution())
.setStatus(issue.status())
.setSeverity(issue.severity())
.setUpdatedAt(now);
}
- public DefaultIssue toDefaultIssue() {
+ public DefaultIssue toDefaultIssue(@Nullable WorkDuration debt) {
DefaultIssue issue = new DefaultIssue();
issue.setKey(kee);
issue.setStatus(status);
issue.setResolution(resolution);
issue.setMessage(message);
issue.setEffortToFix(effortToFix);
- issue.setTechnicalDebt(technicalDebt != null ? WorkUnit.fromLong(technicalDebt) : null);
+ issue.setTechnicalDebt(debt);
issue.setLine(line);
issue.setSeverity(severity);
issue.setReporter(reporter);
import org.sonar.api.issue.internal.FieldDiffs;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.user.User;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.core.user.DefaultUser;
import java.util.Date;
@Test
public void set_past_technical_debt() throws Exception {
- issue.setTechnicalDebt(new WorkUnit.Builder().setDays(15).build());
- WorkUnit previousDebt = new WorkUnit.Builder().setDays(10).build();
+ WorkDuration newDebt = WorkDuration.createFromValueAndUnit(15, WorkDuration.UNIT.DAYS, 8);
+ WorkDuration previousDebt = WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.DAYS, 8);
+ issue.setTechnicalDebt(newDebt);
boolean updated = updater.setPastTechnicalDebt(issue, previousDebt, context);
assertThat(updated).isTrue();
- assertThat(issue.technicalDebt()).isEqualTo(new WorkUnit.Builder().setDays(15).build());
+ assertThat(issue.technicalDebt()).isEqualTo(newDebt);
assertThat(issue.mustSendNotifications()).isFalse();
FieldDiffs.Diff diff = issue.currentChange().get(TECHNICAL_DEBT);
- assertThat(diff.oldValue()).isEqualTo(new WorkUnit.Builder().setDays(10).build().toLong());
- assertThat(diff.newValue()).isEqualTo(new WorkUnit.Builder().setDays(15).build().toLong());
+ assertThat(diff.oldValue()).isEqualTo(previousDebt.toLong());
+ assertThat(diff.newValue()).isEqualTo(newDebt.toLong());
}
@Test
public void set_past_technical_debt_without_previous_value() throws Exception {
- issue.setTechnicalDebt(new WorkUnit.Builder().setDays(15).build());
+ WorkDuration newDebt = WorkDuration.createFromValueAndUnit(15, WorkDuration.UNIT.DAYS, 8);
+ issue.setTechnicalDebt(newDebt);
boolean updated = updater.setPastTechnicalDebt(issue, null, context);
assertThat(updated).isTrue();
- assertThat(issue.technicalDebt()).isEqualTo(new WorkUnit.Builder().setDays(15).build());
+ assertThat(issue.technicalDebt()).isEqualTo(newDebt);
assertThat(issue.mustSendNotifications()).isFalse();
FieldDiffs.Diff diff = issue.currentChange().get(TECHNICAL_DEBT);
assertThat(diff.oldValue()).isNull();
- assertThat(diff.newValue()).isEqualTo(new WorkUnit.Builder().setDays(15).build().toLong());
+ assertThat(diff.newValue()).isEqualTo(newDebt.toLong());
}
@Test
public void set_past_technical_debt_with_null_new_value() throws Exception {
issue.setTechnicalDebt(null);
- WorkUnit previousDebt = new WorkUnit.Builder().setDays(10).build();
+ WorkDuration previousDebt = WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.DAYS, 8);
boolean updated = updater.setPastTechnicalDebt(issue, previousDebt, context);
assertThat(updated).isTrue();
assertThat(issue.technicalDebt()).isNull();
assertThat(issue.mustSendNotifications()).isFalse();
FieldDiffs.Diff diff = issue.currentChange().get(TECHNICAL_DEBT);
- assertThat(diff.oldValue()).isEqualTo(new WorkUnit.Builder().setDays(10).build().toLong());
+ assertThat(diff.oldValue()).isEqualTo(previousDebt.toLong());
assertThat(diff.newValue()).isNull();
}
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import java.util.Calendar;
import java.util.Date;
.setIssueUpdateDate(updatedAt)
.setIssueCloseDate(closedAt);
- DefaultIssue issue = dto.toDefaultIssue();
+ DefaultIssue issue = dto.toDefaultIssue(WorkDuration.create(10, 10, 10, 8));
assertThat(issue.key()).isEqualTo("100");
assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
assertThat(issue.componentKey()).isEqualTo("org.sonar.sample:Sample");
assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FALSE_POSITIVE);
assertThat(issue.effortToFix()).isEqualTo(15.0);
- assertThat(issue.technicalDebt()).isEqualTo(new WorkUnit.Builder().setDays(10).setHours(10).setMinutes(10).build());
+ assertThat(issue.technicalDebt()).isNotNull();
assertThat(issue.line()).isEqualTo(6);
assertThat(issue.severity()).isEqualTo("BLOCKER");
assertThat(issue.message()).isEqualTo("message");
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.RuleQuery;
import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.core.persistence.AbstractDaoTestCase;
import org.sonar.core.persistence.MyBatis;
.setRuleKey(RuleKey.of("squid", "AvoidCycle"))
.setLine(5000)
- .setTechnicalDebt(new WorkUnit.Builder().setMinutes(10).build())
+ .setTechnicalDebt(WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.MINUTES, 8))
.setReporter("emmerik")
.setResolution("OPEN")
.setStatus("OPEN")
// updated fields
.setLine(5000)
- .setTechnicalDebt(new WorkUnit.Builder().setMinutes(10).build())
+ .setTechnicalDebt(WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.MINUTES, 8))
.setChecksum("FFFFF")
.setAuthorLogin("simon")
.setAssignee("loic")
assertThat(result.rootId()).isEqualTo(1);
assertThat(result.ruleKey()).isEqualTo(RuleKey.of("repo", "key"));
assertThat(result.function()).isEqualTo("linear");
- assertThat(result.factor()).isEqualTo(new WorkUnit.Builder().setMinutes(30).build());
- assertThat(result.offset()).isEqualTo(new WorkUnit.Builder().setDays(0).build());
+ assertThat(result.factor()).isEqualTo(WorkUnit.create(30d, WorkUnit.MINUTES));
+ assertThat(result.offset()).isEqualTo(WorkUnit.create(0d, WorkUnit.DAYS));
}
@Test
assertThat(result.rootId()).isEqualTo(1);
assertThat(result.ruleKey()).isEqualTo(RuleKey.of("repo", "key"));
assertThat(result.function()).isEqualTo("linear");
- assertThat(result.factor()).isEqualTo(new WorkUnit.Builder().setMinutes(30).build());
- assertThat(result.offset()).isEqualTo(new WorkUnit.Builder().setDays(0).build());
+ assertThat(result.factor()).isEqualTo(WorkUnit.create(30d, WorkUnit.MINUTES));
+ assertThat(result.offset()).isEqualTo(WorkUnit.create(0d, WorkUnit.DAYS));
}
@Test
.setCharacteristic(characteristic)
.setRuleKey(ruleKey)
.setFunction("linear")
- .setFactor(new WorkUnit.Builder().setHours(2).build())
- .setOffset(new WorkUnit.Builder().setHours(0).build());
+ .setFactor(WorkUnit.create(2d, WorkUnit.HOURS))
+ .setOffset(WorkUnit.create(0d, WorkUnit.HOURS));
sqaleModel.addRootCharacteristic(rootCharacteristic);
RuleKey ruleKey = RuleKey.of("checkstyle", "import");
when(ruleCache.getByRuleKey(ruleKey)).thenReturn(rule);
new DefaultRequirement().setRuleKey(ruleKey)
- .setFunction("linear").setFactor(new WorkUnit.Builder().setMinutes(30).build()).setCharacteristic(javaCharacteristic).setRootCharacteristic(javaRootCharacteristic);
+ .setFunction("linear").setFactor(WorkUnit.create(30d, WorkUnit.MINUTES)).setCharacteristic(javaCharacteristic).setRootCharacteristic(javaRootCharacteristic);
Reader javaModelReader = mock(Reader.class);
when(xmlImporter.importXML(eq(javaModelReader), any(ValidationMessages.class), eq(ruleCache))).thenReturn(javaModel);
// New requirement
new DefaultRequirement().setRuleKey(ruleKey2)
- .setFunction("linear").setFactor(new WorkUnit.Builder().setHours(1).build()).setCharacteristic(javaCharacteristic).setRootCharacteristic(javaRootCharacteristic);
+ .setFunction("linear").setFactor(WorkUnit.create(1d, WorkUnit.HOURS)).setCharacteristic(javaCharacteristic).setRootCharacteristic(javaRootCharacteristic);
Reader javaModelReader = mock(Reader.class);
when(technicalDebtModelRepository.createReaderForXMLFile("java")).thenReturn(javaModelReader);
}
private void checkXmlCorrectlyImported(DefaultTechnicalDebtModel sqale, ValidationMessages messages) {
- checkXmlCorrectlyImported(sqale, new WorkUnit.Builder().setDays(0).build(), messages);
+ checkXmlCorrectlyImported(sqale, WorkUnit.create(0d, WorkUnit.DAYS), messages);
}
private void checkXmlCorrectlyImported(DefaultTechnicalDebtModel sqale, WorkUnit offset, ValidationMessages messages) {
import org.sonar.api.issue.IssueComment;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
private String message;
private Integer line;
private Double effortToFix;
- private WorkUnit technicalDebt;
+ private WorkDuration technicalDebt;
private String status;
private String resolution;
private String reporter;
* Elapsed time to fix the issue
*/
@CheckForNull
- public WorkUnit technicalDebt() {
+ public WorkDuration technicalDebt() {
return technicalDebt;
}
- public DefaultIssue setTechnicalDebt(@Nullable WorkUnit t) {
+ public DefaultIssue setTechnicalDebt(@Nullable WorkDuration t) {
this.technicalDebt = t;
return this;
}
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.technicaldebt.batch.Requirement;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.api.utils.WorkUnit;
import java.util.Date;
private DefaultCharacteristic rootCharacteristic;
private String function;
+ private int factorValue;
+ private WorkDuration.UNIT factorUnit;
+ private int offsetValue;
+ private WorkDuration.UNIT offsetUnit;
private WorkUnit factor;
private WorkUnit offset;
private Date updatedAt;
public DefaultRequirement() {
- this.factor = new WorkUnit.Builder().setDays(0).build();
- this.offset = new WorkUnit.Builder().setDays(0).build();
+ this.factor = WorkUnit.create(0d, WorkUnit.DAYS);
+ this.offset = WorkUnit.create(0d, WorkUnit.DAYS);
}
public Integer id() {
return this;
}
+ /**
+ * @deprecated since 4.2
+ */
+ @Deprecated
public WorkUnit factor() {
return factor;
+// return WorkUnit.create((double) factorValue, fromUnit(factorUnit));
}
+ /**
+ * @deprecated since 4.2
+ */
+ @Deprecated
public DefaultRequirement setFactor(WorkUnit factor) {
this.factor = factor;
return this;
}
+ /**
+ * @deprecated since 4.2
+ */
+ @Deprecated
public WorkUnit offset() {
return offset;
}
+ /**
+ * @deprecated since 4.2
+ */
+ @Deprecated
public DefaultRequirement setOffset(WorkUnit offset) {
this.offset = offset;
return this;
}
+ public int factorValue() {
+ return factorValue;
+ }
+
+ public DefaultRequirement setFactorValue(int factorValue) {
+ this.factorValue = factorValue;
+ return this;
+ }
+
+ public WorkDuration.UNIT factorUnit() {
+ return factorUnit;
+ }
+
+ public DefaultRequirement setFactorUnit(WorkDuration.UNIT factorUnit) {
+ this.factorUnit = factorUnit;
+ return this;
+ }
+
+ public int offsetValue() {
+ return offsetValue;
+ }
+
+ public DefaultRequirement setOffsetValue(int offsetValue) {
+ this.offsetValue = offsetValue;
+ return this;
+ }
+
+ public WorkDuration.UNIT offsetUnit() {
+ return offsetUnit;
+ }
+
+ public DefaultRequirement setOffsetUnit(WorkDuration.UNIT offsetUnit) {
+ this.offsetUnit = offsetUnit;
+ return this;
+ }
+
public Date createdAt() {
return createdAt;
}
return this;
}
+ private static WorkDuration.UNIT toUnit(String requirementUnit){
+ if (requirementUnit.equals(WorkUnit.DAYS)) {
+ return WorkDuration.UNIT.DAYS;
+ } else if (requirementUnit.equals(WorkUnit.HOURS)) {
+ return WorkDuration.UNIT.HOURS;
+ } else if (requirementUnit.equals(WorkUnit.MINUTES)) {
+ return WorkDuration.UNIT.MINUTES;
+ }
+ throw new IllegalStateException("Invalid unit : " + requirementUnit);
+ }
+
+ private static String fromUnit(WorkDuration.UNIT unit){
+ if (unit.equals(WorkDuration.UNIT.DAYS)) {
+ return WorkUnit.DAYS;
+ } else if (unit.equals(WorkDuration.UNIT.HOURS)) {
+ return WorkUnit.HOURS;
+ } else if (unit.equals(WorkDuration.UNIT.MINUTES)) {
+ return WorkUnit.MINUTES;
+ }
+ throw new IllegalStateException("Invalid unit : " + unit);
+ }
@Override
public String toString() {
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.api.utils;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+
+/**
+ * @since 4.2
+ */
+public class WorkDuration implements Serializable {
+
+ static final int DAY_POSITION_IN_LONG = 10000;
+ static final int HOUR_POSITION_IN_LONG = 100;
+ static final int MINUTE_POSITION_IN_LONG = 1;
+
+ public static enum UNIT {DAYS, HOURS, MINUTES}
+
+ private int hoursInDay;
+
+ private long durationInSeconds;
+ private int days;
+ private int hours;
+ private int minutes;
+
+ private WorkDuration(long durationInSeconds, int days, int hours, int minutes, int hoursInDay) {
+ this.durationInSeconds = durationInSeconds;
+ this.days = days;
+ this.hours = hours;
+ this.minutes = minutes;
+ this.hoursInDay = hoursInDay;
+ }
+
+ public static WorkDuration create(int days, int hours, int minutes, int hoursInDay) {
+ long durationInSeconds = days * hoursInDay * 60 * 60;
+ durationInSeconds += hours * 60 * 60;
+ durationInSeconds += minutes * 60;
+ return new WorkDuration(durationInSeconds, days, hours, minutes, hoursInDay);
+ }
+
+ public static WorkDuration createFromValueAndUnit(int value, UNIT unit, int hoursInDay) {
+ switch (unit) {
+ case DAYS:
+ return create(value, 0, 0, hoursInDay);
+ case HOURS:
+ return create(0, value, 0, hoursInDay);
+ case MINUTES:
+ return create(0, 0, value, hoursInDay);
+ default:
+ throw new IllegalStateException("Cannot create work duration");
+ }
+ }
+
+ static WorkDuration createFromLong(long duration, int hoursInDay) {
+ int days = 0, hours = 0, minutes = 0;
+
+ long time = duration;
+ Long currentTime = time / WorkDuration.DAY_POSITION_IN_LONG;
+ if (currentTime > 0) {
+ days = (currentTime.intValue());
+ time = time - (currentTime * WorkDuration.DAY_POSITION_IN_LONG);
+ }
+
+ currentTime = time / WorkDuration.HOUR_POSITION_IN_LONG;
+ if (currentTime > 0) {
+ hours = currentTime.intValue();
+ time = time - (currentTime * WorkDuration.HOUR_POSITION_IN_LONG);
+ }
+
+ currentTime = time / WorkDuration.MINUTE_POSITION_IN_LONG;
+ if (currentTime > 0) {
+ minutes = currentTime.intValue();
+ }
+ return WorkDuration.create(days, hours, minutes, hoursInDay);
+ }
+
+ static WorkDuration createFromSeconds(long seconds, int hoursInDay) {
+ int days = (int) (seconds / hoursInDay / 60d / 60d);
+ long currentDurationInSeconds = seconds - (days * hoursInDay * 60 * 60);
+ int hours = (int) (currentDurationInSeconds / 60d / 60d);
+ currentDurationInSeconds = currentDurationInSeconds - (hours * 60 * 60);
+ int minutes = (int) (currentDurationInSeconds / 60d);
+ return new WorkDuration(seconds, days, hours, minutes, hoursInDay);
+ }
+
+ /**
+ * Return the duration in number of working days.
+ * For instance, 3 days and 4 hours will return 3.5 days (if hoursIndDay is 8).
+ */
+ public double toWorkingDays() {
+ return durationInSeconds / 60d / 60d / hoursInDay;
+ }
+
+ /**
+ * Return the duration using the following format DDHHMM, where DD is the number of days, HH is the number of months, and MM the number of minutes.
+ * For instance, 3 days and 4 hours will return 030400 (if hoursIndDay is 8).
+ */
+ public long toLong() {
+ int workingDays = days;
+ int workingHours = hours;
+ if (hours >= hoursInDay) {
+ int nbAdditionalDays = hours / hoursInDay;
+ workingDays += nbAdditionalDays;
+ workingHours = hours - (nbAdditionalDays * hoursInDay);
+ }
+ return workingDays * DAY_POSITION_IN_LONG + workingHours * HOUR_POSITION_IN_LONG + minutes * MINUTE_POSITION_IN_LONG;
+ }
+
+ public long toSeconds() {
+ return durationInSeconds;
+ }
+
+ public WorkDuration add(@Nullable WorkDuration with) {
+ if (with != null) {
+ return WorkDuration.createFromSeconds(this.toSeconds() + with.toSeconds(), this.hoursInDay);
+ } else {
+ return this;
+ }
+ }
+
+ public WorkDuration subtract(@Nullable WorkDuration with) {
+ if (with != null) {
+ return WorkDuration.createFromSeconds(this.toSeconds() - with.toSeconds(), this.hoursInDay);
+ } else {
+ return this;
+ }
+ }
+
+ public WorkDuration multiply(int factor) {
+ return WorkDuration.createFromSeconds(this.toSeconds() * factor, this.hoursInDay);
+ }
+
+ public int days() {
+ return days;
+ }
+
+ public int hours() {
+ return hours;
+ }
+
+ public int minutes() {
+ return minutes;
+ }
+
+ public int hoursInDay() {
+ return hoursInDay;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ WorkDuration that = (WorkDuration) o;
+ if (durationInSeconds != that.durationInSeconds) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (durationInSeconds ^ (durationInSeconds >>> 32));
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.api.utils;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.config.Settings;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * @since 4.2
+ */
+public final class WorkDurationFactory implements BatchComponent, ServerComponent {
+
+ private final int hoursInDay;
+
+ public WorkDurationFactory(Settings settings) {
+ this.hoursInDay = settings.getInt(CoreProperties.HOURS_IN_DAY);
+ }
+
+ public WorkDuration createFromWorkingValue(int value, WorkDuration.UNIT unit) {
+ return WorkDuration.createFromValueAndUnit(value, unit, hoursInDay);
+ }
+
+ @CheckForNull
+ public WorkDuration createFromWorkingLong(@Nullable Long duration) {
+ if (duration == null) {
+ return null;
+ }
+ return WorkDuration.createFromLong(duration, hoursInDay);
+ }
+
+}
import java.io.Serializable;
/**
- * @since 4.0
+ * @deprecated since 4.2. Use WorkDuration instead
*/
+@Deprecated
public final class WorkUnit implements Serializable {
public static final String DAYS = "d";
public static final String MINUTES = "mn";
public static final String HOURS = "h";
public static final String DEFAULT_UNIT = DAYS;
+ private static final String[] UNITS = {DAYS, MINUTES, HOURS};
public static final double DEFAULT_VALUE = 0.0;
- private static final String[] UNITS = {DAYS, MINUTES, HOURS};
-
- private static final int DAY = 10000;
- private static final int HOUR = 100;
- private static final int MINUTE = 1;
+ private double value = 0d;
+ private String unit = DEFAULT_UNIT;
- private int days;
- private int hours;
- private int minutes;
+ WorkUnit(double value, String unit) {
+ this.value = value;
+ this.unit = unit;
+ }
- private WorkUnit(int days, int hours, int minutes) {
- this.minutes = minutes;
- this.hours = hours;
- this.days = days;
+ public double getValue() {
+ return value;
}
- /**
- * @deprecated since 4.2.
- */
- @Deprecated
- public static WorkUnit create() {
- return create(0d, DEFAULT_UNIT);
+ public String getUnit() {
+ return unit;
}
public static WorkUnit create(@Nullable Double value, @Nullable String unit) {
if (!ArrayUtils.contains(UNITS, defaultIfEmptyUnit)) {
throw new IllegalArgumentException("Unit can not be: " + defaultIfEmptyUnit + ". Possible values are " + ArrayUtils.toString(UNITS));
}
- Double d = value != null ? value : DEFAULT_VALUE;
+ double d = value != null ? value : DEFAULT_VALUE;
if (d < 0.0) {
throw new IllegalArgumentException("Value can not be negative: " + d);
}
-
- int days = 0;
- int hours = 0;
- int minutes = 0;
- if (DAYS.equals(unit)) {
- days = d.intValue();
- } else if (HOURS.equals(unit)) {
- hours = d.intValue();
- } else if (MINUTES.equals(unit)) {
- minutes = d.intValue();
- }
- return new WorkUnit(days, hours, minutes);
- }
-
- public double getValue() {
- if (days > 0) {
- return days + (hours / 24) + (minutes / 60 / 24);
- } else if (hours > 0) {
- return hours + (minutes / 60);
- } else {
- return minutes;
- }
- }
-
- public String getUnit() {
- if (days > 0) {
- return DAYS;
- } else if (hours > 0) {
- return HOURS;
- } else {
- return MINUTES;
- }
- }
-
- /**
- * @since 4.2
- */
- public int days() {
- return days;
- }
-
- /**
- * @since 4.2
- */
- public int hours() {
- return hours;
- }
-
- /**
- * @since 4.2
- */
- public int minutes() {
- return minutes;
- }
-
- /**
- *
- * @since 4.2
- */
- public static WorkUnit fromLong(long durationInLong) {
- Builder builder = new Builder();
-
- long time = durationInLong;
- Long currentTime = time / DAY;
- if (currentTime > 0) {
- builder.setDays(currentTime.intValue());
- time = time - (currentTime * DAY);
- }
-
- currentTime = time / HOUR;
- if (currentTime > 0) {
- builder.setHours(currentTime.intValue());
- time = time - (currentTime * HOUR);
- }
-
- currentTime = time / MINUTE;
- if (currentTime > 0) {
- builder.setMinutes(currentTime.intValue());
- }
-
- return builder.build();
+ return new WorkUnit(d, defaultIfEmptyUnit);
}
- /**
- * Return the duration using the following format DDHHMM, where DD is the number of days, HH is the number of months, and MM the number of minutes.
- * For instance, 5 days and 2 hours will return 050200.
- *
- * @since 4.2
- */
- public long toLong() {
- return days * DAY + hours * HOUR + minutes * MINUTE;
- }
-
- /**
- * Return the duration in number of days.
- * For instance, 5 days and 4 hours will return 5.5 hours (if hoursIndDay is 8).
- *
- * @since 4.2
- */
- public double toDays(int hoursInDay) {
- double resultDays = days;
- resultDays += (double) hours / hoursInDay;
- resultDays += (double) minutes / (hoursInDay * 60.0);
- return resultDays;
+ public static WorkUnit create() {
+ return create(0d, DEFAULT_UNIT);
}
@Override
if (o == null || getClass() != o.getClass()) {
return false;
}
- WorkUnit workDayDuration = (WorkUnit) o;
- if (days != workDayDuration.days) {
- return false;
- }
- if (hours != workDayDuration.hours) {
+
+ WorkUnit workUnit = (WorkUnit) o;
+
+ if (Double.compare(workUnit.value, value) != 0) {
return false;
}
- if (minutes != workDayDuration.minutes) {
+ if (!unit.equals(workUnit.unit)) {
return false;
}
+
return true;
}
@Override
public int hashCode() {
- int result = Integer.valueOf(days).hashCode();
- result = 29 * result + Integer.valueOf(hours).hashCode();
- result = 27 * result + Integer.valueOf(minutes).hashCode();
+ int result;
+ long temp;
+ temp = Double.doubleToLongBits(value);
+ result = (int) (temp ^ (temp >>> 32));
+ result = 31 * result + unit.hashCode();
return result;
}
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
-
- /**
- * @since 4.2
- */
- public static class Builder {
- private int days;
- private int hours;
- private int minutes;
-
- public Builder setDays(int days) {
- this.days = days;
- return this;
- }
-
- public Builder setHours(int hours) {
- this.hours = hours;
- return this;
- }
-
- public Builder setMinutes(int minutes) {
- this.minutes = minutes;
- return this;
- }
-
- public WorkUnit build() {
- return new WorkUnit(days, hours, minutes);
- }
- }
}
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueComment;
import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import java.text.SimpleDateFormat;
import java.util.List;
.setMessage("a message")
.setLine(7)
.setEffortToFix(1.2d)
- .setTechnicalDebt(new WorkUnit.Builder().setDays(1).build())
+ .setTechnicalDebt(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8))
.setActionPlanKey("BCDE")
.setStatus(Issue.STATUS_CLOSED)
.setResolution(Issue.RESOLUTION_FIXED)
assertThat(issue.message()).isEqualTo("a message");
assertThat(issue.line()).isEqualTo(7);
assertThat(issue.effortToFix()).isEqualTo(1.2d);
- assertThat(issue.technicalDebt()).isEqualTo(new WorkUnit.Builder().setDays(1).build());
+ assertThat(issue.technicalDebt()).isEqualTo(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8));
assertThat(issue.actionPlanKey()).isEqualTo("BCDE");
assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED);
.setCharacteristic(characteristic)
.setRootCharacteristic(root)
.setFunction("linear_offset")
- .setFactor(new WorkUnit.Builder().setMinutes(2).build())
- .setOffset(new WorkUnit.Builder().setHours(1).build())
+ .setFactor(WorkUnit.create(2d, WorkUnit.MINUTES))
+ .setOffset(WorkUnit.create(1d, WorkUnit.HOURS))
.setCreatedAt(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"))
.setUpdatedAt(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"));
assertThat(requirement.characteristic()).isEqualTo(characteristic);
assertThat(requirement.rootCharacteristic()).isEqualTo(root);
assertThat(requirement.function()).isEqualTo("linear_offset");
- assertThat(requirement.factor()).isEqualTo(new WorkUnit.Builder().setMinutes(2).build());
- assertThat(requirement.offset()).isEqualTo(new WorkUnit.Builder().setHours(1).build());
+ assertThat(requirement.factor()).isEqualTo(WorkUnit.create(2d, WorkUnit.MINUTES));
+ assertThat(requirement.offset()).isEqualTo(WorkUnit.create(1d, WorkUnit.HOURS));
assertThat(requirement.createdAt()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"));
assertThat(requirement.updatedAt()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"));
}
.setId(1)
.setRuleKey(RuleKey.of("repo", "rule"))
.setFunction("linear_offset")
- .setFactor(new WorkUnit.Builder().setMinutes(2).build())
- .setOffset(new WorkUnit.Builder().setHours(1).build())
+ .setFactor(WorkUnit.create(2d, WorkUnit.MINUTES))
+ .setOffset(WorkUnit.create(1d, WorkUnit.HOURS))
.setRootId(3)
.setParentId(2);
assertThat(requirement.order()).isNull();
assertThat(requirement.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
assertThat(requirement.function()).isEqualTo("linear_offset");
- assertThat(requirement.factor()).isEqualTo(new WorkUnit.Builder().setMinutes(2).build());
- assertThat(requirement.offset()).isEqualTo(new WorkUnit.Builder().setHours(1).build());
+ assertThat(requirement.factor()).isEqualTo(WorkUnit.create(2d, WorkUnit.MINUTES));
+ assertThat(requirement.offset()).isEqualTo(WorkUnit.create(1d, WorkUnit.HOURS));
assertThat(requirement.parentId()).isEqualTo(2);
assertThat(requirement.rootId()).isEqualTo(3);
}
.setId(1)
.setRuleKey(RuleKey.of("repo", "rule"))
.setFunction("linear_offset")
- .setFactor(new WorkUnit.Builder().setMinutes(2).build())
- .setOffset(new WorkUnit.Builder().setHours(1).build())
+ .setFactor(WorkUnit.create(2d, WorkUnit.MINUTES))
+ .setOffset(WorkUnit.create(1d, WorkUnit.HOURS))
.setRootId(3)
.setParentId(2);
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.api.utils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class WorkDurationFactoryTest {
+
+ WorkDurationFactory factory;
+
+ @Before
+ public void setUp() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, 8);
+ factory = new WorkDurationFactory(settings);
+ }
+
+ @Test
+ public void create_from_working_value() throws Exception {
+ // 1 working day -> 8 hours
+ assertThat(factory.createFromWorkingValue(1, WorkDuration.UNIT.DAYS).toSeconds()).isEqualTo(8*60*60);
+ // 8 hours
+ assertThat(factory.createFromWorkingValue(8, WorkDuration.UNIT.HOURS).toSeconds()).isEqualTo(8*60*60);
+ }
+
+ @Test
+ public void create_from_working_long() throws Exception {
+ WorkDuration workDuration = factory.createFromWorkingLong(1l);
+ assertThat(workDuration.days()).isEqualTo(0);
+ assertThat(workDuration.hours()).isEqualTo(0);
+ assertThat(workDuration.minutes()).isEqualTo(1);
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+package org.sonar.api.utils;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class WorkDurationTest {
+
+ private static final int HOURS_IN_DAY = 8;
+
+ @Test
+ public void create_from_days_hours_minutes() throws Exception {
+ WorkDuration workDuration = WorkDuration.create(1, 1, 1, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(1);
+ assertThat(workDuration.hours()).isEqualTo(1);
+ assertThat(workDuration.minutes()).isEqualTo(1);
+ assertThat(workDuration.toSeconds()).isEqualTo(1 * HOURS_IN_DAY * 60 * 60 + 1 * 60 * 60 + 60);
+ assertThat(workDuration.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+ }
+
+ @Test
+ public void create_from_value_and_unit() throws Exception {
+ WorkDuration result = WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, HOURS_IN_DAY);
+ assertThat(result.days()).isEqualTo(1);
+ assertThat(result.hours()).isEqualTo(0);
+ assertThat(result.minutes()).isEqualTo(0);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+ assertThat(result.toSeconds()).isEqualTo(1 * HOURS_IN_DAY * 60 * 60);
+
+ assertThat(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, HOURS_IN_DAY).toSeconds()).isEqualTo(1 * HOURS_IN_DAY * 60 * 60);
+ assertThat(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toSeconds()).isEqualTo(1 * 60 * 60);
+ assertThat(WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).toSeconds()).isEqualTo(60);
+ }
+
+ @Test
+ public void create_from_seconds() throws Exception {
+ WorkDuration workDuration = WorkDuration.createFromSeconds(60, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(0);
+ assertThat(workDuration.hours()).isEqualTo(0);
+ assertThat(workDuration.minutes()).isEqualTo(1);
+
+ workDuration = WorkDuration.createFromSeconds(60 * 60, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(0);
+ assertThat(workDuration.hours()).isEqualTo(1);
+ assertThat(workDuration.minutes()).isEqualTo(0);
+
+ workDuration = WorkDuration.createFromSeconds(HOURS_IN_DAY * 60 * 60, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(1);
+ assertThat(workDuration.hours()).isEqualTo(0);
+ assertThat(workDuration.minutes()).isEqualTo(0);
+ }
+
+ @Test
+ public void create_from_working_long() throws Exception {
+ // 1 minute
+ WorkDuration workDuration = WorkDuration.createFromLong(1l, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(0);
+ assertThat(workDuration.hours()).isEqualTo(0);
+ assertThat(workDuration.minutes()).isEqualTo(1);
+
+ // 1 hour
+ workDuration = WorkDuration.createFromLong(100l, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(0);
+ assertThat(workDuration.hours()).isEqualTo(1);
+ assertThat(workDuration.minutes()).isEqualTo(0);
+
+ // 1 day
+ workDuration = WorkDuration.createFromLong(10000l, HOURS_IN_DAY);
+ assertThat(workDuration.days()).isEqualTo(1);
+ assertThat(workDuration.hours()).isEqualTo(0);
+ assertThat(workDuration.minutes()).isEqualTo(0);
+ }
+
+ @Test
+ public void convert_to_seconds() throws Exception {
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).toSeconds()).isEqualTo(2 * 60);
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toSeconds()).isEqualTo(2 * 60 * 60);
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.DAYS, HOURS_IN_DAY).toSeconds()).isEqualTo(2 * HOURS_IN_DAY * 60 * 60);
+ }
+
+ @Test
+ public void convert_to_working_days() throws Exception {
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).toWorkingDays()).isEqualTo(2d / 60d / 8d);
+ assertThat(WorkDuration.createFromValueAndUnit(240, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).toWorkingDays()).isEqualTo(0.5);
+ assertThat(WorkDuration.createFromValueAndUnit(4, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toWorkingDays()).isEqualTo(0.5);
+ assertThat(WorkDuration.createFromValueAndUnit(8, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toWorkingDays()).isEqualTo(1d);
+ assertThat(WorkDuration.createFromValueAndUnit(16, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toWorkingDays()).isEqualTo(2d);
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.DAYS, HOURS_IN_DAY).toWorkingDays()).isEqualTo(2d);
+ }
+
+ @Test
+ public void convert_to_working_long() throws Exception {
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).toLong()).isEqualTo(2l);
+ assertThat(WorkDuration.createFromValueAndUnit(4, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toLong()).isEqualTo(400l);
+ assertThat(WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toLong()).isEqualTo(10200l);
+ assertThat(WorkDuration.createFromValueAndUnit(8, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).toLong()).isEqualTo(10000l);
+ assertThat(WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.DAYS, HOURS_IN_DAY).toLong()).isEqualTo(20000l);
+ }
+
+ @Test
+ public void add() throws Exception {
+ // 4h + 5h = 1d 1h
+ WorkDuration result = WorkDuration.createFromValueAndUnit(4, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).add(WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.HOURS, HOURS_IN_DAY));
+ assertThat(result.days()).isEqualTo(1);
+ assertThat(result.hours()).isEqualTo(1);
+ assertThat(result.minutes()).isEqualTo(0);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+
+ // 40 m + 30m = 1h 10m
+ result = WorkDuration.createFromValueAndUnit(40, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).add(WorkDuration.createFromValueAndUnit(30, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ assertThat(result.days()).isEqualTo(0);
+ assertThat(result.hours()).isEqualTo(1);
+ assertThat(result.minutes()).isEqualTo(10);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+
+ // 10 m + 20m = 30m
+ assertThat(WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).add(
+ WorkDuration.createFromValueAndUnit(20, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY)
+ ).minutes()).isEqualTo(30);
+ }
+
+ @Test
+ public void subtract() throws Exception {
+ // 1d 1h - 5h = 4h
+ WorkDuration result = WorkDuration.create(1, 1, 0, HOURS_IN_DAY).subtract(WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.HOURS, HOURS_IN_DAY));
+ assertThat(result.days()).isEqualTo(0);
+ assertThat(result.hours()).isEqualTo(4);
+ assertThat(result.minutes()).isEqualTo(0);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+
+ // 1h 10m - 30m = 40m
+ result = WorkDuration.create(0, 1, 10, HOURS_IN_DAY).subtract(WorkDuration.createFromValueAndUnit(30, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
+ assertThat(result.days()).isEqualTo(0);
+ assertThat(result.hours()).isEqualTo(0);
+ assertThat(result.minutes()).isEqualTo(40);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+
+ // 30m - 20m = 10m
+ assertThat(WorkDuration.createFromValueAndUnit(30, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY).subtract(WorkDuration.createFromValueAndUnit(20, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY))
+ .minutes()).isEqualTo(10);
+ }
+
+ @Test
+ public void multiply() throws Exception {
+ // 5h * 2 = 1d 2h
+ WorkDuration result = WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.HOURS, HOURS_IN_DAY).multiply(2);
+ assertThat(result.days()).isEqualTo(1);
+ assertThat(result.hours()).isEqualTo(2);
+ assertThat(result.minutes()).isEqualTo(0);
+ assertThat(result.hoursInDay()).isEqualTo(HOURS_IN_DAY);
+ }
+}
@Test
public void create_default() throws Exception {
WorkUnit workUnit = WorkUnit.create();
+ assertThat(workUnit.getUnit()).isEqualTo("d");
assertThat(workUnit.getValue()).isEqualTo(0.0);
}
+ @Test
+ public void test_equals() throws Exception {
+ assertThat(WorkUnit.create(2.0, "mn")).isEqualTo(WorkUnit.create(2.0, "mn"));
+ assertThat(WorkUnit.create(3.0, "mn")).isNotEqualTo(WorkUnit.create(2.0, "mn"));
+ assertThat(WorkUnit.create(2.0, "h")).isNotEqualTo(WorkUnit.create(2.0, "mn"));
+ }
+
@Test
public void fail_with_bad_unit() throws Exception {
try {
}
}
- @Test
- public void from_long_on_simple_values() {
- checkTimes(WorkUnit.fromLong(1L), 0, 0, 1);
- checkTimes(WorkUnit.fromLong(100L), 0, 1, 0);
- checkTimes(WorkUnit.fromLong(10000L), 1, 0, 0);
- }
-
- @Test
- public void from_long_on_complex_values() {
- checkTimes(WorkUnit.fromLong(10101L), 1, 1, 1);
- checkTimes(WorkUnit.fromLong(101L), 0, 1, 1);
- checkTimes(WorkUnit.fromLong(10001L), 1, 0, 1);
- checkTimes(WorkUnit.fromLong(10100L), 1, 1, 0);
-
- checkTimes(WorkUnit.fromLong(112233L), 11, 22, 33);
- }
-
- @Test
- public void to_long() {
- assertThat(new WorkUnit.Builder().setDays(1).setHours(1).setMinutes(1).build().toLong()).isEqualTo(10101L);
- }
-
- @Test
- public void test_equals_and_hashCode() throws Exception {
- WorkUnit oneMinute = WorkUnit.fromLong(1L);
- WorkUnit oneHours = WorkUnit.fromLong(100L);
- WorkUnit oneDay = WorkUnit.fromLong(10000L);
-
- assertThat(oneMinute).isEqualTo(oneMinute);
- assertThat(oneMinute).isEqualTo(WorkUnit.fromLong(1L));
- assertThat(oneHours).isEqualTo(WorkUnit.fromLong(100L));
- assertThat(oneDay).isEqualTo(WorkUnit.fromLong(10000L));
-
- assertThat(oneMinute).isNotEqualTo(oneHours);
- assertThat(oneHours).isNotEqualTo(oneDay);
-
- assertThat(oneMinute.hashCode()).isEqualTo(oneMinute.hashCode());
- }
-
- private void checkTimes(WorkUnit technicalDebt, int expectedDays, int expectedHours, int expectedMinutes) {
- assertThat(technicalDebt.days()).isEqualTo(expectedDays);
- assertThat(technicalDebt.hours()).isEqualTo(expectedHours);
- assertThat(technicalDebt.minutes()).isEqualTo(expectedMinutes);
- }
-
}
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.ActionPlanDeadlineComparator;
import org.sonar.core.issue.ActionPlanStats;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.db.ActionPlanDao;
-import org.sonar.core.issue.db.ActionPlanDto;
-import org.sonar.core.issue.db.ActionPlanStatsDao;
-import org.sonar.core.issue.db.ActionPlanStatsDto;
-import org.sonar.core.issue.db.IssueDao;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.issue.db.IssueStorage;
+import org.sonar.core.issue.db.*;
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.ResourceDto;
import org.sonar.core.resource.ResourceQuery;
import javax.annotation.CheckForNull;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
import static com.google.common.collect.Lists.newArrayList;
private final IssueDao issueDao;
private final IssueUpdater issueUpdater;
private final IssueStorage issueStorage;
+ private final WorkDurationFactory workDurationFactory;
public ActionPlanService(ActionPlanDao actionPlanDao, ActionPlanStatsDao actionPlanStatsDao, ResourceDao resourceDao, AuthorizationDao authorizationDao,
- IssueDao issueDao, IssueUpdater issueUpdater, IssueStorage issueStorage) {
+ IssueDao issueDao, IssueUpdater issueUpdater, IssueStorage issueStorage, WorkDurationFactory workDurationFactory) {
this.actionPlanDao = actionPlanDao;
this.actionPlanStatsDao = actionPlanStatsDao;
this.resourceDao = resourceDao;
this.issueDao = issueDao;
this.issueUpdater = issueUpdater;
this.issueStorage = issueStorage;
+ this.workDurationFactory = workDurationFactory;
}
public ActionPlan create(ActionPlan actionPlan, UserSession userSession) {
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
List<DefaultIssue> issues = newArrayList();
for (IssueDto issueDto : dtos) {
- DefaultIssue issue = issueDto.toDefaultIssue();
+ DefaultIssue issue = issueDto.toDefaultIssue(workDurationFactory.createFromWorkingLong(issueDto.getTechnicalDebt()));
// Unplan issue
if (issueUpdater.plan(issue, null, context)) {
issues.add(issue);
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
import org.sonar.api.utils.Paging;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.issue.DefaultIssueQueryResult;
import org.sonar.core.issue.db.IssueChangeDao;
import org.sonar.core.issue.db.IssueDao;
private final UserFinder userFinder;
private final ResourceDao resourceDao;
private final ActionPlanService actionPlanService;
+ private final WorkDurationFactory workDurationFactory;
public DefaultIssueFinder(MyBatis myBatis,
- IssueDao issueDao, IssueChangeDao issueChangeDao,
- DefaultRuleFinder ruleFinder,
- UserFinder userFinder,
- ResourceDao resourceDao,
- ActionPlanService actionPlanService) {
+ IssueDao issueDao, IssueChangeDao issueChangeDao,
+ DefaultRuleFinder ruleFinder,
+ UserFinder userFinder,
+ ResourceDao resourceDao,
+ ActionPlanService actionPlanService, WorkDurationFactory workDurationFactory) {
this.myBatis = myBatis;
this.issueDao = issueDao;
this.issueChangeDao = issueChangeDao;
this.userFinder = userFinder;
this.resourceDao = resourceDao;
this.actionPlanService = actionPlanService;
+ this.workDurationFactory = workDurationFactory;
}
DefaultIssue findByKey(String issueKey, String requiredRole) {
if (!UserSession.get().hasProjectPermission(requiredRole, dto.getRootComponentKey())) {
throw new IllegalStateException("User does not have the required role required to change the issue: " + issueKey);
}
- return dto.toDefaultIssue();
+
+ return dto.toDefaultIssue(workDurationFactory.createFromWorkingLong(dto.getTechnicalDebt()));
}
@Override
Set<String> actionPlanKeys = Sets.newHashSet();
Set<String> users = Sets.newHashSet();
for (IssueDto dto : pagedSortedIssues) {
- DefaultIssue defaultIssue = dto.toDefaultIssue();
+ DefaultIssue defaultIssue = dto.toDefaultIssue(workDurationFactory.createFromWorkingLong(dto.getTechnicalDebt()));
issuesByKey.put(dto.getKee(), defaultIssue);
issues.add(defaultIssue);
ruleIds.add(dto.getRuleId());
public Issue findByKey(String key) {
IssueDto dto = issueDao.selectByKey(key);
- return dto != null ? dto.toDefaultIssue() : null;
+ return dto != null ? dto.toDefaultIssue(workDurationFactory.createFromWorkingLong(dto.getTechnicalDebt())) : null;
}
}
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.internal.FieldDiffs;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.i18n.DefaultI18n;
import org.sonar.core.issue.IssueUpdater;
import org.sonar.server.technicaldebt.DebtFormatter;
private final DefaultI18n defaultI18n;
private final DebtFormatter debtFormatter;
+ private final WorkDurationFactory workDurationFactory;
- public IssueChangelogFormatter(DefaultI18n defaultI18n, DebtFormatter debtFormatter) {
+ public IssueChangelogFormatter(DefaultI18n defaultI18n, DebtFormatter debtFormatter, WorkDurationFactory workDurationFactory) {
this.defaultI18n = defaultI18n;
this.debtFormatter = debtFormatter;
+ this.workDurationFactory = workDurationFactory;
}
public List<String> format(Locale locale, FieldDiffs diffs) {
String oldValueString = oldValue != null && !"".equals(oldValue) ? oldValue.toString() : null;
if (IssueUpdater.TECHNICAL_DEBT.equals(key)) {
if (newValueString != null) {
- newValueString = debtFormatter.format(locale, WorkUnit.fromLong(Long.parseLong(newValueString)));
+ newValueString = debtFormatter.format(locale, workDurationFactory.createFromWorkingLong(Long.parseLong(newValueString)));
}
if (oldValueString != null) {
- oldValueString = debtFormatter.format(locale, WorkUnit.fromLong(Long.parseLong(oldValueString)));
+ oldValueString = debtFormatter.format(locale, workDurationFactory.createFromWorkingLong(Long.parseLong(oldValueString)));
}
}
return new IssueChangelogDiffFormat(oldValueString, newValueString);
import org.sonar.api.technicaldebt.server.Characteristic;
import org.sonar.api.user.User;
import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.workflow.Transition;
Component project = result.project(issue);
String actionPlanKey = issue.actionPlanKey();
ActionPlan actionPlan = result.actionPlan(issue);
- WorkUnit technicalDebt = issue.technicalDebt();
+ WorkDuration technicalDebt = issue.technicalDebt();
Date updateDate = issue.updateDate();
Date closeDate = issue.closeDate();
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.api.utils.UriReader;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.api.utils.internal.TempFolderCleaner;
import org.sonar.core.component.SnapshotPerspectives;
import org.sonar.core.config.Logback;
servicesContainer.addSingleton(TechnicalDebtXMLImporter.class);
servicesContainer.addSingleton(DebtFormatter.class);
servicesContainer.addSingleton(DefaultTechnicalDebtManager.class);
+ servicesContainer.addSingleton(WorkDurationFactory.class);
// source
servicesContainer.addSingleton(HtmlSourceDecorator.class);
package org.sonar.server.technicaldebt;
import org.sonar.api.ServerComponent;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.core.i18n.DefaultI18n;
import java.util.Locale;
this.defaultI18n = defaultI18n;
}
- public String format(Locale locale, WorkUnit technicalDebt) {
+ public String format(Locale locale, WorkDuration debt) {
StringBuilder message = new StringBuilder();
- if (technicalDebt.days() > 0) {
- message.append(defaultI18n.message(locale, "issue.technical_debt.x_days", null, technicalDebt.days()));
+ if (debt.days() > 0) {
+ message.append(defaultI18n.message(locale, "issue.technical_debt.x_days", null, debt.days()));
}
- if (technicalDebt.hours() > 0) {
+ if (debt.hours() > 0) {
if (message.length() > 0) {
message.append(" ");
}
- message.append(defaultI18n.message(locale, "issue.technical_debt.x_hours", null, technicalDebt.hours()));
+ message.append(defaultI18n.message(locale, "issue.technical_debt.x_hours", null, debt.hours()));
}
// Do not display minutes if days is not null to not have too much information
- if (technicalDebt.minutes() > 0 && technicalDebt.days() == 0) {
+ if (debt.minutes() > 0 && debt.days() == 0) {
if (message.length() > 0) {
message.append(" ");
}
- message.append(defaultI18n.message(locale, "issue.technical_debt.x_minutes", null, technicalDebt.minutes()));
+ message.append(defaultI18n.message(locale, "issue.technical_debt.x_minutes", null, debt.minutes()));
}
return message.toString();
}
import org.sonar.api.ServerComponent;
import org.sonar.api.technicaldebt.server.Characteristic;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.technicaldebt.DefaultTechnicalDebtManager;
import org.sonar.server.user.UserSession;
private final DebtFormatter debtFormatter;
private final DefaultTechnicalDebtManager finder;
+ private final WorkDurationFactory workDurationFactory;
- public DebtService(DebtFormatter debtFormatter, DefaultTechnicalDebtManager finder) {
+ public DebtService(DebtFormatter debtFormatter, DefaultTechnicalDebtManager finder, WorkDurationFactory workDurationFactory) {
this.debtFormatter = debtFormatter;
this.finder = finder;
+ this.workDurationFactory = workDurationFactory;
}
- public String format(WorkUnit technicalDebt) {
+ public String format(WorkDuration technicalDebt) {
return debtFormatter.format(UserSession.get().locale(), technicalDebt);
}
- public WorkUnit toTechnicalDebt(String technicalDebtInLong) {
- return WorkUnit.fromLong(Long.parseLong(technicalDebtInLong));
+ public WorkDuration toTechnicalDebt(String technicalDebtInLong) {
+ return workDurationFactory.createFromWorkingLong(Long.parseLong(technicalDebtInLong));
}
public List<Characteristic> findRootCharacteristics() {
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.ActionPlanStats;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.db.ActionPlanDao;
-import org.sonar.core.issue.db.ActionPlanDto;
-import org.sonar.core.issue.db.ActionPlanStatsDao;
-import org.sonar.core.issue.db.ActionPlanStatsDto;
-import org.sonar.core.issue.db.IssueDao;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.issue.db.IssueStorage;
+import org.sonar.core.issue.db.*;
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.ResourceDto;
import org.sonar.core.resource.ResourceQuery;
import static org.mockito.Matchers.anyInt;
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 ActionPlanServiceTest {
when(userSession.userId()).thenReturn(10);
when(authorizationDao.isAuthorizedComponentKey(anyString(), eq(10), anyString())).thenReturn(true);
- actionPlanService = new ActionPlanService(actionPlanDao, actionPlanStatsDao, resourceDao, authorizationDao, issueDao, issueUpdater, issueStorage);
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, 8);
+ actionPlanService = new ActionPlanService(actionPlanDao, actionPlanStatsDao, resourceDao, authorizationDao, issueDao, issueUpdater, issueStorage,
+ new WorkDurationFactory(settings));
}
@Test
import com.google.common.collect.Lists;
import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
import org.junit.Test;
+import org.sonar.api.CoreProperties;
import org.sonar.api.component.Component;
+import org.sonar.api.config.Settings;
import org.sonar.api.issue.ActionPlan;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.IssueQuery;
import org.sonar.api.rules.Rule;
import org.sonar.api.user.User;
import org.sonar.api.user.UserFinder;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.db.IssueChangeDao;
public class DefaultIssueFinderTest {
+ private static final int HOURS_IN_DAY = 8;
+
MyBatis mybatis = mock(MyBatis.class);
IssueDao issueDao = mock(IssueDao.class);
IssueChangeDao issueChangeDao = mock(IssueChangeDao.class);
ResourceDao resourceDao = mock(ResourceDao.class);
ActionPlanService actionPlanService = mock(ActionPlanService.class);
UserFinder userFinder = mock(UserFinder.class);
- DefaultIssueFinder finder = new DefaultIssueFinder(mybatis, issueDao, issueChangeDao, ruleFinder, userFinder, resourceDao, actionPlanService);
+ DefaultIssueFinder finder;
+
+ @Before
+ public void setUp() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
+ finder = new DefaultIssueFinder(mybatis, issueDao, issueChangeDao, ruleFinder, userFinder, resourceDao, actionPlanService, new WorkDurationFactory(settings));
+ }
@Test
public void find_issues() {
assertThat(results.issues()).hasSize(1);
DefaultIssue result = (DefaultIssue) results.issues().iterator().next();
- assertThat(result.technicalDebt()).isEqualTo(new WorkUnit.Builder().setMinutes(10).build());
+ assertThat(result.technicalDebt()).isEqualTo(WorkDuration.createFromValueAndUnit(10, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY));
}
}
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.issue.internal.FieldDiffs;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.i18n.DefaultI18n;
import org.sonar.server.technicaldebt.DebtFormatter;
private static final Locale DEFAULT_LOCALE = Locale.getDefault();
+ private static final int HOURS_IN_DAY = 8;
+
@Mock
private DefaultI18n i18n;
@Before
public void before() {
- formatter = new IssueChangelogFormatter(i18n, debtFormatter);
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
+ formatter = new IssueChangelogFormatter(i18n, debtFormatter, new WorkDurationFactory(settings));
}
@Test
FieldDiffs diffs = new FieldDiffs();
diffs.setDiff("technicalDebt", "500", "10000");
- when(debtFormatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setHours(5).build())).thenReturn("5 hours");
- when(debtFormatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setDays(1).build())).thenReturn("1 days");
+ when(debtFormatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.HOURS, HOURS_IN_DAY))).thenReturn("5 hours");
+ when(debtFormatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, HOURS_IN_DAY))).thenReturn("1 days");
when(i18n.message(DEFAULT_LOCALE, "issue.changelog.field.technicalDebt", null)).thenReturn("Technical Debt");
when(i18n.message(DEFAULT_LOCALE, "issue.changelog.changed_to", null, "Technical Debt", "1 days")).thenReturn("Technical Debt changed to 1 days");
FieldDiffs diffs = new FieldDiffs();
diffs.setDiff("technicalDebt", null, "10000");
- when(debtFormatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setDays(1).build())).thenReturn("1 days");
+ when(debtFormatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.DAYS, 8))).thenReturn("1 days");
when(i18n.message(DEFAULT_LOCALE, "issue.changelog.field.technicalDebt", null)).thenReturn("Technical Debt");
when(i18n.message(DEFAULT_LOCALE, "issue.changelog.changed_to", null, "Technical Debt", "1 days")).thenReturn("Technical Debt changed to 1 days");
import org.sonar.api.technicaldebt.server.internal.DefaultCharacteristic;
import org.sonar.api.user.User;
import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultActionPlan;
import org.sonar.core.issue.DefaultIssueQueryResult;
@Test
public void show_issue_with_technical_debt() throws Exception {
- WorkUnit technicalDebt = new WorkUnit.Builder().setHours(2).setMinutes(1).build();
+ WorkDuration technicalDebt = WorkDuration.create(0, 2, 1, 8);
Issue issue = createStandardIssue().setTechnicalDebt(technicalDebt);
issues.add(issue);
@Test
public void show_issue_with_characteristics() throws Exception {
- WorkUnit technicalDebt = new WorkUnit.Builder().setHours(2).setMinutes(1).build();
- ;
+ WorkDuration technicalDebt = WorkDuration.create(0, 2, 1, 8);
Issue issue = createStandardIssue().setTechnicalDebt(technicalDebt);
issues.add(issue);
package org.sonar.server.technicaldebt;
import org.junit.Test;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
import org.sonar.core.i18n.DefaultI18n;
import java.util.Locale;
when(i18n.message(DEFAULT_LOCALE, "issue.technical_debt.x_hours", null, 2)).thenReturn("2 hours");
when(i18n.message(DEFAULT_LOCALE, "issue.technical_debt.x_minutes", null, 1)).thenReturn("1 minutes");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setDays(5).build())).isEqualTo("5 days");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setHours(2).build())).isEqualTo("2 hours");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setMinutes(1).build())).isEqualTo("1 minutes");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.DAYS, 8))).isEqualTo("5 days");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(2, WorkDuration.UNIT.HOURS, 8))).isEqualTo("2 hours");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.createFromValueAndUnit(1, WorkDuration.UNIT.MINUTES, 8))).isEqualTo("1 minutes");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setDays(5).setHours(2).build())).isEqualTo("5 days 2 hours");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setHours(2).setMinutes(1).build())).isEqualTo("2 hours 1 minutes");
- assertThat(formatter.format(DEFAULT_LOCALE, new WorkUnit.Builder().setDays(5).setHours(2).setMinutes(10).build())).isEqualTo("5 days 2 hours");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.create(5, 2, 0, 8))).isEqualTo("5 days 2 hours");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.create(0, 2, 1, 8))).isEqualTo("2 hours 1 minutes");
+ assertThat(formatter.format(DEFAULT_LOCALE, WorkDuration.create(5, 2, 10, 8))).isEqualTo("5 days 2 hours");
}
}
*/
package org.sonar.server.technicaldebt;
+import org.junit.Before;
import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.technicaldebt.server.Characteristic;
import org.sonar.api.technicaldebt.server.internal.DefaultCharacteristic;
-import org.sonar.api.utils.WorkUnit;
+import org.sonar.api.utils.WorkDuration;
+import org.sonar.api.utils.WorkDurationFactory;
import org.sonar.core.technicaldebt.DefaultTechnicalDebtManager;
import java.util.List;
public class DebtServiceTest {
+ private static final int HOURS_IN_DAY = 8;
DebtFormatter debtFormatter = mock(DebtFormatter.class);
DefaultTechnicalDebtManager finder = mock(DefaultTechnicalDebtManager.class);
- DebtService service = new DebtService(debtFormatter, finder);
+
+ DebtService service;
+
+ @Before
+ public void setUp() throws Exception {
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY);
+ service = new DebtService(debtFormatter, finder, new WorkDurationFactory(settings));
+ }
@Test
public void format() {
- WorkUnit technicalDebt = new WorkUnit.Builder().setMinutes(5).build();
+ WorkDuration technicalDebt = WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.MINUTES, HOURS_IN_DAY);
service.format(technicalDebt);
verify(debtFormatter).format(any(Locale.class), eq(technicalDebt));
}
@Test
public void to_technical_debt() {
- assertThat(service.toTechnicalDebt("500")).isEqualTo(new WorkUnit.Builder().setHours(5).build());
+ assertThat(service.toTechnicalDebt("500")).isEqualTo(WorkDuration.createFromValueAndUnit(5, WorkDuration.UNIT.HOURS, HOURS_IN_DAY));
}
@Test