]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3529 First baby step in providing property sets
authorDavid Gageot <david@gageot.net>
Wed, 4 Jul 2012 16:02:37 +0000 (18:02 +0200)
committerDavid Gageot <david@gageot.net>
Wed, 4 Jul 2012 17:14:10 +0000 (19:14 +0200)
21 files changed:
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/CustomMeasuresWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/DemoWidget.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMetricWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedResourcesWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimeMachineWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/FalsePositiveReviewsWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/MyReviewsWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/PlannedReviewsWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/ProjectReviewsWidget.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/reviews/UnplannedReviewsWidget.java
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/demo.html.erb [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/web/PropertyValidation.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetProperty.java
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertySet.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java
sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb
sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java

index 86cb29c81a751bd45462865aed122b631b0fcd87..a9fb669fcd1e77df846d99b9597ce6b611c6d283 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.plugins.core;
 
+import org.sonar.plugins.core.widgets.DemoWidget;
+
 import com.google.common.collect.ImmutableList;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.Extension;
@@ -336,6 +338,7 @@ public final class CorePlugin extends SonarPlugin {
         ReviewsMetricsWidget.class,
         TreemapWidget.class,
         FilterWidget.class,
+        DemoWidget.class,
 
         // dashboards
         DefaultDashboard.class,
index e1c8cdde5300b7ecb88cb3e37d0009f5869d5c86..274e77f926cec14db52b802fbc0226639ea75005 100644 (file)
@@ -21,20 +21,18 @@ package org.sonar.plugins.core.widgets;
 
 import org.sonar.api.web.*;
 
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric4", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric5", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric6", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric7", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric8", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric9", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric10", type = WidgetPropertyType.METRIC)
-    }
-)
+@WidgetProperties({
+  @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric4", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric5", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric6", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric7", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric8", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric9", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric10", type = WidgetPropertyType.METRIC)
+})
 public class CustomMeasuresWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "custom_measures";
@@ -48,4 +46,4 @@ public class CustomMeasuresWidget extends AbstractRubyTemplate implements RubyRa
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/custom_measures.html.erb";
   }
-}
\ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/DemoWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/DemoWidget.java
new file mode 100644 (file)
index 0000000..4854992
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 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.WidgetPropertySet;
+
+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;
+import org.sonar.api.web.WidgetScope;
+
+import static org.sonar.api.web.WidgetScope.*;
+
+@WidgetCategory("Global")
+@WidgetScope(GLOBAL)
+@WidgetProperties(sets = {
+  @WidgetPropertySet(key = "set1",
+    value = {
+      @WidgetProperty(key = "key1", type = WidgetPropertyType.STRING),
+      @WidgetProperty(key = "key2", type = WidgetPropertyType.STRING)
+    }), @WidgetPropertySet(key = "set2",
+    value = {
+      @WidgetProperty(key = "key3", type = WidgetPropertyType.STRING)
+    })}, value = {
+  @WidgetProperty(key = "key4", type = WidgetPropertyType.STRING)
+})
+public class DemoWidget extends AbstractRubyTemplate implements RubyRailsWidget {
+  public String getId() {
+    return "demo";
+  }
+
+  public String getTitle() {
+    return "Demo";
+  }
+
+  @Override
+  protected String getTemplatePath() {
+    return "/org/sonar/plugins/core/widgets/demo.html.erb";
+  }
+}
index f69f6ad46bbbc1a7a553aca27fff717892c1d847..a4a447679d4aaca9943ab9f8c7bc93133bc8fd99 100644 (file)
@@ -26,14 +26,12 @@ 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")
-    }
-)
+@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";
@@ -47,4 +45,4 @@ public class HotspotMetricWidget extends AbstractRubyTemplate implements RubyRai
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/hotspots/hotspot_metric.html.erb";
   }
-}
\ No newline at end of file
+}
index 6c78fb9c14cd9f5851eabfa4ef7951f5aa0a6e74..383cfcecf168780183110cfe48f796586ecf9cf9 100644 (file)
@@ -26,12 +26,10 @@ 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")
-    }
-)
+@WidgetCategory("Hotspots")
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5")
+})
 public class HotspotMostViolatedResourcesWidget extends AbstractRubyTemplate implements RubyRailsWidget {
 
   public String getId() {
@@ -46,4 +44,4 @@ public class HotspotMostViolatedResourcesWidget extends AbstractRubyTemplate imp
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_resources.html.erb";
   }
-}
\ No newline at end of file
+}
index b58ed0cd89a98c34110174c103422e9865060bd8..7fff488beb6200c6fb60c6c141318769b71905cc 100644 (file)
@@ -22,12 +22,10 @@ package org.sonar.plugins.core.widgets;
 import org.sonar.api.web.*;
 
 @WidgetCategory("Hotspots")
-@WidgetProperties(
-  {
-    @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"),
-    @WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING, description = "Values: BLOCKER, CRITICAL, MAJOR, MINOR, INFO")
-  }
-)
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"),
+  @WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING, description = "Values: BLOCKER, CRITICAL, MAJOR, MINOR, INFO")
+})
 public class HotspotMostViolatedRulesWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "hotspot_most_violated_rules";
@@ -41,4 +39,4 @@ public class HotspotMostViolatedRulesWidget extends AbstractRubyTemplate impleme
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb";
   }
-}
\ No newline at end of file
+}
index 47f2f63c562c86161f7d68a387c4ed834aed7d8d..be8a76ab3d777e4bacf9b574b0432cee1bf48901 100644 (file)
@@ -26,7 +26,7 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({"History"})
+@WidgetCategory("History")
 @WidgetProperties({
   @WidgetProperty(key = "title", type = WidgetPropertyType.STRING),
   @WidgetProperty(key = "numberOfColumns", type = WidgetPropertyType.INTEGER, defaultValue = "3"),
index 7ecc27de6727b1b99668b5df476bee0e82d18dd1..6a2857d783992f8539ae77103cc431b8d7eb8525 100644 (file)
@@ -26,17 +26,15 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "History" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "chartTitle", type = WidgetPropertyType.STRING),
-        @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc"),
-        @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "hideEvents", type = WidgetPropertyType.BOOLEAN),
-        @WidgetProperty(key = "chartHeight", type = WidgetPropertyType.INTEGER, defaultValue = "80")
-    }
-)
+@WidgetCategory("History")
+@WidgetProperties({
+  @WidgetProperty(key = "chartTitle", type = WidgetPropertyType.STRING),
+  @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc"),
+  @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC),
+  @WidgetProperty(key = "hideEvents", type = WidgetPropertyType.BOOLEAN),
+  @WidgetProperty(key = "chartHeight", type = WidgetPropertyType.INTEGER, defaultValue = "80")
+})
 public class TimelineWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "timeline";
@@ -50,4 +48,4 @@ public class TimelineWidget extends AbstractRubyTemplate implements RubyRailsWid
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/timeline.html.erb";
   }
-}
\ No newline at end of file
+}
index f973ab6bf36e245c9f5dd1e23f23f6b97511bb56..6e527219c161f4397cbb9f387ff91910fffa0a4a 100644 (file)
@@ -26,13 +26,11 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "Reviews" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", 
-                        description="Maximum number of reviews displayed at the same time.")
-    }
-)
+@WidgetCategory("Reviews")
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5",
+    description = "Maximum number of reviews displayed at the same time.")
+})
 public class FalsePositiveReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "false_positive_reviews";
@@ -46,4 +44,4 @@ public class FalsePositiveReviewsWidget extends AbstractRubyTemplate implements
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb";
   }
-}
\ No newline at end of file
+}
index 0e3a385576a93c6f27f187c8a410a0d2efba97b3..ed95dddabeb001f8c9565a7d596b792f3fe869d3 100644 (file)
@@ -26,13 +26,11 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "Reviews" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", 
-                        description="Maximum number of reviews displayed at the same time.")
-    }
-)
+@WidgetCategory("Reviews")
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5",
+    description = "Maximum number of reviews displayed at the same time.")
+})
 public class MyReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "my_reviews";
@@ -46,4 +44,4 @@ public class MyReviewsWidget extends AbstractRubyTemplate implements RubyRailsWi
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb";
   }
-}
\ No newline at end of file
+}
index eb1abe18dc0f3a08f97695660fd93007fff36843..2b80d72572ebb885298f4fb1cdc4dd9635be1597 100644 (file)
@@ -26,13 +26,11 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "Action plans", "Reviews" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", 
-                        description="Maximum number of reviews displayed at the same time.")
-    }
-)
+@WidgetCategory({"Action plans", "Reviews"})
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5",
+    description = "Maximum number of reviews displayed at the same time.")
+})
 public class PlannedReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "planned_reviews";
@@ -46,4 +44,4 @@ public class PlannedReviewsWidget extends AbstractRubyTemplate implements RubyRa
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/reviews/planned_reviews.html.erb";
   }
-}
\ No newline at end of file
+}
index 1551e84233ee5edd5a4907f2ce366aa330ebad17..c0200af47ac18374aa1398c02c7f2b0e0bf7d2db 100644 (file)
@@ -26,13 +26,11 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "Reviews" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", 
-                        description="Maximum number of reviews displayed at the same time.")
-    }
-)
+@WidgetCategory("Reviews")
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5",
+    description = "Maximum number of reviews displayed at the same time.")
+})
 public class ProjectReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "project_reviews";
@@ -46,4 +44,4 @@ public class ProjectReviewsWidget extends AbstractRubyTemplate implements RubyRa
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb";
   }
-}
\ No newline at end of file
+}
index 83660e87af3f01f8829e47abc576350f07846424..014fc25acf5b0866d1265ffb5b51a27ffc1db40e 100644 (file)
@@ -26,13 +26,11 @@ import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
 import org.sonar.api.web.WidgetPropertyType;
 
-@WidgetCategory({ "Action plans", "Reviews" })
-@WidgetProperties(
-    {
-        @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5", 
-                        description="Maximum number of reviews displayed at the same time.")
-    }
-)
+@WidgetCategory({"Action plans", "Reviews"})
+@WidgetProperties({
+  @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5",
+    description = "Maximum number of reviews displayed at the same time.")
+})
 public class UnplannedReviewsWidget extends AbstractRubyTemplate implements RubyRailsWidget {
   public String getId() {
     return "unplanned_reviews";
@@ -46,4 +44,4 @@ public class UnplannedReviewsWidget extends AbstractRubyTemplate implements Ruby
   protected String getTemplatePath() {
     return "/org/sonar/plugins/core/widgets/reviews/unplanned_reviews.html.erb";
   }
-}
\ No newline at end of file
+}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/demo.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/demo.html.erb
new file mode 100644 (file)
index 0000000..197d45b
--- /dev/null
@@ -0,0 +1,4 @@
+Key1: <%= widget_properties['key1'] -%>
+Key2: <%= widget_properties['key2'] -%>
+Key3: <%= widget_properties['key3'] -%>
+Key4: <%= widget_properties['key4'] -%>
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/PropertyValidation.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/PropertyValidation.java
new file mode 100644 (file)
index 0000000..d49158d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 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.api.web;
+
+public interface PropertyValidation {
+  boolean validate(String value);
+
+  class None implements PropertyValidation {
+    public boolean validate(String value) {
+      return true;
+    }
+  };
+}
index 03b302cd395d9cf42affa6adec0790b28eefd38c..f4fd18a874fd1b1e5bdfc8f81e50cbffe1d95c3c 100644 (file)
@@ -25,4 +25,6 @@ import java.lang.annotation.RetentionPolicy;
 @Retention(RetentionPolicy.RUNTIME)
 public @interface WidgetProperties {
   WidgetProperty[] value() default {};
+
+  WidgetPropertySet[] sets() default {};
 }
index 4fd661a7dfb91758bd739041169086d1a4f667b4..652076a1d489d72c15cb869a8f853476d502a372 100644 (file)
@@ -37,4 +37,6 @@ public @interface WidgetProperty {
   String description() default "";
 
   boolean optional() default true;
+
+  Class<? extends PropertyValidation> validation() default PropertyValidation.None.class;
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertySet.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetPropertySet.java
new file mode 100644 (file)
index 0000000..28de523
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 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.api.web;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface WidgetPropertySet {
+  String key();
+
+  WidgetProperty[] value();
+
+  static WidgetPropertySet DEFAULT = new WidgetPropertySet() {
+    public Class<WidgetPropertySet> annotationType() {
+      return WidgetPropertySet.class;
+    }
+
+    public WidgetProperty[] value() {
+      return null;
+    }
+
+    public String key() {
+      return "";
+    }
+  };
+}
index ae3c1b297981ffb59dd281020c4cdea85a28c988..0770add2d14c554c0eb829e6d08d3481719c777e 100644 (file)
 package org.sonar.server.ui;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.SetMultimap;
 import org.apache.commons.lang.ArrayUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.CompareToBuilder;
@@ -43,6 +46,7 @@ import org.sonar.api.web.WidgetLayout;
 import org.sonar.api.web.WidgetLayoutType;
 import org.sonar.api.web.WidgetProperties;
 import org.sonar.api.web.WidgetProperty;
+import org.sonar.api.web.WidgetPropertySet;
 import org.sonar.api.web.WidgetScope;
 
 import java.util.Collection;
@@ -51,7 +55,8 @@ import java.util.Map;
 @SuppressWarnings("rawtypes")
 public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
 
-  private V view;
+  private final V view;
+  private final boolean isWidget;
   private String[] sections = {NavigationSection.HOME};
   private String[] userRoles = {};
   private String[] resourceScopes = {};
@@ -60,16 +65,17 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
   private String[] defaultForMetrics = {};
   private String description = "";
   private Map<String, WidgetProperty> widgetPropertiesByKey = Maps.newHashMap();
+  private SetMultimap<WidgetPropertySet, WidgetProperty> widgetPropertiesBySet = LinkedHashMultimap.create();
   private String[] widgetCategories = {};
   private WidgetLayoutType widgetLayout = WidgetLayoutType.DEFAULT;
   private boolean isDefaultTab = false;
-  private boolean isWidget = false;
   private boolean isGlobal = false;
   private String[] mandatoryMeasures = {};
   private String[] needOneOfMeasures = {};
 
-  public ViewProxy(final V view) {
+  public ViewProxy(V view) {
     this.view = view;
+    this.isWidget = (view instanceof Widget);
 
     initUserRoles(view);
     initSections(view);
@@ -83,8 +89,6 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
     initWidgetLayout(view);
     initWidgetGlobal(view);
     initRequiredMeasures(view);
-
-    isWidget = (view instanceof Widget);
   }
 
   private void initRequiredMeasures(V view) {
@@ -95,14 +99,14 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
     }
   }
 
-  private void initWidgetLayout(final V view) {
+  private void initWidgetLayout(V view) {
     WidgetLayout layoutAnnotation = AnnotationUtils.getAnnotation(view, WidgetLayout.class);
     if (layoutAnnotation != null) {
       widgetLayout = layoutAnnotation.value();
     }
   }
 
-  private void initWidgetCategory(final V view) {
+  private void initWidgetCategory(V view) {
     WidgetCategory categAnnotation = AnnotationUtils.getAnnotation(view, WidgetCategory.class);
     if (categAnnotation != null) {
       widgetCategories = categAnnotation.value();
@@ -125,29 +129,35 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
     }
   }
 
-  private void initWidgetProperties(final V view) {
+  private void initWidgetProperties(V view) {
     WidgetProperties propAnnotation = AnnotationUtils.getAnnotation(view, WidgetProperties.class);
     if (propAnnotation != null) {
+      for (WidgetPropertySet set : propAnnotation.sets()) {
+        for (WidgetProperty property : set.value()) {
+          widgetPropertiesBySet.put(set, property);
+          widgetPropertiesByKey.put(property.key(), property);
+        }
+      }
       for (WidgetProperty property : propAnnotation.value()) {
+        widgetPropertiesBySet.put(WidgetPropertySet.DEFAULT, property);
         widgetPropertiesByKey.put(property.key(), property);
       }
     }
   }
 
-  private void initDescription(final V view) {
+  private void initDescription(V view) {
     Description descriptionAnnotation = AnnotationUtils.getAnnotation(view, Description.class);
     if (descriptionAnnotation != null) {
       description = descriptionAnnotation.value();
     }
   }
 
-  private void initDefaultTabInfo(final V view) {
+  private void initDefaultTabInfo(V view) {
     DefaultTab defaultTabAnnotation = AnnotationUtils.getAnnotation(view, DefaultTab.class);
     if (defaultTabAnnotation != null) {
       if (defaultTabAnnotation.metrics().length == 0) {
         isDefaultTab = true;
         defaultForMetrics = new String[0];
-
       } else {
         isDefaultTab = false;
         defaultForMetrics = defaultTabAnnotation.metrics();
@@ -155,35 +165,35 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
     }
   }
 
-  private void initResourceLanguage(final V view) {
+  private void initResourceLanguage(V view) {
     ResourceLanguage languageAnnotation = AnnotationUtils.getAnnotation(view, ResourceLanguage.class);
     if (languageAnnotation != null) {
       resourceLanguages = languageAnnotation.value();
     }
   }
 
-  private void initResourceQualifier(final V view) {
+  private void initResourceQualifier(V view) {
     ResourceQualifier qualifierAnnotation = AnnotationUtils.getAnnotation(view, ResourceQualifier.class);
     if (qualifierAnnotation != null) {
       resourceQualifiers = qualifierAnnotation.value();
     }
   }
 
-  private void initResourceScope(final V view) {
+  private void initResourceScope(V view) {
     ResourceScope scopeAnnotation = AnnotationUtils.getAnnotation(view, ResourceScope.class);
     if (scopeAnnotation != null) {
       resourceScopes = scopeAnnotation.value();
     }
   }
 
-  private void initSections(final V view) {
+  private void initSections(V view) {
     NavigationSection sectionAnnotation = AnnotationUtils.getAnnotation(view, NavigationSection.class);
     if (sectionAnnotation != null) {
       sections = sectionAnnotation.value();
     }
   }
 
-  private void initUserRoles(final V view) {
+  private void initUserRoles(V view) {
     UserRole userRoleAnnotation = AnnotationUtils.getAnnotation(view, UserRole.class);
     if (userRoleAnnotation != null) {
       userRoles = userRoleAnnotation.value();
@@ -200,7 +210,7 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
 
   public boolean isController() {
     String id = view.getId();
-    return id !=null && id.length()>0 && id.charAt(0)=='/';
+    return id != null && !id.isEmpty() && id.charAt(0) == '/';
   }
 
   public String getTitle() {
@@ -215,6 +225,10 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
     return widgetPropertiesByKey.values();
   }
 
+  public SetMultimap<WidgetPropertySet, WidgetProperty> getWidgetPropertiesBySet() {
+    return ImmutableSetMultimap.copyOf(widgetPropertiesBySet);
+  }
+
   public WidgetProperty getWidgetProperty(String propertyKey) {
     return widgetPropertiesByKey.get(propertyKey);
   }
@@ -254,23 +268,24 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
   public boolean supportsMetric(String metricKey) {
     return ArrayUtils.contains(defaultForMetrics, metricKey);
   }
-  
+
   public boolean acceptsAvailableMeasures(String[] availableMeasures) {
     for (String mandatoryMeasure : mandatoryMeasures) {
       if (!ArrayUtils.contains(availableMeasures, mandatoryMeasure)) {
         return false;
       }
     }
+
     if (needOneOfMeasures.length == 0) {
       return true;
-    } else {
-      for (String neededMeasure : needOneOfMeasures) {
-        if (ArrayUtils.contains(availableMeasures, neededMeasure)) {
-          return true;
-        }
+    }
+
+    for (String neededMeasure : needOneOfMeasures) {
+      if (ArrayUtils.contains(availableMeasures, neededMeasure)) {
+        return true;
       }
-      return false;
     }
+    return false;
   }
 
   public boolean isWidget() {
@@ -294,13 +309,20 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
   }
 
   public boolean hasRequiredProperties() {
-    boolean requires = false;
     for (WidgetProperty property : getWidgetProperties()) {
       if (!property.optional() && StringUtils.isEmpty(property.defaultValue())) {
-        requires = true;
+        return true;
       }
     }
-    return requires;
+    return false;
+  }
+
+  public boolean validate(WidgetProperty property, String value) {
+    try {
+      return property.validation().newInstance().validate(value);
+    } catch (Exception e) {
+    }
+    return true;
   }
 
   @Override
@@ -336,7 +358,6 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
         .append("languages", resourceLanguages)
         .append("metrics", defaultForMetrics)
         .toString();
-
   }
 
   public int compareTo(ViewProxy other) {
@@ -344,6 +365,5 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
         .append(getTitle(), other.getTitle())
         .append(getId(), other.getId())
         .toComparison();
-
   }
 }
index 79225aa2e7298c75f8cb5373f5f592b68d241b48..b14797cfcc3979f02a81b8515a017ec103d26ece 100644 (file)
       </tr>
     <% end %>
 
-    <% widget.java_definition.getWidgetProperties().sort { |w1, w2| natural_comparison(w1.key, w2.key) }.each do |property_def| %>
-      <tr>
-        <td class="form-key-cell"><%= property_def.key() -%><%= " *" unless property_def.optional() -%></td>
-        <td class="form-val-cell" id="row_<%= property_def.key() -%>">
-          <%= property_value_field(property_def, widget.property_text_value(property_def.key())) -%>
-          <div class="form-val-note">
-            <%= message("widget." + widget.key + ".param." + property_def.key(), :default => property_def.description()) -%>
-          </div>
-        </td>
-      </tr>
+    <% widget.java_definition.getWidgetPropertiesBySet().asMap().sort { |(s1,p1), (s2,p2)| natural_comparison(s1.key, s2.key) }.each do |set,properties| %>
+        <% if widget.java_definition.getWidgetPropertiesBySet().asMap().size > 1 %>
+          <% if set.key().empty? %>
+             <tr>
+              <td celspan="2"><h3>other</h3></td>
+            </tr>
+          <% else %>
+            <tr>
+              <td celspan="2"><h3><%= set.key() -%></h3></td>
+            </tr>
+          <% end %>
+        <% end %>
+        <% properties.sort { |w1, w2| natural_comparison(w1.key, w2.key) }.each do |property_def| %>
+         <tr>
+            <td class="form-key-cell"><%= property_def.key() -%><%= " *" unless property_def.optional() -%></td>
+            <td class="form-val-cell" id="row_<%= property_def.key() -%>">
+                <%= property_value_field(property_def, widget.property_text_value(property_def.key())) -%>
+                <div class="form-val-note">
+                    <%= message("widget." + widget.key + ".param." + property_def.key(), :default => property_def.description()) -%>
+                </div>
+            </td>
+         </tr>
+      <% end %>
     <% end %>
 
     <tr>
index e32606307912e39e5134f1717235dc61438ce0d3..725592eb4ab0068e990acf0a3a751e9d408a9c6b 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.server.ui;
 
+import org.sonar.api.web.WidgetPropertySet;
+
 import org.junit.rules.ExpectedException;
 
 import org.junit.Rule;
@@ -139,6 +141,15 @@ public class ViewProxyTest {
     assertThat(proxy.getWidgetProperties()).hasSize(2);
   }
 
+  @Test
+  public void should_support_property_sets() {
+    ViewProxy proxy = new ViewProxy<Widget>(new EditableWidgetWithSets());
+
+    assertThat(proxy.getWidgetProperties()).hasSize(4);
+    assertThat(proxy.getWidgetPropertiesBySet().keySet()).hasSize(3);
+    assertThat(proxy.getWidgetPropertiesBySet().values()).hasSize(4);
+  }
+
   @Test
   public void widget_should_not_be_global_by_default() {
     ViewProxy proxy = new ViewProxy<Widget>(new EditableWidget());
@@ -158,7 +169,7 @@ public class ViewProxyTest {
     exception.expect(IllegalArgumentException.class);
     exception.expectMessage("INVALID");
     exception.expectMessage("WidgetWithInvalidScope");
-    
+
     new ViewProxy<Widget>(new WidgetWithInvalidScope());
   }
 
@@ -256,7 +267,6 @@ class FakeView implements View {
   @WidgetProperty(key = "bar", defaultValue = "30", type = WidgetPropertyType.INTEGER)
 })
 class EditableWidget implements Widget {
-
   public String getId() {
     return "w1";
   }
@@ -266,6 +276,29 @@ class EditableWidget implements Widget {
   }
 }
 
+@WidgetProperties(sets = {
+  @WidgetPropertySet(key = "set1",
+    value = {
+      @WidgetProperty(key = "foo", optional = false),
+      @WidgetProperty(key = "bar", optional = false),
+    }),
+  @WidgetPropertySet(key = "set2",
+    value = {
+      @WidgetProperty(key = "qix", optional = false),
+    })},
+  value = {
+    @WidgetProperty(key = "fizz", optional = false)
+  })
+class EditableWidgetWithSets implements Widget {
+  public String getId() {
+    return "w3";
+  }
+
+  public String getTitle() {
+    return "W3";
+  }
+}
+
 @WidgetScope("GLOBAL")
 class GlobalWidget implements Widget {
   public String getId() {
@@ -293,7 +326,6 @@ class WidgetWithInvalidScope implements Widget {
   @WidgetProperty(key = "bar")
 })
 class WidgetWithOptionalProperties implements Widget {
-
   public String getId() {
     return "w2";
   }