Bläddra i källkod

SONAR-1927 Dashboards are global instead of detached

tags/3.1
David Gageot 12 år sedan
förälder
incheckning
48b5c8e590
21 ändrade filer med 141 tillägg och 99 borttagningar
  1. 4
    1
      plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/GlobalWidget.java
  2. 1
    0
      plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
  3. 5
    5
      sonar-core/src/main/java/org/sonar/core/dashboard/DashboardDto.java
  4. 3
    3
      sonar-core/src/main/resources/org/sonar/core/dashboard/DashboardMapper-oracle.xml
  5. 3
    3
      sonar-core/src/main/resources/org/sonar/core/dashboard/DashboardMapper.xml
  6. 1
    1
      sonar-core/src/main/resources/org/sonar/core/persistence/schema-derby.ddl
  7. 2
    0
      sonar-core/src/test/java/org/sonar/core/dashboard/DashboardDaoTest.java
  8. 1
    1
      sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldInsert-result.xml
  9. 1
    1
      sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml
  10. 0
    2
      sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml
  11. 0
    8
      sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetCategory.java
  12. 37
    0
      sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetGlobal.java
  13. 6
    9
      sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java
  14. 1
    4
      sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb
  15. 2
    1
      sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboards_controller.rb
  16. 49
    45
      sonar-server/src/main/webapp/WEB-INF/app/models/dashboard.rb
  17. 6
    1
      sonar-server/src/main/webapp/WEB-INF/app/views/dashboards/_create.html.erb
  18. 5
    1
      sonar-server/src/main/webapp/WEB-INF/app/views/dashboards/index.html.erb
  19. 2
    2
      sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
  20. 1
    1
      sonar-server/src/main/webapp/WEB-INF/db/migrate/300_add_global_to_dashboards.rb
  21. 11
    10
      sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java

+ 4
- 1
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/GlobalWidget.java Visa fil

@@ -19,12 +19,15 @@
*/
package org.sonar.plugins.core.widgets;

import org.sonar.api.web.WidgetGlobal;

import org.sonar.api.web.AbstractRubyTemplate;
import org.sonar.api.web.RubyRailsWidget;
import org.sonar.api.web.UserRole;
import org.sonar.api.web.WidgetCategory;

@WidgetCategory(value = "Information", detached = true)
@WidgetCategory("Information")
@WidgetGlobal
@UserRole(UserRole.USER)
public class GlobalWidget extends AbstractRubyTemplate implements RubyRailsWidget {


+ 1
- 0
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties Visa fil

@@ -55,6 +55,7 @@ file=File
files=Files
filter_verb=Filter
follow=Follow
global=Global
hide=Hide
identifier_abbreviated=Id
info=Info

+ 5
- 5
sonar-core/src/main/java/org/sonar/core/dashboard/DashboardDto.java Visa fil

@@ -33,7 +33,7 @@ public final class DashboardDto {
private String description;
private String columnLayout;
private boolean shared;
private boolean detached;
private boolean global;
private Date createdAt;
private Date updatedAt;
private List<WidgetDto> widgetDtos = Lists.newArrayList();
@@ -92,12 +92,12 @@ public final class DashboardDto {
return this;
}

public boolean getDetached() {
return detached;
public boolean getGlobal() {
return global;
}

public DashboardDto setDetached(boolean detached) {
this.detached = detached;
public DashboardDto setGlobal(boolean global) {
this.global = global;
return this;
}


+ 3
- 3
sonar-core/src/main/resources/org/sonar/core/dashboard/DashboardMapper-oracle.xml Visa fil

@@ -4,7 +4,7 @@
<mapper namespace="org.sonar.core.dashboard.DashboardMapper">

<select id="selectGlobalDashboard" parameterType="string" resultType="Dashboard">
select id, user_id as "userId", name, description, column_layout as "columnLayout", shared, detached, created_at as "createdAt", updated_at as "updatedAt"
select id, user_id as "userId", name, description, column_layout as "columnLayout", shared, is_global, created_at as "createdAt", updated_at as "updatedAt"
from dashboards WHERE name=#{id} and user_id is null
</select>

@@ -12,9 +12,9 @@
<selectKey order="BEFORE" resultType="Long" keyProperty="id">
select dashboards_seq.NEXTVAL from DUAL
</selectKey>
INSERT INTO dashboards (id, user_id, name, description, column_layout, shared, detached, created_at, updated_at)
INSERT INTO dashboards (id, user_id, name, description, column_layout, shared, is_global, created_at, updated_at)
VALUES (#{id}, #{userId, jdbcType=FLOAT}, #{name, jdbcType=VARCHAR}, #{description, jdbcType=VARCHAR},
#{columnLayout, jdbcType=INTEGER}, #{shared}, #{detached}, #{createdAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP})
#{columnLayout, jdbcType=INTEGER}, #{shared}, #{global}, #{createdAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP})
</insert>

</mapper>

+ 3
- 3
sonar-core/src/main/resources/org/sonar/core/dashboard/DashboardMapper.xml Visa fil

@@ -4,14 +4,14 @@
<mapper namespace="org.sonar.core.dashboard.DashboardMapper">

<select id="selectGlobalDashboard" parameterType="string" resultType="Dashboard">
select id, user_id as "userId", name, description, column_layout as "columnLayout", shared, detached, created_at as "createdAt", updated_at as "updatedAt"
select id, user_id as "userId", name, description, column_layout as "columnLayout", shared, is_global, created_at as "createdAt", updated_at as "updatedAt"
from dashboards WHERE name=#{id} and user_id is null
</select>

<insert id="insert" parameterType="Dashboard" useGeneratedKeys="true" keyProperty="id">
INSERT INTO dashboards (user_id, name, description, column_layout, shared, detached, created_at, updated_at)
INSERT INTO dashboards (user_id, name, description, column_layout, shared, is_global, created_at, updated_at)
VALUES (#{userId, jdbcType=FLOAT}, #{name, jdbcType=VARCHAR}, #{description, jdbcType=VARCHAR},
#{columnLayout, jdbcType=INTEGER}, #{shared}, #{detached}, #{createdAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP})
#{columnLayout, jdbcType=INTEGER}, #{shared}, #{global}, #{createdAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP})
</insert>

</mapper>

+ 1
- 1
sonar-core/src/main/resources/org/sonar/core/persistence/schema-derby.ddl Visa fil

@@ -413,7 +413,7 @@ CREATE TABLE "DASHBOARDS" (
"DESCRIPTION" VARCHAR(1000),
"COLUMN_LAYOUT" VARCHAR(20),
"SHARED" BOOLEAN,
"DETACHED" BOOLEAN,
"IS_GLOBAL" BOOLEAN,
"CREATED_AT" TIMESTAMP,
"UPDATED_AT" TIMESTAMP
);

+ 2
- 0
sonar-core/src/test/java/org/sonar/core/dashboard/DashboardDaoTest.java Visa fil

@@ -60,6 +60,7 @@ public class DashboardDaoTest extends DaoTestCase {
dashboardDto.setDescription("This is a dashboard");
dashboardDto.setColumnLayout("100%");
dashboardDto.setShared(true);
dashboardDto.setGlobal(true);
dashboardDto.setCreatedAt(aDate);
dashboardDto.setUpdatedAt(aDate);

@@ -94,6 +95,7 @@ public class DashboardDaoTest extends DaoTestCase {
dashboardDto.setDescription(null);
dashboardDto.setColumnLayout(null);
dashboardDto.setShared(true);
dashboardDto.setGlobal(false);
dashboardDto.setCreatedAt(null);
dashboardDto.setUpdatedAt(null);


+ 1
- 1
sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldInsert-result.xml Visa fil

@@ -7,7 +7,7 @@
description="This is a dashboard"
column_layout="100%"
shared="[true]"
detached="[false]"
is_global="[true]"
/>

<widgets

+ 1
- 1
sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml Visa fil

@@ -7,7 +7,7 @@
description="[null]"
column_layout="[null]"
shared="[true]"
detached="[false]"
is_global="[false]"
created_at="[null]"
updated_at="[null]"
/>

+ 0
- 2
sonar-core/src/test/resources/org/sonar/core/dashboard/DashboardDaoTest/shouldSelectGlobalDashboard.xml Visa fil

@@ -7,7 +7,6 @@
description="User SQALE dashboard"
column_layout="100%"
shared="[true]"
detached="[false]"
/>

<dashboards
@@ -17,7 +16,6 @@
description="Global SQALE dashboard"
column_layout="100%"
shared="[true]"
detached="[false]"
/>

</dataset>

+ 0
- 8
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetCategory.java Visa fil

@@ -31,12 +31,4 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface WidgetCategory {
String[] value();

/**
* Is the widget detached from any project?
* Before version 3.1 no widget was detached.
*
* @since 3.1
*/
boolean detached() default false;
}

+ 37
- 0
sonar-plugin-api/src/main/java/org/sonar/api/web/WidgetGlobal.java Visa fil

@@ -0,0 +1,37 @@
/*
* 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.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A widget is global when it can only ne displayed on Global Dashboards.
* It doesn't display information from a projet but rather more general information.
* <p>Before version 3.1 no widget was global.</p>
*
* @since 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WidgetGlobal {
}

+ 6
- 9
sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java Visa fil

@@ -47,7 +47,7 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
private WidgetLayoutType widgetLayout = WidgetLayoutType.DEFAULT;
private boolean isDefaultTab = false;
private boolean isWidget = false;
private boolean isDetached = false;
private boolean isGlobal = false;
private String[] mandatoryMeasures = {};
private String[] needOneOfMeasures = {};

@@ -64,7 +64,7 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
initWidgetProperties(view);
initWidgetCategory(view);
initWidgetLayout(view);
initWidgetDetached(view);
initWidgetGlobal(view);
initRequiredMeasures(view);

isWidget = (view instanceof Widget);
@@ -92,11 +92,8 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
}
}

private void initWidgetDetached(final V view) {
WidgetCategory categAnnotation = AnnotationUtils.getClassAnnotation(view, WidgetCategory.class);
if (categAnnotation != null) {
isDetached = categAnnotation.detached();
}
private void initWidgetGlobal(final V view) {
isGlobal = null != AnnotationUtils.getClassAnnotation(view, WidgetGlobal.class);
}

private void initWidgetProperties(final V view) {
@@ -251,8 +248,8 @@ public class ViewProxy<V extends View> implements Comparable<ViewProxy> {
return isWidget;
}

public boolean isDetached() {
return isDetached;
public boolean isGlobal() {
return isGlobal;
}

public boolean isGwt() {

+ 1
- 4
sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb Visa fil

@@ -191,10 +191,7 @@ class DashboardController < ApplicationController
end

def load_widget_definitions(dashboard, filter_on_category=nil)
@widget_definitions=java_facade.getWidgets()
if dashboard.detached
@widget_definitions=@widget_definitions.select(&:isDetached)
end
@widget_definitions=java_facade.getWidgets().select { |w| w.isGlobal() == dashboard.global}

@widget_categories=@widget_definitions.map(&:getWidgetCategories).flatten.uniq.sort
unless filter_on_category.blank?

+ 2
- 1
sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboards_controller.rb Visa fil

@@ -171,6 +171,7 @@ class DashboardsController < ApplicationController
def load_dashboard_from_params(dashboard)
dashboard.name=params[:name]
dashboard.description=params[:description]
dashboard.global=(params[:global].present?)
dashboard.shared=(params[:shared].present? && is_admin?)
dashboard.user_id=current_user.id
dashboard.column_layout=Dashboard::DEFAULT_LAYOUT if !dashboard.column_layout
@@ -186,4 +187,4 @@ class DashboardsController < ApplicationController
end


end
end

+ 49
- 45
sonar-server/src/main/webapp/WEB-INF/app/models/dashboard.rb Visa fil

@@ -34,83 +34,87 @@ class Dashboard < ActiveRecord::Base
before_destroy :check_not_default_before_destroy

def name(l10n=false)
n=read_attribute(:name)
if l10n
Api::Utils.message("dashboard.#{n}.name", :default => n)
else
n
end
n=read_attribute(:name)
if l10n
Api::Utils.message("dashboard.#{n}.name", :default => n)
else
n
end
end

def shared?
read_attribute(:shared) || false
read_attribute(:shared) || false
end

def global?
read_attribute(:is_global) || false
end

def layout
column_layout
column_layout
end

def user_name
user_id ? user.name : nil
user_id ? user.name : nil
end

def editable_by?(user)
(user && self.user_id==user.id) || (user_id.nil? && user.has_role?(:admin))
(user && self.user_id==user.id) || (user_id.nil? && user.has_role?(:admin))
end

def owner?(user)
self.user_id==user.id
self.user_id==user.id
end

def number_of_columns
column_layout.split('-').size
column_layout.split('-').size
end

def column_size(column_index)
last_widget=widgets.select { |w| w.column_index==column_index }.max { |x, y| x.row_index <=> y.row_index }
last_widget ? last_widget.row_index : 0
last_widget=widgets.select { |w| w.column_index==column_index }.max { |x, y| x.row_index <=> y.row_index }
last_widget ? last_widget.row_index : 0
end

def deep_copy()
dashboard=Dashboard.new(attributes)
dashboard.shared=false
self.widgets.each do |child|
new_widget = Widget.create(child.attributes)
child.properties.each do |prop|
widget_prop = WidgetProperty.create(prop.attributes)
new_widget.properties << widget_prop
end
new_widget.save
dashboard.widgets << new_widget
end
dashboard.save
dashboard
dashboard=Dashboard.new(attributes)
dashboard.shared=false
self.widgets.each do |child|
new_widget = Widget.create(child.attributes)
child.properties.each do |prop|
widget_prop = WidgetProperty.create(prop.attributes)
new_widget.properties << widget_prop
end
new_widget.save
dashboard.widgets << new_widget
end
dashboard.save
dashboard
end

def provided_programmatically?
shared && user_id.nil?
shared && user_id.nil?
end

protected
def check_not_default_before_destroy
if shared?
default_actives = active_dashboards.select { |ad| ad.default? }
return default_actives.size==0
end
true
if shared?
default_actives = active_dashboards.select { |ad| ad.default? }
return default_actives.size==0
end
true
end

def validate_on_update
# Check that not used as default dashboard when unsharing
if shared_was && !shared
# unsharing
default_actives = active_dashboards.select { |ad| ad.default? }
unless default_actives.empty?
errors.add_to_base(Api::Utils.message('dashboard.error_unshare_default'))
end
end
# Check that not used as default dashboard when unsharing
if shared_was && !shared
# unsharing
default_actives = active_dashboards.select { |ad| ad.default? }
unless default_actives.empty?
errors.add_to_base(Api::Utils.message('dashboard.error_unshare_default'))
end
end
end
end
end

+ 6
- 1
sonar-server/src/main/webapp/WEB-INF/app/views/dashboards/_create.html.erb Visa fil

@@ -14,6 +14,11 @@
<%= message('description') -%>:<br/><input type="text" name="description" size="30" maxlength="1000">
</td>
</tr>
<tr>
<td class="left" valign="top">
<%= message('global') -%>:<br/><input type="checkbox" name="global" value="true">
</td>
</tr>
<% if is_admin? %>
<tr>
<td class="left" valign="top">
@@ -31,4 +36,4 @@
</table>
<script>
$('create-dashboard-form').name.focus();
</script>
</script>

+ 5
- 1
sonar-server/src/main/webapp/WEB-INF/app/views/dashboards/index.html.erb Visa fil

@@ -8,6 +8,7 @@
<thead>
<tr>
<th><%= message('name') -%></th>
<th><%= message('global') -%></th>
<% if is_admin %>
<th><%= message('shared') -%></th>
<% end %>
@@ -30,9 +31,12 @@
<p class="small"><%= h active.dashboard.description -%></p>
<% end %>
</td>
<td>
<%= boolean_icon(active.dashboard.global?) -%>
</td>
<% if is_admin %>
<td>
<%= boolean_icon(active.dashboard.shared, {:display_false => false}) -%>
<%= boolean_icon(active.dashboard.shared?) -%>
</td>
<% end %>
<td>

+ 2
- 2
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb Visa fil

@@ -42,7 +42,7 @@
<li class="<%= 'selected' if controller.controller_path=='dependencies' -%>">
<a href="<%= ApplicationController.root_context -%>/dependencies/index"><%= message('dependencies.page') -%></a></li>

<% ActiveDashboard.user_dashboards(current_user).select { |active_dashboard| active_dashboard.dashboard.detached }.each do |active_dashboard| %>
<% ActiveDashboard.user_dashboards(current_user).select { |active_dashboard| active_dashboard.dashboard.global? }.each do |active_dashboard| %>
<li class="<%= 'selected' if @dashboard && controller.controller_path=='dashboard' && active_dashboard.dashboard_id==@dashboard.id -%>">
<a href="<%= ApplicationController.root_context -%>/dashboard/index/?did=<%= active_dashboard.dashboard_id -%>"><%= active_dashboard.dashboard.name(true) -%></a>
</li>
@@ -57,7 +57,7 @@
<% end %>

<% elsif selected_section==Navigation::SECTION_RESOURCE %>
<% ActiveDashboard.user_dashboards(current_user).select { |active_dashboard| !active_dashboard.dashboard.detached }.each do |active_dashboard| %>
<% ActiveDashboard.user_dashboards(current_user).select { |active_dashboard| !active_dashboard.dashboard.global? }.each do |active_dashboard| %>
<li class="<%= 'selected' if @dashboard && controller.controller_path=='dashboard' && active_dashboard.dashboard_id==@dashboard.id -%>">
<a href="<%= ApplicationController.root_context -%>/dashboard/index/<%= @project.id -%>?did=<%= active_dashboard.dashboard_id -%><%= "&"+period_param if period_param -%>"><%= active_dashboard.dashboard.name(true) -%></a>
</li>

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/db/migrate/300_add_global_to_dashboards.rb Visa fil

@@ -24,7 +24,7 @@
class AddGlobalToDashboards < ActiveRecord::Migration

def self.up
add_column 'dashboards', 'detached', :boolean, :null => false, :default => false
add_column 'dashboards', 'global', :boolean, :null => false, :default => false
end

end

+ 11
- 10
sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java Visa fil

@@ -19,6 +19,8 @@
*/
package org.sonar.server.ui;

import org.sonar.api.web.WidgetGlobal;

import org.junit.Test;
import org.sonar.api.web.DefaultTab;
import org.sonar.api.web.NavigationSection;
@@ -26,7 +28,6 @@ import org.sonar.api.web.RequiredMeasures;
import org.sonar.api.web.UserRole;
import org.sonar.api.web.View;
import org.sonar.api.web.Widget;
import org.sonar.api.web.WidgetCategory;
import org.sonar.api.web.WidgetProperties;
import org.sonar.api.web.WidgetProperty;
import org.sonar.api.web.WidgetPropertyType;
@@ -133,17 +134,17 @@ public class ViewProxyTest {
}

@Test
public void widget_should_not_be_detached() {
public void widget_should_not_be_global_by_default() {
ViewProxy proxy = new ViewProxy<Widget>(new EditableWidget());

assertThat(proxy.isDetached()).isFalse();
assertThat(proxy.isGlobal()).isFalse();
}

@Test
public void widget_should_be_detached() {
ViewProxy proxy = new ViewProxy<Widget>(new DetachedWidget());
public void widget_should_be_global() {
ViewProxy proxy = new ViewProxy<Widget>(new GlobalWidget());

assertThat(proxy.isDetached()).isTrue();
assertThat(proxy.isGlobal()).isTrue();
}

@Test
@@ -250,14 +251,14 @@ class EditableWidget implements Widget {
}
}

@WidgetCategory(value = "", detached = true)
class DetachedWidget implements Widget {
@WidgetGlobal
class GlobalWidget implements Widget {
public String getId() {
return "detached";
return "global";
}

public String getTitle() {
return "Detached";
return "Global";
}
}


Laddar…
Avbryt
Spara