summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@gmail.com>2013-04-16 11:11:49 +0200
committerJulien Lancelot <julien.lancelot@gmail.com>2013-04-16 11:11:49 +0200
commitc1b8246f1512e42dde50486d059d906b98c6eec2 (patch)
treef4b4b0c0403c53b08746faa09c0ba73c5f5115f8
parent9f8e526ef4cf4cdbeb497378bb893910eeebb9b7 (diff)
downloadsonarqube-c1b8246f1512e42dde50486d059d906b98c6eec2.tar.gz
sonarqube-c1b8246f1512e42dde50486d059d906b98c6eec2.zip
SONAR-3755 Add first version of Issues drilldown
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/issue/IssuesDecorator.java2
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/DefaultPages.java38
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb3
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/drilldown_controller.rb88
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb7
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/resource_controller.rb105
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/helpers/resource_helper.rb5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/drilldown/issues.html.erb192
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb3
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_header_issues.html.erb70
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_index_issues.html.erb172
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_issue.html.erb79
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_options_issues.html.erb117
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/resource/_rules_filter_issues.html.erb112
-rw-r--r--sonar-server/src/test/java/org/sonar/server/ui/ViewsTest.java2
16 files changed, 988 insertions, 14 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index 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<Issue> 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() {
}
@@ -129,6 +123,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})
@UserRole(UserRole.CODEVIEWER)
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?<parameters>
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<<issue
+ end
+ end
+
+ if !@expanded && @lines
+ filter_lines { |line| line.issues? }
+ end
+
+ # TODO
+ #@review_screens_by_vid=nil
+ #if current_user && has_role?(:user, @resource)
+ # @review_screens_by_vid = RuleFailure.available_java_screens_for_violations(violations, @resource, current_user)
+ #end
+ end
def render_source
load_sources()
@@ -463,7 +547,8 @@ class ResourceController < ApplicationController
end
class Line
- attr_accessor :index, :source, :revision, :author, :datetime, :violations, :hits, :conditions, :covered_conditions, :hidden, :highlighted, :deprecated_conditions_label, :covered_lines
+ attr_accessor :index, :source, :revision, :author, :datetime, :violations, :issues, :hits, :conditions, :covered_conditions, :hidden, :highlighted,
+ :deprecated_conditions_label, :covered_lines
def initialize(source)
@source=source
@@ -475,10 +560,20 @@ class ResourceController < ApplicationController
@visible=true
end
+ def add_issue(issue)
+ @issues||=[]
+ @issues<<issue
+ @visible=true
+ end
+
def violations?
@violations && @violations.size>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' -%>
+
+<div class="line-block">
+ <div id="snapshot_title" class="page_title">
+ <h4>
+ <%
+ 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? %>
+ <form method="GET" action="<%= url_for :only_path=>true, :overwrite_params => {:period => nil} -%>" style="display: inline" class="spacer-left">
+ <%= dropdown_tag "period", period_select_option_tags(@snapshot, 'small'), {
+ :width => '200px',
+ }, {:id => 'select-comparison', :onchange => 'submit()'} -%>
+ </form>
+ <% end %>
+ </h4>
+ </div>
+</div>
+
+<table class="width100 spacer-bottom" cellpadding="0" cellspacing="0" id="columns">
+ <tr>
+ <td align="left" width="1%" nowrap class="spacer-right" valign="top">
+
+ <%
+ 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
+ %>
+ <h3><%= message('issues_drilldown.col.severity') -%></h3>
+ <table class="spacedicon" style="border: 1px solid #ccc;">
+ <%= 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} %>
+ </table>
+ </td>
+ <td align="left" style="white-space: normal;" valign="top">
+ <h3><%= message('issues_drilldown.col.rule') -%></h3>
+
+ <div class="scrollable">
+ <table class="spacedicon" width="100%" id="col_rules">
+ <%
+ 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
+ %>
+ <tr class="<%= clazz -%>">
+ <td width="1%" nowrap>
+ <a id="<%= "rule#{rule_index}" -%>" 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' -%>">
+ <img src="<%= ApplicationController.root_context -%>/images/priority/<%= rule_measure.rule_priority -%>.png"/></a>
+ </td>
+ <td>
+ <%= 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}") -%>
+ </td>
+ <td class="right" nowrap="nowrap">
+ <span><%= @period ? format_variation(rule_measure, :period => @period, :style => 'light') : rule_measure.formatted_value -%></span>
+ </td>
+ <td class="left last">
+ <%= barchart(:width => 70, :percent => (100 * value / max).to_i, :color => (@period ? '#cc0000' : '#777')) if max>0 %>
+ </td>
+ </tr>
+ <% end %>
+
+ <% if rule_index==0 %>
+ <tr class="even">
+ <td><%= message('issues_drilldown.no_issue') -%></td>
+ </tr>
+ <% end %>
+ </table>
+ </div>
+ </td>
+ </tr>
+</table>
+<%
+ paths=[]
+ rids=[]
+ first_column=true
+ @drilldown.columns.each_with_index do |column, index|
+%>
+ <% if first_column %>
+ <table class="spacer-bottom" style="width: 100%">
+ <tr>
+ <% end %>
+ <td class="<%= 'spacer-left' unless first_column -%>" nowrap>
+ <div class="scrollable" id="col_<%= index -%>">
+ <table class="spaced">
+ <%
+ 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
+ %>
+ <tr class="<%= clazz -%>" id="row_<%= index -%>_<%= row_index -%>">
+ <td nowrap>
+ <% if resource.source_code? %>
+ <a href="<%= url_for :controller => 'resource', :action => 'index', :id => resource.key, :period => params[:period], :metric => @metric ? @metric.id : nil,
+ :rule => @rule ? @rule.id : @severity, :display_title => 'true' -%>"
+ onclick="window.open(this.href,'resource-<%= resource.key.parameterize -%>','height=800,width=900,scrollbars=1,resizable=1');return false;"
+ id="popup-<%= resource.key.parameterize -%>"
+ target="_blank"><%= image_tag 'new-window-16.gif', :alt => message('new_window') -%></a>
+ <% else %>
+ <%= link_to(image_tag('zoom.png'), {:id => resource.id}, {:class => 'nolink'}) %>
+ <% end %>
+ <%= qualifier_icon(resource) -%>&nbsp;
+ <% if resource.source_code? %>
+ <a href="#" onclick="$j('#col_<%= index -%> tr').removeClass('selected'); $j('#row_<%= index -%>_<%= row_index -%>').addClass('selected'); d(<%= resource.id -%>);"
+ alt="<%= h resource.name(true) -%>" title="<%= h resource.name(true) -%>"><%= h resource.name(false) %></a>
+ <% else %>
+ <%= link_to(h(resource.name), {:only_path => true, :overwrite_params => {:rids => (selected ? rids-[resource.id] : rids+[resource.id])}}) -%>
+ <% end %>
+ </td>
+ <td class="right" nowrap>
+ <%= @period ? format_variation(measure, :period => @period, :style => 'light') : measure.formatted_value -%>
+ </td>
+ </tr>
+ <% end %>
+ </table>
+ </div>
+ </td>
+ <% if column.switch? || index==@drilldown.columns.size-1 %>
+ </tr>
+ </table>
+ <% end
+ first_column = column.switch?
+ rids<<column.selected_snapshot.project_id if column.selected_snapshot
+ end
+ %>
+
+<script>
+ $$('#col_rules tr.selected').each(function (item) {
+ item.scrollIntoView(true);
+ });
+ <% for i in 0...@drilldown.columns.size do %>
+ $$('#col_<%= i -%> tr.selected').each(function (item) {
+ item.scrollIntoView(true);
+ });
+ <% end %>
+</script>
+
+<div id="accordion-panel"/>
+
+<%= 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 @@
<li class="<%= 'active' if request.request_uri.include?('/drilldown/violations') -%>">
<a href="<%= ApplicationController.root_context -%>/drilldown/violations/<%= @project.id -%><%= "?"+period_param if period_param -%>"><%= message('violations_drilldown.page') -%></a>
</li>
+ <li class="<%= 'active' if request.request_uri.include?('/drilldown/issues') -%>">
+ <a href="<%= ApplicationController.root_context -%>/drilldown/issues/<%= @project.id -%><%= "?"+period_param if period_param -%>"><%= message('issues_drilldown.page') -%></a>
+ </li>
<% 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 @@
+<div class="violations_header tab_header">
+ <% if @period && measure('new_issues') %>
+ <table>
+ <tr>
+ <td><span class="big"><%= format_variation('new_issues', :default => 0, :period => @period, :style => 'none') -%></span>&nbsp;<%= message('new_issues').downcase -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/BLOCKER.png' -%></td>
+ <td class="name"><%= message('blocker') -%>:</td>
+ <td class="value"><%= format_variation('new_blocker_issues', :default => 0, :period => @period, :style => 'none') -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/CRITICAL.png' -%></td>
+ <td class="name"><%= message('critical') -%>:</td>
+ <td class="value"><%= format_variation('new_critical_issues', :default => 0, :period => @period, :style => 'none') -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/MAJOR.png' -%></td>
+ <td class="name"><%= message('major') -%>:</td>
+ <td class="value"><%= format_variation('new_major_issues', :default => 0, :period => @period, :style => 'none') -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/MINOR.png' -%></td>
+ <td class="name"><%= message('minor') -%>:</td>
+ <td class="value"><%= format_variation('new_minor_issues', :default => 0, :period => @period, :style => 'none') -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/INFO.png' -%></td>
+ <td class="name"><%= message('info') -%>:</td>
+ <td class="value"><%= format_variation('new_info_issues', :default => 0, :period => @period, :style => 'none') -%></td>
+ </tr>
+ </table>
+
+ <% else %>
+ <table class="sourceHeader">
+ <tr>
+ <td nowrap><span class="big"><%= format_measure('issues', :default => 0) -%></span>&nbsp;<%= message('issues').downcase -%></td>
+
+ <td class="sep"> </td>
+ <td nowrap><%= image_tag 'priority/BLOCKER.png' -%></td>
+ <td class="name"><%= message('blocker') -%>:</td>
+ <td class="value"><%= format_measure('blocker_issues', :default => 0) -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/CRITICAL.png' -%></td>
+ <td class="name"><%= message('critical') -%>:</td>
+ <td class="value"><%= format_measure('critical_issues', :default => 0) -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/MAJOR.png' -%></td>
+ <td class="name"><%= message('major') -%>:</td>
+ <td class="value"><%= format_measure('major_issues', :default => 0) -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/MINOR.png' -%></td>
+ <td class="name"><%= message('minor') -%>:</td>
+ <td class="value"><%= format_measure('minor_issues', :default => 0) -%></td>
+
+ <td class="sep"> </td>
+ <td><%= image_tag 'priority/INFO.png' -%></td>
+ <td class="name"><%= message('info') -%>:</td>
+ <td class="value"><%= format_measure('info_issues', :default => 0) -%></td>
+ </tr>
+ </table>
+ <% end %>
+
+ <%= render :partial => 'options_issues' -%>
+
+</div>
+
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 @@
+<div>
+ <div class="accordion-item-header">
+ <%= render :partial => 'tabs' -%>
+ </div>
+ <div class="accordion-item-body">
+ <%= render :partial => "resource/header_#{@extension.getId()}" -%>
+
+ <% if @display_issues && @global_issues && @global_issues.size>0 -%>
+ <table class="global_violations" cellpadding="0" cellspacing="0" border="0">
+ <% @global_issues.each do |issue| %>
+ <tr>
+ <td><%= render :partial => 'issue', :locals => {:issue => issue} -%></td>
+ </tr>
+ <% end %>
+ </table>
+ <% end %>
+
+ <% if @lines && @lines.size>0 %>
+
+ <table id="sources" class="sources2 code" cellpadding="0" cellspacing="0" border="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
+ %>
+ <tr>
+ <td colspan="<%= colspan -%>" class="new_section"></td>
+ </tr>
+ <%
+ 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
+ %>
+ <tr class="row pos<%= index+1 -%>">
+ <%
+ # TODO
+ #if @display_manual_violation_form
+ if false
+ %>
+ <td class="plus"><a onclick="return sVF(this, <%= @resource.id -%>,<%= index + 1 -%>,<%= gray_colspan -%>,<%= white_colspan -%>)"></a></td>
+ <%
+ end
+ if @scm_available
+ if current_revision!=line.revision
+ current_revision=line.revision
+ title = "Revision #{h(line.revision)}"
+ %>
+ <td class="scm revision">
+ <span class="date"><a href="#" title="<%= title -%>" alt="<%= title -%>"><%= Java::OrgSonarApiUtils::DateUtils.formatDate(line.datetime) if line.datetime -%></a></span>
+ <span class="author"><%= h(line.author) -%></span></td>
+ <% else %>
+ <td class="scm"></td>
+ <% end
+ end
+ %>
+ <td class="lid L<%= index+1 -%>"><a name="L<%= index+1 -%>" href="#L<%= index+1 -%>"><%= index + 1 -%></a>
+ </td>
+
+ <% if @display_coverage %>
+ <% if line.highlighted? %>
+ <td class="ind <%= hits_status -%>" title="<%= message('coverage_viewer.line_covered_by_x_tests', {:params => line.covered_lines.to_s}) if line.covered_lines > 0 -%>">
+ <% if line.covered_lines > 0 %>
+ <a href="<%= ApplicationController.root_context -%>/test/testable/<%= h @snapshot.resource.key -%>?line=<%= index+1 -%>"
+ class="sources-testable-link-<%= index+1 -%>" onclick="openAccordionItem(this.href, this); return false;"><%= line.covered_lines -%></a>
+ <% end %>
+ </td>
+ <td class="ind <%= conditions_status -%>">
+ <% if line.deprecated_conditions_label -%>
+ <%= line.deprecated_conditions_label -%>
+ <% elsif line.conditions && line.conditions>0 -%>
+ <span title="<%= h message('coverage_viewer.x_covered_branches', {:params => line.covered_conditions.to_s})-%>"><%= line.covered_conditions -%>/<%= line.conditions -%></span>
+ <% end %>
+ </td>
+ <% else %>
+ <td class="ind"></td>
+ <td class="ind"></td>
+ <% end %>
+ <% end %>
+ <td class="line <%= status -%>">
+ <pre><%= line.source -%></pre>
+ </td>
+ </tr>
+ <% if @display_issues && line.issues? %>
+ <tr>
+ <%
+ # TODO
+ #if @display_manual_violation_form
+ if false
+ %>
+ <td class="gray"></td>
+ <% end
+ if @scm_available %>
+ <td class="scm"></td>
+ <% 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 %>
+ &nbsp;
+ <% end %>
+ <% end %>
+ </td>
+ </tr>
+ <% end %>
+ <% end %>
+ </table>
+
+ <% if @filtered && !has_displayed_lines %>
+ <p style="padding: 10px"><%= message('no_lines_match_your_filter_criteria') -%></p>
+ <% end %>
+
+ <% end %>
+
+ <% if @duplication_groups %>
+ <%= render :partial => 'duplications' -%>
+ <% end %>
+
+ </div>
+</div>
+
+
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 @@
+<div id="vId<%= issue.key -%>">
+ <div class="violation">
+ <div class="vtitle">
+ <%
+ # TODO
+ #if violation.review
+ if false
+ %>
+ <div class="review_permalink">
+ </div>
+ <% end %>
+
+ <img src="<%= ApplicationController.root_context -%>/images/priority/<%= issue.severity -%>.png">
+ &nbsp;
+ <span class="rulename">
+ <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 => issue.rule_key, :layout => 'false' -%>">
+ <%= h(Rule.by_key_or_id(issue.rule_repository_key + ':' + issue.rule_key).name) -%></a>
+ </span>
+ &nbsp;
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+
+ <% if issue.created_at %>
+ <span><%= distance_of_time_in_words_to_now(to_date(issue.created_at)) -%></span>
+ &nbsp;
+ <% end %>
+ <%
+ # TODO
+ #if violation.switched_off?
+ if false
+ %>
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+ <span class="falsePositive"><%= message('false_positive') -%></span>
+ &nbsp;
+ <% end %>
+ <%
+ # TODO
+ #if violation.review && violation.review.resolved?
+ if false
+ %>
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+ <span class="reviewResolved"><%= message('reviews.status.RESOLVED') -%></span>
+ &nbsp;
+ <% end %>
+ <%
+ # TODO display assignee name instead of login
+ #if violation.review && violation.review.assignee_id
+ if false
+ %>
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+ <%= message('assigned_to') -%> <%= h(issue.assignee) -%>
+ &nbsp;
+ <% end %>
+ <%
+ # TODO
+ #if violation.review && violation.review.planned?
+ if false
+ %>
+ <%= image_tag 'sep12.png' -%>
+ &nbsp;
+ <%= message('reviews.planned_for_x', :params => h(violation.review.action_plan.name)) -%>
+ &nbsp;
+ <% end %>
+
+ </div>
+
+ <div class="discussionComment first">
+ <div id="vMsg<%= issue.key -%>">
+ <%= issue.message || '&nbsp;' -%>
+ </div>
+ </div>
+
+ </div>
+
+</div> \ 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
+%>
+
+ <div class="source_options">
+ <script>
+ applyOptions = function (elt) {
+ var currentForm = $j(elt).closest('.options-form');
+ var params = currentForm.serialize();
+ var url = '<%= ApplicationController.root_context -%>/resource/index/<%= @resource.key %>?display_title=<%= params[:display_title].to_s -%>&'+ params;
+ openAccordionItem(url, elt, true);
+ return true;
+ };
+ </script>
+ <form method="GET" action="<%= url_for :controller => 'resource', :action => 'index', :id => @resource.key -%>" class="options-form">
+ <input type="hidden" name="tab" value="<%= params[:tab] -%>"/>
+ <input type="hidden" name="metric" value="<%= params[:metric] -%>"/>
+ <input type="hidden" name="period" value="<%= params[:period] -%>"/>
+
+ <table>
+ <tr>
+ <%
+ first=true
+ if @expandable %>
+ <td class="<%= 'first' if first -%>">
+ <input type="checkbox" value="true" name="expand" <%= 'checked' if @expanded -%> onclick="applyOptions(this)"/>
+ <label for="expand"><%= message('full_source') -%></label>
+ </td>
+ <% first=false
+ end %>
+
+ <% if @scm_available && !@display_issues && @snapshot.project_snapshot.periods? %>
+ <td class="<%= 'first' if first -%>">
+ <select name="period" class="period" onchange="applyOptions(this)">
+ <option value=""><%= message('time_changes') -%>...</option>
+ <%= period_select_options(@snapshot, 1) -%>
+ <%= period_select_options(@snapshot, 2) -%>
+ <%= period_select_options(@snapshot, 3) -%>
+ <%= period_select_options(@snapshot, 4) -%>
+ <%= period_select_options(@snapshot, 5) -%>
+ </select>
+ </td>
+ <%
+ first=false
+ end %>
+
+ <% if @display_issues %>
+ <td class="<%= 'first' if first -%>">
+ <select name="period" class="period" onchange="applyOptions(this)">
+ <option value=""><%= message('time_changes') -%>...</option>
+ <%= violation_period_select_options(@snapshot, 1) -%>
+ <%= violation_period_select_options(@snapshot, 2) -%>
+ <%= violation_period_select_options(@snapshot, 3) -%>
+ <%= violation_period_select_options(@snapshot, 4) -%>
+ <%= violation_period_select_options(@snapshot, 5) -%>
+ </select>
+ </td>
+
+ <td class="<%= 'first' if first -%>"><%= render :partial => 'rules_filter_issues' -%></td>
+ <% first=false
+ end %>
+
+ <% if @display_coverage %>
+ <td class="<%= 'first' if first -%>">
+ <select class="coverage_filter" name="coverage_filter" onchange="applyOptions(this)">
+ <optgroup label="<%= h message('coverage_viewer.unit_tests') -%>">
+ <option value="lines_to_cover" <%= 'selected' if @coverage_filter=='lines_to_cover' -%>><%= Metric.name_for('lines_to_cover') -%></option>
+ <option value="uncovered_lines" <%= 'selected' if @coverage_filter=='uncovered_lines' -%>><%= Metric.name_for('uncovered_lines') -%></option>
+ <option value="conditions_to_cover" <%= 'selected' if @coverage_filter=='conditions_to_cover' -%>><%= Metric.name_for('conditions_to_cover') -%></option>
+ <option value="uncovered_conditions" <%= 'selected' if @coverage_filter=='uncovered_conditions' -%>><%= Metric.name_for('uncovered_conditions') -%></option>
+ </optgroup>
+ <% if @display_it_coverage %>
+ <optgroup label="<%= h message('coverage_viewer.integration_tests') -%>">
+ <option value="it_lines_to_cover" <%= 'selected' if @coverage_filter=='it_lines_to_cover' -%>><%= Metric.name_for('it_lines_to_cover') -%></option>
+ <option value="it_uncovered_lines" <%= 'selected' if @coverage_filter=='it_uncovered_lines' -%>><%= Metric.name_for('it_uncovered_lines') -%></option>
+ <option value="it_conditions_to_cover" <%= 'selected' if @coverage_filter=='it_conditions_to_cover' -%>><%= Metric.name_for('it_conditions_to_cover') -%></option>
+ <option value="it_uncovered_conditions" <%= 'selected' if @coverage_filter=='it_uncovered_conditions' -%>><%= Metric.name_for('it_uncovered_conditions') -%></option>
+ </optgroup>
+ <% end %>
+ <% if @display_overall_coverage %>
+ <optgroup label="<%= h message('coverage_viewer.overall_tests') -%>">
+ <option value="overall_lines_to_cover" <%= 'selected' if @coverage_filter=='overall_lines_to_cover' -%>><%= Metric.name_for('overall_lines_to_cover') -%></option>
+ <option value="overall_uncovered_lines" <%= 'selected' if @coverage_filter=='overall_uncovered_lines' -%>><%= Metric.name_for('overall_uncovered_lines') -%></option>
+ <option value="overall_conditions_to_cover" <%= 'selected' if @coverage_filter=='overall_conditions_to_cover' -%>><%= Metric.name_for('overall_conditions_to_cover') -%></option>
+ <option value="overall_uncovered_conditions" <%= 'selected' if @coverage_filter=='overall_uncovered_conditions' -%>><%= Metric.name_for('overall_uncovered_conditions') -%></option>
+ </optgroup>
+ <% end %>
+ <% if @testable && !@testable.testCases.empty? %>
+ <optgroup label="<%= h message('coverage_viewer.per_test') -%>">
+ <option value="lines_covered_per_test" <%= 'selected' if @coverage_filter=='lines_covered_per_test' -%>><%= message('coverage_viewer.lines_covered_per_test') -%></option>
+ </optgroup>
+ <% end %>
+ </select>
+ </td>
+
+ <% if @coverage_filter=='lines_covered_per_test' %>
+ <td class="<%= 'first' if first -%>">
+ <select class="test_case_filter" name="test_case_filter" onchange="applyOptions(this)">
+ <option value=""><%= message('coverage_viewer.select_test') -%></option>
+ <% @test_case_by_test_plan.sort_by{|test_plan, test_cases| test_plan.component.longName}.each do |test_plan, test_cases| %>
+ <optgroup label="<%= test_plan.component.longName %>">
+ <% test_cases.sort_by{|test_case| test_case.name}.each do |test_case| %>
+ <option value="<%= test_case.name -%>" <%= 'selected' if @test_case_filter==test_case.name -%>><%= test_case.name -%></option>
+ <% end %>
+ </optgroup>
+ <% end %>
+ </select>
+ </td>
+ <% end %>
+
+ <% first=false
+ end %>
+ </tr>
+ </table>
+ </form>
+ </div>
+<% 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
+%>
+
+<select name="rule" onchange="applyOptions(this)">
+
+ <option value="all"><%= message('all_issues') -%></option>
+
+ <!--TODO Add false_positive, active, ... issues -->
+
+ <optgroup label="<%= message('severity') -%>">
+ <% if blocker_issues
+ value=(@period ? blocker_issues.variation(@period) : blocker_issues.value)
+ if value && value>0
+ %>
+ <option value="<%= Sonar::RulePriority::BLOCKER.to_s -%>" <%= 'selected' if params[:rule]==Sonar::RulePriority::BLOCKER.to_s -%>><%= message('severity.BLOCKER') %>
+ (<%= blocker_issues.format_numeric_value(value) -%>)
+ </option>
+ <% end
+ end %>
+
+ <% if critical_issues
+ value=(@period ? critical_issues.variation(@period) : critical_issues.value)
+ if value && value>0
+ %>
+ <option value="<%= Sonar::RulePriority::CRITICAL.to_s -%>" <%= 'selected' if params[:rule]==Sonar::RulePriority::CRITICAL.to_s -%>><%= message('severity.CRITICAL') %>
+ (<%= critical_issues.format_numeric_value(value) -%>)
+ </option>
+ <% end
+ end
+ %>
+
+ <% if major_issues
+ value=(@period ? major_issues.variation(@period) : major_issues.value)
+ if value && value>0
+ %>
+ <option value="<%= Sonar::RulePriority::MAJOR.to_s -%>" <%= 'selected' if params[:rule]==Sonar::RulePriority::MAJOR.to_s -%>><%= message('severity.MAJOR') %>
+ (<%= major_issues.format_numeric_value(value) -%>)
+ </option>
+ <% end
+ end
+ %>
+
+ <% if minor_issues
+ value=(@period ? minor_issues.variation(@period) : minor_issues.value)
+ if value && value>0
+ %>
+ <option value="<%= Sonar::RulePriority::MINOR.to_s -%>" <%= 'selected' if params[:rule]==Sonar::RulePriority::MINOR.to_s -%>><%= message('severity.MINOR') %>
+ (<%= minor_issues.format_numeric_value(value) -%>)
+ </option>
+ <% end
+ end
+ %>
+
+ <% if info_issues
+ value=(@period ? info_issues.variation(@period) : info_issues.value)
+ if value && value>0
+ %>
+ <option value="<%= Sonar::RulePriority::INFO.to_s -%>" <%= 'selected' if params[:rule]==Sonar::RulePriority::INFO.to_s -%>><%= message('severity.INFO') %>
+ (<%= info_issues.format_numeric_value(value) -%>)
+ </option>
+ <% end
+ end
+ %>
+ </optgroup>
+
+
+ <optgroup label="<%= message('rule') -%>">
+ <%= options_for_select(rule_options, params[:rule].to_i) -%>
+ </optgroup>
+
+</select> \ 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<FakeResourceViewer>(FAKE_TAB))).isEqualTo(true);
}