@@ -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 { | |||
@@ -55,6 +55,7 @@ file=File | |||
files=Files | |||
filter_verb=Filter | |||
follow=Follow | |||
global=Global | |||
hide=Hide | |||
identifier_abbreviated=Id | |||
info=Info |
@@ -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; | |||
} | |||
@@ -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> |
@@ -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> |
@@ -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 | |||
); |
@@ -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); | |||
@@ -7,7 +7,7 @@ | |||
description="This is a dashboard" | |||
column_layout="100%" | |||
shared="[true]" | |||
detached="[false]" | |||
is_global="[true]" | |||
/> | |||
<widgets |
@@ -7,7 +7,7 @@ | |||
description="[null]" | |||
column_layout="[null]" | |||
shared="[true]" | |||
detached="[false]" | |||
is_global="[false]" | |||
created_at="[null]" | |||
updated_at="[null]" | |||
/> |
@@ -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> |
@@ -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; | |||
} |
@@ -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 { | |||
} |
@@ -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() { |
@@ -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? |
@@ -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 |
@@ -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 |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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 |
@@ -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"; | |||
} | |||
} | |||