return ImmutableList.of(
PropertyDefinition.builder(WorkUnitConverter.PROPERTY_HOURS_IN_DAY)
.name("Number of working hours in a day")
- .defaultValue("" + WorkUnitConverter.DEFAULT_HOURS_IN_DAY)
+ .defaultValue("8")
.category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
.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.core.technicaldebt;
+
+import javax.annotation.CheckForNull;
+
+import java.util.concurrent.TimeUnit;
+
+public class RemediationCostTimeUnit {
+
+ private TimeUnitValue days;
+ private TimeUnitValue hours;
+ private TimeUnitValue minutes;
+
+ private static final int ONE_HOUR_IN_MINUTE = 60;
+
+ private RemediationCostTimeUnit(long input, int hoursInDay) {
+ int oneWorkingDay = hoursInDay * ONE_HOUR_IN_MINUTE;
+ if (input >= oneWorkingDay) {
+ long nbDays = input / oneWorkingDay;
+ this.days = new TimeUnitValue(nbDays, TimeUnit.DAYS);
+ input = input - (nbDays * oneWorkingDay);
+ } else {
+ this.days = new TimeUnitValue(0L, TimeUnit.DAYS);
+ }
+
+ if (input >= ONE_HOUR_IN_MINUTE) {
+ long nbHours = input / ONE_HOUR_IN_MINUTE;
+ this.hours = new TimeUnitValue(nbHours, TimeUnit.HOURS);
+ input = input - (nbHours * ONE_HOUR_IN_MINUTE);
+ } else {
+ this.hours = new TimeUnitValue(0L, TimeUnit.HOURS);
+ }
+
+ this.minutes = new TimeUnitValue(input, TimeUnit.MINUTES);
+ }
+
+ public static RemediationCostTimeUnit of(Long time, int hoursInDay) {
+ return new RemediationCostTimeUnit(time, hoursInDay);
+ }
+
+ @CheckForNull
+ public TimeUnitValue days() {
+ return days;
+ }
+
+ @CheckForNull
+ public TimeUnitValue hours() {
+ return hours;
+ }
+
+ @CheckForNull
+ public TimeUnitValue minutes() {
+ return minutes;
+ }
+
+ public static class TimeUnitValue {
+
+ private long value;
+ private TimeUnit unit;
+
+ private TimeUnitValue(long value, TimeUnit unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+
+ public long value() {
+ return value;
+ }
+
+ public TimeUnit unit() {
+ return unit;
+ }
+ }
+}
import org.apache.commons.lang.StringUtils;
import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
import org.sonar.api.config.Settings;
-public final class WorkUnitConverter implements BatchComponent {
-
- public static final int DEFAULT_HOURS_IN_DAY = 8;
+public final class WorkUnitConverter implements BatchComponent, ServerComponent {
public static final String PROPERTY_HOURS_IN_DAY = "sonar.technicalDebt.hoursInDay";
- private int hoursInDay = DEFAULT_HOURS_IN_DAY;
+ private int hoursInDay;
public WorkUnitConverter(Settings settings) {
this.hoursInDay = settings.getInt(PROPERTY_HOURS_IN_DAY);
}
- public int getHoursInDay() {
- return hoursInDay;
- }
-
public double toDays(WorkUnit factor) {
- double result;
if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) {
- result = factor.getValue();
+ return factor.getValue();
} else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) {
- result = factor.getValue() / hoursInDay;
+ return factor.getValue() / hoursInDay;
} else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) {
- result = factor.getValue() / (hoursInDay * 60.0);
+ return factor.getValue() / (hoursInDay * 60.0);
} else {
throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit());
}
- return result;
}
public long toMinutes(WorkUnit factor) {
- long result;
if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) {
- result = Double.valueOf(factor.getValue() * hoursInDay * 60d).longValue();
+ return Double.valueOf(factor.getValue() * hoursInDay * 60d).longValue();
} else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) {
- result = Double.valueOf(factor.getValue() * 60d).longValue();
+ return Double.valueOf(factor.getValue() * 60d).longValue();
} else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) {
- result = Double.valueOf(factor.getValue()).longValue();
+ return Double.valueOf(factor.getValue()).longValue();
} else {
throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit());
}
- return result;
}
+
+ public RemediationCostTimeUnit toRemediationCostTimeUnit(long minutes){
+ return RemediationCostTimeUnit.of(minutes, hoursInDay);
+ }
+
}
--- /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.core.technicaldebt;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class RemediationCostTimeUnitTest {
+
+ private static final int HOURS_IN_DAY = 8;
+
+ @Test
+ public void convert_simple_values() {
+ checkTimes(RemediationCostTimeUnit.of(15L, HOURS_IN_DAY), 15L, 0L, 0L);
+ checkTimes(RemediationCostTimeUnit.of(120L, HOURS_IN_DAY), 0L, 2L, 0L);
+ checkTimes(RemediationCostTimeUnit.of(480L, HOURS_IN_DAY), 0L, 0L, 1L);
+ }
+
+ @Test
+ public void convert_complex_values() {
+ checkTimes(RemediationCostTimeUnit.of(70L, HOURS_IN_DAY), 10L, 1L, 0L);
+ checkTimes(RemediationCostTimeUnit.of(490L, HOURS_IN_DAY), 10L, 0L, 1L);
+ checkTimes(RemediationCostTimeUnit.of(550L, HOURS_IN_DAY), 10L, 1L, 1L);
+ }
+
+ private void checkTimes(RemediationCostTimeUnit remediationCostTimeUnit, Long expectedMinutes, Long expectedHours, Long expectedDays ) {
+ if (expectedMinutes != null) {
+ checkTime(remediationCostTimeUnit.minutes(), expectedMinutes, TimeUnit.MINUTES);
+ } else {
+ assertThat(remediationCostTimeUnit.minutes().value()).isEqualTo(0L);
+ }
+ if (expectedHours != null) {
+ checkTime(remediationCostTimeUnit.hours(), expectedHours, TimeUnit.HOURS);
+ } else {
+ assertThat(remediationCostTimeUnit.hours().value()).isEqualTo(0L);
+ }
+ if (expectedDays != null) {
+ checkTime(remediationCostTimeUnit.days(), expectedDays, TimeUnit.DAYS);
+ } else {
+ assertThat(remediationCostTimeUnit.days().value()).isEqualTo(0L);
+ }
+ }
+
+ private void checkTime(RemediationCostTimeUnit.TimeUnitValue timeUnitValue, Long expectedValue, TimeUnit expectedUnit) {
+ assertThat(timeUnitValue.value()).isEqualTo(expectedValue);
+ assertThat(timeUnitValue.unit()).isEqualTo(expectedUnit);
+ }
+
+
+}
public class WorkUnitConverterTest {
@Test
- public void concert_to_days() {
+ public void convert_to_days() {
Settings settings = new Settings();
settings.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, "12");
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.ResourceDto;
import org.sonar.core.resource.ResourceQuery;
+import org.sonar.core.technicaldebt.RemediationCostTimeUnit;
+import org.sonar.core.technicaldebt.WorkUnitConverter;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.user.UserSession;
import org.sonar.server.util.RubyUtils;
private final ActionService actionService;
private final IssueFilterService issueFilterService;
private final IssueBulkChangeService issueBulkChangeService;
+ private final WorkUnitConverter workUnitConverter;
public InternalRubyIssueService(IssueService issueService,
IssueCommentService commentService,
IssueChangelogService changelogService, ActionPlanService actionPlanService,
IssueStatsFinder issueStatsFinder, ResourceDao resourceDao, ActionService actionService,
- IssueFilterService issueFilterService, IssueBulkChangeService issueBulkChangeService) {
+ IssueFilterService issueFilterService, IssueBulkChangeService issueBulkChangeService,
+ WorkUnitConverter workUnitConverter) {
this.issueService = issueService;
this.commentService = commentService;
this.changelogService = changelogService;
this.actionService = actionService;
this.issueFilterService = issueFilterService;
this.issueBulkChangeService = issueBulkChangeService;
+ this.workUnitConverter = workUnitConverter;
}
public IssueStatsFinder.IssueStatsResult findIssueAssignees(Map<String, Object> params) {
return result;
}
- private boolean isActionPlanNameAvailable(@Nullable DefaultActionPlan existingActionPlan, String name, String projectParam){
+ private boolean isActionPlanNameAvailable(@Nullable DefaultActionPlan existingActionPlan, String name, String projectParam) {
return (existingActionPlan == null || !name.equals(existingActionPlan.name())) && actionPlanService.isNameAlreadyUsedForProject(name, projectParam);
}
}
}
- public boolean canUserShareIssueFilter(){
+ public boolean canUserShareIssueFilter() {
return issueFilterService.canShareFilter(UserSession.get());
}
}
}
+ @CheckForNull
+ public RemediationCostTimeUnit remediationCost(Issue issue) {
+ Long remediationCost = issue.remediationCost();
+ if (remediationCost != null) {
+ return workUnitConverter.toRemediationCostTimeUnit(issue.remediationCost());
+ } else {
+ return null;
+ }
+ }
+
}
import org.sonar.core.resource.DefaultResourcePermissions;
import org.sonar.core.rule.DefaultRuleFinder;
import org.sonar.core.source.HtmlSourceDecorator;
+import org.sonar.core.technicaldebt.WorkUnitConverter;
import org.sonar.core.test.TestPlanPerspectiveLoader;
import org.sonar.core.test.TestablePerspectiveLoader;
import org.sonar.core.timemachine.Periods;
servicesContainer.addSingleton(TechnicalDebtManager.class);
servicesContainer.addSingleton(TechnicalDebtModelFinder.class);
servicesContainer.addSingleton(XMLImporter.class);
+ servicesContainer.addSingleton(WorkUnitConverter.class);
// text
servicesContainer.addSingleton(MacroInterpreter.class);
<% end %>
<img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- <a href="#" onclick="return toggleIssueChangelog(this)" class="gray issue-changelog-link" id="toggle-issue-changelog"><%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(issue.creationDate())) -%></a>
+ <a href="#" onclick="return toggleIssueChangelog(this)" class="gray issue-changelog-link"
+ id="toggle-issue-changelog"><%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(issue.creationDate())) -%></a>
<% if issue.reporter %>
<img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
<span><%= message('issue.authorLogin') -%> <%= issue.authorLogin -%></span>
<% end %>
+ <% if issue.remediationCost %>
+ <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
+
+ <span><%= issue.remediationCost -%></span>
+
+ <% end %>
</div>
<div class="issue-rule rule-desc" style="display: none"></div>
ResourceDto project = new ResourceDto().setKey("org.sonar.Sample");
when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(project);
service = new InternalRubyIssueService(issueService, commentService, changelogService, actionPlanService, issueStatsFinder, resourceDao, actionService,
- issueFilterService, issueBulkChangeService);
+ issueFilterService, issueBulkChangeService, null);
}
@Test