@cubejs-client/react 1.7.13 → 1.7.14

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.
Files changed (62) hide show
  1. package/dist/CubeContext.d.ts +35 -0
  2. package/dist/CubeContext.d.ts.map +1 -0
  3. package/dist/CubeProvider.d.ts +31 -0
  4. package/dist/CubeProvider.d.ts.map +1 -0
  5. package/dist/QueryBuilder.d.ts +135 -0
  6. package/dist/QueryBuilder.d.ts.map +1 -0
  7. package/dist/QueryRenderer.d.ts +34 -0
  8. package/dist/QueryRenderer.d.ts.map +1 -0
  9. package/dist/QueryRendererWithTotals.d.ts +4 -0
  10. package/dist/QueryRendererWithTotals.d.ts.map +1 -0
  11. package/dist/cubejs-client-react.cjs.js +248 -23
  12. package/dist/cubejs-client-react.cjs.js.map +1 -1
  13. package/dist/cubejs-client-react.esm.js +253 -31
  14. package/dist/cubejs-client-react.esm.js.map +1 -1
  15. package/dist/cubejs-client-react.umd.js +248 -23
  16. package/dist/cubejs-client-react.umd.js.map +1 -1
  17. package/dist/hooks/cube-fetch.d.ts +6 -0
  18. package/dist/hooks/cube-fetch.d.ts.map +1 -0
  19. package/dist/hooks/cube-meta.d.ts +4 -0
  20. package/dist/hooks/cube-meta.d.ts.map +1 -0
  21. package/dist/hooks/cube-query.d.ts +39 -0
  22. package/dist/hooks/cube-query.d.ts.map +1 -0
  23. package/dist/hooks/cube-sql.d.ts +12 -0
  24. package/dist/hooks/cube-sql.d.ts.map +1 -0
  25. package/dist/hooks/deep-compare-memoize.d.ts +2 -0
  26. package/dist/hooks/deep-compare-memoize.d.ts.map +1 -0
  27. package/dist/hooks/dry-run.d.ts +7 -0
  28. package/dist/hooks/dry-run.d.ts.map +1 -0
  29. package/dist/hooks/lazy-dry-run.d.ts +7 -0
  30. package/dist/hooks/lazy-dry-run.d.ts.map +1 -0
  31. package/dist/index.d.ts +21 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/types.d.ts +765 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/utils.d.ts +2 -0
  36. package/dist/utils.d.ts.map +1 -0
  37. package/package.json +14 -10
  38. package/src/CubeContext.ts +37 -0
  39. package/src/CubeProvider.tsx +42 -0
  40. package/src/{QueryBuilder.jsx → QueryBuilder.tsx} +238 -89
  41. package/src/{QueryRenderer.jsx → QueryRenderer.tsx} +39 -14
  42. package/src/QueryRendererWithTotals.tsx +24 -0
  43. package/src/hooks/cube-fetch.ts +98 -0
  44. package/src/hooks/cube-meta.ts +8 -0
  45. package/src/hooks/{cube-query.js → cube-query.ts} +90 -14
  46. package/src/hooks/cube-sql.ts +22 -0
  47. package/src/hooks/{deep-compare-memoize.js → deep-compare-memoize.ts} +2 -2
  48. package/src/hooks/dry-run.ts +17 -0
  49. package/src/hooks/lazy-dry-run.ts +24 -0
  50. package/src/index.ts +77 -0
  51. package/src/types.ts +837 -0
  52. package/src/{utils.js → utils.ts} +3 -3
  53. package/index.d.ts +0 -697
  54. package/src/CubeContext.js +0 -3
  55. package/src/CubeProvider.jsx +0 -14
  56. package/src/QueryRendererWithTotals.jsx +0 -20
  57. package/src/hooks/cube-fetch.js +0 -66
  58. package/src/hooks/cube-meta.js +0 -5
  59. package/src/hooks/cube-sql.js +0 -8
  60. package/src/hooks/dry-run.js +0 -8
  61. package/src/hooks/lazy-dry-run.js +0 -11
  62. package/src/index.js +0 -18
@@ -2,8 +2,45 @@ import React, { createContext, useRef, useContext, useState, useEffect } from 'r
2
2
  import { equals, toPairs, fromPairs, uniqBy, prop, indexBy, uniq, pick, clone } from 'ramda';
3
3
  import { isQueryPresent, getQueryMembers, flattenFilters, moveItemInArray, movePivotItem, removeEmptyQueryFields, defaultOrder, ResultSet, validateQuery, defaultHeuristics, areQueriesEqual } from '@cubejs-client/core';
4
4
 
5
+ /**
6
+ * In case when you need direct access to `cubeApi` you can use `CubeContext` anywhere in your app
7
+ *
8
+ * ```js
9
+ * import React from 'react';
10
+ * import { CubeContext } from '@cubejs-client/react';
11
+ *
12
+ * export default function DisplayComponent() {
13
+ * const { cubeApi } = React.useContext(CubeContext);
14
+ * const [rawResults, setRawResults] = React.useState([]);
15
+ * const query = {
16
+ * ...
17
+ * };
18
+ *
19
+ * React.useEffect(() => {
20
+ * cubeApi.load(query).then((resultSet) => {
21
+ * setRawResults(resultSet.rawData());
22
+ * });
23
+ * }, [query]);
24
+ *
25
+ * return (
26
+ * <>
27
+ * {rawResults.map(row => (
28
+ * ...
29
+ * ))}
30
+ * </>
31
+ * )
32
+ * }
33
+ * ```
34
+ */
35
+ // The context has no default value: `cubeApi` is only available under a
36
+ // `CubeProvider`, and consumers guard against a missing context.
5
37
  var CubeContext = /*#__PURE__*/createContext(null);
6
38
 
39
+ /**
40
+ * `<QueryRenderer />` a react component that accepts a query, fetches the given query, and uses the render prop to render the resulting data
41
+ * @stickyTypes QueryRendererProps, QueryRendererRenderProps
42
+ * @noInheritDoc
43
+ */
7
44
  class QueryRenderer extends React.Component {
8
45
  static contextType = CubeContext;
9
46
  static defaultProps = {
@@ -64,6 +101,16 @@ class QueryRenderer extends React.Component {
64
101
  this.loadQueries(queries);
65
102
  }
66
103
  }
104
+
105
+ // These sit after the lifecycle methods because `react/sort-comp` sorts
106
+ // TypeScript field declarations into `everything-else`, which the configured
107
+ // order puts after `lifecycle`. Runtime is unaffected: field initializers run
108
+ // right after `super()`, so the constructor's assignments still win.
109
+ //
110
+ // `this.context` is not re-declared: React types it as `any`, and a field
111
+ // declaration would be emitted at runtime and shadow the context React
112
+ // assigns.
113
+
67
114
  cubeApi() {
68
115
  // eslint-disable-next-line react/destructuring-assignment
69
116
  return this.props.cubeApi || this.context && this.context.cubeApi;
@@ -191,6 +238,8 @@ class QueryRenderer extends React.Component {
191
238
  sqlQuery
192
239
  };
193
240
  if (render) {
241
+ // The prop is declared as returning `void` for backwards compatibility,
242
+ // while what it returns is what gets rendered
194
243
  return render(loadState);
195
244
  }
196
245
  return null;
@@ -212,6 +261,7 @@ const QueryRendererWithTotals = ({
212
261
  ...restProps
213
262
  }) => /*#__PURE__*/React.createElement(QueryRenderer, _extends({
214
263
  queries: {
264
+ // `granularity: null` is sent to the API on purpose to get totals
215
265
  totals: {
216
266
  ...query,
217
267
  dimensions: [],
@@ -264,6 +314,71 @@ const granularities = [{
264
314
  name: 'year',
265
315
  title: 'Year'
266
316
  }];
317
+
318
+ /**
319
+ * `<QueryBuilder />` is used to build interactive analytics query builders. It abstracts state management and API calls to Cube.js Backend. It uses render prop technique and doesn’t render anything itself, but calls the render function instead.
320
+ *
321
+ * **Example**
322
+ *
323
+ * [Open in CodeSandbox](https://codesandbox.io/s/z6r7qj8wm)
324
+ * ```js
325
+ * import React from 'react';
326
+ * import ReactDOM from 'react-dom';
327
+ * import { Layout, Divider, Empty, Select } from 'antd';
328
+ * import { QueryBuilder } from '@cubejs-client/react';
329
+ * import cube from '@cubejs-client/core';
330
+ * import 'antd/dist/antd.css';
331
+ *
332
+ * import ChartRenderer from './ChartRenderer';
333
+ *
334
+ * const cubeApi = cube('YOUR-CUBE-API-TOKEN', {
335
+ * apiUrl: 'http://localhost:4000/cubejs-api/v1',
336
+ * });
337
+ *
338
+ * const App = () => (
339
+ * <QueryBuilder
340
+ * query={{
341
+ * timeDimensions: [
342
+ * {
343
+ * dimension: 'LineItems.createdAt',
344
+ * granularity: 'month',
345
+ * },
346
+ * ],
347
+ * }}
348
+ * cubeApi={cubeApi}
349
+ * render={({ resultSet, measures, availableMeasures, updateMeasures }) => (
350
+ * <Layout.Content style={{ padding: '20px' }}>
351
+ * <Select
352
+ * mode="multiple"
353
+ * style={{ width: '100%' }}
354
+ * placeholder="Please select"
355
+ * onSelect={(measure) => updateMeasures.add(measure)}
356
+ * onDeselect={(measure) => updateMeasures.remove(measure)}
357
+ * >
358
+ * {availableMeasures.map((measure) => (
359
+ * <Select.Option key={measure.name} value={measure}>
360
+ * {measure.title}
361
+ * </Select.Option>
362
+ * ))}
363
+ * </Select>
364
+ * <Divider />
365
+ * {measures.length > 0 ? (
366
+ * <ChartRenderer resultSet={resultSet} />
367
+ * ) : (
368
+ * <Empty description="Select measure or dimension to get started" />
369
+ * )}
370
+ * </Layout.Content>
371
+ * )}
372
+ * />
373
+ * );
374
+ *
375
+ * const rootElement = document.getElementById("root");
376
+ * ReactDOM.render(<App />, rootElement);
377
+ * ```
378
+ * @stickyTypes QueryBuilderProps, QueryBuilderRenderProps, QueryBuilderState
379
+ * @noInheritDoc
380
+ * @order 2
381
+ */
267
382
  class QueryBuilder extends React.Component {
268
383
  static contextType = CubeContext;
269
384
  static defaultProps = {
@@ -371,7 +486,7 @@ class QueryBuilder extends React.Component {
371
486
  const newMeta = await this.cubeApi().meta();
372
487
  if (!equals(newMeta, meta) && typeof onSchemaChange === 'function') {
373
488
  onSchemaChange({
374
- schemaVersion,
489
+ schemaVersion: schemaVersion,
375
490
  refresh: async () => {
376
491
  await this.fetchMeta();
377
492
  }
@@ -385,6 +500,16 @@ class QueryBuilder extends React.Component {
385
500
  }
386
501
  }
387
502
  }
503
+
504
+ // These sit after the lifecycle methods because `react/sort-comp` sorts
505
+ // TypeScript field declarations into `everything-else`, which the configured
506
+ // order puts after `lifecycle`. Runtime is unaffected: field initializers run
507
+ // right after `super()`, so the constructor's assignments still win.
508
+ //
509
+ // `this.context` is not re-declared: React types it as `any`, and a field
510
+ // declaration would be emitted at runtime and shadow the context React
511
+ // assigns.
512
+
388
513
  fetchMeta = async () => {
389
514
  if (!this.cubeApi()) {
390
515
  return;
@@ -399,9 +524,10 @@ class QueryBuilder extends React.Component {
399
524
  });
400
525
  meta = await this.cubeApi().meta();
401
526
  } catch (error) {
402
- metaError = error.response?.plainError || error;
403
- richMetaError = error;
404
- metaErrorStack = error.response?.stack?.replace(error.message || '', '') || '';
527
+ const requestError = error;
528
+ metaError = requestError.response?.plainError || requestError;
529
+ richMetaError = requestError;
530
+ metaErrorStack = requestError.response?.stack?.replace(requestError.message || '', '') || '';
405
531
  }
406
532
  this.setState({
407
533
  meta,
@@ -537,6 +663,9 @@ class QueryBuilder extends React.Component {
537
663
  const indexedMeasures = indexBy(prop('cubeName'), availableMembers.measures);
538
664
  const indexedDimensions = indexBy(prop('cubeName'), availableMembers.dimensions);
539
665
  const cubeNames = uniq([...Object.keys(indexedMeasures), ...Object.keys(indexedDimensions)]).sort();
666
+
667
+ // Measures and dimensions of a cube are merged into one member list, which
668
+ // the declared type describes as either one or the other
540
669
  availableFilterMembers = cubeNames.map(name => {
541
670
  const cube = indexedMeasures[name] || indexedDimensions[name];
542
671
  return {
@@ -577,12 +706,14 @@ class QueryBuilder extends React.Component {
577
706
  query,
578
707
  error: queryError,
579
708
  // Match same name as QueryRenderer prop
580
- validatedQuery,
709
+ validatedQuery: validatedQuery,
581
710
  isQueryPresent: this.isQueryPresent(),
582
711
  chartType,
583
712
  measures,
584
713
  dimensions,
585
- timeDimensions,
714
+ // The declared granularity options require a `name`, which the
715
+ // "w/o grouping" option leaves out
716
+ timeDimensions: timeDimensions,
586
717
  segments,
587
718
  filters,
588
719
  orderMembers,
@@ -667,7 +798,7 @@ class QueryBuilder extends React.Component {
667
798
  missingMembers,
668
799
  refresh: this.fetchMeta,
669
800
  isFetchingMeta,
670
- dryRunResponse,
801
+ dryRunResponse: dryRunResponse,
671
802
  ...queryRendererProps
672
803
  };
673
804
  }
@@ -767,9 +898,10 @@ class QueryBuilder extends React.Component {
767
898
  });
768
899
  }
769
900
  } catch (error) {
901
+ const requestError = error;
770
902
  this.setState({
771
- queryError: new Error(error.response?.plainError || error.message),
772
- richQueryError: new Error(error.message || error.toString())
903
+ queryError: new Error(requestError.response?.plainError || requestError.message),
904
+ richQueryError: new Error(requestError.message || requestError.toString())
773
905
  });
774
906
  }
775
907
  }
@@ -788,7 +920,7 @@ class QueryBuilder extends React.Component {
788
920
  meta
789
921
  } = this.state;
790
922
  return defaultHeuristics(newState, query, {
791
- meta,
923
+ meta: meta,
792
924
  sessionGranularity: sessionGranularity || 'day'
793
925
  });
794
926
  }
@@ -832,18 +964,42 @@ class QueryBuilder extends React.Component {
832
964
  }
833
965
  }
834
966
 
835
- function CubeProvider({
967
+ /**
968
+ * Cube.js context provider
969
+ * ```js
970
+ * import React from 'react';
971
+ * import cube from '@cubejs-client/core';
972
+ * import { CubeProvider } from '@cubejs-client/react';
973
+ *
974
+ * const API_URL = 'https://harsh-eel.aws-us-east-2.cubecloudapp.dev';
975
+ * const CUBE_TOKEN =
976
+ * 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.* eyJpYXQiOjE1OTE3MDcxNDgsImV4cCI6MTU5NDI5OTE0OH0.* n5jGLQJ14igg6_Hri_Autx9qOIzVqp4oYxmX27V-4T4';
977
+ *
978
+ * const cubeApi = cube(CUBE_TOKEN, {
979
+ * apiUrl: `${API_URL}/cubejs-api/v1`,
980
+ * });
981
+ *
982
+ * export default function App() {
983
+ * return (
984
+ * <CubeProvider cubeApi={cubeApi}>
985
+ * //...
986
+ * </CubeProvider>
987
+ * )
988
+ * }
989
+ * ```
990
+ * @stickyTypes
991
+ * @order 10
992
+ */
993
+ const CubeProvider = ({
836
994
  cubeApi,
837
995
  children,
838
996
  options = {}
839
- }) {
840
- return /*#__PURE__*/React.createElement(CubeContext.Provider, {
841
- value: {
842
- cubeApi,
843
- options
844
- }
845
- }, children);
846
- }
997
+ }) => /*#__PURE__*/React.createElement(CubeContext.Provider, {
998
+ value: {
999
+ cubeApi,
1000
+ options
1001
+ }
1002
+ }, children);
847
1003
 
848
1004
  function useDeepCompareMemoize(value) {
849
1005
  const ref = useRef([]);
@@ -853,6 +1009,10 @@ function useDeepCompareMemoize(value) {
853
1009
  return ref.current;
854
1010
  }
855
1011
 
1012
+ /**
1013
+ * @hidden
1014
+ */
1015
+
856
1016
  function useCubeFetch(method, options = {}) {
857
1017
  const context = useContext(CubeContext);
858
1018
  const mutexRef = useRef({});
@@ -867,6 +1027,7 @@ function useCubeFetch(method, options = {}) {
867
1027
  async function load(loadOptions = {}, ignoreSkip = false) {
868
1028
  const cubeApi = options.cubeApi || context?.cubeApi;
869
1029
  const query = loadOptions.query || options.query;
1030
+ const onlyViews = 'onlyViews' in loadOptions ? loadOptions.onlyViews : options.onlyViews;
870
1031
  const queryCondition = method === 'meta' ? true : query && isQueryPresent(query);
871
1032
  if (cubeApi && (ignoreSkip || !skip) && queryCondition) {
872
1033
  setError(null);
@@ -879,17 +1040,23 @@ function useCubeFetch(method, options = {}) {
879
1040
  mutexKey: method,
880
1041
  ...(options.baseRequestId ? {
881
1042
  baseRequestId: options.baseRequestId
1043
+ } : {}),
1044
+ ...(method === 'meta' && onlyViews ? {
1045
+ onlyViews: true
882
1046
  } : {})
883
1047
  };
884
1048
  const args = method === 'meta' ? [coreOptions] : [query, coreOptions];
1049
+ // `method` picks between overloaded `CubeApi` methods, so the call is
1050
+ // dispatched dynamically while keeping `cubeApi` as the receiver
1051
+ const fetchMethod = cubeApi[method];
885
1052
  try {
886
- const response = await cubeApi[method](...args);
1053
+ const fetchResponse = await fetchMethod.apply(cubeApi, args);
887
1054
  setResponse({
888
- response,
1055
+ response: fetchResponse,
889
1056
  isLoading: false
890
1057
  });
891
- } catch (error) {
892
- setError(error);
1058
+ } catch (fetchError) {
1059
+ setError(fetchError);
893
1060
  setResponse({
894
1061
  isLoading: false,
895
1062
  response: null
@@ -899,14 +1066,23 @@ function useCubeFetch(method, options = {}) {
899
1066
  }
900
1067
  useEffect(() => {
901
1068
  load();
1069
+ // `order` is read off a single query; an array of queries does not carry it
902
1070
  }, useDeepCompareMemoize([Object.keys(options.query?.order || {}), options, context]));
903
1071
  return {
904
1072
  ...response,
905
1073
  error,
906
- refetch: options => load(options, true)
1074
+ refetch: refetchOptions => load(refetchOptions, true)
907
1075
  };
908
1076
  }
909
1077
 
1078
+ /**
1079
+ * The hook resolves with a `SqlQuery`, while its `response` stays declared as a
1080
+ * dry-run response for backwards compatibility — `UseCubeSqlResult` spells the
1081
+ * real shape. `refetch` is declared, as it is on `useDryRun` and `useCubeMeta`,
1082
+ * because all three return the same object.
1083
+ *
1084
+ * @hidden
1085
+ */
910
1086
  function useCubeSql(query, options = {}) {
911
1087
  return useCubeFetch('sql', {
912
1088
  ...options,
@@ -914,6 +1090,9 @@ function useCubeSql(query, options = {}) {
914
1090
  });
915
1091
  }
916
1092
 
1093
+ /**
1094
+ * @hidden
1095
+ */
917
1096
  function useDryRun(query, options = {}) {
918
1097
  return useCubeFetch('dryRun', {
919
1098
  ...options,
@@ -921,6 +1100,9 @@ function useDryRun(query, options = {}) {
921
1100
  });
922
1101
  }
923
1102
 
1103
+ /**
1104
+ * @hidden
1105
+ */
924
1106
  function useLazyDryRun(query, options = {}) {
925
1107
  const {
926
1108
  refetch,
@@ -933,6 +1115,42 @@ function useLazyDryRun(query, options = {}) {
933
1115
  return [refetch, result];
934
1116
  }
935
1117
 
1118
+ /**
1119
+ * A React hook for executing Cube.js queries
1120
+ * ```js
1121
+ * import React from 'react';
1122
+ * import { Table } from 'antd';
1123
+ * import { useCubeQuery } from '@cubejs-client/react';
1124
+ *
1125
+ * export default function App() {
1126
+ * const { resultSet, isLoading, error, progress } = useCubeQuery({
1127
+ * measures: ['Orders.count'],
1128
+ * dimensions: ['Orders.createdAt.month'],
1129
+ * });
1130
+ *
1131
+ * if (isLoading) {
1132
+ * return <div>{progress?.stage || 'Loading...'}</div>;
1133
+ * }
1134
+ *
1135
+ * if (error) {
1136
+ * return <div>{error.toString()}</div>;
1137
+ * }
1138
+ *
1139
+ * if (!resultSet) {
1140
+ * return null;
1141
+ * }
1142
+ *
1143
+ * const dataSource = resultSet.tablePivot();
1144
+ * const columns = resultSet.tableColumns();
1145
+ *
1146
+ * return <Table columns={columns} dataSource={dataSource} />;
1147
+ * }
1148
+ *
1149
+ * ```
1150
+ * @order 1
1151
+ * @stickyTypes
1152
+ */
1153
+
936
1154
  function useCubeQuery(query, options = {}) {
937
1155
  const mutexRef = useRef({});
938
1156
  const [currentQuery, setCurrentQuery] = useState(null);
@@ -942,9 +1160,9 @@ function useCubeQuery(query, options = {}) {
942
1160
  const [error, setError] = useState(null);
943
1161
  const context = useContext(CubeContext);
944
1162
  let subscribeRequest = null;
945
- const progressCallback = ({
946
- progressResponse
947
- }) => setProgress(progressResponse);
1163
+
1164
+ // `progressResponse` is not part of the public `ProgressResult` API
1165
+ const progressCallback = progressResult => setProgress(progressResult.progressResponse);
948
1166
  async function fetch() {
949
1167
  const {
950
1168
  resetResultSetOnChange
@@ -970,8 +1188,8 @@ function useCubeQuery(query, options = {}) {
970
1188
  });
971
1189
  setResultSet(response);
972
1190
  setProgress(null);
973
- } catch (error) {
974
- setError(error);
1191
+ } catch (loadError) {
1192
+ setError(loadError);
975
1193
  setResultSet(null);
976
1194
  setProgress(null);
977
1195
  }
@@ -988,7 +1206,10 @@ function useCubeQuery(query, options = {}) {
988
1206
  }
989
1207
  async function loadQuery() {
990
1208
  if (!skip && isQueryPresent(query)) {
991
- if (!areQueriesEqual(currentQuery, query)) {
1209
+ // `areQueriesEqual` is declared for a single query, and reads no more
1210
+ // than `order` off one when given an array of queries
1211
+ const previousQuery = currentQuery;
1212
+ if (!areQueriesEqual(previousQuery, query)) {
992
1213
  if (resetResultSetOnChange == null || resetResultSetOnChange) {
993
1214
  setResultSet(null);
994
1215
  }
@@ -1036,7 +1257,8 @@ function useCubeQuery(query, options = {}) {
1036
1257
  subscribeRequest = null;
1037
1258
  }
1038
1259
  };
1039
- }, useDeepCompareMemoize([query, Object.keys(query && query.order || {}), options, context]));
1260
+ // `order` is read off a single query; an array of queries does not carry it
1261
+ }, useDeepCompareMemoize([query, Object.keys(query?.order || {}), options, context]));
1040
1262
  return {
1041
1263
  isLoading,
1042
1264
  resultSet,