Skip to content

Commit 11c1106

Browse files
authored
feat: Parse dates on client side (#522) Thanks to @richipargo!
* Change default order value If you don't set a order value on the intial query format `@cubejs-server/core` doesn't handle empty object * Add date validation * update tests * validate string response values as well
1 parent 964c6d8 commit 11c1106

File tree

2 files changed

+226
-1
lines changed

2 files changed

+226
-1
lines changed

packages/cubejs-client-core/src/ResultSet.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const TIME_SERIES = {
2828
};
2929

3030
const DateRegex = /^\d\d\d\d-\d\d-\d\d$/;
31+
const ISO8601_REGEX = /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
3132

3233
/**
3334
* Provides a convenient interface for data manipulation.
@@ -287,12 +288,24 @@ class ResultSet {
287288
* @param pivotConfig
288289
*/
289290
chartPivot(pivotConfig) {
291+
const validate = (value) => {
292+
if (ISO8601_REGEX.test(value)) {
293+
return new Date(value);
294+
} else if (!Number.isNaN(Number.parseFloat(value))) {
295+
return Number.parseFloat(value);
296+
}
297+
298+
return value;
299+
};
300+
290301
return this.pivot(pivotConfig).map(({ xValues, yValuesArray }) => ({
291302
category: this.axisValuesString(xValues, ', '), // TODO deprecated
292303
x: this.axisValuesString(xValues, ', '),
293304
...(
294305
yValuesArray
295-
.map(([yValues, m]) => ({ [this.axisValuesString(yValues, ', ')]: m && Number.parseFloat(m) }))
306+
.map(([yValues, m]) => ({
307+
[this.axisValuesString(yValues, ', ')]: m && validate(m),
308+
}))
296309
.reduce((a, b) => Object.assign(a, b), {})
297310
)
298311
}));

packages/cubejs-client-core/src/ResultSet.test.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,218 @@ describe('ResultSet', () => {
9090
});
9191
});
9292

93+
describe('chartPivot', () => {
94+
test('String field', () => {
95+
const resultSet = new ResultSet({
96+
query: {
97+
measures: ['Foo.count'],
98+
dimensions: ['Foo.name'],
99+
filters: [],
100+
timezone: 'UTC',
101+
timeDimensions: [],
102+
},
103+
data: [{
104+
'Foo.name': 'Name 1',
105+
'Foo.count': 'Some string',
106+
}],
107+
lastRefreshTime: '2020-03-18T13:41:04.436Z',
108+
usedPreAggregations: {},
109+
annotation: {
110+
measures: {
111+
'Foo.count': {
112+
title: 'Foo Count',
113+
shortTitle: 'Count',
114+
type: 'number',
115+
},
116+
},
117+
dimensions: {
118+
'Foo.name': {
119+
title: 'Foo Name',
120+
shortTitle: 'Name',
121+
type: 'string',
122+
},
123+
},
124+
segments: {},
125+
timeDimensions: {},
126+
},
127+
});
128+
129+
expect(resultSet.chartPivot()).toEqual([{
130+
x: 'Name 1',
131+
category: 'Name 1',
132+
'Foo.count': 'Some string',
133+
}]);
134+
});
135+
136+
test('Null field', () => {
137+
const resultSet = new ResultSet({
138+
query: {
139+
measures: ['Foo.count'],
140+
dimensions: ['Foo.name'],
141+
filters: [],
142+
timezone: 'UTC',
143+
timeDimensions: [],
144+
},
145+
data: [{
146+
'Foo.name': 'Name 1',
147+
'Foo.count': null,
148+
}],
149+
lastRefreshTime: '2020-03-18T13:41:04.436Z',
150+
usedPreAggregations: {},
151+
annotation: {
152+
measures: {
153+
'Foo.count': {
154+
title: 'Foo Count',
155+
shortTitle: 'Count',
156+
type: 'number',
157+
},
158+
},
159+
dimensions: {
160+
'Foo.name': {
161+
title: 'Foo Name',
162+
shortTitle: 'Name',
163+
type: 'string',
164+
},
165+
},
166+
segments: {},
167+
timeDimensions: {},
168+
},
169+
});
170+
171+
expect(resultSet.chartPivot()).toEqual([{
172+
x: 'Name 1',
173+
category: 'Name 1',
174+
'Foo.count': null,
175+
}]);
176+
});
177+
178+
test('Empty field', () => {
179+
const resultSet = new ResultSet({
180+
query: {
181+
measures: ['Foo.count'],
182+
dimensions: ['Foo.name'],
183+
filters: [],
184+
timezone: 'UTC',
185+
timeDimensions: [],
186+
},
187+
data: [{
188+
'Foo.name': 'Name 1',
189+
'Foo.count': undefined,
190+
}],
191+
lastRefreshTime: '2020-03-18T13:41:04.436Z',
192+
usedPreAggregations: {},
193+
annotation: {
194+
measures: {
195+
'Foo.count': {
196+
title: 'Foo Count',
197+
shortTitle: 'Count',
198+
type: 'number',
199+
},
200+
},
201+
dimensions: {
202+
'Foo.name': {
203+
title: 'Foo Name',
204+
shortTitle: 'Name',
205+
type: 'string',
206+
},
207+
},
208+
segments: {},
209+
timeDimensions: {},
210+
},
211+
});
212+
213+
expect(resultSet.chartPivot()).toEqual([{
214+
x: 'Name 1',
215+
category: 'Name 1',
216+
'Foo.count': undefined,
217+
}]);
218+
});
219+
220+
test('Number field', () => {
221+
const resultSet = new ResultSet({
222+
query: {
223+
measures: ['Foo.count'],
224+
dimensions: ['Foo.name'],
225+
filters: [],
226+
timezone: 'UTC',
227+
timeDimensions: [],
228+
},
229+
data: [{
230+
'Foo.name': 'Name 1',
231+
'Foo.count': 10,
232+
}],
233+
lastRefreshTime: '2020-03-18T13:41:04.436Z',
234+
usedPreAggregations: {},
235+
annotation: {
236+
measures: {
237+
'Foo.count': {
238+
title: 'Foo Count',
239+
shortTitle: 'Count',
240+
type: 'number',
241+
},
242+
},
243+
dimensions: {
244+
'Foo.name': {
245+
title: 'Foo Name',
246+
shortTitle: 'Name',
247+
type: 'string',
248+
},
249+
},
250+
segments: {},
251+
timeDimensions: {},
252+
},
253+
});
254+
255+
expect(resultSet.chartPivot()).toEqual([{
256+
x: 'Name 1',
257+
category: 'Name 1',
258+
'Foo.count': 10,
259+
}]);
260+
});
261+
262+
test('time field results', () => {
263+
const resultSet = new ResultSet({
264+
query: {
265+
measures: ['Foo.latestRun'],
266+
dimensions: ['Foo.name'],
267+
filters: [],
268+
timezone: 'UTC',
269+
timeDimensions: [],
270+
},
271+
data: [{
272+
'Foo.name': 'Name 1',
273+
'Foo.latestRun': '2020-03-11T18:06:09.403Z',
274+
}],
275+
lastRefreshTime: '2020-03-18T13:41:04.436Z',
276+
usedPreAggregations: {},
277+
annotation: {
278+
measures: {
279+
'Foo.latestRun': {
280+
title: 'Foo Latest Run',
281+
shortTitle: 'Latest Run',
282+
type: 'number',
283+
},
284+
},
285+
dimensions: {
286+
'Foo.name': {
287+
title: 'Foo Name',
288+
shortTitle: 'Name',
289+
type: 'string',
290+
},
291+
},
292+
segments: {},
293+
timeDimensions: {},
294+
},
295+
});
296+
297+
expect(resultSet.chartPivot()).toEqual([{
298+
x: 'Name 1',
299+
category: 'Name 1',
300+
'Foo.latestRun': new Date('2020-03-11T18:06:09.403Z'),
301+
}]);
302+
});
303+
});
304+
93305
describe('normalizePivotConfig', () => {
94306
test('fills missing x, y', () => {
95307
const resultSet = new ResultSet({

0 commit comments

Comments
 (0)