aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2014-02-07 12:25:32 +0100
committerJulien Lancelot <julien.lancelot@sonarsource.com>2014-02-07 12:25:32 +0100
commiteb6c3ebf24003a296f2a69ce5ba60fbd24e651b6 (patch)
treee65671b30a2e2cdd9ef4a9fa9681aacfa4e0bda7
parent18c4b94aaf596645ecd53d9df1101986a7a1d599 (diff)
downloadsonarqube-eb6c3ebf24003a296f2a69ce5ba60fbd24e651b6.tar.gz
sonarqube-eb6c3ebf24003a296f2a69ce5ba60fbd24e651b6.zip
Update issue ui in resource viewer update to be consistent with issues filter
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties1
-rw-r--r--sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java16
-rw-r--r--sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManagerTest.java39
-rw-r--r--sonar-server/src/main/java/org/sonar/server/rule/ws/RuleShowWsHandler.java1
-rw-r--r--sonar-server/src/main/java/org/sonar/server/technicaldebt/DebtService.java6
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb21
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb282
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb14
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/issue/_technicaldebt.html.erb8
-rw-r--r--sonar-server/src/main/webapp/javascripts/issue.js68
-rw-r--r--sonar-server/src/main/webapp/stylesheets/style.css4
-rw-r--r--sonar-server/src/test/java/org/sonar/server/technicaldebt/DebtServiceTest.java9
12 files changed, 240 insertions, 229 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index 4a467aa054d..cbe50409e85 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -582,6 +582,7 @@ issue.manual.no_rules.non_admin=At least one manual rule must exist before manua
issue.reported_by=Reported by
issue.authorLogin=Author:
issue.component_deleted=Removed
+issue.debt=Debt:
issue.technical_debt=Technical Debt:
issue.technical_debt_short=Debt
issue.technical_debt.x_days={0} days
diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java
index 86913704cf3..3bb7b5bae7d 100644
--- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java
+++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java
@@ -22,6 +22,7 @@ package org.sonar.core.technicaldebt;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
import org.sonar.api.technicaldebt.server.Characteristic;
import org.sonar.api.technicaldebt.server.TechnicalDebtManager;
import org.sonar.api.technicaldebt.server.internal.DefaultCharacteristic;
@@ -31,6 +32,7 @@ import org.sonar.core.technicaldebt.db.CharacteristicDto;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
@@ -41,9 +43,11 @@ import static com.google.common.collect.Lists.newArrayList;
public class DefaultTechnicalDebtManager implements TechnicalDebtManager {
private final CharacteristicDao dao;
+ private final RuleFinder ruleFinder;
- public DefaultTechnicalDebtManager(CharacteristicDao dao) {
+ public DefaultTechnicalDebtManager(CharacteristicDao dao, RuleFinder ruleFinder) {
this.dao = dao;
+ this.ruleFinder = ruleFinder;
}
public List<Characteristic> findRootCharacteristics() {
@@ -65,6 +69,16 @@ public class DefaultTechnicalDebtManager implements TechnicalDebtManager {
}
@CheckForNull
+ public Characteristic findRequirementByRuleId(int ruleId) {
+ CharacteristicDto requirementDto = dao.selectByRuleId(ruleId);
+ if (requirementDto != null) {
+ Rule rule = ruleFinder.findById(ruleId);
+ return toCharacteristic(requirementDto, RuleKey.of(rule.getRepositoryKey(), rule.getKey()));
+ }
+ return null;
+ }
+
+ @CheckForNull
public Characteristic findRequirementByRule(Rule rule) {
CharacteristicDto requirementDto = dao.selectByRuleId(rule.getId());
if (requirementDto != null) {
diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManagerTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManagerTest.java
index a20cb142ee9..7ac3d435309 100644
--- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManagerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManagerTest.java
@@ -27,6 +27,7 @@ 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.technicaldebt.server.Characteristic;
import org.sonar.api.utils.WorkUnit;
import org.sonar.core.technicaldebt.db.CharacteristicDao;
@@ -44,11 +45,14 @@ public class DefaultTechnicalDebtManagerTest {
@Mock
CharacteristicDao dao;
+ @Mock
+ RuleFinder ruleFinder;
+
DefaultTechnicalDebtManager finder;
@Before
public void setUp() throws Exception {
- finder = new DefaultTechnicalDebtManager(dao);
+ finder = new DefaultTechnicalDebtManager(dao, ruleFinder);
}
@Test
@@ -126,4 +130,37 @@ public class DefaultTechnicalDebtManagerTest {
Characteristic result = finder.findCharacteristicById(2);
assertThat(result).isNull();
}
+
+ @Test
+ public void find_requirement_by_rule_id() throws Exception {
+ Rule rule = Rule.create("repo", "key");
+ rule.setId(1);
+
+ when(ruleFinder.findById(1)).thenReturn(rule);
+
+ when(dao.selectByRuleId(rule.getId())).thenReturn(
+ new CharacteristicDto().setId(3).setRuleId(10).setParentId(2).setRootId(1).setFunction("linear").setFactorValue(30.0).setFactorUnit("mn"));
+
+ Characteristic result = finder.findRequirementByRuleId(1);
+
+ assertThat(result.id()).isEqualTo(3);
+ assertThat(result.parentId()).isEqualTo(2);
+ assertThat(result.rootId()).isEqualTo(1);
+ assertThat(result.ruleKey()).isEqualTo(RuleKey.of("repo", "key"));
+ assertThat(result.function()).isEqualTo("linear");
+ assertThat(result.factor()).isEqualTo(WorkUnit.create(30.0, WorkUnit.MINUTES));
+ assertThat(result.offset()).isEqualTo(WorkUnit.create());
+ }
+
+ @Test
+ public void not_find_requirement_by_rule_id_on_unknown_requirement() throws Exception {
+ Rule rule = Rule.create("repo", "key");
+ rule.setId(1);
+
+ when(ruleFinder.findById(1)).thenReturn(rule);
+
+ when(dao.selectByRuleId(rule.getId())).thenReturn(null);
+
+ assertThat(finder.findRequirementByRuleId(1)).isNull();
+ }
}
diff --git a/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleShowWsHandler.java b/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleShowWsHandler.java
index 6efedab37c1..ccb97dc0ce8 100644
--- a/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleShowWsHandler.java
+++ b/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleShowWsHandler.java
@@ -74,6 +74,7 @@ public class RuleShowWsHandler implements RequestHandler {
@CheckForNull
private Rule findRule(RuleKey ruleKey) {
+ // TODO remove this when manual rules when be indexed in E/S
if (ruleKey.repository().equals(Rule.MANUAL_REPOSITORY_KEY)) {
org.sonar.api.rules.Rule rule = ruleFinder.findByKey(ruleKey);
if (rule != null) {
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/DebtService.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/DebtService.java
index 464214c03e3..fe89ec33b3b 100644
--- a/sonar-server/src/main/java/org/sonar/server/technicaldebt/DebtService.java
+++ b/sonar-server/src/main/java/org/sonar/server/technicaldebt/DebtService.java
@@ -22,12 +22,12 @@ package org.sonar.server.technicaldebt;
import org.sonar.api.ServerComponent;
import org.sonar.api.issue.internal.WorkDayDuration;
-import org.sonar.api.rules.Rule;
import org.sonar.api.technicaldebt.server.Characteristic;
import org.sonar.core.technicaldebt.DefaultTechnicalDebtManager;
import org.sonar.server.user.UserSession;
import javax.annotation.CheckForNull;
+
import java.util.List;
public class DebtService implements ServerComponent {
@@ -52,8 +52,8 @@ public class DebtService implements ServerComponent {
return finder.findRootCharacteristics();
}
- public Characteristic findRequirement(Rule rule) {
- return finder.findRequirementByRule(rule);
+ public Characteristic findRequirementByRuleId(int ruleId) {
+ return finder.findRequirementByRuleId(ruleId);
}
@CheckForNull
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb
index 58df6c0e39f..a9b04bcd49b 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb
@@ -187,6 +187,12 @@ class IssueController < ApplicationController
require_parameters :id
rule_key = params[:id].split(':')
@rule = Rule.first(:conditions => ['plugin_name=? and plugin_rule_key=?', rule_key[0], rule_key[1]])
+ @requirement = Internal.technical_debt.findRequirementByRuleId(@rule.id)
+ # Requirement can be null if it's disabled or if there's no requirement on this rule
+ if @requirement
+ @characteristic = Internal.technical_debt.findCharacteristic(@requirement.parentId)
+ @root_characteristic = Internal.technical_debt.findCharacteristic(@requirement.rootId)
+ end
render :partial => 'issue/rule'
end
@@ -200,21 +206,6 @@ class IssueController < ApplicationController
render :partial => 'issue/changelog'
end
- # Display the technical debt detail in the issue panel
- def technicaldebt
- verify_ajax_request
- require_parameters :id
- @issue_results = Api.issues.find(params[:id])
- @issue = @issue_results.first()
-
- rule = @issue_results.rule(@issue)
- @requirement = Internal.technical_debt.findRequirement(rule)
- # Requirement can be null if it's disabled
- @characteristic = Internal.technical_debt.findCharacteristic(@requirement.parentId) if @requirement
- @root_characteristic = Internal.technical_debt.findCharacteristic(@requirement.rootId) if @requirement
- render :partial => 'issue/technicaldebt'
- end
-
private
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb
index 0d790ae6585..18aca66d630 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb
@@ -1,166 +1,158 @@
-<div class="code-issue" data-issue-key="<%= issue.key -%>" data-issue-component="<%= issue.componentKey() -%>" data-issue-rule="<%= u issue.ruleKey().toString() -%>">
- <div class="code-issue-name">
- <div style="float: right">
+<div id="issue-<%= u issue.key -%>" class="code-issue code-issue-collapsed" data-issue-key="<%= issue.key -%>" data-issue-component="<%= issue.componentKey() -%>" data-issue-rule="<%= u issue.ruleKey().toString() -%>">
+ <div class="code-issue-name code-issue-toggle">
+ <div class="code-issue-name-rule">
+ <i class="icon-severity-<%= issue.severity.downcase -%>"></i>
+ <span class="rulename">
+ <%= h !issue.message.blank? ? Api::Utils.split_newlines(issue.message).join('<br/>') : @issue_results.rule(issue).getName() -%>
+ </span>
+ </div>
+ <div class="code-issue-permalink">
<a href="#" onclick="return openIssuePopup(this)" class="issue-permalink"><img src="<%= ApplicationController.root_context -%>/images/new-window-16.gif"></a>
</div>
+ </div>
- <i class="icon-severity-<%= issue.severity.downcase -%>"></i>
- &nbsp;
- <a href="#" onclick="return toggleIssueRule(this)" class="rulename issue-rule-link"><%= h @issue_results.rule(issue).getName() -%></a>
- &nbsp;
- <% if issue.resolution %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span><%= message("issue.resolution.#{issue.resolution}") -%></span>
- &nbsp;
- <% else %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span><%= message("issue.status.#{issue.status}") -%></span>
- &nbsp;
- <% end %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <%= message('issue.updated') -%>&nbsp;<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.updateDate())) -%></a>
- &nbsp;
- <% if issue.reporter %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span><%= message('issue.reported_by') -%> <%= @issue_results.user(issue.reporter).name -%></span>
- &nbsp;
+ <ul class="code-issue-actions code-issue-list">
+ <% if current_user %>
+ <li><a href='#' onclick="return issueForm('comment', this)" class="link-action spacer-right" autofocus><%= message('issue.comment.formlink') -%></a></li>
<% end %>
- <% if issue.authorLogin %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span><%= message('issue.authorLogin') -%>&nbsp;<%= issue.authorLogin -%></span>
- &nbsp;
- <% end %>
- <% if issue.technicalDebt %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <%= message('issue.technical_debt') -%>&nbsp;<a href="#" onclick="return toggleTechnicalDebt(this)" class="gray issue-technicaldebt-link"
- id="toggle-issue-technicaldebt"><%= Internal.technical_debt.format(issue.technicalDebt) -%></a>
- &nbsp;
+ <li>
+ <i class="icon-status-<%= issue.status.downcase -%>"></i><%= message("issue.status.#{issue.status}") -%> <%= '(' + message("issue.resolution.#{issue.resolution}") + ')' if issue.resolution -%>
+ </li>
+ <% if current_user %>
+ <% transitions = Internal.issues.listTransitions(issue).to_a
+ if !transitions.empty? && transitions.first
+ first_transition = transitions.first %>
+ <li>
+ <!-- Display only the first transition -->
+ <a href="#" onclick="return doIssueTransition(this, '<%= first_transition.key -%>')" class="link-action issue-transition spacer-left">
+ <%= message("issue.transition.#{first_transition.key}") -%></a>
+ <!-- Display remaining transitions -->
+ <% if transitions.size > 1 %>
+ <div class="dropdown">
+ <a href="#" class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;"/></a>
+ <ul style="display: none" class="dropdown-menu">
+ <% transitions[1..-1].each do |transition| %>
+ <li>
+ <a href="#" onclick="return doIssueTransition(this, '<%= transition.key -%>')" class="link-action spacer-right"><%= message("issue.transition.#{transition.key}") -%></a>
+ </li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ </li>
+ <% end %>
<% end %>
- </div>
-
- <div class="issue-rule rule-desc" style="display: none"></div>
- <div class="issue-changelog" id="issue-changelog" style="display: none"></div>
- <div class="issue-technicaldebt" id="issue-technicaldebt" style="display: none"></div>
-
- <% unless issue.message.blank? %>
- <div class="code-issue-msg">
- <%= Api::Utils.split_newlines(h(issue.message)).join('<br/>') -%>
- </div>
- <% end %>
-
- <%
- issue.comments.each do |comment|
- comment_html_id = "comment-#{comment.key}-#{rand(100)}"
- %>
- <div class="code-issue-comment" id="<%= comment_html_id -%>" data-comment-key="<%= comment.key -%>">
- <h4>
- <%= image_tag('reviews/comment.png') -%> &nbsp;<b><%= @issue_results.user(comment.userLogin()).name() -%></b>
- (<%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(comment.createdAt)) -%>)
- <% if current_user && current_user.login==comment.userLogin %>
- &nbsp;
- <%= image_tag 'sep12.png' -%>
- &nbsp;
- <a class="link-action" href="#" onclick="return formEditIssueComment(this)"><%= message('edit') -%></a>
- <a class="link-action spacer-right" href="#" onclick="return deleteIssueComment(this, '<%= escape_javascript(message('issue.comment.delete_confirm_message')) -%>')"><%= message('delete') -%></a>
- <% end %>
- </h4>
- <%= Internal.text.markdownToHtml(comment.markdownText) -%>
- </div>
- <% end %>
-
- <% if current_user %>
-
- <div class="code-issue-actions">
- <a href='#' onclick="return issueForm('comment', this)" class="link-action spacer-right" autofocus><%= message('issue.comment.formlink') -%></a>
- <% unless issue.resolution %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span class="spacer-right">
+ <% unless issue.resolution %>
+ <li>
<% if issue.assignee %>
- <a href='#' onclick="return issueForm('assign', this)" class="link-action"><%= message('assigned_to') -%></a> <%= h @issue_results.user(issue.assignee).name -%>
- <% else %>
+ <% if current_user %>
+ <a href='#' onclick="return issueForm('assign', this)" class="link-action"><%= message('assigned_to') -%></a> <%= h @issue_results.user(issue.assignee).name -%>
+ <% else %>
+ <%= message('assigned_to') -%> <strong><%= h @issue_results.user(issue.assignee).name -%></strong>
+ <% end %>
+ <% elsif current_user %>
<a href='#' onclick="return issueForm('assign', this)" class="link-action"><%= message('issue.assign.formlink') -%></a>
<% if issue.assignee != current_user.login %>
[<a href="#" onclick="return assignIssueToMe(this)" class="link-action"><%= message('issue.assign.to_me') -%></a>]
<% end %>
<% end %>
- </span>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <span class="spacer-right">
- <% if issue.actionPlanKey %>
- <a href="#" onclick="return issueForm('plan', this)" class="link-action"><%= message('issue.planned_for') -%></a> <%= h(@issue_results.actionPlan(issue).name()) -%>
- <% else %>
- <a href="#" onclick="return issueForm('plan', this)" class="link-action"><%= message('issue.do_plan') -%></a>
- <% end %>
- </span>
- <% end %>
-
- <%
- transitions = Internal.issues.listTransitions(issue).to_a
-
- # Display only the first transition
- if !transitions.empty? && transitions.first
- first_transition = transitions.first
- %>
- <img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>
- &nbsp;
- <a href="#" onclick="return doIssueTransition(this, '<%= first_transition.key -%>')" class="link-action spacer-right"><%= message("issue.transition.#{first_transition.key}") -%></a>
+ </li>
+ <li>
+ <% if issue.actionPlanKey %>
+ <% if current_user %>
+ <a href="#" onclick="return issueForm('plan', this)" class="link-action"><%= message('issue.planned_for') -%></a> <%= h(@issue_results.actionPlan(issue).name()) -%>
+ <% else %>
+ <%= message('issue.planned_for') -%> <strong><%= h(@issue_results.actionPlan(issue).name()) -%></strong>
+ <% end %>
+ <% elsif current_user %>
+ <a href="#" onclick="return issueForm('plan', this)" class="link-action"><%= message('issue.do_plan') -%></a>
<% end %>
+ </li>
+ <% end %>
+ <% if current_user %>
+ <% plugin_actions = Internal.issues.listActions(issue)
+ if !issue.resolution || !plugin_actions.empty? %>
+ <li>
+ <div class="dropdown">
+ <a href="#" class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;"><%= message('more_actions') -%></a>
+ <ul style="display: none" class="dropdown-menu">
+ <% if Java::OrgSonarServerUser::UserSession.get().hasProjectPermission('issueadmin', issue.projectKey) %>
+ <% unless issue.resolution %>
+ <li>
+ <a href="#" onclick="return issueForm('severity', this)" class="link-action spacer-right"><%= message("issue.set_severity") -%></a>
+ </li>
+ <% end %>
+ <% end %>
- <%
- plugin_actions = Internal.issues.listActions(issue)
- shouldDisplayDropDown = transitions.size > 1 || !issue.resolution || !plugin_actions.empty?
- if shouldDisplayDropDown
- %>
- <div class="dropdown">
- <a href="#" class="link-action link-more" onclick="showDropdownMenuOnElement($j(this).next('.dropdown-menu')); return false;"><%= message('more_actions') -%></a>
- <ul style="display: none" class="dropdown-menu">
- <% if Java::OrgSonarServerUser::UserSession.get().hasProjectPermission('issueadmin', issue.projectKey) %>
- <% unless issue.resolution %>
+ <% # Display actions defined by plugins
+ plugin_actions.each do |action| %>
<li>
- <a href="#" onclick="return issueForm('severity', this)" class="link-action spacer-right"><%= message("issue.set_severity") -%></a>
+ <a href="#" onclick="return doPluginIssueAction(this, '<%= action.key -%>')" class="link-action spacer-right"><%= message("issue.action.#{action.key}.formlink") -%></a>
</li>
<% end %>
- <% end %>
+ </ul>
+ </div>
+ </li>
+ <% end %>
+ <% end %>
+ <% if issue.technicalDebt %>
+ <li><%= message('issue.debt') -%>&nbsp;<%= Internal.technical_debt.format(issue.technicalDebt) -%></li>
+ <% end %>
+ <% if issue.authorLogin %>
+ <li><%= message('issue.authorLogin') -%>&nbsp;<%= issue.authorLogin -%></li>
+ <% end %>
+ <% if issue.reporter %>
+ <li><%= message('issue.reported_by') -%>&nbsp;<%= @issue_results.user(issue.reporter).name -%></li>
+ <% end %>
+ </ul>
+ <div class="code-issue-form hidden"></div>
- <% # Display remaining transitions
- if transitions.size > 1
- transitions[1..-1].each do |transition| %>
- <li>
- <a href="#" onclick="return doIssueTransition(this, '<%= transition.key -%>')" class="link-action spacer-right"><%= message("issue.transition.#{transition.key}") -%></a>
- </li>
- <% end
- end %>
+ <div class="code-issue-details">
+ <ul class="tabs">
+ <li>
+ <a href="#tab-issue-rule"><%= message('rule') -%></a>
+ </li>
+ <li>
+ <a href="#tab-issue-changelog"><%= message('changelog') -%></a>
+ </li>
+ </ul>
- <% # Display actions defined by plugins
- plugin_actions.each do |action| %>
- <li>
- <a href="#" onclick="return doPluginIssueAction(this, '<%= action.key -%>')" class="link-action spacer-right"><%= message("issue.action.#{action.key}.formlink") -%></a>
- </li>
- <% end %>
- </ul>
- </div>
- <% end %>
+ <div id="tab-issue-rule">
+ <%= image_tag 'loading.gif', :class => 'rule-loading hidden' -%>
+ <div class="issue-rule rule-desc"></div>
</div>
- <div class="code-issue-form hidden"></div>
- <% elsif issue.assignee || issue.actionPlanKey %>
- <div class="code-issue-actions">
- <% if issue.assignee %>
- <span class="gray"><%= message('assigned_to') -%> <%= h @issue_results.user(issue.assignee).name -%></span>
- &nbsp;
- <% end %>
- <% if issue.actionPlanKey %>
- <% if issue.assignee %><img src="<%= ApplicationController.root_context -%>/images/sep12.png"/>&nbsp;<% end %>
- <span class="gray"><%= message('issue.planned_for') -%> <%= h(@issue_results.actionPlan(issue).name()) -%></span>
- <% end %>
+
+ <div id="tab-issue-changelog">
+ <%= image_tag 'loading.gif', :class => 'changelog-loading hidden' -%>
+ <table class="issue-changelog spaced">
+ </table>
</div>
- <% end %>
+ </div>
+
+ <div class="code-issue-comments">
+ <% issue.comments.each do |comment|
+ comment_html_id = "comment-#{comment.key}-#{rand(100)}" %>
+ <div class="code-issue-comment" id="<%= comment_html_id -%>" data-comment-key="<%= comment.key -%>">
+ <h4>
+ <%= image_tag('reviews/comment.png') -%> &nbsp;<b><%= @issue_results.user(comment.userLogin()).name() -%></b>
+ (<%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(comment.createdAt)) -%>)
+ <% if current_user && current_user.login==comment.userLogin %>
+ &nbsp;
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+ <a class="link-action" href="#" onclick="return formEditIssueComment(this)"><%= message('edit') -%></a>
+ <a class="link-action spacer-right" href="#" onclick="return deleteIssueComment(this, '<%= escape_javascript(message('issue.comment.delete_confirm_message')) -%>')"><%= message('delete') -%></a>
+ <% end %>
+ </h4>
+ <%= Internal.text.markdownToHtml(comment.markdownText) -%>
+ </div>
+ <% end %>
+ </div>
</div>
+
+<script>
+ $j('#issue-<%= u issue.key -%> .code-issue-details').tabs();
+ $j('#issue-<%= u issue.key -%> .code-issue-toggle').click(function() {
+ toggleIssueCollapsed(this);
+ });
+</script>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb
index f93e91898f2..19a91902617 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb
@@ -1,3 +1,5 @@
+<h1 class="marginbottom10"><%= h @rule.name %></h1>
+
<div class="marginbottom10">
<% if @rule.description.strip.start_with?('<p>') %>
<%= Internal.text.interpretMacros(@rule.description) %>
@@ -12,8 +14,12 @@
</div>
<% end %>
-<div class="note">
- <%= h @rule.plugin_name -%>
+<p class="note">
+ <%= h @rule.plugin_name -%>:<%= h @rule.plugin_rule_key -%>
&nbsp;<%= image_tag 'sep12.png' -%>&nbsp;
- <a href="#" onclick="return openIssueRulePopup(this)"><%= h @rule.plugin_rule_key -%></a>
-</div>
+ <% if @requirement %>
+ <%= @root_characteristic.name -%>&nbsp;&gt;&nbsp;<%= @characteristic.name -%>
+ <% else %>
+ <%= message 'issue.technical_debt_deleted' %>
+ <% end %>
+</p>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_technicaldebt.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_technicaldebt.html.erb
deleted file mode 100644
index 09078b49363..00000000000
--- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_technicaldebt.html.erb
+++ /dev/null
@@ -1,8 +0,0 @@
-<div>
- <% if @requirement %>
- <%= @root_characteristic.name %>&nbsp;&gt;&nbsp;<%= @characteristic.name %>
- <% else %>
- <%= message 'issue.technical_debt_deleted' %>
- <% end %>
-</div>
-
diff --git a/sonar-server/src/main/webapp/javascripts/issue.js b/sonar-server/src/main/webapp/javascripts/issue.js
index e38027e80dc..3a91c7401ba 100644
--- a/sonar-server/src/main/webapp/javascripts/issue.js
+++ b/sonar-server/src/main/webapp/javascripts/issue.js
@@ -203,67 +203,45 @@ function submitCreateIssueForm(elt) {
return false;
}
-function toggleIssueRule(elt) {
+function toggleIssueCollapsed(elt) {
var issueElt = $j(elt).closest('[data-issue-rule]');
- var ruleElt = issueElt.find('.issue-rule');
- if (ruleElt.is(':visible')) {
- ruleElt.slideUp('fast');
- } else {
- issueElt.find('.issue-changelog').slideUp('fast');
- issueElt.find('.issue-technicaldebt').slideUp('fast');
+ issueElt.toggleClass('code-issue-collapsed');
+
+ if (!issueElt.hasClass('code-issue-collapsed')) {
+
+ // Load rule desc
+ // Display loading images and hide existing content
+ var ruleLoading = issueElt.find('.rule-loading');
+ ruleLoading.removeClass('hidden');
+ var ruleElt = issueElt.find('.issue-rule');
+ ruleElt.addClass('hidden');
var ruleKey = issueElt.attr('data-issue-rule');
$j.get(baseUrl + "/issue/rule/" + ruleKey, function (html) {
ruleElt.html(html);
- ruleElt.slideDown('fast');
-
// re-enable the links opening modal popups
ruleElt.find('.open-modal').modal();
+ }).always(function () {
+ ruleLoading.addClass('hidden');
+ ruleElt.removeClass('hidden');
});
- }
- return false;
-}
-function toggleIssueChangelog(elt) {
- var issueElt = $j(elt).closest('[data-issue-key]');
- var changelogElt = issueElt.find('.issue-changelog');
- if (changelogElt.is(':visible')) {
- changelogElt.slideUp('fast');
- } else {
- issueElt.find('.issue-rule').slideUp('fast');
- issueElt.find('.issue-technicaldebt').slideUp('fast');
+ // Load changelog
+ // Display loading images and hide existing content
+ var cangelogLoading = issueElt.find('.changelog-loading');
+ cangelogLoading.removeClass('hidden');
+ var changelogElt = issueElt.find('.issue-changelog');
+ changelogElt.addClass('hidden');
var issueKey = issueElt.attr('data-issue-key');
$j.get(baseUrl + "/issue/changelog/" + issueKey, function (html) {
changelogElt.html(html);
- changelogElt.slideDown('fast');
- });
- }
- return false;
-}
-
-function toggleTechnicalDebt(elt) {
- var issueElt = $j(elt).closest('[data-issue-key]');
- var debtElt = issueElt.find('.issue-technicaldebt');
- if (debtElt.is(':visible')) {
- debtElt.slideUp('fast');
- } else {
- issueElt.find('.issue-changelog').slideUp('fast');
- issueElt.find('.issue-rule').slideUp('fast');
- var issueKey = issueElt.attr('data-issue-key');
- $j.get(baseUrl + "/issue/technicaldebt/" + issueKey, function (html) {
- debtElt.html(html);
- debtElt.slideDown('fast');
+ }).always(function () {
+ cangelogLoading.addClass('hidden');
+ changelogElt.removeClass('hidden');
});
}
return false;
}
-function openIssueRulePopup(elt) {
- var issueElt = $j(elt).closest('[data-issue-rule]');
- var ruleKey = issueElt.attr('data-issue-rule');
- openPopup(baseUrl + "/rules/show/" + ruleKey + "?layout=false", 'rule');
- return false;
-}
-
function openIssuePopup(elt) {
var issueElt = $j(elt).closest('[data-issue-key]');
var issueKey = issueElt.attr('data-issue-key');
diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css
index 2edc06c0cb0..84b5ba0a23e 100644
--- a/sonar-server/src/main/webapp/stylesheets/style.css
+++ b/sonar-server/src/main/webapp/stylesheets/style.css
@@ -814,6 +814,7 @@ th.operations, td.operations {
line-height: 16px;
color: #777;
border: 1px solid #DDD;
+ border-bottom: medium none;
}
.code-issue-msg {
@@ -854,7 +855,7 @@ th.operations, td.operations {
top: 5px; right: 26px;
}
-.code-issue-comment, .code-issue-msg, .code-issue-actions, .code-issue-form, .issue-rule, .issue-changelog, .issue-technicaldebt {
+.code-issue-comment, .code-issue-msg, .code-issue-actions, .code-issue-form {
background-color: #EFEFEF;
border: 1px solid #DDD;
border-top: none;
@@ -944,6 +945,7 @@ th.operations, td.operations {
.code-issue-list {
font-size: 0;
white-space: nowrap;
+ background-color: #E4ECF3;
}
.code-issue-list > li {
diff --git a/sonar-server/src/test/java/org/sonar/server/technicaldebt/DebtServiceTest.java b/sonar-server/src/test/java/org/sonar/server/technicaldebt/DebtServiceTest.java
index b0a1cd61cb2..586444b6f5e 100644
--- a/sonar-server/src/test/java/org/sonar/server/technicaldebt/DebtServiceTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/technicaldebt/DebtServiceTest.java
@@ -21,7 +21,6 @@ package org.sonar.server.technicaldebt;
import org.junit.Test;
import org.sonar.api.issue.internal.WorkDayDuration;
-import org.sonar.api.rules.Rule;
import org.sonar.api.technicaldebt.server.Characteristic;
import org.sonar.api.technicaldebt.server.internal.DefaultCharacteristic;
import org.sonar.core.technicaldebt.DefaultTechnicalDebtManager;
@@ -61,11 +60,9 @@ public class DebtServiceTest {
}
@Test
- public void find_requirement() {
- Rule rule = Rule.create("repo", "key");
- Characteristic requirement = new DefaultCharacteristic();
- when(finder.findRequirementByRule(rule)).thenReturn(requirement);
- assertThat(service.findRequirement(rule)).isEqualTo(requirement);
+ public void find_requirement_by_rule_id() {
+ service.findRequirementByRuleId(1);
+ verify(finder).findRequirementByRuleId(1);
}
@Test