@cubejs-client/core 1.3.15 → 1.3.16

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.
Files changed (100) hide show
  1. package/dist/{cubejs-client-core.js → cubejs-client-core.cjs.js} +1016 -411
  2. package/dist/cubejs-client-core.cjs.js.map +1 -0
  3. package/dist/cubejs-client-core.umd.js +2901 -12088
  4. package/dist/cubejs-client-core.umd.js.map +1 -1
  5. package/dist/src/HttpTransport.d.ts +54 -0
  6. package/dist/src/HttpTransport.d.ts.map +1 -0
  7. package/dist/src/HttpTransport.js +55 -0
  8. package/dist/src/Meta.d.ts +62 -0
  9. package/dist/src/Meta.d.ts.map +1 -0
  10. package/dist/src/Meta.js +150 -0
  11. package/dist/src/ProgressResult.d.ts +8 -0
  12. package/dist/src/ProgressResult.d.ts.map +1 -0
  13. package/dist/src/ProgressResult.js +11 -0
  14. package/dist/src/RequestError.d.ts +6 -0
  15. package/dist/src/RequestError.d.ts.map +1 -0
  16. package/dist/src/RequestError.js +7 -0
  17. package/dist/src/ResultSet.d.ts +430 -0
  18. package/dist/src/ResultSet.d.ts.map +1 -0
  19. package/dist/src/ResultSet.js +952 -0
  20. package/dist/src/SqlQuery.d.ts +17 -0
  21. package/dist/src/SqlQuery.d.ts.map +1 -0
  22. package/dist/src/SqlQuery.js +11 -0
  23. package/dist/src/index.d.ts +194 -0
  24. package/dist/src/index.d.ts.map +1 -0
  25. package/dist/src/index.js +411 -0
  26. package/dist/src/index.umd.d.ts +3 -0
  27. package/dist/src/index.umd.d.ts.map +1 -0
  28. package/dist/src/index.umd.js +6 -0
  29. package/dist/src/time.d.ts +70 -0
  30. package/dist/src/time.d.ts.map +1 -0
  31. package/dist/src/time.js +249 -0
  32. package/dist/src/types.d.ts +424 -0
  33. package/dist/src/types.d.ts.map +1 -0
  34. package/dist/src/types.js +1 -0
  35. package/dist/src/utils.d.ts +19 -0
  36. package/dist/src/utils.d.ts.map +1 -0
  37. package/dist/src/utils.js +294 -0
  38. package/dist/test/CubeApi.test.d.ts +7 -0
  39. package/dist/test/CubeApi.test.d.ts.map +1 -0
  40. package/dist/test/CubeApi.test.js +279 -0
  41. package/dist/test/HttpTransport.test.d.ts +2 -0
  42. package/dist/test/HttpTransport.test.d.ts.map +1 -0
  43. package/dist/test/HttpTransport.test.js +244 -0
  44. package/dist/test/ResultSet.test.d.ts +7 -0
  45. package/dist/test/ResultSet.test.d.ts.map +1 -0
  46. package/dist/test/ResultSet.test.js +1725 -0
  47. package/dist/test/compare-date-range.test.d.ts +2 -0
  48. package/dist/test/compare-date-range.test.d.ts.map +1 -0
  49. package/dist/test/compare-date-range.test.js +742 -0
  50. package/dist/test/data-blending.test.d.ts +2 -0
  51. package/dist/test/data-blending.test.d.ts.map +1 -0
  52. package/dist/test/data-blending.test.js +423 -0
  53. package/dist/test/default-heuristics.test.d.ts +2 -0
  54. package/dist/test/default-heuristics.test.d.ts.map +1 -0
  55. package/dist/test/default-heuristics.test.js +108 -0
  56. package/dist/test/drill-down.test.d.ts +2 -0
  57. package/dist/test/drill-down.test.d.ts.map +1 -0
  58. package/dist/test/drill-down.test.js +373 -0
  59. package/dist/test/fixtures/datablending/load-responses.json +261 -0
  60. package/dist/test/granularity.test.d.ts +2 -0
  61. package/dist/test/granularity.test.d.ts.map +1 -0
  62. package/dist/test/granularity.test.js +218 -0
  63. package/dist/test/helpers.d.ts +283 -0
  64. package/dist/test/helpers.d.ts.map +1 -0
  65. package/dist/test/helpers.js +974 -0
  66. package/dist/test/index.test.d.ts +7 -0
  67. package/dist/test/index.test.d.ts.map +1 -0
  68. package/dist/test/index.test.js +370 -0
  69. package/dist/test/table.test.d.ts +2 -0
  70. package/dist/test/table.test.d.ts.map +1 -0
  71. package/dist/test/table.test.js +757 -0
  72. package/dist/test/utils.test.d.ts +2 -0
  73. package/dist/test/utils.test.d.ts.map +1 -0
  74. package/dist/test/utils.test.js +32 -0
  75. package/package.json +26 -21
  76. package/dist/cubejs-client-core.esm.js +0 -1639
  77. package/dist/cubejs-client-core.esm.js.map +0 -1
  78. package/dist/cubejs-client-core.js.map +0 -1
  79. package/index.d.ts +0 -1338
  80. package/src/HttpTransport.js +0 -60
  81. package/src/HttpTransport.test.js +0 -117
  82. package/src/Meta.js +0 -142
  83. package/src/ProgressResult.js +0 -13
  84. package/src/RequestError.js +0 -7
  85. package/src/ResultSet.js +0 -746
  86. package/src/SqlQuery.js +0 -13
  87. package/src/index.js +0 -398
  88. package/src/index.test.js +0 -454
  89. package/src/index.umd.js +0 -8
  90. package/src/tests/ResultSet.test.js +0 -1655
  91. package/src/tests/compare-date-range.test.js +0 -753
  92. package/src/tests/data-blending.test.js +0 -432
  93. package/src/tests/default-heuristics.test.js +0 -118
  94. package/src/tests/drill-down.test.js +0 -402
  95. package/src/tests/fixtures/datablending/load-responses.json +0 -261
  96. package/src/tests/granularity.test.js +0 -225
  97. package/src/tests/table.test.js +0 -791
  98. package/src/tests/utils.test.js +0 -35
  99. package/src/time.js +0 -296
  100. package/src/utils.js +0 -368
@@ -1,1639 +0,0 @@
1
- import { v4 } from 'uuid';
2
- import dayjs from 'dayjs';
3
- import { fromPairs, toPairs, equals, clone, indexBy, prop, mergeDeepLeft, pipe, map, filter, reduce, minBy, maxBy, flatten, pluck, mergeAll, uniq, dropLast, groupBy, unnest as unnest$1 } from 'ramda';
4
- import quarterOfYear from 'dayjs/plugin/quarterOfYear';
5
- import duration from 'dayjs/plugin/duration';
6
- import isoWeek from 'dayjs/plugin/isoWeek';
7
- import en from 'dayjs/locale/en';
8
- import fetch from 'cross-fetch';
9
- import 'url-search-params-polyfill';
10
-
11
- dayjs.extend(quarterOfYear);
12
- dayjs.extend(duration);
13
- dayjs.extend(isoWeek);
14
- const GRANULARITIES = [{
15
- name: undefined,
16
- title: 'w/o grouping'
17
- }, {
18
- name: 'second',
19
- title: 'Second'
20
- }, {
21
- name: 'minute',
22
- title: 'Minute'
23
- }, {
24
- name: 'hour',
25
- title: 'Hour'
26
- }, {
27
- name: 'day',
28
- title: 'Day'
29
- }, {
30
- name: 'week',
31
- title: 'Week'
32
- }, {
33
- name: 'month',
34
- title: 'Month'
35
- }, {
36
- name: 'quarter',
37
- title: 'Quarter'
38
- }, {
39
- name: 'year',
40
- title: 'Year'
41
- }];
42
- const DEFAULT_GRANULARITY = 'day';
43
-
44
- // When granularity is week, weekStart Value must be 1. However, since the client can change it globally
45
- // (https://day.js.org/docs/en/i18n/changing-locale) So the function below has been added.
46
- const internalDayjs = (...args) => dayjs(...args).locale({
47
- ...en,
48
- weekStart: 1
49
- });
50
- const TIME_SERIES = {
51
- day: range => range.by('d').map(d => d.format('YYYY-MM-DDT00:00:00.000')),
52
- month: range => range.snapTo('month').by('M').map(d => d.format('YYYY-MM-01T00:00:00.000')),
53
- year: range => range.snapTo('year').by('y').map(d => d.format('YYYY-01-01T00:00:00.000')),
54
- hour: range => range.by('h').map(d => d.format('YYYY-MM-DDTHH:00:00.000')),
55
- minute: range => range.by('m').map(d => d.format('YYYY-MM-DDTHH:mm:00.000')),
56
- second: range => range.by('s').map(d => d.format('YYYY-MM-DDTHH:mm:ss.000')),
57
- week: range => range.snapTo('week').by('w').map(d => d.startOf('week').format('YYYY-MM-DDT00:00:00.000')),
58
- quarter: range => range.snapTo('quarter').by('quarter').map(d => d.startOf('quarter').format('YYYY-MM-DDT00:00:00.000'))
59
- };
60
- const isPredefinedGranularity = granularity => !!TIME_SERIES[granularity];
61
- const DateRegex = /^\d\d\d\d-\d\d-\d\d$/;
62
- const LocalDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z?$/;
63
- const dayRange = (from, to) => ({
64
- by: value => {
65
- const results = [];
66
- let start = internalDayjs(from);
67
- const end = internalDayjs(to);
68
- while (start.isBefore(end) || start.isSame(end)) {
69
- results.push(start);
70
- start = start.add(1, value);
71
- }
72
- return results;
73
- },
74
- snapTo: value => dayRange(internalDayjs(from).startOf(value), internalDayjs(to).endOf(value)),
75
- start: internalDayjs(from),
76
- end: internalDayjs(to)
77
- });
78
-
79
- /**
80
- * Parse PostgreSQL-like interval string into object
81
- * E.g. '2 years 15 months 100 weeks 99 hours 15 seconds'
82
- * Negative units are also supported
83
- * E.g. '-2 months 5 days -10 hours'
84
- *
85
- * TODO: It's copy/paste of parseSqlInterval from @cubejs-backend/shared [time.ts]
86
- * It's not referenced to omit imports of moment.js staff.
87
- * Probably one day we should choose one implementation and reuse it in other places.
88
- */
89
- function parseSqlInterval(intervalStr) {
90
- const interval = {};
91
- const parts = intervalStr.split(/\s+/);
92
- for (let i = 0; i < parts.length; i += 2) {
93
- const value = parseInt(parts[i], 10);
94
- const unit = parts[i + 1];
95
-
96
- // Remove ending 's' (e.g., 'days' -> 'day')
97
- const singularUnit = unit.endsWith('s') ? unit.slice(0, -1) : unit;
98
- interval[singularUnit] = value;
99
- }
100
- return interval;
101
- }
102
-
103
- /**
104
- * Adds interval to provided date.
105
- * TODO: It's copy/paste of addInterval from @cubejs-backend/shared [time.ts]
106
- * but operates with dayjs instead of moment.js
107
- * @param {dayjs} date
108
- * @param interval
109
- * @returns {dayjs}
110
- */
111
- function addInterval(date, interval) {
112
- let res = date.clone();
113
- Object.entries(interval).forEach(([key, value]) => {
114
- res = res.add(value, key);
115
- });
116
- return res;
117
- }
118
-
119
- /**
120
- * Adds interval to provided date.
121
- * TODO: It's copy/paste of subtractInterval from @cubejs-backend/shared [time.ts]
122
- * but operates with dayjs instead of moment.js
123
- * @param {dayjs} date
124
- * @param interval
125
- * @returns {dayjs}
126
- */
127
- function subtractInterval(date, interval) {
128
- let res = date.clone();
129
- Object.entries(interval).forEach(([key, value]) => {
130
- res = res.subtract(value, key);
131
- });
132
- return res;
133
- }
134
-
135
- /**
136
- * Returns the closest date prior to date parameter aligned with the origin point
137
- * TODO: It's copy/paste of alignToOrigin from @cubejs-backend/shared [time.ts]
138
- * but operates with dayjs instead of moment.js
139
- */
140
- function alignToOrigin(startDate, interval, origin) {
141
- let alignedDate = startDate.clone();
142
- let intervalOp;
143
- let isIntervalNegative = false;
144
- let offsetDate = addInterval(origin, interval);
145
-
146
- // The easiest way to check the interval sign
147
- if (offsetDate.isBefore(origin)) {
148
- isIntervalNegative = true;
149
- }
150
- offsetDate = origin.clone();
151
- if (startDate.isBefore(origin)) {
152
- intervalOp = isIntervalNegative ? addInterval : subtractInterval;
153
- while (offsetDate.isAfter(startDate)) {
154
- offsetDate = intervalOp(offsetDate, interval);
155
- }
156
- alignedDate = offsetDate;
157
- } else {
158
- intervalOp = isIntervalNegative ? subtractInterval : addInterval;
159
- while (offsetDate.isBefore(startDate)) {
160
- alignedDate = offsetDate.clone();
161
- offsetDate = intervalOp(offsetDate, interval);
162
- }
163
- if (offsetDate.isSame(startDate)) {
164
- alignedDate = offsetDate;
165
- }
166
- }
167
- return alignedDate;
168
- }
169
-
170
- /**
171
- * Returns the time series points for the custom interval
172
- * TODO: It's almost a copy/paste of timeSeriesFromCustomInterval from
173
- * @cubejs-backend/shared [time.ts] but operates with dayjs instead of moment.js
174
- */
175
- const timeSeriesFromCustomInterval = (from, to, granularity) => {
176
- const intervalParsed = parseSqlInterval(granularity.interval);
177
- const start = internalDayjs(from);
178
- const end = internalDayjs(to);
179
- let origin = granularity.origin ? internalDayjs(granularity.origin) : internalDayjs().startOf('year');
180
- if (granularity.offset) {
181
- origin = addInterval(origin, parseSqlInterval(granularity.offset));
182
- }
183
- let alignedStart = alignToOrigin(start, intervalParsed, origin);
184
- const dates = [];
185
- while (alignedStart.isBefore(end) || alignedStart.isSame(end)) {
186
- dates.push(alignedStart.format('YYYY-MM-DDTHH:mm:ss.000'));
187
- alignedStart = addInterval(alignedStart, intervalParsed);
188
- }
189
- return dates;
190
- };
191
-
192
- /**
193
- * Returns the lowest time unit for the interval
194
- * @protected
195
- * @param {string} interval
196
- * @returns {string}
197
- */
198
- const diffTimeUnitForInterval = interval => {
199
- if (/second/i.test(interval)) {
200
- return 'second';
201
- } else if (/minute/i.test(interval)) {
202
- return 'minute';
203
- } else if (/hour/i.test(interval)) {
204
- return 'hour';
205
- } else if (/day/i.test(interval)) {
206
- return 'day';
207
- } else if (/week/i.test(interval)) {
208
- return 'day';
209
- } else if (/month/i.test(interval)) {
210
- return 'month';
211
- } else if (/quarter/i.test(interval)) {
212
- return 'month';
213
- } else /* if (/year/i.test(interval)) */{
214
- return 'year';
215
- }
216
- };
217
- const granularityOrder = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second'];
218
- const minGranularityForIntervals = (i1, i2) => {
219
- const g1 = diffTimeUnitForInterval(i1);
220
- const g2 = diffTimeUnitForInterval(i2);
221
- const g1pos = granularityOrder.indexOf(g1);
222
- const g2pos = granularityOrder.indexOf(g2);
223
- if (g1pos > g2pos) {
224
- return g1;
225
- }
226
- return g2;
227
- };
228
- const granularityFor = dateStr => {
229
- const dayjsDate = internalDayjs(dateStr);
230
- const month = dayjsDate.month();
231
- const date = dayjsDate.date();
232
- const hours = dayjsDate.hour();
233
- const minutes = dayjsDate.minute();
234
- const seconds = dayjsDate.second();
235
- const milliseconds = dayjsDate.millisecond();
236
- const weekDay = dayjsDate.isoWeekday();
237
- if (month === 0 && date === 1 && hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0) {
238
- return 'year';
239
- } else if (date === 1 && hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0) {
240
- return 'month';
241
- } else if (weekDay === 1 && hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0) {
242
- return 'week';
243
- } else if (hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0) {
244
- return 'day';
245
- } else if (minutes === 0 && seconds === 0 && milliseconds === 0) {
246
- return 'hour';
247
- } else if (seconds === 0 && milliseconds === 0) {
248
- return 'minute';
249
- } else if (milliseconds === 0) {
250
- return 'second';
251
- }
252
- return 'second'; // TODO return 'millisecond';
253
- };
254
-
255
- function removeEmptyQueryFields(_query) {
256
- const query = _query || {};
257
- return fromPairs(toPairs(query).map(([key, value]) => {
258
- if (['measures', 'dimensions', 'segments', 'timeDimensions', 'filters'].includes(key)) {
259
- if (Array.isArray(value) && value.length === 0) {
260
- return null;
261
- }
262
- }
263
- if (key === 'order' && value) {
264
- if (Array.isArray(value) && !value.length) {
265
- return null;
266
- } else if (!Object.keys(value).length) {
267
- return null;
268
- }
269
- }
270
- return [key, value];
271
- }).filter(Boolean));
272
- }
273
- function validateQuery(_query) {
274
- const query = _query || {};
275
- return removeEmptyQueryFields({
276
- ...query,
277
- filters: (query.filters || []).filter(f => f.operator),
278
- timeDimensions: (query.timeDimensions || []).filter(td => !(!td.dateRange && !td.granularity))
279
- });
280
- }
281
- function areQueriesEqual(query1 = {}, query2 = {}) {
282
- return equals(Object.entries(query1 && query1.order || {}), Object.entries(query2 && query2.order || {})) && equals(query1, query2);
283
- }
284
- function defaultOrder(query) {
285
- const granularity = (query.timeDimensions || []).find(d => d.granularity);
286
- if (granularity) {
287
- return {
288
- [granularity.dimension]: 'asc'
289
- };
290
- } else if ((query.measures || []).length > 0 && (query.dimensions || []).length > 0) {
291
- return {
292
- [query.measures[0]]: 'desc'
293
- };
294
- } else if ((query.dimensions || []).length > 0) {
295
- return {
296
- [query.dimensions[0]]: 'asc'
297
- };
298
- }
299
- return {};
300
- }
301
- function defaultHeuristics(newState, oldQuery = {}, options) {
302
- const {
303
- query,
304
- ...props
305
- } = clone(newState);
306
- const {
307
- meta,
308
- sessionGranularity
309
- } = options;
310
- const granularity = sessionGranularity || DEFAULT_GRANULARITY;
311
- let state = {
312
- query,
313
- ...props
314
- };
315
- let newQuery = null;
316
- if (!areQueriesEqual(query, oldQuery)) {
317
- newQuery = query;
318
- }
319
- if (Array.isArray(newQuery) || Array.isArray(oldQuery)) {
320
- return newState;
321
- }
322
- if (newQuery) {
323
- if ((oldQuery.timeDimensions || []).length === 1 && (newQuery.timeDimensions || []).length === 1 && newQuery.timeDimensions[0].granularity && oldQuery.timeDimensions[0].granularity !== newQuery.timeDimensions[0].granularity) {
324
- state = {
325
- ...state,
326
- sessionGranularity: newQuery.timeDimensions[0].granularity
327
- };
328
- }
329
- if ((oldQuery.measures || []).length === 0 && (newQuery.measures || []).length > 0 || (oldQuery.measures || []).length === 1 && (newQuery.measures || []).length === 1 && oldQuery.measures[0] !== newQuery.measures[0]) {
330
- const [td] = newQuery.timeDimensions || [];
331
- const defaultTimeDimension = meta.defaultTimeDimensionNameFor(newQuery.measures[0]);
332
- newQuery = {
333
- ...newQuery,
334
- timeDimensions: defaultTimeDimension ? [{
335
- dimension: defaultTimeDimension,
336
- granularity: td && td.granularity || granularity,
337
- dateRange: td && td.dateRange
338
- }] : []
339
- };
340
- return {
341
- ...state,
342
- pivotConfig: null,
343
- shouldApplyHeuristicOrder: true,
344
- query: newQuery,
345
- chartType: defaultTimeDimension ? 'line' : 'number'
346
- };
347
- }
348
- if ((oldQuery.dimensions || []).length === 0 && (newQuery.dimensions || []).length > 0) {
349
- newQuery = {
350
- ...newQuery,
351
- timeDimensions: (newQuery.timeDimensions || []).map(td => ({
352
- ...td,
353
- granularity: undefined
354
- }))
355
- };
356
- return {
357
- ...state,
358
- pivotConfig: null,
359
- shouldApplyHeuristicOrder: true,
360
- query: newQuery,
361
- chartType: 'table'
362
- };
363
- }
364
- if ((oldQuery.dimensions || []).length > 0 && (newQuery.dimensions || []).length === 0) {
365
- newQuery = {
366
- ...newQuery,
367
- timeDimensions: (newQuery.timeDimensions || []).map(td => ({
368
- ...td,
369
- granularity: td.granularity || granularity
370
- }))
371
- };
372
- return {
373
- ...state,
374
- pivotConfig: null,
375
- shouldApplyHeuristicOrder: true,
376
- query: newQuery,
377
- chartType: (newQuery.timeDimensions || []).length ? 'line' : 'number'
378
- };
379
- }
380
- if (((oldQuery.dimensions || []).length > 0 || (oldQuery.measures || []).length > 0) && (newQuery.dimensions || []).length === 0 && (newQuery.measures || []).length === 0) {
381
- newQuery = {
382
- ...newQuery,
383
- timeDimensions: [],
384
- filters: []
385
- };
386
- return {
387
- ...state,
388
- pivotConfig: null,
389
- shouldApplyHeuristicOrder: true,
390
- query: newQuery,
391
- sessionGranularity: null
392
- };
393
- }
394
- return state;
395
- }
396
- if (state.chartType) {
397
- const newChartType = state.chartType;
398
- if ((newChartType === 'line' || newChartType === 'area') && (oldQuery.timeDimensions || []).length === 1 && !oldQuery.timeDimensions[0].granularity) {
399
- const [td] = oldQuery.timeDimensions;
400
- return {
401
- ...state,
402
- pivotConfig: null,
403
- query: {
404
- ...oldQuery,
405
- timeDimensions: [{
406
- ...td,
407
- granularity
408
- }]
409
- }
410
- };
411
- }
412
- if ((newChartType === 'pie' || newChartType === 'table' || newChartType === 'number') && (oldQuery.timeDimensions || []).length === 1 && oldQuery.timeDimensions[0].granularity) {
413
- const [td] = oldQuery.timeDimensions;
414
- return {
415
- ...state,
416
- pivotConfig: null,
417
- shouldApplyHeuristicOrder: true,
418
- query: {
419
- ...oldQuery,
420
- timeDimensions: [{
421
- ...td,
422
- granularity: undefined
423
- }]
424
- }
425
- };
426
- }
427
- }
428
- return state;
429
- }
430
- function isQueryPresent(query) {
431
- if (!query) {
432
- return false;
433
- }
434
- return (Array.isArray(query) ? query : [query]).every(q => q.measures && q.measures.length || q.dimensions && q.dimensions.length || q.timeDimensions && q.timeDimensions.length);
435
- }
436
- function movePivotItem(pivotConfig, sourceIndex, destinationIndex, sourceAxis, destinationAxis) {
437
- const nextPivotConfig = {
438
- ...pivotConfig,
439
- x: [...pivotConfig.x],
440
- y: [...pivotConfig.y]
441
- };
442
- const id = pivotConfig[sourceAxis][sourceIndex];
443
- const lastIndex = nextPivotConfig[destinationAxis].length - 1;
444
- if (id === 'measures') {
445
- destinationIndex = lastIndex + 1;
446
- } else if (sourceAxis === destinationAxis && destinationIndex >= lastIndex && nextPivotConfig[destinationAxis][lastIndex] === 'measures') {
447
- destinationIndex = lastIndex - 1;
448
- } else if (sourceAxis !== destinationAxis && destinationIndex > lastIndex && nextPivotConfig[destinationAxis][lastIndex] === 'measures') {
449
- destinationIndex = lastIndex;
450
- }
451
- nextPivotConfig[sourceAxis].splice(sourceIndex, 1);
452
- nextPivotConfig[destinationAxis].splice(destinationIndex, 0, id);
453
- return nextPivotConfig;
454
- }
455
- function moveItemInArray(list, sourceIndex, destinationIndex) {
456
- const result = [...list];
457
- const [removed] = result.splice(sourceIndex, 1);
458
- result.splice(destinationIndex, 0, removed);
459
- return result;
460
- }
461
- function flattenFilters(filters = []) {
462
- return filters.reduce((memo, filter) => {
463
- if (filter.or || filter.and) {
464
- return [...memo, ...flattenFilters(filter.or || filter.and)];
465
- }
466
- return [...memo, filter];
467
- }, []);
468
- }
469
- function getQueryMembers(query = {}) {
470
- const keys = ['measures', 'dimensions', 'segments'];
471
- const members = new Set();
472
- keys.forEach(key => (query[key] || []).forEach(member => members.add(member)));
473
- (query.timeDimensions || []).forEach(td => members.add(td.dimension));
474
- flattenFilters(query.filters).forEach(filter => members.add(filter.dimension || filter.member));
475
- return [...members];
476
- }
477
- function getOrderMembersFromOrder(orderMembers, order) {
478
- const ids = new Set();
479
- const indexedOrderMembers = indexBy(prop('id'), orderMembers);
480
- const entries = Array.isArray(order) ? order : Object.entries(order || {});
481
- const nextOrderMembers = [];
482
- entries.forEach(([memberId, currentOrder]) => {
483
- if (currentOrder !== 'none' && indexedOrderMembers[memberId]) {
484
- ids.add(memberId);
485
- nextOrderMembers.push({
486
- ...indexedOrderMembers[memberId],
487
- order: currentOrder
488
- });
489
- }
490
- });
491
- orderMembers.forEach(member => {
492
- if (!ids.has(member.id)) {
493
- nextOrderMembers.push({
494
- ...member,
495
- order: member.order || 'none'
496
- });
497
- }
498
- });
499
- return nextOrderMembers;
500
- }
501
- function aliasSeries(values, index, pivotConfig, duplicateMeasures) {
502
- const nonNullValues = values.filter(value => value != null);
503
- if (pivotConfig && pivotConfig.aliasSeries && pivotConfig.aliasSeries[index]) {
504
- return [pivotConfig.aliasSeries[index], ...nonNullValues];
505
- } else if (duplicateMeasures.has(nonNullValues[0])) {
506
- return [index, ...nonNullValues];
507
- }
508
- return nonNullValues;
509
- }
510
-
511
- const groupByToPairs = keyFn => {
512
- const acc = new Map();
513
- return data => {
514
- data.forEach(row => {
515
- const key = keyFn(row);
516
- if (!acc.has(key)) {
517
- acc.set(key, []);
518
- }
519
- acc.get(key).push(row);
520
- });
521
- return Array.from(acc.entries());
522
- };
523
- };
524
- const unnest = arr => {
525
- const res = [];
526
- arr.forEach(subArr => {
527
- subArr.forEach(element => res.push(element));
528
- });
529
- return res;
530
- };
531
- const QUERY_TYPE = {
532
- REGULAR_QUERY: 'regularQuery',
533
- COMPARE_DATE_RANGE_QUERY: 'compareDateRangeQuery',
534
- BLENDING_QUERY: 'blendingQuery'
535
- };
536
- class ResultSet {
537
- static measureFromAxis(axisValues) {
538
- return axisValues[axisValues.length - 1];
539
- }
540
- static timeDimensionMember(td) {
541
- return `${td.dimension}.${td.granularity}`;
542
- }
543
- static deserialize(data, options = {}) {
544
- return new ResultSet(data.loadResponse, options);
545
- }
546
- constructor(loadResponse, options = {}) {
547
- this.loadResponse = loadResponse;
548
- if (this.loadResponse.queryType != null) {
549
- this.queryType = loadResponse.queryType;
550
- this.loadResponses = loadResponse.results;
551
- } else {
552
- this.queryType = QUERY_TYPE.REGULAR_QUERY;
553
- this.loadResponse.pivotQuery = {
554
- ...loadResponse.query,
555
- queryType: this.queryType
556
- };
557
- this.loadResponses = [loadResponse];
558
- }
559
- if (!Object.values(QUERY_TYPE).includes(this.queryType)) {
560
- throw new Error('Unknown query type');
561
- }
562
- this.parseDateMeasures = options.parseDateMeasures;
563
- this.options = options;
564
- this.backwardCompatibleData = [];
565
- }
566
- drillDown(drillDownLocator, pivotConfig) {
567
- if (this.queryType === QUERY_TYPE.COMPARE_DATE_RANGE_QUERY) {
568
- throw new Error('compareDateRange drillDown query is not currently supported');
569
- }
570
- if (this.queryType === QUERY_TYPE.BLENDING_QUERY) {
571
- throw new Error('Data blending drillDown query is not currently supported');
572
- }
573
- const {
574
- query
575
- } = this.loadResponses[0];
576
- const {
577
- xValues = [],
578
- yValues = []
579
- } = drillDownLocator;
580
- const normalizedPivotConfig = this.normalizePivotConfig(pivotConfig);
581
- const values = [];
582
- normalizedPivotConfig.x.forEach((member, currentIndex) => values.push([member, xValues[currentIndex]]));
583
- normalizedPivotConfig.y.forEach((member, currentIndex) => values.push([member, yValues[currentIndex]]));
584
- const {
585
- filters: parentFilters = [],
586
- segments = []
587
- } = this.query();
588
- const {
589
- measures
590
- } = this.loadResponses[0].annotation;
591
- let [, measureName] = values.find(([member]) => member === 'measures') || [];
592
- if (measureName === undefined) {
593
- [measureName] = Object.keys(measures);
594
- }
595
- if (!(measures[measureName] && measures[measureName].drillMembers || []).length) {
596
- return null;
597
- }
598
- const filters = [{
599
- member: measureName,
600
- operator: 'measureFilter'
601
- }, ...parentFilters];
602
- const timeDimensions = [];
603
- values.filter(([member]) => member !== 'measures').forEach(([member, value]) => {
604
- const [cubeName, dimension, granularity] = member.split('.');
605
- if (granularity !== undefined) {
606
- const range = dayRange(value, value).snapTo(granularity);
607
- const originalTimeDimension = query.timeDimensions.find(td => td.dimension);
608
- let dateRange = [range.start, range.end];
609
- if (originalTimeDimension?.dateRange) {
610
- const [originalStart, originalEnd] = originalTimeDimension.dateRange;
611
- dateRange = [dayjs(originalStart) > range.start ? dayjs(originalStart) : range.start, dayjs(originalEnd) < range.end ? dayjs(originalEnd) : range.end];
612
- }
613
- timeDimensions.push({
614
- dimension: [cubeName, dimension].join('.'),
615
- dateRange: dateRange.map(dt => dt.format('YYYY-MM-DDTHH:mm:ss.SSS'))
616
- });
617
- } else if (value == null) {
618
- filters.push({
619
- member,
620
- operator: 'notSet'
621
- });
622
- } else {
623
- filters.push({
624
- member,
625
- operator: 'equals',
626
- values: [value.toString()]
627
- });
628
- }
629
- });
630
- if (timeDimensions.length === 0 && query.timeDimensions.length > 0 && query.timeDimensions[0].granularity == null) {
631
- timeDimensions.push(query.timeDimensions[0]);
632
- }
633
- return {
634
- ...measures[measureName].drillMembersGrouped,
635
- filters,
636
- ...(segments.length > 0 ? {
637
- segments
638
- } : {}),
639
- timeDimensions,
640
- segments,
641
- timezone: query.timezone
642
- };
643
- }
644
- series(pivotConfig) {
645
- return this.seriesNames(pivotConfig).map(({
646
- title,
647
- shortTitle,
648
- key
649
- }) => ({
650
- title,
651
- shortTitle,
652
- key,
653
- series: this.chartPivot(pivotConfig).map(({
654
- x,
655
- ...obj
656
- }) => ({
657
- value: obj[key],
658
- x
659
- }))
660
- }));
661
- }
662
- axisValues(axis, resultIndex = 0) {
663
- const {
664
- query
665
- } = this.loadResponses[resultIndex];
666
- return row => {
667
- const value = measure => axis.filter(d => d !== 'measures').map(d => row[d] != null ? row[d] : null).concat(measure ? [measure] : []);
668
- if (axis.find(d => d === 'measures') && (query.measures || []).length) {
669
- return query.measures.map(value);
670
- }
671
- return [value()];
672
- };
673
- }
674
- axisValuesString(axisValues, delimiter) {
675
- const formatValue = v => {
676
- if (v == null) {
677
- return '∅';
678
- } else if (v === '') {
679
- return '[Empty string]';
680
- } else {
681
- return v;
682
- }
683
- };
684
- return axisValues.map(formatValue).join(delimiter || ', ');
685
- }
686
- static getNormalizedPivotConfig(query = {}, pivotConfig = null) {
687
- const defaultPivotConfig = {
688
- x: [],
689
- y: [],
690
- fillMissingDates: true,
691
- joinDateRange: false
692
- };
693
- const {
694
- measures = [],
695
- dimensions = []
696
- } = query;
697
- const timeDimensions = (query.timeDimensions || []).filter(td => !!td.granularity);
698
- pivotConfig = pivotConfig || (timeDimensions.length ? {
699
- x: timeDimensions.map(td => ResultSet.timeDimensionMember(td)),
700
- y: dimensions
701
- } : {
702
- x: dimensions,
703
- y: []
704
- });
705
- pivotConfig = mergeDeepLeft(pivotConfig, defaultPivotConfig);
706
- const substituteTimeDimensionMembers = axis => axis.map(subDim => timeDimensions.find(td => td.dimension === subDim) && !dimensions.find(d => d === subDim) ? ResultSet.timeDimensionMember(query.timeDimensions.find(td => td.dimension === subDim)) : subDim);
707
- pivotConfig.x = substituteTimeDimensionMembers(pivotConfig.x);
708
- pivotConfig.y = substituteTimeDimensionMembers(pivotConfig.y);
709
- const allIncludedDimensions = pivotConfig.x.concat(pivotConfig.y);
710
- const allDimensions = timeDimensions.map(td => ResultSet.timeDimensionMember(td)).concat(dimensions);
711
- const dimensionFilter = key => allDimensions.includes(key) || key === 'measures';
712
- pivotConfig.x = pivotConfig.x.concat(allDimensions.filter(d => !allIncludedDimensions.includes(d) && d !== 'compareDateRange')).filter(dimensionFilter);
713
- pivotConfig.y = pivotConfig.y.filter(dimensionFilter);
714
- if (!pivotConfig.x.concat(pivotConfig.y).find(d => d === 'measures')) {
715
- pivotConfig.y.push('measures');
716
- }
717
- if (dimensions.includes('compareDateRange') && !pivotConfig.y.concat(pivotConfig.x).includes('compareDateRange')) {
718
- pivotConfig.y.unshift('compareDateRange');
719
- }
720
- if (!measures.length) {
721
- pivotConfig.x = pivotConfig.x.filter(d => d !== 'measures');
722
- pivotConfig.y = pivotConfig.y.filter(d => d !== 'measures');
723
- }
724
- return pivotConfig;
725
- }
726
- normalizePivotConfig(pivotConfig) {
727
- return ResultSet.getNormalizedPivotConfig(this.loadResponse.pivotQuery, pivotConfig);
728
- }
729
- timeSeries(timeDimension, resultIndex, annotations) {
730
- if (!timeDimension.granularity) {
731
- return null;
732
- }
733
- let {
734
- dateRange
735
- } = timeDimension;
736
- if (!dateRange) {
737
- const member = ResultSet.timeDimensionMember(timeDimension);
738
- const dates = pipe(map(row => row[member] && internalDayjs(row[member])), filter(Boolean))(this.timeDimensionBackwardCompatibleData(resultIndex));
739
- dateRange = dates.length && [reduce(minBy(d => d.toDate()), dates[0], dates), reduce(maxBy(d => d.toDate()), dates[0], dates)] || null;
740
- }
741
- if (!dateRange) {
742
- return null;
743
- }
744
- const padToDay = timeDimension.dateRange ? timeDimension.dateRange.find(d => d.match(DateRegex)) : !['hour', 'minute', 'second'].includes(timeDimension.granularity);
745
- const [start, end] = dateRange;
746
- const range = dayRange(start, end);
747
- if (isPredefinedGranularity(timeDimension.granularity)) {
748
- return TIME_SERIES[timeDimension.granularity](padToDay ? range.snapTo('d') : range);
749
- }
750
- if (!annotations[`${timeDimension.dimension}.${timeDimension.granularity}`]) {
751
- throw new Error(`Granularity "${timeDimension.granularity}" not found in time dimension "${timeDimension.dimension}"`);
752
- }
753
- return timeSeriesFromCustomInterval(start, end, annotations[`${timeDimension.dimension}.${timeDimension.granularity}`].granularity);
754
- }
755
- pivot(pivotConfig) {
756
- pivotConfig = this.normalizePivotConfig(pivotConfig);
757
- const {
758
- pivotQuery: query
759
- } = this.loadResponse;
760
- const pivotImpl = (resultIndex = 0) => {
761
- let groupByXAxis = groupByToPairs(({
762
- xValues
763
- }) => this.axisValuesString(xValues));
764
- const measureValue = (row, measure) => row[measure] || pivotConfig.fillWithValue || 0;
765
- if (pivotConfig.fillMissingDates && pivotConfig.x.length === 1 && equals(pivotConfig.x, (query.timeDimensions || []).filter(td => Boolean(td.granularity)).map(td => ResultSet.timeDimensionMember(td)))) {
766
- const series = this.loadResponses.map(loadResponse => this.timeSeries(loadResponse.query.timeDimensions[0], resultIndex, loadResponse.annotation.timeDimensions));
767
- if (series[0]) {
768
- groupByXAxis = rows => {
769
- const byXValues = groupBy(({
770
- xValues
771
- }) => xValues[0], rows);
772
- return series[resultIndex].map(d => [d, byXValues[d] || [{
773
- xValues: [d],
774
- row: {}
775
- }]]);
776
- };
777
- }
778
- }
779
- const xGrouped = pipe(map(row => this.axisValues(pivotConfig.x, resultIndex)(row).map(xValues => ({
780
- xValues,
781
- row
782
- }))), unnest, groupByXAxis)(this.timeDimensionBackwardCompatibleData(resultIndex));
783
- const yValuesMap = {};
784
- xGrouped.forEach(([, rows]) => {
785
- rows.forEach(({
786
- row
787
- }) => {
788
- this.axisValues(pivotConfig.y, resultIndex)(row).forEach(values => {
789
- if (Object.keys(row).length > 0) {
790
- yValuesMap[values.join()] = values;
791
- }
792
- });
793
- });
794
- });
795
- const allYValues = Object.values(yValuesMap);
796
- const measureOnX = Boolean(pivotConfig.x.find(d => d === 'measures'));
797
- return xGrouped.map(([, rows]) => {
798
- const {
799
- xValues
800
- } = rows[0];
801
- const yGrouped = {};
802
- rows.forEach(({
803
- row
804
- }) => {
805
- const arr = this.axisValues(pivotConfig.y, resultIndex)(row).map(yValues => ({
806
- yValues,
807
- row
808
- }));
809
- arr.forEach(res => {
810
- yGrouped[this.axisValuesString(res.yValues)] = res;
811
- });
812
- });
813
- return {
814
- xValues,
815
- yValuesArray: unnest(allYValues.map(yValues => {
816
- const measure = measureOnX ? ResultSet.measureFromAxis(xValues) : ResultSet.measureFromAxis(yValues);
817
- return [[yValues, measureValue((yGrouped[this.axisValuesString(yValues)] || {
818
- row: {}
819
- }).row, measure)]];
820
- }))
821
- };
822
- });
823
- };
824
- const pivots = this.loadResponses.length > 1 ? this.loadResponses.map((_, index) => pivotImpl(index)) : [];
825
- return pivots.length ? this.mergePivots(pivots, pivotConfig.joinDateRange) : pivotImpl();
826
- }
827
- mergePivots(pivots, joinDateRange) {
828
- const minLengthPivot = pivots.reduce((memo, current) => memo != null && current.length >= memo.length ? memo : current, null);
829
- return minLengthPivot.map((_, index) => {
830
- const xValues = joinDateRange ? [pivots.map(pivot => pivot[index] && pivot[index].xValues || []).join(', ')] : minLengthPivot[index].xValues;
831
- return {
832
- xValues,
833
- yValuesArray: unnest(pivots.map(pivot => pivot[index].yValuesArray))
834
- };
835
- });
836
- }
837
- pivotedRows(pivotConfig) {
838
- // TODO
839
- return this.chartPivot(pivotConfig);
840
- }
841
- chartPivot(pivotConfig) {
842
- const validate = value => {
843
- if (this.parseDateMeasures && LocalDateRegex.test(value)) {
844
- return new Date(value);
845
- } else if (!Number.isNaN(Number.parseFloat(value))) {
846
- return Number.parseFloat(value);
847
- }
848
- return value;
849
- };
850
- const duplicateMeasures = new Set();
851
- if (this.queryType === QUERY_TYPE.BLENDING_QUERY) {
852
- const allMeasures = flatten(this.loadResponses.map(({
853
- query
854
- }) => query.measures));
855
- allMeasures.filter((e, i, a) => a.indexOf(e) !== i).forEach(m => duplicateMeasures.add(m));
856
- }
857
- return this.pivot(pivotConfig).map(({
858
- xValues,
859
- yValuesArray
860
- }) => {
861
- const yValuesMap = {};
862
- yValuesArray.forEach(([yValues, m], i) => {
863
- yValuesMap[this.axisValuesString(aliasSeries(yValues, i, pivotConfig, duplicateMeasures), ',')] = m && validate(m);
864
- });
865
- return {
866
- x: this.axisValuesString(xValues, ','),
867
- xValues,
868
- ...yValuesMap
869
- };
870
- });
871
- }
872
- tablePivot(pivotConfig) {
873
- const normalizedPivotConfig = this.normalizePivotConfig(pivotConfig || {});
874
- const isMeasuresPresent = normalizedPivotConfig.x.concat(normalizedPivotConfig.y).includes('measures');
875
- return this.pivot(normalizedPivotConfig).map(({
876
- xValues,
877
- yValuesArray
878
- }) => fromPairs(normalizedPivotConfig.x.map((key, index) => [key, xValues[index]]).concat(isMeasuresPresent ? yValuesArray.map(([yValues, measure]) => [yValues.length ? yValues.join() : 'value', measure]) : [])));
879
- }
880
- tableColumns(pivotConfig) {
881
- const normalizedPivotConfig = this.normalizePivotConfig(pivotConfig || {});
882
- const annotations = pipe(pluck('annotation'), reduce(mergeDeepLeft(), {}))(this.loadResponses);
883
- const flatMeta = Object.values(annotations).reduce((a, b) => ({
884
- ...a,
885
- ...b
886
- }), {});
887
- const schema = {};
888
- const extractFields = key => {
889
- const {
890
- title,
891
- shortTitle,
892
- type,
893
- format,
894
- meta
895
- } = flatMeta[key] || {};
896
- return {
897
- key,
898
- title,
899
- shortTitle,
900
- type,
901
- format,
902
- meta
903
- };
904
- };
905
- const pivot = this.pivot(normalizedPivotConfig);
906
- (pivot[0] && pivot[0].yValuesArray || []).forEach(([yValues]) => {
907
- if (yValues.length > 0) {
908
- let currentItem = schema;
909
- yValues.forEach((value, index) => {
910
- currentItem[`_${value}`] = {
911
- key: value,
912
- memberId: normalizedPivotConfig.y[index] === 'measures' ? value : normalizedPivotConfig.y[index],
913
- children: currentItem[`_${value}`] && currentItem[`_${value}`].children || {}
914
- };
915
- currentItem = currentItem[`_${value}`].children;
916
- });
917
- }
918
- });
919
- const toColumns = (item = {}, path = []) => {
920
- if (Object.keys(item).length === 0) {
921
- return [];
922
- }
923
- return Object.values(item).map(({
924
- key,
925
- ...currentItem
926
- }) => {
927
- const children = toColumns(currentItem.children, [...path, key]);
928
- const {
929
- title,
930
- shortTitle,
931
- ...fields
932
- } = extractFields(currentItem.memberId);
933
- const dimensionValue = key !== currentItem.memberId || title == null ? key : '';
934
- if (!children.length) {
935
- return {
936
- ...fields,
937
- key,
938
- dataIndex: [...path, key].join(),
939
- title: [title, dimensionValue].join(' ').trim(),
940
- shortTitle: dimensionValue || shortTitle
941
- };
942
- }
943
- return {
944
- ...fields,
945
- key,
946
- title: [title, dimensionValue].join(' ').trim(),
947
- shortTitle: dimensionValue || shortTitle,
948
- children
949
- };
950
- });
951
- };
952
- let otherColumns = [];
953
- if (!pivot.length && normalizedPivotConfig.y.includes('measures')) {
954
- otherColumns = (this.loadResponses[0].query.measures || []).map(key => ({
955
- ...extractFields(key),
956
- dataIndex: key
957
- }));
958
- }
959
-
960
- // Syntatic column to display the measure value
961
- if (!normalizedPivotConfig.y.length && normalizedPivotConfig.x.includes('measures')) {
962
- otherColumns.push({
963
- key: 'value',
964
- dataIndex: 'value',
965
- title: 'Value',
966
- shortTitle: 'Value',
967
- type: 'string'
968
- });
969
- }
970
- return normalizedPivotConfig.x.map(key => {
971
- if (key === 'measures') {
972
- return {
973
- key: 'measures',
974
- dataIndex: 'measures',
975
- title: 'Measures',
976
- shortTitle: 'Measures',
977
- type: 'string'
978
- };
979
- }
980
- return {
981
- ...extractFields(key),
982
- dataIndex: key
983
- };
984
- }).concat(toColumns(schema)).concat(otherColumns);
985
- }
986
- totalRow(pivotConfig) {
987
- return this.chartPivot(pivotConfig)[0];
988
- }
989
- categories(pivotConfig) {
990
- // TODO
991
- return this.chartPivot(pivotConfig);
992
- }
993
- seriesNames(pivotConfig) {
994
- pivotConfig = this.normalizePivotConfig(pivotConfig);
995
- const measures = pipe(pluck('annotation'), pluck('measures'), mergeAll)(this.loadResponses);
996
- const seriesNames = unnest(this.loadResponses.map((_, index) => pipe(map(this.axisValues(pivotConfig.y, index)), unnest, uniq)(this.timeDimensionBackwardCompatibleData(index))));
997
- const duplicateMeasures = new Set();
998
- if (this.queryType === QUERY_TYPE.BLENDING_QUERY) {
999
- const allMeasures = flatten(this.loadResponses.map(({
1000
- query
1001
- }) => query.measures));
1002
- allMeasures.filter((e, i, a) => a.indexOf(e) !== i).forEach(m => duplicateMeasures.add(m));
1003
- }
1004
- return seriesNames.map((axisValues, i) => {
1005
- const aliasedAxis = aliasSeries(axisValues, i, pivotConfig, duplicateMeasures);
1006
- return {
1007
- title: this.axisValuesString(pivotConfig.y.find(d => d === 'measures') ? dropLast(1, aliasedAxis).concat(measures[ResultSet.measureFromAxis(axisValues)].title) : aliasedAxis, ', '),
1008
- shortTitle: this.axisValuesString(pivotConfig.y.find(d => d === 'measures') ? dropLast(1, aliasedAxis).concat(measures[ResultSet.measureFromAxis(axisValues)].shortTitle) : aliasedAxis, ', '),
1009
- key: this.axisValuesString(aliasedAxis, ','),
1010
- yValues: axisValues
1011
- };
1012
- });
1013
- }
1014
- query() {
1015
- if (this.queryType !== QUERY_TYPE.REGULAR_QUERY) {
1016
- throw new Error(`Method is not supported for a '${this.queryType}' query type. Please use decompose`);
1017
- }
1018
- return this.loadResponses[0].query;
1019
- }
1020
- pivotQuery() {
1021
- return this.loadResponse.pivotQuery || null;
1022
- }
1023
- totalRows() {
1024
- return this.loadResponses[0].total;
1025
- }
1026
- rawData() {
1027
- if (this.queryType !== QUERY_TYPE.REGULAR_QUERY) {
1028
- throw new Error(`Method is not supported for a '${this.queryType}' query type. Please use decompose`);
1029
- }
1030
- return this.loadResponses[0].data;
1031
- }
1032
- annotation() {
1033
- if (this.queryType !== QUERY_TYPE.REGULAR_QUERY) {
1034
- throw new Error(`Method is not supported for a '${this.queryType}' query type. Please use decompose`);
1035
- }
1036
- return this.loadResponses[0].annotation;
1037
- }
1038
- timeDimensionBackwardCompatibleData(resultIndex) {
1039
- if (resultIndex === undefined) {
1040
- throw new Error('resultIndex is required');
1041
- }
1042
- if (!this.backwardCompatibleData[resultIndex]) {
1043
- const {
1044
- data,
1045
- query
1046
- } = this.loadResponses[resultIndex];
1047
- const timeDimensions = (query.timeDimensions || []).filter(td => Boolean(td.granularity));
1048
- this.backwardCompatibleData[resultIndex] = data.map(row => ({
1049
- ...row,
1050
- ...fromPairs(Object.keys(row).filter(field => timeDimensions.find(d => d.dimension === field) && !row[ResultSet.timeDimensionMember(timeDimensions.find(d => d.dimension === field))]).map(field => [ResultSet.timeDimensionMember(timeDimensions.find(d => d.dimension === field)), row[field]]))
1051
- }));
1052
- }
1053
- return this.backwardCompatibleData[resultIndex];
1054
- }
1055
- decompose() {
1056
- return this.loadResponses.map(result => new ResultSet({
1057
- queryType: QUERY_TYPE.REGULAR_QUERY,
1058
- pivotQuery: {
1059
- ...result.query,
1060
- queryType: QUERY_TYPE.REGULAR_QUERY
1061
- },
1062
- results: [result]
1063
- }, this.options));
1064
- }
1065
- serialize() {
1066
- return {
1067
- loadResponse: clone(this.loadResponse)
1068
- };
1069
- }
1070
- }
1071
-
1072
- class SqlQuery {
1073
- constructor(sqlQuery) {
1074
- this.sqlQuery = sqlQuery;
1075
- }
1076
- rawQuery() {
1077
- return this.sqlQuery.sql;
1078
- }
1079
- sql() {
1080
- return this.rawQuery().sql[0];
1081
- }
1082
- }
1083
-
1084
- /**
1085
- * @module @cubejs-client/core
1086
- */
1087
- const memberMap = memberArray => fromPairs(memberArray.map(m => [m.name, m]));
1088
- const operators = {
1089
- string: [{
1090
- name: 'contains',
1091
- title: 'contains'
1092
- }, {
1093
- name: 'notContains',
1094
- title: 'does not contain'
1095
- }, {
1096
- name: 'equals',
1097
- title: 'equals'
1098
- }, {
1099
- name: 'notEquals',
1100
- title: 'does not equal'
1101
- }, {
1102
- name: 'set',
1103
- title: 'is set'
1104
- }, {
1105
- name: 'notSet',
1106
- title: 'is not set'
1107
- }, {
1108
- name: 'startsWith',
1109
- title: 'starts with'
1110
- }, {
1111
- name: 'notStartsWith',
1112
- title: 'does not start with'
1113
- }, {
1114
- name: 'endsWith',
1115
- title: 'ends with'
1116
- }, {
1117
- name: 'notEndsWith',
1118
- title: 'does not end with'
1119
- }],
1120
- number: [{
1121
- name: 'equals',
1122
- title: 'equals'
1123
- }, {
1124
- name: 'notEquals',
1125
- title: 'does not equal'
1126
- }, {
1127
- name: 'set',
1128
- title: 'is set'
1129
- }, {
1130
- name: 'notSet',
1131
- title: 'is not set'
1132
- }, {
1133
- name: 'gt',
1134
- title: '>'
1135
- }, {
1136
- name: 'gte',
1137
- title: '>='
1138
- }, {
1139
- name: 'lt',
1140
- title: '<'
1141
- }, {
1142
- name: 'lte',
1143
- title: '<='
1144
- }],
1145
- time: [{
1146
- name: 'equals',
1147
- title: 'equals'
1148
- }, {
1149
- name: 'notEquals',
1150
- title: 'does not equal'
1151
- }, {
1152
- name: 'inDateRange',
1153
- title: 'in date range'
1154
- }, {
1155
- name: 'notInDateRange',
1156
- title: 'not in date range'
1157
- }, {
1158
- name: 'afterDate',
1159
- title: 'after date'
1160
- }, {
1161
- name: 'afterOrOnDate',
1162
- title: 'after or on date'
1163
- }, {
1164
- name: 'beforeDate',
1165
- title: 'before date'
1166
- }, {
1167
- name: 'beforeOrOnDate',
1168
- title: 'before or on date'
1169
- }]
1170
- };
1171
-
1172
- /**
1173
- * Contains information about available cubes and it's members.
1174
- */
1175
- class Meta {
1176
- constructor(metaResponse) {
1177
- this.meta = metaResponse;
1178
- const {
1179
- cubes
1180
- } = this.meta;
1181
- this.cubes = cubes;
1182
- this.cubesMap = fromPairs(cubes.map(c => [c.name, {
1183
- measures: memberMap(c.measures),
1184
- dimensions: memberMap(c.dimensions),
1185
- segments: memberMap(c.segments)
1186
- }]));
1187
- }
1188
- membersForQuery(query, memberType) {
1189
- return unnest$1(this.cubes.map(c => c[memberType])).sort((a, b) => a.title > b.title ? 1 : -1);
1190
- }
1191
- membersGroupedByCube() {
1192
- const memberKeys = ['measures', 'dimensions', 'segments', 'timeDimensions'];
1193
- return this.cubes.reduce((memo, cube) => {
1194
- memberKeys.forEach(key => {
1195
- let members = cube[key];
1196
- if (key === 'timeDimensions') {
1197
- members = cube.dimensions.filter(m => m.type === 'time');
1198
- }
1199
- memo[key] = [...memo[key], {
1200
- cubeName: cube.name,
1201
- cubeTitle: cube.title,
1202
- type: cube.type,
1203
- public: cube.public,
1204
- members
1205
- }];
1206
- });
1207
- return memo;
1208
- }, {
1209
- measures: [],
1210
- dimensions: [],
1211
- segments: [],
1212
- timeDimensions: []
1213
- });
1214
- }
1215
- resolveMember(memberName, memberType) {
1216
- const [cube] = memberName.split('.');
1217
- if (!this.cubesMap[cube]) {
1218
- return {
1219
- title: memberName,
1220
- error: `Cube not found ${cube} for path '${memberName}'`
1221
- };
1222
- }
1223
- const memberTypes = Array.isArray(memberType) ? memberType : [memberType];
1224
- const member = memberTypes.map(type => this.cubesMap[cube][type] && this.cubesMap[cube][type][memberName]).find(m => m);
1225
- if (!member) {
1226
- return {
1227
- title: memberName,
1228
- error: `Path not found '${memberName}'`
1229
- };
1230
- }
1231
- return member;
1232
- }
1233
- defaultTimeDimensionNameFor(memberName) {
1234
- const [cube] = memberName.split('.');
1235
- if (!this.cubesMap[cube]) {
1236
- return null;
1237
- }
1238
- return Object.keys(this.cubesMap[cube].dimensions || {}).find(d => this.cubesMap[cube].dimensions[d].type === 'time');
1239
- }
1240
- filterOperatorsForMember(memberName, memberType) {
1241
- const member = this.resolveMember(memberName, memberType);
1242
- return operators[member.type] || operators.string;
1243
- }
1244
- }
1245
-
1246
- class ProgressResult {
1247
- constructor(progressResponse) {
1248
- this.progressResponse = progressResponse;
1249
- }
1250
- stage() {
1251
- return this.progressResponse.stage;
1252
- }
1253
- timeElapsed() {
1254
- return this.progressResponse.timeElapsed;
1255
- }
1256
- }
1257
-
1258
- class HttpTransport {
1259
- constructor({
1260
- authorization,
1261
- apiUrl,
1262
- method,
1263
- headers = {},
1264
- credentials,
1265
- fetchTimeout
1266
- }) {
1267
- this.authorization = authorization;
1268
- this.apiUrl = apiUrl;
1269
- this.method = method;
1270
- this.headers = headers;
1271
- this.credentials = credentials;
1272
- this.fetchTimeout = fetchTimeout;
1273
- }
1274
- request(method, {
1275
- baseRequestId,
1276
- ...params
1277
- }) {
1278
- let spanCounter = 1;
1279
- const searchParams = new URLSearchParams(params && Object.keys(params).map(k => ({
1280
- [k]: typeof params[k] === 'object' ? JSON.stringify(params[k]) : params[k]
1281
- })).reduce((a, b) => ({
1282
- ...a,
1283
- ...b
1284
- }), {}));
1285
- let url = `${this.apiUrl}/${method}${searchParams.toString().length ? `?${searchParams}` : ''}`;
1286
- const requestMethod = this.method || (url.length < 2000 ? 'GET' : 'POST');
1287
- if (requestMethod === 'POST') {
1288
- url = `${this.apiUrl}/${method}`;
1289
- this.headers['Content-Type'] = 'application/json';
1290
- }
1291
-
1292
- // Currently, all methods make GET requests. If a method makes a request with a body payload,
1293
- // remember to add {'Content-Type': 'application/json'} to the header.
1294
- const runRequest = () => fetch(url, {
1295
- method: requestMethod,
1296
- headers: {
1297
- Authorization: this.authorization,
1298
- 'x-request-id': baseRequestId && `${baseRequestId}-span-${spanCounter++}`,
1299
- ...this.headers
1300
- },
1301
- credentials: this.credentials,
1302
- body: requestMethod === 'POST' ? JSON.stringify(params) : null,
1303
- signal: this.fetchTimeout ? AbortSignal.timeout(this.fetchTimeout) : undefined
1304
- });
1305
- return {
1306
- /* eslint no-unsafe-finally: off */
1307
- async subscribe(callback) {
1308
- let result = {
1309
- error: 'network Error' // add default error message
1310
- };
1311
- try {
1312
- result = await runRequest();
1313
- } finally {
1314
- return callback(result, () => this.subscribe(callback));
1315
- }
1316
- }
1317
- };
1318
- }
1319
- }
1320
-
1321
- class RequestError extends Error {
1322
- constructor(message, response, status) {
1323
- super(message);
1324
- this.response = response;
1325
- this.status = status;
1326
- }
1327
- }
1328
-
1329
- let mutexCounter = 0;
1330
- const MUTEX_ERROR = 'Mutex has been changed';
1331
-
1332
- /**
1333
- * Query result dataset formats enum.
1334
- */
1335
- const ResultType = {
1336
- DEFAULT: 'default',
1337
- COMPACT: 'compact'
1338
- };
1339
- function mutexPromise(promise) {
1340
- return new Promise(async (resolve, reject) => {
1341
- try {
1342
- resolve(await promise);
1343
- } catch (error) {
1344
- if (error !== MUTEX_ERROR) {
1345
- reject(error);
1346
- }
1347
- }
1348
- });
1349
- }
1350
- class CubeApi {
1351
- constructor(apiToken, options) {
1352
- if (apiToken !== null && !Array.isArray(apiToken) && typeof apiToken === 'object') {
1353
- options = apiToken;
1354
- apiToken = undefined;
1355
- }
1356
- options = options || {};
1357
- if (!options.transport && !options.apiUrl) {
1358
- throw new Error('The `apiUrl` option is required');
1359
- }
1360
- this.apiToken = apiToken;
1361
- this.apiUrl = options.apiUrl;
1362
- this.method = options.method;
1363
- this.headers = options.headers || {};
1364
- this.credentials = options.credentials;
1365
- this.transport = options.transport || new HttpTransport({
1366
- authorization: typeof apiToken === 'function' ? undefined : apiToken,
1367
- apiUrl: this.apiUrl,
1368
- method: this.method,
1369
- headers: this.headers,
1370
- credentials: this.credentials
1371
- });
1372
- this.pollInterval = options.pollInterval || 5;
1373
- this.parseDateMeasures = options.parseDateMeasures;
1374
- this.castNumerics = typeof options.castNumerics === 'boolean' ? options.castNumerics : false;
1375
- this.networkErrorRetries = options.networkErrorRetries || 0;
1376
- this.updateAuthorizationPromise = null;
1377
- }
1378
- request(method, params) {
1379
- return this.transport.request(method, {
1380
- baseRequestId: v4(),
1381
- ...params
1382
- });
1383
- }
1384
- loadMethod(request, toResult, options, callback) {
1385
- const mutexValue = ++mutexCounter;
1386
- if (typeof options === 'function' && !callback) {
1387
- callback = options;
1388
- options = undefined;
1389
- }
1390
- options = options || {};
1391
- const mutexKey = options.mutexKey || 'default';
1392
- if (options.mutexObj) {
1393
- options.mutexObj[mutexKey] = mutexValue;
1394
- }
1395
- const requestPromise = this.updateTransportAuthorization().then(() => request());
1396
- let skipAuthorizationUpdate = true;
1397
- let unsubscribed = false;
1398
- const checkMutex = async () => {
1399
- const requestInstance = await requestPromise;
1400
- if (options.mutexObj && options.mutexObj[mutexKey] !== mutexValue) {
1401
- unsubscribed = true;
1402
- if (requestInstance.unsubscribe) {
1403
- await requestInstance.unsubscribe();
1404
- }
1405
- throw MUTEX_ERROR;
1406
- }
1407
- };
1408
- let networkRetries = this.networkErrorRetries;
1409
- const loadImpl = async (response, next) => {
1410
- const requestInstance = await requestPromise;
1411
- const subscribeNext = async () => {
1412
- if (options.subscribe && !unsubscribed) {
1413
- if (requestInstance.unsubscribe) {
1414
- return next();
1415
- } else {
1416
- await new Promise(resolve => setTimeout(() => resolve(), this.pollInterval * 1000));
1417
- return next();
1418
- }
1419
- }
1420
- return null;
1421
- };
1422
- const continueWait = async wait => {
1423
- if (!unsubscribed) {
1424
- if (wait) {
1425
- await new Promise(resolve => setTimeout(() => resolve(), this.pollInterval * 1000));
1426
- }
1427
- return next();
1428
- }
1429
- return null;
1430
- };
1431
- if (options.subscribe && !skipAuthorizationUpdate) {
1432
- await this.updateTransportAuthorization();
1433
- }
1434
- skipAuthorizationUpdate = false;
1435
- if (response.status === 502 || response.error && response.error.toLowerCase() === 'network error' && --networkRetries >= 0) {
1436
- await checkMutex();
1437
- return continueWait(true);
1438
- }
1439
- let body = {};
1440
- let text = '';
1441
- try {
1442
- text = await response.text();
1443
- body = JSON.parse(text);
1444
- } catch (_) {
1445
- body.error = text;
1446
- }
1447
- if (body.error === 'Continue wait') {
1448
- await checkMutex();
1449
- if (options.progressCallback) {
1450
- options.progressCallback(new ProgressResult(body));
1451
- }
1452
- return continueWait();
1453
- }
1454
- if (response.status !== 200) {
1455
- await checkMutex();
1456
- if (!options.subscribe && requestInstance.unsubscribe) {
1457
- await requestInstance.unsubscribe();
1458
- }
1459
- const error = new RequestError(body.error, body, response.status); // TODO error class
1460
- if (callback) {
1461
- callback(error);
1462
- } else {
1463
- throw error;
1464
- }
1465
- return subscribeNext();
1466
- }
1467
- await checkMutex();
1468
- if (!options.subscribe && requestInstance.unsubscribe) {
1469
- await requestInstance.unsubscribe();
1470
- }
1471
- const result = toResult(body);
1472
- if (callback) {
1473
- callback(null, result);
1474
- } else {
1475
- return result;
1476
- }
1477
- return subscribeNext();
1478
- };
1479
- const promise = requestPromise.then(requestInstance => mutexPromise(requestInstance.subscribe(loadImpl)));
1480
- if (callback) {
1481
- return {
1482
- unsubscribe: async () => {
1483
- const requestInstance = await requestPromise;
1484
- unsubscribed = true;
1485
- if (requestInstance.unsubscribe) {
1486
- return requestInstance.unsubscribe();
1487
- }
1488
- return null;
1489
- }
1490
- };
1491
- } else {
1492
- return promise;
1493
- }
1494
- }
1495
- async updateTransportAuthorization() {
1496
- if (this.updateAuthorizationPromise) {
1497
- await this.updateAuthorizationPromise;
1498
- return;
1499
- }
1500
- if (typeof this.apiToken === 'function') {
1501
- this.updateAuthorizationPromise = new Promise(async (resolve, reject) => {
1502
- try {
1503
- const token = await this.apiToken();
1504
- if (this.transport.authorization !== token) {
1505
- this.transport.authorization = token;
1506
- }
1507
- resolve();
1508
- } catch (error) {
1509
- reject(error);
1510
- } finally {
1511
- this.updateAuthorizationPromise = null;
1512
- }
1513
- });
1514
- await this.updateAuthorizationPromise;
1515
- }
1516
- }
1517
-
1518
- /**
1519
- * Add system properties to a query object.
1520
- * @param {Query} query
1521
- * @param {string} responseFormat
1522
- * @returns {void}
1523
- * @private
1524
- */
1525
- patchQueryInternal(query, responseFormat) {
1526
- if (responseFormat === ResultType.COMPACT && query.responseFormat !== ResultType.COMPACT) {
1527
- return {
1528
- ...query,
1529
- responseFormat: ResultType.COMPACT
1530
- };
1531
- } else {
1532
- return query;
1533
- }
1534
- }
1535
-
1536
- /**
1537
- * Process result fetched from the gateway#load method according
1538
- * to the network protocol.
1539
- * @param {*} response
1540
- * @returns ResultSet
1541
- * @private
1542
- */
1543
- loadResponseInternal(response, options = {}) {
1544
- if (response.results.length) {
1545
- if (options.castNumerics) {
1546
- response.results.forEach(result => {
1547
- const numericMembers = Object.entries({
1548
- ...result.annotation.measures,
1549
- ...result.annotation.dimensions
1550
- }).map(([k, v]) => {
1551
- if (v.type === 'number') {
1552
- return k;
1553
- }
1554
- return undefined;
1555
- }).filter(Boolean);
1556
- result.data = result.data.map(row => {
1557
- numericMembers.forEach(key => {
1558
- if (row[key] != null) {
1559
- row[key] = Number(row[key]);
1560
- }
1561
- });
1562
- return row;
1563
- });
1564
- });
1565
- }
1566
- if (response.results[0].query.responseFormat && response.results[0].query.responseFormat === ResultType.COMPACT) {
1567
- response.results.forEach((result, j) => {
1568
- const data = [];
1569
- result.data.dataset.forEach(r => {
1570
- const row = {};
1571
- result.data.members.forEach((m, i) => {
1572
- row[m] = r[i];
1573
- });
1574
- data.push(row);
1575
- });
1576
- response.results[j].data = data;
1577
- });
1578
- }
1579
- }
1580
- return new ResultSet(response, {
1581
- parseDateMeasures: this.parseDateMeasures
1582
- });
1583
- }
1584
- load(query, options, callback, responseFormat = ResultType.DEFAULT) {
1585
- options = {
1586
- castNumerics: this.castNumerics,
1587
- ...options
1588
- };
1589
- if (responseFormat === ResultType.COMPACT) {
1590
- if (Array.isArray(query)) {
1591
- query = query.map(q => this.patchQueryInternal(q, ResultType.COMPACT));
1592
- } else {
1593
- query = this.patchQueryInternal(query, ResultType.COMPACT);
1594
- }
1595
- }
1596
- return this.loadMethod(() => this.request('load', {
1597
- query,
1598
- queryType: 'multi'
1599
- }), response => this.loadResponseInternal(response, options), options, callback);
1600
- }
1601
- subscribe(query, options, callback, responseFormat = ResultType.DEFAULT) {
1602
- options = {
1603
- castNumerics: this.castNumerics,
1604
- ...options
1605
- };
1606
- if (responseFormat === ResultType.COMPACT) {
1607
- if (Array.isArray(query)) {
1608
- query = query.map(q => this.patchQueryInternal(q, ResultType.COMPACT));
1609
- } else {
1610
- query = this.patchQueryInternal(query, ResultType.COMPACT);
1611
- }
1612
- }
1613
- return this.loadMethod(() => this.request('subscribe', {
1614
- query,
1615
- queryType: 'multi'
1616
- }), response => this.loadResponseInternal(response, options), {
1617
- ...options,
1618
- subscribe: true
1619
- }, callback);
1620
- }
1621
- sql(query, options, callback) {
1622
- return this.loadMethod(() => this.request('sql', {
1623
- query
1624
- }), response => Array.isArray(response) ? response.map(body => new SqlQuery(body)) : new SqlQuery(response), options, callback);
1625
- }
1626
- meta(options, callback) {
1627
- return this.loadMethod(() => this.request('meta'), body => new Meta(body), options, callback);
1628
- }
1629
- dryRun(query, options, callback) {
1630
- return this.loadMethod(() => this.request('dry-run', {
1631
- query
1632
- }), response => response, options, callback);
1633
- }
1634
- }
1635
- var index = (apiToken, options) => new CubeApi(apiToken, options);
1636
-
1637
- export default index;
1638
- export { CubeApi, DEFAULT_GRANULARITY, DateRegex, GRANULARITIES, HttpTransport, LocalDateRegex, Meta, RequestError, ResultSet, TIME_SERIES, addInterval, aliasSeries, areQueriesEqual, dayRange, defaultHeuristics, defaultOrder, diffTimeUnitForInterval, flattenFilters, getOrderMembersFromOrder, getQueryMembers, granularityFor, internalDayjs, isPredefinedGranularity, isQueryPresent, minGranularityForIntervals, moveItemInArray, movePivotItem, parseSqlInterval, removeEmptyQueryFields, subtractInterval, timeSeriesFromCustomInterval, validateQuery };
1639
- //# sourceMappingURL=cubejs-client-core.esm.js.map