kanaui 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|