]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2074 Improve timeline widget
authorFabrice Bellingard <bellingard@gmail.com>
Mon, 22 Aug 2011 16:59:58 +0000 (18:59 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Mon, 22 Aug 2011 16:59:58 +0000 (18:59 +0200)
- Add top and bottom "padding" inside the graph for better readibility
- Display a message if only 1 snapshot available
- Display the "legend" under the graph with localized metric names
- Add possibility to specify widget height in the options
- Select first the last snapshot values when rendering the widget
- Add date label (localized)

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java
plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/webapp/WEB-INF/app/models/trends_chart.rb
sonar-server/src/main/webapp/javascripts/protovis-sonar.js

index 6faba17f93960b7baa7f24e8213b407d649f4e9e..0569f5ff6d5f1b6faee507087eff57beab346f1f 100644 (file)
@@ -31,7 +31,8 @@ import org.sonar.api.web.WidgetPropertyType;
         @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC),
         @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC),
         @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC),
-        @WidgetProperty(key = "displayEvents", type = WidgetPropertyType.BOOLEAN)
+        @WidgetProperty(key = "displayEvents", type = WidgetPropertyType.BOOLEAN),
+        @WidgetProperty(key = "widgetHeight", type = WidgetPropertyType.INTEGER)
     }
 )
 public class TimelineWidget extends AbstractRubyTemplate implements RubyRailsWidget {
index 1c2f6ffc5203e46e78e09e21692e89bdf2b34aa1..a8d8d10ee459285a6d6ada0e90d6ca565316734a 100644 (file)
@@ -1,43 +1,64 @@
 <%= javascript_include_tag 'protovis-sonar' %>
 <%
   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] = message('metric.' + metric.name + '.name')
     end
   end
   
+  # Retrieve metric trend information
   options = {}
   from_date = dashboard_configuration.from_datetime
   if from_date
     options[:from] = from_date
   end
-  
   TrendsChart.time_machine_measures(@resource, metric_data_map.keys, options).each()  do |trend_item|
-    metric_data_map[trend_item["metric_id"].to_i] << {:date => trend_item["created_at"], :value => trend_item["value"]}
+    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 = "["
-  metric_data_map.keys.each() do |metric_id|
+  js_snapshots = "["
+  js_metrics = "["
+  metric_data_map.keys.each_with_index() do |metric_id, index|
+    js_metrics += "\"" + metric_name_map[metric_id] + "\","
     js_data += "["
     metric_data_map[metric_id].each() do |metric_data|
       m_date = Time.parse(metric_data[:date])
       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 +=  "),\"y\":" 
-                 js_data +=  sprintf( "%0.02f", metric_data[:value])
-                 js_data +=  "},"
+      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 += "),\"y\":" 
+      js_data += sprintf( "%0.02f", metric_data[:value])
+      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]
+        js_snapshots += ",\"d\":\""
+        js_snapshots += human_short_date(m_date)
+        js_snapshots += "\"},"
+      end
     end
     js_data += "],"
   end
   js_data += "]"
-   
+  js_snapshots += "]"
+  js_metrics += "]"
+  
+  # And prepare translations for labels
+  js_translations = "{"
+  js_translations += "\"date\":\"" + message("date") + "\""
+  js_translations += "}"
+  
 %>
 
 <% if widget_properties["chartTitle"] %>
 <% end %>
 
 
-<div id="timeline-chart-<%= widget.id -%>"></div>
-<script type="text/javascript+protovis">
-  function d(y,m,d) {
-    return new Date(y,m,d);
-  }
-  var data = <%= js_data -%>;  
-  var timeline = new SonarWidgets.Timeline('timeline-chart-<%= widget.id -%>').data(data);
-  timeline.render();
+<% if metric_data_map.values[0].size == 1 %>
+
+       <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.timeline.timeline_not_displayed') -%></span>
+
+<% else %>
+
+       <div id="timeline-chart-<%= widget.id -%>"></div>
+       <script type="text/javascript+protovis">
+         function d(y,m,d) {
+           return new Date(y,m,d);
+         }
+         var data = <%= js_data -%>;
+         var snapshots = <%= js_snapshots -%>;
+         var metrics = <%= js_metrics -%>;
+         var translations = <%= js_translations -%>;
+         var timeline = new SonarWidgets.Timeline('timeline-chart-<%= widget.id -%>')
+                                                       .height(<%= widget_properties["widgetHeight"] ? widget_properties["widgetHeight"] : "null" -%>)
+                                                       .data(data)
+                                                       .snapshots(snapshots)
+                                                       .metrics(metrics)
+                                                       .translations(translations);
+         timeline.render();
+       
+       </script>
 
-</script>
\ No newline at end of file
+<% end %>
\ No newline at end of file
index f61dd520dddd0852e9b87685e70791e6079615af..a61de34dc633a7971fbb86463ac8fb898215ded6 100644 (file)
@@ -510,6 +510,7 @@ widget.size.paragraphs.suffix=\ paragraphs
 
 widget.timeline.name=Timeline
 widget.timeline.description=Displays up to 3 metrics on a history chart.
+widget.timeline.timeline_not_displayed=The timeline won't be displayed as currently only 1 snapshot is available.
 
 widget.ckjm.name=Chidamber & Kemerer
 widget.ckjm.description=Reports on LCOM4 and RFC average and distribution.
index 00d2a31ab02021faa97451efc5b77b3fc328aa51..8c60ee3eed5129bcdd0a8a9eadd88f421e4b5f41 100644 (file)
@@ -41,7 +41,7 @@ class TrendsChart
 
   def self.time_machine_measures(resource, metric_ids, options={})
     unless metric_ids.empty?
-      sql= "select s.created_at as created_at, m.value as value, m.metric_id as metric_id " +
+      sql= "select s.created_at as created_at, m.value as value, m.metric_id as metric_id, s.id as sid " +
             " from project_measures m LEFT OUTER JOIN snapshots s ON s.id=m.snapshot_id " +
             " where m.rule_id is null " +
             " and s.status=? " +
index aa6e34e2ac957ac14a48a23db1ae93a1aa91a715..1de76f4c5ac2b40a558717e4700e8d736620ca98 100755 (executable)
@@ -2,29 +2,54 @@ window.SonarWidgets = {}
 
 SonarWidgets.Timeline = function (divId) {
        this.wDivId = divId;
+       this.wHeight;
        this.wData;
+       this.wSnapshots;
+       this.wMetrics;
+       this.wTranslations;
+       this.height = function(height) {
+               this.wHeight = height;
+               return this;
+       }
        this.data = function(data) {
                this.wData = data;
                return this;
        }
+       this.snapshots = function(snapshots) {
+               this.wSnapshots = snapshots;
+               return this;
+       }
+       this.metrics = function(metrics) {
+               this.wMetrics = metrics;
+               return this;
+       }
+       this.translations = function(translations) {
+               this.wTranslations = translations;
+               return this;
+       }
 }
 
 SonarWidgets.Timeline.prototype.render = function() {
        
+       var trendData = this.wData;
+       var metrics = this.wMetrics;
+       var snapshots = this.wSnapshots;
+       var translations = this.wTranslations;
        var widgetDiv = document.getElementById(this.wDivId);
        
        /* Sizing and scales. */
+       var footerHeight = 30 + this.wMetrics.size() * 12;
        var w = widgetDiv.parentNode.clientWidth - 60, 
-               h = 300 - 25,
+               h = (this.wHeight == null ? 250 : this.wHeight) + footerHeight - 5,
                S=2;
 
        var x = pv.Scale.linear(pv.blend(pv.map(data, function(d) {return d;})), function(d) {return d.x}).range(0, w);
        var y = new Array(data.length);
        for(var i = 0; i < data.length; i++){ 
-               y[i]=pv.Scale.linear(data[i], function(d) {return d.y;}).range(0, h)
+               y[i]=pv.Scale.linear(data[i], function(d) {return d.y;}).range(10, h-10)
        }
        var interpolate = "linear"; /* cardinal or linear */
-       var idx = -1;
+       var idx = this.wData[0].size() - 1;
 
        /* The root panel. */
        var vis = new pv.Panel()
@@ -33,7 +58,7 @@ SonarWidgets.Timeline.prototype.render = function() {
                .height(h)
                .left(30)
                .right(20)
-               .bottom(20)
+               .bottom(footerHeight)
                .top(5)
                .strokeStyle("#CCC");
 
@@ -61,7 +86,7 @@ SonarWidgets.Timeline.prototype.render = function() {
 
        /* A panel for each data series. */
        var panel = vis.add(pv.Panel)
-               .data(this.wData);
+               .data(trendData);
 
        /* The line. */
        var line = panel.add(pv.Line)
@@ -71,7 +96,7 @@ SonarWidgets.Timeline.prototype.render = function() {
                .interpolate(function() {return interpolate;})
                .lineWidth(2);
 
-       /* The mouseover dots and label. */
+       /* The mouseover dots and label in footer. */
        line.add(pv.Dot)
                .visible(function() {return idx >= 0;})
                .data(function(d) {return [d[idx]];})
@@ -80,12 +105,19 @@ SonarWidgets.Timeline.prototype.render = function() {
                .size(20) 
                .lineWidth(1)
                .add(pv.Dot)
-               .left(10)
-               .bottom(function() {return this.parent.index * 12 + 10;})
+               .left(150)
+               .bottom(function() {return 0 - 30 - this.parent.index * 12;})
                .anchor("right").add(pv.Label)
-               .text(function(d) {return d.y.toFixed(2);});
-
-
+               .font("12px Arial,Helvetica,sans-serif")
+               .text(function(d) {return metrics[this.parent.index] + ": " + d.y.toFixed(2);});
+       
+       /* The date of the selected dot in footer. */
+       vis.add(pv.Label)
+               .left(0)
+               .bottom(-36)
+               .font("12px Arial,Helvetica,sans-serif")
+               .text(function() {return translations.date + ": " + snapshots[idx].d});
+       
        /* An invisible bar to capture events (without flickering). */
        vis.add(pv.Bar)
                .fillStyle("rgba(0,0,0,.001)")