@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 {
<%= 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
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.
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=? " +
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()
.height(h)
.left(30)
.right(20)
- .bottom(20)
+ .bottom(footerHeight)
.top(5)
.strokeStyle("#CCC");
/* 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)
.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]];})
.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)")