@cubejs-client/core 0.36.2 → 1.0.0

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/index.d.ts CHANGED
@@ -1303,4 +1303,10 @@ declare module '@cubejs-client/core' {
1303
1303
  stage: string;
1304
1304
  timeElapsed: number;
1305
1305
  };
1306
+
1307
+ export function granularityFor(dateStr: string): string;
1308
+
1309
+ export function minGranularityForIntervals(i1: string, i2: string): string;
1310
+
1311
+ export function isPredefinedGranularity(granularity: TimeDimensionGranularity): boolean;
1306
1312
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubejs-client/core",
3
- "version": "0.36.2",
3
+ "version": "1.0.0",
4
4
  "engines": {},
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,5 +45,5 @@
45
45
  "eslint-plugin-node": "^5.2.1",
46
46
  "jest": "^27"
47
47
  },
48
- "gitHead": "ab3771bfce10eaabafff99404250a3681be79137"
48
+ "gitHead": "a8f48f289ebf11ce3b0480cf3713ba060c7e0270"
49
49
  }
package/src/ResultSet.js CHANGED
@@ -1,33 +1,19 @@
1
1
  import dayjs from 'dayjs';
2
- import quarterOfYear from 'dayjs/plugin/quarterOfYear';
3
-
4
- import en from 'dayjs/locale/en';
5
2
  import {
6
3
  groupBy, pipe, fromPairs, uniq, filter, map, dropLast, equals, reduce, minBy, maxBy, clone, mergeDeepLeft,
7
4
  pluck, mergeAll, flatten,
8
5
  } from 'ramda';
9
6
 
10
7
  import { aliasSeries } from './utils';
11
-
12
- dayjs.extend(quarterOfYear);
13
-
14
- // When granularity is week, weekStart Value must be 1. However, since the client can change it globally (https://day.js.org/docs/en/i18n/changing-locale)
15
- // So the function below has been added.
16
- const internalDayjs = (...args) => dayjs(...args).locale({ ...en, weekStart: 1 });
17
-
18
- export const TIME_SERIES = {
19
- day: (range) => range.by('d').map(d => d.format('YYYY-MM-DDT00:00:00.000')),
20
- month: (range) => range.snapTo('month').by('M').map(d => d.format('YYYY-MM-01T00:00:00.000')),
21
- year: (range) => range.snapTo('year').by('y').map(d => d.format('YYYY-01-01T00:00:00.000')),
22
- hour: (range) => range.by('h').map(d => d.format('YYYY-MM-DDTHH:00:00.000')),
23
- minute: (range) => range.by('m').map(d => d.format('YYYY-MM-DDTHH:mm:00.000')),
24
- second: (range) => range.by('s').map(d => d.format('YYYY-MM-DDTHH:mm:ss.000')),
25
- week: (range) => range.snapTo('week').by('w').map(d => d.startOf('week').format('YYYY-MM-DDT00:00:00.000')),
26
- quarter: (range) => range.snapTo('quarter').by('quarter').map(d => d.startOf('quarter').format('YYYY-MM-DDT00:00:00.000')),
27
- };
28
-
29
- const DateRegex = /^\d\d\d\d-\d\d-\d\d$/;
30
- const LocalDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z?$/;
8
+ import {
9
+ DateRegex,
10
+ dayRange,
11
+ internalDayjs,
12
+ isPredefinedGranularity,
13
+ LocalDateRegex,
14
+ TIME_SERIES,
15
+ timeSeriesFromCustomInterval
16
+ } from './time';
31
17
 
32
18
  const groupByToPairs = (keyFn) => {
33
19
  const acc = new Map();
@@ -56,25 +42,6 @@ const unnest = (arr) => {
56
42
  return res;
57
43
  };
58
44
 
59
- export const dayRange = (from, to) => ({
60
- by: (value) => {
61
- const results = [];
62
-
63
- let start = internalDayjs(from);
64
- const end = internalDayjs(to);
65
-
66
- while (start.isBefore(end) || start.isSame(end)) {
67
- results.push(start);
68
- start = start.add(1, value);
69
- }
70
-
71
- return results;
72
- },
73
- snapTo: (value) => dayRange(internalDayjs(from).startOf(value), internalDayjs(to).endOf(value)),
74
- start: internalDayjs(from),
75
- end: internalDayjs(to),
76
- });
77
-
78
45
  export const QUERY_TYPE = {
79
46
  REGULAR_QUERY: 'regularQuery',
80
47
  COMPARE_DATE_RANGE_QUERY: 'compareDateRangeQuery',
@@ -163,15 +130,15 @@ class ResultSet {
163
130
  if (granularity !== undefined) {
164
131
  const range = dayRange(value, value).snapTo(granularity);
165
132
  const originalTimeDimension = query.timeDimensions.find((td) => td.dimension);
166
-
133
+
167
134
  let dateRange = [
168
135
  range.start,
169
136
  range.end
170
137
  ];
171
-
138
+
172
139
  if (originalTimeDimension?.dateRange) {
173
140
  const [originalStart, originalEnd] = originalTimeDimension.dateRange;
174
-
141
+
175
142
  dateRange = [
176
143
  dayjs(originalStart) > range.start ? dayjs(originalStart) : range.start,
177
144
  dayjs(originalEnd) < range.end ? dayjs(originalEnd) : range.end,
@@ -195,7 +162,7 @@ class ResultSet {
195
162
  });
196
163
  }
197
164
  });
198
-
165
+
199
166
  if (
200
167
  timeDimensions.length === 0 &&
201
168
  query.timeDimensions.length > 0 &&
@@ -321,7 +288,7 @@ class ResultSet {
321
288
  return ResultSet.getNormalizedPivotConfig(this.loadResponse.pivotQuery, pivotConfig);
322
289
  }
323
290
 
324
- timeSeries(timeDimension, resultIndex) {
291
+ timeSeries(timeDimension, resultIndex, annotations) {
325
292
  if (!timeDimension.granularity) {
326
293
  return null;
327
294
  }
@@ -352,12 +319,18 @@ class ResultSet {
352
319
  const [start, end] = dateRange;
353
320
  const range = dayRange(start, end);
354
321
 
355
- if (!TIME_SERIES[timeDimension.granularity]) {
356
- throw new Error(`Unsupported time granularity: ${timeDimension.granularity}`);
322
+ if (isPredefinedGranularity(timeDimension.granularity)) {
323
+ return TIME_SERIES[timeDimension.granularity](
324
+ padToDay ? range.snapTo('d') : range
325
+ );
326
+ }
327
+
328
+ if (!annotations[`${timeDimension.dimension}.${timeDimension.granularity}`]) {
329
+ throw new Error(`Granularity "${timeDimension.granularity}" not found in time dimension "${timeDimension.dimension}"`);
357
330
  }
358
331
 
359
- return TIME_SERIES[timeDimension.granularity](
360
- padToDay ? range.snapTo('d') : range
332
+ return timeSeriesFromCustomInterval(
333
+ start, end, annotations[`${timeDimension.dimension}.${timeDimension.granularity}`].granularity
361
334
  );
362
335
  }
363
336
 
@@ -381,7 +354,10 @@ class ResultSet {
381
354
  ))
382
355
  ) {
383
356
  const series = this.loadResponses.map(
384
- (loadResponse) => this.timeSeries(loadResponse.query.timeDimensions[0], resultIndex)
357
+ (loadResponse) => this.timeSeries(
358
+ loadResponse.query.timeDimensions[0],
359
+ resultIndex, loadResponse.annotation.timeDimensions
360
+ )
385
361
  );
386
362
 
387
363
  if (series[0]) {
package/src/index.js CHANGED
@@ -271,22 +271,22 @@ class CubeApi {
271
271
  if (v.type === 'number') {
272
272
  return k;
273
273
  }
274
-
274
+
275
275
  return undefined;
276
276
  }).filter(Boolean);
277
-
277
+
278
278
  result.data = result.data.map((row) => {
279
279
  numericMembers.forEach((key) => {
280
280
  if (row[key] != null) {
281
281
  row[key] = Number(row[key]);
282
282
  }
283
283
  });
284
-
284
+
285
285
  return row;
286
286
  });
287
287
  });
288
288
  }
289
-
289
+
290
290
  if (response.results[0].query.responseFormat &&
291
291
  response.results[0].query.responseFormat === ResultType.COMPACT) {
292
292
  response.results.forEach((result, j) => {
@@ -302,7 +302,7 @@ class CubeApi {
302
302
  });
303
303
  }
304
304
  }
305
-
305
+
306
306
  return new ResultSet(response, {
307
307
  parseDateMeasures: this.parseDateMeasures
308
308
  });
@@ -388,3 +388,4 @@ export default (apiToken, options) => new CubeApi(apiToken, options);
388
388
 
389
389
  export { CubeApi, HttpTransport, ResultSet, RequestError, Meta };
390
390
  export * from './utils';
391
+ export * from './time';
@@ -102,6 +102,303 @@ describe('ResultSet', () => {
102
102
  ];
103
103
  expect(resultSet.timeSeries(timeDimension)).toEqual(output);
104
104
  });
105
+
106
+ test('it generates array of dates - custom interval - 1 year, origin - 2020-01-01', () => {
107
+ const resultSet = new ResultSet({});
108
+ const timeDimension = {
109
+ dateRange: ['2021-01-01', '2023-12-31'],
110
+ granularity: 'one_year',
111
+ dimension: 'Events.time'
112
+ };
113
+ const output = [
114
+ '2021-01-01T00:00:00.000',
115
+ '2022-01-01T00:00:00.000',
116
+ '2023-01-01T00:00:00.000'
117
+ ];
118
+ expect(resultSet.timeSeries(timeDimension, 1, {
119
+ 'Events.time.one_year': {
120
+ title: 'Time Dimension',
121
+ shortTitle: 'TD',
122
+ type: 'time',
123
+ granularity: {
124
+ name: '1 year',
125
+ title: '1 year',
126
+ interval: '1 year',
127
+ origin: '2020-01-01',
128
+ },
129
+ },
130
+ })).toEqual(output);
131
+ });
132
+
133
+ test('it generates array of dates - custom interval - 1 year, origin - 2025-03-01', () => {
134
+ const resultSet = new ResultSet({});
135
+ const timeDimension = {
136
+ dateRange: ['2021-01-01', '2022-12-31'],
137
+ granularity: 'one_year',
138
+ dimension: 'Events.time'
139
+ };
140
+ const output = [
141
+ '2020-03-01T00:00:00.000',
142
+ '2021-03-01T00:00:00.000',
143
+ '2022-03-01T00:00:00.000',
144
+ ];
145
+ expect(resultSet.timeSeries(timeDimension, 1, {
146
+ 'Events.time.one_year': {
147
+ title: 'Time Dimension',
148
+ shortTitle: 'TD',
149
+ type: 'time',
150
+ granularity: {
151
+ name: '1 year',
152
+ title: '1 year',
153
+ interval: '1 year',
154
+ origin: '2025-03-01',
155
+ },
156
+ },
157
+ })).toEqual(output);
158
+ });
159
+
160
+ test('it generates array of dates - custom interval - 1 year, offset - 2 months', () => {
161
+ const resultSet = new ResultSet({});
162
+ const timeDimension = {
163
+ dateRange: ['2021-01-01', '2022-12-31'],
164
+ granularity: 'one_year',
165
+ dimension: 'Events.time'
166
+ };
167
+ const output = [
168
+ '2020-03-01T00:00:00.000',
169
+ '2021-03-01T00:00:00.000',
170
+ '2022-03-01T00:00:00.000',
171
+ ];
172
+ expect(resultSet.timeSeries(timeDimension, 1, {
173
+ 'Events.time.one_year': {
174
+ title: 'Time Dimension',
175
+ shortTitle: 'TD',
176
+ type: 'time',
177
+ granularity: {
178
+ name: '1 year',
179
+ title: '1 year',
180
+ interval: '1 year',
181
+ offset: '2 months',
182
+ },
183
+ },
184
+ })).toEqual(output);
185
+ });
186
+
187
+ test('it generates array of dates - custom interval - 2 months, origin - 2019-01-01', () => {
188
+ const resultSet = new ResultSet({});
189
+ const timeDimension = {
190
+ dateRange: ['2021-01-01', '2021-12-31'],
191
+ granularity: 'two_months',
192
+ dimension: 'Events.time'
193
+ };
194
+ const output = [
195
+ '2021-01-01T00:00:00.000',
196
+ '2021-03-01T00:00:00.000',
197
+ '2021-05-01T00:00:00.000',
198
+ '2021-07-01T00:00:00.000',
199
+ '2021-09-01T00:00:00.000',
200
+ '2021-11-01T00:00:00.000',
201
+ ];
202
+ expect(resultSet.timeSeries(timeDimension, 1, {
203
+ 'Events.time.two_months': {
204
+ title: 'Time Dimension',
205
+ shortTitle: 'TD',
206
+ type: 'time',
207
+ granularity: {
208
+ name: '2 months',
209
+ title: '2 months',
210
+ interval: '2 months',
211
+ origin: '2019-01-01',
212
+ },
213
+ },
214
+ })).toEqual(output);
215
+ });
216
+
217
+ test('it generates array of dates - custom interval - 2 months, no offset', () => {
218
+ const resultSet = new ResultSet({});
219
+ const timeDimension = {
220
+ dateRange: ['2021-01-01', '2021-12-31'],
221
+ granularity: 'two_months',
222
+ dimension: 'Events.time'
223
+ };
224
+ const output = [
225
+ '2021-01-01T00:00:00.000',
226
+ '2021-03-01T00:00:00.000',
227
+ '2021-05-01T00:00:00.000',
228
+ '2021-07-01T00:00:00.000',
229
+ '2021-09-01T00:00:00.000',
230
+ '2021-11-01T00:00:00.000',
231
+ ];
232
+ expect(resultSet.timeSeries(timeDimension, 1, {
233
+ 'Events.time.two_months': {
234
+ title: 'Time Dimension',
235
+ shortTitle: 'TD',
236
+ type: 'time',
237
+ granularity: {
238
+ name: '2 months',
239
+ title: '2 months',
240
+ interval: '2 months',
241
+ },
242
+ },
243
+ })).toEqual(output);
244
+ });
245
+
246
+ test('it generates array of dates - custom interval - 2 months, origin - 2019-03-15', () => {
247
+ const resultSet = new ResultSet({});
248
+ const timeDimension = {
249
+ dateRange: ['2021-01-01', '2021-12-31'],
250
+ granularity: 'two_months',
251
+ dimension: 'Events.time'
252
+ };
253
+ const output = [
254
+ '2020-11-15T00:00:00.000',
255
+ '2021-01-15T00:00:00.000',
256
+ '2021-03-15T00:00:00.000',
257
+ '2021-05-15T00:00:00.000',
258
+ '2021-07-15T00:00:00.000',
259
+ '2021-09-15T00:00:00.000',
260
+ '2021-11-15T00:00:00.000',
261
+ ];
262
+ expect(resultSet.timeSeries(timeDimension, 1, {
263
+ 'Events.time.two_months': {
264
+ title: 'Time Dimension',
265
+ shortTitle: 'TD',
266
+ type: 'time',
267
+ granularity: {
268
+ name: '2 months',
269
+ title: '2 months',
270
+ interval: '2 months',
271
+ origin: '2019-03-15',
272
+ },
273
+ },
274
+ })).toEqual(output);
275
+ });
276
+
277
+ test('it generates array of dates - custom interval - 1 months 2 weeks 3 days, origin - 2021-01-25', () => {
278
+ const resultSet = new ResultSet({});
279
+ const timeDimension = {
280
+ dateRange: ['2021-01-01', '2021-12-31'],
281
+ granularity: 'one_mo_two_we_three_d',
282
+ dimension: 'Events.time'
283
+ };
284
+ const output = [
285
+ '2020-12-08T00:00:00.000',
286
+ '2021-01-25T00:00:00.000',
287
+ '2021-03-14T00:00:00.000',
288
+ '2021-05-01T00:00:00.000',
289
+ '2021-06-18T00:00:00.000',
290
+ '2021-08-04T00:00:00.000',
291
+ '2021-09-21T00:00:00.000',
292
+ '2021-11-07T00:00:00.000',
293
+ '2021-12-24T00:00:00.000',
294
+ ];
295
+ expect(resultSet.timeSeries(timeDimension, 1, {
296
+ 'Events.time.one_mo_two_we_three_d': {
297
+ title: 'Time Dimension',
298
+ shortTitle: 'TD',
299
+ type: 'time',
300
+ granularity: {
301
+ name: '1 months 2 weeks 3 days',
302
+ title: '1 months 2 weeks 3 days',
303
+ interval: '1 months 2 weeks 3 days',
304
+ origin: '2021-01-25',
305
+ },
306
+ },
307
+ })).toEqual(output);
308
+ });
309
+
310
+ test('it generates array of dates - custom interval - 3 weeks, origin - 2020-12-15', () => {
311
+ const resultSet = new ResultSet({});
312
+ const timeDimension = {
313
+ dateRange: ['2021-01-01', '2021-03-01'],
314
+ granularity: 'three_weeks',
315
+ dimension: 'Events.time'
316
+ };
317
+ const output = [
318
+ '2020-12-15T00:00:00.000',
319
+ '2021-01-05T00:00:00.000',
320
+ '2021-01-26T00:00:00.000',
321
+ '2021-02-16T00:00:00.000',
322
+ ];
323
+ expect(resultSet.timeSeries(timeDimension, 1, {
324
+ 'Events.time.three_weeks': {
325
+ title: 'Time Dimension',
326
+ shortTitle: 'TD',
327
+ type: 'time',
328
+ granularity: {
329
+ name: '3 weeks',
330
+ title: '3 weeks',
331
+ interval: '3 weeks',
332
+ origin: '2020-12-15',
333
+ },
334
+ },
335
+ })).toEqual(output);
336
+ });
337
+
338
+ test('it generates array of dates - custom interval - 2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds, origin - 2021-01-01', () => {
339
+ const resultSet = new ResultSet({});
340
+ const timeDimension = {
341
+ dateRange: ['2021-01-01', '2021-12-31'],
342
+ granularity: 'two_mo_3w_4d_5h_6m_7s',
343
+ dimension: 'Events.time'
344
+ };
345
+ const output = [
346
+ '2021-01-01T00:00:00.000',
347
+ '2021-03-26T05:06:07.000',
348
+ '2021-06-20T10:12:14.000',
349
+ '2021-09-14T15:18:21.000',
350
+ '2021-12-09T20:24:28.000',
351
+ ];
352
+ expect(resultSet.timeSeries(timeDimension, 1, {
353
+ 'Events.time.two_mo_3w_4d_5h_6m_7s': {
354
+ title: 'Time Dimension',
355
+ shortTitle: 'TD',
356
+ type: 'time',
357
+ granularity: {
358
+ name: 'two_mo_3w_4d_5h_6m_7s',
359
+ title: 'two_mo_3w_4d_5h_6m_7s',
360
+ interval: '2 months 3 weeks 4 days 5 hours 6 minutes 7 seconds',
361
+ origin: '2021-01-01',
362
+ },
363
+ },
364
+ })).toEqual(output);
365
+ });
366
+
367
+ test('it generates array of dates - custom interval - 10 minutes 15 seconds, origin - 2021-02-01 09:59:45', () => {
368
+ const resultSet = new ResultSet({});
369
+ const timeDimension = {
370
+ dateRange: ['2021-02-01 10:00:00', '2021-02-01 12:00:00'],
371
+ granularity: 'ten_min_fifteen_sec',
372
+ dimension: 'Events.time'
373
+ };
374
+ const output = [
375
+ '2021-02-01T09:59:45.000',
376
+ '2021-02-01T10:10:00.000',
377
+ '2021-02-01T10:20:15.000',
378
+ '2021-02-01T10:30:30.000',
379
+ '2021-02-01T10:40:45.000',
380
+ '2021-02-01T10:51:00.000',
381
+ '2021-02-01T11:01:15.000',
382
+ '2021-02-01T11:11:30.000',
383
+ '2021-02-01T11:21:45.000',
384
+ '2021-02-01T11:32:00.000',
385
+ '2021-02-01T11:42:15.000',
386
+ '2021-02-01T11:52:30.000',
387
+ ];
388
+ expect(resultSet.timeSeries(timeDimension, 1, {
389
+ 'Events.time.ten_min_fifteen_sec': {
390
+ title: 'Time Dimension',
391
+ shortTitle: 'TD',
392
+ type: 'time',
393
+ granularity: {
394
+ name: '10 minutes 15 seconds',
395
+ title: '10 minutes 15 seconds',
396
+ interval: '10 minutes 15 seconds',
397
+ origin: '2021-02-01 09:59:45',
398
+ },
399
+ },
400
+ })).toEqual(output);
401
+ });
105
402
  });
106
403
 
107
404
  describe('chartPivot', () => {
@@ -145,7 +442,7 @@ describe('ResultSet', () => {
145
442
  expect(resultSet.chartPivot()).toEqual([
146
443
  {
147
444
  x: 'Name 1',
148
-
445
+
149
446
  'Foo.count': 'Some string',
150
447
  xValues: [
151
448
  'Name 1'
@@ -194,7 +491,7 @@ describe('ResultSet', () => {
194
491
  expect(resultSet.chartPivot()).toEqual([
195
492
  {
196
493
  x: 'Name 1',
197
-
494
+
198
495
  'Foo.count': 0,
199
496
  xValues: [
200
497
  'Name 1'
@@ -291,7 +588,7 @@ describe('ResultSet', () => {
291
588
  expect(resultSet.chartPivot()).toEqual([
292
589
  {
293
590
  x: 'Name 1',
294
-
591
+
295
592
  'Foo.count': 10,
296
593
  xValues: [
297
594
  'Name 1'
@@ -343,7 +640,7 @@ describe('ResultSet', () => {
343
640
  expect(resultSet.chartPivot()).toEqual([
344
641
  {
345
642
  x: 'Name 1',
346
-
643
+
347
644
  'Foo.latestRun': new Date('2020-03-11T18:06:09.403Z'),
348
645
  xValues: [
349
646
  'Name 1'
@@ -1,3 +1,5 @@
1
+ /* globals describe,test,expect */
2
+
1
3
  import 'jest';
2
4
  import dayjs from 'dayjs';
3
5
  import ko from 'dayjs/locale/ko';
@@ -1,7 +1,7 @@
1
1
  import 'jest';
2
2
 
3
- import { TIME_SERIES, dayRange } from '../ResultSet';
4
3
  import { defaultOrder } from '../utils';
4
+ import { dayRange, TIME_SERIES } from '../time';
5
5
 
6
6
  describe('utils', () => {
7
7
  test('default order', () => {