Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/cubejs-client-core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1303,4 +1303,10 @@ declare module '@cubejs-client/core' {
stage: string;
timeElapsed: number;
};

export function granularityFor(dateStr: string): string;

export function minGranularityForIntervals(i1: string, i2: string): string;

export function isPredefinedGranularity(granularity: TimeDimensionGranularity): boolean;
}
80 changes: 28 additions & 52 deletions packages/cubejs-client-core/src/ResultSet.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,19 @@
import dayjs from 'dayjs';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';

import en from 'dayjs/locale/en';
import {
groupBy, pipe, fromPairs, uniq, filter, map, dropLast, equals, reduce, minBy, maxBy, clone, mergeDeepLeft,
pluck, mergeAll, flatten,
} from 'ramda';

import { aliasSeries } from './utils';

dayjs.extend(quarterOfYear);

// 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)
// So the function below has been added.
const internalDayjs = (...args) => dayjs(...args).locale({ ...en, weekStart: 1 });

export const TIME_SERIES = {
day: (range) => range.by('d').map(d => d.format('YYYY-MM-DDT00:00:00.000')),
month: (range) => range.snapTo('month').by('M').map(d => d.format('YYYY-MM-01T00:00:00.000')),
year: (range) => range.snapTo('year').by('y').map(d => d.format('YYYY-01-01T00:00:00.000')),
hour: (range) => range.by('h').map(d => d.format('YYYY-MM-DDTHH:00:00.000')),
minute: (range) => range.by('m').map(d => d.format('YYYY-MM-DDTHH:mm:00.000')),
second: (range) => range.by('s').map(d => d.format('YYYY-MM-DDTHH:mm:ss.000')),
week: (range) => range.snapTo('week').by('w').map(d => d.startOf('week').format('YYYY-MM-DDT00:00:00.000')),
quarter: (range) => range.snapTo('quarter').by('quarter').map(d => d.startOf('quarter').format('YYYY-MM-DDT00:00:00.000')),
};

const DateRegex = /^\d\d\d\d-\d\d-\d\d$/;
const LocalDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z?$/;
import {
DateRegex,
dayRange,
internalDayjs,
isPredefinedGranularity,
LocalDateRegex,
TIME_SERIES,
timeSeriesFromCustomInterval
} from './time';

const groupByToPairs = (keyFn) => {
const acc = new Map();
Expand Down Expand Up @@ -56,25 +42,6 @@ const unnest = (arr) => {
return res;
};

export const dayRange = (from, to) => ({
by: (value) => {
const results = [];

let start = internalDayjs(from);
const end = internalDayjs(to);

while (start.isBefore(end) || start.isSame(end)) {
results.push(start);
start = start.add(1, value);
}

return results;
},
snapTo: (value) => dayRange(internalDayjs(from).startOf(value), internalDayjs(to).endOf(value)),
start: internalDayjs(from),
end: internalDayjs(to),
});

export const QUERY_TYPE = {
REGULAR_QUERY: 'regularQuery',
COMPARE_DATE_RANGE_QUERY: 'compareDateRangeQuery',
Expand Down Expand Up @@ -163,15 +130,15 @@ class ResultSet {
if (granularity !== undefined) {
const range = dayRange(value, value).snapTo(granularity);
const originalTimeDimension = query.timeDimensions.find((td) => td.dimension);

let dateRange = [
range.start,
range.end
];

if (originalTimeDimension?.dateRange) {
const [originalStart, originalEnd] = originalTimeDimension.dateRange;

dateRange = [
dayjs(originalStart) > range.start ? dayjs(originalStart) : range.start,
dayjs(originalEnd) < range.end ? dayjs(originalEnd) : range.end,
Expand All @@ -195,7 +162,7 @@ class ResultSet {
});
}
});

if (
timeDimensions.length === 0 &&
query.timeDimensions.length > 0 &&
Expand Down Expand Up @@ -321,7 +288,7 @@ class ResultSet {
return ResultSet.getNormalizedPivotConfig(this.loadResponse.pivotQuery, pivotConfig);
}

timeSeries(timeDimension, resultIndex) {
timeSeries(timeDimension, resultIndex, annotations) {
if (!timeDimension.granularity) {
return null;
}
Expand Down Expand Up @@ -352,12 +319,18 @@ class ResultSet {
const [start, end] = dateRange;
const range = dayRange(start, end);

if (!TIME_SERIES[timeDimension.granularity]) {
throw new Error(`Unsupported time granularity: ${timeDimension.granularity}`);
if (isPredefinedGranularity(timeDimension.granularity)) {
return TIME_SERIES[timeDimension.granularity](
padToDay ? range.snapTo('d') : range
);
}

if (!annotations[`${timeDimension.dimension}.${timeDimension.granularity}`]) {
throw new Error(`Granularity "${timeDimension.granularity}" not found in time dimension "${timeDimension.dimension}"`);
}

return TIME_SERIES[timeDimension.granularity](
padToDay ? range.snapTo('d') : range
return timeSeriesFromCustomInterval(
start, end, annotations[`${timeDimension.dimension}.${timeDimension.granularity}`].granularity
);
}

Expand All @@ -381,7 +354,10 @@ class ResultSet {
))
) {
const series = this.loadResponses.map(
(loadResponse) => this.timeSeries(loadResponse.query.timeDimensions[0], resultIndex)
(loadResponse) => this.timeSeries(
loadResponse.query.timeDimensions[0],
resultIndex, loadResponse.annotation.timeDimensions
)
);

if (series[0]) {
Expand Down
11 changes: 6 additions & 5 deletions packages/cubejs-client-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,22 +271,22 @@ class CubeApi {
if (v.type === 'number') {
return k;
}

return undefined;
}).filter(Boolean);

result.data = result.data.map((row) => {
numericMembers.forEach((key) => {
if (row[key] != null) {
row[key] = Number(row[key]);
}
});

return row;
});
});
}

if (response.results[0].query.responseFormat &&
response.results[0].query.responseFormat === ResultType.COMPACT) {
response.results.forEach((result, j) => {
Expand All @@ -302,7 +302,7 @@ class CubeApi {
});
}
}

return new ResultSet(response, {
parseDateMeasures: this.parseDateMeasures
});
Expand Down Expand Up @@ -388,3 +388,4 @@ export default (apiToken, options) => new CubeApi(apiToken, options);

export { CubeApi, HttpTransport, ResultSet, RequestError, Meta };
export * from './utils';
export * from './time';
Loading
Loading