diff options
author | Julien Lancelot <julien.lancelot@gmail.com> | 2013-04-16 11:11:49 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@gmail.com> | 2013-04-16 11:11:49 +0200 |
commit | c1b8246f1512e42dde50486d059d906b98c6eec2 (patch) | |
tree | f4b4b0c0403c53b08746faa09c0ba73c5f5115f8 | |
parent | 9f8e526ef4cf4cdbeb497378bb893910eeebb9b7 (diff) | |
download | sonarqube-c1b8246f1512e42dde50486d059d906b98c6eec2.tar.gz sonarqube-c1b8246f1512e42dde50486d059d906b98c6eec2.zip |
SONAR-3755 Add first version of Issues drilldown
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) -%> + <% 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> <%= 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> <%= 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 %> + + <% 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"> + + <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> + + <%= image_tag 'sep12.png' -%> + + + <% if issue.created_at %> + <span><%= distance_of_time_in_words_to_now(to_date(issue.created_at)) -%></span> + + <% end %> + <% + # TODO + #if violation.switched_off? + if false + %> + <%= image_tag 'sep12.png' -%> + + <span class="falsePositive"><%= message('false_positive') -%></span> + + <% end %> + <% + # TODO + #if violation.review && violation.review.resolved? + if false + %> + <%= image_tag 'sep12.png' -%> + + <span class="reviewResolved"><%= message('reviews.status.RESOLVED') -%></span> + + <% 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 %> + + </div> + + <div class="discussionComment first"> + <div id="vMsg<%= issue.key -%>"> + <%= issue.message || ' ' -%> + </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); } |