From c1b8246f1512e42dde50486d059d906b98c6eec2 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 16 Apr 2013 11:11:49 +0200 Subject: [PATCH] SONAR-3755 Add first version of Issues drilldown --- .../resources/org/sonar/l10n/core.properties | 7 + .../sonar/batch/issue/IssuesDecorator.java | 2 +- .../org/sonar/server/ui/DefaultPages.java | 38 +++- .../app/controllers/api/issues_controller.rb | 3 +- .../app/controllers/drilldown_controller.rb | 88 ++++++++ .../app/controllers/issues_controller.rb | 7 +- .../app/controllers/resource_controller.rb | 105 +++++++++- .../WEB-INF/app/helpers/resource_helper.rb | 5 + .../app/views/drilldown/issues.html.erb | 192 ++++++++++++++++++ .../app/views/layouts/_layout.html.erb | 3 + .../views/resource/_header_issues.html.erb | 70 +++++++ .../app/views/resource/_index_issues.html.erb | 172 ++++++++++++++++ .../app/views/resource/_issue.html.erb | 79 +++++++ .../views/resource/_options_issues.html.erb | 117 +++++++++++ .../resource/_rules_filter_issues.html.erb | 112 ++++++++++ .../java/org/sonar/server/ui/ViewsTest.java | 2 +- 16 files changed, 988 insertions(+), 14 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_issues.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_options_issues.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/resource/_rules_filter_issues.html.erb 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 9d42dd64c23..548de094270 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 @@ -168,6 +168,7 @@ added_since_previous_version_detailed=Added since previous version ({0}) added_since_version=Added since version {0} alerts_feed=Alerts feed all_violations=All violations +all_issues=All issues are_you_sure=Are you sure? assigned_to=Assigned to bulk_change=Bulk Change @@ -347,6 +348,7 @@ project_history.page=History quality_profiles.page=Quality Profiles reviews.page=Reviews issues.page=Issues +issues_drilldown.page=Issues Drilldown settings.page=General Settings source.page=Source system_info.page=System Info @@ -1097,6 +1099,11 @@ violations_drilldown.col.severity=Severity violations_drilldown.col.rule=Rule violations_drilldown.no_violations=No violations +issues_drilldown.click_for_more_on_x=Click for more on {0} : {1} +issues_drilldown.col.severity=Severity +issues_drilldown.col.rule=Rule +issues_drilldown.no_issue=No issue + #------------------------------------------------------------------------------ # diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java index 3f2ed8f6b96..c54d552830f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java @@ -64,7 +64,7 @@ public class IssuesDecorator implements Decorator { } public void decorate(Resource resource, DecoratorContext context) { - Issuable issuable = perspectives.as(Issuable.class, context.getResource()); + Issuable issuable = perspectives.as(Issuable.class, resource); Collection issues = issuable.issues(); computeTotalIssues(context, issues); computeIssuesPerSeverities(context, issues); diff --git a/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java b/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java index ee33ebc0308..5947a81b47c 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java @@ -21,20 +21,14 @@ package org.sonar.server.ui; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.resources.Qualifiers; -import org.sonar.api.web.DefaultTab; -import org.sonar.api.web.NavigationSection; -import org.sonar.api.web.RequiredMeasures; -import org.sonar.api.web.ResourceQualifier; -import org.sonar.api.web.RubyRailsPage; -import org.sonar.api.web.UserRole; -import org.sonar.api.web.View; +import org.sonar.api.web.*; /** * @since 2.7 */ public final class DefaultPages { - private static final View[] PAGES = {new SourceTab(), new CoverageTab(), new ViolationsTab(), new DuplicationsTab()}; + private static final View[] PAGES = {new SourceTab(), new CoverageTab(), new ViolationsTab(), new IssuesTab(), new DuplicationsTab()}; private DefaultPages() { } @@ -128,6 +122,34 @@ public final class DefaultPages { } } + @NavigationSection(NavigationSection.RESOURCE_TAB) + @DefaultTab( + metrics = {CoreMetrics.ISSUES_DENSITY_KEY, CoreMetrics.WEIGHTED_ISSUES_KEY, CoreMetrics.ISSUES_KEY, CoreMetrics.BLOCKER_ISSUES_KEY, + CoreMetrics.CRITICAL_ISSUES_KEY, CoreMetrics.MAJOR_ISSUES_KEY, CoreMetrics.MINOR_ISSUES_KEY, CoreMetrics.INFO_ISSUES_KEY, + CoreMetrics.NEW_VIOLATIONS_KEY, CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY, CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY, CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY, + CoreMetrics.NEW_MINOR_VIOLATIONS_KEY, CoreMetrics.NEW_INFO_VIOLATIONS_KEY}) + // TODO +// CoreMetrics.ACTIVE_REVIEWS_KEY, CoreMetrics.UNASSIGNED_REVIEWS_KEY, +// CoreMetrics.UNPLANNED_REVIEWS_KEY, CoreMetrics.FALSE_POSITIVE_REVIEWS_KEY, CoreMetrics.UNREVIEWED_VIOLATIONS_KEY, CoreMetrics.NEW_UNREVIEWED_VIOLATIONS_KEY}) + @ResourceQualifier( + value = {Qualifiers.VIEW, Qualifiers.SUBVIEW, Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.PACKAGE, Qualifiers.DIRECTORY, Qualifiers.FILE, Qualifiers.CLASS, + Qualifiers.UNIT_TEST_FILE}) + @UserRole(UserRole.CODEVIEWER) + private static final class IssuesTab implements RubyRailsPage { + public String getTemplate() { + // not used, hardcoded in BrowseController + return "browse/index"; + } + + public String getId() { + return "issues"; + } + + public String getTitle() { + return "Issues"; + } + } + @NavigationSection(NavigationSection.RESOURCE_TAB) @DefaultTab(metrics = {CoreMetrics.DUPLICATED_LINES_KEY, CoreMetrics.DUPLICATED_BLOCKS_KEY, CoreMetrics.DUPLICATED_FILES_KEY, CoreMetrics.DUPLICATED_LINES_DENSITY_KEY}) @ResourceQualifier({Qualifiers.FILE, Qualifiers.CLASS}) diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb index ebeda15122e..9e047fd4487 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb @@ -22,7 +22,8 @@ class Api::IssuesController < Api::ApiController # GET /api/issues/search? def search - results = Api.issues.find(params, current_user.id) + user = current_user ? current_user.id : nil + results = Api.issues.find(params, user) render :json => jsonp(issues_to_json(results.issues)) end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb index 4db519bdda8..1f1a1b1c060 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb @@ -141,6 +141,84 @@ class DrilldownController < ApplicationController @display_viewers=display_violation_viewers?(@drilldown.highlighted_snapshot || @snapshot) end + def issues + @rule=Rule.by_key_or_id(params[:rule]) + + # variation measures + if params[:period].present? && params[:period].to_i>0 + @period=params[:period].to_i + metric_prefix = 'new_' + else + @period=nil + metric_prefix = '' + end + + @severity = params[:severity] + @rule_severity = params[:rule_sev] || @severity + + if @rule && @rule_severity.blank? + # workaround for SONAR-3255 : guess the severity + @rule_severity=guess_rule_severity_for_issues_metric(@snapshot, @rule, metric_prefix) + end + + if @rule_severity.present? + # Filter resources by severity + @metric = Metric::by_key("#{metric_prefix}#{@rule_severity.downcase}_issues") + else + @metric = Metric::by_key("#{metric_prefix}issues") + end + + # selected resources + if params[:rids] + @selected_rids= params[:rids] + elsif params[:resource] + highlighted_resource=Project.by_key(params[:resource]) + @selected_rids=(highlighted_resource ? [highlighted_resource.id] : []) + else + @selected_rids=[] + end + @selected_rids=@selected_rids.map { |r| r.to_i } + + # options for Drilldown + options={:exclude_zero_value => true, :period => @period} + if @rule + params[:rule]=@rule.key # workaround for SONAR-1767 : the javascript hash named "rp" in the HTML source must contain the rule key, but not the rule id + options[:rule_id]=@rule.id + end + + # load data + @drilldown = Drilldown.new(@resource, @metric, @selected_rids, options) + + @highlighted_resource=@drilldown.highlighted_resource + if @highlighted_resource.nil? && @drilldown.columns.empty? + @highlighted_resource=@resource + end + + # + # Initialize filter by rule + # + if @severity.present? + # Filter on severity -> filter rule measures by the selected metric + @rule_measures = @snapshot.rule_measures(@metric) + else + # No filter -> loads all the rules + metrics=[ + Metric.by_key("#{metric_prefix}blocker_issues"), + Metric.by_key("#{metric_prefix}critical_issues"), + Metric.by_key("#{metric_prefix}major_issues"), + Metric.by_key("#{metric_prefix}minor_issues"), + Metric.by_key("#{metric_prefix}info_issues") + ] + @rule_measures = @snapshot.rule_measures(metrics) + end + + snapshot = @drilldown.highlighted_snapshot || @snapshot + # FIXME For the moment the issues API return issues for all the resource tree, so it's imposible to know if there are issues only for the project for instance + # issues = Api.issues.find({'componentKey' => snapshot.project.key}, current_user.id).issues + #@display_viewers = true if snapshot.file? || issues.size>0 + @display_viewers = snapshot.file? + end + private def select_metric(metric_key, default_key) @@ -196,4 +274,14 @@ class DrilldownController < ApplicationController end Severity::MAJOR end + + def guess_rule_severity_for_issues_metric(snapshot, rule, metric_prefix) + Severity::KEYS.each do |severity| + if snapshot.rule_measure(Metric.by_key("#{metric_prefix}#{severity.downcase}_issues"), rule) + return severity + end + end + Severity::MAJOR + end + end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb index 27c0bfa50fb..e60daf9c9d1 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb @@ -21,13 +21,16 @@ class IssuesController < ApplicationController def index - @issues = find_issues({}).issues + @issues = find_issues({}) end protected def find_issues(map) - Api.issues.find(map, current_user.id) + user = current_user ? current_user.id : nil + issues = [] + Api.issues.find(map, user).issues.each {|issue| issues << issue} + issues end end \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb index fbcb801ae98..9a6bced2e04 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb @@ -46,6 +46,9 @@ class ResourceController < ApplicationController if @extension.getId()=='violations' render_violations() render_partial_index() + elsif @extension.getId()=='issues' + render_issues() + render :partial => 'index_issues' elsif (@extension.getId()=='coverage') render_coverage() render_partial_index() @@ -427,6 +430,87 @@ class ResourceController < ApplicationController end end + def render_issues + load_sources() + @display_issues = true + @global_issues = [] + @expandable = (@lines != nil) + @filtered = !@expanded + rule_param = params[:rule] + + options = {'components' => @resource.key} + + # TODO + #if rule_param.blank? && params[:metric] + # metric = Metric.by_id(params[:metric]) + # if metric && (metric.name=='active_reviews' || metric.name=='unassigned_reviews' || metric.name=='unplanned_reviews' || metric.name=='false_positive_reviews' || + # metric.name=='unreviewed_violations' || metric.name=='new_unreviewed_violations') + # rule_param = metric.name.gsub(/new_/, '') + # + # # hack to select the correct option in the rule filter select-box + # params[:rule] = rule_param + # end + #end + + if !rule_param.blank? && rule_param != 'all' + #if rule_param=='false_positive_reviews' + # options[:switched_off]=true + # + #elsif rule_param=='active_reviews' + # options[:review_statuses]=[Review::STATUS_OPEN, Review::STATUS_REOPENED, nil] + # + #elsif rule_param=='unassigned_reviews' + # options[:review_statuses]=[Review::STATUS_OPEN, Review::STATUS_REOPENED, nil] + # options[:review_assignee_id]=nil + # + #elsif rule_param=='unplanned_reviews' + # options[:review_statuses]=[Review::STATUS_OPEN, Review::STATUS_REOPENED, nil] + # options[:planned]=false + # + #elsif rule_param=='unreviewed_violations' + # options[:review_statuses]=[nil] + + if Sonar::RulePriority.id(rule_param) + options['severities'] = rule_param + + else + # TODO + rule = Rule.by_key_or_id(rule_param) + #options[:ruleKey] = rule.key + #options[:ruleRepository] = rule.key + end + end + + + if @period + date = @snapshot.period_datetime(@period) + if date + # TODO + #options[:created_after]=date.advance(:minutes => 1) + end + end + + user = current_user ? current_user.id : nil + issues = Api.issues.find(options, user).issues + issues.each do |issue| + # sorted by severity => from blocker to info + if @lines && issue.line && issue.line>0 && issue.line<=@lines.size + @lines[issue.line-1].add_issue(issue) + else + @global_issues<0 end + def issues? + @issues && @issues.size>0 + end + def violation_severity if @violations && @violations.size>0 @violations[0].failure_level @@ -487,6 +582,14 @@ class ResourceController < ApplicationController end end + def issue_severity + if @issues && @issues.size>0 + @issues[0].severity + else + nil + end + end + def after(date) if date && @datetime @datetime.after(date) diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/resource_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/resource_helper.rb index 592a7821e21..c0b0c1dfc80 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/resource_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/resource_helper.rb @@ -42,4 +42,9 @@ module ResourceHelper '(' + (value1.to_i - value2.to_i).to_s + '/' + value1 + ')' end + + def to_date(java_date) + java_date ? Api::Utils.format_datetime(Time.at(java_date.time/1000)) : nil + end + end \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb new file mode 100644 index 00000000000..4f8350fdd6c --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb @@ -0,0 +1,192 @@ +<%= render :partial => 'header' -%> + +
+
+

+ <% + profile_measure=@snapshot.measure(Metric::PROFILE) + %> + <% if profile_measure %>Profile <%= link_to profile_measure.data, :controller => '/rules_configuration', :action => 'index', :id => profile_measure.value.to_i %> + <% end %> + <% if @snapshot.project_snapshot.periods? %> +
+ <%= dropdown_tag "period", period_select_option_tags(@snapshot, 'small'), { + :width => '200px', + }, {:id => 'select-comparison', :onchange => 'submit()'} -%> +
+ <% end %> +

+
+
+ + + + + + +
+ + <% + value_column = (@period ? "variation_value_#{@period}" : 'value') + max = 0 + if @period + blocker_issues=@snapshot.measure('new_blocker_issues') + critical_issues=@snapshot.measure('new_critical_issues') + major_issues=@snapshot.measure('new_major_issues') + minor_issues=@snapshot.measure('new_minor_issues') + info_issues=@snapshot.measure('new_info_issues') + else + blocker_issues=@snapshot.measure('blocker_issues') + critical_issues=@snapshot.measure('critical_issues') + major_issues=@snapshot.measure('major_issues') + minor_issues=@snapshot.measure('minor_issues') + info_issues=@snapshot.measure('info_issues') + end + + [blocker_issues, critical_issues, major_issues, minor_issues, info_issues].each do |m| + value = measure_or_variation_value(m) + max = value if value && value>max + end + %> +

<%= message('issues_drilldown.col.severity') -%>

+ + <%= render :partial => 'severity', :locals => {:css => 'even', :severity => 'BLOCKER', :max => max, :measure => blocker_issues} %> + <%= render :partial => 'severity', :locals => {:css => 'odd', :severity => 'CRITICAL', :max => max, :measure => critical_issues} %> + <%= render :partial => 'severity', :locals => {:css => 'even', :severity => 'MAJOR', :max => max, :measure => major_issues} %> + <%= render :partial => 'severity', :locals => {:css => 'odd', :severity => 'MINOR', :max => max, :measure => minor_issues} %> + <%= render :partial => 'severity', :locals => {:css => 'even', :severity => 'INFO', :max => max, :measure => info_issues} %> +
+
+

<%= message('issues_drilldown.col.rule') -%>

+ +
+ + <% + max=0 + rule_index=0 + already_selected=false + @rule_measures.each do |m| + value = m.send(value_column) if m + max=value if value && value>max + end + @rule_measures.sort do |x, y| + val=y.rule_priority<=>x.rule_priority + if val==0 + x_value=x.send(value_column) + y_value=y.send(value_column) + y_value <=> x_value + else + val + end + end.each do |rule_measure| + value = rule_measure.send(value_column) + next if value.nil? || value==0 + rule=rule_measure.rule + clazz = cycle('even', 'odd', :name => 'rules') + selected = !already_selected && @rule && @rule.id==rule_measure.rule_id && (@rule_severity.nil? || @rule_severity==rule_measure.severity) + already_selected = true if selected + clazz = clazz + ' selected' if selected + rule_index+=1 + %> + + + + + + + <% end %> + + <% if rule_index==0 %> + + + + <% end %> +
+ " title="<%= message('issues_drilldown.click_for_more_on_x', :params => [rule.plugin_name, rule.plugin_rule_key]) -%>" + 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' -%>"> + + + <%= link_to(rule.name, {:controller => :drilldown, :action => :issues, :id => @resource.id, :rule => (selected ? nil : rule.key), + :rule_sev => (selected ? nil : rule_measure.severity), :sid => nil, :severity => @severity, :period => @period, + :rids => (selected ? nil : @selected_rids)}, :title => "#{rule.plugin_name}: #{rule.plugin_rule_key}") -%> + + <%= @period ? format_variation(rule_measure, :period => @period, :style => 'light') : rule_measure.formatted_value -%> + + <%= barchart(:width => 70, :percent => (100 * value / max).to_i, :color => (@period ? '#cc0000' : '#777')) if max>0 %> +
<%= message('issues_drilldown.no_issue') -%>
+
+
+<% + paths=[] + rids=[] + first_column=true + @drilldown.columns.each_with_index do |column, index| +%> + <% if first_column %> + + + <% end %> + + <% if column.switch? || index==@drilldown.columns.size-1 %> + +
+
+ + <% + column.measures.each_with_index do |measure, row_index| + resource=column.resource(measure) + clazz = cycle('even', 'odd', :name => "col_#{index}") + selected = column.selected_snapshot && column.selected_snapshot.project_id==resource.id + if selected + clazz += ' selected' + paths << [h(resource.name), @selected_rids-[resource.id]] + end + %> + + + + + <% end %> +
+ <% if resource.source_code? %> + <%= image_tag 'new-window-16.gif', :alt => message('new_window') -%> + <% else %> + <%= link_to(image_tag('zoom.png'), {:id => resource.id}, {:class => 'nolink'}) %> + <% end %> + <%= qualifier_icon(resource) -%>  + <% if resource.source_code? %> + <%= h resource.name(false) %> + <% else %> + <%= link_to(h(resource.name), {:only_path => true, :overwrite_params => {:rids => (selected ? rids-[resource.id] : rids+[resource.id])}}) -%> + <% end %> + + <%= @period ? format_variation(measure, :period => @period, :style => 'light') : measure.formatted_value -%> +
+
+
+ <% end + first_column = column.switch? + rids< + + + +
+ +<%= render :partial => 'footer' -%> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index 8c319dad608..e3edb654cce 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -85,6 +85,9 @@
  • "><%= message('violations_drilldown.page') -%>
  • +
  • + "><%= message('issues_drilldown.page') -%> +
  • <% controller.java_facade.getPages(Navigation::SECTION_RESOURCE, @project.scope, @project.qualifier, @project.language, @project.last_snapshot.metric_keys.to_java(:string)).each do |page| page_url = (page.isController() ? "#{page.getId()}?id=#{@project.id}" : "/plugins/resource/#{@project.id}?page=#{page.getId()}") %> diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_issues.html.erb new file mode 100644 index 00000000000..28b68d2b06a --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_issues.html.erb @@ -0,0 +1,70 @@ +
    + <% if @period && measure('new_issues') %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    <%= format_variation('new_issues', :default => 0, :period => @period, :style => 'none') -%> <%= message('new_issues').downcase -%> <%= image_tag 'priority/BLOCKER.png' -%><%= message('blocker') -%>:<%= format_variation('new_blocker_issues', :default => 0, :period => @period, :style => 'none') -%> <%= image_tag 'priority/CRITICAL.png' -%><%= message('critical') -%>:<%= format_variation('new_critical_issues', :default => 0, :period => @period, :style => 'none') -%> <%= image_tag 'priority/MAJOR.png' -%><%= message('major') -%>:<%= format_variation('new_major_issues', :default => 0, :period => @period, :style => 'none') -%> <%= image_tag 'priority/MINOR.png' -%><%= message('minor') -%>:<%= format_variation('new_minor_issues', :default => 0, :period => @period, :style => 'none') -%> <%= image_tag 'priority/INFO.png' -%><%= message('info') -%>:<%= format_variation('new_info_issues', :default => 0, :period => @period, :style => 'none') -%>
    + + <% else %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    <%= format_measure('issues', :default => 0) -%> <%= message('issues').downcase -%> <%= image_tag 'priority/BLOCKER.png' -%><%= message('blocker') -%>:<%= format_measure('blocker_issues', :default => 0) -%> <%= image_tag 'priority/CRITICAL.png' -%><%= message('critical') -%>:<%= format_measure('critical_issues', :default => 0) -%> <%= image_tag 'priority/MAJOR.png' -%><%= message('major') -%>:<%= format_measure('major_issues', :default => 0) -%> <%= image_tag 'priority/MINOR.png' -%><%= message('minor') -%>:<%= format_measure('minor_issues', :default => 0) -%> <%= image_tag 'priority/INFO.png' -%><%= message('info') -%>:<%= format_measure('info_issues', :default => 0) -%>
    + <% end %> + + <%= render :partial => 'options_issues' -%> + +
    + 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 new file mode 100644 index 00000000000..09df64a7341 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb @@ -0,0 +1,172 @@ +
    +
    + <%= render :partial => 'tabs' -%> +
    +
    + <%= render :partial => "resource/header_#{@extension.getId()}" -%> + + <% if @display_issues && @global_issues && @global_issues.size>0 -%> + + <% @global_issues.each do |issue| %> + + + + <% end %> +
    <%= render :partial => 'issue', :locals => {:issue => issue} -%>
    + <% end %> + + <% if @lines && @lines.size>0 %> + + + <% + colspan=2 + gray_colspan=1 + white_colspan=0 + # TODO + #if @display_manual_violation_form + # colspan+=1 + # gray_colspan+=1 + #end + if @scm_available + colspan+=1 + gray_colspan+=1 + end + if @display_coverage + colspan+=2 + white_colspan+=2 + end + + current_revision=nil + previous_hidden=false + first_section=true + has_displayed_lines=false + @lines.each_with_index do |line, index| + if line.hidden? && !@expanded + previous_hidden=true + next + end + + if previous_hidden && !first_section + current_revision=nil + %> + + + + <% + end + previous_hidden=false + first_section=false + + status=hits_status=conditions_status='' + if line.highlighted? + has_displayed_lines=true + if @display_coverage && line.hits + hits_status=(line.hits>0 ? 'ok' : 'ko') + if line.conditions && line.conditions>0 && line.covered_conditions + if line.covered_conditions==0 + status='ko' + conditions_status='ko' + elsif line.covered_conditions==line.conditions + status='' + conditions_status='ok' + else + conditions_status='warn' + status='warn' + end + elsif line.hits + status=(line.hits>0 ? '' : 'ko') + end + elsif @display_issues && line.issues? + status="ko" + end + end + %> + + <% + # TODO + #if @display_manual_violation_form + if false + %> + + <% + end + if @scm_available + if current_revision!=line.revision + current_revision=line.revision + title = "Revision #{h(line.revision)}" + %> + + <% else %> + + <% end + end + %> + + + <% if @display_coverage %> + <% if line.highlighted? %> + + + <% else %> + + + <% end %> + <% end %> + + + <% if @display_issues && line.issues? %> + + <% + # TODO + #if @display_manual_violation_form + if false + %> + + <% end + if @scm_available %> + + <% end %> + + + + <% end %> + <% end %> +
    + <%= Java::OrgSonarApiUtils::DateUtils.formatDate(line.datetime) if line.datetime -%> + <%= h(line.author) -%><%= index + 1 -%> + + <% if line.covered_lines > 0 %> + <%= line.covered_lines -%> + <% end %> + + <% if line.deprecated_conditions_label -%> + <%= line.deprecated_conditions_label -%> + <% elsif line.conditions && line.conditions>0 -%> + <%= line.covered_conditions -%>/<%= line.conditions -%> + <% end %> + +
    <%= line.source -%>
    +
    + <% line.issues.each_with_index do |issue, index| %> + <%= render :partial => 'issue', :locals => {:issue => issue} -%> + <% if index < line.issues.size-1 %> +   + <% end %> + <% end %> +
    + + <% if @filtered && !has_displayed_lines %> +

    <%= message('no_lines_match_your_filter_criteria') -%>

    + <% end %> + + <% end %> + + <% if @duplication_groups %> + <%= render :partial => 'duplications' -%> + <% end %> + +
    +
    + + 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 new file mode 100644 index 00000000000..33ad50c0861 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb @@ -0,0 +1,79 @@ +
    +
    +
    + <% + # TODO + #if violation.review + if false + %> + + <% end %> + + +   + + + <%= h(Rule.by_key_or_id(issue.rule_repository_key + ':' + issue.rule_key).name) -%> + +   + <%= image_tag 'sep12.png' -%> +   + + <% if issue.created_at %> + <%= distance_of_time_in_words_to_now(to_date(issue.created_at)) -%> +   + <% end %> + <% + # TODO + #if violation.switched_off? + if false + %> + <%= image_tag 'sep12.png' -%> +   + <%= message('false_positive') -%> +   + <% end %> + <% + # TODO + #if violation.review && violation.review.resolved? + if false + %> + <%= image_tag 'sep12.png' -%> +   + <%= message('reviews.status.RESOLVED') -%> +   + <% end %> + <% + # TODO display assignee name instead of login + #if violation.review && violation.review.assignee_id + if false + %> + <%= image_tag 'sep12.png' -%> +   + <%= message('assigned_to') -%> <%= h(issue.assignee) -%> +   + <% end %> + <% + # TODO + #if violation.review && violation.review.planned? + if false + %> + <%= image_tag 'sep12.png' -%> +   + <%= message('reviews.planned_for_x', :params => h(violation.review.action_plan.name)) -%> +   + <% end %> + +
    + +
    +
    + <%= issue.message || ' ' -%> +
    +
    + +
    + +
    \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_options_issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_options_issues.html.erb new file mode 100644 index 00000000000..8713a75e997 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_options_issues.html.erb @@ -0,0 +1,117 @@ +<% display_options = @scm_available || @expandable || @filtered || @display_issues + if display_options +%> + +
    + +
    + + + + + + + <% + first=true + if @expandable %> + + <% first=false + end %> + + <% if @scm_available && !@display_issues && @snapshot.project_snapshot.periods? %> + + <% + first=false + end %> + + <% if @display_issues %> + + + + <% first=false + end %> + + <% if @display_coverage %> + + + <% if @coverage_filter=='lines_covered_per_test' %> + + <% end %> + + <% first=false + end %> + +
    + onclick="applyOptions(this)"/> + + + + + + <%= render :partial => 'rules_filter_issues' -%> + + + +
    +
    +
    +<% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_rules_filter_issues.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_rules_filter_issues.html.erb new file mode 100644 index 00000000000..9507a049fd4 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/resource/_rules_filter_issues.html.erb @@ -0,0 +1,112 @@ +<% + if @period + blocker_issues = @snapshot.measure('new_blocker_issues') + critical_issues = @snapshot.measure('new_critical_issues') + major_issues = @snapshot.measure('new_major_issues') + minor_issues = @snapshot.measure('new_minor_issues') + info_issues = @snapshot.measure('new_info_issues') + metrics = [ + Metric.by_key("new_blocker_issues"), + Metric.by_key("new_critical_issues"), + Metric.by_key("new_major_issues"), + Metric.by_key("new_minor_issues"), + Metric.by_key("new_info_issues") + ] + else + blocker_issues = @snapshot.measure('blocker_issues') + critical_issues = @snapshot.measure('critical_issues') + major_issues = @snapshot.measure('major_issues') + minor_issues = @snapshot.measure('minor_issues') + info_issues = @snapshot.measure('info_issues') + metrics = [ + Metric.by_key("blocker_issues"), + Metric.by_key("critical_issues"), + Metric.by_key("major_issues"), + Metric.by_key("minor_issues"), + Metric.by_key("info_issues") + ] + end + + rule_counts=Hash.new(0) + @snapshot.rule_measures(metrics).each do |rule_measure| + count=(@period ? rule_measure.variation(@period) : rule_measure.value) + if count && count>0 + rule_counts[rule_measure.rule] += count.to_i + end + end + + rule_options=[] + rule_counts.keys.sort.each do |rule| + label = "#{rule.name} (#{rule_counts[rule]})" + rule_options<<[label, rule.id] + end +%> + + \ No newline at end of file diff --git a/sonar-server/src/test/java/org/sonar/server/ui/ViewsTest.java b/sonar-server/src/test/java/org/sonar/server/ui/ViewsTest.java index 82e7081d493..08809e58d8f 100644 --- a/sonar-server/src/test/java/org/sonar/server/ui/ViewsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/ui/ViewsTest.java @@ -66,7 +66,7 @@ public class ViewsTest { public void should_get_resource_viewers() { final Views views = new Views(VIEWS); List resourceViewers = views.getPages(NavigationSection.RESOURCE_TAB); - assertThat(resourceViewers.size()).isEqualTo(1 + 4 /* default */); + assertThat(resourceViewers.size()).isEqualTo(1 + 5 /* default */); assertThat(resourceViewers.contains(new ViewProxy(FAKE_TAB))).isEqualTo(true); } -- 2.39.5