kanaui 5.0.1 → 5.0.2
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/charts/line_chart.js +11 -17
- data/app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js +21 -11
- data/app/assets/javascripts/kanaui/kiddo/helper.js +52 -8
- data/app/assets/javascripts/kanaui/kiddo/settings.js +1 -0
- data/app/assets/stylesheets/kanaui/kanaui.css +84 -18
- data/app/controllers/kanaui/dashboard_controller.rb +2 -3
- data/app/helpers/kanaui/dashboard_helper.rb +9 -3
- data/app/views/kanaui/dashboard/index.html.erb +40 -14
- data/app/views/kanaui/reports/_form.html.erb +4 -10
- data/config/locales/en.bootstrap.yml +2 -2
- data/lib/kanaui/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c74b8f72b208b13b20e079654c00f793b3d0c663cd33f9dbb4957aa6fd420e6
|
|
4
|
+
data.tar.gz: 859e32a01514363bb64f4c3772c0d6b70b2ed72fb0daa0d4bda8facef7bba877
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52219b23e1cdd624809a26bc71ab8ea903d3c649ff5e5e6d3fe74538c850341680a189d9e50f1c19097df907430b49dc27009f63319a27f110635b3a4afa6589
|
|
7
|
+
data.tar.gz: 2606b237d12c8e5f4ecf5bcca19bed63ef34eb38c0adb862f4924f3e3d6afe898fdccf53474d328d49cbaa9026d38253f88e4306f5049937c9fd46ccb4601336
|
|
@@ -68,26 +68,24 @@
|
|
|
68
68
|
return dataset.name;
|
|
69
69
|
}));
|
|
70
70
|
|
|
71
|
-
// Create legend container
|
|
71
|
+
// Create legend container to the right of the chart area
|
|
72
72
|
var legendContainer = svg
|
|
73
73
|
.append("g")
|
|
74
74
|
.attr("class", "chart-legend")
|
|
75
|
-
.attr("transform", "translate(" + (self.width
|
|
75
|
+
.attr("transform", "translate(" + (self.width + self.margin_left + 15) + ", 10)");
|
|
76
76
|
|
|
77
77
|
// Calculate total values for legend
|
|
78
78
|
var legendData = datasets.map(function (dataset, index) {
|
|
79
79
|
var latestValue = dataset.values[dataset.values.length - 1];
|
|
80
|
-
var totalCount = dataset.values.length;
|
|
81
80
|
return {
|
|
82
81
|
name: dataset.name,
|
|
83
82
|
value: latestValue ? latestValue.y : 0,
|
|
84
|
-
count: totalCount,
|
|
85
83
|
color: self.color(dataset.name),
|
|
86
84
|
index: index,
|
|
87
85
|
};
|
|
88
86
|
});
|
|
89
87
|
|
|
90
|
-
// Create legend items
|
|
88
|
+
// Create legend items as a vertical column
|
|
91
89
|
var legendItems = legendContainer
|
|
92
90
|
.selectAll(".legend-item")
|
|
93
91
|
.data(legendData)
|
|
@@ -95,34 +93,30 @@
|
|
|
95
93
|
.append("g")
|
|
96
94
|
.attr("class", "legend-item");
|
|
97
95
|
|
|
98
|
-
var
|
|
99
|
-
legendItems.each(function (d
|
|
96
|
+
var yOffset = 0;
|
|
97
|
+
legendItems.each(function (d) {
|
|
100
98
|
var legendItem = d3.select(this);
|
|
101
99
|
|
|
102
|
-
// Add colored circle
|
|
103
100
|
legendItem
|
|
104
101
|
.append("circle")
|
|
105
|
-
.attr("cx",
|
|
106
|
-
.attr("cy",
|
|
102
|
+
.attr("cx", 6)
|
|
103
|
+
.attr("cy", yOffset)
|
|
107
104
|
.attr("r", 6)
|
|
108
105
|
.style("fill", d.color);
|
|
109
106
|
|
|
110
|
-
// Add text label
|
|
111
107
|
var labelText =
|
|
112
|
-
helper.formatSeriesName(d.name
|
|
108
|
+
helper.formatSeriesName(d.name, self.reportName) + ": " + helper.formatValue(d.value);
|
|
113
109
|
legendItem
|
|
114
110
|
.append("text")
|
|
115
|
-
.attr("x",
|
|
116
|
-
.attr("y",
|
|
111
|
+
.attr("x", 18)
|
|
112
|
+
.attr("y", yOffset)
|
|
117
113
|
.attr("dy", "0.35em")
|
|
118
114
|
.style("font-size", "0.875rem")
|
|
119
115
|
.style("font-weight", "500")
|
|
120
116
|
.style("fill", "#6B7280")
|
|
121
117
|
.text(labelText);
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
var textWidth = this.getBBox().width;
|
|
125
|
-
xOffset += textWidth + 40; // Add spacing between items
|
|
119
|
+
yOffset += 22;
|
|
126
120
|
});
|
|
127
121
|
|
|
128
122
|
// Render data lines
|
|
@@ -59,15 +59,16 @@
|
|
|
59
59
|
var infoBox = info.node().getBBox();
|
|
60
60
|
var infoTitleBox = infoTitle.node().getBBox();
|
|
61
61
|
var margin = 40;
|
|
62
|
+
var minWidth = Math.max(box.width, infoTitleBox.width) + margin;
|
|
62
63
|
|
|
63
64
|
info.attr("height", infoBox.height + box.height + 7);
|
|
64
|
-
if (infoBox.width <
|
|
65
|
-
info.attr("width",
|
|
66
|
-
infoTitleBg.attr("width",
|
|
65
|
+
if (infoBox.width < minWidth) {
|
|
66
|
+
info.attr("width", minWidth);
|
|
67
|
+
infoTitleBg.attr("width", minWidth);
|
|
67
68
|
|
|
68
69
|
$("#mouseover_canvas #info-title").attr(
|
|
69
70
|
"dx",
|
|
70
|
-
|
|
71
|
+
minWidth / 2 - infoTitleBox.width / 2
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
};
|
|
@@ -81,7 +82,9 @@
|
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
function mousemove(event) {
|
|
84
|
-
var
|
|
85
|
+
var pointer = d3.pointer(event, this);
|
|
86
|
+
var mouseX = pointer[0];
|
|
87
|
+
var mouseY = pointer[1];
|
|
85
88
|
|
|
86
89
|
$("#mouseover_canvas .chart_values").detach().remove();
|
|
87
90
|
$("#mouseover_canvas .chart_circles").detach().remove();
|
|
@@ -115,11 +118,7 @@
|
|
|
115
118
|
.attr("cy", y(d.y))
|
|
116
119
|
.style("fill", self.color(name));
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
canvas.attr("transform", "translate(" + canvasPosition + ",0)");
|
|
121
|
-
|
|
122
|
-
canvas.select("#info-title").text(d.date);
|
|
121
|
+
canvas.select("#info-title").text(helper.formatDate(d.date));
|
|
123
122
|
|
|
124
123
|
elementsForLegend.push({ element: element, d: d });
|
|
125
124
|
});
|
|
@@ -150,11 +149,22 @@
|
|
|
150
149
|
.text(
|
|
151
150
|
element.d === undefined
|
|
152
151
|
? element.element.name
|
|
153
|
-
: helper.formatValueDisplay(element.element.name, element.d)
|
|
152
|
+
: helper.formatValueDisplay(element.element.name, element.d, self.reportName)
|
|
154
153
|
);
|
|
155
154
|
|
|
156
155
|
addInfoDimensions(text);
|
|
157
156
|
});
|
|
157
|
+
|
|
158
|
+
// Position tooltip to follow the cursor; flip left when near the right edge.
|
|
159
|
+
var tooltipOffset = 15;
|
|
160
|
+
var tooltipWidth = info.node().getBBox().width;
|
|
161
|
+
var tooltipHeight = info.node().getBBox().height;
|
|
162
|
+
var localX = (mouseX + tooltipOffset + tooltipWidth > self.width)
|
|
163
|
+
? mouseX - tooltipWidth - tooltipOffset
|
|
164
|
+
: mouseX + tooltipOffset;
|
|
165
|
+
var canvasX = localX + self.margin_left;
|
|
166
|
+
var canvasY = Math.max(0, mouseY - 150);
|
|
167
|
+
canvas.attr("transform", "translate(" + canvasX + "," + canvasY + ")");
|
|
158
168
|
}
|
|
159
169
|
},
|
|
160
170
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
(function (Kiddo, d3) {
|
|
2
2
|
Kiddo.Helper = function () {
|
|
3
|
+
var formatValue = function (d) {
|
|
4
|
+
return d % 1 === 0 ? d3.format(",d")(d) : d3.format(",.2f")(d);
|
|
5
|
+
};
|
|
3
6
|
var formatCurrency = function (d) {
|
|
4
7
|
return "$" + formatValue(d);
|
|
5
8
|
};
|
|
6
|
-
var formatValue = d3.format(",.2f");
|
|
7
9
|
|
|
8
10
|
var humanizeSegment = function (segment) {
|
|
9
11
|
segment = String(segment || "")
|
|
@@ -30,33 +32,75 @@
|
|
|
30
32
|
.join(" ");
|
|
31
33
|
};
|
|
32
34
|
|
|
33
|
-
var formatSeriesName = function (name) {
|
|
35
|
+
var formatSeriesName = function (name, reportName) {
|
|
34
36
|
return String(name || "")
|
|
35
37
|
.split(/\s*::\s*/)
|
|
38
|
+
.filter(function (segment) {
|
|
39
|
+
// Remove numeric-only segments (tenant record id)
|
|
40
|
+
return !/^\d+$/.test(segment.trim());
|
|
41
|
+
})
|
|
36
42
|
.map(function (segment) {
|
|
37
43
|
var parts = segment.split(/\s*:\s*/);
|
|
38
44
|
if (parts.length > 1) {
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
var qualifier = humanizeSegment(parts.slice(1).join(": "));
|
|
46
|
+
var label = (reportName && parts[0].trim().toLowerCase() === "count")
|
|
47
|
+
? reportName
|
|
48
|
+
: humanizeSegment(parts[0]);
|
|
41
49
|
|
|
50
|
+
// For count-based reports, a numeric qualifier is usually tenant id
|
|
51
|
+
// and should not be displayed in UI labels.
|
|
52
|
+
if (reportName && parts[0].trim().toLowerCase() === "count" && /^\d+$/.test(qualifier)) {
|
|
53
|
+
return label;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return label + " (" + qualifier + ")";
|
|
57
|
+
}
|
|
42
58
|
return humanizeSegment(segment);
|
|
43
59
|
})
|
|
44
60
|
.filter(function (segment) {
|
|
45
61
|
return segment.length > 0;
|
|
46
62
|
})
|
|
47
|
-
.join("
|
|
63
|
+
.join(" : ");
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Extracts just the qualifier part (e.g. "EUR" from "count: EUR :: 1") for compact tooltip labels
|
|
67
|
+
var formatSeriesLabel = function (name) {
|
|
68
|
+
var segments = String(name || "")
|
|
69
|
+
.split(/\s*::\s*/)
|
|
70
|
+
.filter(function (segment) {
|
|
71
|
+
return !/^\d+$/.test(segment.trim());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (segments.length === 0) return humanizeSegment(name);
|
|
75
|
+
|
|
76
|
+
var parts = segments[0].split(/\s*:\s*/);
|
|
77
|
+
if (parts.length > 1) {
|
|
78
|
+
return humanizeSegment(parts.slice(1).join(": "));
|
|
79
|
+
}
|
|
80
|
+
return humanizeSegment(segments[0]);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
var parseDateFn = d3.timeParse("%Y-%m-%d");
|
|
84
|
+
var formatDateFn = d3.timeFormat("%b %d, %Y");
|
|
85
|
+
var formatDate = function (dateStr) {
|
|
86
|
+
var parsed = parseDateFn(dateStr);
|
|
87
|
+
return parsed ? formatDateFn(parsed) : dateStr;
|
|
48
88
|
};
|
|
49
89
|
|
|
50
90
|
return {
|
|
51
|
-
parseDate:
|
|
91
|
+
parseDate: parseDateFn,
|
|
52
92
|
bisectDate: d3.bisector(function (d) {
|
|
53
93
|
return d.x;
|
|
54
94
|
}).left,
|
|
55
95
|
formatCurrency: formatCurrency,
|
|
56
96
|
formatValue: formatValue,
|
|
57
97
|
formatSeriesName: formatSeriesName,
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
formatDate: formatDate,
|
|
99
|
+
formatValueDisplay: function (name, d, reportName) {
|
|
100
|
+
var seriesLabel = reportName
|
|
101
|
+
? formatSeriesName(name, reportName)
|
|
102
|
+
: formatSeriesLabel(name);
|
|
103
|
+
return seriesLabel + ": " + formatValue(d.y);
|
|
60
104
|
},
|
|
61
105
|
};
|
|
62
106
|
};
|
|
@@ -103,11 +103,40 @@
|
|
|
103
103
|
|
|
104
104
|
.kenui-analytics-dashboard-index .well ul {
|
|
105
105
|
list-style-type: none;
|
|
106
|
-
margin
|
|
106
|
+
margin: 0.625rem 0 0;
|
|
107
107
|
background: #fafafa;
|
|
108
|
-
width:
|
|
109
|
-
padding:
|
|
108
|
+
width: 100%;
|
|
109
|
+
padding: 1rem 1.25rem;
|
|
110
110
|
border-radius: 0.5rem;
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: 0.5rem 1.25rem;
|
|
114
|
+
align-items: flex-start;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Smoothing option links (Weekly/Monthly average/sum) render as inline pills */
|
|
118
|
+
.kenui-analytics-dashboard-index .well > ul > li.smoothing-option {
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.kenui-analytics-dashboard-index .well > ul > li.smoothing-option > a {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
background: #ffffff;
|
|
126
|
+
border: 0.0625rem solid #d5d7da;
|
|
127
|
+
border-radius: 999px;
|
|
128
|
+
padding: 0.375rem 0.75rem;
|
|
129
|
+
text-decoration: none;
|
|
130
|
+
color: #414651;
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
font-size: 0.8125rem;
|
|
133
|
+
line-height: 1.25rem;
|
|
134
|
+
transition: all 0.15s ease-in-out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.kenui-analytics-dashboard-index .well > ul > li.smoothing-option > a:hover {
|
|
138
|
+
border-color: #1570ef;
|
|
139
|
+
color: #1570ef;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
.kenui-analytics-dashboard-index .well ul li a {
|
|
@@ -168,6 +197,26 @@
|
|
|
168
197
|
background: #ffffff;
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
.kenui-analytics-dashboard-index .chart-title-container {
|
|
201
|
+
width: auto;
|
|
202
|
+
height: auto;
|
|
203
|
+
display: block;
|
|
204
|
+
justify-content: unset;
|
|
205
|
+
align-items: unset;
|
|
206
|
+
position: static;
|
|
207
|
+
margin-top: 15px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.kenui-analytics-dashboard-index .chart-title {
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
font-size: 1.25rem;
|
|
213
|
+
line-height: 1.75rem;
|
|
214
|
+
color: #111827;
|
|
215
|
+
white-space: normal;
|
|
216
|
+
transform: none;
|
|
217
|
+
text-align: left;
|
|
218
|
+
}
|
|
219
|
+
|
|
171
220
|
.kenui-analytics-dashboard-index #chartAnchor svg {
|
|
172
221
|
background: #ffffff;
|
|
173
222
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
@@ -619,20 +668,32 @@
|
|
|
619
668
|
.kenui-analytics-dashboard-index .advanced-controls {
|
|
620
669
|
margin: 0.625rem 0;
|
|
621
670
|
padding: 0;
|
|
671
|
+
flex: 1 1 100%;
|
|
622
672
|
}
|
|
623
673
|
|
|
624
674
|
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
|
|
625
|
-
background: #
|
|
675
|
+
background: #ffffff;
|
|
626
676
|
border: 0.0625rem solid #e9eaeb;
|
|
627
677
|
border-radius: 0.5rem;
|
|
628
|
-
padding:
|
|
678
|
+
padding: 1rem 1.25rem;
|
|
629
679
|
margin-top: 0.625rem;
|
|
680
|
+
display: grid;
|
|
681
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
682
|
+
gap: 1rem 1.5rem;
|
|
683
|
+
align-items: start;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal > .form-group:last-of-type {
|
|
687
|
+
grid-column: 1 / -1;
|
|
688
|
+
justify-self: end;
|
|
689
|
+
margin-top: 0;
|
|
630
690
|
}
|
|
631
691
|
|
|
632
692
|
.kenui-analytics-dashboard-index .advanced-controls fieldset {
|
|
633
693
|
border: none;
|
|
634
694
|
margin: 0;
|
|
635
695
|
padding: 0;
|
|
696
|
+
min-width: 0;
|
|
636
697
|
}
|
|
637
698
|
|
|
638
699
|
.kenui-analytics-dashboard-index .advanced-controls legend {
|
|
@@ -647,10 +708,10 @@
|
|
|
647
708
|
}
|
|
648
709
|
|
|
649
710
|
.kenui-analytics-dashboard-index .advanced-controls .form-group {
|
|
650
|
-
margin-bottom:
|
|
711
|
+
margin-bottom: 0.75rem;
|
|
651
712
|
display: flex;
|
|
652
713
|
flex-direction: column;
|
|
653
|
-
align-items:
|
|
714
|
+
align-items: stretch;
|
|
654
715
|
}
|
|
655
716
|
|
|
656
717
|
.kenui-analytics-dashboard-index .advanced-controls .control-label {
|
|
@@ -675,7 +736,7 @@
|
|
|
675
736
|
|
|
676
737
|
.kenui-analytics-dashboard-index .advanced-controls select.form-control {
|
|
677
738
|
height: 6rem;
|
|
678
|
-
min-width:
|
|
739
|
+
min-width: 0;
|
|
679
740
|
width: 100%;
|
|
680
741
|
}
|
|
681
742
|
|
|
@@ -715,18 +776,19 @@
|
|
|
715
776
|
}
|
|
716
777
|
|
|
717
778
|
/* Current Analytics Query Section */
|
|
718
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
719
|
-
background: #
|
|
779
|
+
.kenui-analytics-dashboard-index .well ul li.query-block {
|
|
780
|
+
background: #ffffff;
|
|
720
781
|
border: 0.0625rem solid #e9eaeb;
|
|
721
782
|
border-radius: 0.5rem;
|
|
722
|
-
padding: 0.
|
|
723
|
-
margin: 0
|
|
783
|
+
padding: 0.75rem 1rem;
|
|
784
|
+
margin: 0;
|
|
724
785
|
display: flex;
|
|
725
786
|
flex-direction: column;
|
|
726
|
-
gap: 0.
|
|
787
|
+
gap: 0.5rem;
|
|
788
|
+
flex: 1 1 100%;
|
|
727
789
|
}
|
|
728
790
|
|
|
729
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
791
|
+
.kenui-analytics-dashboard-index .well ul li.query-block a {
|
|
730
792
|
color: #1570ef;
|
|
731
793
|
text-decoration: none;
|
|
732
794
|
font-weight: 500;
|
|
@@ -739,12 +801,12 @@
|
|
|
739
801
|
transition: color 0.15s ease-in-out;
|
|
740
802
|
}
|
|
741
803
|
|
|
742
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
804
|
+
.kenui-analytics-dashboard-index .well ul li.query-block a:hover {
|
|
743
805
|
color: #0d5bb8;
|
|
744
806
|
text-decoration: underline;
|
|
745
807
|
}
|
|
746
808
|
|
|
747
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
809
|
+
.kenui-analytics-dashboard-index .well ul li.query-block pre {
|
|
748
810
|
background: #ffffff;
|
|
749
811
|
border: 0.0625rem solid #d5d7da;
|
|
750
812
|
border-radius: 0.375rem;
|
|
@@ -760,7 +822,7 @@
|
|
|
760
822
|
box-shadow: 0 0.0625rem 0.1875rem rgba(0, 0, 0, 0.1);
|
|
761
823
|
}
|
|
762
824
|
|
|
763
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
825
|
+
.kenui-analytics-dashboard-index .well ul li.query-block .query-label {
|
|
764
826
|
font-weight: 500;
|
|
765
827
|
font-size: 0.875rem;
|
|
766
828
|
line-height: 1.25rem;
|
|
@@ -770,6 +832,10 @@
|
|
|
770
832
|
|
|
771
833
|
/* Responsive adjustments for smaller screens */
|
|
772
834
|
@media (max-width: 768px) {
|
|
835
|
+
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
|
|
836
|
+
grid-template-columns: 1fr;
|
|
837
|
+
}
|
|
838
|
+
|
|
773
839
|
.kenui-analytics-dashboard-index .advanced-controls .form-group {
|
|
774
840
|
flex-direction: column;
|
|
775
841
|
align-items: flex-start;
|
|
@@ -785,7 +851,7 @@
|
|
|
785
851
|
width: 100%;
|
|
786
852
|
}
|
|
787
853
|
|
|
788
|
-
.kenui-analytics-dashboard-index .well ul li
|
|
854
|
+
.kenui-analytics-dashboard-index .well ul li.query-block pre {
|
|
789
855
|
font-size: 0.75rem;
|
|
790
856
|
padding: 0.5rem 0.75rem;
|
|
791
857
|
}
|
|
@@ -146,7 +146,7 @@ module Kanaui
|
|
|
146
146
|
groups[field_name] = params["group_#{field_name}"]
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
filter_query = ''
|
|
149
|
+
filter_query = +''
|
|
150
150
|
filters.each do |k, v|
|
|
151
151
|
next if v.blank?
|
|
152
152
|
|
|
@@ -158,8 +158,7 @@ module Kanaui
|
|
|
158
158
|
groups.each do |k, v|
|
|
159
159
|
next if v.blank?
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
query << "^dimension:#{k}(#{v.join('|')}|-)"
|
|
161
|
+
query << "^dimension:#{k}(#{v.join('|')})"
|
|
163
162
|
end
|
|
164
163
|
|
|
165
164
|
# Template variables
|
|
@@ -18,9 +18,15 @@ module Kanaui
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def reports(start_date, end_date, name, smooth, sql_only, format, options = {})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
query = {
|
|
22
|
+
'format' => format,
|
|
23
|
+
'startDate' => start_date,
|
|
24
|
+
'endDate' => end_date,
|
|
25
|
+
'name' => name
|
|
26
|
+
}
|
|
27
|
+
query['smooth'] = smooth if smooth
|
|
28
|
+
query['sqlOnly'] = 'true' if sql_only.present?
|
|
29
|
+
path = "#{KILLBILL_ANALYTICS_PREFIX}/reports?#{query.to_query}"
|
|
24
30
|
response = KillBillClient::API.get path, {}, options
|
|
25
31
|
response.body
|
|
26
32
|
end
|
|
@@ -150,6 +150,11 @@
|
|
|
150
150
|
<div class="">
|
|
151
151
|
<% if params[:name] %>
|
|
152
152
|
|
|
153
|
+
<div class="chart-title-container mb-2">
|
|
154
|
+
<h2 class="chart-title" id="chart-title">
|
|
155
|
+
<%= @report['reportPrettyName'] %>
|
|
156
|
+
</h2>
|
|
157
|
+
</div>
|
|
153
158
|
<div id="date-controls" style="display: none;">
|
|
154
159
|
<div class="custom-tabs my-4">
|
|
155
160
|
<% @available_start_dates.each do |key, value| %>
|
|
@@ -158,14 +163,7 @@
|
|
|
158
163
|
</div>
|
|
159
164
|
</div>
|
|
160
165
|
<div id="loading-spinner"></div>
|
|
161
|
-
<div
|
|
162
|
-
<div class="chart-title-container">
|
|
163
|
-
<h2 class="chart-title" id="chart-title">
|
|
164
|
-
<%= @raw_name.titleize %>
|
|
165
|
-
</h2>
|
|
166
|
-
</div>
|
|
167
|
-
<div id="chartAnchor" data-reports-path="<%= kanaui_engine.reports_path(params.to_h) %>" class="w-100 h-100"></div>
|
|
168
|
-
</div>
|
|
166
|
+
<div id="chartAnchor" data-reports-path="<%= kanaui_engine.reports_path(params.to_h) %>" data-report-name="<%= @report['reportPrettyName'] %>" class="w-100"></div>
|
|
169
167
|
|
|
170
168
|
<hr>
|
|
171
169
|
|
|
@@ -207,16 +205,16 @@
|
|
|
207
205
|
<% at_least_two_months = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_month - 1.month > params[:start_date].to_date) %>
|
|
208
206
|
<% at_least_two_weeks = params[:start_date].blank? || params[:end_date].blank? || (params[:end_date].to_date.beginning_of_week - 1.week > params[:start_date].to_date) %>
|
|
209
207
|
<% if params[:smooth] != 'AVERAGE_WEEKLY' && at_least_two_weeks %>
|
|
210
|
-
<li><%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %></li>
|
|
208
|
+
<li class="smoothing-option"><%= link_to 'Weekly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_WEEKLY')) %></li>
|
|
211
209
|
<% end %>
|
|
212
210
|
<% if params[:smooth] != 'AVERAGE_MONTHLY' && at_least_two_months %>
|
|
213
|
-
<li><%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %></li>
|
|
211
|
+
<li class="smoothing-option"><%= link_to 'Monthly average', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'AVERAGE_MONTHLY')) %></li>
|
|
214
212
|
<% end %>
|
|
215
213
|
<% if params[:smooth] != 'SUM_WEEKLY' && at_least_two_weeks %>
|
|
216
|
-
<li><%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %></li>
|
|
214
|
+
<li class="smoothing-option"><%= link_to 'Weekly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_WEEKLY')) %></li>
|
|
217
215
|
<% end %>
|
|
218
216
|
<% if params[:smooth] != 'SUM_MONTHLY' && at_least_two_months %>
|
|
219
|
-
<li><%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %></li>
|
|
217
|
+
<li class="smoothing-option"><%= link_to 'Monthly sum', kanaui_engine.dashboard_index_path(params.to_h.merge(:smooth => 'SUM_MONTHLY')) %></li>
|
|
220
218
|
<% end %>
|
|
221
219
|
<% end %>
|
|
222
220
|
<% filter_fields = ((@report['schema'] || {})['fields'] || []).select { |field| !field['distinctValues'].blank? && field['dataType'] =~ /char/ } # To ignore tenant_record_id %>
|
|
@@ -259,14 +257,14 @@
|
|
|
259
257
|
</div>
|
|
260
258
|
<% end %>
|
|
261
259
|
</li>
|
|
262
|
-
<li>
|
|
260
|
+
<li class="query-block">
|
|
263
261
|
<div class="query-label">
|
|
264
262
|
Current Analytics query: <%= link_to '<i class="fa fa-question-circle"></i>'.html_safe, 'http://docs.killbill.io/latest/userguide_analytics.html#_dashboard_api', :target => '_blank' %>
|
|
265
263
|
</div>
|
|
266
264
|
<pre><%= params[:name] -%></pre>
|
|
267
265
|
</li>
|
|
268
266
|
<% end %>
|
|
269
|
-
<li><%= link_to t('kanaui.dashboard.advanced_controls.sql_query'), kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %></li>
|
|
267
|
+
<li class="smoothing-option"><%= link_to t('kanaui.dashboard.advanced_controls.sql_query'), kanaui_engine.reports_path(params.to_h.merge(:sql_only => true)) %></li>
|
|
270
268
|
</ul>
|
|
271
269
|
</div>
|
|
272
270
|
</div>
|
|
@@ -318,4 +316,32 @@
|
|
|
318
316
|
// Safe to navigate
|
|
319
317
|
window.location.href = url;
|
|
320
318
|
}
|
|
319
|
+
|
|
320
|
+
// Persist the open/closed state of #advanced-controls across page reloads
|
|
321
|
+
(function () {
|
|
322
|
+
var STORAGE_KEY = 'kanaui.advancedControls.open';
|
|
323
|
+
var panel = document.getElementById('advanced-controls');
|
|
324
|
+
if (!panel) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
var toggle = document.querySelector('[data-bs-toggle="collapse"][href="#advanced-controls"]');
|
|
328
|
+
|
|
329
|
+
// Restore previous state before Bootstrap initializes
|
|
330
|
+
try {
|
|
331
|
+
if (localStorage.getItem(STORAGE_KEY) === '1') {
|
|
332
|
+
panel.classList.add('show');
|
|
333
|
+
if (toggle) {
|
|
334
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
335
|
+
toggle.classList.remove('collapsed');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (e) { /* localStorage may be unavailable */ }
|
|
339
|
+
|
|
340
|
+
panel.addEventListener('shown.bs.collapse', function () {
|
|
341
|
+
try { localStorage.setItem(STORAGE_KEY, '1'); } catch (e) {}
|
|
342
|
+
});
|
|
343
|
+
panel.addEventListener('hidden.bs.collapse', function () {
|
|
344
|
+
try { localStorage.setItem(STORAGE_KEY, '0'); } catch (e) {}
|
|
345
|
+
});
|
|
346
|
+
})();
|
|
321
347
|
<% end %>
|
|
@@ -128,13 +128,7 @@
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
document.querySelectorAll('[data-kanaui-tooltip]').forEach(function (el) {
|
|
131
|
-
el.addEventListener('
|
|
132
|
-
event.stopPropagation();
|
|
133
|
-
if (activeTooltipTrigger === el && tooltip.classList.contains('is-visible')) {
|
|
134
|
-
hideTooltip();
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
131
|
+
el.addEventListener('mouseenter', function () {
|
|
138
132
|
var rect = el.getBoundingClientRect();
|
|
139
133
|
var left = window.pageXOffset + rect.right + 12;
|
|
140
134
|
var top = window.pageYOffset + rect.top - 2;
|
|
@@ -153,10 +147,10 @@
|
|
|
153
147
|
tooltip.classList.add('is-flipped');
|
|
154
148
|
}
|
|
155
149
|
});
|
|
156
|
-
});
|
|
157
150
|
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
el.addEventListener('mouseleave', function () {
|
|
152
|
+
hideTooltip();
|
|
153
|
+
});
|
|
160
154
|
});
|
|
161
155
|
|
|
162
156
|
document.addEventListener('keydown', function (event) {
|
|
@@ -37,7 +37,7 @@ en:
|
|
|
37
37
|
source_query: "SQL Query"
|
|
38
38
|
source_query_help: "Don't add a trailing ;"
|
|
39
39
|
source_configuration_error: "Either Source Table Name or SQL Query is required."
|
|
40
|
-
source_name: "
|
|
40
|
+
source_name: "Remote Database Name"
|
|
41
41
|
source_name_help: "Must match a database configuration entry in the Analytics plugin"
|
|
42
42
|
tooltips:
|
|
43
43
|
report_name: "Unique identifier for your report."
|
|
@@ -59,7 +59,7 @@ en:
|
|
|
59
59
|
pretty_name: "Pretty Name"
|
|
60
60
|
type: "Type"
|
|
61
61
|
source: "Source"
|
|
62
|
-
source_name: "
|
|
62
|
+
source_name: "Remote Database Name"
|
|
63
63
|
source_query: "Source Query"
|
|
64
64
|
refresh_procedure: "Refresh Procedure"
|
|
65
65
|
refresh_frequency: "Refresh Frequency"
|
data/lib/kanaui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kanaui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.0.
|
|
4
|
+
version: 5.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kill Bill core team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bootstrap-datepicker-rails
|