kanaui 5.0.0 → 5.0.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 +5 -7
- data/app/assets/javascripts/kanaui/kiddo/charts/line_chart.js +31 -36
- data/app/assets/javascripts/kanaui/kiddo/charts/pie_chart.js +18 -13
- data/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js +3 -3
- data/app/assets/javascripts/kanaui/kiddo/helper.js +45 -2
- data/app/assets/javascripts/kanaui/kiddo/kiddo_initialize.js +29 -6
- data/app/assets/stylesheets/kanaui/kanaui.css +19 -0
- data/app/assets/stylesheets/kanaui/reports.css +85 -0
- data/app/controllers/kanaui/dashboard_controller.rb +18 -14
- data/app/controllers/kanaui/engine_controller.rb +21 -2
- data/app/controllers/kanaui/reports_controller.rb +22 -13
- data/app/helpers/kanaui/dashboard_helper.rb +1 -1
- data/app/views/kanaui/dashboard/index.html.erb +9 -4
- data/app/views/kanaui/layouts/kanaui_application.html.erb +11 -15
- data/app/views/kanaui/reports/_form.html.erb +130 -23
- data/app/views/kanaui/reports/_reports_table.html.erb +22 -17
- data/app/views/kanaui/reports/edit.html.erb +1 -1
- data/app/views/kanaui/reports/index.html.erb +6 -3
- data/app/views/kanaui/reports/new.html.erb +1 -1
- data/config/locales/en.bootstrap.yml +64 -0
- data/config/routes.rb +3 -3
- data/lib/kanaui/engine.rb +0 -1
- data/lib/kanaui/version.rb +1 -1
- data/vendor/assets/javascripts/d3.js +2 -0
- data/vendor/assets/javascripts/log4javascript.js +274 -0
- metadata +4 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6d1a8dc2490699d189f74a1c0e895e29563a342eee594a0739d0dc5b47bafd4
|
|
4
|
+
data.tar.gz: 83b3fe017d28bb1c44c2ff1641ead0c64a2c8fab62607d533f415f2609f651ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa06748ad56e379a637fcc1348af0fdbc922d0c0a3923ec88abeaf7d56b0994c5bcae3a5b02e354e11927e74015f7764a3ea334a568f831a633d98d9a64d1946
|
|
7
|
+
data.tar.gz: 0a89abd1fccd25272ece9b7c01a725f64f177d41574e35c7611f80f3ba57f49ab7411426231a4495425549d9684c873fdfd2029a77bb25e9ffebbe703e949f38
|
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
var self = this;
|
|
4
4
|
|
|
5
5
|
var makeXAxis = function () {
|
|
6
|
-
return d3.
|
|
6
|
+
return d3.axisBottom(self.x).ticks(6);
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
var makeYAxis = function () {
|
|
10
|
-
return d3
|
|
11
|
-
.
|
|
12
|
-
.scale(self.y)
|
|
13
|
-
.orient("left")
|
|
10
|
+
return d3
|
|
11
|
+
.axisLeft(self.y)
|
|
14
12
|
.tickFormat(d3.format(",d"));
|
|
15
13
|
};
|
|
16
14
|
|
|
@@ -28,13 +26,13 @@
|
|
|
28
26
|
"transform",
|
|
29
27
|
"translate(" + self.margin_left + "," + self.height + ")"
|
|
30
28
|
)
|
|
31
|
-
.call(makeXAxis().tickSize(-self.height
|
|
29
|
+
.call(makeXAxis().tickSize(-self.height).tickFormat(""));
|
|
32
30
|
|
|
33
31
|
svg
|
|
34
32
|
.append("g")
|
|
35
33
|
.attr("class", "grid")
|
|
36
34
|
.attr("transform", "translate(" + self.margin_left + ",0)")
|
|
37
|
-
.call(makeYAxis().tickSize(-self.width
|
|
35
|
+
.call(makeYAxis().tickSize(-self.width).tickFormat(""));
|
|
38
36
|
|
|
39
37
|
svg
|
|
40
38
|
.append("g")
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Kiddo.LineChart = function () {
|
|
3
3
|
var self = this;
|
|
4
4
|
|
|
5
|
-
this.x = d3.
|
|
6
|
-
this.y = d3.
|
|
5
|
+
this.x = d3.scaleTime().range([0, this.width]);
|
|
6
|
+
this.y = d3.scaleLinear().range([this.height, 0]);
|
|
7
7
|
|
|
8
|
-
var valueline = d3
|
|
8
|
+
var valueline = d3
|
|
9
9
|
.line()
|
|
10
10
|
.x(function (d) {
|
|
11
11
|
return self.x(d.x);
|
|
@@ -13,59 +13,60 @@
|
|
|
13
13
|
.y(function (d) {
|
|
14
14
|
return self.y(d.y);
|
|
15
15
|
})
|
|
16
|
-
.
|
|
16
|
+
.curve(d3.curveMonotoneX);
|
|
17
17
|
|
|
18
18
|
var axes = Kiddo.Axes.apply(this);
|
|
19
19
|
var helper = new Kiddo.Helper();
|
|
20
20
|
|
|
21
|
-
// Custom
|
|
22
|
-
var
|
|
23
|
-
"#
|
|
24
|
-
"#
|
|
25
|
-
"#
|
|
26
|
-
"#
|
|
27
|
-
"#
|
|
28
|
-
"#
|
|
29
|
-
"#
|
|
30
|
-
"#
|
|
21
|
+
// Custom multi-color theme matching the image
|
|
22
|
+
var colors = [
|
|
23
|
+
"#2196F3", // blue
|
|
24
|
+
"#E53935", // red
|
|
25
|
+
"#43A047", // green
|
|
26
|
+
"#FB8C00", // orange
|
|
27
|
+
"#8E24AA", // purple
|
|
28
|
+
"#00ACC1", // cyan
|
|
29
|
+
"#F4511E", // deep orange
|
|
30
|
+
"#6D4C41", // brown
|
|
31
31
|
];
|
|
32
|
-
self.color = d3.
|
|
32
|
+
self.color = d3.scaleOrdinal().range(colors);
|
|
33
33
|
|
|
34
34
|
return {
|
|
35
35
|
render: function (svg, json) {
|
|
36
36
|
var title = json.title,
|
|
37
37
|
datasets = json.data;
|
|
38
38
|
|
|
39
|
+
datasets.forEach(function (dataset) {
|
|
40
|
+
dataset.values.forEach(function (d) {
|
|
41
|
+
d.date = d.x.split("T")[0];
|
|
42
|
+
d.x = helper.parseDate(d.date);
|
|
43
|
+
d.y = +d.y;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
39
47
|
// Scale the range of the data before rendering axes
|
|
40
48
|
var allValues = datasets.reduce(function (result, element) {
|
|
41
49
|
return result.concat(element.values);
|
|
42
50
|
}, []);
|
|
43
51
|
|
|
44
52
|
var x_domain = d3.extent(allValues, function (d) {
|
|
45
|
-
return
|
|
53
|
+
return d.x;
|
|
46
54
|
});
|
|
47
55
|
|
|
48
56
|
self.x.domain(x_domain);
|
|
49
57
|
|
|
50
|
-
var y_domain =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return d.y;
|
|
54
|
-
});
|
|
55
|
-
}),
|
|
56
|
-
d3.max(datasets, function (datum) {
|
|
57
|
-
return d3.max(datum.values, function (d) {
|
|
58
|
-
return d.y;
|
|
59
|
-
});
|
|
60
|
-
}),
|
|
61
|
-
];
|
|
58
|
+
var y_domain = d3.extent(allValues, function (d) {
|
|
59
|
+
return d.y;
|
|
60
|
+
});
|
|
62
61
|
|
|
63
62
|
self.y.domain(y_domain);
|
|
64
63
|
|
|
65
64
|
// Render axes first
|
|
66
65
|
axes.render(svg, title);
|
|
67
66
|
|
|
68
|
-
self.color.domain(
|
|
67
|
+
self.color.domain(datasets.map(function (dataset) {
|
|
68
|
+
return dataset.name;
|
|
69
|
+
}));
|
|
69
70
|
|
|
70
71
|
// Create legend container at the top
|
|
71
72
|
var legendContainer = svg
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
|
|
109
110
|
// Add text label
|
|
110
111
|
var labelText =
|
|
111
|
-
d.name + "
|
|
112
|
+
helper.formatSeriesName(d.name) + " (" + d.count + "): " + d3.format(",.2f")(d.value);
|
|
112
113
|
legendItem
|
|
113
114
|
.append("text")
|
|
114
115
|
.attr("x", xOffset + 18)
|
|
@@ -129,12 +130,6 @@
|
|
|
129
130
|
var data = dataset.values,
|
|
130
131
|
name = dataset.name;
|
|
131
132
|
|
|
132
|
-
data.forEach(function (d) {
|
|
133
|
-
d.date = d.x.split("T")[0]; // Support both date and date/times
|
|
134
|
-
d.x = helper.parseDate(d.date);
|
|
135
|
-
d.y = +d.y;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
133
|
svg
|
|
139
134
|
.append("path")
|
|
140
135
|
.attr("class", "line")
|
|
@@ -2,26 +2,27 @@
|
|
|
2
2
|
Kiddo.PieChart = function () {
|
|
3
3
|
var self = this;
|
|
4
4
|
var radius = Math.min(this.width, this.height) / 2;
|
|
5
|
+
var helper = new Kiddo.Helper();
|
|
5
6
|
|
|
6
7
|
// Custom blue color theme - matching the line chart
|
|
7
|
-
var
|
|
8
|
-
"#
|
|
9
|
-
"#
|
|
10
|
-
"#
|
|
11
|
-
"#
|
|
12
|
-
"#
|
|
13
|
-
"#
|
|
14
|
-
"#
|
|
15
|
-
"#
|
|
8
|
+
var colors = [
|
|
9
|
+
"#2196F3", // blue
|
|
10
|
+
"#E53935", // red
|
|
11
|
+
"#43A047", // green
|
|
12
|
+
"#FB8C00", // orange
|
|
13
|
+
"#8E24AA", // purple
|
|
14
|
+
"#00ACC1", // cyan
|
|
15
|
+
"#F4511E", // deep orange
|
|
16
|
+
"#6D4C41", // brown
|
|
16
17
|
];
|
|
17
|
-
var color = d3.
|
|
18
|
+
var color = d3.scaleOrdinal().range(colors);
|
|
18
19
|
|
|
19
|
-
var arc = d3
|
|
20
|
+
var arc = d3
|
|
20
21
|
.arc()
|
|
21
22
|
.outerRadius(radius - 10)
|
|
22
23
|
.innerRadius(0);
|
|
23
24
|
|
|
24
|
-
var pie = d3
|
|
25
|
+
var pie = d3
|
|
25
26
|
.pie()
|
|
26
27
|
.sort(null)
|
|
27
28
|
.value(function (d) {
|
|
@@ -41,6 +42,10 @@
|
|
|
41
42
|
d.value = +d.value;
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
color.domain(data.map(function (d, index) {
|
|
46
|
+
return index;
|
|
47
|
+
}));
|
|
48
|
+
|
|
44
49
|
var g = svg
|
|
45
50
|
.selectAll(".arc")
|
|
46
51
|
.data(pie(data))
|
|
@@ -74,7 +79,7 @@
|
|
|
74
79
|
})
|
|
75
80
|
.html(function (d, i) {
|
|
76
81
|
return (
|
|
77
|
-
colorCircle(d.data.value, i) + d.data.label + ": " + d.data.value
|
|
82
|
+
colorCircle(d.data.value, i) + helper.formatSeriesName(d.data.label) + ": " + d.data.value
|
|
78
83
|
);
|
|
79
84
|
})
|
|
80
85
|
.attr("class", "chart_values");
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
.attr("transform", "translate(" + self.margin_left + ",0)");
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
function mousemove() {
|
|
84
|
-
var
|
|
83
|
+
function mousemove(event) {
|
|
84
|
+
var mouseX = d3.pointer(event, this)[0];
|
|
85
85
|
|
|
86
86
|
$("#mouseover_canvas .chart_values").detach().remove();
|
|
87
87
|
$("#mouseover_canvas .chart_circles").detach().remove();
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
var data = element.values;
|
|
95
95
|
var name = element.name;
|
|
96
96
|
|
|
97
|
-
var x0 = x.invert(
|
|
97
|
+
var x0 = x.invert(mouseX),
|
|
98
98
|
i = helper.bisectDate(data, x0, 1),
|
|
99
99
|
d0 = data[i - 1],
|
|
100
100
|
d1 = data[i];
|
|
@@ -5,15 +5,58 @@
|
|
|
5
5
|
};
|
|
6
6
|
var formatValue = d3.format(",.2f");
|
|
7
7
|
|
|
8
|
+
var humanizeSegment = function (segment) {
|
|
9
|
+
segment = String(segment || "")
|
|
10
|
+
.replace(/[_-]+/g, " ")
|
|
11
|
+
.replace(/\s+/g, " ")
|
|
12
|
+
.trim();
|
|
13
|
+
|
|
14
|
+
if (!segment) {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return segment
|
|
19
|
+
.split(" ")
|
|
20
|
+
.map(function (word) {
|
|
21
|
+
if (/^\d+(\.\d+)?$/.test(word)) {
|
|
22
|
+
return word;
|
|
23
|
+
}
|
|
24
|
+
if (/^[A-Z]{2,3}$/.test(word)) {
|
|
25
|
+
return word;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
29
|
+
})
|
|
30
|
+
.join(" ");
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var formatSeriesName = function (name) {
|
|
34
|
+
return String(name || "")
|
|
35
|
+
.split(/\s*::\s*/)
|
|
36
|
+
.map(function (segment) {
|
|
37
|
+
var parts = segment.split(/\s*:\s*/);
|
|
38
|
+
if (parts.length > 1) {
|
|
39
|
+
return humanizeSegment(parts[0]) + " (" + humanizeSegment(parts.slice(1).join(": ")) + ")";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return humanizeSegment(segment);
|
|
43
|
+
})
|
|
44
|
+
.filter(function (segment) {
|
|
45
|
+
return segment.length > 0;
|
|
46
|
+
})
|
|
47
|
+
.join(" / ");
|
|
48
|
+
};
|
|
49
|
+
|
|
8
50
|
return {
|
|
9
|
-
parseDate: d3.
|
|
51
|
+
parseDate: d3.timeParse("%Y-%m-%d"),
|
|
10
52
|
bisectDate: d3.bisector(function (d) {
|
|
11
53
|
return d.x;
|
|
12
54
|
}).left,
|
|
13
55
|
formatCurrency: formatCurrency,
|
|
14
56
|
formatValue: formatValue,
|
|
57
|
+
formatSeriesName: formatSeriesName,
|
|
15
58
|
formatValueDisplay: function (name, d) {
|
|
16
|
-
return name + ": " + formatValue(d.y); // Add currency boolean on backend later -- formatCurrency(d.y); }
|
|
59
|
+
return formatSeriesName(name) + ": " + formatValue(d.y); // Add currency boolean on backend later -- formatCurrency(d.y); }
|
|
17
60
|
},
|
|
18
61
|
};
|
|
19
62
|
};
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
(function (d3, $, window, document, undefined) {
|
|
2
|
+
function errorMessage(error) {
|
|
3
|
+
if (error && error.responseText) {
|
|
4
|
+
try {
|
|
5
|
+
var response = JSON.parse(error.responseText);
|
|
6
|
+
if (response.message) {
|
|
7
|
+
return response.message;
|
|
8
|
+
}
|
|
9
|
+
} catch (ex) {
|
|
10
|
+
return error.responseText;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return error && error.message ? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderError(message) {
|
|
18
|
+
var escapedMessage = $("<div/>").text(message).html();
|
|
19
|
+
$("#chartAnchor").prepend(
|
|
20
|
+
'<div class="alert alert-danger" role="alert">' + escapedMessage + "</div>"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
2
24
|
$(document).ready(function () {
|
|
3
25
|
if ($("#chartAnchor").length == 0) {
|
|
4
26
|
return;
|
|
5
27
|
}
|
|
6
28
|
|
|
7
|
-
d3.json($("#chartAnchor").data("reports-path")
|
|
29
|
+
d3.json($("#chartAnchor").data("reports-path")).then(function (json) {
|
|
8
30
|
$("#loading-spinner").remove();
|
|
9
31
|
|
|
10
32
|
var renderer = new Kiddo.Renderer("#chartAnchor");
|
|
11
33
|
|
|
12
|
-
if (error) {
|
|
13
|
-
ajaxErrorAlert(error);
|
|
14
|
-
return renderer.noData();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
34
|
var data = json[0];
|
|
18
35
|
|
|
19
36
|
if (
|
|
@@ -49,6 +66,12 @@
|
|
|
49
66
|
console.log(ex);
|
|
50
67
|
renderer.noData();
|
|
51
68
|
}
|
|
69
|
+
}).catch(function (error) {
|
|
70
|
+
$("#loading-spinner").remove();
|
|
71
|
+
|
|
72
|
+
var renderer = new Kiddo.Renderer("#chartAnchor");
|
|
73
|
+
renderError(errorMessage(error));
|
|
74
|
+
return renderer.noData();
|
|
52
75
|
});
|
|
53
76
|
});
|
|
54
77
|
})(d3, jQuery, window, document);
|
|
@@ -329,11 +329,25 @@
|
|
|
329
329
|
|
|
330
330
|
/* app/views/kanaui/reports/index.html.erb */
|
|
331
331
|
|
|
332
|
+
.kanaui-flash-container {
|
|
333
|
+
margin: 1rem 0 1.5rem;
|
|
334
|
+
position: relative;
|
|
335
|
+
z-index: 1;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.kanaui-flash-container .alert {
|
|
339
|
+
margin-bottom: 0.75rem;
|
|
340
|
+
}
|
|
341
|
+
|
|
332
342
|
.kanaui-reports-index .configured-reports {
|
|
333
343
|
max-width: 80rem;
|
|
334
344
|
width: 100%;
|
|
335
345
|
}
|
|
336
346
|
|
|
347
|
+
.kanaui-reports-index .kanaui-report-notice {
|
|
348
|
+
margin-bottom: 1rem;
|
|
349
|
+
}
|
|
350
|
+
|
|
337
351
|
.kanaui-reports-index .configured-reports .configured-reports-header {
|
|
338
352
|
display: flex;
|
|
339
353
|
align-items: start;
|
|
@@ -393,6 +407,11 @@
|
|
|
393
407
|
color: #d92d20 !important;
|
|
394
408
|
}
|
|
395
409
|
|
|
410
|
+
.kanaui-reports-index .disabled-table-button {
|
|
411
|
+
color: #98a2b3 !important;
|
|
412
|
+
cursor: not-allowed;
|
|
413
|
+
}
|
|
414
|
+
|
|
396
415
|
.kanaui-reports-index .edit-button {
|
|
397
416
|
color: #535862 !important;
|
|
398
417
|
}
|
|
@@ -3,6 +3,91 @@
|
|
|
3
3
|
white-space: nowrap;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
.kanaui-field-control {
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: 8px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.kanaui-field-control-top {
|
|
13
|
+
align-items: flex-start;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.kanaui-field-control .form-control {
|
|
17
|
+
flex: 1 1 auto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.kanaui-field-help {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
flex: 0 0 auto;
|
|
25
|
+
width: 22px;
|
|
26
|
+
height: 22px;
|
|
27
|
+
padding: 0;
|
|
28
|
+
color: #6b7280;
|
|
29
|
+
background: transparent;
|
|
30
|
+
border: 0;
|
|
31
|
+
box-shadow: none;
|
|
32
|
+
font-size: 16px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
line-height: 1;
|
|
35
|
+
text-align: center;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.kanaui-field-help:hover,
|
|
40
|
+
.kanaui-field-help:focus {
|
|
41
|
+
color: #111827;
|
|
42
|
+
background: transparent;
|
|
43
|
+
outline: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.kanaui-field-help:focus {
|
|
47
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.24);
|
|
48
|
+
border-radius: 3px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.kanaui-field-tooltip {
|
|
52
|
+
position: absolute;
|
|
53
|
+
z-index: 1070;
|
|
54
|
+
display: none;
|
|
55
|
+
max-width: 360px;
|
|
56
|
+
padding: 10px 12px;
|
|
57
|
+
color: #1f2937;
|
|
58
|
+
background-color: #fff;
|
|
59
|
+
border: 1px solid #d1d5db;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
font-weight: 400;
|
|
64
|
+
line-height: 1.5;
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.kanaui-field-tooltip::before {
|
|
69
|
+
content: "";
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: 12px;
|
|
72
|
+
left: -6px;
|
|
73
|
+
width: 0;
|
|
74
|
+
height: 0;
|
|
75
|
+
border-top: 6px solid transparent;
|
|
76
|
+
border-bottom: 6px solid transparent;
|
|
77
|
+
border-right: 6px solid #fff;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.kanaui-field-tooltip.is-flipped::before {
|
|
81
|
+
left: auto;
|
|
82
|
+
right: -6px;
|
|
83
|
+
border-right: 0;
|
|
84
|
+
border-left: 6px solid #fff;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.kanaui-field-tooltip.is-visible {
|
|
88
|
+
display: block;
|
|
89
|
+
}
|
|
90
|
+
|
|
6
91
|
.sql-query {
|
|
7
92
|
word-break: break-word;
|
|
8
93
|
}
|
|
@@ -11,7 +11,7 @@ module Kanaui
|
|
|
11
11
|
|
|
12
12
|
@raw_name = (params[:name] || '').split('^')[0]
|
|
13
13
|
|
|
14
|
-
@end_date = params[:end_date] ||
|
|
14
|
+
@end_date = params[:end_date] || Time.zone.today.to_s
|
|
15
15
|
|
|
16
16
|
@available_start_dates = start_date_options
|
|
17
17
|
@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'])
|
|
@@ -19,9 +19,13 @@ module Kanaui
|
|
|
19
19
|
@reports = JSON.parse(raw_reports)
|
|
20
20
|
@report = current_report(@reports) || {}
|
|
21
21
|
|
|
22
|
-
# If no report name is provided, redirect to
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# If no report name is provided, redirect to a default report.
|
|
23
|
+
# Prefer the historical default (second report) when present, but fall back
|
|
24
|
+
# to the first report so a single configured report still renders the
|
|
25
|
+
# dashboard controls and chart area.
|
|
26
|
+
default_report = @reports[1] || @reports[0] if @reports.is_a?(Array)
|
|
27
|
+
default_name = default_report&.fetch('reportName', nil).presence
|
|
28
|
+
if @raw_name.blank? && default_name.present?
|
|
25
29
|
query_params = { start_date: @start_date,
|
|
26
30
|
end_date: @end_date,
|
|
27
31
|
name: default_name,
|
|
@@ -29,8 +33,8 @@ module Kanaui
|
|
|
29
33
|
sql_only: params[:sql_only],
|
|
30
34
|
format: params[:format] }
|
|
31
35
|
|
|
32
|
-
query_params[:fake] = params[:fake]
|
|
33
|
-
query_params[:type] = params[:type]
|
|
36
|
+
query_params[:fake] = params[:fake] if params[:fake].present?
|
|
37
|
+
query_params[:type] = params[:type] if params[:type].present?
|
|
34
38
|
|
|
35
39
|
redirect_to dashboard_index_path(query_params) and return
|
|
36
40
|
end
|
|
@@ -46,14 +50,14 @@ module Kanaui
|
|
|
46
50
|
name = query.present? ? "#{params[:name]}#{query}^metric:count" : params[:name]
|
|
47
51
|
query_params = { start_date: @start_date,
|
|
48
52
|
end_date: @end_date,
|
|
49
|
-
name
|
|
53
|
+
name:,
|
|
50
54
|
smooth: params[:smooth],
|
|
51
55
|
sql_only: params[:sql_only],
|
|
52
56
|
format: params[:format] }
|
|
53
57
|
|
|
54
58
|
# Test only
|
|
55
|
-
query_params[:fake] = params[:fake]
|
|
56
|
-
query_params[:type] = params[:type]
|
|
59
|
+
query_params[:fake] = params[:fake] if params[:fake].present?
|
|
60
|
+
query_params[:type] = params[:type] if params[:type].present?
|
|
57
61
|
|
|
58
62
|
redirect_to dashboard_index_path(query_params) and return
|
|
59
63
|
end
|
|
@@ -86,12 +90,12 @@ module Kanaui
|
|
|
86
90
|
respond_to do |fmt|
|
|
87
91
|
fmt.csv do
|
|
88
92
|
filename = params[:name]
|
|
89
|
-
|
|
93
|
+
if params[:start_date].present?
|
|
90
94
|
filename += "_#{params[:start_date]}"
|
|
91
|
-
filename += "-#{params[:end_date]}"
|
|
95
|
+
filename += "-#{params[:end_date]}" if params[:end_date].present?
|
|
92
96
|
end
|
|
93
97
|
filename += '.csv'
|
|
94
|
-
send_data(raw_reports, filename:
|
|
98
|
+
send_data(raw_reports, filename:)
|
|
95
99
|
end
|
|
96
100
|
fmt.all { render json: reports }
|
|
97
101
|
end
|
|
@@ -146,10 +150,10 @@ module Kanaui
|
|
|
146
150
|
filters.each do |k, v|
|
|
147
151
|
next if v.blank?
|
|
148
152
|
|
|
149
|
-
filter_query
|
|
153
|
+
filter_query.presence&.<<('%26')
|
|
150
154
|
filter_query << "(#{k}=#{v.join("|#{k}=")})"
|
|
151
155
|
end
|
|
152
|
-
query << "^filter:#{filter_query}"
|
|
156
|
+
query << "^filter:#{filter_query}" if filter_query.present?
|
|
153
157
|
|
|
154
158
|
groups.each do |k, v|
|
|
155
159
|
next if v.blank?
|
|
@@ -27,8 +27,27 @@ module Kanaui
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
rescue_from(KillBillClient::API::ResponseError) do |killbill_exception|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
error_message = I18n.t('kanaui.errors.killbill_communication', error: as_string(killbill_exception))
|
|
31
|
+
|
|
32
|
+
if json_request?
|
|
33
|
+
render json: { message: error_message }, status: killbill_exception.response.code.to_i
|
|
34
|
+
else
|
|
35
|
+
flash[:error] = error_message
|
|
36
|
+
# Avoid redirect loops when the dashboard itself cannot be loaded.
|
|
37
|
+
if controller_name == 'dashboard' && action_name == 'index'
|
|
38
|
+
render plain: error_message, status: killbill_exception.response.code.to_i
|
|
39
|
+
else
|
|
40
|
+
redirect_to dashboard_index_path
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def json_request?
|
|
46
|
+
request.format.json? || params[:format] == 'json' || dashboard_reports_json_request?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def dashboard_reports_json_request?
|
|
50
|
+
controller_name == 'dashboard' && action_name == 'reports' && params[:format].blank?
|
|
32
51
|
end
|
|
33
52
|
|
|
34
53
|
def as_string(e)
|
|
@@ -4,48 +4,57 @@ module Kanaui
|
|
|
4
4
|
class ReportsController < Kanaui::EngineController
|
|
5
5
|
def index
|
|
6
6
|
@reports = JSON.parse(Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient)).map(&:deep_symbolize_keys)
|
|
7
|
+
@report_notice = report_notice_from_flash
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def new
|
|
10
11
|
@report = {}
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def create
|
|
14
|
-
Kanaui::DashboardHelper::DashboardApi.create_report(report_from_params.to_json, options_for_klient)
|
|
15
|
-
|
|
16
|
-
flash[:notice] = 'Report successfully created'
|
|
17
|
-
redirect_to action: :index
|
|
18
|
-
end
|
|
19
|
-
|
|
20
14
|
def edit
|
|
21
15
|
@report = JSON.parse(Kanaui::DashboardHelper::DashboardApi.available_reports(options_for_klient))
|
|
22
16
|
.find { |x| x['reportName'] == params.require(:id) }
|
|
23
17
|
.deep_symbolize_keys
|
|
24
18
|
end
|
|
25
19
|
|
|
20
|
+
def create
|
|
21
|
+
Kanaui::DashboardHelper::DashboardApi.create_report(report_from_params.to_json, options_for_klient)
|
|
22
|
+
|
|
23
|
+
redirect_to_index_with_notice(:created)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
26
|
def update
|
|
27
27
|
Kanaui::DashboardHelper::DashboardApi.update_report(params.require(:id), report_from_params.to_json, options_for_klient)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
redirect_to action: :index
|
|
29
|
+
redirect_to_index_with_notice(:updated)
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def refresh
|
|
34
33
|
Kanaui::DashboardHelper::DashboardApi.refresh_report(params.require(:id), options_for_klient)
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
redirect_to action: :index
|
|
35
|
+
redirect_to_index_with_notice(:refresh_scheduled)
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
def destroy
|
|
41
39
|
Kanaui::DashboardHelper::DashboardApi.delete_report(params.require(:id), options_for_klient)
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
redirect_to action: :index
|
|
41
|
+
redirect_to_index_with_notice(:deleted)
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
private
|
|
48
45
|
|
|
46
|
+
def report_notice_from_flash
|
|
47
|
+
notice_key = flash[:report_notice].presence&.to_s
|
|
48
|
+
return nil unless %w[created updated refresh_scheduled deleted].include?(notice_key)
|
|
49
|
+
|
|
50
|
+
I18n.t("kanaui.reports.notices.#{notice_key}", default: nil)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def redirect_to_index_with_notice(notice_key)
|
|
54
|
+
flash[:report_notice] = notice_key
|
|
55
|
+
redirect_to action: :index
|
|
56
|
+
end
|
|
57
|
+
|
|
49
58
|
def report_from_params
|
|
50
59
|
{
|
|
51
60
|
reportName: params[:report_name],
|