aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Bellingard <bellingard@gmail.com>2011-10-13 18:15:54 +0200
committerFabrice Bellingard <bellingard@gmail.com>2011-10-13 18:18:28 +0200
commit480885cebde277101d7f4f0e5e00b76dc5734ad6 (patch)
treeabf57baf3bb477a8f5e277e71ebf2a28b972b802
parent4e549769e27bf9dcb08aeb52d8b407a8f414a396 (diff)
downloadsonarqube-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
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java236
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java50
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java49
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java49
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb75
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb86
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb125
-rw-r--r--plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties14
-rw-r--r--sonar-server/src/main/webapp/stylesheets/style.css4
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;
}