kanaui 5.0.2 → 5.0.3

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
  SHA256:
3
- metadata.gz: 0c74b8f72b208b13b20e079654c00f793b3d0c663cd33f9dbb4957aa6fd420e6
4
- data.tar.gz: 859e32a01514363bb64f4c3772c0d6b70b2ed72fb0daa0d4bda8facef7bba877
3
+ metadata.gz: 42c66ccd44ece7ec950473fb78babede09820bc172266b706dae65bc8c6a918a
4
+ data.tar.gz: 479b475c455f7206e5b6dc9cee937d2233024fb6315ba45707f849026ed492b0
5
5
  SHA512:
6
- metadata.gz: 52219b23e1cdd624809a26bc71ab8ea903d3c649ff5e5e6d3fe74538c850341680a189d9e50f1c19097df907430b49dc27009f63319a27f110635b3a4afa6589
7
- data.tar.gz: 2606b237d12c8e5f4ecf5bcca19bed63ef34eb38c0adb862f4924f3e3d6afe898fdccf53474d328d49cbaa9026d38253f88e4306f5049937c9fd46ccb4601336
6
+ metadata.gz: 349c9dfbd70cef4a3a99416dfba1320850cb9e006d062f5bb2b6002b1dfa03b3b9b20c91e5473a292f57138ad69c5792793ffa699d5023442409defdd6bb1624
7
+ data.tar.gz: edaeac825ea58a5a349853cbe1b5a74a583cc147f75118d14b99a31cc118d547c51e9fc863e22d710eea6b00c6b95d515ae24cb89cd266fffe14625b2c8346f3
@@ -21,57 +21,240 @@
21
21
  );
22
22
  }
23
23
 
24
+ // Workaround for a MariaDB JDBC connector v3.x parser bug in the bundled
25
+ // analytics plugin: any request that contains a `^dimension:` clause causes
26
+ // the plugin to generate a `CASE ... END AS \`field\`` expression that the
27
+ // driver mangles, returning a 500 ("near '' at line N").
28
+ //
29
+ // The same data can be obtained by issuing one `^filter:(field=value)`
30
+ // request per dimension value and stitching the single-series payloads
31
+ // together client-side. Filter-only requests don't trigger the bug.
32
+ //
33
+ // Returns an array of { path, label } objects. When no dimension is present,
34
+ // returns a single entry with the original path and a null label.
35
+ function expandDimensionToFilters(reportsPath) {
36
+ var url;
37
+ try {
38
+ url = new URL(reportsPath, window.location.origin);
39
+ } catch (e) {
40
+ return [{ path: reportsPath, label: null }];
41
+ }
42
+
43
+ var nameParam = url.searchParams.get("name");
44
+ if (!nameParam) {
45
+ return [{ path: reportsPath, label: null }];
46
+ }
47
+
48
+ var parts = nameParam.split("^");
49
+ var baseName = parts[0];
50
+ var clauses = parts.slice(1);
51
+
52
+ var dimensionField = null;
53
+ var dimensionValues = [];
54
+ var otherClauses = [];
55
+
56
+ clauses.forEach(function (c) {
57
+ var m = c.match(/^dimension:([^(]+)\(([^)]+)\)$/);
58
+ if (m && dimensionField === null) {
59
+ dimensionField = m[1];
60
+ dimensionValues = m[2].split("|");
61
+ } else {
62
+ otherClauses.push(c);
63
+ }
64
+ });
65
+
66
+ if (dimensionField === null || dimensionValues.length === 0) {
67
+ return [{ path: reportsPath, label: null }];
68
+ }
69
+
70
+ // If there is also a filter on the same field (e.g. filter:(currency=USD|EUR)),
71
+ // intersect it with the dimension values so the filter still narrows the
72
+ // displayed series. Otherwise we'd silently re-introduce values the user
73
+ // filtered out.
74
+ var sameFieldFilterValues = null;
75
+ otherClauses.forEach(function (c) {
76
+ var fm = c.match(/^filter:\((.+)\)$/);
77
+ if (!fm) {
78
+ return;
79
+ }
80
+ // Filter syntax is repeated `field=value` pairs joined by `|`,
81
+ // e.g. `currency=USD|currency=EUR` (NOT `currency=USD|EUR`).
82
+ var pairs = fm[1].split("|");
83
+ var values = [];
84
+ var matched = false;
85
+ for (var i = 0; i < pairs.length; i++) {
86
+ var eq = pairs[i].indexOf("=");
87
+ if (eq === -1) {
88
+ continue;
89
+ }
90
+ var field = pairs[i].substring(0, eq);
91
+ var value = pairs[i].substring(eq + 1);
92
+ if (field === dimensionField) {
93
+ matched = true;
94
+ values.push(value);
95
+ }
96
+ }
97
+ if (matched) {
98
+ sameFieldFilterValues = (sameFieldFilterValues || []).concat(values);
99
+ }
100
+ });
101
+
102
+ if (sameFieldFilterValues) {
103
+ dimensionValues = dimensionValues.filter(function (v) {
104
+ return sameFieldFilterValues.indexOf(v) !== -1;
105
+ });
106
+ if (dimensionValues.length === 0) {
107
+ // Filter excluded every dimension value — nothing to render.
108
+ return [];
109
+ }
110
+ }
111
+
112
+ return dimensionValues.map(function (value) {
113
+ // Drop the same-field filter clause — we replace it with a single-value
114
+ // filter for this fan-out request. Filters on other fields are kept.
115
+ var preservedClauses = otherClauses.filter(function (c) {
116
+ var fm = c.match(/^filter:\((.+)\)$/);
117
+ if (!fm) {
118
+ return true;
119
+ }
120
+ return fm[1].indexOf(dimensionField + "=") === -1;
121
+ });
122
+
123
+ preservedClauses.push("filter:(" + dimensionField + "=" + value + ")");
124
+
125
+ var newName = baseName + "^" + preservedClauses.join("^");
126
+ var newUrl = new URL(url.toString());
127
+ newUrl.searchParams.set("name", newName);
128
+
129
+ return { path: newUrl.toString(), label: value };
130
+ });
131
+ }
132
+
133
+ // Merge an array of single-series plugin responses (one per dimension value)
134
+ // into a single multi-series payload compatible with the existing renderer.
135
+ function mergeFanOutResults(results) {
136
+ var firstValid = null;
137
+ for (var i = 0; i < results.length; i++) {
138
+ var r = results[i];
139
+ if (r && r.json && r.json[0]) {
140
+ firstValid = r;
141
+ break;
142
+ }
143
+ }
144
+ if (!firstValid) {
145
+ return null;
146
+ }
147
+
148
+ var merged = {
149
+ name: firstValid.json[0].name,
150
+ type: firstValid.json[0].type,
151
+ data: [],
152
+ };
153
+
154
+ results.forEach(function (r) {
155
+ if (!r || !r.json || !r.json[0] || !r.json[0].data) {
156
+ return;
157
+ }
158
+ r.json[0].data.forEach(function (series) {
159
+ merged.data.push({
160
+ // Use the dimension value as the series name so the chart legend
161
+ // distinguishes USD vs EUR vs GBP etc.
162
+ name: r.label || series.name,
163
+ values: series.values,
164
+ value: series.value,
165
+ });
166
+ });
167
+ });
168
+
169
+ return merged;
170
+ }
171
+
172
+ function renderPayload(data) {
173
+ var renderer = new Kiddo.Renderer("#chartAnchor");
174
+
175
+ if (data === undefined || data.data === undefined || data.data.length == 0) {
176
+ return renderer.noData();
177
+ }
178
+
179
+ try {
180
+ switch (data.type) {
181
+ case "COUNTERS":
182
+ renderer.pieChart(data);
183
+ break;
184
+ case "TIMELINE":
185
+ renderer.lineChart(data);
186
+ // Date controls only make sense for timelines
187
+ $("#date-controls").show();
188
+ break;
189
+ case "TABLE":
190
+ renderer.table(data);
191
+ break;
192
+ default:
193
+ console.log("No such type implemented: " + data.type);
194
+ renderer.noData();
195
+ }
196
+ } catch (ex) {
197
+ console.log(ex);
198
+ renderer.noData();
199
+ }
200
+ }
201
+
24
202
  $(document).ready(function () {
25
203
  if ($("#chartAnchor").length == 0) {
26
204
  return;
27
205
  }
28
206
 
29
- d3.json($("#chartAnchor").data("reports-path")).then(function (json) {
207
+ var reportsPath = $("#chartAnchor").data("reports-path");
208
+ var requests = expandDimensionToFilters(reportsPath);
209
+
210
+ if (window.console && console.info) {
211
+ console.info(
212
+ "[kanaui] dimension fan-out:",
213
+ requests.length,
214
+ "request(s)",
215
+ requests.map(function (r) {
216
+ return r.label;
217
+ })
218
+ );
219
+ }
220
+
221
+ if (requests.length === 0) {
30
222
  $("#loading-spinner").remove();
223
+ return new Kiddo.Renderer("#chartAnchor").noData();
224
+ }
31
225
 
32
- var renderer = new Kiddo.Renderer("#chartAnchor");
226
+ var fetchAll = Promise.all(
227
+ requests.map(function (req) {
228
+ return d3.json(req.path).then(function (json) {
229
+ return { label: req.label, json: json };
230
+ });
231
+ })
232
+ );
33
233
 
34
- var data = json[0];
234
+ fetchAll
235
+ .then(function (results) {
236
+ $("#loading-spinner").remove();
35
237
 
36
- if (
37
- data === undefined ||
38
- data.data === undefined ||
39
- data.data.length == 0
40
- ) {
41
- return renderer.noData();
42
- }
238
+ var data;
239
+ if (results.length === 1 && results[0].label === null) {
240
+ // No dimension fan-out — preserve original single-request behavior.
241
+ data = results[0].json && results[0].json[0];
242
+ } else {
243
+ data = mergeFanOutResults(results);
244
+ }
43
245
 
44
- var render = function (type) {
45
- switch (type) {
46
- case "COUNTERS":
47
- renderer.pieChart(data);
48
- break;
49
- case "TIMELINE":
50
- renderer.lineChart(data);
51
- // Date controls only make sense for timelines
52
- $("#date-controls").show();
53
- break;
54
- case "TABLE":
55
- renderer.table(data);
56
- break;
57
- default:
58
- console.log("No such type implemented: " + type);
59
- renderer.noData();
246
+ if (!data) {
247
+ return new Kiddo.Renderer("#chartAnchor").noData();
60
248
  }
61
- };
62
249
 
63
- try {
64
- render(data.type);
65
- } catch (ex) {
66
- console.log(ex);
67
- renderer.noData();
68
- }
69
- }).catch(function (error) {
70
- $("#loading-spinner").remove();
250
+ renderPayload(data);
251
+ })
252
+ .catch(function (error) {
253
+ $("#loading-spinner").remove();
71
254
 
72
- var renderer = new Kiddo.Renderer("#chartAnchor");
73
- renderError(errorMessage(error));
74
- return renderer.noData();
75
- });
255
+ var renderer = new Kiddo.Renderer("#chartAnchor");
256
+ renderError(errorMessage(error));
257
+ return renderer.noData();
258
+ });
76
259
  });
77
260
  })(d3, jQuery, window, document);
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kanaui
4
- VERSION = '5.0.2'
4
+ VERSION = '5.0.3'
5
5
  end
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.2
4
+ version: 5.0.3
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-06-03 00:00:00.000000000 Z
11
+ date: 2026-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bootstrap-datepicker-rails
@@ -106,14 +106,14 @@ dependencies:
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '7.0'
109
+ version: '7.2'
110
110
  type: :runtime
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '7.0'
116
+ version: '7.2'
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: spinjs-rails
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -203,7 +203,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
203
  requirements:
204
204
  - - ">="
205
205
  - !ruby/object:Gem::Version
206
- version: '0'
206
+ version: 3.1.0
207
207
  required_rubygems_version: !ruby/object:Gem::Requirement
208
208
  requirements:
209
209
  - - ">="