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 +4 -4
- data/app/assets/javascripts/kanaui/kiddo/axes.js +25 -25
- data/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js +12 -7
- data/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js +67 -39
- data/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js +19 -11
- data/app/assets/javascripts/kanaui/kiddo/renderer.js +7 -2
- data/app/assets/javascripts/kanaui/kiddo/settings.js +1 -1
- data/app/assets/stylesheets/kanaui/reports.css +1 -1
- data/app/controllers/kanaui/dashboard_controller.rb +82 -22
- data/app/controllers/kanaui/engine_controller.rb +10 -0
- data/app/controllers/kanaui/reports_controller.rb +60 -0
- data/app/helpers/kanaui/dashboard_helper.rb +28 -2
- data/app/views/kanaui/dashboard/index.html.erb +90 -15
- data/app/views/kanaui/reports/_form.html.erb +49 -0
- data/app/views/kanaui/reports/_reports_table.html.erb +60 -0
- data/app/views/kanaui/reports/edit.html.erb +10 -0
- data/app/views/kanaui/reports/index.html.erb +17 -0
- data/app/views/kanaui/reports/new.html.erb +10 -0
- data/config/routes.rb +5 -1
- data/lib/kanaui/version.rb +1 -1
- metadata +121 -121
- data/app/assets/javascripts/kanaui/killbill.js +0 -1114
- data/app/assets/javascripts/kanaui/purl.js +0 -271
- data/app/assets/javascripts/kanaui/reports.graphs.js +0 -190
- data/app/assets/javascripts/kanaui/reports.js +0 -201
- data/app/assets/javascripts/kanaui/reports.urls.js +0 -44
- data/app/assets/javascripts/kanaui/tests.js +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b509920c148aef35ccf0bffbe38b935b669a35ff
|
4
|
+
data.tar.gz: e5d121e521e55c1c3f882f7f0bbfd576c961a7e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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 = [
|
34
|
-
|
35
|
-
|
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.
|
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
|
34
|
+
.attr("width", self.width)
|
45
35
|
.attr("height", 30);
|
46
36
|
|
47
|
-
canvas.append("text")
|
48
|
-
.attr("dy", "
|
49
|
-
.attr("dx",
|
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(
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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')
|
@@ -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(
|
9
|
+
raw_reports = Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)
|
10
10
|
|
11
|
-
@
|
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
|
-
@
|
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(
|
23
|
-
render json
|
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[
|
28
|
-
|
29
|
-
render json: reports
|
30
|
-
end
|
45
|
+
format = params[:format] || 'json'
|
46
|
+
raw_reports = fetch_reports(format)
|
31
47
|
|
32
|
-
|
48
|
+
if format == 'json' && params[:sql_only] != 'true'
|
49
|
+
reports = JSON.parse(raw_reports)
|
33
50
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 = @
|
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[
|
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[
|
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
|