diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2011-12-15 18:44:21 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2011-12-15 18:44:21 +0100 |
commit | 16232e514c5b0d064a114906fb31441b97a480ae (patch) | |
tree | ffdea318d342f10e6396ae5120ea073d0acc1738 | |
parent | b84f33030464fcc3e6e70affb1680381516a19e6 (diff) | |
download | sonarqube-16232e514c5b0d064a114906fb31441b97a480ae.tar.gz sonarqube-16232e514c5b0d064a114906fb31441b97a480ae.zip |
SONAR-1929 refactor the extension point to define dashboards
17 files changed, 392 insertions, 305 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 b56dd42f55c..58b343aeb46 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 @@ -34,7 +34,7 @@ import org.sonar.plugins.core.charts.DistributionBarChart; import org.sonar.plugins.core.charts.XradarChart; import org.sonar.plugins.core.colorizers.JavaColorizerFormat; import org.sonar.plugins.core.dashboards.HotspotsDashboard; -import org.sonar.plugins.core.dashboards.MainDashboard; +import org.sonar.plugins.core.dashboards.DefaultDashboard; import org.sonar.plugins.core.metrics.UserManagedMetrics; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; import org.sonar.plugins.core.sensors.*; @@ -250,7 +250,7 @@ public class CorePlugin extends SonarPlugin { extensions.add(ReviewsPerDeveloperWidget.class); // dashboards - extensions.add(MainDashboard.class); + extensions.add(DefaultDashboard.class); extensions.add(HotspotsDashboard.class); // chart diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/MainDashboard.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/DefaultDashboard.java index 46d1a35e850..85f331b914a 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/MainDashboard.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/DefaultDashboard.java @@ -28,24 +28,32 @@ import org.sonar.api.web.dashboard.DashboardTemplate; * * @since 2.13 */ -public final class MainDashboard extends DashboardTemplate { +public final class DefaultDashboard extends DashboardTemplate { @Override public Dashboard createDashboard() { - Dashboard dashboard = Dashboard.create("main", "Dashboard"); + Dashboard dashboard = Dashboard.create("dashboard", "Dashboard"); dashboard.setLayout(DashboardLayout.TWO_COLUMNS); - dashboard.addWidget("size", 1, 1); - dashboard.addWidget("comments_duplications", 1, 2); - dashboard.addWidget("complexity", 1, 3); - dashboard.addWidget("code_coverage", 1, 4); - dashboard.addWidget("events", 1, 5); - dashboard.addWidget("description", 1, 6); - dashboard.addWidget("rules", 2, 1); - dashboard.addWidget("alerts", 2, 2); - dashboard.addWidget("file_design", 2, 3); - dashboard.addWidget("package_design", 2, 4); - dashboard.addWidget("ckjm", 2, 5); + addFirstColumn(dashboard); + addSecondColumn(dashboard); return dashboard; } + private void addFirstColumn(Dashboard dashboard) { + dashboard.addWidget("size", 1); + dashboard.addWidget("comments_duplications", 1); + dashboard.addWidget("complexity", 1); + dashboard.addWidget("code_coverage", 1); + dashboard.addWidget("events", 1); + dashboard.addWidget("description", 1); + } + + private void addSecondColumn(Dashboard dashboard) { + dashboard.addWidget("rules", 2); + dashboard.addWidget("alerts", 2); + dashboard.addWidget("file_design", 2); + dashboard.addWidget("package_design", 2); + dashboard.addWidget("ckjm", 2); + } + }
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/HotspotsDashboard.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/HotspotsDashboard.java index 5624cb34409..c8b9a4fb648 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/HotspotsDashboard.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/HotspotsDashboard.java @@ -20,8 +20,8 @@ package org.sonar.plugins.core.dashboards; import org.sonar.api.web.dashboard.Dashboard; +import org.sonar.api.web.dashboard.DashboardLayout; import org.sonar.api.web.dashboard.DashboardTemplate; -import org.sonar.api.web.dashboard.Widget; /** * Hotspot dashboard for Sonar @@ -36,37 +36,45 @@ public final class HotspotsDashboard extends DashboardTemplate { @Override public Dashboard createDashboard() { - Dashboard dashboard = Dashboard.create("hotspots", "Hotspots"); + Dashboard dashboard = Dashboard.createByName("Hotspots"); + dashboard.setLayout(DashboardLayout.TWO_COLUMNS); - Widget widget = dashboard.addWidget("hotspot_most_violated_rules", 1, 1); + addFirstColumn(dashboard); + addSecondColumn(dashboard); - widget = dashboard.addWidget(HOTSPOT_METRIC, 1, 2); - widget.addProperty(METRIC_PROPERTY, "test_execution_time"); - widget.addProperty(TITLE_PROPERTY, "Longest unit tests"); + return dashboard; + } - widget = dashboard.addWidget(HOTSPOT_METRIC, 1, 3); - widget.addProperty(METRIC_PROPERTY, "complexity"); - widget.addProperty(TITLE_PROPERTY, "Highest complexity"); + private void addFirstColumn(Dashboard dashboard) { + dashboard.addWidget("hotspot_most_violated_rules", 1); - widget = dashboard.addWidget(HOTSPOT_METRIC, 1, 4); - widget.addProperty(METRIC_PROPERTY, "duplicated_lines"); - widget.addProperty(TITLE_PROPERTY, "Highest duplications"); + Dashboard.Widget widget = dashboard.addWidget(HOTSPOT_METRIC, 1); + widget.setProperty(METRIC_PROPERTY, "test_execution_time"); + widget.setProperty(TITLE_PROPERTY, "Longest unit tests"); - widget = dashboard.addWidget("hotspot_most_violated_resources", 2, 1); + widget = dashboard.addWidget(HOTSPOT_METRIC, 1); + widget.setProperty(METRIC_PROPERTY, "complexity"); + widget.setProperty(TITLE_PROPERTY, "Highest complexity"); - widget = dashboard.addWidget(HOTSPOT_METRIC, 2, 2); - widget.addProperty(METRIC_PROPERTY, "uncovered_lines"); - widget.addProperty(TITLE_PROPERTY, "Highest untested lines"); + widget = dashboard.addWidget(HOTSPOT_METRIC, 1); + widget.setProperty(METRIC_PROPERTY, "duplicated_lines"); + widget.setProperty(TITLE_PROPERTY, "Highest duplications"); + } - widget = dashboard.addWidget(HOTSPOT_METRIC, 2, 3); - widget.addProperty(METRIC_PROPERTY, "function_complexity"); - widget.addProperty(TITLE_PROPERTY, "Highest average method complexity"); + private void addSecondColumn(Dashboard dashboard) { + dashboard.addWidget("hotspot_most_violated_resources", 2); - widget = dashboard.addWidget(HOTSPOT_METRIC, 2, 4); - widget.addProperty(METRIC_PROPERTY, "public_undocumented_api"); - widget.addProperty(TITLE_PROPERTY, "Most undocumented APIs"); + Dashboard.Widget widget = dashboard.addWidget(HOTSPOT_METRIC, 2); + widget.setProperty(METRIC_PROPERTY, "uncovered_lines"); + widget.setProperty(TITLE_PROPERTY, "Highest untested lines"); - return dashboard; + widget = dashboard.addWidget(HOTSPOT_METRIC, 2); + widget.setProperty(METRIC_PROPERTY, "function_complexity"); + widget.setProperty(TITLE_PROPERTY, "Highest average method complexity"); + + widget = dashboard.addWidget(HOTSPOT_METRIC, 2); + widget.setProperty(METRIC_PROPERTY, "public_undocumented_api"); + widget.setProperty(TITLE_PROPERTY, "Most undocumented APIs"); } }
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_metric.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_metric.html.erb index 7c397e1366b..47368a29e23 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_metric.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_metric.html.erb @@ -36,7 +36,7 @@ <% unless snapshots && !snapshots.empty? %> <h3><%= title -%></h3> - <span class="empty_widget"><%= message('widget.hotspot_metric.cannot_display_not_numeric_metric') -%></span> + <span class="empty_widget"><%= message('no_results') -%></span> <% else %> <div class="line-block"> diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/DefaultDashboardTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/DefaultDashboardTest.java new file mode 100644 index 00000000000..d5148fa1767 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/DefaultDashboardTest.java @@ -0,0 +1,38 @@ +/* + * 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.dashboards; + +import org.hamcrest.core.Is; +import org.junit.Test; +import org.sonar.api.web.dashboard.Dashboard; +import org.sonar.api.web.dashboard.DashboardLayout; + +import static org.junit.Assert.assertThat; + +public class DefaultDashboardTest { + @Test + public void shouldCreateDashboard() { + Dashboard main = new DefaultDashboard().createDashboard(); + assertThat(main.getId(), Is.is("dashboard")); + assertThat(main.getName(), Is.is("Dashboard")); + assertThat(main.getLayout(), Is.is(DashboardLayout.TWO_COLUMNS)); + assertThat(main.getWidgets().size(), Is.is(11)); + } +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/HotspotsDashboardTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/HotspotsDashboardTest.java new file mode 100644 index 00000000000..1057e9a5c96 --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/dashboards/HotspotsDashboardTest.java @@ -0,0 +1,38 @@ +/* + * 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.dashboards; + +import org.hamcrest.core.Is; +import org.junit.Test; +import org.sonar.api.web.dashboard.Dashboard; +import org.sonar.api.web.dashboard.DashboardLayout; + +import static org.junit.Assert.assertThat; + +public class HotspotsDashboardTest { + @Test + public void shouldCreateDashboard() { + Dashboard hotspots = new HotspotsDashboard().createDashboard(); + assertThat(hotspots.getId(), Is.is("hotspots")); + assertThat(hotspots.getName(), Is.is("Hotspots")); + assertThat(hotspots.getLayout(), Is.is(DashboardLayout.TWO_COLUMNS)); + assertThat(hotspots.getWidgets().size(), Is.is(8)); + } +} 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 ab73c7f6e6b..fed078cfa23 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 @@ -298,7 +298,6 @@ lcom4_viewer.page=LCOM4 # GWT pages -org.sonar.plugins.core.hotspots.GwtHotspots.page=Hotspots org.sonar.plugins.design.ui.page.DesignPage.page=Design org.sonar.plugins.design.ui.libraries.LibrariesPage.page=Libraries org.sonar.plugins.design.ui.dependencies.DependenciesTab.page=Dependencies @@ -460,11 +459,12 @@ property.category.server_id=Server ID # #------------------------------------------------------------------------------ -dashboard.sonar-main.name=Dashboard -dashboard.sonar-main.description=Default dashboard +# Default dashboard +dashboard.dashboard.name=Dashboard +dashboard.dashboard.description=Default dashboard -dashboard.sonar-hotspots.name=Hotspots -dashboard.sonar-hotspots.description=Most useful hotspots widgets +dashboard.hotspots.name=Hotspots +dashboard.hotspots.description=Most useful hotspots widgets #------------------------------------------------------------------------------ @@ -589,7 +589,6 @@ 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 this metric. widget.hotspot_metric.more=More widget.hotspot_metric.hotspots_by_x=Hotspots by {0} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Dashboard.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Dashboard.java index 5e91fa7f8b0..9f56b90eab6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Dashboard.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Dashboard.java @@ -19,9 +19,14 @@ */ package org.sonar.api.web.dashboard; -import com.google.common.collect.Lists; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; import java.util.Collection; +import java.util.List; +import java.util.Map; /** * Definition of a dashboard. @@ -37,7 +42,7 @@ public final class Dashboard { private String name; private String description; private DashboardLayout layout = DashboardLayout.TWO_COLUMNS; - private Collection<Widget> widgets = Lists.newArrayList(); + private ListMultimap<Integer, Widget> widgetsByColumn = ArrayListMultimap.create(); private Dashboard() { } @@ -52,21 +57,36 @@ public final class Dashboard { } /** - * Add a widget with the given parameters, and return the newly created {@link Widget} object if one wants to add parameters to it. + * The id is deduced from the name. */ - public Widget addWidget(String id, int columnId, int rowId) { - Widget widget = new Widget(id, columnId, rowId); - widgets.add(widget); - return widget; + public static Dashboard createByName(String name) { + String id = StringUtils.trimToEmpty(name); + id = StringUtils.lowerCase(id); + id = StringUtils.replaceChars(id, ' ', '_'); + return new Dashboard() + .setId(id) + .setName(name); } /** - * Returns the list of widgets. - * - * @return the widgets of this dashboard + * Add a widget with the given parameters, and return the newly created {@link Widget} object if one wants to add parameters to it. */ + public Widget addWidget(String widgetId, int columnId) { + if (columnId < 1) { + throw new IllegalArgumentException("Widget column starts with 1"); + } + + Widget widget = new Widget(widgetId); + widgetsByColumn.put(columnId, widget); + return widget; + } + public Collection<Widget> getWidgets() { - return widgets; + return widgetsByColumn.values(); + } + + public List<Widget> getWidgetsOfColumn(int columnId) { + return widgetsByColumn.get(columnId); } /** @@ -82,6 +102,9 @@ public final class Dashboard { * @param id the id to set */ private Dashboard setId(String id) { + if (StringUtils.isBlank(id)) { + throw new IllegalArgumentException("Dashboard id can not be blank"); + } this.id = id; return this; } @@ -133,12 +156,49 @@ public final class Dashboard { return layout; } + public Dashboard setLayout(DashboardLayout dl) { + if (dl == null) { + throw new IllegalArgumentException("The layout of the dashboard '" + getId() + "' can not be null"); + } + this.layout = dl; + return this; + } + + /** - * @param layout the layout to set + * Note that this class is an inner class to avoid confusion with the extension point org.sonar.api.web.Widget. */ - public Dashboard setLayout(DashboardLayout layout) { - this.layout = layout; - return this; + public static final class Widget { + private String id; + private Map<String, String> properties; + + Widget(String id) { + this.id = id; + this.properties = Maps.newHashMap(); + } + + public Widget setProperty(String key, String value) { + properties.put(key, value); + return this; + } + + /** + * Returns the properties of this widget. + * + * @return the properties + */ + public Map<String, String> getProperties() { + return properties; + } + + /** + * Returns the identifier of this widget. + * + * @return the id + */ + public String getId() { + return id; + } } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/DashboardLayout.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/DashboardLayout.java index 02d83565008..3b698476dc7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/DashboardLayout.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/DashboardLayout.java @@ -29,37 +29,47 @@ public enum DashboardLayout { /** * Only 1 column that take all the page */ - ONE_COLUMN("100%"), + ONE_COLUMN("100%", 1), /** * 2 columns of the same width */ - TWO_COLUMNS("50%-50%"), + TWO_COLUMNS("50%-50%", 2), /** * 2 columns with the first one smaller than the second */ - TWO_COLUMNS_30_70("30%-70%"), + TWO_COLUMNS_30_70("30%-70%", 2), /** * 2 columns with the first one bigger than the second */ - TWO_COLUMNS_70_30("70%-30%"), + TWO_COLUMNS_70_30("70%-30%", 2), /** * 3 columns of the same width */ - TREE_COLUMNS("33%-33%-33%"); + TREE_COLUMNS("33%-33%-33%", 3); - private String layout; + private String code; + private int columns; - private DashboardLayout(String layout) { - this.layout = layout; + private DashboardLayout(String code, int columns) { + this.code = code; + this.columns = columns; + } + + public String getCode() { + return code; + } + + public int getColumns() { + return columns; } @Override public String toString() { - return layout; + return code; } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Widget.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Widget.java deleted file mode 100644 index 8929f4f60f9..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/web/dashboard/Widget.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.api.web.dashboard; - -import java.util.Map; - -import com.google.common.collect.Maps; - -/** - * - * Definition of a widget inside a dashboard. - * - * @since 2.13 - */ -public final class Widget { - - private String id; - private int columnIndex; - private int rowIndex; - private Map<String, String> properties; - - Widget(String id, int columnIndex, int rowIndex) { - this.id = id; - this.columnIndex = columnIndex; - this.rowIndex = rowIndex; - this.properties = Maps.newHashMap(); - } - - /** - * Adds a property to this widget. - * - * @param key - * the id of the property - * @param value - * the value of the property - */ - public void addProperty(String key, String value) { - properties.put(key, value); - } - - /** - * Returns the properties of this widget. - * - * @return the properties - */ - public Map<String, String> getProperties() { - return properties; - } - - /** - * Returns the identifier of this widget. - * - * @return the id - */ - public String getId() { - return id; - } - - /** - * Returns the column index of this widget. - * - * @return the columnIndex - */ - public int getColumnIndex() { - return columnIndex; - } - - /** - * Returns the row index of this widget. - * - * @return the rowIndex - */ - public int getRowIndex() { - return rowIndex; - } - -} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/DashboardTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/DashboardTest.java index 3555d786c4e..b35a740c2ea 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/DashboardTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/DashboardTest.java @@ -19,11 +19,9 @@ */ package org.sonar.api.web.dashboard; +import org.hamcrest.core.Is; import org.junit.Test; -import java.util.Map.Entry; -import java.util.Set; - import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; @@ -31,36 +29,64 @@ import static org.junit.Assert.assertThat; public class DashboardTest { @Test - public void shouldCreateDashboardAndWidget() throws Exception { + public void shouldCreateDashboard() { Dashboard dashboard = Dashboard.create("fake-dashboard", "Fake"); assertThat(dashboard.getId(), is("fake-dashboard")); assertThat(dashboard.getName(), is("Fake")); assertThat(dashboard.getLayout(), is(DashboardLayout.TWO_COLUMNS)); assertThat(dashboard.getDescription(), nullValue()); + } + + @Test + public void shouldAddWidgets() { + Dashboard dashboard = Dashboard.createByName("Fake"); + Dashboard.Widget mostViolatedRules = dashboard.addWidget("most_violated_rules", 1); + assertThat(mostViolatedRules.getId(), is("most_violated_rules")); + assertThat(dashboard.getWidgets().size(), is(1)); + assertThat(dashboard.getWidgetsOfColumn(1).size(), is(1)); - Widget widget = dashboard.addWidget("fake-widget", 12, 13); - assertThat(widget.getId(), is("fake-widget")); - assertThat(widget.getColumnIndex(), is(12)); - assertThat(widget.getRowIndex(), is(13)); + dashboard.addWidget("hotspots", 1); + assertThat(dashboard.getWidgets().size(), is(2)); + assertThat(dashboard.getWidgetsOfColumn(1).size(), is(2)); - widget.addProperty("fake-property", "fake_metric"); - Set<Entry<String, String>> properties = widget.getProperties().entrySet(); - assertThat(properties.size(), is(1)); - Entry<String, String> property = properties.iterator().next(); - assertThat(property.getKey(), is("fake-property")); - assertThat(property.getValue(), is("fake_metric")); + // widgets are sorted by order of insertion + assertThat(dashboard.getWidgetsOfColumn(1).get(1).getId(), is("hotspots")); } @Test - public void shouldAddWidget() throws Exception { - Dashboard dashboard = Dashboard.create("fake-dashboard", "Fake"); - dashboard.addWidget("fake-widget", 12, 13); + public void shouldAddWidgetsOnDifferentColumns() { + Dashboard dashboard = Dashboard.createByName("Fake"); + + dashboard.addWidget("most_violated_rules", 1); + assertThat(dashboard.getWidgets().size(), is(1)); + assertThat(dashboard.getWidgetsOfColumn(1).size(), is(1)); - Widget widget = dashboard.getWidgets().iterator().next(); + dashboard.addWidget("hotspots", 2); + assertThat(dashboard.getWidgets().size(), is(2)); + assertThat(dashboard.getWidgetsOfColumn(2).size(), is(1)); + } - assertThat(widget.getId(), is("fake-widget")); - assertThat(widget.getColumnIndex(), is(12)); - assertThat(widget.getRowIndex(), is(13)); + @Test(expected = IllegalArgumentException.class) + public void shouldFailIfBlankId() { + Dashboard.create(" ", "Fake"); } + @Test(expected = IllegalArgumentException.class) + public void shouldFailToDeduceIdFromName() { + Dashboard.createByName(" "); + } + + @Test + public void shouldCreateByName() { + Dashboard dashboard = Dashboard.createByName("Fake"); + assertThat(dashboard.getId(), Is.is("fake")); + + dashboard = Dashboard.createByName(" Fake You "); + assertThat(dashboard.getId(), Is.is("fake_you")); + } + + @Test + public void shouldAddSeveralTimesTheSameWidget() { + // TODO + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/WidgetTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/WidgetTest.java index a463fc81bf9..95af6699d0f 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/WidgetTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/dashboard/WidgetTest.java @@ -30,19 +30,19 @@ import static org.junit.Assert.assertThat; public class WidgetTest { @Test - public void shouldCreateWidgetWithProperties() throws Exception { - Dashboard dashboard = Dashboard.create("fake-dashboard", "Fake"); - Widget widget = dashboard.addWidget("fake-widget", 12, 13); + public void shouldCreateWidget() { + Dashboard dashboard = Dashboard.createByName("Fake"); + Dashboard.Widget widget = dashboard.addWidget("fake-widget", 1); assertThat(widget.getId(), is("fake-widget")); - assertThat(widget.getColumnIndex(), is(12)); - assertThat(widget.getRowIndex(), is(13)); - - widget.addProperty("fake-property", "fake_metric"); - Set<Entry<String, String>> properties = widget.getProperties().entrySet(); - assertThat(properties.size(), is(1)); - Entry<String, String> property = properties.iterator().next(); - assertThat(property.getKey(), is("fake-property")); - assertThat(property.getValue(), is("fake_metric")); + } + + @Test + public void shouldSetProperty() { + Dashboard dashboard = Dashboard.createByName("Fake"); + Dashboard.Widget widget = dashboard.addWidget("fake-widget", 1); + widget.setProperty("foo", "bar"); + + assertThat(widget.getProperties().get("foo"), is("bar")); } } diff --git a/sonar-server/src/main/java/org/sonar/server/startup/RegisterNewDashboards.java b/sonar-server/src/main/java/org/sonar/server/startup/RegisterNewDashboards.java index 462cc0899e1..66d356278af 100644 --- a/sonar-server/src/main/java/org/sonar/server/startup/RegisterNewDashboards.java +++ b/sonar-server/src/main/java/org/sonar/server/startup/RegisterNewDashboards.java @@ -25,7 +25,6 @@ import org.slf4j.LoggerFactory; import org.sonar.api.utils.TimeProfiler; import org.sonar.api.web.dashboard.Dashboard; import org.sonar.api.web.dashboard.DashboardTemplate; -import org.sonar.api.web.dashboard.Widget; import org.sonar.persistence.dashboard.*; import org.sonar.persistence.template.LoadedTemplateDao; import org.sonar.persistence.template.LoadedTemplateDto; @@ -42,7 +41,7 @@ import java.util.Map.Entry; public final class RegisterNewDashboards { private static final Logger LOG = LoggerFactory.getLogger(RegisterNewDashboards.class); - private static final String MAIN_DASHBOARD_ID = "main"; + static final String DEFAULT_DASHBOARD_ID = "dashboard"; private List<DashboardTemplate> dashboardTemplates; private DashboardDao dashboardDao; @@ -66,28 +65,28 @@ public final class RegisterNewDashboards { // load the dashboards that need to be loaded List<DashboardDto> loadedDashboards = Lists.newArrayList(); - DashboardDto mainDashboard = null; + DashboardDto defaultDashboard = null; for (DashboardTemplate dashboardTemplate : dashboardTemplates) { Dashboard dashboard = dashboardTemplate.createDashboard(); if (shouldBeRegistered(dashboard)) { DashboardDto dashboardDto = loadDashboard(dashboard); - if (MAIN_DASHBOARD_ID.equals(dashboard.getId())) { - mainDashboard = dashboardDto; + if (DEFAULT_DASHBOARD_ID.equals(dashboard.getId())) { + defaultDashboard = dashboardDto; } else { loadedDashboards.add(dashboardDto); } } } // and activate them - activateDashboards(loadedDashboards, mainDashboard); + activateDashboards(loadedDashboards, defaultDashboard); profiler.stop(); } - protected void activateDashboards(List<DashboardDto> loadedDashboards, DashboardDto mainDashboard) { + protected void activateDashboards(List<DashboardDto> loadedDashboards, DashboardDto defaultDashboard) { int nextOrderIndex; - if (mainDashboard != null) { - activateDashboard(mainDashboard, 1); + if (defaultDashboard != null) { + activateDashboard(defaultDashboard, 1); nextOrderIndex = 2; } else { nextOrderIndex = activeDashboardDao.selectMaxOrderIndexForNullUser() + 1; @@ -121,29 +120,33 @@ public final class RegisterNewDashboards { dashboardDto.setKey(dashboard.getId()); dashboardDto.setName(dashboard.getName()); dashboardDto.setDescription(dashboard.getDescription()); - dashboardDto.setColumnLayout(dashboard.getLayout().toString()); + dashboardDto.setColumnLayout(dashboard.getLayout().getCode()); dashboardDto.setShared(true); dashboardDto.setCreatedAt(now); dashboardDto.setUpdatedAt(now); - for (Widget widget : dashboard.getWidgets()) { - WidgetDto widgetDto = new WidgetDto(); - widgetDto.setKey(widget.getId()); - widgetDto.setColumnIndex(widget.getColumnIndex()); - widgetDto.setRowIndex(widget.getRowIndex()); - widgetDto.setConfigured(true); - widgetDto.setCreatedAt(now); - widgetDto.setUpdatedAt(now); - dashboardDto.addWidget(widgetDto); - - for (Entry<String, String> property : widget.getProperties().entrySet()) { - WidgetPropertyDto propDto = new WidgetPropertyDto(); - propDto.setKey(property.getKey()); - propDto.setValue(property.getValue()); - widgetDto.addWidgetProperty(propDto); + for (int columnIndex = 1; columnIndex <= dashboard.getLayout().getColumns(); columnIndex++) { + List<Dashboard.Widget> widgets = dashboard.getWidgetsOfColumn(columnIndex); + for (int rowIndex = 1; rowIndex <= widgets.size(); rowIndex++) { + Dashboard.Widget widget = widgets.get(rowIndex - 1); + WidgetDto widgetDto = new WidgetDto(); + widgetDto.setKey(widget.getId()); + widgetDto.setColumnIndex(columnIndex); + widgetDto.setRowIndex(rowIndex); + widgetDto.setConfigured(true); + widgetDto.setCreatedAt(now); + widgetDto.setUpdatedAt(now); + dashboardDto.addWidget(widgetDto); + + for (Entry<String, String> property : widget.getProperties().entrySet()) { + WidgetPropertyDto propDto = new WidgetPropertyDto(); + propDto.setKey(property.getKey()); + propDto.setValue(property.getValue()); + widgetDto.addWidgetProperty(propDto); + } } - } + } return dashboardDto; } diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/235_create_loaded_templates.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/235_create_loaded_templates.rb index bd313df7d43..88a50a17a47 100644 --- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/235_create_loaded_templates.rb +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/235_create_loaded_templates.rb @@ -30,9 +30,9 @@ class CreateLoadedTemplates < ActiveRecord::Migration end # if this is a migration, then the default dashboard already exists in the DB so it should not be loaded again - main_dashboard = Dashboard.find(:first, :conditions => {:name => 'Dashboard'}) - if main_dashboard - LoadedTemplate.create({:kee => 'sonar-main', :template_type => 'DASHBOARD'}) + default_dashboard = Dashboard.find(:first, :conditions => {:name => 'Dashboard', :user_id => nil}) + if default_dashboard + LoadedTemplate.create({:kee => 'dashboard', :template_type => 'DASHBOARD'}) end end diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/236_add_key_to_dashboards.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/236_add_key_to_dashboards.rb index 77edc445efa..3650036b005 100644 --- a/sonar-server/src/main/webapp/WEB-INF/db/migrate/236_add_key_to_dashboards.rb +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/236_add_key_to_dashboards.rb @@ -32,11 +32,6 @@ class AddKeyToDashboards < ActiveRecord::Migration Dashboard.update_all "kee = '#{key}'", ["id = ?", d.id] end - main_dashboard = Dashboard.find(:first, :conditions => {:name => 'Dashboard'}) - if main_dashboard - Dashboard.update_all "kee = 'main'", ["id = ?", main_dashboard.id] - end - change_column 'dashboards', 'kee', :string, :limit => 200, :null => false Dashboard.reset_column_information end diff --git a/sonar-server/src/main/webapp/javascripts/protovis-sonar.js b/sonar-server/src/main/webapp/javascripts/protovis-sonar.js index 0fa5a591ecc..9b415412add 100755 --- a/sonar-server/src/main/webapp/javascripts/protovis-sonar.js +++ b/sonar-server/src/main/webapp/javascripts/protovis-sonar.js @@ -33,15 +33,15 @@ SonarWidgets.StackArea = function (divId) { } SonarWidgets.StackArea.prototype.render = function() { - + var trendData = this.wData; var metrics = this.wMetrics; var snapshots = this.wSnapshots; var colors = this.wColors; - + var widgetDiv = $(this.wDivId); var headerFont = "10.5px Arial,Helvetica,sans-serif"; - + /* Computes the total of the trendData of each date */ var total = []; for (i=0; i<trendData[0].size(); i++) { @@ -51,7 +51,7 @@ SonarWidgets.StackArea.prototype.render = function() { } total[i] = "" + Math.round(total[i]*10)/10 } - + /* Computes the highest Y value */ var maxY = 0; for (i=0; i<trendData[0].size(); i++) { @@ -61,23 +61,23 @@ SonarWidgets.StackArea.prototype.render = function() { } if (currentYSum > maxY) { maxY = currentYSum;} } - + /* Computes minimum width of left margin according to the max Y value so that the Y-axis is correctly displayed */ var leftMargin = 25; var maxYLength = (Math.round(maxY) + "").length; minMargin = maxYLength * 7 + Math.floor(maxYLength /3) * 2; // first part is for numbers and second for commas (1000-separator) if (minMargin > leftMargin) { leftMargin = minMargin; } - + /* Sizing and scales. */ var headerHeight = 40; - var w = widgetDiv.getOffsetParent().getWidth() - leftMargin - 40; + var w = widgetDiv.getOffsetParent().getWidth() - leftMargin - 40; var h = (this.wHeight == null ? 200 : this.wHeight) + headerHeight; var x = pv.Scale.linear(pv.blend(pv.map(trendData, function(d) {return d;})), function(d) {return d.x}).range(0, w); var y = pv.Scale.linear(0, maxY).range(0, h-headerHeight); var idx_numbers = trendData[0].size(); var idx = idx_numbers - 1; - + function computeIdx(xPixels) { var mx = x.invert(xPixels); var i = pv.search(trendData[0].map(function(d) {return d.x;}), mx); @@ -106,7 +106,7 @@ SonarWidgets.StackArea.prototype.render = function() { .anchor("bottom") .add(pv.Label) .text(x.tickFormat); - + /* Y-axis and ticks. */ vis.add(pv.Rule) .data(y.ticks(6)) @@ -115,7 +115,7 @@ SonarWidgets.StackArea.prototype.render = function() { .anchor("left") .add(pv.Label) .text(y.tickFormat); - + /* The stack layout */ var area = vis.add(pv.Layout.Stack) .layers(trendData) @@ -125,7 +125,7 @@ SonarWidgets.StackArea.prototype.render = function() { .add(pv.Area) .fillStyle(function() {return colors[this.parent.index % colors.size()][0];}) .strokeStyle("rgba(128,128,128,.8)"); - + /* Stack labels. */ var firstIdx = computeIdx(w/5); var lastIdx = computeIdx(w*4/5); @@ -141,53 +141,51 @@ SonarWidgets.StackArea.prototype.render = function() { .font(function(d) { return Math.round(5 + Math.sqrt(y(d.y))) + "px sans-serif";}) .textStyle("#DDD") .text(function(d) {return metrics[this.parent.index] + ": " + d.y;}); - + /* The total cost of the selected dot in the header. */ vis.add(pv.Label) .left(8) .top(16) .font(headerFont) .text(function() {return "Total: " + total[idx];}); - + /* The date of the selected dot in the header. */ vis.add(pv.Label) .left(w/2) .top(16) .font(headerFont) .text(function() {return snapshots[idx].ld;}); - - + + /* The event labels */ - if (true) { - eventColor = "rgba(75,159,213,1)"; - eventHoverColor = "rgba(202,227,242,1)"; - vis.add(pv.Line) - .strokeStyle("rgba(0,0,0,.001)") - .data(snapshots) - .left(function(s) {return x(s.d);}) - .bottom(0) - .anchor("top") - .add(pv.Dot) - .bottom(-6) - .shape("triangle") - .angle(pv.radians(180)) - .strokeStyle("grey") - .visible(function(s) {return s.e.size() > 0;}) - .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;}) - .add(pv.Dot) - .radius(3) - .visible(function(s) {return s.e.size() > 0 && this.index == idx;}) - .left(w/2+8) - .top(24) - .shape("triangle") - .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;}) - .strokeStyle("grey") - .anchor("right") - .add(pv.Label) - .font(headerFont) - .text(function(s) {return s.e.size() == 0 ? "" : s.e[0] + ( s.e[1] ? " (... +" + (s.e.size()-1) + ")" : "");}); - } - + eventColor = "rgba(75,159,213,1)"; + eventHoverColor = "rgba(202,227,242,1)"; + vis.add(pv.Line) + .strokeStyle("rgba(0,0,0,.001)") + .data(snapshots) + .left(function(s) {return x(s.d);}) + .bottom(0) + .anchor("top") + .add(pv.Dot) + .bottom(-6) + .shape("triangle") + .angle(pv.radians(180)) + .strokeStyle("grey") + .visible(function(s) {return s.e.size() > 0;}) + .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;}) + .add(pv.Dot) + .radius(3) + .visible(function(s) {return s.e.size() > 0 && this.index == idx;}) + .left(w/2+8) + .top(24) + .shape("triangle") + .fillStyle(function() {return this.index == idx ? eventHoverColor : eventColor;}) + .strokeStyle("grey") + .anchor("right") + .add(pv.Label) + .font(headerFont) + .text(function(s) {return s.e.size() == 0 ? "" : s.e[0] + ( s.e[1] ? " (... +" + (s.e.size()-1) + ")" : "");}); + /* An invisible bar to capture events (without flickering). */ vis.add(pv.Bar) .fillStyle("rgba(0,0,0,.001)") @@ -201,10 +199,9 @@ SonarWidgets.StackArea.prototype.render = function() { idx = computeIdx(vis.mouse().x); return vis; }); - + vis.render(); - -} +}; @@ -260,39 +257,39 @@ SonarWidgets.Timeline = function (divId) { this.height = function(height) { this.wHeight = height; return this; - } + }; this.data = function(data) { this.wData = data; return this; - } + }; this.snapshots = function(snapshots) { this.wSnapshots = snapshots; return this; - } + }; this.metrics = function(metrics) { this.wMetrics = metrics; return this; - } + }; this.events = function(events) { this.wEvents = events; return this; - } -} + }; +}; SonarWidgets.Timeline.prototype.render = function() { - + var trendData = this.wData; var metrics = this.wMetrics; var snapshots = this.wSnapshots; var events = this.wEvents; - + var widgetDiv = $(this.wDivId); var headerFont = "10.5px Arial,Helvetica,sans-serif"; - + /* Sizing and scales. */ var headerHeight = 4 + Math.max(this.wMetrics.size(), events ? 2 : 1) * 18; - var w = widgetDiv.getOffsetParent().getWidth() - 60; - var h = (this.wHeight == null ? 80 : this.wHeight) + headerHeight; + var w = widgetDiv.getOffsetParent().getWidth() - 60; + var h = (this.wHeight==null || this.wHeight<=0 ? 80 : this.wHeight) + headerHeight; var yMaxHeight = h-headerHeight; var x = pv.Scale.linear(pv.blend(pv.map(trendData, function(d) {return d;})), function(d) {return d.x}).range(0, w); @@ -341,7 +338,7 @@ SonarWidgets.Timeline.prototype.render = function() { .data(function(d) {return [d[idx]];}) .fillStyle(function() {return line.strokeStyle();}) .strokeStyle("#000") - .size(20) + .size(20) .lineWidth(1) .add(pv.Dot) .radius(3) @@ -350,14 +347,14 @@ SonarWidgets.Timeline.prototype.render = function() { .anchor("right").add(pv.Label) .font(headerFont) .text(function(d) {return metrics[this.parent.index] + ": " + d.yl;}); - + /* The date of the selected dot in the header. */ vis.add(pv.Label) .left(w/2) .top(16) .font(headerFont) .text(function() {return snapshots[idx].d;}); - + /* The event labels */ if (events) { eventColor = "rgba(75,159,213,1)"; @@ -387,7 +384,7 @@ SonarWidgets.Timeline.prototype.render = function() { .font(headerFont) .text(function(e) {return e.l[0].n + ( e.l[1] ? " (... +" + (e.l.size()-1) + ")" : "");}); } - + /* An invisible bar to capture events (without flickering). */ vis.add(pv.Bar) .fillStyle("rgba(0,0,0,.001)") @@ -404,7 +401,7 @@ SonarWidgets.Timeline.prototype.render = function() { idx = idx < 0 ? 0 : idx; return vis; }); - + vis.render(); - + }
\ No newline at end of file diff --git a/sonar-server/src/test/java/org/sonar/server/startup/RegisterNewDashboardsTest.java b/sonar-server/src/test/java/org/sonar/server/startup/RegisterNewDashboardsTest.java index 42088666274..9e2b466ae2e 100644 --- a/sonar-server/src/test/java/org/sonar/server/startup/RegisterNewDashboardsTest.java +++ b/sonar-server/src/test/java/org/sonar/server/startup/RegisterNewDashboardsTest.java @@ -25,7 +25,6 @@ import org.junit.Test; import org.sonar.api.web.dashboard.Dashboard; import org.sonar.api.web.dashboard.DashboardLayout; import org.sonar.api.web.dashboard.DashboardTemplate; -import org.sonar.api.web.dashboard.Widget; import org.sonar.persistence.dashboard.*; import org.sonar.persistence.template.LoadedTemplateDao; import org.sonar.persistence.template.LoadedTemplateDto; @@ -103,8 +102,8 @@ public class RegisterNewDashboardsTest { WidgetDto widgetDto = dto.getWidgets().iterator().next(); assertThat(widgetDto.getKey(), is("fake-widget")); assertThat(widgetDto.getDescription(), is(nullValue())); - assertThat(widgetDto.getColumnIndex(), is(12)); - assertThat(widgetDto.getRowIndex(), is(13)); + assertThat(widgetDto.getColumnIndex(), is(1)); + assertThat(widgetDto.getRowIndex(), is(1)); assertThat(widgetDto.getConfigured(), is(true)); assertNotNull(widgetDto.getCreatedAt()); assertNotNull(widgetDto.getUpdatedAt()); @@ -151,16 +150,16 @@ public class RegisterNewDashboardsTest { } @Test - public void shouldActivateMainDashboard() throws Exception { - DashboardDto mainDashboard = mock(DashboardDto.class); - when(mainDashboard.getName()).thenReturn("Main"); - when(mainDashboard.getId()).thenReturn(1L); + public void shouldActivateDefaultDashboard() throws Exception { + DashboardDto defaultDashboard = mock(DashboardDto.class); + when(defaultDashboard.getName()).thenReturn(RegisterNewDashboards.DEFAULT_DASHBOARD_ID); + when(defaultDashboard.getId()).thenReturn(1L); DashboardDto d1 = mock(DashboardDto.class); when(d1.getName()).thenReturn("Bar"); when(d1.getId()).thenReturn(16L); List<DashboardDto> loadedDashboards = Lists.newArrayList(d1); - registerNewDashboards.activateDashboards(loadedDashboards, mainDashboard); + registerNewDashboards.activateDashboards(loadedDashboards, defaultDashboard); ActiveDashboardDto ad1 = new ActiveDashboardDto(); ad1.setDashboardId(1L); @@ -178,8 +177,8 @@ public class RegisterNewDashboardsTest { public Dashboard createDashboard() { Dashboard dashboard = Dashboard.create("fake-dashboard", "Fake"); dashboard.setLayout(DashboardLayout.TWO_COLUMNS_30_70); - Widget widget = dashboard.addWidget("fake-widget", 12, 13); - widget.addProperty("fake-property", "fake_metric"); + Dashboard.Widget widget = dashboard.addWidget("fake-widget", 1); + widget.setProperty("fake-property", "fake_metric"); return dashboard; } } |