diff options
18 files changed, 380 insertions, 189 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/user/RubyUserService.java b/sonar-plugin-api/src/main/java/org/sonar/api/user/RubyUserService.java index cb46f2fa95c..c7dd59f0e9e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/user/RubyUserService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/user/RubyUserService.java @@ -38,7 +38,7 @@ public interface RubyUserService extends ServerComponent { * <p/> * Optional parameters are: * <ul> - * <li><code>q</code> to match all the logins or names containing this search query</li> + * <li><code>s</code> to match all the logins or names containing this search query</li> * <li><code>logins</code>, as an array of strings (['simon', 'julien']) or a comma-separated list of logins ('simon,julien')</li> * <li><code>includeDeactivated</code> as a boolean. By Default deactivated users are excluded from query.</li> * </ul> diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml index 7d4eaa0cd69..ba963f5be3b 100644 --- a/sonar-server/pom.xml +++ b/sonar-server/pom.xml @@ -256,6 +256,7 @@ <include>**/dashboard-min.js</include> <include>**/duplication-min.js</include> <include>**/resource-min.js</include> + <include>**/issue-min.js</include> <include>**/recent-history.js</include> </includes> <output>${project.build.directory}/${project.build.finalName}/javascripts/sonar.js</output> 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 9eb7358570e..f01dff84114 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 @@ -21,6 +21,7 @@ package org.sonar.server.issue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import org.apache.commons.lang.StringUtils; import org.sonar.api.ServerComponent; import org.sonar.api.issue.ActionPlan; import org.sonar.api.issue.Issue; @@ -37,7 +38,6 @@ import org.sonar.core.resource.ResourceQuery; import org.sonar.server.platform.UserSession; import javax.annotation.Nullable; - import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; @@ -72,8 +72,8 @@ public class InternalRubyIssueService implements ServerComponent { return issueService.doTransition(issueKey, transitionKey, UserSession.get()); } - public Issue assign(String issueKey, String transitionKey) { - return issueService.assign(issueKey, transitionKey, UserSession.get()); + public Issue assign(String issueKey, @Nullable String assignee) { + return issueService.assign(issueKey, StringUtils.defaultIfBlank(assignee, null), UserSession.get()); } public Issue setSeverity(String issueKey, String severity) { diff --git a/sonar-server/src/main/java/org/sonar/server/issue/PublicRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/PublicRubyIssueService.java index 6377d61fa9d..072972b58e5 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/PublicRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/PublicRubyIssueService.java @@ -32,7 +32,7 @@ import org.sonar.api.web.UserRole; import org.sonar.server.util.RubyUtils; import javax.annotation.Nullable; - +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -50,6 +50,10 @@ public class PublicRubyIssueService implements RubyIssueService { this.finder = f; } + public IssueQueryResult find(String issueKey) { + return finder.find(IssueQuery.builder().issueKeys(Arrays.asList(issueKey)).build()); + } + /** * Requires the role {@link org.sonar.api.web.UserRole#CODEVIEWER} */ @@ -109,7 +113,6 @@ public class PublicRubyIssueService implements RubyIssueService { } - public void start() { // used to force pico to instantiate the singleton at startup } diff --git a/sonar-server/src/main/java/org/sonar/server/user/DefaultRubyUserService.java b/sonar-server/src/main/java/org/sonar/server/user/DefaultRubyUserService.java index d561d7230a5..8a24eb59d63 100644 --- a/sonar-server/src/main/java/org/sonar/server/user/DefaultRubyUserService.java +++ b/sonar-server/src/main/java/org/sonar/server/user/DefaultRubyUserService.java @@ -52,7 +52,7 @@ public class DefaultRubyUserService implements RubyUserService { builder.includeDeactivated(); } builder.logins(RubyUtils.toStrings(params.get("logins"))); - builder.searchText((String)params.get("q")); + builder.searchText((String)params.get("s")); return builder.build(); } } 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 76e60d23963..648f4c5d92c 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 @@ -109,7 +109,7 @@ class IssueController < ApplicationController require_parameters :issue init_issue(params[:issue]) init_resource - @action_plans = Internal.issues.findOpenActionPlans(@resource.key) + @action_plans = Internal.issues.findOpenActionPlans(@resource.key) render :partial => 'issue/plan_form' end @@ -132,6 +132,35 @@ class IssueController < ApplicationController render_issue_detail end + def action_form + verify_ajax_request + require_parameters :id, :issue + + action_type = params[:id] + + # not used yet + issue_key = params[:issue] + + render :partial => "issue/#{action_type}_form" + end + + def do_action + verify_post_request + require_parameters :id, :issue + + issue_key = params[:issue] + action_type = params[:id] + + if action_type=='comment' + Internal.issues.addComment(issue_key, params[:text]) + elsif action_type=='assign' + Internal.issues.assign(issue_key, params[:assignee]) + end + + @issue_results = Api.issues.find(issue_key) + render :partial => 'resource/issue', :locals => {:issue => @issue_results.issues.get(0)} + end + # # # ACTIONS FROM ISSUES TAB OF CODE VIEWER @@ -206,7 +235,7 @@ class IssueController < ApplicationController require_parameters :issue init_issue(params[:issue]) init_resource - @action_plans = Internal.issues.findOpenActionPlans(@resource.key) + @action_plans = Internal.issues.findOpenActionPlans(@resource.key) render :partial => 'issue/code_viewer/plan_form' end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb index ac0a1819192..4b580f6caaf 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb @@ -589,30 +589,12 @@ module ApplicationHelper html += "\">X</a></span>" end - # - # Creates an enhanced dropdown selection box of resources. Values are loaded on the fly via Ajax requests. - # ==== Options - # * <tt>:width</tt> - The width suffixed with unit, for example '300px' or '100%'. Default is '250px' - # * <tt>:html_id</tt> - The id of the HTML element. Default is the name. - # * <tt>:html_class</tt> - The class of the HTML element. Default is empty. - # * <tt>:qualifiers</tt> - Array of resource qualifiers to filter. - # * <tt>:resource_type_property</tt> -Filter on resource types on which the property is enabled, for example 'supportsGlobalDashboards'. - # * <tt>:selected_resource</tt> - the resource that is selected by default. - # * <tt>:placeholder</tt> - the label to display when nothing is selected - # * <tt>:allow_clear</tt> - true if resource can be de-selected. Default is false. - # * <tt>:select2_options</tt> - hash of select2 options - # - def resource_select_tag(name, options={}) - width=options[:width] + + def select2_tag(name, ws_url, options={}) + width=options[:width]||'250px' html_id=options[:html_id]||name html_class=options[:html_class]||'' - - ws_url="#{ApplicationController::root_context}/api/resources/search?f=s2&" - if options[:qualifiers] - ws_url+="q=#{options[:qualifiers].join(',')}" - elsif options[:resource_type_property] - ws_url+="qp=#{options[:resource_type_property]}" - end + min_length=options[:min_length] ajax_options={ 'quietMillis' => 300, @@ -622,7 +604,6 @@ module ApplicationHelper } ajax_options.merge!(options[:select2_ajax_options]) if options[:select2_ajax_options] - min_length = 3 # see limitation in /api/resources/search js_options={ 'minimumInputLength' => min_length, 'allowClear' => options[:allow_clear]||false, @@ -638,9 +619,10 @@ module ApplicationHelper html = "<input type='hidden' id='#{html_id}' class='#{html_class}' name='#{name}'/>" js = "$j('##{html_id}').select2({#{js_options.map { |k, v| "#{k}:#{v}" }.join(',')}});" - resource = options[:selected_resource] - if resource - js += "$j('##{html_id}').select2('data', {id: #{resource.id}, text: '#{escape_javascript(resource.name(true))}'});" + selected_id=options[:selected_id] + selected_text=options[:selected_text] + if selected_id && selected_text + js += "$j('##{html_id}').select2('data', {id: #{selected_id}, text: '#{escape_javascript(selected_text)}'});" end "#{html}<script>#{js}</script>" @@ -648,6 +630,63 @@ module ApplicationHelper # + # Creates an enhanced dropdown selection box of resources. Values are loaded on the fly via Ajax requests. + # ==== Options + # * <tt>:width</tt> - The width suffixed with unit, for example '300px' or '100%'. Default is '250px' + # * <tt>:html_id</tt> - The id of the HTML element. Default is the name. + # * <tt>:html_class</tt> - The class of the HTML element. Default is empty. + # * <tt>:qualifiers</tt> - Array of resource qualifiers to filter. + # * <tt>:resource_type_property</tt> -Filter on resource types on which the property is enabled, for example 'supportsGlobalDashboards'. + # * <tt>:selected_resource</tt> - the resource that is selected by default. + # * <tt>:placeholder</tt> - the label to display when nothing is selected + # * <tt>:allow_clear</tt> - true if resource can be de-selected. Default is false. + # * <tt>:select2_options</tt> - hash of select2 options + # + def resource_select_tag(name, options={}) + # see limitation in /api/resources/search + options[:min_length]=3 + + ws_url="#{ApplicationController::root_context}/api/resources/search?f=s2&" + if options[:qualifiers] + ws_url+="q=#{options[:qualifiers].join(',')}" + elsif options[:resource_type_property] + ws_url+="qp=#{options[:resource_type_property]}" + end + + selected_resource = options[:selected_resource] + if selected_resource + options[:selected_id]=resource.id + options[:selected_text]=resource.name(true) + end + + select2_tag(name, ws_url, options) + end + + # + # Creates an enhanced dropdown selection box of users. Values are loaded on the fly via Ajax requests. + # ==== Options + # * <tt>:width</tt> - The width suffixed with unit, for example '300px' or '100%'. Default is '250px' + # * <tt>:html_id</tt> - The id of the HTML element. Default is the name. + # * <tt>:html_class</tt> - The class of the HTML element. Default is empty. + # * <tt>:selected_user</tt> - the user that is selected by default. + # * <tt>:placeholder</tt> - the label to display when nothing is selected + # * <tt>:allow_clear</tt> - true if resource can be de-selected. Default is false. + # * <tt>:select2_options</tt> - hash of select2 options + # + def user_select_tag(name, options={}) + ws_url="#{ApplicationController::root_context}/api/users/search?f=s2" + options[:min_length]=2 + + user = options[:selected_user] + if user + options[:selected_id]=user.login + options[:selected_text]=user.name + end + + select2_tag(name, ws_url, options) + end + + # # Creates an enhanced dropdown selection box of metrics. # ==== Options # * <tt>:selected_key</tt> - the key of the metric that is selected by default. 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 294c88cc82c..60890ab5def 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 @@ -1,40 +1,14 @@ -<% - assignee_check_script = "if ($('autocompleteText-issue_assignee_login').value != '' && $('issue_assignee_login').value == '') { alert($('autocompleteText-issue_assignee_login').value + '" + message('issues.user_does_not_exist') + "'); return false;}" -%> - -<form method="post" - onsubmit="<%= assignee_check_script -%> new Ajax.Updater('issue', '<%= url_for :action => 'assign' -%>', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> - <%= hidden_field_tag :issue, params[:issue] -%> - +<form action=""> + <input type="hidden" name="issue" value="<%= params[:issue] -%>"/> + <input type="hidden" name="id" value="assign"/> <table class="width100"> <tr> <td style="vertical-align:top"> - <textarea id="comment" rows="4" name="text" style="width: 100%"></textarea> - </td> - <td class="sep"></td> - <td style="vertical-align:top;width: 90px"> - <%= render :partial => 'markdown/tips' -%> + <%= user_select_tag('assignee') -%> + <input type="button" value="<%= message('issues.action.assign.button') -%>" onclick="doIssueAction(this)"> + <%= link_to_function message('cancel'), 'cancelIssueForm(this)' -%> + <span class="hidden"><%= image_tag 'loading.gif' -%></span> </td> </tr> </table> - - <%= user_autocomplete_field 'issue_assignee_login', '' -%> - - - <%= submit_to_remote "submit_btn", message('issues.action.assign.button'), - :url => {:action => 'assign'}, - :update => "issue", - :before => assignee_check_script -%> - - <%= image_tag 'sep12.png' -%> - - <%= submit_to_remote "submit_me_btn", message('issues.action.assign_to_me.button'), - :url => {:action => 'assign', :issue => params[:issue], :me => true}, - :update => "issue", - :html => {:disabled => (@issue.assignee == current_user.login)} -%> - - <%= link_to_remote message('cancel'), :url => {:controller => 'issue', :action => 'show', :key => params[:issue]}, :update => 'issue' -%> - <script> - $('autocompleteText-issue_assignee_login').focus() - </script> </form>
\ No newline at end of file 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 new file mode 100644 index 00000000000..447a0258999 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/issue/_comment_form.html.erb @@ -0,0 +1,18 @@ +<form action=""> + <input type="hidden" name="issue" value="<%= params[:issue] -%>"/> + <input type="hidden" name="id" value="comment"/> + <table class="width100"> + <tr> + <td style="vertical-align:top"> + <textarea rows="4" name="text" style="width: 100%" autofocus="autofocus"></textarea> + </td> + </tr> + <tr> + <td> + <input type="submit" value="<%= message('reviews.comment_submit') -%>" onclick="return doIssueAction(this)"> + <%= link_to_function message('cancel'), 'cancelIssueForm(this)' -%> + <span class="loading hidden"></span> + </td> + </tr> + </table> +</form> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb index a429508df4d..fbcd228b259 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_head.html.erb @@ -17,13 +17,13 @@ end if @page_title - title="Sonar - #{h(@page_title)}" + title="SonarQube - #{h(@page_title)}" elsif @project - title="Sonar - #{h(@project.name)}" + title="SonarQube - #{h(@project.name)}" elsif @resource title="#{h(@resource.long_name)}" else - title='Sonar' + title='SonarQube' end %> <title><%= title -%></title> @@ -53,6 +53,7 @@ <%= javascript_include_tag 'dashboard' %> <%= javascript_include_tag 'duplication' %> <%= javascript_include_tag 'resource' %> + <%= javascript_include_tag 'issue' %> <%= javascript_include_tag 'recent-history' %> <% end %> <!--[if lte IE 8]><%= javascript_include_tag 'protovis-msie' -%><![endif]--> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb index 778ed831a06..53e88b0b344 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb @@ -1,4 +1,5 @@ -<div> +<% accordionId = rand(10000) %> +<div id="accordion<%= accordionId -%>"> <div class="accordion-item-header"> <%= render :partial => 'tabs' -%> </div> @@ -16,7 +17,6 @@ <% end %> <% if @lines && @lines.size>0 %> - <%= render :partial => "shared/source_display", :locals => { :display_manual_violation_form => @display_manual_violation_form, \ :scm_available => @scm_available, \ :display_coverage => @display_coverage, \ @@ -29,7 +29,6 @@ :review_screens_by_vid => @review_screens_by_vid, \ :filtered => @filtered} %> - <% end %> <% if @duplication_groups %> @@ -38,5 +37,7 @@ </div> </div> - +<script> + $j('#accordion<%= accordionId -%> .open-modal').modal(); +</script> 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 602e9eb82bc..662a6635323 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 @@ -1,111 +1,89 @@ -<div id="issue-key<%= issue.key -%>"> - <div class="violation"> - <div class="vtitle"> - <div class="review_permalink"> - <span class="review_permalink"><%= link_to image_tag('zoom.png'), :controller => "issue", :action => "view", :id => issue.key -%></span> - </div> +<div class="code-issue" data-issue-key="<%= issue.key -%>"> + <div class="code-issue-name"> + <div class="code-issue-link"> + <%= link_to image_tag('zoom.png'), :controller => "issue", :action => "view", :id => issue.key -%> + </div> - <img src="<%= ApplicationController.root_context -%>/images/priority/<%= issue.severity -%>.png"> - + <img src="<%= ApplicationController.root_context -%>/images/priority/<%= issue.severity -%>.png"> + <span class="rulename"> <% rule_name = Internal.rules.ruleL10nName(@issue_results.rule(issue)) %> - <a class="open-modal issue-rule-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> + <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> + + <%= image_tag 'sep12.png' -%> + + + <span><%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(issue.creationDate())) -%></span> + + <% + if issue.resolution + %> + <%= image_tag 'sep12.png' -%> + <span><%= message("issues.resolution.#{issue.resolution}") -%></span> + + <% end %> + <% + if issue.assignee + %> <%= image_tag 'sep12.png' -%> - - <span><%= distance_of_time_in_words_to_now(Api::Utils.java_to_ruby_datetime(issue.creationDate())) -%></span> + <%= message('assigned_to') -%> <%= h(@issue_results.user(issue.assignee).name) -%> - <% - if issue.resolution == 'FALSE-POSITIVE' - %> - <%= image_tag 'sep12.png' -%> - - <span class="reviewResolution<%= issue.resolution %>"><%= message("issues.resolution.#{issue.resolution}") -%></span> - - <% end %> - <% - if issue.status == 'RESOLVED' - %> - <%= image_tag 'sep12.png' -%> - - <span class="reviewStatus<%= issue.status -%>"><%= message("issues.status.#{issue.status}") -%></span> - - <% end %> - <% - if issue.assignee - %> - <%= image_tag 'sep12.png' -%> - - <%= message('assigned_to') -%> <%= h(@issue_results.user(issue.assignee).name) -%> - - <% end %> - <% - if issue.actionPlanKey() - %> - <%= image_tag 'sep12.png' -%> - - <%= message('issues.planned_for_x', :params => h(@issue_results.actionPlan(issue).name())) if @issue_results.actionPlan(issue) -%> - - <% end %> + <% end %> + <% + if issue.actionPlanKey() + %> + <%= image_tag 'sep12.png' -%> + + <%= message('issues.planned_for_x', :params => h(@issue_results.actionPlan(issue).name())) if @issue_results.actionPlan(issue) -%> + + <% end %> + </div> + + <% unless issue.description.blank? %> + <div class="code-issue-msg"> + <%= Api::Utils.split_newlines(h(issue.description)).join('<br/>') -%> </div> + <% end %> - <div class="discussionComment first"> - <div id="issue-description<%= issue.key -%>"> - <% issue_description = issue.description ? Api::Utils.split_newlines(h(issue.description)).join('<br/>') : '' %> - <%= issue_description || ' ' -%> - </div> + <% issue.comments.each do |comment| %> + <div class="code-issue-comment"> + <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)) -%>) + </h4> + <%= Internal.text.markdownToHtml(comment.markdownText) -%> </div> + <% end %> - <% if current_user %> - <div class="vActions" id="issue-actions<%= issue.key -%>"> - <% if defined?(error_message) && error_message %> - <div id="issue-error-<%= issue.key -%>" class="error"><%= h error_message -%> - <a href="#" onclick="$('issue-error-<%= issue.key -%>').hide(); return false;"><%= message('issues.hide_this_message') -%></a></div> - <% end %> + <% if current_user %> + <div class="code-issue-actions"> + <a href='#' onclick="return issueForm('comment', this)" class="link-action spacer-right"><%= message('reviews.comment') -%></a> - <% unless issue.status == 'RESOLVED' %> - <%= link_to_function message('issues.action.assign.button'), "displayIssueAssignForm('#{issue.key}')", - :name => message('issues.action.assign.button'), :class => 'link-action spacer-right' -%> - <% end %> + <% unless issue.resolution %> + <a href='#' onclick="return issueForm('assign', this)" class="link-action spacer-right"><%= message('issues.action.assign.button') -%></a> + <% end %> - <% Internal.issues.listTransitions(issue.key).each do |transition| %> + <% Internal.issues.listTransitions(issue.key).each do |transition| %> <%= link_to_function message("issues.transition.#{transition.key}.button"), "displayIssueTransitionForm('#{issue.key}', '#{transition.key}')", :name => message("issues.transition.#{transition.key}.button"), :class => 'link-action spacer-right' -%> - <% end %> + <% end %> - <% unless issue.resolution && issue.status == 'RESOLVED' %> - <div class="dropdown"> - <a href="#" class="link-action link-more" onclick="showDropdownMenu('more<%= issue.key -%>');return false;"><%= message('more_actions') -%></a> - <ul style="display: none" class="dropdown-menu" id="more<%= issue.key -%>"> - <li><%= link_to_function message('issues.action.change_severity.button'), "displayIssueChangeSeverityForm('#{issue.key}')", - :name => message('issues.action.change_severity.button') -%></li> - <li><%= link_to_function message('issues.action.plan.button'), "displayIssuePlanForm('#{issue.key}')", - :name => message('issues.action.plan.button') -%></li> - </ul> - </div> - <% end %> - </div> - <% end %> - <% issue.comments.each do |comment| %> - <div class="discussionComment"> - <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)) -%>) - </h4> - <%= Internal.text.markdownToHtml(comment.markdownText) -%> + <% unless issue.resolution && issue.status == 'RESOLVED' %> + <div class="dropdown"> + <a href="#" class="link-action link-more" onclick="showDropdownMenu('more<%= issue.key -%>');return false;"><%= message('more_actions') -%></a> + <ul style="display: none" class="dropdown-menu" id="more<%= issue.key -%>"> + <li><%= link_to_function message('issues.action.change_severity.button'), "displayIssueChangeSeverityForm('#{issue.key}')", + :name => message('issues.action.change_severity.button') -%></li> + <li><%= link_to_function message('issues.action.plan.button'), "displayIssuePlanForm('#{issue.key}')", + :name => message('issues.action.plan.button') -%></li> + </ul> + </div> + <% end %> </div> - <% end %> - </div> - <div class="discussionComment" id="issue-form<%= issue.key -%>" style="display:none"></div> -</div> - -<script type="text/javascript"> - $j(document).ready(function () { - // As rule links will be loaded after open-modal has been processed by jquery, we have to process manually issue-rule-modal classes - $j('.issue-rule-modal').modal() - }); -</script>
\ No newline at end of file + <div class="code-issue-form hidden"></div> + <% end %> +</div>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/shared/_source_issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/shared/_source_issues.html.erb index 37d4a4da779..c0a0f827a4f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/shared/_source_issues.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/shared/_source_issues.html.erb @@ -1,17 +1,17 @@ <tr> - <% if display_manual_violation_form %> - <td class="gray"></td> - <% end - if scm_available %> - <td class="scm"></td> + <% if display_manual_violation_form %> + <td class="gray"></td> + <% end + if scm_available %> + <td class="scm"></td> + <% end %> + <td class="lid"></td> + <td class="code-issues"> + <% line.issues.each_with_index do |issue, index| %> + <%= render :partial => 'issue', :locals => {:issue => issue} -%> + <% if index < line.issues.size-1 %> + + <% end %> <% end %> - <td class="lid"></td> - <td class="violations"> - <% line.issues.each_with_index do |issue, index| %> - <%= render :partial => 'issue', :locals => {:issue => issue} -%> - <% if index < line.issues.size-1 %> - - <% end %> - <% end %> - </td> + </td> </tr>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/javascripts/issue.js b/sonar-server/src/main/webapp/javascripts/issue.js new file mode 100644 index 00000000000..47cb33c632a --- /dev/null +++ b/sonar-server/src/main/webapp/javascripts/issue.js @@ -0,0 +1,54 @@ +function issueForm(actionType, elt) { + var issueElt = $j(elt).closest('[data-issue-key]'); + var issueKey = issueElt.attr('data-issue-key'); + var actionsElt = issueElt.find('.code-issue-actions'); + var formElt = issueElt.find('.code-issue-form'); + + actionsElt.addClass('hidden'); + formElt.html("<img src='" + baseUrl + "/images/loading-small.gif'>").removeClass('hidden'); + + $j.ajax(baseUrl + "/issue/action_form/" + actionType + "?issue=" + issueKey) + .done(function (msg) { + formElt.html(msg); + var focusField = formElt.find('[autofocus]'); + if (focusField != null) { + focusField.focus(); + } + }) + .fail(function (jqXHR, textStatus) { + alert(textStatus); + }); + return false; +} + +function cancelIssueForm(elt) { + var issueElt = $j(elt).closest('[data-issue-key]'); + var actionsElt = issueElt.find('.code-issue-actions'); + var formElt = issueElt.find('.code-issue-form'); + + formElt.addClass('hidden'); + actionsElt.removeClass('hidden'); + return false; +} + +function doIssueAction(elt) { + var formElt = $j(elt).closest('form'); + formElt.find('.loading').removeClass('hidden'); + formElt.find(':submit').prop('disabled', true); + $j.ajax({ + type: "POST", + url: baseUrl + '/issue/do_action', + data: formElt.serialize()} + ).success(function (htmlResponse) { + var issueElt = formElt.closest('[data-issue-key]'); + issueElt.html(htmlResponse); + // re-enable the links opening modal popups + issueElt.find('.open-modal').modal(); + } + ).fail(function (jqXHR, textStatus) { + cancelIssueForm(elt); + alert(textStatus); + }); + return false; +} + diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css index d974c51814b..77768ce5488 100644 --- a/sonar-server/src/main/webapp/stylesheets/style.css +++ b/sonar-server/src/main/webapp/stylesheets/style.css @@ -176,7 +176,7 @@ button, .button, input[type="submit"], input[type="button"] { -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); - color: #333333; + color: #333; border: 1px solid #CCC; background: #EBEBEB; background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#E7E7E7)); @@ -742,6 +742,18 @@ ul.operations li img { padding: 10px; } + + + + + + + + + + + + div.vtitle { background-color: #E4ECF3; margin: 0; @@ -788,7 +800,7 @@ span.rulename, span.rulename a { div.review_permalink { float: right; - color: #333333; + color: #333; font-weight: bold; margin: 0; padding: 0 10px; @@ -880,6 +892,84 @@ span.rulename a:hover { vertical-align: text-bottom; } +.code-issues { + background-color: #FFF; + padding: 10px; +} + +.code-issue { + background-color: #FFF; + margin: 0; + font-size: 12px; +} + +.code-issue-name { + background-color: #E4ECF3; + margin: 0; + padding: 5px 10px; + line-height: 16px; + color: #777; + border: 1px solid #DDD; +} + +.code-issue-name img { + vertical-align: text-bottom; +} + +.code-issue-link { + float: right; + color: #333; + font-weight: bold; + margin: 0; + padding: 0 10px; + text-shadow: 1px 1px 0 #FFFFFF; +} + +.code-issue-link a { + color: #777; + font-size: 11px; + padding: 0 0 0 20px; +} + +.code-issue-comment, .code-issue-msg, .code-issue-actions, .code-issue-form { + background-color: #EFEFEF; + border: 1px solid #DDD; + border-top: none; + line-height: 1.5em; + margin: 0; + padding: 5px 10px; +} + +.code-issue-msg h4 { + font-size: 90%; + margin-bottom: 2px; +} + +.code-issue-msg h4 img { + vertical-align: sub; +} + +.code-issue-msg li { + list-style: square inside; +} + +.code-issue-msg pre { + padding: 10px; + border: 1px dashed #DDD; + color: #444; + font-size: 12px; +} + + + + + + + + + + + .tab_header { border-bottom: 1px solid #DDD; background-color: #EFEFEF; diff --git a/sonar-server/src/test/java/org/sonar/server/user/DefaultRubyUserServiceTest.java b/sonar-server/src/test/java/org/sonar/server/user/DefaultRubyUserServiceTest.java index 1dbf3b95f90..5dac4625e4d 100644 --- a/sonar-server/src/test/java/org/sonar/server/user/DefaultRubyUserServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/user/DefaultRubyUserServiceTest.java @@ -38,14 +38,17 @@ public class DefaultRubyUserServiceTest { public void parse_query() throws Exception { service.find(ImmutableMap.<String, Object>of( "logins", "simon,loic", - "includeDeactivated", "true" + "includeDeactivated", "true", + "s", "sim" )); verify(finder, times(1)).find(argThat(new ArgumentMatcher<UserQuery>() { @Override public boolean matches(Object o) { UserQuery query = (UserQuery) o; - return query.includeDeactivated() && query.logins().contains("simon") && query.logins().contains("loic") && query.logins().size() == 2; + return query.includeDeactivated() && + query.logins().contains("simon") && query.logins().contains("loic") && query.logins().size() == 2 && + query.searchText().equals("sim"); } })); } @@ -58,7 +61,7 @@ public class DefaultRubyUserServiceTest { @Override public boolean matches(Object o) { UserQuery query = (UserQuery) o; - return !query.includeDeactivated() && query.logins() == null; + return !query.includeDeactivated() && query.logins() == null && query.searchText()==null; } })); } diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/user/UserQuery.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/user/UserQuery.java index 07f802fb8a9..43d0e33c6ac 100644 --- a/sonar-ws-client/src/main/java/org/sonar/wsclient/user/UserQuery.java +++ b/sonar-ws-client/src/main/java/org/sonar/wsclient/user/UserQuery.java @@ -52,9 +52,9 @@ public class UserQuery { public UserQuery searchText(@Nullable String s) { if (s != null) { - params.put("q", s); + params.put("s", s); } else { - params.remove("q"); + params.remove("s"); } return this; } diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/user/UserQueryTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/user/UserQueryTest.java index fbb9b4d26a1..475e95c4f70 100644 --- a/sonar-ws-client/src/test/java/org/sonar/wsclient/user/UserQueryTest.java +++ b/sonar-ws-client/src/test/java/org/sonar/wsclient/user/UserQueryTest.java @@ -52,6 +52,6 @@ public class UserQueryTest { @Test public void should_search_by_text() throws Exception { UserQuery query = UserQuery.create().searchText("sim"); - assertThat(query.urlParams().get("q")).isEqualTo("sim"); + assertThat(query.urlParams().get("s")).isEqualTo("sim"); } } |