]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 refactor rendering of issues
authorSimon Brandhof <simon.brandhof@gmail.com>
Sun, 2 Jun 2013 22:18:15 +0000 (00:18 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Sun, 2 Jun 2013 22:18:31 +0000 (00:18 +0200)
25 files changed:
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeDao.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueChangeMapper.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueChangeMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/db/IssueChangeDaoTest.java
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueChangeService.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java [deleted file]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/issue_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_changelog.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_issue.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_manual_issue_created.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_rule.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show_modal.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/_view.html.erb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/show.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/issue/view.html.erb [deleted file]
sonar-server/src/main/webapp/WEB-INF/app/views/issues/_list.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/shared/_source_display.erb
sonar-server/src/main/webapp/javascripts/issue.js
sonar-server/src/main/webapp/stylesheets/jquery-ui.css
sonar-server/src/main/webapp/stylesheets/layout.css
sonar-server/src/main/webapp/stylesheets/style.css
sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java

index e6aee9a21b99d55f296003a273ec7ce9dc1674eb..c06f5d61b8e8199ee68b1ea25768cb64b7695d79 100644 (file)
@@ -48,6 +48,17 @@ public class IssueChangeDao implements BatchComponent, ServerComponent {
     return selectByIssuesAndType(session, issueKeys, IssueChangeDto.TYPE_COMMENT);
   }
 
+  public List<IssueChangeDto> selectIssueChangelog(String issueKey) {
+    SqlSession session = mybatis.openSession();
+    try {
+      IssueChangeMapper mapper = session.getMapper(IssueChangeMapper.class);
+      return mapper.selectByIssue(issueKey);
+
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
   @CheckForNull
   public DefaultIssueComment selectCommentByKey(String commentKey) {
     SqlSession session = mybatis.openSession();
index d778e70b6b8c83ff54db98cef13978bce6f17be0..b9c85f78fb130fba8a739093fd17b62310c4d205 100644 (file)
@@ -45,4 +45,6 @@ public interface IssueChangeMapper {
    */
   List<IssueChangeDto> selectByIssuesAndType(@Param("issueKeys") Collection<String> issueKeys,
                                              @Param("changeType") String changeType);
+
+  List<IssueChangeDto> selectByIssue(String issueKey);
 }
index ac4ca892f1099e9f4de1a4b225acfd58afdecac6..83cd957bd93330a64997e546d2e30dd9b53836e7 100644 (file)
     from issue_changes c
     where c.change_type=#{changeType} and c.kee=#{key}
   </select>
+
+  <select id="selectByIssue" parameterType="string" resultType="IssueChange">
+    select
+    <include refid="issueChangeColumns"/>
+    from issue_changes c
+    where c.issue_key=#{id}
+    order by created_at asc
+  </select>
 </mapper>
 
index 89a901ef396472f254da53ecf5966324bad19034..21a3f64bd34a462042fe5291325761b958659637 100644 (file)
@@ -62,6 +62,19 @@ public class IssueChangeDaoTest extends AbstractDaoTestCase {
     assertThat(second.markdownText()).isEqualTo("recent comment");
   }
 
+  @Test
+  public void selectIssueChangelog() {
+    setupData("shared");
+
+    List<IssueChangeDto> changes = dao.selectIssueChangelog("1000");
+    assertThat(changes).hasSize(3);
+
+    // chronological order
+    assertThat(changes.get(0).getId()).isEqualTo(100);
+    assertThat(changes.get(1).getId()).isEqualTo(101);
+    assertThat(changes.get(2).getId()).isEqualTo(102);
+  }
+
   @Test
   public void selectCommentsByIssues_empty_input() {
     // no need to connect to db
index 8588fd26eae567218c0776b8408fb772f44f5e3d..4944d000b081e096be943406b716af3dd63b36b8 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.api.utils.SonarException;
 import org.sonar.core.issue.ActionPlanStats;
 import org.sonar.core.issue.DefaultActionPlan;
 import org.sonar.core.issue.DefaultIssueBuilder;
+import org.sonar.core.issue.db.IssueChangeDto;
 import org.sonar.core.issue.workflow.Transition;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
@@ -55,18 +56,18 @@ import java.util.Map;
 public class InternalRubyIssueService implements ServerComponent {
 
   private final IssueService issueService;
-  private final IssueCommentService commentService;
+  private final IssueChangeService changeService;
   private final ActionPlanService actionPlanService;
   private final IssueStatsFinder issueStatsFinder;
   private final ResourceDao resourceDao;
   private final ActionService actionService;
 
   public InternalRubyIssueService(IssueService issueService,
-                                  IssueCommentService commentService,
+                                  IssueChangeService changeService,
                                   ActionPlanService actionPlanService,
                                   IssueStatsFinder issueStatsFinder, ResourceDao resourceDao, ActionService actionService) {
     this.issueService = issueService;
-    this.commentService = commentService;
+    this.changeService = changeService;
     this.actionPlanService = actionPlanService;
     this.issueStatsFinder = issueStatsFinder;
     this.resourceDao = resourceDao;
@@ -93,6 +94,11 @@ public class InternalRubyIssueService implements ServerComponent {
     return Issue.RESOLUTIONS;
   }
 
+  public List<IssueChangeDto> changelog(String issueKey) {
+    // TODO verify security
+    return changeService.changelog(issueKey);
+  }
+
   public Issue doTransition(String issueKey, String transitionKey) {
     return issueService.doTransition(issueKey, transitionKey, UserSession.get());
   }
@@ -110,19 +116,19 @@ public class InternalRubyIssueService implements ServerComponent {
   }
 
   public IssueComment addComment(String issueKey, String text) {
-    return commentService.addComment(issueKey, text, UserSession.get());
+    return changeService.addComment(issueKey, text, UserSession.get());
   }
 
   public IssueComment deleteComment(String commentKey) {
-    return commentService.deleteComment(commentKey, UserSession.get());
+    return changeService.deleteComment(commentKey, UserSession.get());
   }
 
   public IssueComment editComment(String commentKey, String newText) {
-    return commentService.editComment(commentKey, newText, UserSession.get());
+    return changeService.editComment(commentKey, newText, UserSession.get());
   }
 
   public IssueComment findComment(String commentKey) {
-    return commentService.findComment(commentKey);
+    return changeService.findComment(commentKey);
   }
 
   /**
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueChangeService.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueChangeService.java
new file mode 100644 (file)
index 0000000..450f35c
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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.server.issue;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.issue.IssueComment;
+import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.issue.IssueQueryResult;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.DefaultIssueComment;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.IssueNotifications;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.db.IssueChangeDao;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueStorage;
+import org.sonar.server.user.UserSession;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class IssueChangeService implements ServerComponent {
+
+  private final IssueUpdater updater;
+  private final IssueChangeDao changeDao;
+  private final IssueStorage storage;
+  private final DefaultIssueFinder finder;
+  private final IssueNotifications issueNotifications;
+
+  public IssueChangeService(IssueUpdater updater, IssueChangeDao changeDao, IssueStorage storage, DefaultIssueFinder finder, IssueNotifications issueNotifications) {
+    this.updater = updater;
+    this.changeDao = changeDao;
+    this.storage = storage;
+    this.finder = finder;
+    this.issueNotifications = issueNotifications;
+  }
+
+  public List<IssueChangeDto> changelog(String issueKey) {
+    // TODO verify security
+    return changeDao.selectIssueChangelog(issueKey);
+  }
+
+  public IssueComment findComment(String commentKey) {
+    return changeDao.selectCommentByKey(commentKey);
+  }
+
+  public IssueComment addComment(String issueKey, String text, UserSession userSession) {
+    verifyLoggedIn(userSession);
+
+    IssueQueryResult queryResult = loadIssue(issueKey);
+    DefaultIssue issue = (DefaultIssue) queryResult.first();
+
+    IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
+    updater.addComment(issue, text, context);
+    storage.save(issue);
+    issueNotifications.sendChanges(issue, context, queryResult, text);
+    return issue.comments().get(issue.comments().size() - 1);
+  }
+
+  public IssueComment deleteComment(String commentKey, UserSession userSession) {
+    DefaultIssueComment comment = changeDao.selectCommentByKey(commentKey);
+    if (comment == null) {
+      // TODO throw 404
+      throw new IllegalStateException();
+    }
+    if (Strings.isNullOrEmpty(comment.userLogin()) || !Objects.equal(comment.userLogin(), userSession.login())) {
+      // TODO throw unauthorized
+      throw new IllegalStateException();
+    }
+
+    // check authorization
+    finder.findByKey(comment.issueKey(), UserRole.USER);
+
+    changeDao.delete(commentKey);
+    return comment;
+  }
+
+  public IssueComment editComment(String commentKey, String text, UserSession userSession) {
+    DefaultIssueComment comment = changeDao.selectCommentByKey(commentKey);
+    if (comment == null) {
+      // TODO throw 404
+      throw new IllegalStateException();
+    }
+    if (Strings.isNullOrEmpty(comment.userLogin()) || !Objects.equal(comment.userLogin(), userSession.login())) {
+      // TODO throw unauthorized
+      throw new IllegalStateException();
+    }
+
+    // check authorization
+    finder.findByKey(comment.issueKey(), UserRole.USER);
+
+    IssueChangeDto dto = IssueChangeDto.of(comment);
+    dto.setUpdatedAt(new Date());
+    dto.setChangeData(text);
+    changeDao.update(dto);
+
+    return comment;
+  }
+
+  private void verifyLoggedIn(UserSession userSession) {
+    if (!userSession.isLoggedIn()) {
+      // must be logged
+      throw new IllegalStateException("User is not logged in");
+    }
+  }
+
+  public IssueQueryResult loadIssue(String issueKey) {
+    IssueQuery query = IssueQuery.builder().issueKeys(Arrays.asList(issueKey)).requiredRole(UserRole.USER).build();
+    return finder.find(query);
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java
deleted file mode 100644 (file)
index d513de5..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.server.issue;
-
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
-import org.sonar.api.ServerComponent;
-import org.sonar.api.issue.IssueComment;
-import org.sonar.api.issue.IssueQuery;
-import org.sonar.api.issue.IssueQueryResult;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.DefaultIssueComment;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.issue.IssueNotifications;
-import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.db.IssueChangeDao;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.server.user.UserSession;
-
-import java.util.Arrays;
-import java.util.Date;
-
-public class IssueCommentService implements ServerComponent {
-
-  private final IssueUpdater updater;
-  private final IssueChangeDao changeDao;
-  private final IssueStorage storage;
-  private final DefaultIssueFinder finder;
-  private final IssueNotifications issueNotifications;
-
-  public IssueCommentService(IssueUpdater updater, IssueChangeDao changeDao, IssueStorage storage, DefaultIssueFinder finder, IssueNotifications issueNotifications) {
-    this.updater = updater;
-    this.changeDao = changeDao;
-    this.storage = storage;
-    this.finder = finder;
-    this.issueNotifications = issueNotifications;
-  }
-
-  public IssueComment findComment(String commentKey) {
-    return changeDao.selectCommentByKey(commentKey);
-  }
-
-  public IssueComment addComment(String issueKey, String text, UserSession userSession) {
-    verifyLoggedIn(userSession);
-
-    IssueQueryResult queryResult = loadIssue(issueKey);
-    DefaultIssue issue = (DefaultIssue) queryResult.first();
-
-    IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.login());
-    updater.addComment(issue, text, context);
-    storage.save(issue);
-    issueNotifications.sendChanges(issue, context, queryResult, text);
-    return issue.comments().get(issue.comments().size() - 1);
-  }
-
-  public IssueComment deleteComment(String commentKey, UserSession userSession) {
-    DefaultIssueComment comment = changeDao.selectCommentByKey(commentKey);
-    if (comment == null) {
-      // TODO throw 404
-      throw new IllegalStateException();
-    }
-    if (Strings.isNullOrEmpty(comment.userLogin()) || !Objects.equal(comment.userLogin(), userSession.login())) {
-      // TODO throw unauthorized
-      throw new IllegalStateException();
-    }
-
-    // check authorization
-    finder.findByKey(comment.issueKey(), UserRole.USER);
-
-    changeDao.delete(commentKey);
-    return comment;
-  }
-
-  public IssueComment editComment(String commentKey, String text, UserSession userSession) {
-    DefaultIssueComment comment = changeDao.selectCommentByKey(commentKey);
-    if (comment == null) {
-      // TODO throw 404
-      throw new IllegalStateException();
-    }
-    if (Strings.isNullOrEmpty(comment.userLogin()) || !Objects.equal(comment.userLogin(), userSession.login())) {
-      // TODO throw unauthorized
-      throw new IllegalStateException();
-    }
-
-    // check authorization
-    finder.findByKey(comment.issueKey(), UserRole.USER);
-
-    IssueChangeDto dto = IssueChangeDto.of(comment);
-    dto.setUpdatedAt(new Date());
-    dto.setChangeData(text);
-    changeDao.update(dto);
-
-    return comment;
-  }
-
-  private void verifyLoggedIn(UserSession userSession) {
-    if (!userSession.isLoggedIn()) {
-      // must be logged
-      throw new IllegalStateException("User is not logged in");
-    }
-  }
-
-  public IssueQueryResult loadIssue(String issueKey) {
-    IssueQuery query = IssueQuery.builder().issueKeys(Arrays.asList(issueKey)).requiredRole(UserRole.USER).build();
-    return finder.find(query);
-  }
-}
index 869e3b9aaf1900734a858e369a32d946803c0bcd..47225cd44a6ac2ae683240d46c3c6564acefb8f9 100644 (file)
@@ -262,7 +262,7 @@ public final class Platform {
     servicesContainer.addSingleton(FunctionExecutor.class);
     servicesContainer.addSingleton(IssueWorkflow.class);
     servicesContainer.addSingleton(IssueService.class);
-    servicesContainer.addSingleton(IssueCommentService.class);
+    servicesContainer.addSingleton(IssueChangeService.class);
     servicesContainer.addSingleton(DefaultIssueFinder.class);
     servicesContainer.addSingleton(IssueStatsFinder.class);
     servicesContainer.addSingleton(PublicRubyIssueService.class);
index 8d38916322ed189d46b02383a120651cc054acc6..fe6e05e03970f8a5a3be6a5b23da8f7dc497fc26 100644 (file)
@@ -22,17 +22,6 @@ class IssueController < ApplicationController
 
   helper SourceHelper
 
-  def view
-    require_parameters :id
-    init_issue
-
-    if request.xhr?
-      render :partial => 'issue/view', :locals => {:issue => @issue, :issue_results => @issue_results, :snapshot =>  @snapshot, :show_source => true}
-    else
-      render :action => 'view'
-    end
-  end
-
   # GET /issue/show/<key>
   # This URL is used by the Eclipse Plugin
   #
@@ -46,7 +35,15 @@ class IssueController < ApplicationController
   def show
     require_parameters :id
     init_issue
-    render :action => 'view'
+
+    if params[:modal]
+      render :partial => 'issue/show_modal'
+    elsif request.xhr?
+      render :partial => 'issue/show'
+    else
+      render :action => 'show'
+    end
+
   end
 
   # Form used for: assign, comment, transition, change severity and plan
@@ -153,7 +150,7 @@ class IssueController < ApplicationController
     issue_result = Internal.issues.create(params.merge({:component => component_key}))
     if issue_result.ok
       @issue_results = Api.issues.find(issue_result.get.key)
-      render :partial => 'issue/issue', :locals => {:issue => @issue_results.issues.get(0)}
+      render :partial => 'issue/manual_issue_created', :locals => {:issue => @issue_results.first}
     else
       render :partial => 'shared/result_messages', :status => 500, :locals => {:result => issue_result}
     end
@@ -172,12 +169,31 @@ class IssueController < ApplicationController
     render :partial => 'project/widgets/issues/issues_list'
   end
 
+  # Display the rule description in the issue panel
+  def rule
+    verify_ajax_request
+    require_parameters :id
+    rule_key = params[:id].split(':')
+    @rule = Rule.first(:conditions => ['plugin_name=? and plugin_rule_key=?', rule_key[0], rule_key[1]], :include => :rule_note)
+    render :partial => 'issue/rule'
+  end
+
+  # Display the changelog in the issue panel
+  def changelog
+    verify_ajax_request
+    require_parameters :id
+    @issue_results = Api.issues.find(params[:id])
+    @issue = @issue_results.first()
+    @changes = Internal.issues.changelog(params[:id])
+    render :partial => 'issue/changelog'
+  end
+
 
   private
 
   def init_issue
     @issue_results = Api.issues.find(params[:id])
-    @issue = @issue_results.issues.get(0)
+    @issue = @issue_results.first()
 
     resource = Project.by_key(@issue.componentKey())
     @snapshot = resource.last_snapshot if resource.last_snapshot
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_changelog.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_changelog.html.erb
new file mode 100644 (file)
index 0000000..cdff4e0
--- /dev/null
@@ -0,0 +1,19 @@
+<ul>
+  <li>
+    <%= format_datetime(@issue.creationDate()) -%>
+    <% if @issue.reporter %>
+    - <%= message('issue.reported_by') -%> <%= @issue_results.user(@issue.reporter).name -%>
+    <% end %>
+    - <%= message('created') -%>
+  </li>
+
+  <% @changes.each do |change| %>
+  <li>
+    <%= format_datetime(change.createdAt()) -%> - <%= change.userLogin -%> - <%= change.changeData -%>
+  </li>
+  <% end %>
+</ul>
+
+<div class="note">
+  <a href="#" onclick="return hideIssueMore(this)">Hide</a><%= image_tag 'asc.png' -%>
+</div>
\ No newline at end of file
index 823d19a95cc6032e07b5709391c09ad2956eb26e..ec1de4dc521bd488d9f8698bbc3a5df22266ff80 100644 (file)
@@ -1,12 +1,15 @@
-<div class="code-issue" data-issue-key="<%= issue.key -%>" data-issue-component="<%= issue.componentKey() -%>">
+<div class="code-issue" data-issue-key="<%= issue.key -%>" data-issue-component="<%= issue.componentKey() -%>" data-issue-rule="<%= issue.ruleKey().toString() -%>">
   <div class="code-issue-name">
-    <img src="<%= ApplicationController.root_context -%>/images/priority/<%= issue.severity -%>.png">
+    <div style="float: right">
+      <a target="issue"
+         href="<%= url_for :controller => 'issue', :action => 'show', :id => issue.key -%>">
+        <img src="<%= ApplicationController.root_context -%>/images/new-window-16.gif">
+      </a>
+    </div>
+
+    <img src="<%= ApplicationController.root_context -%>/images/priority/<%= issue.severity -%>.png" title="<%= h message("severity.#{issue.severity}") -%>">
     &nbsp;
-      <span class="rulename">
-        <% rule_name = Internal.rules.ruleL10nName(@issue_results.rule(issue)) %>
-        <a class="open-modal" modal-width="800" href="<%= url_for :controller => 'rules', :action => 'show', :id => issue.rule_key.to_s, :modal => 'true', :layout => 'false' -%>">
-          <%= h rule_name -%></a>
-      </span>
+    <a href="#" onclick="return showIssueRule(this)" class="rulename"><%= h Internal.rules.ruleL10nName(@issue_results.rule(issue)) -%></a>
     &nbsp;
     <% if issue.resolution %>
       <%= image_tag 'sep12.png' -%>
     <% end %>
     <%= image_tag 'sep12.png' -%>
     &nbsp;
-    <%
-       created_at = Api::Utils.java_to_ruby_datetime(issue.creationDate())
-       updated_at = Api::Utils.java_to_ruby_datetime(issue.updateDate())
-       dates_title = "Created at #{format_datetime(created_at)} and updated at #{format_datetime(updated_at)}"
-    %>
-    <span title="<%= h dates_title -%>"><%= distance_of_time_in_words_to_now(created_at) -%></span>
+    <a href="#" onclick="return showIssueChangelog(this)" class="gray"><%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(issue.creationDate())) -%></a>
     &nbsp;
     <% if issue.reporter %>
       <%= image_tag 'sep12.png' -%>
@@ -36,6 +34,8 @@
     <% end %>
   </div>
 
+  <div class="issue-more" style="display: none"></div>
+
   <% unless issue.message.blank? %>
     <div class="code-issue-msg">
       <%= Api::Utils.split_newlines(h(issue.message)).join('<br/>') -%>
         <%= 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 formDeleteIssueComment(this)"><%= message('delete') -%></a>
+          &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 formDeleteIssueComment(this)"><%= message('delete') -%></a>
         <% end %>
       </h4>
       <%= Internal.text.markdownToHtml(comment.markdownText) -%>
   <% end %>
 
   <% if current_user %>
-    <% transitions = Internal.issues.listTransitions(issue) %>
-
     <div class="code-issue-actions">
       <a href='#' onclick="return issueForm('comment', this)" class="link-action spacer-right"><%= message('issue.comment.formlink') -%></a>
       <% unless issue.resolution %>
         <%= image_tag 'sep12.png' -%>
         &nbsp;
         <span class="spacer-right">
-        <% if !issue.assignee %>
+        <% 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 %>
           <a href='#' onclick="return issueForm('assign', this)" class="link-action"><%= message('issue.assign.formlink') -%></a>
-          <% if issue.assignee!=current_user.login %>
+            <% if issue.assignee!=current_user.login %>
             [<a href="#" onclick="return assignIssueToMe(this)" class="link-action"><%= message('issue.assign.to_me') -%></a>]
-          <% end %>
-        <% else %>
-          <a href='#' onclick="return issueForm('assign', this)" class="link-action"><%= message('assigned_to') -%></a> <%= @issue_results.user(issue.assignee).name -%>
+            <% end %>
         <% end %>
         </span>
       <% end %>
         <%= image_tag 'sep12.png' -%>
         &nbsp;
         <span class="spacer-right">
-          <% if !@issue_results.actionPlan(issue) %>
-            <a href="#" onclick="return issueForm('plan', this)" class="link-action"><%= message('issue.do_plan') -%></a>
-          <% else %>
+          <% 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)
          if transitions.size > 0 && transitions.first
-         transition = transitions.first
+           transition = transitions.first
       %>
         <%= image_tag 'sep12.png' -%>
         &nbsp;
           <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">
             <% unless issue.resolution %>
-              <li><a href="#" onclick="return issueForm('severity', this)" class="link-action spacer-right"><%= message("issue.set_severity") -%></a></li>
+              <li>
+                <a href="#" onclick="return issueForm('severity', this)" class="link-action spacer-right"><%= message("issue.set_severity") -%></a>
+              </li>
             <% end %>
             <% transitions.each do |transition| %>
-              <li><a href="#" onclick="return doIssueTransition(this, '<%= transition.key -%>')" class="link-action spacer-right"><%= message("issue.transition.#{transition.key}") -%></a></li>
+              <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 %>
     </div>
     <div class="code-issue-form hidden"></div>
+  <% elsif issue.assignee || issue.actionPlanKey %>
+    <div class="code-issue-actions">
+      <% if issue.assignee %>
+        <%= message('assigned_to') -%> <%= h @issue_results.user(issue.assignee).name -%>
+        &nbsp;
+      <% end %>
+      <% if issue.actionPlanKey %>
+        <%= message('issue.planned_for') -%> <%= h(@issue_results.actionPlan(issue).name()) -%>
+      <% end %>
+    </div>
   <% end %>
 </div>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_manual_issue_created.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_manual_issue_created.html.erb
new file mode 100644 (file)
index 0000000..60c05dc
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="code-issues">
+  <%= render :partial => 'issue/issue', :locals => {:issue => issue} -%>
+</div>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..100d2b2
--- /dev/null
@@ -0,0 +1,24 @@
+<div class="marginbottom10">
+  <% if @rule.description.strip.start_with?('<p>') %>
+    <%= Internal.text.interpretMacros(@rule.description) %>
+  <% else %>
+    <p><%= Internal.text.interpretMacros(@rule.description) %></p>
+  <% end %>
+</div>
+
+<% if @rule.note %>
+  <div class="marginbottom10">
+    <%= @rule.note.html_text -%>
+  </div>
+<% end %>
+
+<div class="note">
+  <a href="#" onclick="return hideIssueMore(this)">Hide</a><%= image_tag 'asc.png' -%>
+  &nbsp;<%= image_tag 'sep12.png' -%>&nbsp;
+  <%= @rule.plugin_name -%>
+  &nbsp;<%= image_tag 'sep12.png' -%>&nbsp;
+  <a onclick="window.open(this.href,'rule','height=800,width=900,scrollbars=1,resizable=1');return false;"
+     href="<%= url_for :controller => 'rules', :action => 'show', :id => @rule.key, :layout => 'false' -%>">
+    <%= @rule.plugin_rule_key -%>
+  </a>
+</div>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show.html.erb
new file mode 100644 (file)
index 0000000..432e091
--- /dev/null
@@ -0,0 +1,16 @@
+<div class="source_title">
+  <div class="subtitle">
+    <%= h @issue_results.project(@issue).name() -%>
+  </div>
+  <span class="h1"><%= h @issue_results.component(@issue).longName() -%></span>
+</div>
+
+<div class="marginbottom10">
+<%= render :partial => 'issue/issue', :locals => {:issue => @issue_results.first} -%>
+</div>
+
+<% if @snapshot && @issue.line && params[:source]!='false' %>
+  <div class="bordered">
+    <%= snapshot_html_source(@snapshot, {:line_range => (@issue.line-5)..(@issue.line+5), :highlighted_lines => [@issue.line]}) -%>
+  </div>
+<% end %>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show_modal.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_show_modal.html.erb
new file mode 100644 (file)
index 0000000..3f7f25b
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="modal-body">
+  <%= render :partial => 'issue/show' -%>
+</div>
+<div class="modal-foot">
+  <input type="button" value="<%= h message('close') -%>" onclick="return closeModalWindow()">
+</div>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_view.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_view.html.erb
deleted file mode 100644 (file)
index 27a6ec1..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<div id="content">
-  <div style="border: 1px solid #DDDDDD">
-    <div class="violations_header tab_header">
-      <h3>Issue #<%= issue.key %></h3>
-      <h3><%= h issue_results.project(issue).name -%></h3>
-      <h3><%= h issue_results.component(issue).name -%></h3>
-    </div>
-
-    <div class="marginbottom10 issue-content">
-      <%= render :partial => 'issue/issue', :locals => {:issue => issue_results.issues.get(0)} -%>
-    </div>
-
-    <% if snapshot && issue.line && show_source %>
-    <div>
-      <%= snapshot_html_source(snapshot, {:line_range => (issue.line-5)..(issue.line+5), :highlighted_lines => [issue.line]}) -%>
-    </div>
-    <% end %>
-  </div>
-</div>
-
-<script>
-  // Apply modal function on the rule link
-  $j('.issue-content .open-modal').modal();
-</script>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/show.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/show.html.erb
new file mode 100644 (file)
index 0000000..bd91dd4
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="content">
+  <%= render :partial => 'issue/show' -%>
+</div>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/view.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/view.html.erb
deleted file mode 100644 (file)
index 1204421..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<div>
-  <%= render :partial => 'issue/view', :locals => {:issue => @issue, :issue_results => @issue_results, :snapshot =>  @snapshot, :show_source => params[:source]!='false'} -%>
-</div>
index 5af382f732fece105721b9be3e15ae9a67823252..1c37bd8933e84d588d57a50d114b1de9493481e4 100644 (file)
@@ -72,7 +72,7 @@
             <%= message("issue.resolution.#{issue.resolution}") if issue.resolution -%>
           </td>
           <td>
-            <a class='open-modal rule-modal' modal-width='800' href='<%= url_for :controller => 'issue', :action => 'view', :id => issue.key %>'>
+            <a class='open-modal rule-modal' modal-width='800' href='<%= url_for :controller => 'issue', :action => 'show', :id => issue.key, :modal => true -%>'>
               <%= h truncate(issue.message, :length => 100) -%></a>
           </td>
           <td>
index 938111690045be18425c05857be8aa5075428e58..43ab11dd10c581280d150abd931f987237093087 100644 (file)
@@ -2,7 +2,7 @@
    current_display_id = "sources_#{rand(100)}"
 %>
 
-<table id="<%= current_display_id %>" class="sources2 code" cellpadding="0" cellspacing="0" border="0">
+<table id="<%= current_display_id %>" class="sources2 code" cellpadding="0" cellspacing="0">
 
     <script>
       $j("#<%= current_display_id %>").on("click", "span.sym", {id: "<%= current_display_id.to_s() %>"}, highlight_usages);
index e4db720071638e4b2f9f7d5f9f01d90a035cae52..3da0135513c163fcd7c5525cde8beb9cf6ec18a9 100644 (file)
@@ -201,3 +201,33 @@ function submitCreateIssueForm(elt) {
   return false;
 }
 
+function hideIssueMore(elt) {
+  var issueElt = $j(elt).closest('[data-issue-key]');
+  var moreElt = issueElt.find('.issue-more');
+  moreElt.slideUp('fast');
+  return false;
+}
+
+function showIssueRule(elt) {
+  var issueElt = $j(elt).closest('[data-issue-rule]');
+  var ruleKey = issueElt.attr('data-issue-rule');
+  var moreElt = issueElt.find('.issue-more');
+  moreElt.slideUp('fast');
+  $j.get(baseUrl + "/issue/rule/" + ruleKey, function (html) {
+    moreElt.html(html);
+    moreElt.slideDown('fast');
+  });
+  return false;
+}
+
+function showIssueChangelog(elt) {
+  var issueElt = $j(elt).closest('[data-issue-key]');
+  var issueKey = issueElt.attr('data-issue-key');
+  var moreElt = issueElt.find('.issue-more');
+  moreElt.slideUp('fast');
+  $j.get(baseUrl + "/issue/changelog/" + issueKey, function (html) {
+    moreElt.html(html);
+    moreElt.slideDown('fast');
+  });
+  return false;
+}
\ No newline at end of file
index fee2e781017fae64458c8857508901f6bc3b5269..43807ea735e418ede42853d37cc0e7c7c799e57a 100755 (executable)
   color: #333333;
 }
 
+/*
 .ui-widget-content a {
   color: #333333;
 }
+*/
 
 .ui-widget-header {
   border: 1px solid #e78f08;
index 4aee93245dd18e7918f431a92e73266c5753e2ca..5d34b77e010b07094fae5e1495ae5b07630fe198 100644 (file)
@@ -257,10 +257,6 @@ ul.sidebar select, ul.sidebar input {
   display: block;
 }
 
-.nolayout {
-  padding: 10px;
-}
-
 .page-split-left {
   min-width: 200px;
   max-width: 200px;
index b708c1c9bc20d58632efd755ada973fdb30a9834..0234e55ba4f98d60649a6e6486e359f09611aa37 100644 (file)
@@ -661,7 +661,7 @@ ul.operations li img {
 /* SOURCE */
 .sources2 {
   width: 100%;
-  border-bottom: 1px solid #DDD;
+  border: 0;
   margin: 0;
   background-color: #FFF;
 }
@@ -737,12 +737,12 @@ ul.operations li img {
   font-size: 12px;
 }
 
-span.rulename, span.rulename a {
+.rulename, .rulename a {
   color: #444;
   font-weight: bold;
 }
 
-span.rulename a:hover {
+.rulename a:hover {
   text-decoration: underline;
 }
 
@@ -822,7 +822,7 @@ span.rulename a:hover {
 }
 
 .code-issue-create-form {
-  padding-bottom: 10px;
+  padding: 10px;
 }
 
 .code-global-issues {
@@ -831,14 +831,13 @@ span.rulename a:hover {
 
 .code-issues {
   background-color: #FFF;
-  padding-bottom: 10px;
+  padding: 10px;
 }
 
 .code-issue {
   background-color: #FFF;
   margin: 0;
   font-size: 12px;
-  padding: 10px 10px 0 10px;
 }
 
 .code-issue-name {
@@ -854,7 +853,7 @@ span.rulename a:hover {
   vertical-align: text-bottom;
 }
 
-.code-issue-comment, .code-issue-msg, .code-issue-actions, .code-issue-form {
+.code-issue-comment, .code-issue-msg, .code-issue-actions, .code-issue-form, .issue-more {
   background-color: #EFEFEF;
   border: 1px solid #DDD;
   border-top: none;
@@ -1079,11 +1078,14 @@ div.progress td.open {
 }
 
 div.progress div.note {
-  color: #777777;
+  color: #777;
   font-size: 93%;
   font-weight: normal;
   white-space: nowrap;
 }
+div.note a {
+  color: #777777;
+}
 
 /* SEARCH AUTOCOMPLETE FIELDS */
 #searchInput {
index 77d5eaf84af2c4778c0fd77a9467a2029b9e5fc3..512cc97413eac5b6b1497c324bde14df7285a1a7 100644 (file)
@@ -40,7 +40,7 @@ public class InternalRubyIssueServiceTest {
 
   private InternalRubyIssueService internalRubyIssueService;
   private IssueService issueService = mock(IssueService.class);
-  private IssueCommentService commentService = mock(IssueCommentService.class);
+  private IssueChangeService commentService = mock(IssueChangeService.class);
   private ActionPlanService actionPlanService = mock(ActionPlanService.class);
   private ResourceDao resourceDao = mock(ResourceDao.class);
   private IssueStatsFinder issueStatsFinder = mock(IssueStatsFinder.class);