@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.
- package/dist/{cubejs-client-core.js → cubejs-client-core.cjs.js} +1016 -411
- package/dist/cubejs-client-core.cjs.js.map +1 -0
- package/dist/cubejs-client-core.umd.js +2901 -12088
- package/dist/cubejs-client-core.umd.js.map +1 -1
- package/dist/src/HttpTransport.d.ts +54 -0
- package/dist/src/HttpTransport.d.ts.map +1 -0
- package/dist/src/HttpTransport.js +55 -0
- package/dist/src/Meta.d.ts +62 -0
- package/dist/src/Meta.d.ts.map +1 -0
- package/dist/src/Meta.js +150 -0
- package/dist/src/ProgressResult.d.ts +8 -0
- package/dist/src/ProgressResult.d.ts.map +1 -0
- package/dist/src/ProgressResult.js +11 -0
- package/dist/src/RequestError.d.ts +6 -0
- package/dist/src/RequestError.d.ts.map +1 -0
- package/dist/src/RequestError.js +7 -0
- package/dist/src/ResultSet.d.ts +430 -0
- package/dist/src/ResultSet.d.ts.map +1 -0
- package/dist/src/ResultSet.js +952 -0
- package/dist/src/SqlQuery.d.ts +17 -0
- package/dist/src/SqlQuery.d.ts.map +1 -0
- package/dist/src/SqlQuery.js +11 -0
- package/dist/src/index.d.ts +194 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +411 -0
- package/dist/src/index.umd.d.ts +3 -0
- package/dist/src/index.umd.d.ts.map +1 -0
- package/dist/src/index.umd.js +6 -0
- package/dist/src/time.d.ts +70 -0
- package/dist/src/time.d.ts.map +1 -0
- package/dist/src/time.js +249 -0
- package/dist/src/types.d.ts +424 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils.d.ts +19 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +294 -0
- package/dist/test/CubeApi.test.d.ts +7 -0
- package/dist/test/CubeApi.test.d.ts.map +1 -0
- package/dist/test/CubeApi.test.js +279 -0
- package/dist/test/HttpTransport.test.d.ts +2 -0
- package/dist/test/HttpTransport.test.d.ts.map +1 -0
- package/dist/test/HttpTransport.test.js +244 -0
- package/dist/test/ResultSet.test.d.ts +7 -0
- package/dist/test/ResultSet.test.d.ts.map +1 -0
- package/dist/test/ResultSet.test.js +1725 -0
- package/dist/test/compare-date-range.test.d.ts +2 -0
- package/dist/test/compare-date-range.test.d.ts.map +1 -0
- package/dist/test/compare-date-range.test.js +742 -0
- package/dist/test/data-blending.test.d.ts +2 -0
- package/dist/test/data-blending.test.d.ts.map +1 -0
- package/dist/test/data-blending.test.js +423 -0
- package/dist/test/default-heuristics.test.d.ts +2 -0
- package/dist/test/default-heuristics.test.d.ts.map +1 -0
- package/dist/test/default-heuristics.test.js +108 -0
- package/dist/test/drill-down.test.d.ts +2 -0
- package/dist/test/drill-down.test.d.ts.map +1 -0
- package/dist/test/drill-down.test.js +373 -0
- package/dist/test/fixtures/datablending/load-responses.json +261 -0
- package/dist/test/granularity.test.d.ts +2 -0
- package/dist/test/granularity.test.d.ts.map +1 -0
- package/dist/test/granularity.test.js +218 -0
- package/dist/test/helpers.d.ts +283 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/helpers.js +974 -0
- package/dist/test/index.test.d.ts +7 -0
- package/dist/test/index.test.d.ts.map +1 -0
- package/dist/test/index.test.js +370 -0
- package/dist/test/table.test.d.ts +2 -0
- package/dist/test/table.test.d.ts.map +1 -0
- package/dist/test/table.test.js +757 -0
- package/dist/test/utils.test.d.ts +2 -0
- package/dist/test/utils.test.d.ts.map +1 -0
- package/dist/test/utils.test.js +32 -0
- package/package.json +26 -21
- package/dist/cubejs-client-core.esm.js +0 -1639
- package/dist/cubejs-client-core.esm.js.map +0 -1
- package/dist/cubejs-client-core.js.map +0 -1
- package/index.d.ts +0 -1338
- package/src/HttpTransport.js +0 -60
- package/src/HttpTransport.test.js +0 -117
- package/src/Meta.js +0 -142
- package/src/ProgressResult.js +0 -13
- package/src/RequestError.js +0 -7
- package/src/ResultSet.js +0 -746
- package/src/SqlQuery.js +0 -13
- package/src/index.js +0 -398
- package/src/index.test.js +0 -454
- package/src/index.umd.js +0 -8
- package/src/tests/ResultSet.test.js +0 -1655
- package/src/tests/compare-date-range.test.js +0 -753
- package/src/tests/data-blending.test.js +0 -432
- package/src/tests/default-heuristics.test.js +0 -118
- package/src/tests/drill-down.test.js +0 -402
- package/src/tests/fixtures/datablending/load-responses.json +0 -261
- package/src/tests/granularity.test.js +0 -225
- package/src/tests/table.test.js +0 -791
- package/src/tests/utils.test.js +0 -35
- package/src/time.js +0 -296
- 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
|