@cubejs-client/core 1.6.35 → 1.6.37
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/src/format-d3-numeric-locale.d.ts +1 -1
- package/dist/src/format-d3-numeric-locale.d.ts.map +1 -1
- package/dist/src/format-d3-numeric-locale.js +20 -30
- package/dist/src/format.d.ts +10 -1
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.js +100 -50
- package/dist/test/format.test.js +78 -3
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { FormatLocaleDefinition, FormatLocaleObject } from 'd3-format';
|
|
2
|
-
export declare const formatD3NumericLocale: Record<string, FormatLocaleDefinition
|
|
2
|
+
export declare const formatD3NumericLocale: Record<string, Omit<FormatLocaleDefinition, 'currency'>>;
|
|
3
3
|
export declare function getD3NumericLocale(locale: string, currencyCode?: string): FormatLocaleObject;
|
|
4
4
|
//# sourceMappingURL=format-d3-numeric-locale.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-d3-numeric-locale.d.ts","sourceRoot":"","sources":["../../src/format-d3-numeric-locale.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"format-d3-numeric-locale.d.ts","sourceRoot":"","sources":["../../src/format-d3-numeric-locale.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAI5E,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,sBAAsB,EAAE,UAAU,CAAC,CAc1F,CAAC;AA8DF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,SAAQ,GAAG,kBAAkB,CAyB3F"}
|
|
@@ -1,33 +1,20 @@
|
|
|
1
1
|
import { formatLocale } from 'd3-format';
|
|
2
|
-
import enUS from 'd3-format/locale/en-US.json';
|
|
3
|
-
import enGB from 'd3-format/locale/en-GB.json';
|
|
4
|
-
import zhCN from 'd3-format/locale/zh-CN.json';
|
|
5
|
-
import esES from 'd3-format/locale/es-ES.json';
|
|
6
|
-
import esMX from 'd3-format/locale/es-MX.json';
|
|
7
|
-
import deDE from 'd3-format/locale/de-DE.json';
|
|
8
|
-
import jaJP from 'd3-format/locale/ja-JP.json';
|
|
9
|
-
import frFR from 'd3-format/locale/fr-FR.json';
|
|
10
|
-
import ptBR from 'd3-format/locale/pt-BR.json';
|
|
11
|
-
import koKR from 'd3-format/locale/ko-KR.json';
|
|
12
|
-
import itIT from 'd3-format/locale/it-IT.json';
|
|
13
|
-
import nlNL from 'd3-format/locale/nl-NL.json';
|
|
14
|
-
import ruRU from 'd3-format/locale/ru-RU.json';
|
|
15
2
|
// Pre-built d3 locale definitions for the most popular locales.
|
|
16
3
|
// Used as a fallback when Intl is unavailable (e.g. some edge runtimes).
|
|
17
4
|
export const formatD3NumericLocale = {
|
|
18
|
-
'en-US':
|
|
19
|
-
'en-GB':
|
|
20
|
-
'zh-CN':
|
|
21
|
-
'es-ES':
|
|
22
|
-
'es-MX':
|
|
23
|
-
'de-DE':
|
|
24
|
-
'ja-JP':
|
|
25
|
-
'fr-FR':
|
|
26
|
-
'pt-BR':
|
|
27
|
-
'ko-KR':
|
|
28
|
-
'it-IT':
|
|
29
|
-
'nl-NL':
|
|
30
|
-
'ru-RU':
|
|
5
|
+
'en-US': { decimal: '.', thousands: ',', grouping: [3] },
|
|
6
|
+
'en-GB': { decimal: '.', thousands: ',', grouping: [3] },
|
|
7
|
+
'zh-CN': { decimal: '.', thousands: ',', grouping: [3] },
|
|
8
|
+
'es-ES': { decimal: ',', thousands: '.', grouping: [3] },
|
|
9
|
+
'es-MX': { decimal: '.', thousands: ',', grouping: [3] },
|
|
10
|
+
'de-DE': { decimal: ',', thousands: '.', grouping: [3] },
|
|
11
|
+
'ja-JP': { decimal: '.', thousands: ',', grouping: [3] },
|
|
12
|
+
'fr-FR': { decimal: ',', thousands: '\u00a0', grouping: [3], percent: '\u202f%' },
|
|
13
|
+
'pt-BR': { decimal: ',', thousands: '.', grouping: [3] },
|
|
14
|
+
'ko-KR': { decimal: '.', thousands: ',', grouping: [3] },
|
|
15
|
+
'it-IT': { decimal: ',', thousands: '.', grouping: [3] },
|
|
16
|
+
'nl-NL': { decimal: ',', thousands: '.', grouping: [3] },
|
|
17
|
+
'ru-RU': { decimal: ',', thousands: '\u00a0', grouping: [3] },
|
|
31
18
|
};
|
|
32
19
|
const currencySymbols = {
|
|
33
20
|
USD: '$',
|
|
@@ -39,7 +26,7 @@ const currencySymbols = {
|
|
|
39
26
|
INR: '₹',
|
|
40
27
|
RUB: '₽',
|
|
41
28
|
};
|
|
42
|
-
function
|
|
29
|
+
function getCurrencyOverride(locale, currencyCode) {
|
|
43
30
|
try {
|
|
44
31
|
const cf = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyCode });
|
|
45
32
|
const currencyParts = cf.formatToParts(1);
|
|
@@ -76,7 +63,7 @@ function getD3NumericLocaleFromIntl(locale, currencyCode = 'USD') {
|
|
|
76
63
|
decimal: find('decimal') || '.',
|
|
77
64
|
thousands: find('group') || ',',
|
|
78
65
|
grouping: deriveGrouping(locale),
|
|
79
|
-
currency:
|
|
66
|
+
currency: getCurrencyOverride(locale, currencyCode),
|
|
80
67
|
};
|
|
81
68
|
}
|
|
82
69
|
const localeCache = Object.create(null);
|
|
@@ -87,7 +74,7 @@ export function getD3NumericLocale(locale, currencyCode = 'USD') {
|
|
|
87
74
|
}
|
|
88
75
|
let definition;
|
|
89
76
|
if (formatD3NumericLocale[locale]) {
|
|
90
|
-
definition = { ...formatD3NumericLocale[locale], currency:
|
|
77
|
+
definition = { ...formatD3NumericLocale[locale], currency: getCurrencyOverride(locale, currencyCode) };
|
|
91
78
|
}
|
|
92
79
|
else {
|
|
93
80
|
try {
|
|
@@ -95,7 +82,10 @@ export function getD3NumericLocale(locale, currencyCode = 'USD') {
|
|
|
95
82
|
}
|
|
96
83
|
catch (e) {
|
|
97
84
|
console.warn('Failed to generate d3 local via Intl, failing back to en-US', e);
|
|
98
|
-
definition =
|
|
85
|
+
definition = {
|
|
86
|
+
...formatD3NumericLocale['en-US'],
|
|
87
|
+
currency: getCurrencyOverride(locale, currencyCode)
|
|
88
|
+
};
|
|
99
89
|
}
|
|
100
90
|
}
|
|
101
91
|
localeCache[key] = formatLocale(definition);
|
package/dist/src/format.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DimensionFormat, MeasureFormat, TCubeMemberType } from './types';
|
|
2
|
+
export declare function formatDateByGranularity(value: Date | string | number, granularity?: string): string;
|
|
2
3
|
export type FormatValueMember = {
|
|
3
4
|
type: TCubeMemberType;
|
|
4
5
|
format?: DimensionFormat | MeasureFormat;
|
|
@@ -13,5 +14,13 @@ export type FormatValueOptions = FormatValueMember & {
|
|
|
13
14
|
/** String to return for null/undefined values. Defaults to '∅'. */
|
|
14
15
|
emptyPlaceholder?: string;
|
|
15
16
|
};
|
|
16
|
-
export
|
|
17
|
+
export type GetFormatOptions = {
|
|
18
|
+
locale?: string;
|
|
19
|
+
};
|
|
20
|
+
export type GetFormatResult = {
|
|
21
|
+
formatString: string | null;
|
|
22
|
+
formatFunc: (value: any) => string;
|
|
23
|
+
};
|
|
24
|
+
export declare function getFormat(member: FormatValueMember, { locale }?: GetFormatOptions): GetFormatResult;
|
|
25
|
+
export declare function formatValue(value: any, options: FormatValueOptions): string;
|
|
17
26
|
//# sourceMappingURL=format.d.ts.map
|
package/dist/src/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/format.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAoE/E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAOnG;AAUD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,CAAC,EAAE,eAAe,GAAG,aAAa,CAAC;IACzC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yHAAyH;IACzH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,2GAA2G;IAC3G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC;CACpC,CAAC;AAwBF,wBAAgB,SAAS,CACvB,MAAM,EAAE,iBAAiB,EACzB,EAAE,MAAsB,EAAE,GAAE,gBAAqB,GAChD,eAAe,CA0EjB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,kBAAkB,GAC1B,MAAM,CAQR"}
|
package/dist/src/format.js
CHANGED
|
@@ -19,26 +19,45 @@ function detectLocale() {
|
|
|
19
19
|
const currentLocale = detectLocale();
|
|
20
20
|
const DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S';
|
|
21
21
|
const DEFAULT_DATE_FORMAT = '%Y-%m-%d';
|
|
22
|
-
const
|
|
22
|
+
const DEFAULT_DATE_WEEK_FORMAT = '%Y-%m-%d W%V';
|
|
23
|
+
const DEFAULT_DATE_MONTH_FORMAT = '%Y %b';
|
|
23
24
|
const DEFAULT_DATE_QUARTER_FORMAT = '%Y-Q%q';
|
|
24
25
|
const DEFAULT_DATE_YEAR_FORMAT = '%Y';
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
case 'quarter':
|
|
33
|
-
return DEFAULT_DATE_QUARTER_FORMAT;
|
|
34
|
-
case 'year':
|
|
35
|
-
return DEFAULT_DATE_YEAR_FORMAT;
|
|
36
|
-
case 'second':
|
|
37
|
-
case 'minute':
|
|
38
|
-
case 'hour':
|
|
39
|
-
default:
|
|
40
|
-
return DEFAULT_DATETIME_FORMAT;
|
|
26
|
+
function getFormatByGrain(grain) {
|
|
27
|
+
// Grains that should show date and time (sub-day granularities)
|
|
28
|
+
const dateTimeGrains = ['second', 'minute', 'hour'];
|
|
29
|
+
// Grains that should show date only (day and above granularities)
|
|
30
|
+
const dateOnlyGrains = ['day', 'week', 'month', 'quarter', 'year'];
|
|
31
|
+
if (grain === 'day') {
|
|
32
|
+
return DEFAULT_DATE_FORMAT;
|
|
41
33
|
}
|
|
34
|
+
if (grain === 'week') {
|
|
35
|
+
return DEFAULT_DATE_WEEK_FORMAT;
|
|
36
|
+
}
|
|
37
|
+
if (grain === 'month') {
|
|
38
|
+
return DEFAULT_DATE_MONTH_FORMAT;
|
|
39
|
+
}
|
|
40
|
+
if (grain === 'quarter') {
|
|
41
|
+
return DEFAULT_DATE_QUARTER_FORMAT;
|
|
42
|
+
}
|
|
43
|
+
if (grain === 'year') {
|
|
44
|
+
return DEFAULT_DATE_YEAR_FORMAT;
|
|
45
|
+
}
|
|
46
|
+
if (!grain || dateTimeGrains.includes(grain)) {
|
|
47
|
+
return DEFAULT_DATETIME_FORMAT;
|
|
48
|
+
}
|
|
49
|
+
if (dateOnlyGrains.includes(grain)) {
|
|
50
|
+
return DEFAULT_DATE_FORMAT;
|
|
51
|
+
}
|
|
52
|
+
// Fallback to datetime for unknown grains
|
|
53
|
+
return DEFAULT_DATETIME_FORMAT;
|
|
54
|
+
}
|
|
55
|
+
export function formatDateByGranularity(value, granularity) {
|
|
56
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
57
|
+
if (Number.isNaN(date.getTime())) {
|
|
58
|
+
return 'Invalid date';
|
|
59
|
+
}
|
|
60
|
+
return timeFormat(getFormatByGrain(granularity))(date);
|
|
42
61
|
}
|
|
43
62
|
function parseNumber(value) {
|
|
44
63
|
if (value === null || value === undefined) {
|
|
@@ -46,63 +65,94 @@ function parseNumber(value) {
|
|
|
46
65
|
}
|
|
47
66
|
return parseFloat(value);
|
|
48
67
|
}
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
return
|
|
68
|
+
function formatBoolean(value) {
|
|
69
|
+
if (typeof value === 'boolean') {
|
|
70
|
+
return value.toString();
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === 'number') {
|
|
73
|
+
return Boolean(value).toString();
|
|
74
|
+
}
|
|
75
|
+
// Some SQL drivers return booleans as '0'/'1' or 'true'/'false' strings, It's incorrect behaivour in Cube,
|
|
76
|
+
// but let's format it as boolean for backward compatibility.
|
|
77
|
+
if (value === '0' || value === 'false') {
|
|
78
|
+
return 'false';
|
|
52
79
|
}
|
|
80
|
+
if (value === '1' || value === 'true') {
|
|
81
|
+
return 'true';
|
|
82
|
+
}
|
|
83
|
+
return String(value);
|
|
84
|
+
}
|
|
85
|
+
export function getFormat(member, { locale = currentLocale } = {}) {
|
|
86
|
+
const { type, format, currency = 'USD', granularity } = member;
|
|
53
87
|
if (type === 'boolean') {
|
|
54
|
-
|
|
55
|
-
return value.toString();
|
|
56
|
-
}
|
|
57
|
-
if (typeof value === 'number') {
|
|
58
|
-
return Boolean(value).toString();
|
|
59
|
-
}
|
|
60
|
-
// Some SQL drivers return booleans as '0'/'1' or 'true'/'false' strings, It's incorrect behaivour in Cube,
|
|
61
|
-
// but let's format it as boolean for backward compatibility.
|
|
62
|
-
if (value === '0' || value === 'false') {
|
|
63
|
-
return 'false';
|
|
64
|
-
}
|
|
65
|
-
if (value === '1' || value === 'true') {
|
|
66
|
-
return 'true';
|
|
67
|
-
}
|
|
68
|
-
return String(value);
|
|
88
|
+
return { formatString: null, formatFunc: formatBoolean };
|
|
69
89
|
}
|
|
70
90
|
if (format && typeof format === 'object') {
|
|
71
91
|
if (format.type === 'custom-numeric') {
|
|
72
|
-
return
|
|
92
|
+
return {
|
|
93
|
+
formatString: format.value,
|
|
94
|
+
formatFunc: (value) => d3Format(format.value)(parseNumber(value)),
|
|
95
|
+
};
|
|
73
96
|
}
|
|
74
97
|
if (format.type === 'custom-time') {
|
|
75
|
-
|
|
76
|
-
|
|
98
|
+
return {
|
|
99
|
+
formatString: format.value,
|
|
100
|
+
formatFunc: (value) => {
|
|
101
|
+
const date = new Date(value);
|
|
102
|
+
return Number.isNaN(date.getTime()) ? 'Invalid date' : timeFormat(format.value)(date);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
77
105
|
}
|
|
78
106
|
// { type: 'link', label: string } — return value as string
|
|
79
|
-
return String(value);
|
|
107
|
+
return { formatString: null, formatFunc: (value) => String(value) };
|
|
80
108
|
}
|
|
81
109
|
if (typeof format === 'string') {
|
|
82
110
|
switch (format) {
|
|
83
111
|
case 'currency':
|
|
84
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
formatString: DEFAULT_CURRENCY_FORMAT,
|
|
114
|
+
formatFunc: (value) => getD3NumericLocale(locale, currency).format(DEFAULT_CURRENCY_FORMAT)(parseNumber(value)),
|
|
115
|
+
};
|
|
85
116
|
case 'percent':
|
|
86
|
-
return
|
|
117
|
+
return {
|
|
118
|
+
formatString: DEFAULT_PERCENT_FORMAT,
|
|
119
|
+
formatFunc: (value) => getD3NumericLocale(locale).format(DEFAULT_PERCENT_FORMAT)(parseNumber(value)),
|
|
120
|
+
};
|
|
87
121
|
case 'number':
|
|
88
|
-
return
|
|
122
|
+
return {
|
|
123
|
+
formatString: DEFAULT_NUMBER_FORMAT,
|
|
124
|
+
formatFunc: (value) => getD3NumericLocale(locale).format(DEFAULT_NUMBER_FORMAT)(parseNumber(value)),
|
|
125
|
+
};
|
|
89
126
|
case 'id':
|
|
90
|
-
return
|
|
127
|
+
return {
|
|
128
|
+
formatString: DEFAULT_ID_FORMAT,
|
|
129
|
+
formatFunc: (value) => d3Format(DEFAULT_ID_FORMAT)(parseNumber(value)),
|
|
130
|
+
};
|
|
91
131
|
case 'imageUrl':
|
|
92
132
|
case 'link':
|
|
93
133
|
default:
|
|
94
|
-
return String(value);
|
|
134
|
+
return { formatString: null, formatFunc: (value) => String(value) };
|
|
95
135
|
}
|
|
96
136
|
}
|
|
97
137
|
// No explicit format — infer from type
|
|
98
138
|
if (type === 'time') {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
139
|
+
return {
|
|
140
|
+
formatString: getFormatByGrain(granularity),
|
|
141
|
+
formatFunc: (value) => formatDateByGranularity(value, granularity),
|
|
142
|
+
};
|
|
103
143
|
}
|
|
104
144
|
if (type === 'number') {
|
|
105
|
-
return
|
|
145
|
+
return {
|
|
146
|
+
formatString: DEFAULT_NUMBER_FORMAT,
|
|
147
|
+
formatFunc: (value) => getD3NumericLocale(locale, currency).format(DEFAULT_NUMBER_FORMAT)(parseNumber(value)),
|
|
148
|
+
};
|
|
106
149
|
}
|
|
107
|
-
return String(value);
|
|
150
|
+
return { formatString: null, formatFunc: (value) => String(value) };
|
|
151
|
+
}
|
|
152
|
+
export function formatValue(value, options) {
|
|
153
|
+
const { emptyPlaceholder = '∅' } = options;
|
|
154
|
+
if (value === null || value === undefined) {
|
|
155
|
+
return emptyPlaceholder;
|
|
156
|
+
}
|
|
157
|
+
return getFormat(options, { locale: options.locale }).formatFunc(value);
|
|
108
158
|
}
|
package/dist/test/format.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { formatValue } from '../src/format';
|
|
2
|
+
import { formatValue, formatDateByGranularity, getFormat } from '../src/format';
|
|
3
3
|
describe('formatValue', () => {
|
|
4
4
|
it('format null', () => {
|
|
5
5
|
expect(formatValue(null, { type: 'number' })).toBe('∅');
|
|
@@ -53,11 +53,12 @@ describe('formatValue', () => {
|
|
|
53
53
|
});
|
|
54
54
|
it('type-based fallback: time with grain', () => {
|
|
55
55
|
expect(formatValue('2024-03-15T00:00:00.000', { type: 'time', granularity: 'day' })).toBe('2024-03-15');
|
|
56
|
-
expect(formatValue('2024-03-01T00:00:00.000', { type: 'time', granularity: 'month' })).toBe('2024
|
|
56
|
+
expect(formatValue('2024-03-01T00:00:00.000', { type: 'time', granularity: 'month' })).toBe('2024 Mar');
|
|
57
57
|
expect(formatValue('2024-01-01T00:00:00.000', { type: 'time', granularity: 'year' })).toBe('2024');
|
|
58
|
-
expect(formatValue('2024-03-11T00:00:00.000', { type: 'time', granularity: 'week' })).toBe('2024-03-11');
|
|
58
|
+
expect(formatValue('2024-03-11T00:00:00.000', { type: 'time', granularity: 'week' })).toBe('2024-03-11 W11');
|
|
59
59
|
expect(formatValue('2024-03-01T00:00:00.000', { type: 'time', granularity: 'quarter' })).toBe('2024-Q1');
|
|
60
60
|
expect(formatValue('2024-03-15T14:00:00.000', { type: 'time', granularity: 'hour' })).toBe('2024-03-15 14:00:00');
|
|
61
|
+
expect(formatValue('2024-03-15T14:30:00.000', { type: 'time', granularity: 'minute' })).toBe('2024-03-15 14:30:00');
|
|
61
62
|
expect(formatValue('2024-03-15T14:30:45.000', { type: 'time' })).toBe('2024-03-15 14:30:45');
|
|
62
63
|
});
|
|
63
64
|
it('format with nl-NL locale', () => {
|
|
@@ -99,3 +100,77 @@ describe('formatValue', () => {
|
|
|
99
100
|
expect(formatValue('0', { type: 'boolean' })).toBe('false');
|
|
100
101
|
});
|
|
101
102
|
});
|
|
103
|
+
describe('formatDateByGranularity', () => {
|
|
104
|
+
it('formats each predefined granularity', () => {
|
|
105
|
+
const iso = '2024-03-15T14:30:45.000';
|
|
106
|
+
expect(formatDateByGranularity(iso, 'second')).toBe('2024-03-15 14:30:45');
|
|
107
|
+
expect(formatDateByGranularity(iso, 'minute')).toBe('2024-03-15 14:30:45');
|
|
108
|
+
expect(formatDateByGranularity(iso, 'hour')).toBe('2024-03-15 14:30:45');
|
|
109
|
+
expect(formatDateByGranularity(iso, 'day')).toBe('2024-03-15');
|
|
110
|
+
expect(formatDateByGranularity(iso, 'week')).toBe('2024-03-15 W11');
|
|
111
|
+
expect(formatDateByGranularity(iso, 'month')).toBe('2024 Mar');
|
|
112
|
+
expect(formatDateByGranularity(iso, 'quarter')).toBe('2024-Q1');
|
|
113
|
+
expect(formatDateByGranularity(iso, 'year')).toBe('2024');
|
|
114
|
+
});
|
|
115
|
+
it('accepts Date, ISO string, and epoch-number inputs', () => {
|
|
116
|
+
const date = new Date('2024-03-15T00:00:00.000');
|
|
117
|
+
expect(formatDateByGranularity(date, 'day')).toBe('2024-03-15');
|
|
118
|
+
expect(formatDateByGranularity(date.getTime(), 'day')).toBe('2024-03-15');
|
|
119
|
+
expect(formatDateByGranularity('2024-03-15T00:00:00.000', 'day')).toBe('2024-03-15');
|
|
120
|
+
});
|
|
121
|
+
it('falls back to second-grain format for missing or unknown granularity', () => {
|
|
122
|
+
expect(formatDateByGranularity('2024-03-15T14:30:45.000')).toBe('2024-03-15 14:30:45');
|
|
123
|
+
expect(formatDateByGranularity('2024-03-15T14:30:45.000', 'decade')).toBe('2024-03-15 14:30:45');
|
|
124
|
+
});
|
|
125
|
+
it('returns "Invalid date" on bad input', () => {
|
|
126
|
+
expect(formatDateByGranularity('not-a-date', 'day')).toBe('Invalid date');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('getFormat', () => {
|
|
130
|
+
it('time dimension: returns d3 format string per granularity', () => {
|
|
131
|
+
expect(getFormat({ type: 'time', granularity: 'day' }).formatString).toBe('%Y-%m-%d');
|
|
132
|
+
expect(getFormat({ type: 'time', granularity: 'month' }).formatString).toBe('%Y %b');
|
|
133
|
+
expect(getFormat({ type: 'time', granularity: 'year' }).formatString).toBe('%Y');
|
|
134
|
+
expect(getFormat({ type: 'time', granularity: 'hour' }).formatString).toBe('%Y-%m-%d %H:%M:%S');
|
|
135
|
+
expect(getFormat({ type: 'time' }).formatString).toBe('%Y-%m-%d %H:%M:%S');
|
|
136
|
+
});
|
|
137
|
+
it('time dimension: formatFunc delegates to formatDateByGranularity', () => {
|
|
138
|
+
const { formatFunc } = getFormat({ type: 'time', granularity: 'month' });
|
|
139
|
+
expect(formatFunc('2024-03-01T00:00:00.000')).toBe('2024 Mar');
|
|
140
|
+
});
|
|
141
|
+
it('number with currency format', () => {
|
|
142
|
+
const { formatString, formatFunc } = getFormat({ type: 'number', format: 'currency' });
|
|
143
|
+
expect(formatString).toBe('$,.2f');
|
|
144
|
+
expect(formatFunc(1234.56)).toBe('$1,234.56');
|
|
145
|
+
expect(formatFunc('1234.56')).toBe('$1,234.56');
|
|
146
|
+
});
|
|
147
|
+
it('number with percent format', () => {
|
|
148
|
+
const { formatString, formatFunc } = getFormat({ type: 'number', format: 'percent' });
|
|
149
|
+
expect(formatString).toBe('.2%');
|
|
150
|
+
expect(formatFunc(0.1234)).toBe('12.34%');
|
|
151
|
+
});
|
|
152
|
+
it('number with no explicit format falls back to default number format', () => {
|
|
153
|
+
const { formatString, formatFunc } = getFormat({ type: 'number' });
|
|
154
|
+
expect(formatString).toBe(',.2f');
|
|
155
|
+
expect(formatFunc(1234.56)).toBe('1,234.56');
|
|
156
|
+
});
|
|
157
|
+
it('custom-numeric format exposes the spec as formatString', () => {
|
|
158
|
+
const { formatString, formatFunc } = getFormat({ type: 'number', format: { type: 'custom-numeric', value: '.2s' } });
|
|
159
|
+
expect(formatString).toBe('.2s');
|
|
160
|
+
expect(formatFunc(1500)).toBe('1.5k');
|
|
161
|
+
});
|
|
162
|
+
it('custom-time format exposes the spec as formatString', () => {
|
|
163
|
+
const { formatString, formatFunc } = getFormat({ type: 'time', format: { type: 'custom-time', value: '%Y-%m-%d' } });
|
|
164
|
+
expect(formatString).toBe('%Y-%m-%d');
|
|
165
|
+
expect(formatFunc('2024-03-15T10:30:00.000')).toBe('2024-03-15');
|
|
166
|
+
});
|
|
167
|
+
it('string fallback returns identity formatFunc', () => {
|
|
168
|
+
const { formatString, formatFunc } = getFormat({ type: 'string' });
|
|
169
|
+
expect(formatString).toBeNull();
|
|
170
|
+
expect(formatFunc('hello')).toBe('hello');
|
|
171
|
+
});
|
|
172
|
+
it('locale option is honored by formatFunc', () => {
|
|
173
|
+
const { formatFunc } = getFormat({ type: 'number', format: 'currency', currency: 'EUR' }, { locale: 'nl-NL' });
|
|
174
|
+
expect(formatFunc(1234.56)).toBe('€1.234,56');
|
|
175
|
+
});
|
|
176
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubejs-client/core",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.37",
|
|
4
4
|
"engines": {},
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
],
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@cubejs-backend/linter": "1.6.
|
|
58
|
+
"@cubejs-backend/linter": "1.6.37",
|
|
59
59
|
"@types/d3-format": "^3",
|
|
60
60
|
"@types/d3-time-format": "^4",
|
|
61
61
|
"@types/moment-range": "^4.0.0",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"eslintConfig": {
|
|
68
68
|
"extends": "../cubejs-linter"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "e4ef0024dd5136329f8b3743c4ee83dd844cee86"
|
|
71
71
|
}
|