kanaui 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5028db371172c24fb078fdc217e985e058cfbecf
4
- data.tar.gz: da0922bc0da3a84267d30f0f80138a23fa9f92c9
3
+ metadata.gz: b509920c148aef35ccf0bffbe38b935b669a35ff
4
+ data.tar.gz: e5d121e521e55c1c3f882f7f0bbfd576c961a7e9
5
5
  SHA512:
6
- metadata.gz: 9fc14b185e6631a093a74bee47b1825b7fd04aa4dbc7cbc519318e24657cd721168fb70304e5cc3859bd3a2474d499f7c8d45dbbf8f2d18b2688f2d2c6241b6a
7
- data.tar.gz: ce0be1bf02f07f8173a4c99f7b389408ffc5f7f8eef47233e970753e5811f550ae05f650d3eea74089f30b52982183f4ba6f447c437f9da6049accb8938249ee
6
+ metadata.gz: e8ebbb2db468390719e6c125880f483382f2cfd61ca4256128409c7dfa89aa11b367d3f6b5bdc039897c81de3fd627007348ff2c86e82096e94392c4c7f47b4d
7
+ data.tar.gz: bc6c3397d0fa1f6192f81cf85c199934126c4a42283791ec8e6ec7b0b8e31dc7678804dc5c288474713f22a9ccad7dac089967a2a6fed42440b5edb83956bc81
@@ -13,7 +13,7 @@
13
13
  return d3.svg.axis()
14
14
  .scale(self.y)
15
15
  .orient("left")
16
- .tickFormat(d3.format('d'));
16
+ .tickFormat(d3.format(',d'));
17
17
  }
18
18
 
19
19
  var xAxis = makeXAxis();
@@ -23,32 +23,32 @@
23
23
  x: xAxis,
24
24
  y: yAxis,
25
25
  render: function(svg, yTitle){
26
- svg.append("g")
27
- .attr("class", "x axis")
28
- .attr("transform", "translate(" + self.margin_left + "," + self.height + ")")
29
- .call(xAxis);
30
-
31
- svg.append("g")
32
- .attr("class", "y axis")
33
- .attr("transform", "translate(" + self.margin_left + ",0)")
34
- .call(yAxis)
35
-
36
- .append("text")
37
- .attr("transform", "rotate(-90)")
38
- .attr("y", 6)
39
- .attr("dy", ".71em")
40
- .style("text-anchor", "end")
41
- .text(yTitle);
26
+ svg.append("g")
27
+ .attr("class", "grid")
28
+ .attr("transform", "translate(" + self.margin_left + "," + self.height + ")")
29
+ .call(makeXAxis().tickSize(-self.height, 0, 0).tickFormat(""));
30
+
31
+ svg.append("g")
32
+ .attr("class", "grid")
33
+ .attr("transform", "translate(" + self.margin_left + ",0)")
34
+ .call(makeYAxis().tickSize(-self.width, 0, 0).tickFormat(""));
42
35
 
43
36
  svg.append("g")
44
- .attr("class", "grid")
45
- .attr("transform", "translate(" + self.margin_left + "," + self.height + ")")
46
- .call(makeXAxis().tickSize(-self.height, 0, 0).tickFormat(""));
47
-
48
- svg.append("g")
49
- .attr("class", "grid")
50
- .attr("transform", "translate(" + self.margin_left + ",0)")
51
- .call(makeYAxis().tickSize(-self.width, 0, 0).tickFormat(""));
37
+ .attr("class", "x axis")
38
+ .attr("transform", "translate(" + self.margin_left + "," + self.height + ")")
39
+ .call(xAxis);
40
+
41
+ svg.append("g")
42
+ .attr("class", "y axis")
43
+ .attr("transform", "translate(" + self.margin_left + ",0)")
44
+ .call(yAxis);
45
+ //.append("text")
46
+ //.attr("transform", "rotate(-90)")
47
+ //.attr("y", 6)
48
+ //.attr("dy", ".71em")
49
+ //.style("text-anchor", "end")
50
+ //.text(yTitle);
51
+
52
52
  }
53
53
  };
54
54
  };
@@ -30,11 +30,16 @@
30
30
 
31
31
  self.x.domain(x_domain);
32
32
 
33
- var y_domain = [0, d3.max(datasets, function(datum){
34
- return d3.max(datum.values, function(d){
35
- return d.y;
36
- });
37
- })];
33
+ var y_domain = [d3.min(datasets, function (datum) {
34
+ return d3.min(datum.values, function (d) {
35
+ return d.y;
36
+ });
37
+ }),
38
+ d3.max(datasets, function (datum) {
39
+ return d3.max(datum.values, function (d) {
40
+ return d.y;
41
+ });
42
+ })];
38
43
 
39
44
  self.y.domain(y_domain);
40
45
 
@@ -47,8 +52,8 @@
47
52
  name = dataset.name;
48
53
 
49
54
  data.forEach(function(d) {
50
- d.date = d.x;
51
- d.x = helper.parseDate(d.x);
55
+ d.date = d.x.split('T')[0]; // Support both date and date/times
56
+ d.x = helper.parseDate(d.date);
52
57
  d.y = +d.y;
53
58
  });
54
59
 
@@ -12,23 +12,13 @@
12
12
  .style("display", "none");
13
13
 
14
14
  var canvas = svg.append("g")
15
+ .attr("id", "mouseover_canvas")
15
16
  .style("display", "none");
16
17
 
17
18
  var info = canvas.append("rect")
18
19
  .attr("class", "information")
19
20
  .attr("width", self.width / 2);
20
21
 
21
- var addInfoDimensions = function(element){
22
- var box = element.node().getBBox();
23
- var infoBox = info.node().getBBox();
24
- var margin = 10;
25
-
26
- info.attr("height", infoBox.height + box.height + margin);
27
- if(infoBox.width < box.width){
28
- info.attr("width", box.width + margin);
29
- }
30
- };
31
-
32
22
  // The magic:
33
23
  svg.append("rect")
34
24
  .attr("class", "overlay")
@@ -41,45 +31,49 @@
41
31
 
42
32
  var infoTitleBg = canvas.append("rect")
43
33
  .attr("class", "info-title__bg")
44
- .attr("width", self.width / 2)
34
+ .attr("width", self.width)
45
35
  .attr("height", 30);
46
36
 
47
- canvas.append("text")
48
- .attr("dy", ".85em")
49
- .attr("dx", 100)
37
+ var infoTitle = canvas.append("text")
38
+ .attr("dy", "1em")
39
+ .attr("dx", 0)
50
40
  .attr("class", "info-title")
51
41
  .attr("id", "info-title");
52
42
 
53
- addInfoDimensions(infoTitleBg);
43
+ var addInfoDimensions = function(element) {
44
+ // On mouseover, element is the current label_idx
45
+ var box = element.node().getBBox();
46
+ // infoBox is the .information rect
47
+ var infoBox = info.node().getBBox();
48
+ var infoTitleBox = infoTitle.node().getBBox();
49
+ var margin = 40;
50
+
51
+ info.attr("height", infoBox.height + box.height + 7);
52
+ if (infoBox.width < box.width) {
53
+ info.attr("width", box.width + margin);
54
+ infoTitleBg.attr("width", box.width + margin);
55
+
56
+ $('#mouseover_canvas #info-title').attr("dx", (box.width + margin) / 2 - infoTitleBox.width / 2);
57
+ }
58
+ };
54
59
 
55
60
  self.datasets.forEach(function(element, index){
56
61
  focus.append("circle")
57
62
  .attr("r", 4.5)
58
63
  .attr("id", "circle_" + index)
59
64
  .attr("transform", "translate(" + self.margin_left + ",0)");
60
-
61
- canvas.append("circle")
62
- .attr("r", 5.5)
63
- .attr("cx", 10)
64
- .attr("cy", (index + 2) * 25)
65
- .style("fill", self.color(element.name))
66
- .style("stroke", "black")
67
-
68
- var text = canvas.append("text")
69
- .attr("y", (index + 2) * 25)
70
- .attr("x", 20)
71
- .attr("cx", 20)
72
- .attr("dy", ".35em")
73
- .attr("class", "chart_values")
74
- .attr("id", "label_" + index)
75
- .text(element.name);
76
-
77
- addInfoDimensions(text);
78
65
  });
79
66
 
80
67
  function mousemove() {
81
68
  var _this = this;
82
69
 
70
+ $('#mouseover_canvas .chart_values').detach().remove();
71
+ $('#mouseover_canvas .chart_circles').detach().remove();
72
+ info.attr("height", infoTitleBg.node().getBBox().height + 10);
73
+ info.attr("width", 1);
74
+ infoTitle.attr("width", 1);
75
+
76
+ var elementsForLegend = [];
83
77
  self.datasets.forEach(function(element, index){
84
78
  var data = element.values;
85
79
  var name = element.name;
@@ -87,16 +81,24 @@
87
81
  var x0 = x.invert(d3.mouse(_this)[0]),
88
82
  i = helper.bisectDate(data, x0, 1),
89
83
  d0 = data[i - 1],
90
- d1 = data[i],
91
- d = x0 - d0.x > d1.x - x0 ? d1 : d0;
84
+ d1 = data[i];
85
+
86
+ if (d0 !== undefined && d1 !== undefined) {
87
+ var d = x0 - d0.x > d1.x - x0 ? d1 : d0;
88
+ } else if (d0 !== undefined) {
89
+ var d = d0;
90
+ } else if (d1 !== undefined) {
91
+ var d = d1;
92
+ } else {
93
+ return;
94
+ }
92
95
 
93
96
  focus.select("#circle_" + index)
94
97
  .attr("cx", x(d.x))
95
98
  .attr("cy", y(d.y))
96
99
  .style("fill", self.color(name));
97
100
 
98
- var infoWidth = info.node().getBBox().width
99
- var canvasPosition = x(x0) > self.width / 2 ? 50 : self.width - infoWidth;
101
+ var canvasPosition = x(x0) > self.width / 2 ? 50 : self.width / 2;
100
102
 
101
103
  canvas
102
104
  .attr("transform", "translate(" + canvasPosition + ",0)");
@@ -104,7 +106,33 @@
104
106
  canvas.select("#info-title")
105
107
  .text(d.date);
106
108
 
107
- text = canvas.select("#label_" + index).text(helper.formatValueDisplay(name, d));
109
+ elementsForLegend.push({element: element, d: d});
110
+ });
111
+
112
+ elementsForLegend.sort(function (a, b) {
113
+ return a.d.y > b.d.y ? -1 : (a.d.y < b.d.y ? 1 : 0);
114
+ });
115
+
116
+ // Limit the number of legend items (document largest values only)
117
+ elementsForLegend.slice(0,10).forEach(function(element, index) {
118
+ canvas.append("circle")
119
+ .attr("r", 5.5)
120
+ .attr("cx", 15)
121
+ .attr("cy", (index + 2) * 25)
122
+ .attr("class", "chart_circles")
123
+ .style("fill", self.color(element.element.name))
124
+ .style("stroke", "black")
125
+
126
+ var text = canvas.append("text")
127
+ .attr("y", (index + 2) * 25)
128
+ .attr("x", 25)
129
+ .attr("cx", 25)
130
+ .attr("dy", ".35em")
131
+ .attr("class", "chart_values")
132
+ .attr("id", "label_" + index)
133
+ .text(element.d === undefined ? element.element.name : helper.formatValueDisplay(element.element.name, element.d));
134
+
135
+ addInfoDimensions(text);
108
136
  });
109
137
  }
110
138
  }
@@ -3,40 +3,48 @@
3
3
  if($('#chartAnchor').length == 0) { return; }
4
4
 
5
5
  d3.json($('#chartAnchor').data('reports-path'), function(error, json){
6
- if(error){ throw error };
6
+ $('#loading-spinner').remove();
7
+
8
+ var renderer = new Kiddo.Renderer('#chartAnchor');
9
+
10
+ if (error) {
11
+ console.log(error);
12
+ return renderer.noData();
13
+ }
7
14
 
8
15
  var data = json[0];
9
16
 
10
- $('#loading-spinner').remove();
17
+ if (data === undefined ||
18
+ data.data === undefined ||
19
+ data.data.length == 0) {
20
+ return renderer.noData();
21
+ }
11
22
 
12
23
  var render = function(type){
13
- if(data.data.length == 0) { return renderer.noData(); }
14
24
  switch(type){
15
25
  case 'COUNTERS':
16
- var renderer = new Kiddo.Renderer('#chartAnchor');
17
- renderer.pieChart(data)
26
+ renderer.pieChart(data);
18
27
  break;
19
28
  case 'TIMELINE':
20
- var renderer = new Kiddo.Renderer('#chartAnchor');
21
29
  renderer.lineChart(data);
30
+ // Date controls only make sense for timelines
31
+ $('#date-controls').show();
22
32
  break;
23
33
  case 'TABLE':
24
- new ReportsDataTables(null).buildTable(data['data'][0], $('#chartAnchor'));
34
+ renderer.table(data);
25
35
  break;
26
36
  default:
27
37
  console.log('No such type implemented: ' + type);
28
- var renderer = new Kiddo.Renderer('#chartAnchor');
29
38
  renderer.noData();
30
39
  }
31
40
  };
32
41
 
33
42
  try{
34
- render(json[0].type);
35
- }catch(ex){
43
+ render(data.type);
44
+ } catch (ex){
36
45
  console.log(ex);
37
46
  renderer.noData();
38
47
  }
39
-
40
48
  });
41
49
  });
42
50
  })(d3, jQuery, window, document);
@@ -9,6 +9,7 @@
9
9
  .append('svg')
10
10
  .attr('width', settings.raw_width)
11
11
  .attr('height', settings.raw_height)
12
+ .attr('style', 'overflow: visible')
12
13
  .append('g')
13
14
  .attr('transform', 'translate(' + settings.margin_left + ',' + settings.margin_top + ')');
14
15
 
@@ -18,11 +19,15 @@
18
19
  chart.render(svg, data);
19
20
  },
20
21
 
21
- pieChart: function(data){
22
- var chart = Kiddo.PieChart.apply(settings);
22
+ pieChart: function(data){var chart = Kiddo.PieChart.apply(settings);
23
23
  chart.render(svg, data);
24
24
  },
25
25
 
26
+ table: function(data){
27
+ svg.node().parentNode.remove();
28
+ new ReportsDataTables(null).buildTable(data['data'][0], $(selector));
29
+ },
30
+
26
31
  noData: function(){
27
32
  svg.append('text')
28
33
  .attr('class', 'chart-info')
@@ -2,7 +2,7 @@
2
2
  Kiddo.Settings = function(){
3
3
  var margin_top = 30,
4
4
  margin_bottom = 40,
5
- margin_left = 30,
5
+ margin_left = 50,
6
6
  margin_right = 150,
7
7
  raw_width = parseInt(this.element.node().getBoundingClientRect().width, 10),
8
8
  raw_height = 500;
@@ -10,7 +10,7 @@
10
10
  }
11
11
 
12
12
  .x.axis path {
13
- display: none;
13
+ #display: none;
14
14
  }
15
15
 
16
16
  .line {
@@ -6,44 +6,64 @@ module Kanaui
6
6
  # the reports and available_reports endpoints below.
7
7
  #
8
8
  def index
9
- raw_reports = Kanaui::DashboardHelper::DashboardApi.available_reports(options)
9
+ raw_reports = Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)
10
10
 
11
- @endDate = params['endDate'] || Date.today.to_s
11
+ @raw_name = (params[:name] || '').split('^')[0]
12
+
13
+ @end_date = params[:end_date] || Date.today.to_s
12
14
 
13
15
  @available_start_dates = start_date_options
14
- @startDate = params['startDate'] || @available_start_dates['Last 6 months']
16
+ @start_date = params[:start_date] || (params[:delta_days].present? ? (@end_date.to_date - params[:delta_days].to_i.day).to_s : @available_start_dates['Last 6 months'])
15
17
 
16
18
  @reports = JSON.parse(raw_reports)
19
+ @report = current_report(@reports) || {}
20
+
21
+ query = build_slice_and_dice_query
22
+
23
+ # Redirect also in case the dates have been updated to avoid any confusion in the view
24
+ if query.present? || params[:start_date].blank? || params[:end_date].blank?
25
+ # TODO Make metrics configurable
26
+ name = query.present? ? "#{params[:name]}#{query}^metric:count" : params[:name]
27
+ redirect_to dashboard_index_path(:start_date => @start_date,
28
+ :end_date => @end_date,
29
+ :name => name,
30
+ :smooth => params[:smooth],
31
+ :sql_only => params[:sql_only],
32
+ :format => params[:format]) and return
33
+ end
34
+
17
35
  render
18
36
  end
19
37
 
20
38
  # Not used anymore as reports are pulled from index
21
39
  def available_reports
22
- available_reports = Kanaui::DashboardHelper::DashboardApi.available_reports(options)
23
- render json: available_reports
40
+ available_reports = Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)
41
+ render :json => available_reports
24
42
  end
25
43
 
26
44
  def reports
27
- format = params['format'] || 'json'
28
- reports = fetch_reports(format)
29
- render json: reports
30
- end
45
+ format = params[:format] || 'json'
46
+ raw_reports = fetch_reports(format)
31
47
 
32
- private
48
+ if format == 'json' && params[:sql_only] != 'true'
49
+ reports = JSON.parse(raw_reports)
33
50
 
34
- def options
35
- user = current_tenant_user
36
- {
37
- :username => user[:username],
38
- :password => user[:password],
39
- :session_id => user[:session_id],
40
- :api_key => user[:api_key],
41
- :api_secret => user[:api_secret]
42
- }
51
+ # Remove reports with empty data
52
+ reports.reject! do |report|
53
+ reject = false
54
+ (report['data'] || []).each { |data| reject = true if (data['values'] || []).empty? && data['value'].blank? }
55
+ reject
56
+ end
57
+ else
58
+ reports = raw_reports
59
+ end
60
+ render :json => reports
43
61
  end
44
62
 
63
+ private
64
+
45
65
  def start_date_options
46
- end_date = @endDate.to_date
66
+ end_date = @end_date.to_date
47
67
  {
48
68
  'Last month' => (end_date - 1.month).to_s,
49
69
  'Last 3 months' => (end_date - 3.months).to_s,
@@ -53,13 +73,53 @@ module Kanaui
53
73
  end
54
74
 
55
75
  def fetch_reports(format)
56
- if params['fake']
76
+ if params[:fake]
57
77
  type = params.fetch('type', 'pie')
58
78
  File.read(Kanaui::Engine.root.join('lib', 'sample_data', "#{type}.json"))
59
79
  else
60
- Kanaui::DashboardHelper::DashboardApi.reports(params['startDate'], params['endDate'], params['name'], params['smooth'], format, options)
80
+ Kanaui::DashboardHelper::DashboardApi.reports(params[:start_date],
81
+ params[:end_date],
82
+ params[:name],
83
+ params[:smooth],
84
+ params[:sql_only],
85
+ format,
86
+ options_for_klient)
61
87
  end
62
88
  end
63
89
 
90
+ def current_report(reports)
91
+ return nil if @raw_name.blank?
92
+
93
+ reports.find { |r| r['reportName'] == @raw_name }
94
+ end
95
+
96
+ def build_slice_and_dice_query
97
+ query = ''
98
+
99
+ filters = {}
100
+ groups = {}
101
+ ((@report['schema'] || {})['fields'] || []).each do |field|
102
+ field_name = field['name']
103
+
104
+ filters[field_name] = params["filter_#{field_name}"]
105
+ groups[field_name] = params["group_#{field_name}"]
106
+ end
107
+
108
+ filter_query = ''
109
+ filters.each do |k, v|
110
+ next if v.blank?
111
+ filter_query << '%26' unless filter_query.blank?
112
+ filter_query << "(#{k}=#{v.join("|#{k}=")})"
113
+ end
114
+ query << "^filter:#{filter_query}" unless filter_query.blank?
115
+
116
+ groups.each do |k, v|
117
+ next if v.blank?
118
+ # TODO Make "no other" configurable
119
+ query << "^dimension:#{k}(#{v.join('|')}|-)"
120
+ end
121
+
122
+ query
123
+ end
64
124
  end
65
125
  end