diff --git a/packages/cubejs-client-core/index.d.ts b/packages/cubejs-client-core/index.d.ts index 0f79e7473fe12..f4c8fbf9a0765 100644 --- a/packages/cubejs-client-core/index.d.ts +++ b/packages/cubejs-client-core/index.d.ts @@ -96,9 +96,13 @@ declare module '@cubejs-client/core' { */ subscribe?: boolean; /** - * A Cube.js API instance. If not provided will be taken from `CubeProvider` + * A Cube API instance. If not provided will be taken from `CubeProvider` */ cubejsApi?: CubejsApi; + /** + * If enabled, all members of the 'number' type will be automatically converted to numerical values on the client side + */ + castNumerics?: boolean; /** * Function that receives `ProgressResult` on each `Continue wait` message. */ diff --git a/packages/cubejs-client-core/src/index.js b/packages/cubejs-client-core/src/index.js index dc21af23222dd..0c213fd345df1 100644 --- a/packages/cubejs-client-core/src/index.js +++ b/packages/cubejs-client-core/src/index.js @@ -257,24 +257,51 @@ class CubejsApi { * @returns ResultSet * @private */ - loadResponseInternal(response) { + loadResponseInternal(response, options = {}) { if ( - response.results.length && - response.results[0].query.responseFormat && - response.results[0].query.responseFormat === ResultType.COMPACT + response.results.length ) { - response.results.forEach((result, j) => { - const data = []; - result.data.dataset.forEach((r) => { - const row = {}; - result.data.members.forEach((m, i) => { - row[m] = r[i]; + if (options.castNumerics) { + response.results.forEach((result) => { + const numericMembers = Object.entries({ + ...result.annotation.measures, + ...result.annotation.dimensions, + }).map(([k, v]) => { + 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; }); - data.push(row); }); - response.results[j].data = data; - }); + } + + if (response.results[0].query.responseFormat && + response.results[0].query.responseFormat === ResultType.COMPACT) { + response.results.forEach((result, j) => { + const data = []; + result.data.dataset.forEach((r) => { + const row = {}; + result.data.members.forEach((m, i) => { + row[m] = r[i]; + }); + data.push(row); + }); + response.results[j].data = data; + }); + } } + return new ResultSet(response, { parseDateMeasures: this.parseDateMeasures }); @@ -293,7 +320,7 @@ class CubejsApi { query, queryType: 'multi', }), - this.loadResponseInternal.bind(this), + (response) => this.loadResponseInternal(response, options), options, callback ); @@ -312,7 +339,7 @@ class CubejsApi { query, queryType: 'multi', }), - this.loadResponseInternal.bind(this), + (response) => this.loadResponseInternal(response, options), { ...options, subscribe: true }, callback ); diff --git a/packages/cubejs-client-react/index.d.ts b/packages/cubejs-client-react/index.d.ts index 5a94af42b104c..c7f0a0818af61 100644 --- a/packages/cubejs-client-react/index.d.ts +++ b/packages/cubejs-client-react/index.d.ts @@ -34,8 +34,13 @@ declare module '@cubejs-client/react' { QueryRecordType, } from '@cubejs-client/core'; + type CubeProviderOptions = { + castNumerics?: boolean; + } + type CubeProviderProps = { cubejsApi: CubejsApi | null; + options?: CubeProviderOptions; children: React.ReactNode; }; @@ -69,6 +74,7 @@ declare module '@cubejs-client/react' { type CubeContextProps = { cubejsApi: CubejsApi; + options?: CubeProviderOptions; }; /** @@ -466,6 +472,10 @@ declare module '@cubejs-client/react' { * When `true` the resultSet will be reset to `null` first */ resetResultSetOnChange?: boolean; + /** + * If enabled, all members of the 'number' type will be automatically converted to numerical values on the client side + */ + castNumerics?: boolean; }; type UseCubeQueryResult = { diff --git a/packages/cubejs-client-react/src/CubeProvider.jsx b/packages/cubejs-client-react/src/CubeProvider.jsx index a638bd217887e..c2e3b2d12fb0e 100644 --- a/packages/cubejs-client-react/src/CubeProvider.jsx +++ b/packages/cubejs-client-react/src/CubeProvider.jsx @@ -1,6 +1,14 @@ import React from 'react'; import CubeContext from './CubeContext'; -export default function CubeProvider({ cubejsApi, children }) { - return {children}; +export default function CubeProvider({ cubejsApi, children, options = {} }) { + return ( + + {children} + + ); } diff --git a/packages/cubejs-client-react/src/hooks/cube-query.js b/packages/cubejs-client-react/src/hooks/cube-query.js index c6a890e00fa79..a6dbbff31d842 100644 --- a/packages/cubejs-client-react/src/hooks/cube-query.js +++ b/packages/cubejs-client-react/src/hooks/cube-query.js @@ -24,7 +24,7 @@ export function useCubeQuery(query, options = {}) { const cubejsApi = options.cubejsApi || context?.cubejsApi; if (!cubejsApi) { - throw new Error('Cube.js API client is not provided'); + throw new Error('Cube API client is not provided'); } if (resetResultSetOnChange) { @@ -33,12 +33,13 @@ export function useCubeQuery(query, options = {}) { setError(null); setLoading(true); - + try { const response = await cubejsApi.load(query, { mutexObj: mutexRef.current, mutexKey: 'query', progressCallback, + castNumerics: Boolean(typeof options.castNumerics === 'boolean' ? options.castNumerics : context?.options?.castNumerics) }); if (isMounted()) { @@ -64,7 +65,7 @@ export function useCubeQuery(query, options = {}) { const cubejsApi = options.cubejsApi || context?.cubejsApi; if (!cubejsApi) { - throw new Error('Cube.js API client is not provided'); + throw new Error('Cube API client is not provided'); } async function loadQuery() {