diff options
9 files changed, 562 insertions, 24 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index a343985c1de..c8f802a0e5d 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -201,6 +201,7 @@ public class CorePlugin implements Plugin { extensions.add(SizeWidget.class); extensions.add(EventsWidget.class); extensions.add(CustomMeasuresWidget.class); + extensions.add(NewCoverageWidget.class); // chart extensions.add(XradarChart.class); @@ -237,7 +238,8 @@ public class CorePlugin implements Plugin { extensions.add(ViolationPersisterDecorator.class); extensions.add(NewViolationsDecorator.class); extensions.add(TimeMachineConfigurationPersister.class); - extensions.add(NewCoverageDecorator.class); + extensions.add(NewCoverageFileAnalyzer.class); + extensions.add(NewCoverageAggregator.class); return extensions; } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageAggregator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageAggregator.java new file mode 100644 index 00000000000..f4d56097161 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageAggregator.java @@ -0,0 +1,86 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.timemachine; + +import org.apache.commons.lang.ArrayUtils; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; + +import java.util.Arrays; +import java.util.List; + +public final class NewCoverageAggregator implements Decorator { + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @DependedUpon + public List<Metric> generatesNewCoverageMetrics() { + return Arrays.asList(CoreMetrics.NEW_LINES_TO_COVER, CoreMetrics.NEW_UNCOVERED_LINES, + CoreMetrics.NEW_CONDITIONS_TO_COVER, CoreMetrics.NEW_UNCOVERED_CONDITIONS); + } + + public void decorate(Resource resource, DecoratorContext context) { + if (shouldDecorate(resource)) { + int maxPeriods = (Qualifiers.isView(resource, true) ? 3 : 5); + aggregate(context, CoreMetrics.NEW_LINES_TO_COVER, maxPeriods); + aggregate(context, CoreMetrics.NEW_UNCOVERED_LINES, maxPeriods); + aggregate(context, CoreMetrics.NEW_CONDITIONS_TO_COVER, maxPeriods); + aggregate(context, CoreMetrics.NEW_UNCOVERED_CONDITIONS, maxPeriods); + } + } + + private void aggregate(DecoratorContext context, Metric metric, int maxPeriods) { + int[] variations = {0,0,0,0,0}; + boolean[] hasValues = {false,false,false,false,false}; + for (Measure child : context.getChildrenMeasures(metric)) { + for (int indexPeriod=1 ; indexPeriod<=maxPeriods ; indexPeriod++) { + Double variation = child.getVariation(indexPeriod); + if (variation!=null) { + variations[indexPeriod-1]=variations[indexPeriod-1] + variation.intValue(); + hasValues[indexPeriod-1]=true; + } + } + } + + if (ArrayUtils.contains(hasValues, true)) { + Measure measure = new Measure(metric); + for (int index=0 ; index<5 ; index++) { + if (hasValues[index]) { + measure.setVariation(index+1, (double)variations[index]); + } + } + context.saveMeasure(measure); + } + } + + private boolean shouldDecorate(Resource resource) { + return Scopes.isHigherThan(resource, Scopes.FILE); + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzer.java index 18297718721..6b7018560b1 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzer.java @@ -21,7 +21,6 @@ package org.sonar.plugins.core.timemachine; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import org.apache.commons.lang.NumberUtils; import org.apache.commons.lang.ObjectUtils; import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; @@ -46,18 +45,18 @@ import java.util.Map; /** * @since 2.7 */ -public final class NewCoverageDecorator implements Decorator { +public final class NewCoverageFileAnalyzer implements Decorator { private List<PeriodStruct> structs; - public NewCoverageDecorator(TimeMachineConfiguration timeMachineConfiguration) { + public NewCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration) { structs = Lists.newArrayList(); for (PastSnapshot pastSnapshot : timeMachineConfiguration.getProjectPastSnapshots()) { structs.add(new PeriodStruct(pastSnapshot)); } } - NewCoverageDecorator(List<PeriodStruct> structs) { + NewCoverageFileAnalyzer(List<PeriodStruct> structs) { this.structs = structs; } @@ -67,7 +66,7 @@ public final class NewCoverageDecorator implements Decorator { } private boolean shouldDecorate(Resource resource) { - return isFile(resource); + return Scopes.isFile(resource) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier()); } @DependsUpon @@ -156,10 +155,6 @@ public final class NewCoverageDecorator implements Decorator { } - private boolean isFile(Resource resource) { - return Scopes.isFile(resource) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier()); - } - public static final class PeriodStruct { int index; Date date; @@ -167,7 +162,7 @@ public final class NewCoverageDecorator implements Decorator { PeriodStruct(PastSnapshot pastSnapshot) { this.index = pastSnapshot.getIndex(); - this.date = pastSnapshot.getDate(); + this.date = pastSnapshot.getTargetDate(); } PeriodStruct(int index, Date date) { @@ -187,7 +182,7 @@ public final class NewCoverageDecorator implements Decorator { //TODO warning } else if (lineDate.after(date)) { - // TODO experiment if date comparison is faster or not + // TODO test if string comparison is faster or not addLine(hits > 0); addConditions(conditions, coveredConditions); } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/NewCoverageWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/NewCoverageWidget.java new file mode 100644 index 00000000000..8ca5e63cb4e --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/NewCoverageWidget.java @@ -0,0 +1,42 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.core.widgets; + +import org.sonar.api.web.AbstractRubyTemplate; +import org.sonar.api.web.Description; +import org.sonar.api.web.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; + +@WidgetCategory({"Tests"}) +@Description("TODO") +public class NewCoverageWidget extends AbstractRubyTemplate implements RubyRailsWidget { + public String getId() { + return "new_coverage"; + } + + public String getTitle() { + return "New coverage"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/new_coverage.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/new_coverage.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/new_coverage.html.erb new file mode 100644 index 00000000000..102db31ca17 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/new_coverage.html.erb @@ -0,0 +1,39 @@ +<h2>New coverage</h2> + <table> + <tr> + <td>New lines</td> + <% m=measure('new_lines_to_cover') %> + <% 5.times do |index| %> + <td> + <%= format_variation(m, :index => index+1) -%> + </td> + <% end %> + </tr> + <tr> + <td>New uncovered</td> + <% m=measure('new_uncovered_lines') %> + <% 5.times do |index| %> + <td> + <%= format_variation(m, :index => index+1) -%> + </td> + <% end %> + </tr> + <tr> + <td>New conditions</td> + <% m=measure('new_conditions_to_cover') %> + <% 5.times do |index| %> + <td> + <%= format_variation(m, :index => index+1) -%> + </td> + <% end %> + </tr> + <tr> + <td>New uncovered conditions</td> + <% m=measure('new_uncovered_conditions') %> + <% 5.times do |index| %> + <td> + <%= format_variation(m, :index => index+1) -%> + </td> + <% end %> + </tr> + </table> diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzerTest.java index e94cb5b8518..3eb6a0f27ed 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzerTest.java @@ -19,7 +19,6 @@ */ package org.sonar.plugins.core.timemachine; -import org.apache.commons.lang.NumberUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Test; @@ -37,7 +36,7 @@ import java.util.List; import static org.mockito.Mockito.*; -public class NewCoverageDecoratorTest { +public class NewCoverageFileAnalyzerTest { @Test public void shouldDoNothingIfNoScmData() throws ParseException { @@ -45,7 +44,7 @@ public class NewCoverageDecoratorTest { when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)) .thenReturn(new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "1=10")); - NewCoverageDecorator decorator = newDecorator(); + NewCoverageFileAnalyzer decorator = newDecorator(); decorator.doDecorate(context); verify(context, never()).saveMeasure((Measure) anyObject()); } @@ -56,7 +55,7 @@ public class NewCoverageDecoratorTest { when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)) .thenReturn(new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2008-05-18T00:00:00+0000")); - NewCoverageDecorator decorator = newDecorator(); + NewCoverageFileAnalyzer decorator = newDecorator(); decorator.doDecorate(context); verify(context, never()).saveMeasure((Measure) anyObject()); @@ -70,7 +69,7 @@ public class NewCoverageDecoratorTest { when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); - NewCoverageDecorator decorator = newDecorator(); + NewCoverageFileAnalyzer decorator = newDecorator(); decorator.doDecorate(context); // line 11 has been updated after date1 (2009-12-25). This line is covered. @@ -102,7 +101,7 @@ public class NewCoverageDecoratorTest { when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); - NewCoverageDecorator decorator = newDecorator(); + NewCoverageFileAnalyzer decorator = newDecorator(); decorator.doDecorate(context); // line 11 has been updated after date1 (2009-12-25). This line has 1 covered condition amongst 4 @@ -134,7 +133,7 @@ public class NewCoverageDecoratorTest { when(context.getMeasure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE)).thenReturn( new Measure(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, "10=2007-01-15T00:00:00+0000;11=2011-01-01T00:00:00+0000")); - NewCoverageDecorator decorator = newDecorator(); + NewCoverageFileAnalyzer decorator = newDecorator(); decorator.doDecorate(context); // line 11 has been updated after date1 (2009-12-25) but it has no conditions @@ -170,11 +169,11 @@ public class NewCoverageDecoratorTest { } } - private NewCoverageDecorator newDecorator() throws ParseException { - List<NewCoverageDecorator.PeriodStruct> structs = Arrays.asList( - new NewCoverageDecorator.PeriodStruct(1, newDate("2009-12-25")), - new NewCoverageDecorator.PeriodStruct(3, newDate("2011-02-18"))); - return new NewCoverageDecorator(structs); + private NewCoverageFileAnalyzer newDecorator() throws ParseException { + List<NewCoverageFileAnalyzer.PeriodStruct> structs = Arrays.asList( + new NewCoverageFileAnalyzer.PeriodStruct(1, newDate("2009-12-25")), + new NewCoverageFileAnalyzer.PeriodStruct(3, newDate("2011-02-18"))); + return new NewCoverageFileAnalyzer(structs); } private Date newDate(String s) throws ParseException { diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/browse_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/browse_controller.rb new file mode 100644 index 00000000000..5ae0ae64c12 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/browse_controller.rb @@ -0,0 +1,172 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2011 SonarSource +# mailto:contact AT sonarsource DOT com +# +# Sonar is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Sonar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Sonar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +# +class BrowseController < ApplicationController + + SECTION=Navigation::SECTION_RESOURCE + + def index + @resource = Project.by_key(params[:id]) + + if (@resource && has_role?(:user, @resource)) + @snapshot=@resource.last_snapshot + render_resource() + else + access_denied + end + end + + private + + + def render_resource + @source = @snapshot.source + if @source + source_lines=Java::OrgSonarServerUi::JRubyFacade.new.colorizeCode(@source.data, @snapshot.project.language).split("\n") + load_scm() + + @lines=[] + current_revision=nil + source_lines.each_with_index do |source, index| + line=Line.new(source) + @lines<<line + + line.revision=@revisions_by_line[index+1] + line.author=@authors_by_line[index+1] + line.date=@dates_by_line[index+1] + + if line.revision + if current_revision!=line.revision + current_revision=line.revision + line.display_scm=true + end + end + end + end + + if (params[:tab]=='violations') + load_violations_tab() + elsif (params[:tab]=='coverage') + load_coverage_tab() + else + load_source_tab() + end + + params[:layout]='false' + end + + def load_scm() + @scm_available=(@snapshot.measure('last_commit_datetimes_by_line')!=nil) + @display_scm=(params[:scm]=='true') + if @display_scm + @authors_by_line=load_distribution('authors_by_line') + @revisions_by_line=load_distribution('revisions_by_line') + @dates_by_line=load_distribution('last_commit_datetimes_by_line') + else + @authors_by_line={} + @revisions_by_line={} + @dates_by_line={} + end + end + + def load_distribution(metric_key) + m=@snapshot.measure(metric_key) + m ? m.data_as_line_distribution() : {} + end + + def load_coverage_tab + @display_coverage=true + @hits_by_line=load_distribution('coverage_line_hits_data') + @conditions_by_line=load_distribution('conditions_by_line') + @covered_conditions_by_line=load_distribution('covered_conditions_by_line') + + @hits_by_line.each_pair do |line_id,hits| + line=@lines[line_id-1] + if line + line.hits=hits.to_i + line.conditions=@conditions_by_line[line_id].to_i + line.covered_conditions=@covered_conditions_by_line[line_id].to_i + end + end + end + + def load_violations_tab + @display_violations=true + @global_violations=[] + + conditions='snapshot_id=?' + values=[@snapshot.id] + unless params[:rule].blank? + if params[:rule].include?(':') + rule=Rule.by_key_or_id(params[:rule]) + conditions += ' AND rule_id=?' + values<<(rule ? rule.id : -1) + else + # severity + conditions += ' AND failure_level=?' + values<<params[:rule].to_i + end + end + + unless params[:date].blank? + conditions+=' AND created_at>=?' + values<<Date::strptime(params[:date]) + end + + RuleFailure.find(:all, :include => 'rule', :conditions => [conditions] + values, :order => 'failure_level DESC').each do |violation| + # sorted by severity => from blocker to info + if violation.line && violation.line>0 + @lines[violation.line-1].add_violation(violation) + else + @global_violations<<violation + end + end + end + + def load_source_tab + + end + + class Line + attr_accessor :source, :revision, :author, :date, :display_scm, :violations, :hits, :conditions, :covered_conditions + + def initialize(source) + @source=source + @display_scm=false + end + + def add_violation(violation) + @violations||=[] + @violations<<violation + @visible=true + end + + def violations? + @violations && @violations.size>0 + end + + def violation_severity + if @violations && @violations.size>0 + @violations[0].failure_level + else + nil + end + end + end +end
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/browse/_violation.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/browse/_violation.html.erb new file mode 100644 index 00000000000..e0803a8ec42 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/browse/_violation.html.erb @@ -0,0 +1,5 @@ +<img src="<%= ApplicationController.root_context -%>/images/priority/<%=violation.failure_level-%>.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 => violation.rule.key, :layout => 'false' -%>"><%= h(violation.rule.name) -%></a></span> + ยป +<%= h(violation.message) -%>
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/browse/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/browse/index.html.erb new file mode 100644 index 00000000000..d05ecd6f3c6 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/browse/index.html.erb @@ -0,0 +1,198 @@ +<style> +#source_metrics { + width: 100%; +} +#source_metrics { + width: 100%; +} +.sources2 { + width: 100%; + border: 1px solid #DDD; + margin: 0; +} +.sources2 td.lid { + background-color: #ECECEC; + border-right: 1px solid #DDDDDD; + border-left: 1px solid #DDDDDD; + text-align: right; + padding: 2px 0.5em 0 0.5em; + vertical-align: top; + font-size: 11px; +} +.sources2 td.lid a { + text-decoration: none; + color: #AAA; +} +.sources2 td.scm { + border-right: 1px solid #DDD; + background-color: #ECECEC; +} +.sources2 td.revision { + border-top: 1px solid #DDD; + vertical-align: top; + padding: 2px 0.3em; + white-space: nowrap; +} +.sources2 span.date, .sources2 span.date a { + color: #AAA; + font-size: 11px; + text-decoration: none; +} +.sources2 span.author, .sources2 span.author a { + font-size: 11px; +} +.sources2 td.rule { + padding: 1px 0.5em; +} +.sources2 td.violations { + background-color: #ECECEC; + min-width: 400px; +} +span.rulename, span.rulename a { + color: #4183C4; + text-decoration: none; +} +span.rulename a:hover { + text-decoration: underline; +} +.sources2 td.violations img { + vertical-align: sub; +} +.sources2 td.line { + padding-left: 1em; + width: 100%; + white-space: pre; + font-size: 12px; + font-family: monospace; +} +.sources2 td.section { + border-top: 1px solid #DDD; + border-bottom: 1px solid #DDD; +} +.sources2 td.ind { + border-right: 1px solid #DDD; + min-width: 1.5em; + padding: 0 0.3em; + text-align: center; + vertical-align: middle; +} +.sources2 td.ok { + background-color: #ACE97C; + border-top: 1px solid #6EC563; + border-bottom: 1px solid #6EC563; +} +.sources2 td.sev0, .sources2 td.sev1, .sources2 td.warn { + /* info, minor */ + background-color: #FFF6BF; + border-top: 1px solid #FFD324; + border-bottom: 1px solid #FFD324; +} +.sources2 td.sev2, .sources2 td.sev3, .sources2 td.sev4, .sources2 td.ko { + /* major, critical, blocker */ + background-color: #FF9090; + border-top: 1px solid #FF5252; + border-bottom: 1px solid #FF5252; +} + +#source_title { + padding: 10px 0; +} +#source_title span.h1 { + font-size: 16px; + margin-right: 10px; +} +.source_links { + font-size: 11px; +} +#global_violations { + width: 100%; + border: 1px solid #DDD; + margin-bottom: 10px; +} +#global_violations td { + background-color: #ECECEC; + padding: 3px 0.5em; +} +#global_violations td img, #source_title img { + vertical-align: sub; +} +</style> + +<div id="source_title"> + <span class="h1"><%= qualifier_icon(@resource) -%> <%= @resource.long_name -%></span> + <% if @lines %> + | <span class="source_link"><a href="<%= ApplicationController.root_context -%>/api/sources?resource=<%= @resource.key -%>&format=txt" target="raw">raw</a></span> + <% end %> +</div> +<table id="source_metrics" cellpadding="0" cellspacing="0" border="0"> + +</table> + +<% if @display_violations && @global_violations && @global_violations.size>0 -%> + <table id="global_violations" cellpadding="0" cellspacing="0" border="0"> + <% @global_violations.each do |violation| %> + <tr> + <td><%= render :partial => 'violation', :locals => {:violation => violation} -%></td> + </tr> + <% end %> + </table> +<% end %> + +<% if @lines && @lines.size>0 %> +<table id="sources" class="sources2 code" cellpadding="0" cellspacing="0" border="0"> + <% + @lines.each_with_index do |line, index| + status=hits_status=conditions_status='' + 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_violations + status="sev#{line.violation_severity}" + end + %> + <tr> + <% if @display_scm + if line.display_scm + title = "Revision #{h(line.revision)} (#{line.date})" + %> + <td class="scm revision"><span class="date"><a href="#" title="<%= title -%>" alt="<%= title -%>"><%= line.date[0..9] -%></a></span> <span class="author"><%= h(line.author) -%></span></td> + <% else %> + <td class="scm"></td> + <% end + end %> + <% if @display_violations %> + <td class="rule <%= 'violations section' if line.violations? -%>"> + <% if line.violations? + line.violations.each_with_index do |violation, violation_index| %> + <%= '<br/>' if violation_index>0 %> + <%= render :partial => 'violation', :locals => {:violation => violation} -%> + <% + end + end + %> + </td> + <% end %> + <td class="lid <%= ' section' if line.violations? -%>" id="L<%= index+1 -%>"><a name="L<%= index+1 -%>" href="#L<%= index+1 -%>"><%= index + 1 -%></a></td> + + <% if @display_coverage %> + <td class="ind <%= hits_status -%>"><%= line.hits -%></td> + <td class="ind <%= conditions_status -%>"><% if line.conditions && line.conditions>0 %><%= line.covered_conditions -%>/<%= line.conditions -%><% end %></td> + <% end %> + <td class="line <%= status -%>"><%= line.source -%></td> + </tr> + <% end %> +</table> +<% end %>
\ No newline at end of file |