From f149f41b21b5d630bdaf98eaa80f15e47ef990e9 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 15 Dec 2011 16:28:55 +0100 Subject: [PATCH] SONAR-3034 API: default value of WidgetProperty is not injected in Ruby widgets The column WIDGET_PROPERTIES.VALUE_TYPE is removed because it duplicates the Java extensions. --- .../HotspotMostViolatedRulesWidget.java | 10 +- .../widgets/hotspots/hotspot_metric.html.erb | 2 +- .../hotspot_most_violated_resources.html.erb | 89 ++--- .../hotspot_most_violated_rules.html.erb | 4 +- .../reviews/false_positive_reviews.html.erb | 50 +-- .../core/widgets/reviews/my_reviews.html.erb | 48 +-- .../widgets/reviews/project_reviews.html.erb | 2 +- .../core/widgets/time_machine.html.erb | 186 +++++------ .../plugins/core/widgets/timeline.html.erb | 310 +++++++++--------- .../org/sonar/jpa/entity/SchemaMigration.java | 2 +- .../dashboard/WidgetPropertyDto.java | 28 +- .../dashboard/WidgetPropertyMapper-oracle.xml | 6 +- .../dashboard/WidgetPropertyMapper.xml | 6 +- .../org/sonar/persistence/rows-derby.sql | 1 + .../org/sonar/persistence/schema-derby.ddl | 3 +- .../dashboard/DashboardDaoTest.java | 12 +- .../DashboardDaoTest/shouldInsert-result.xml | 39 ++- ...shouldInsertWithNullableColumns-result.xml | 47 ++- .../java/org/sonar/server/ui/ViewProxy.java | 48 +-- .../app/controllers/dashboard_controller.rb | 32 +- .../main/webapp/WEB-INF/app/models/widget.rb | 50 ++- .../WEB-INF/app/models/widget_property.rb | 84 +++-- .../dashboard/_configure_widget.html.erb | 78 ++--- .../app/views/dashboard/_widget.html.erb | 58 ++-- .../dashboard/_widget_definition.html.erb | 15 +- .../dashboard/_widget_properties.html.erb | 55 ++-- .../app/views/dashboard/configure.html.erb | 101 +++--- ...elete_value_type_from_widget_properties.rb | 31 ++ .../org/sonar/server/ui/ViewProxyTest.java | 10 +- 29 files changed, 708 insertions(+), 699 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/db/migrate/237_delete_value_type_from_widget_properties.rb diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java index fbd14931150..05f0bdc845e 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/HotspotMostViolatedRulesWidget.java @@ -21,12 +21,12 @@ package org.sonar.plugins.core.widgets; import org.sonar.api.web.*; -@WidgetCategory({"Hotspots"}) +@WidgetCategory("Hotspots") @WidgetProperties( - { - @WidgetProperty(key = "numberOfLines", type = WidgetPropertyType.INTEGER, defaultValue = "5"), - @WidgetProperty(key = "defaultSeverity", type = WidgetPropertyType.STRING, description = "Values: BLOCKER, CRITICAL, MAJOR, MINOR, INFO") - } + { + @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() { 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 1ba4c65a7b1..7c397e1366b 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 @@ -1,7 +1,7 @@ <% metric = widget_properties["metric"] metric = Metric.by_key('ncloc') unless metric - limit = widget_properties["numberOfLines"] || 5 + limit = widget_properties["numberOfLines"] title = widget_properties["title"] title = message('widget.hotspot_metric.hotspots_by_x', :params => metric.short_name) if title.blank? diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_resources.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_resources.html.erb index 3f054649615..d69c7fa7dc6 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_resources.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_resources.html.erb @@ -1,32 +1,31 @@ <% - limit = widget_properties["numberOfLines"] || 5 - - metric = Metric.by_key('weighted_violations') - - snapshots_conditions=["snapshots.scope = 'FIL'", "snapshots.islast=:islast", "snapshots.status = 'P'"] - snapshots_values={:islast => true} - snapshots_conditions << "(snapshots.qualifier = 'CLA' OR snapshots.qualifier = 'FIL' OR snapshots.qualifier = 'TRK')" - snapshots_conditions << '(snapshots.id=:sid OR (snapshots.root_snapshot_id=:root_sid AND snapshots.path LIKE :path))' - snapshots_values[:sid]=@snapshot.id - snapshots_values[:root_sid] = (@snapshot.root_snapshot_id || @snapshot.id) - snapshots_values[:path]="#{@snapshot.path}#{@snapshot.id}.%" - - measures_conditions = ["project_measures.rule_id IS NULL", "project_measures.characteristic_id IS NULL"] - measures_values = {} - measures_conditions << "project_measures.metric_id = :m_id" - measures_values[:m_id] = metric.id - - measures=ProjectMeasure.find(:all, - :joins => :snapshot, - :conditions => [ (snapshots_conditions + measures_conditions).join(' AND '), snapshots_values.merge(measures_values)], - :order => "project_measures.value #{'DESC' if metric.direction<0}", - :limit => limit) - - snapshots=Snapshot.find(measures.map {|m| m.snapshot_id}, :include => 'project') - snapshots_by_id = {} - snapshots.each do |s| - snapshots_by_id[s.id]=s - end + limit = widget_properties["numberOfLines"] + metric = Metric.by_key('weighted_violations') + + snapshots_conditions=["snapshots.scope = 'FIL'", "snapshots.islast=:islast", "snapshots.status = 'P'"] + snapshots_values={:islast => true} + snapshots_conditions << "(snapshots.qualifier = 'CLA' OR snapshots.qualifier = 'FIL' OR snapshots.qualifier = 'TRK')" + snapshots_conditions << '(snapshots.id=:sid OR (snapshots.root_snapshot_id=:root_sid AND snapshots.path LIKE :path))' + snapshots_values[:sid]=@snapshot.id + snapshots_values[:root_sid] = (@snapshot.root_snapshot_id || @snapshot.id) + snapshots_values[:path]="#{@snapshot.path}#{@snapshot.id}.%" + + measures_conditions = ["project_measures.rule_id IS NULL", "project_measures.characteristic_id IS NULL"] + measures_values = {} + measures_conditions << "project_measures.metric_id = :m_id" + measures_values[:m_id] = metric.id + + measures=ProjectMeasure.find(:all, + :joins => :snapshot, + :conditions => [(snapshots_conditions + measures_conditions).join(' AND '), snapshots_values.merge(measures_values)], + :order => "project_measures.value #{'DESC' if metric.direction<0}", + :limit => limit) + + snapshots=Snapshot.find(measures.map { |m| m.snapshot_id }, :include => 'project') + snapshots_by_id = {} + snapshots.each do |s| + snapshots_by_id[s.id]=s + end %>
@@ -37,18 +36,22 @@
- + + + + + -<% - measures.each do |measure| - resource = snapshots_by_id[measure.snapshot_id].resource - violations_per_severity={} - measure.text_value.split(';').each do |part| - fields=part.split('=') - violations_per_severity[fields[0]]=fields[1] - end -%> - + <% + measures.each do |measure| + resource = snapshots_by_id[measure.snapshot_id].resource + violations_per_severity={} + measure.text_value.split(';').each do |part| + fields=part.split('=') + violations_per_severity[fields[0]]=fields[1] + end + %> + @@ -81,10 +84,10 @@ + -<% - end -%> + <% + end + %>
<%= link_to_resource(resource, resource.name, {:tab => :violations}) -%> <%= violations_per_severity["INFO"] ? violations_per_severity["INFO"].to_s : "0" -%> -
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb index 0ef8719bb34..988f6c606c6 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/hotspots/hotspot_most_violated_rules.html.erb @@ -1,5 +1,5 @@ <% - limit = widget_properties["numberOfLines"] || 5 + limit = widget_properties["numberOfLines"] metric_prefix = dashboard_configuration.selected_period? ? 'new_' : '' value_column = (dashboard_configuration.selected_period? ? "variation_value_#{dashboard_configuration.period_index}" : 'value') measures_by_severity = {} @@ -16,7 +16,7 @@ end measures_by_severity['']=all_measures.sort { |a, b| b.send(value_column) <=> a.send(value_column) }[0...limit] - default_severity = widget_properties['defaultSeverity']||'' + default_severity = widget_properties['defaultSeverity'] default_severity = '' unless Severity::KEYS.include?(default_severity) %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb index 296436c8a12..800b5b1b7f7 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/false_positive_reviews.html.erb @@ -1,31 +1,31 @@ -<% - if has_role?(:user, @project) - limit = widget_properties["numberOfLines"].to_i == 0 ? 5 : widget_properties["numberOfLines"].to_i - - if @dashboard_configuration.selected_period? - from_date = @dashboard_configuration.from_datetime - to_date = DateTime.now - end +<% + if has_role?(:user, @project) + limit = widget_properties["numberOfLines"] + + if @dashboard_configuration.selected_period? + from_date = @dashboard_configuration.from_datetime + to_date = DateTime.now + end %> -
-
- - <%= message('widgets.more') -%> - +
+
+ + <%= message('widgets.more') -%> + +
+

<%= message('widget.false_positive_reviews.name') -%>

-

<%= message('widget.false_positive_reviews.name') -%>

-
-
- <%= render :partial => 'project/widgets/reviews/reviews_list', - :locals => {:assignee_login => '', - :project_key => @project.key, - :statuses => 'RESOLVED', - :resolution => 'FALSE-POSITIVE', - :limit => limit, - :widget_id => widget.id.to_s} %> -
+
+ <%= render :partial => 'project/widgets/reviews/reviews_list', + :locals => {:assignee_login => '', + :project_key => @project.key, + :statuses => 'RESOLVED', + :resolution => 'FALSE-POSITIVE', + :limit => limit, + :widget_id => widget.id.to_s} %> +
<% end %> diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb index e467103072e..8212be3f2ed 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/my_reviews.html.erb @@ -1,30 +1,30 @@ -<% - if current_user && has_role?(:user, @project) - limit = widget_properties["numberOfLines"].to_i == 0 ? 5 : widget_properties["numberOfLines"].to_i - - if @dashboard_configuration.selected_period? - from_date = @dashboard_configuration.from_datetime - to_date = DateTime.now - end +<% + if current_user && has_role?(:user, @project) + limit = widget_properties["numberOfLines"] + + if @dashboard_configuration.selected_period? + from_date = @dashboard_configuration.from_datetime + to_date = DateTime.now + end %> -
-
- - <%= message('widgets.more') -%> - +
+ +

<%= message('widget.my_reviews.name') -%>

-

<%= message('widget.my_reviews.name') -%>

-
-
- <%= render :partial => 'project/widgets/reviews/reviews_list', - :locals => {:assignee_login => current_user.login, - :project_key => @project.key, - :statuses => 'OPEN,REOPENED', - :resolution => '', - :limit => limit, - :widget_id => widget.id.to_s} %> -
+
+ <%= render :partial => 'project/widgets/reviews/reviews_list', + :locals => {:assignee_login => current_user.login, + :project_key => @project.key, + :statuses => 'OPEN,REOPENED', + :resolution => '', + :limit => limit, + :widget_id => widget.id.to_s} %> +
<% end %> \ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb index 2d89c60c43e..ae226d7c497 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/reviews/project_reviews.html.erb @@ -1,6 +1,6 @@ <% if has_role?(:user, @project) - limit = widget_properties["numberOfLines"].to_i == 0 ? 5 : widget_properties["numberOfLines"].to_i + limit = widget_properties["numberOfLines"] from_date=nil to_date=nil diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb index f580f788a06..c2677939d40 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb @@ -1,116 +1,116 @@ <% - # Retrieve widget settings - metric_ids = [] - (1..10).each do |index| - metric=widget_properties["metric#{index}"] - if metric - metric_ids << metric.id - end - end - if metric_ids.empty? - # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric - ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") - metric_ids << ncloc.id - end - numberOfColumns = widget_properties["numberOfColumns"].to_i == 0 ? 4 : widget_properties["numberOfColumns"].to_i - - # Retrieve the measures for each metric on each snapshot - options = {} - from_date = dashboard_configuration.from_datetime - if from_date - options[:from] = from_date - end - snapshots=Snapshot.for_timemachine_widget(@resource, numberOfColumns, options) - sids = snapshots.collect{|s| s.id}.uniq - measures=ProjectMeasure.find(:all, - :conditions => ["snapshot_id IN (:snapshot_id) AND metric_id IN (:metric_id) AND rule_id IS NULL AND characteristic_id IS NULL", - {:snapshot_id => sids, :metric_id => metric_ids}] - ) + # Retrieve widget settings + metric_ids = [] + (1..10).each do |index| + metric=widget_properties["metric#{index}"] + if metric + metric_ids << metric.id + end + end + if metric_ids.empty? + # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric + ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") + metric_ids << ncloc.id + end + number_of_columns = widget_properties["numberOfColumns"] - # And prepare the rows to display - snapshot_by_id={} - snapshots.each do |s| - snapshot_by_id[s.id]=s - end - rows_by_metric_id={} - measures.each do |measure| - next unless measure.metric + # Retrieve the measures for each metric on each snapshot + options = {} + from_date = dashboard_configuration.from_datetime + if from_date + options[:from] = from_date + end + snapshots=Snapshot.for_timemachine_widget(@resource, number_of_columns, options) + sids = snapshots.collect { |s| s.id }.uniq + measures=ProjectMeasure.find(:all, + :conditions => ["snapshot_id IN (:snapshot_id) AND metric_id IN (:metric_id) AND rule_id IS NULL AND characteristic_id IS NULL", + {:snapshot_id => sids, :metric_id => metric_ids}] + ) - if measure.metric.timemachine? && (measure.value || measure.text_value) - row=rows_by_metric_id[measure.metric_id] - unless row - row=Sonar::TimemachineRow.new(measure.metric) - rows_by_metric_id[measure.metric_id]=row - end + # And prepare the rows to display + snapshot_by_id={} + snapshots.each do |s| + snapshot_by_id[s.id]=s + end + rows_by_metric_id={} + measures.each do |measure| + next unless measure.metric - #optimization : avoid eager loading of snapshots - measure.snapshot=snapshot_by_id[measure.snapshot_id] - row.add_measure(measure) - end - end - - # Create the list of rows to display in the same order as defined by the user - rows=[] - metric_ids.each do |metric_id| - row = rows_by_metric_id[metric_id] - if row - rows< 1 + if measure.metric.timemachine? && (measure.value || measure.text_value) + row=rows_by_metric_id[measure.metric_id] + unless row + row=Sonar::TimemachineRow.new(measure.metric) + rows_by_metric_id[measure.metric_id]=row + end + + #optimization : avoid eager loading of snapshots + measure.snapshot=snapshot_by_id[measure.snapshot_id] + row.add_measure(measure) + end + end + + # Create the list of rows to display in the same order as defined by the user + rows=[] + metric_ids.each do |metric_id| + row = rows_by_metric_id[metric_id] + if row + rows< 1 %>
- +
- + - - <% - snapshots.each do |snapshot| - event = snapshot.event('Version') + + <% + snapshots.each do |snapshot| + event = snapshot.event('Version') %> <% end %> <% if displaySparkLine %> - + <% end %> - - + + - - <% - rows.select{|row| row.metric.val_type != Metric::VALUE_TYPE_DISTRIB}.each do |row| - %> - - - <% - snapshots.each do |snapshot| - measure=row.measure(snapshot) - %> - - <% end %> + <% - sparkline_url=row.sparkline_url - if displaySparkLine && sparkline_url + rows.select { |row| row.metric.val_type != Metric::VALUE_TYPE_DISTRIB }.each do |row| %> - - <% end %> - + + + <% + snapshots.each do |snapshot| + measure=row.measure(snapshot) + %> + + <% end %> + <% + sparkline_url=row.sparkline_url + if displaySparkLine && sparkline_url + %> + + <% end %> + <% end %> - + -
- <%= l snapshot.created_at.to_date -%> -
- <%= event.name unless event==nil -%> + <%= l snapshot.created_at.to_date -%> +
+ <%= event.name unless event==nil -%>
- <%= row.metric.short_name %> - <%= format_measure(measure, :skip_span_id => true) %>
- <%= image_tag(sparkline_url) %> -
+ <%= row.metric.short_name %> + <%= format_measure(measure, :skip_span_id => true) %> + <%= image_tag(sparkline_url) %> +
+
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb index 5d8dab0b896..7a4d6b773c7 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb @@ -1,170 +1,170 @@ <% - # Retrieve widget settings - metric_data_map = {} - metric_name_map = {} - (1..3).each do |index| - metric=widget_properties["metric#{index}"] - if metric - metric_data_map[metric.id] = [] - metric_name_map[metric.id] = metric.short_name - end - end - if metric_data_map.empty? - # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric - ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") - metric_data_map[ncloc.id] = [] - metric_name_map[ncloc.id] = message('metric.ncloc.name') - end - chartHeight = widget_properties["chartHeight"].to_i == 0 ? "null" : widget_properties["chartHeight"] - - # Retrieve metric trend information - options = {} - from_date = dashboard_configuration.from_datetime - if from_date - options[:from] = from_date - end - metric_count_per_snapshot_id = {} - TrendsChart.time_machine_measures(@resource, metric_data_map.keys, options).each() do |trend_item| - sid = trend_item["sid"] - if metric_count_per_snapshot_id[sid] - metric_count_per_snapshot_id[sid] += 1 - else - metric_count_per_snapshot_id[sid] = 1 - end - metric_data_map[trend_item["metric_id"].to_i] << {:date => trend_item["created_at"], :value => trend_item["value"], :sid => trend_item["sid"]} - end + # Retrieve widget settings + metric_data_map = {} + metric_name_map = {} + (1..3).each do |index| + metric=widget_properties["metric#{index}"] + if metric + metric_data_map[metric.id] = [] + metric_name_map[metric.id] = metric.short_name + end + end + if metric_data_map.empty? + # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric + ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") + metric_data_map[ncloc.id] = [] + metric_name_map[ncloc.id] = message('metric.ncloc.name') + end + chartHeight = widget_properties["chartHeight"] + + # Retrieve metric trend information + options = {} + from_date = dashboard_configuration.from_datetime + if from_date + options[:from] = from_date + end + metric_count_per_snapshot_id = {} + TrendsChart.time_machine_measures(@resource, metric_data_map.keys, options).each() do |trend_item| + sid = trend_item["sid"] + if metric_count_per_snapshot_id[sid] + metric_count_per_snapshot_id[sid] += 1 + else + metric_count_per_snapshot_id[sid] = 1 + end + metric_data_map[trend_item["metric_id"].to_i] << {:date => trend_item["created_at"], :value => trend_item["value"], :sid => trend_item["sid"]} + end + + # Create JS structures to print out in the HTML page + js_data = "[" + js_snapshots = "[" + js_metrics = "[" + total_number_of_metrics = metric_name_map.keys.size() + metric_data_map.keys.each_with_index() do |metric_id, index| + unless metric_data_map[metric_id].empty? + js_metrics += "\"" + metric_name_map[metric_id] + "\"," + js_data += "[" + metric_data_map[metric_id].each() do |metric_data| + # for every metric value, we need to check that the corresponding snapshot has values for each metric (if not, Protovis won't be able to display) + if metric_count_per_snapshot_id[metric_data[:sid]]==total_number_of_metrics + m_date = metric_data[:date] + # Only Oracle returns a Time object, so let's parse this string if it's not a Time instance + m_date = Time.parse(metric_data[:date]) unless m_date.is_a? Time + m_value = sprintf("%0.02f", metric_data[:value]) + m_value_localized = (m_value.end_with? '.00') ? number_with_precision(metric_data[:value], :precision => 0).to_s : number_with_precision(metric_data[:value], :precision => 2).to_s + js_data += "{x:d(" + js_data += m_date.year.to_s + js_data += "," + # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) + js_data += (m_date.month - 1).to_s + js_data += "," + js_data += m_date.day.to_s + js_data += "," + js_data += m_date.hour.to_s + js_data += "," + js_data += m_date.min.to_s + js_data += "," + js_data += m_date.sec.to_s + js_data += "),y:" + js_data += m_value + js_data += ",yl:\"" + js_data += m_value_localized + js_data += "\"}," + if index == 0 + # we fill the js_snapshots array (no need to do this more than once) + js_snapshots += "{sid:" + js_snapshots += metric_data[:sid].to_s + js_snapshots += ",d:\"" + js_snapshots += human_short_date m_date + js_snapshots += "\"}," + end + end + end + js_data = js_data.chomp(',') + "]," + end + end + js_data = js_data.chomp(',') + "]" + js_snapshots = js_snapshots.chomp(',') + "]" + js_metrics = js_metrics.chomp(',') + "]" + + # Prepare also event structure if required + unless widget_properties["hideEvents"] + events = {} + unless from_date + # find the oldest date + metric_data_map.values.each() do |metric_data_array| + first_date = metric_data_array[0][:date] + # Only Oracle returns a Time object, so let's parse this string if it's not a Time instance + first_date = Time.parse(metric_data_array[0][:date]) unless first_date.is_a? Time + from_date = first_date if !from_date || from_date > first_date + end + end + Event.find(:all, :conditions => ["resource_id=? AND event_date>=?", @resource.id, from_date], :order => 'event_date').each() do |event| + if events[event.event_date] + events[event.event_date] << event + else + date_entry = [event] + events[event.event_date] = date_entry + end + end + js_events = "[" + events.keys().sort.each() do |e_date| + e_details = events[e_date] + js_events += "{sid:" + js_events += e_details[0].snapshot_id.to_s + js_events += ",d:d(" + js_events += e_date.year.to_s + js_events += "," + # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) + js_events += (e_date.month - 1).to_s + js_events += "," + js_events += e_date.day.to_s + js_events += "," + js_events += e_date.hour.to_s + js_events += "," + js_events += e_date.min.to_s + js_events += "," + js_events += e_date.sec.to_s + js_events += "),l:[" + e_details.each() do |e| + js_events += "{n:\"" + js_events += e.name + js_events += "\"}," + end + js_events = js_events.chomp(',') + "]}," + end + js_events = js_events.chomp(',') + "]" + end - # Create JS structures to print out in the HTML page - js_data = "[" - js_snapshots = "[" - js_metrics = "[" - total_number_of_metrics = metric_name_map.keys.size() - metric_data_map.keys.each_with_index() do |metric_id, index| - unless metric_data_map[metric_id].empty? - js_metrics += "\"" + metric_name_map[metric_id] + "\"," - js_data += "[" - metric_data_map[metric_id].each() do |metric_data| - # for every metric value, we need to check that the corresponding snapshot has values for each metric (if not, Protovis won't be able to display) - if metric_count_per_snapshot_id[metric_data[:sid]]==total_number_of_metrics - m_date = metric_data[:date] - # Only Oracle returns a Time object, so let's parse this string if it's not a Time instance - m_date = Time.parse(metric_data[:date]) unless m_date.is_a? Time - m_value = sprintf( "%0.02f", metric_data[:value]) - m_value_localized = (m_value.end_with? '.00') ? number_with_precision(metric_data[:value], :precision => 0).to_s : number_with_precision(metric_data[:value], :precision => 2).to_s - js_data += "{x:d(" - js_data += m_date.year.to_s - js_data += "," - # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) - js_data += (m_date.month - 1).to_s - js_data += "," - js_data += m_date.day.to_s - js_data += "," - js_data += m_date.hour.to_s - js_data += "," - js_data += m_date.min.to_s - js_data += "," - js_data += m_date.sec.to_s - js_data += "),y:" - js_data += m_value - js_data += ",yl:\"" - js_data += m_value_localized - js_data += "\"}," - if index == 0 - # we fill the js_snapshots array (no need to do this more than once) - js_snapshots += "{sid:" - js_snapshots += metric_data[:sid].to_s - js_snapshots += ",d:\"" - js_snapshots += human_short_date m_date - js_snapshots += "\"}," - end - end - end - js_data = js_data.chomp(',') + "]," - end - end - js_data = js_data.chomp(',') + "]" - js_snapshots = js_snapshots.chomp(',') + "]" - js_metrics = js_metrics.chomp(',') + "]" - - # Prepare also event structure if required - unless widget_properties["hideEvents"] - events = {} - unless from_date - # find the oldest date - metric_data_map.values.each() do |metric_data_array| - first_date = metric_data_array[0][:date] - # Only Oracle returns a Time object, so let's parse this string if it's not a Time instance - first_date = Time.parse(metric_data_array[0][:date]) unless first_date.is_a? Time - from_date = first_date if !from_date || from_date > first_date - end - end - Event.find(:all, :conditions => ["resource_id=? AND event_date>=?", @resource.id, from_date], :order => 'event_date').each() do |event| - if events[event.event_date] - events[event.event_date] << event - else - date_entry = [event] - events[event.event_date] = date_entry - end - end - js_events = "[" - events.keys().sort.each() do |e_date| - e_details = events[e_date] - js_events += "{sid:" - js_events += e_details[0].snapshot_id.to_s - js_events += ",d:d(" - js_events += e_date.year.to_s - js_events += "," - # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) - js_events += (e_date.month - 1).to_s - js_events += "," - js_events += e_date.day.to_s - js_events += "," - js_events += e_date.hour.to_s - js_events += "," - js_events += e_date.min.to_s - js_events += "," - js_events += e_date.sec.to_s - js_events += "),l:[" - e_details.each() do |e| - js_events += "{n:\"" - js_events += e.name - js_events += "\"}," - end - js_events = js_events.chomp(',') + "]}," - end - js_events = js_events.chomp(',') + "]" - end - %> <% if widget_properties["chartTitle"] %> -

<%= h(widget_properties["chartTitle"]) -%>

+

<%= h(widget_properties["chartTitle"]) -%>

<% end %> <% if metric_data_map.values[0].size == 1 %> - <%= message('widget.timeline.timeline_not_displayed') -%> + <%= message('widget.timeline.timeline_not_displayed') -%> <% else %> -
- +
+ <% end %> \ No newline at end of file diff --git a/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java b/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java index 81e9315bedb..1b69803b7c8 100644 --- a/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java +++ b/sonar-core/src/main/java/org/sonar/jpa/entity/SchemaMigration.java @@ -42,7 +42,7 @@ public class SchemaMigration { - complete the Derby DDL file used for unit tests : sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl */ - public static final int LAST_VERSION = 236; + public static final int LAST_VERSION = 237; public final static String TABLE_NAME = "schema_migrations"; diff --git a/sonar-core/src/main/java/org/sonar/persistence/dashboard/WidgetPropertyDto.java b/sonar-core/src/main/java/org/sonar/persistence/dashboard/WidgetPropertyDto.java index d71e64228e4..01a85391022 100644 --- a/sonar-core/src/main/java/org/sonar/persistence/dashboard/WidgetPropertyDto.java +++ b/sonar-core/src/main/java/org/sonar/persistence/dashboard/WidgetPropertyDto.java @@ -25,7 +25,6 @@ public class WidgetPropertyDto { private Long widgetId; private String key; private String value; - private String valueType; /** * @return the id @@ -35,8 +34,7 @@ public class WidgetPropertyDto { } /** - * @param id - * the id to set + * @param id the id to set */ public void setId(Long id) { this.id = id; @@ -50,8 +48,7 @@ public class WidgetPropertyDto { } /** - * @param widgetId - * the widgetId to set + * @param widgetId the widgetId to set */ public void setWidgetId(Long widgetId) { this.widgetId = widgetId; @@ -65,8 +62,7 @@ public class WidgetPropertyDto { } /** - * @param key - * the key to set + * @param key the key to set */ public void setKey(String key) { this.key = key; @@ -80,26 +76,10 @@ public class WidgetPropertyDto { } /** - * @param value - * the value to set + * @param value the value to set */ public void setValue(String value) { this.value = value; } - /** - * @return the valueType - */ - public String getValueType() { - return valueType; - } - - /** - * @param valueType - * the valueType to set - */ - public void setValueType(String valueType) { - this.valueType = valueType; - } - } diff --git a/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper-oracle.xml b/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper-oracle.xml index 35234fd036f..e3cf5c803f4 100644 --- a/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper-oracle.xml +++ b/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper-oracle.xml @@ -3,9 +3,9 @@ - - INSERT INTO widget_properties (id, widget_id, kee, text_value, value_type) - VALUES (widget_properties_seq.NEXTVAL, #{widgetId}, #{key, jdbcType=VARCHAR}, #{value, jdbcType=VARCHAR}, #{valueType, jdbcType=VARCHAR}) + + INSERT INTO widget_properties (id, widget_id, kee, text_value) + VALUES (widget_properties_seq.NEXTVAL, #{widgetId}, #{key, jdbcType=VARCHAR}, #{value, jdbcType=VARCHAR}) diff --git a/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper.xml b/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper.xml index 9fafa59e58f..0ce1248fa27 100644 --- a/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/persistence/dashboard/WidgetPropertyMapper.xml @@ -3,9 +3,9 @@ - - INSERT INTO widget_properties (widget_id, kee, text_value, value_type) - VALUES (#{widgetId}, #{key, jdbcType=VARCHAR}, #{value, jdbcType=VARCHAR}, #{valueType, jdbcType=VARCHAR}) + + INSERT INTO widget_properties (widget_id, kee, text_value) + VALUES (#{widgetId}, #{key, jdbcType=VARCHAR}, #{value, jdbcType=VARCHAR}) diff --git a/sonar-core/src/main/resources/org/sonar/persistence/rows-derby.sql b/sonar-core/src/main/resources/org/sonar/persistence/rows-derby.sql index 294d38fd155..fa0381c613c 100644 --- a/sonar-core/src/main/resources/org/sonar/persistence/rows-derby.sql +++ b/sonar-core/src/main/resources/org/sonar/persistence/rows-derby.sql @@ -165,6 +165,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('233'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('234'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('235'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('236'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('237'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl b/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl index 66fa1a27040..010332f89b6 100644 --- a/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl +++ b/sonar-core/src/main/resources/org/sonar/persistence/schema-derby.ddl @@ -183,8 +183,7 @@ CREATE TABLE "WIDGET_PROPERTIES" ( "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), "WIDGET_ID" INTEGER NOT NULL, "KEE" VARCHAR(100), - "TEXT_VALUE" VARCHAR(4000), - "VALUE_TYPE" VARCHAR(20) + "TEXT_VALUE" VARCHAR(4000) ); CREATE TABLE "EVENTS" ( diff --git a/sonar-core/src/test/java/org/sonar/persistence/dashboard/DashboardDaoTest.java b/sonar-core/src/test/java/org/sonar/persistence/dashboard/DashboardDaoTest.java index 9b8c882e100..e7160dd0294 100644 --- a/sonar-core/src/test/java/org/sonar/persistence/dashboard/DashboardDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/persistence/dashboard/DashboardDaoTest.java @@ -19,15 +19,11 @@ */ package org.sonar.persistence.dashboard; -import java.util.Date; - import org.junit.Before; import org.junit.Test; import org.sonar.persistence.DaoTestCase; -import org.sonar.persistence.dashboard.DashboardDao; -import org.sonar.persistence.dashboard.DashboardDto; -import org.sonar.persistence.dashboard.WidgetDto; -import org.sonar.persistence.dashboard.WidgetPropertyDto; + +import java.util.Date; public class DashboardDaoTest extends DaoTestCase { @@ -67,12 +63,11 @@ public class DashboardDaoTest extends DaoTestCase { WidgetPropertyDto property = new WidgetPropertyDto(); property.setKey("displayITs"); property.setValue("true"); - property.setValueType("BOOLEAN"); widgetDto.addWidgetProperty(property); dao.insert(dashboardDto); - checkTables("shouldInsert", new String[] { "created_at", "updated_at" }, "dashboards", "widgets", "widget_properties"); + checkTables("shouldInsert", new String[]{"created_at", "updated_at"}, "dashboards", "widgets", "widget_properties"); } @Test @@ -103,7 +98,6 @@ public class DashboardDaoTest extends DaoTestCase { WidgetPropertyDto property = new WidgetPropertyDto(); property.setKey(null); property.setValue(null); - property.setValueType(null); widgetDto.addWidgetProperty(property); dao.insert(dashboardDto); diff --git a/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsert-result.xml b/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsert-result.xml index 9515145dcce..91f73beae63 100644 --- a/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsert-result.xml +++ b/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsert-result.xml @@ -1,29 +1,28 @@ + id="1" + kee="d-key" + user_id="6" + name="My Dashboard" + description="This is a dashboard" + column_layout="100%" + shared="[true]"/> + id="1" + dashboard_id="1" + widget_key="code_coverage" + name="Code coverage" + description="Widget for code coverage" + column_index="13" + row_index="14" + configured="[true]"/> + id="1" + widget_id="1" + kee="displayITs" + text_value="true"/> diff --git a/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml b/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml index 729b5deef89..787d1cb0d5e 100644 --- a/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml +++ b/sonar-core/src/test/resources/org/sonar/persistence/dashboard/DashboardDaoTest/shouldInsertWithNullableColumns-result.xml @@ -1,33 +1,32 @@ + id="1" + kee="d-key" + user_id="[null]" + name="[null]" + description="[null]" + column_layout="[null]" + shared="[true]" + created_at="[null]" + updated_at="[null]"/> + id="1" + dashboard_id="1" + widget_key="code_coverage" + name="[null]" + description="[null]" + column_index="[null]" + row_index="[null]" + configured="[true]" + created_at="[null]" + updated_at="[null]"/> + id="1" + widget_id="1" + kee="[null]" + text_value="[null]"/> diff --git a/sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java b/sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java index badeb5f78ec..bad9fd1bb0e 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/ViewProxy.java @@ -19,6 +19,7 @@ */ package org.sonar.server.ui; +import com.google.common.collect.Maps; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.CompareToBuilder; @@ -27,6 +28,9 @@ import org.apache.commons.lang.builder.ToStringBuilder; import org.sonar.api.utils.AnnotationUtils; import org.sonar.api.web.*; +import java.util.Collection; +import java.util.Map; + public class ViewProxy implements Comparable { private V view; @@ -37,7 +41,7 @@ public class ViewProxy implements Comparable { private String[] resourceLanguages = {}; private String[] defaultForMetrics = {}; private String description = ""; - private WidgetProperty[] widgetProperties = {}; + private Map widgetPropertiesByKey = Maps.newHashMap(); private String[] widgetCategories = {}; private WidgetLayoutType widgetLayout = WidgetLayoutType.DEFAULT; private boolean isDefaultTab = false; @@ -90,7 +94,9 @@ public class ViewProxy implements Comparable { WidgetProperties propAnnotation = AnnotationUtils.getClassAnnotation(view, WidgetProperties.class); if (propAnnotation != null) { - this.widgetProperties = propAnnotation.value(); + for (WidgetProperty property : propAnnotation.value()) { + widgetPropertiesByKey.put(property.key(), property); + } } WidgetCategory categAnnotation = AnnotationUtils.getClassAnnotation(view, WidgetCategory.class); @@ -122,8 +128,12 @@ public class ViewProxy implements Comparable { return description; } - public WidgetProperty[] getWidgetProperties() { - return widgetProperties; + public Collection getWidgetProperties() { + return widgetPropertiesByKey.values(); + } + + public WidgetProperty getWidgetProperty(String propertyKey) { + return widgetPropertiesByKey.get(propertyKey); } public String[] getWidgetCategories() { @@ -175,12 +185,12 @@ public class ViewProxy implements Comparable { } public boolean isEditable() { - return !ArrayUtils.isEmpty(widgetProperties); + return !widgetPropertiesByKey.isEmpty(); } public boolean hasRequiredProperties() { boolean requires = false; - for (WidgetProperty property : widgetProperties) { + for (WidgetProperty property : getWidgetProperties()) { if (!property.optional() && StringUtils.isEmpty(property.defaultValue())) { requires = true; } @@ -207,29 +217,29 @@ public class ViewProxy implements Comparable { } ViewProxy rhs = (ViewProxy) obj; return new EqualsBuilder() - .append(getId(), rhs.getId()) - .isEquals(); + .append(getId(), rhs.getId()) + .isEquals(); } @Override public String toString() { return new ToStringBuilder(this) - .append("id", view.getId()) - .append("sections", sections) - .append("userRoles", userRoles) - .append("scopes", resourceScopes) - .append("qualifiers", resourceQualifiers) - .append("languages", resourceLanguages) - .append("metrics", defaultForMetrics) - .toString(); + .append("id", view.getId()) + .append("sections", sections) + .append("userRoles", userRoles) + .append("scopes", resourceScopes) + .append("qualifiers", resourceQualifiers) + .append("languages", resourceLanguages) + .append("metrics", defaultForMetrics) + .toString(); } public int compareTo(ViewProxy other) { return new CompareToBuilder() - .append(getTitle(), other.getTitle()) - .append(getId(), other.getId()) - .toComparison(); + .append(getTitle(), other.getTitle()) + .append(getId(), other.getId()) + .toComparison(); } } \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb index 1a05eac678a..5f56517a2b7 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/dashboard_controller.rb @@ -115,34 +115,20 @@ class DashboardController < ApplicationController def save_widget widget=Widget.find(params[:wid].to_i) #TODO check owner of dashboard - definition=java_facade.getWidget(widget.widget_key) - errors_by_property_key={} - definition.getWidgetProperties().each do |property_def| - value=params[property_def.key()] || property_def.defaultValue() - value='false' if value.empty? && property_def.type.name()==WidgetProperty::TYPE_BOOLEAN - - errors=WidgetProperty.validate_definition(property_def, value) - if errors.empty? - widget.set_property(property_def.key(), value, property_def.type.name()) - else - widget.unset_property(property_def.key()) - errors_by_property_key[property_def.key()]=errors + Widget.transaction do + widget.properties.clear + widget.java_definition.getWidgetProperties().each do |java_property| + value=params[java_property.key()] || java_property.defaultValue() + if value && !value.empty? + prop = widget.properties.build(:kee => java_property.key, :text_value => value) + prop.save! + end end - end - - if errors_by_property_key.empty? widget.configured=true - widget.save - widget.properties.each { |p| p.save } + widget.save! render :update do |page| page.redirect_to(url_for(:action => :configure, :did => widget.dashboard_id, :id => params[:id])) end - else - widget.configured=false - widget.save - render :update do |page| - page.replace_html "widget_props_#{widget.id}", :partial => 'dashboard/widget_properties', :locals => {:widget => widget, :definition => definition, :errors_by_property_key => errors_by_property_key} - end end end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/widget.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/widget.rb index 7a10a4791ec..24e8616d3de 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/widget.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/widget.rb @@ -21,14 +21,14 @@ class Widget < ActiveRecord::Base has_many :properties, :dependent => :delete_all, :class_name => 'WidgetProperty' belongs_to :dashboards - validates_presence_of :name - validates_length_of :name, :within => 1..256 + validates_presence_of :name + validates_length_of :name, :within => 1..256 - validates_presence_of :widget_key - validates_length_of :widget_key, :within => 1..256 + validates_presence_of :widget_key + validates_length_of :widget_key, :within => 1..256 def property(key) - properties().each do |p| + self.properties().each do |p| return p if (p.key==key) end nil @@ -42,43 +42,33 @@ class Widget < ActiveRecord::Base "block_#{id}" end - def property_value(key, default_value=nil) + def property_text_value(key) prop=property(key) - (prop ? prop.value : nil) || default_value + prop ? prop.text_value : nil end - def set_property(key, value, value_type) + def property_value(key) prop=property(key) - if prop - prop.text_value=value - prop.value_type=value_type - else - prop=self.properties.build(:kee => key, :text_value => value, :value_type => value_type) - end - properties_as_hash[key]=prop.typed_value - end - - def unset_property(key) - prop=property(key) - self.properties.delete(prop) if prop - end - - def delete_property(key) - prop=property(key) - if prop - properties.delete(prop) - end + prop ? prop.value : nil end def properties_as_hash @properties_hash ||= begin hash={} - properties.each do |prop| - hash[prop.key]=prop.typed_value + java_definition.getWidgetProperties().each do |property_definition| + prop = property(property_definition.key) + hash[property_definition.key]=(prop ? prop.value : WidgetProperty.text_to_value(property_definition.defaultValue(), property_definition.type().name())) end hash end - @properties_hash + end + + def java_definition + Java::OrgSonarServerUi::JRubyFacade.getInstance().getWidget(key) + end + + def layout + java_definition.getWidgetLayout().name() end end \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/widget_property.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/widget_property.rb index 4aa241e38d3..6697aedcaa8 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/widget_property.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/widget_property.rb @@ -26,59 +26,77 @@ class WidgetProperty < ActiveRecord::Base belongs_to :widget - validates_length_of :kee, :within => 1..100 - validates_length_of :text_value, :maximum => 4000, :allow_blank => true, :allow_nil => true + validates_length_of :kee, :within => 1..100 + validates_length_of :text_value, :maximum => 4000, :allow_blank => true, :allow_nil => true def key kee end - def value - text_value + def text_value + read_attribute(:text_value) || default_text_value end - def typed_value - case value_type - when TYPE_INTEGER - value.to_i - when TYPE_FLOAT - Float(value) - when TYPE_BOOLEAN - value=='true' - when TYPE_METRIC - Metric.by_key(value.to_s) - else - value - end + def default_text_value + java_definition.defaultValue() + end + + def type + @type ||= + begin + java_definition.type().name() + end + end + + def java_definition + @java_definition ||= + begin + widget.java_definition.getWidgetProperty(key) + end + end + + def value + WidgetProperty.text_to_value(text_value, type) end def to_hash_json - {:key => key, :value => value.to_s} + {:key => key, :value => text_value} end def to_xml(xml=Builder::XmlMarkup.new(:indent => 0)) xml.property do xml.key(prop_key) - xml.value {xml.cdata!(text_value.to_s)} + xml.value { xml.cdata!(text_value) } end xml end - def self.validate_definition(definition, value) - errors=[] - if value.empty? - errors<<"Missing value" unless definition.optional() + def self.text_to_value(text, type) + case type + when TYPE_INTEGER + text.to_i + when TYPE_FLOAT + Float(text) + when TYPE_BOOLEAN + text=='true' + when TYPE_METRIC + Metric.by_key(text) + else + text + end + end + + protected + def validate + errors.add_to_base("Unknown property: #{key}") unless java_definition + errors.add_to_base("Unknown type for property #{key}") unless type + if text_value.empty? + errors.add_to_base("#{key} is empty") unless java_definition.optional() else - errors<<"Please type an integer (example: 123)" if definition.type.name()==TYPE_INTEGER && value.to_i.to_s!=value - if definition.type.name()==TYPE_FLOAT - begin - Float(value) - rescue - errors<<"Please type a number (example: 123.45)" - end - end - errors<<"Please check value" if definition.type.name()==TYPE_BOOLEAN && !(value=="true" || value=="false") + errors.add_to_base("#{key} is not an integer") if type==TYPE_INTEGER && !Api::Utils.is_integer?(text_value) + errors.add_to_base("#{key} is not a decimal number") if type==TYPE_FLOAT && !Api::Utils.is_number?(text_value) + errors.add_to_base("#{key} is not a boolean") if type==TYPE_BOOLEAN && !(text_value=="true" || text_value=="false") end - errors end + end \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_configure_widget.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_configure_widget.html.erb index 92d817dae95..c27485fcf58 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_configure_widget.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_configure_widget.html.erb @@ -1,48 +1,50 @@ - <% +<% begin - widget_body=render :inline => definition.getTarget().getTemplate(), :locals => {:widget_properties => widget.properties_as_hash, :widget => widget, :dashboard_configuration => @dashboard_configuration} + widget_body=render :inline => widget.java_definition.getTarget().getTemplate(), :locals => {:widget_properties => widget.properties_as_hash, :widget => widget, :dashboard_configuration => @dashboard_configuration} rescue => error - logger.error(message('dashboard.cannot_render_widget_x', :params => [definition.getId(), error])) - logger.error(error.backtrace.join("\n")) - widget_body="" + logger.error(message('dashboard.cannot_render_widget_x', :params => [widget.java_definition.getId(), error])) + logger.error(error.backtrace.join("\n")) + widget_body="" end - %> +%> -
- <%= message('delete') -%> - <% if definition.isEditable() %> - <%= message('edit') -%> - <% end %> - <%= h message('widget.' + definition.getId() + '.name', :default => definition.getTitle()) -%> -
+
+ <%= message('delete') -%> + <% if widget.java_definition.isEditable() %> + <%= message('edit') -%> + <% end %> + <%= h message('widget.' + widget.java_definition.getId() + '.name', :default => widget.java_definition.getTitle()) -%> +
-
- <%= render :partial => 'dashboard/widget_properties', :locals => {:widget => widget, :definition => definition} -%> -
+
+ <%= render :partial => 'dashboard/widget_properties', :locals => {:widget => widget} -%> +
-
- -
- <% if widget_body.include? '<' %> - <% - default_layout=(definition.getWidgetLayout().name()=='DEFAULT') +
+ +
+ <% if widget_body.include? '<' %> + <% + default_layout=(widget.layout=='DEFAULT') if default_layout - %> -
- <% end %> - <%= widget_body -%> - <% if default_layout %>
<% end %> - <% else %> -

<%= message('no_data') -%>

- <% end %> -
-
+ %> +
+ <% end %> + <%= widget_body -%> + <% if default_layout %> +
+ <% end %> + <% else %> +

<%= message('no_data') -%>

+ <% end %> +
+
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget.html.erb index 54265983a6f..49a370f57b1 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget.html.erb @@ -1,31 +1,35 @@ -
-<% if widget.configured %> - <% - begin - widget_body=render :inline => definition.getTarget().getTemplate(), :locals => {:widget_properties => widget.properties_as_hash, :widget => widget, :dashboard_configuration => @dashboard_configuration} - rescue => error - logger.error(message('dashboard.cannot_render_widget_x', :params => [definition.getId(), error])) - logger.error(error.backtrace.join("\n")) - widget_body="" - end +
+ <% if widget.configured %> + <% + begin + widget_body=render :inline => widget.java_definition.getTarget().getTemplate(), :locals => {:widget_properties => widget.properties_as_hash, :widget => widget, :dashboard_configuration => @dashboard_configuration} + rescue => error + logger.error(message('dashboard.cannot_render_widget_x', :params => [widget.key, error])) + logger.error(error.backtrace.join("\n")) + widget_body="" + end - if widget_body.include?('<') - %> + if widget_body.include?('<') + %> + <% + default_layout=(widget.java_definition.getWidgetLayout().name()=='DEFAULT') + if default_layout + %> +
+ <% end %> + <%= widget_body -%> + <% if default_layout %> +
+ <% end %> <% - default_layout=(definition.getWidgetLayout().name()=='DEFAULT') - if default_layout + end %> -
- <% end %> - <%= widget_body -%> - <% if default_layout %>
<% end %> - <% - end - %> -<% else %> - -<% end %> -
+ <% else %> + + <% end %> +
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_definition.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_definition.html.erb index 6126d0999db..aa7a65bfd48 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_definition.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_definition.html.erb @@ -1,9 +1,10 @@ -
-

<%= h message('widget.' + definition.getId() + '.name', :default => definition.getTitle()) -%>

-

<%= h message('widget.' + definition.getId() + '.description', :default => definition.getDescription()) -%>

-<%= form_tag :action => 'add_widget', :did => dashboard_id, :id => resource_id, :widget => definition.getId() %> - - -
+
+

<%= h message('widget.' + definition.getId() + '.name', :default => definition.getTitle()) -%>

+ +

<%= h message('widget.' + definition.getId() + '.description', :default => definition.getDescription()) -%>

+ <%= form_tag :action => 'add_widget', :did => dashboard_id, :id => resource_id, :widget => definition.getId() %> + + +
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb index 949fda10184..e071c6ce7e1 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/_widget_properties.html.erb @@ -1,35 +1,30 @@ -<% form_remote_tag :url => {:action => 'save_widget', :wid => widget.id, :id => params[:id]}, :method => :post do -%> - <% if defined?(errors_by_property_key) && errors_by_property_key %> -
    - <% errors_by_property_key.each_pair do |property_key, error| %> -
  • <%= property_key -%>: <%= h(error)-%>
  • -
- <% end %> - <% end %> - - - <% definition.getWidgetProperties().each do |property_def| - value=widget.property_value(property_def.key(), property_def.defaultValue()) - %> - - - + + + +
<%= property_def.key() -%><%= "*" unless property_def.optional()==true -%>: - <%= property_value_field(property_def, value) -%> - +<% form_remote_tag :url => {:action => 'save_widget', :wid => widget.id, :id => params[:id]}, + :method => :post, + :update => {:failure => "error#{widget.id}"}, + :failure => "$('error#{widget.id}').show()" do -%> + + + + <% widget.java_definition.getWidgetProperties().each do |property_def| %> + + + - - <% end %> - - - -
<%= property_def.key() -%><%= "*" unless property_def.optional()==true -%>: + <%= property_value_field(property_def, widget.property_text_value(property_def.key())) -%> + <%= message("widget." + widget.key + ".param." + property_def.key(), :default => property_def.description()) -%> -
- <%= submit_tag message('save') %> - <% if widget.configured %> - <%= message('cancel') -%> - <% end %>
- <%= hidden_field_tag "widgetid", "", :class => "widgetid" %> + <% end %> +
+ <%= submit_tag message('save') %> + <% if widget.configured %> + <%= message('cancel') -%> + <% end %> +
+ <%= hidden_field_tag "widgetid", "", :class => "widgetid" %> <% end -%> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb index 75c89a89aa0..54b9856d40d 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/dashboard/configure.html.erb @@ -1,70 +1,67 @@
<%= render :partial => 'dashboard/header', :locals => {:back => true} %>
- <%= render :partial => 'dashboard/widget_definitions', :locals => {:dashboard_id => @dashboard.id, :resource_id => @resource.id, :filter_on_category => nil}-%> + <%= render :partial => 'dashboard/widget_definitions', :locals => {:dashboard_id => @dashboard.id, :resource_id => @resource.id, :filter_on_category => nil} -%>
<% - columns=@dashboard.column_layout.split('-') - for index in 1..columns.size() + columns=@dashboard.column_layout.split('-') + for index in 1..columns.size() %>
-
0px <%= index>1 ? "5px" : "0px" -%>;"> - +
0 <%= index>1 ? "5px" : "0px" -%>;"> + - <% - @dashboard.widgets.select{|widget| widget.column_index==index}.sort_by{|widget| widget.row_index}.each do |widget| - widget_definition=@widget_definitions.find{|wd| wd.getId()==widget.widget_key } - if widget_definition - %> -
- <%= render :partial => 'dashboard/configure_widget', :locals => {:widget => widget, :definition => widget_definition} %> -
- <% - end - end - %> + <% + @dashboard.widgets.select { |widget| widget.column_index==index }.sort_by { |widget| widget.row_index }.each do |widget| + %> +
+ <%= render :partial => 'dashboard/configure_widget', :locals => {:widget => widget} %> +
+ <% + end + %>
<% end %> diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_delete_value_type_from_widget_properties.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_delete_value_type_from_widget_properties.rb new file mode 100644 index 00000000000..874312dad09 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/237_delete_value_type_from_widget_properties.rb @@ -0,0 +1,31 @@ +# +# Sonar, entreprise quality control 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 +# + +# +# Sonar 2.13 +# +class DeleteValueTypeFromWidgetProperties < ActiveRecord::Migration + + def self.up + remove_column('widget_properties', 'value_type') + WidgetProperty.reset_column_information() + end + +end diff --git a/sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java b/sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java index 06c4ea36332..5b8c69c2484 100644 --- a/sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java +++ b/sonar-server/src/test/java/org/sonar/server/ui/ViewProxyTest.java @@ -119,7 +119,7 @@ public class ViewProxyTest { public void widgetShouldBeEditable() { ViewProxy proxy = new ViewProxy(new EditableWidget()); assertThat(proxy.isEditable(), is(true)); - assertThat(proxy.getWidgetProperties().length, is(2)); + assertThat(proxy.getWidgetProperties().size(), is(2)); } @Test @@ -153,8 +153,8 @@ class FakeView implements View { } @WidgetProperties({ - @WidgetProperty(key="foo", optional = false), - @WidgetProperty(key="bar", defaultValue = "30", type = WidgetPropertyType.INTEGER) + @WidgetProperty(key = "foo", optional = false), + @WidgetProperty(key = "bar", defaultValue = "30", type = WidgetPropertyType.INTEGER) }) class EditableWidget implements Widget { @@ -168,8 +168,8 @@ class EditableWidget implements Widget { } @WidgetProperties({ - @WidgetProperty(key="foo"), - @WidgetProperty(key="bar") + @WidgetProperty(key = "foo"), + @WidgetProperty(key = "bar") }) class WidgetWithOptionalProperties implements Widget { -- 2.39.5