]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1928 Extract widgets from the hotspots page
authorFabrice Bellingard <bellingard@gmail.com>
Thu, 13 Oct 2011 16:15:54 +0000 (18:15 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Thu, 13 Oct 2011 16:18:28 +0000 (18:18 +0200)
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

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_metric.html.erb [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_resources.html.erb [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspot_most_violated_rules.html.erb [new file with mode: 0644]
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/webapp/stylesheets/style.css

index 75f70be23f0c076644d0e984c4071e58a5904581..8554184bde2ca5efd83b5f9599f6cb0272a35579 100644 (file)
@@ -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 (file)
index 0000000..c3d1829
--- /dev/null
@@ -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 (file)
index 0000000..7349c90
--- /dev/null
@@ -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 (file)
index 0000000..4757246
--- /dev/null
@@ -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 (file)
index 0000000..3d9ac5d
--- /dev/null
@@ -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 (file)
index 0000000..521d33e
--- /dev/null
@@ -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 (file)
index 0000000..40ff506
--- /dev/null
@@ -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
index 73e9f58573e40aba63885b004147a62c1c5dda4c..cbc2864d0e23519150d1324832269076cd8deea0 100644 (file)
@@ -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.
+
 
 #------------------------------------------------------------------------------
 #
index 8b8ebd6585de969300a125403b70f2198f4c3071..f871bc29205eaeef67d8d40c210fe3794b171f28 100644 (file)
@@ -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;
 }