diff options
15 files changed, 158 insertions, 24 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java index 3a752f8d4d9..36b88fd29c4 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java @@ -85,7 +85,7 @@ public class IssueHandlers implements BatchExtension { @Override public IssueHandler.Context setMessage(@Nullable String s) { - updater.setMessage(issue, s); + updater.setMessage(issue, s, changeContext); return this; } @@ -103,7 +103,7 @@ public class IssueHandlers implements BatchExtension { @Override public IssueHandler.Context setEffortToFix(@Nullable Double d) { - updater.setEffortToFix(issue, d); + updater.setEffortToFix(issue, d, changeContext); return this; } 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 c3db4d5774d..d5444a8b4f0 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 @@ -535,6 +535,7 @@ issue.comment.submit=Comment issue.comment.delete_confirm_title=Delete Comment issue.comment.delete_confirm_message=Do you want to delete this comment? issue.comment.delete_confirm_button=Delete +issue.edit_comment.submit=Edit Comment issue.transition.resolve=Resolve issue.transition.falsepositive=False-Positive issue.transition.reopen=Reopen diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java index 6d2444cb7df..d01c8f5ffb1 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java @@ -23,6 +23,7 @@ import com.google.common.base.Objects; import org.apache.commons.lang.StringUtils; import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; +import org.sonar.api.issue.IssueComment; import javax.annotation.Nullable; @@ -40,6 +41,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(severity, issue.severity())) { issue.setFieldDiff(context, "severity", issue.severity(), severity); issue.setSeverity(severity); + issue.setUpdateDate(context.date()); return true; } return false; @@ -50,6 +52,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { issue.setFieldDiff(context, "severity", issue.severity(), severity); issue.setSeverity(severity); issue.setManualSeverity(true); + issue.setUpdateDate(context.date()); return true; } return false; @@ -60,6 +63,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(sanitizedAssignee, issue.assignee())) { issue.setFieldDiff(context, "assignee", issue.assignee(), sanitizedAssignee); issue.setAssignee(sanitizedAssignee); + issue.setUpdateDate(context.date()); return true; } return false; @@ -77,6 +81,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(resolution, issue.resolution())) { issue.setFieldDiff(context, "resolution", issue.resolution(), resolution); issue.setResolution(resolution); + issue.setUpdateDate(context.date()); return true; } return false; @@ -86,6 +91,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(status, issue.status())) { issue.setFieldDiff(context, "status", issue.status(), status); issue.setStatus(status); + issue.setUpdateDate(context.date()); return true; } return false; @@ -95,20 +101,23 @@ public class IssueUpdater implements BatchComponent, ServerComponent { issue.setAuthorLogin(authorLogin); } - public void setMessage(DefaultIssue issue, @Nullable String s) { + public void setMessage(DefaultIssue issue, @Nullable String s, IssueChangeContext context) { issue.setMessage(s); + issue.setUpdateDate(context.date()); } public void addComment(DefaultIssue issue, String text, IssueChangeContext context) { issue.addComment(DefaultIssueComment.create(issue.key(), context.login(), text)); + issue.setUpdateDate(context.date()); } public void setCloseDate(DefaultIssue issue, @Nullable Date d) { issue.setCloseDate(d); } - public void setEffortToFix(DefaultIssue issue, @Nullable Double d) { + public void setEffortToFix(DefaultIssue issue, @Nullable Double d, IssueChangeContext context) { issue.setEffortToFix(d); + issue.setUpdateDate(context.date()); } public boolean setAttribute(DefaultIssue issue, String key, @Nullable String value, IssueChangeContext context) { @@ -116,6 +125,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(oldValue, value)) { issue.setFieldDiff(context, key, oldValue, value); issue.setAttribute(key, value); + issue.setUpdateDate(context.date()); return true; } return false; @@ -126,6 +136,7 @@ public class IssueUpdater implements BatchComponent, ServerComponent { if (!Objects.equal(sanitizedActionPlanKey, issue.actionPlanKey())) { issue.setFieldDiff(context, "actionPlanKey", issue.actionPlanKey(), sanitizedActionPlanKey); issue.setActionPlanKey(sanitizedActionPlanKey); + issue.setUpdateDate(context.date()); return true; } return false; diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java b/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java index 49cde1f22c1..f1b0af58f17 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/ChangeDtoConverter.java @@ -24,13 +24,13 @@ import org.sonar.core.issue.FieldDiffs; import java.util.Date; -class ChangeDtoConverter { +public class ChangeDtoConverter { private ChangeDtoConverter() { // only static methods } - static IssueChangeDto commentToDto(DefaultIssueComment comment) { + public static IssueChangeDto commentToDto(DefaultIssueComment comment) { IssueChangeDto dto = newDto(comment.issueKey()); dto.setKey(comment.key()); dto.setChangeType(IssueChangeDto.TYPE_COMMENT); @@ -39,7 +39,7 @@ class ChangeDtoConverter { return dto; } - static IssueChangeDto changeToDto(String issueKey, FieldDiffs diffs) { + public static IssueChangeDto changeToDto(String issueKey, FieldDiffs diffs) { IssueChangeDto dto = newDto(issueKey); dto.setChangeType(IssueChangeDto.TYPE_FIELD_CHANGE); dto.setChangeData(diffs.toString()); diff --git a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index 031a65b9440..a7e846688d6 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -96,6 +96,10 @@ public class InternalRubyIssueService implements ServerComponent { return commentService.editComment(commentKey, newText, UserSession.get()); } + public IssueComment findComment(String commentKey) { + return commentService.findComment(commentKey); + } + public Issue create(Map<String, String> parameters) { String componentKey = parameters.get("component"); // TODO verify authorization 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 index ac74a91e476..ad68816b633 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/IssueCommentService.java @@ -28,7 +28,9 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssueComment; import org.sonar.core.issue.IssueChangeContext; import org.sonar.core.issue.IssueUpdater; +import org.sonar.core.issue.db.ChangeDtoConverter; 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.platform.UserSession; @@ -48,6 +50,10 @@ public class IssueCommentService implements ServerComponent { this.finder = finder; } + public IssueComment findComment(String commentKey) { + return changeDao.selectCommentByKey(commentKey); + } + public IssueComment addComment(String issueKey, String text, UserSession userSession) { verifyLoggedIn(userSession); DefaultIssue issue = finder.findByKey(issueKey, UserRole.USER); @@ -90,6 +96,11 @@ public class IssueCommentService implements ServerComponent { // check authorization finder.findByKey(comment.issueKey(), UserRole.USER); + IssueChangeDto dto = ChangeDtoConverter.commentToDto(comment); + dto.setUpdatedAt(new Date()); + dto.setChangeData(text); + changeDao.update(dto); + return comment; } 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 81ad0bbdf31..a5ad2fac4b4 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 @@ -26,9 +26,12 @@ class IssueController < ApplicationController require_parameters :id @issue_results = Api.issues.find(params[:id]) - params[:layout] = 'false' - - render :action => 'show' + if request.xhr? + render :partial => 'resource/issue', :locals => {:issue => @issue_results.issues.get(0)} + else + params[:layout] = 'false' + render :action => 'show' + end end def action_form @@ -67,19 +70,40 @@ class IssueController < ApplicationController render :partial => 'resource/issue', :locals => {:issue => @issue_results.issues.get(0)} end + # form to edit comment + def edit_comment_form + verify_ajax_request + require_parameters :id + + @comment = Internal.issues.findComment(params[:id]) + + render :partial => 'issue/edit_comment_form' + end + + def edit_comment + verify_post_request + require_parameters :key, :text + + text = Api::Utils.read_post_request_param(params[:text]) + comment = Internal.issues.editComment(params[:key], text) + + @issue_results = Api.issues.find(comment.issueKey) + render :partial => 'resource/issue', :locals => {:issue => @issue_results.issues.get(0)} + end + # modal window to delete comment def delete_comment_form verify_ajax_request - require_parameters :key + require_parameters :id render :partial => 'issue/delete_comment_form' end def delete_comment verify_post_request - require_parameters :key + require_parameters :id - comment = Internal.issues.deleteComment(params[:key]) + comment = Internal.issues.deleteComment(params[:id]) @issue_results = Api.issues.find(comment.issueKey) render :partial => 'resource/issue', :locals => {:issue => @issue_results.issues.get(0)} diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_assign_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_assign_form.html.erb index 97ea7d669c6..ed1010acfd4 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_assign_form.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_assign_form.html.erb @@ -5,7 +5,7 @@ <tr> <td style="vertical-align:top"> <%= user_select_tag('assignee', :html_id => "assignee-#{params[:issue]}") -%> - <input type="button" value="<%= message('issue.assign.submit') -%>" onclick="postIssueForm(this)"> + <input type="button" value="<%= message('issue.assign.submit') -%>" onclick="submitIssueForm(this)"> <%= link_to_function message('cancel'), 'closeIssueForm(this)' -%> <span class="hidden"><%= image_tag 'loading.gif' -%></span> </td> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_comment_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_comment_form.html.erb index 9a1d185cfef..bf24b487672 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_comment_form.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_comment_form.html.erb @@ -9,7 +9,7 @@ </tr> <tr> <td style="padding-top: 5px"> - <input type="submit" value="<%= message('issue.comment.submit') -%>" onclick="return postIssueForm(this)"> + <input type="submit" value="<%= message('issue.comment.submit') -%>" onclick="return submitIssueForm(this)"> <%= link_to_function message('cancel'), 'closeIssueForm(this)' -%> <span class="loading hidden"></span> </td> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_delete_comment_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_delete_comment_form.html.erb index 9c790fe6ecb..26b6e7d4aed 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_delete_comment_form.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_delete_comment_form.html.erb @@ -1,5 +1,5 @@ <form id="delete-comment-form" method="post" action="<%= ApplicationController.root_context -%>/issue/delete_comment"> - <input type="hidden" name="key" value="<%= params[:key] -%>"> + <input type="hidden" name="id" value="<%= params[:id] -%>"> <fieldset> <div class="modal-head"> <h2><%= message 'issue.comment.delete_confirm_title' -%></h2> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_edit_comment_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_edit_comment_form.html.erb new file mode 100644 index 00000000000..80620759567 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_edit_comment_form.html.erb @@ -0,0 +1,21 @@ +<form action=""> + <input type="hidden" name="key" value="<%= @comment.key -%>"/> + <table class="width100"> + <tr> + <td style="vertical-align:top" colspan="2"> + <textarea rows="4" name="text" style="width: 100%" autofocus="autofocus"><%= @comment.markdownText -%></textarea> + </td> + </tr> + <tr> + <td style="padding-top: 5px"> + <input type="submit" value="<%= message('issue.edit_comment.submit') -%>" + onclick="doEditIssueComment(this);return false"> + <%= link_to_function message('cancel'), 'refreshIssue(this)' -%> + <span class="loading hidden"></span> + </td> + <td align="right"> + <%= render :partial => 'markdown/tips' -%> + </td> + </tr> + </table> +</form>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_plan_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_plan_form.html.erb index 1d8afd8c29f..f06cfc15455 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_plan_form.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_plan_form.html.erb @@ -18,7 +18,7 @@ <% end %> </select> - <input type="button" value="<%= message('issue.plan.submit') -%>" onclick="postIssueForm(this)"> + <input type="button" value="<%= message('issue.plan.submit') -%>" onclick="submitIssueForm(this)"> <%= link_to_function message('cancel'), 'closeIssueForm(this)' -%> <span class="hidden"><%= image_tag 'loading.gif' -%></span> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_severity_form.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_severity_form.html.erb index 1195ceafd1b..9a9ef58e26d 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_severity_form.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_severity_form.html.erb @@ -10,7 +10,7 @@ <% end %> </select> - <input type="submit" value="<%= message('issue.set_severity.submit') -%>" onclick="return postIssueForm(this)"> + <input type="submit" value="<%= message('issue.set_severity.submit') -%>" onclick="return submitIssueForm(this)"> <%= link_to_function message('cancel'), 'closeIssueForm(this)' -%> <span class="loading hidden"></span> </td> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb index 48d4776ddee..416798629f4 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb @@ -54,15 +54,16 @@ issue.comments.each do |comment| comment_html_id = "comment-#{comment.key}-#{rand(100)}" %> - <div class="code-issue-comment" id="<%= comment_html_id -%>"> + <div class="code-issue-comment" id="<%= comment_html_id -%>" data-comment-key="<%= comment.key -%>"> <h4> <%= image_tag('reviews/comment.png') -%> <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 %> - - <%= image_tag 'sep12.png' -%> - - <a class="link-action" href="<%= ApplicationController.root_context -%>/issue/delete_comment_form?key=<%= comment.key -%>&htmlId=<%=comment_html_id-%>" onclick="$j(this).openModal();return false"><%= message('delete') -%></a> + + <%= image_tag 'sep12.png' -%> + + <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) -%> diff --git a/sonar-server/src/main/webapp/javascripts/issue.js b/sonar-server/src/main/webapp/javascripts/issue.js index d30b769acbb..b5e80bb34d3 100644 --- a/sonar-server/src/main/webapp/javascripts/issue.js +++ b/sonar-server/src/main/webapp/javascripts/issue.js @@ -1,3 +1,4 @@ +/* Open form for most common actions like comment, assign or plan */ function issueForm(actionType, elt) { var issueElt = $j(elt).closest('[data-issue-key]'); var issueKey = issueElt.attr('data-issue-key'); @@ -21,6 +22,7 @@ function issueForm(actionType, elt) { return false; } +/* Close forms opened through the method issueForm() */ function closeIssueForm(elt) { var issueElt = $j(elt).closest('[data-issue-key]'); var actionsElt = issueElt.find('.code-issue-actions'); @@ -36,7 +38,8 @@ function notifyIssueChange(issueKey) { $j(document).trigger('sonar.issue.updated', [issueKey]); } -function postIssueForm(elt) { +/* Submit forms opened through the method issueForm() */ +function submitIssueForm(elt) { var formElt = $j(elt).closest('form'); formElt.find('.loading').removeClass('hidden'); formElt.find(':submit').prop('disabled', true); @@ -46,6 +49,7 @@ function postIssueForm(elt) { data: formElt.serialize()} ).success(function (htmlResponse) { var issueElt = formElt.closest('[data-issue-key]'); + var issueKey = issueElt.attr('data-issue-key'); var replaced = $j(htmlResponse); issueElt.replaceWith(replaced); @@ -64,6 +68,8 @@ function postIssueForm(elt) { function doIssueAction(elt, action, parameters) { var issueElt = $j(elt).closest('[data-issue-key]'); var issueKey = issueElt.attr('data-issue-key'); + + issueElt.find('.code-issue-actions').html("<img src='" + baseUrl + "/images/loading.gif'>"); parameters['issue'] = issueKey; $j.ajax({ @@ -95,4 +101,59 @@ function assignIssueToMe(elt) { function doIssueTransition(elt, transition) { var parameters = {'transition': transition}; return doIssueAction(elt, 'transition', parameters) +} + +function formDeleteIssueComment(elt) { + var commentElt = $j(elt).closest("[data-comment-key]"); + var htmlId = commentElt.attr('id'); + var commentKey = commentElt.attr('data-comment-key'); + return openModalWindow(baseUrl + '/issue/delete_comment_form/' + commentKey + '?htmlId=' + htmlId, {}); +} + +function formEditIssueComment(elt) { + var commentElt = $j(elt).closest("[data-comment-key]"); + var commentKey = commentElt.attr('data-comment-key'); + var issueElt = commentElt.closest('[data-issue-key]'); + + issueElt.find('.code-issue-actions').addClass('hidden'); + commentElt.html("<img src='" + baseUrl + "/images/loading.gif'>"); + + $j.get(baseUrl + "/issue/edit_comment_form/" + commentKey, function (html) { + commentElt.html(html); + }); + return false; +} + +function doEditIssueComment(elt) { + var formElt = $j(elt).closest('form'); + var issueElt = formElt.closest('[data-issue-key]'); + var issueKey = issueElt.attr('data-issue-key'); + $j.ajax({ + type: "POST", + url: baseUrl + "/issue/edit_comment", + data: formElt.serialize(), + success: function (htmlResponse) { + var replaced = $j(htmlResponse); + issueElt.replaceWith(replaced); + + // re-enable the links opening modal popups + replaced.find('.open-modal').modal(); + + notifyIssueChange(issueKey); + } + }); + return false; +} + +function refreshIssue(elt) { + var issueElt = $j(elt).closest('[data-issue-key]'); + var issueKey = issueElt.attr('data-issue-key'); + $j.get(baseUrl + "/issue/show/" + issueKey, function (html) { + var replaced = $j(html); + issueElt.replaceWith(replaced); + + // re-enable the links opening modal popups + replaced.find('.open-modal').modal(); + }); + return false; }
\ No newline at end of file |