diff options
author | Fabrice Bellingard <bellingard@gmail.com> | 2011-10-13 18:15:54 +0200 |
---|---|---|
committer | Fabrice Bellingard <bellingard@gmail.com> | 2011-10-13 18:18:28 +0200 |
commit | 480885cebde277101d7f4f0e5e00b76dc5734ad6 (patch) | |
tree | abf57baf3bb477a8f5e277e71ebf2a28b972b802 | |
parent | 4e549769e27bf9dcb08aeb52d8b407a8f414a396 (diff) | |
download | sonarqube-480885cebde277101d7f4f0e5e00b76dc5734ad6.tar.gz sonarqube-480885cebde277101d7f4f0e5e00b76dc5734ad6.zip |
SONAR-1928 Extract widgets from the hotspots page
This commit includes:
- SONAR-2070: new widget for most violated rules
- SONAR-2071: new widget for most violated resources
- SONAR-2902: new metric hotspot widget
9 files changed, 546 insertions, 142 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 75f70be23f0..8554184bde2 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 @@ -19,7 +19,8 @@ */ package org.sonar.plugins.core; -import com.google.common.collect.Lists; +import java.util.List; + import org.sonar.api.CoreProperties; import org.sonar.api.Properties; import org.sonar.api.Property; @@ -36,12 +37,50 @@ import org.sonar.plugins.core.duplicationsviewer.DuplicationsViewerDefinition; import org.sonar.plugins.core.hotspots.Hotspots; import org.sonar.plugins.core.metrics.UserManagedMetrics; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; -import org.sonar.plugins.core.sensors.*; +import org.sonar.plugins.core.sensors.BranchCoverageDecorator; +import org.sonar.plugins.core.sensors.CheckAlertThresholds; +import org.sonar.plugins.core.sensors.CloseReviewsDecorator; +import org.sonar.plugins.core.sensors.CommentDensityDecorator; +import org.sonar.plugins.core.sensors.CoverageDecorator; +import org.sonar.plugins.core.sensors.DirectoriesDecorator; +import org.sonar.plugins.core.sensors.FilesDecorator; +import org.sonar.plugins.core.sensors.GenerateAlertEvents; +import org.sonar.plugins.core.sensors.LineCoverageDecorator; +import org.sonar.plugins.core.sensors.ManualMeasureDecorator; +import org.sonar.plugins.core.sensors.ProfileEventsSensor; +import org.sonar.plugins.core.sensors.ProfileSensor; +import org.sonar.plugins.core.sensors.ProjectLinksSensor; +import org.sonar.plugins.core.sensors.UnitTestDecorator; +import org.sonar.plugins.core.sensors.VersionEventsSensor; +import org.sonar.plugins.core.sensors.ViolationsDecorator; +import org.sonar.plugins.core.sensors.ViolationsDensityDecorator; +import org.sonar.plugins.core.sensors.WeightedViolationsDecorator; import org.sonar.plugins.core.testdetailsviewer.TestsViewerDefinition; -import org.sonar.plugins.core.timemachine.*; -import org.sonar.plugins.core.widgets.*; +import org.sonar.plugins.core.timemachine.NewCoverageAggregator; +import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer; +import org.sonar.plugins.core.timemachine.NewViolationsDecorator; +import org.sonar.plugins.core.timemachine.ReferenceAnalysis; +import org.sonar.plugins.core.timemachine.TendencyDecorator; +import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister; +import org.sonar.plugins.core.timemachine.VariationDecorator; +import org.sonar.plugins.core.timemachine.ViolationPersisterDecorator; +import org.sonar.plugins.core.timemachine.ViolationTrackingDecorator; +import org.sonar.plugins.core.widgets.AlertsWidget; +import org.sonar.plugins.core.widgets.CodeCoverageWidget; +import org.sonar.plugins.core.widgets.CommentsDuplicationsWidget; +import org.sonar.plugins.core.widgets.ComplexityWidget; +import org.sonar.plugins.core.widgets.CustomMeasuresWidget; +import org.sonar.plugins.core.widgets.DescriptionWidget; +import org.sonar.plugins.core.widgets.EventsWidget; +import org.sonar.plugins.core.widgets.HotspotMetricWidget; +import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget; +import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget; +import org.sonar.plugins.core.widgets.RulesWidget; +import org.sonar.plugins.core.widgets.SizeWidget; +import org.sonar.plugins.core.widgets.TimeMachineWidget; +import org.sonar.plugins.core.widgets.TimelineWidget; -import java.util.List; +import com.google.common.collect.Lists; @Properties({ @Property( @@ -49,168 +88,78 @@ import java.util.List; defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, name = "Server base URL", description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.", - project = false, - global = true, - category = CoreProperties.CATEGORY_GENERAL), - @Property( - key = CoreProperties.CORE_COVERAGE_PLUGIN_PROPERTY, - defaultValue = "cobertura", - name = "Code coverage plugin", - description = "Key of the code coverage plugin to use.", - project = true, - global = true, + project = false, global = true, category = CoreProperties.CATEGORY_GENERAL), + @Property(key = CoreProperties.CORE_COVERAGE_PLUGIN_PROPERTY, defaultValue = "cobertura", name = "Code coverage plugin", + description = "Key of the code coverage plugin to use.", project = true, global = true, category = CoreProperties.CATEGORY_CODE_COVERAGE), - @Property( - key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, - defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE, - name = "Import sources", - description = "Set to false if sources should not be displayed, e.g. for security reasons.", - project = true, - module = true, - global = true, - category = CoreProperties.CATEGORY_SECURITY), - @Property( - key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, - defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE, - name = "Tendency period", - description = TendencyDecorator.PROP_DAYS_DESCRIPTION, - project = false, - global = true, + @Property(key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE, + name = "Import sources", description = "Set to false if sources should not be displayed, e.g. for security reasons.", + project = true, module = true, global = true, category = CoreProperties.CATEGORY_SECURITY), + @Property(key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE, + name = "Tendency period", description = TendencyDecorator.PROP_DAYS_DESCRIPTION, project = false, global = true, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - @Property( - key = CoreProperties.SKIP_TENDENCIES_PROPERTY, - defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE, - name = "Skip tendencies", - description = "Skip calculation of measure tendencies", - project = true, - module = false, - global = true, + @Property(key = CoreProperties.SKIP_TENDENCIES_PROPERTY, defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE, + name = "Skip tendencies", description = "Skip calculation of measure tendencies", project = true, module = false, global = true, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - @Property( - key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY, - name = "Exclude modules", - description = "Maven artifact ids of modules to exclude (comma-separated).", - project = true, - global = false, + @Property(key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY, name = "Exclude modules", + description = "Maven artifact ids of modules to exclude (comma-separated).", project = true, global = false, category = CoreProperties.CATEGORY_GENERAL), - @Property( - key = CoreProperties.CORE_RULE_WEIGHTS_PROPERTY, - defaultValue = CoreProperties.CORE_RULE_WEIGHTS_DEFAULT_VALUE, - name = "Rules weight", - description = "A weight is associated to each priority to calculate the Rules Compliance Index.", - project = false, - global = true, - category = CoreProperties.CATEGORY_GENERAL), - @Property( - key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, - defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE, - name = "Force user authentication", - description = "Forcing user authentication stops un-logged users to access Sonar.", - project = false, - global = true, + @Property(key = CoreProperties.CORE_RULE_WEIGHTS_PROPERTY, defaultValue = CoreProperties.CORE_RULE_WEIGHTS_DEFAULT_VALUE, + name = "Rules weight", description = "A weight is associated to each priority to calculate the Rules Compliance Index.", + project = false, global = true, category = CoreProperties.CATEGORY_GENERAL), + @Property(key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, defaultValue = "" + + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE, name = "Force user authentication", + description = "Forcing user authentication stops un-logged users to access Sonar.", project = false, global = true, category = CoreProperties.CATEGORY_SECURITY), - @Property( - key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY, - defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE, - name = "Allow users to sign up online", - description = "Users can sign up online.", - project = false, - global = true, - category = CoreProperties.CATEGORY_SECURITY), - @Property( - key = CoreProperties.CORE_DEFAULT_GROUP, - defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE, - name = "Default user group", - description = "Any new users will automatically join this group.", - project = false, - global = true, + @Property(key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY, defaultValue = "" + + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE, name = "Allow users to sign up online", + description = "Users can sign up online.", project = false, global = true, category = CoreProperties.CATEGORY_SECURITY), + @Property(key = CoreProperties.CORE_DEFAULT_GROUP, defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE, + name = "Default user group", description = "Any new users will automatically join this group.", project = false, global = true, category = CoreProperties.CATEGORY_SECURITY), @Property( key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY, defaultValue = "en", name = "Locale used for violation messages", description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property", - project = true, - global = true, - category = CoreProperties.CATEGORY_L10N), + project = true, global = true, category = CoreProperties.CATEGORY_L10N), @Property( key = "sonar.timemachine.period1", name = "Period 1", - description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " + - "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " + - "compare to previous analysis</li><li>A version, for example 1.2</li></ul>", - project = false, - global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1, - category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - @Property( - key = "sonar.timemachine.period2", - name = "Period 2", - description = "See the property 'Period 1'", - project = false, - global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2, - category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - @Property( - key = "sonar.timemachine.period3", - name = "Period 3", - description = "See the property 'Period 1'", - project = false, - global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3, - category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), + description = "Period used to compare measures and track new violations. Values are : <ul class='bullet'><li>Number of days before " + + "analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, for example 2010-12-25</li><li>'previous_analysis' to " + + "compare to previous analysis</li><li>A version, for example 1.2</li></ul>", project = false, global = true, + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), + @Property(key = "sonar.timemachine.period2", name = "Period 2", description = "See the property 'Period 1'", project = false, + global = true, defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), + @Property(key = "sonar.timemachine.period3", name = "Period 3", description = "See the property 'Period 1'", project = false, + global = true, defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = "sonar.timemachine.period4", name = "Period 4", - description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " + - "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " + - "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>", - project = true, - global = false, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4, - category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - @Property( - key = "sonar.timemachine.period5", - name = "Period 5", - description = "See the property 'Period 4'", - project = true, - global = false, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5, + description = "Period used to compare measures and track new violations. This property is specific to the project. Values are : " + + "<ul class='bullet'><li>Number of days before analysis, for example 5.</li><li>A custom date. Format is yyyy-MM-dd, " + + "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>", + project = true, global = false, defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), - + @Property(key = "sonar.timemachine.period5", name = "Period 5", description = "See the property 'Period 4'", project = true, + global = false, defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5, category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), // SERVER-SIDE TECHNICAL PROPERTIES - @Property( - key = "sonar.useStructureDump", - name = "Use Structure Dump", - description = "Used when creating database schema", - project = false, - global = false, - defaultValue = "true"), - @Property( - key = "sonar.authenticator.downcase", - name = "Downcase login", - description = "Downcase login during user authentication, typically for Active Directory", - project = false, - global = false, + @Property(key = "sonar.useStructureDump", name = "Use Structure Dump", description = "Used when creating database schema", + project = false, global = false, defaultValue = "true"), + @Property(key = "sonar.authenticator.downcase", name = "Downcase login", + description = "Downcase login during user authentication, typically for Active Directory", project = false, global = false, defaultValue = "false"), - @Property( - key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS, - name = "Create user accounts", - description = "Create accounts when authenticating users via an external system", - project = false, - global = false, + @Property(key = CoreProperties.CORE_AUTHENTICATOR_CREATE_USERS, name = "Create user accounts", + description = "Create accounts when authenticating users via an external system", project = false, global = false, defaultValue = "false"), - @Property( - key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, - name = "Ignore failures during authenticator startup", - defaultValue = "false", - project = false, - global = false) -}) + @Property(key = CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, name = "Ignore failures during authenticator startup", + defaultValue = "false", project = false, global = false) }) public class CorePlugin extends SonarPlugin { + @SuppressWarnings({ "rawtypes", "unchecked" }) public List getExtensions() { List extensions = Lists.newLinkedList(); @@ -240,6 +189,9 @@ public class CorePlugin extends SonarPlugin { extensions.add(CustomMeasuresWidget.class); extensions.add(TimelineWidget.class); extensions.add(TimeMachineWidget.class); + extensions.add(HotspotMetricWidget.class); + extensions.add(HotspotMostViolatedResourcesWidget.class); + extensions.add(HotspotMostViolatedRulesWidget.class); // chart extensions.add(XradarChart.class); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java new file mode 100644 index 00000000000..c3d1829bf69 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java @@ -0,0 +1,50 @@ +/* + * 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.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; +import org.sonar.api.web.WidgetProperties; +import org.sonar.api.web.WidgetProperty; +import org.sonar.api.web.WidgetPropertyType; + +@WidgetCategory({ "Hotspots" }) +@WidgetProperties( + { + @WidgetProperty(key = "title", type = WidgetPropertyType.STRING), + @WidgetProperty(key = "metric", type = WidgetPropertyType.METRIC, defaultValue = "ncloc"), + @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") + } +) +public class HotspotMetricWidget extends AbstractRubyTemplate implements RubyRailsWidget { + public String getId() { + return "hotspot_metric"; + } + + public String getTitle() { + return "Metric hotspot"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/hotspot_metric.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java new file mode 100644 index 00000000000..7349c90d176 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java @@ -0,0 +1,49 @@ +/* + * 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.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; +import org.sonar.api.web.WidgetProperties; +import org.sonar.api.web.WidgetProperty; +import org.sonar.api.web.WidgetPropertyType; + +@WidgetCategory({ "Hotspots" }) +@WidgetProperties( + { + @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5") + } +) +public class HotspotMostViolatedResourcesWidget extends AbstractRubyTemplate implements RubyRailsWidget { + + public String getId() { + return "hotspot_most_violated_resources"; + } + + public String getTitle() { + return "Most violated resources"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java new file mode 100644 index 00000000000..47572462bb5 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java @@ -0,0 +1,49 @@ +/* + * 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.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; +import org.sonar.api.web.WidgetProperties; +import org.sonar.api.web.WidgetProperty; +import org.sonar.api.web.WidgetPropertyType; + +@WidgetCategory({ "Hotspots" }) +@WidgetProperties( + { + @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"), + @WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING) + } +) +public class HotspotMostViolatedRulesWidget extends AbstractRubyTemplate implements RubyRailsWidget { + public String getId() { + return "hotspot_most_violated_rules"; + } + + public String getTitle() { + return "Most violated rules"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb new file mode 100644 index 00000000000..3d9ac5d1459 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb @@ -0,0 +1,75 @@ +<% + metric = widget_properties["metric"] + unless metric + metric = Metric.find(:first, :conditions => "name = 'ncloc'") + end + limit = widget_properties["numberOfLines"] + unless limit + limit = 5 + end + title = widget_properties["title"] + unless title && !title.blank? + title = message('widget.hotspot_metric.hotspots_for_x', :params => metric.short_name) + end + + snapshots = nil + if metric.numeric? + snapshots_conditions = ["snapshots.scope = 'FIL'", "project_measures.rule_id IS NULL", "project_measures.characteristic_id IS NULL"] + snapshots_values = {} + snapshots_conditions << "snapshots.path LIKE :path" + snapshots_values[:path] = "#{@snapshot.path}#{@snapshot.id}.%" + snapshots_conditions << "project_measures.metric_id = :m_id" + snapshots_values[:m_id] = metric.id + snapshots_conditions << "snapshots.root_project_id = :root_id" + snapshots_values[:root_id] = @snapshot.root_project_id + + snapshots=Snapshot.find(:all, + :conditions => [snapshots_conditions.join(' AND '), snapshots_values], + :include => ['project', 'measures'], + :order => "project_measures.value #{'DESC' if metric.direction<0}", + :limit => limit) + end +%> + + +<% unless snapshots %> + <h3><%= title -%></h3> + <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.hotspot_metric.cannot_display_not_numeric_metric') -%></span> +<% else %> + +<div class="line-block"> + <div style="float:right"> + <a href="<%= url_for_drilldown(metric) -%>"><%= message('widget.hotspot_metric.more') -%></a> + </div> + <h3><%= title -%></h3> +</div> + +<table id="hotspots-<%= metric.name-%>-<%= widget.id -%>" class="data"> + <thead><tr><th colspan="3"/></tr></thead> + <tbody> +<% + metric_max_value = snapshots.first.measure(metric).value + snapshots.each do |s| + measure = s.measure(metric) + resource = s.resource +%> + <tr class="<%= cycle 'even','odd' -%>"> + <td> + <%= link_to_resource(resource, resource.name) -%> + </td> + <td class="right"> + <%= measure.formatted_value -%> + </td> + <td class="barchart"> + <div class="barchart" style="width: <%= (measure.value*100/metric_max_value).round.to_i -%>%"> + <div style="width: 100%;background-color:#777;"></div> + </div> + </td> + </tr> +<% + end +%> + </tbody> +</table> + +<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb new file mode 100644 index 00000000000..521d33e6d68 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb @@ -0,0 +1,86 @@ +<% + limit = widget_properties["numberOfLines"] + unless limit + limit = 5 + end + + metric = Metric.find(:first, :conditions => "name = 'weighted_violations'") + + snapshots_conditions = ["snapshots.scope = 'FIL'"] + snapshots_values = {} + snapshots_conditions << "(snapshots.qualifier = 'CLA' OR snapshots.qualifier = 'FIL' OR snapshots.qualifier = 'TRK')" + snapshots_conditions << "project_measures.rule_id IS NULL" + snapshots_conditions << "project_measures.characteristic_id IS NULL" + snapshots_conditions << "snapshots.path LIKE :path" + snapshots_values[:path] = "#{@snapshot.path}#{@snapshot.id}.%" + snapshots_conditions << "project_measures.metric_id = :m_id" + snapshots_values[:m_id] = metric.id + snapshots_conditions << "snapshots.root_project_id = :root_id" + snapshots_values[:root_id] = @snapshot.root_project_id + + snapshots=Snapshot.find(:all, + :conditions => [snapshots_conditions.join(' AND '), snapshots_values], + :include => ['project', 'measures'], + :order => "project_measures.value DESC", + :limit => limit) +%> + +<div class="line-block"> + <div style="float:right"> + <a href="<%= url_for_drilldown(metric) -%>"><%= message('widget.hotspot_metric.more') -%></a> + </div> + <h3><%= message('widget.hotspot_most_violated_resources.name') -%></h3> +</div> + +<table id="most-violated-resources-<%= widget.id -%>" class="data"> + <thead><tr><th colspan="11"/></tr></thead> + <tbody> +<% + snapshots.each do |s| + resource = s.resource + violations_per_severity={} + s.measure(metric).text_value.split(';').each do |part| + fields=part.split('=') + violations_per_severity[fields[0]]=fields[1] + end +%> + <tr class="<%= cycle 'even','odd' -%>"> + <td> + <%= link_to_resource(resource, resource.name) -%> + </td> + <td class="small right"> + <%= image_tag('priority/BLOCKER.png') -%> + </td> + <td class="small left"> + <%= violations_per_severity["BLOCKER"] ? violations_per_severity["BLOCKER"].to_s : "0" -%> + </td> + <td class="small right"> + <%= image_tag('priority/CRITICAL.png') -%> + </td> + <td class="small left"> + <%= violations_per_severity["CRITICAL"] ? violations_per_severity["CRITICAL"].to_s : "0" -%> + </td> + <td class="small right"> + <%= image_tag('priority/MAJOR.png') -%> + </td> + <td class="small left"> + <%= violations_per_severity["MAJOR"] ? violations_per_severity["MAJOR"].to_s : "0" -%> + </td> + <td class="small right"> + <%= image_tag('priority/MINOR.png') -%> + </td> + <td class="small left"> + <%= violations_per_severity["MINOR"] ? violations_per_severity["MINOR"].to_s : "0" -%> + </td> + <td class="small right"> + <%= image_tag('priority/INFO.png') -%> + </td> + <td class="small left"> + <%= violations_per_severity["INFO"] ? violations_per_severity["INFO"].to_s : "0" -%> + </td> + </tr> +<% + end +%> + </tbody> +</table>
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb new file mode 100644 index 00000000000..40ff506c627 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb @@ -0,0 +1,125 @@ +<% + violations_metric = Metric.find(:first, :conditions => "name = 'violations'") + limit = widget_properties["numberOfLines"] + unless limit + limit = 5 + end + defaultSeverity = widget_properties["defaultSeverity"] + if defaultSeverity + # we try to figure out if the user has specified a prefered severity, may it be an integer (0->4) or a text value ("blocker", ...) + defaultSeverityIdFromText = Sonar::RulePriority.id(widget_properties["defaultSeverity"].upcase) + defaultSeverity = defaultSeverityIdFromText.to_s if defaultSeverityIdFromText + end + defaultSeverity = "" unless defaultSeverity=="0" || defaultSeverity=="1" || defaultSeverity=="2" || defaultSeverity=="3" || defaultSeverity=="4" + + measures_conditions = ["rule_id IS NOT NULL", "characteristic_id IS NULL"] + measures_values = {} + measures_conditions << "snapshot_id = :s_id" + measures_values[:s_id] = @snapshot.id + measures_conditions << "metric_id = :m_id" + measures_values[:m_id] = violations_metric.id + measures_conditions << "metric_id = :m_id" + measures_values[:m_id] = violations_metric.id + + measures_by_priority = {} + measures_by_priority[""] = ProjectMeasure.find(:all, + :conditions => [measures_conditions.join(' AND '), measures_values], + :include => 'rule', + :order => 'value DESC', + :limit => limit) + (0..4).each do |priority| + measures_by_priority[priority.to_s] = ProjectMeasure.find(:all, + :conditions => [measures_conditions.join(' AND ') + " AND rule_priority = " + priority.to_s, measures_values], + :include => 'rule', + :order => 'value DESC', + :limit => limit) + end +%> + +<script type="text/javascript"> + + function showCorrespondingDiv(severity) { + divs = $$('#widget-<%= widget.id-%> div.hotspot'); + for (i=0; i<divs.size(); i++) { + divs[i].hide(); + } + $('most-violated-rules-<%= widget.id -%>-' + severity).show(); + } + +</script> + +<div class="line-block"> + <div style="float:right"> + <a href="<%= url_for(:controller => 'drilldown', :action => 'violations', :id => @resource.key) -%>"><%= message('widget.hotspot_metric.more') -%></a> + </div> + <h3><%= message('widget.hotspot_most_violated_rules.name') -%> + <select class="small" style="margin-left: 20px" onchange="showCorrespondingDiv(this.value);"> + <option value="" <%= 'selected' if defaultSeverity=="" -%>><%= message('widget.hotspot_most_violated_rules.any_severity') -%></option> + <option value="4" <%= 'selected' if defaultSeverity=="4" -%>><%= message('severity.BLOCKER') -%></option> + <option value="3" <%= 'selected' if defaultSeverity=="3" -%>><%= message('severity.CRITICAL') -%></option> + <option value="2" <%= 'selected' if defaultSeverity=="2" -%>><%= message('severity.MAJOR') -%></option> + <option value="1" <%= 'selected' if defaultSeverity=="1" -%>><%= message('severity.MINOR') -%></option> + <option value="0" <%= 'selected' if defaultSeverity=="0" -%>><%= message('severity.INFO') -%></option> + </select> + </h3> +</div> + + +<div id="widget-<%= widget.id-%>"> +<% + measures_by_priority.keys.each do |priority| + measures = measures_by_priority[priority] + if measures.empty? +%> + + <div id="most-violated-rules-<%= widget.id -%>-<%= priority -%>" class="hotspot" style="padding-top:10px"> + <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.hotspot_most_violated_rules.no_violation_for_severity') -%></span> + </div> + +<% + else +%> + + <div id="most-violated-rules-<%= widget.id -%>-<%= priority -%>" class="hotspot"> + <table class="data"> + <thead><tr><th colspan="3"/></tr></thead> + <tbody> + <% + violations_max_value = measures.first.value + measures.each do |m| + rule = m.rule + %> + <tr class="<%= cycle 'even','odd' -%>"> + <td> + <a href="<%= url_for(:controller => 'drilldown', :action => 'violations', :id => @resource.key, :priority => Sonar::RulePriority.to_s(m.rule_priority)) -%>"> + <%= image_tag('priority/' + m.rule_priority.to_s + '.png') -%> + </a> + </td> + <td> + <a href="<%= url_for(:controller => 'drilldown', :action => 'violations', :id => @resource.key, :rule => rule.key) -%>"><%= rule.name -%></a> + </td> + <td class="right"> + <%= m.formatted_value -%> + </td> + <td class="barchart"> + <div class="barchart" style="width: <%= (m.value*100/violations_max_value).round.to_i -%>%"> + <div style="width: 100%;background-color:#777;"></div> + </div> + </td> + </tr> + <% + end + %> + </tbody> + </table> + </div> + +<% + end + end +%> +</div> + +<script type="text/javascript"> + showCorrespondingDiv("<%= defaultSeverity -%>"); +</script>
\ No newline at end of file diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties index 73e9f58573e..cbc2864d0e2 100644 --- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -546,6 +546,20 @@ widget.package_design.dependencies_to_cut=Dependencies to cut widget.package_design.between_packages.suffix=\ between packages widget.package_design.between_files.suffix=\ between files +widget.hotspot_metric.name=Metric hotspot +widget.hotspot_metric.description=Shows the files that have the worst result for a specific metric. +widget.hotspot_metric.cannot_display_not_numeric_metric=The hotspot widget cannot display non-numerical metrics. +widget.hotspot_metric.more=More +widget.hotspot_metric.hotspots_for_x=Hotspots for {0} + +widget.hotspot_most_violated_rules.name=Most violated rules +widget.hotspot_most_violated_rules.description=Shows the rules that are the most violated. +widget.hotspot_most_violated_rules.no_violation_for_severity=No violation for this severity +widget.hotspot_most_violated_rules.any_severity=Any severity + +widget.hotspot_most_violated_resources.name=Most violated resources +widget.hotspot_most_violated_resources.description=Shows the resources that have the most violations. + #------------------------------------------------------------------------------ # diff --git a/sonar-server/src/main/webapp/stylesheets/style.css b/sonar-server/src/main/webapp/stylesheets/style.css index 8b8ebd6585d..f871bc29205 100644 --- a/sonar-server/src/main/webapp/stylesheets/style.css +++ b/sonar-server/src/main/webapp/stylesheets/style.css @@ -1838,6 +1838,10 @@ table.data, table.spaced, .gwt-SourcePanel .sources { width: 100%; } +table.data td.barchart { + width: 100px; +} + table.without-header { border-top: 1px solid #ddd; } |