aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java4
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageAggregator.java86
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzer.java (renamed from plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/NewCoverageDecorator.java)17
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/NewCoverageWidget.java42
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/new_coverage.html.erb39
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageFileAnalyzerTest.java (renamed from plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/NewCoverageDecoratorTest.java)23
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/browse_controller.rb172
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/browse/_violation.html.erb5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/views/browse/index.html.erb198
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