@finos/legend-data-cube 0.0.24 → 0.0.26

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 (89) hide show
  1. package/docs/column-configuration.display-value-as-link.md +6 -0
  2. package/docs/column-configuration.kind.md +14 -0
  3. package/docs/column-configuration.missing-value-format.md +5 -0
  4. package/docs/column-configuration.unit.md +5 -0
  5. package/docs/extended-column.level.md +14 -0
  6. package/docs/grid-configuration.row-limit.md +5 -0
  7. package/lib/__lib__/DataCubeDocumentation.d.ts +5 -3
  8. package/lib/__lib__/DataCubeDocumentation.d.ts.map +1 -1
  9. package/lib/__lib__/DataCubeDocumentation.js +5 -3
  10. package/lib/__lib__/DataCubeDocumentation.js.map +1 -1
  11. package/lib/components/core/DataCubeFormUtils.d.ts +1 -0
  12. package/lib/components/core/DataCubeFormUtils.d.ts.map +1 -1
  13. package/lib/components/core/DataCubeFormUtils.js +4 -3
  14. package/lib/components/core/DataCubeFormUtils.js.map +1 -1
  15. package/lib/components/view/editor/DataCubeEditorColumnPropertiesPanel.d.ts.map +1 -1
  16. package/lib/components/view/editor/DataCubeEditorColumnPropertiesPanel.js +12 -9
  17. package/lib/components/view/editor/DataCubeEditorColumnPropertiesPanel.js.map +1 -1
  18. package/lib/components/view/editor/DataCubeEditorColumnSelector.js +2 -2
  19. package/lib/components/view/editor/DataCubeEditorGeneralPropertiesPanel.d.ts.map +1 -1
  20. package/lib/components/view/editor/DataCubeEditorGeneralPropertiesPanel.js +8 -7
  21. package/lib/components/view/editor/DataCubeEditorGeneralPropertiesPanel.js.map +1 -1
  22. package/lib/index.css +2 -2
  23. package/lib/index.css.map +1 -1
  24. package/lib/package.json +3 -3
  25. package/lib/stores/core/DataCubeQueryBuilder.d.ts +3 -0
  26. package/lib/stores/core/DataCubeQueryBuilder.d.ts.map +1 -1
  27. package/lib/stores/core/DataCubeQueryBuilder.js +8 -1
  28. package/lib/stores/core/DataCubeQueryBuilder.js.map +1 -1
  29. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts +1 -0
  30. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts.map +1 -1
  31. package/lib/stores/core/DataCubeQueryBuilderUtils.js +14 -7
  32. package/lib/stores/core/DataCubeQueryBuilderUtils.js.map +1 -1
  33. package/lib/stores/core/DataCubeQueryEngine.d.ts +6 -0
  34. package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
  35. package/lib/stores/core/DataCubeQueryEngine.js +7 -0
  36. package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
  37. package/lib/stores/core/models/DataCubeConfiguration.d.ts +1 -0
  38. package/lib/stores/core/models/DataCubeConfiguration.d.ts.map +1 -1
  39. package/lib/stores/core/models/DataCubeConfiguration.js +2 -0
  40. package/lib/stores/core/models/DataCubeConfiguration.js.map +1 -1
  41. package/lib/stores/view/editor/DataCubeEditorGeneralPropertiesPanelState.d.ts +2 -2
  42. package/lib/stores/view/editor/DataCubeEditorGeneralPropertiesPanelState.d.ts.map +1 -1
  43. package/lib/stores/view/editor/DataCubeEditorGeneralPropertiesPanelState.js +4 -3
  44. package/lib/stores/view/editor/DataCubeEditorGeneralPropertiesPanelState.js.map +1 -1
  45. package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.d.ts +1 -0
  46. package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.d.ts.map +1 -1
  47. package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.js +5 -0
  48. package/lib/stores/view/editor/DataCubeEditorMutableConfiguration.js.map +1 -1
  49. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts +14 -2
  50. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
  51. package/lib/stores/view/grid/DataCubeGridClientEngine.js +48 -42
  52. package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
  53. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts +20 -3
  54. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts.map +1 -1
  55. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js +89 -40
  56. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js.map +1 -1
  57. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js +1 -1
  58. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js.map +1 -1
  59. package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts +13 -4
  60. package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts.map +1 -1
  61. package/lib/stores/view/grid/DataCubeGridQueryBuilder.js +40 -9
  62. package/lib/stores/view/grid/DataCubeGridQueryBuilder.js.map +1 -1
  63. package/lib/stores/view/grid/DataCubeGridQuerySnapshotBuilder.d.ts +2 -2
  64. package/lib/stores/view/grid/DataCubeGridQuerySnapshotBuilder.d.ts.map +1 -1
  65. package/lib/stores/view/grid/DataCubeGridQuerySnapshotBuilder.js +30 -14
  66. package/lib/stores/view/grid/DataCubeGridQuerySnapshotBuilder.js.map +1 -1
  67. package/lib/stores/view/grid/DataCubeGridState.d.ts +2 -0
  68. package/lib/stores/view/grid/DataCubeGridState.d.ts.map +1 -1
  69. package/lib/stores/view/grid/DataCubeGridState.js +5 -1
  70. package/lib/stores/view/grid/DataCubeGridState.js.map +1 -1
  71. package/package.json +8 -8
  72. package/src/__lib__/DataCubeDocumentation.ts +5 -3
  73. package/src/components/core/DataCubeFormUtils.tsx +5 -1
  74. package/src/components/view/editor/DataCubeEditorColumnPropertiesPanel.tsx +40 -14
  75. package/src/components/view/editor/DataCubeEditorColumnSelector.tsx +2 -2
  76. package/src/components/view/editor/DataCubeEditorGeneralPropertiesPanel.tsx +41 -30
  77. package/src/stores/core/DataCubeQueryBuilder.ts +13 -0
  78. package/src/stores/core/DataCubeQueryBuilderUtils.ts +32 -6
  79. package/src/stores/core/DataCubeQueryEngine.ts +8 -0
  80. package/src/stores/core/models/DataCubeConfiguration.ts +2 -0
  81. package/src/stores/view/editor/DataCubeEditorGeneralPropertiesPanelState.ts +5 -4
  82. package/src/stores/view/editor/DataCubeEditorMutableConfiguration.ts +7 -0
  83. package/src/stores/view/grid/DataCubeGridClientEngine.ts +67 -52
  84. package/src/stores/view/grid/DataCubeGridConfigurationBuilder.tsx +99 -42
  85. package/src/stores/view/grid/DataCubeGridMenuBuilder.tsx +1 -1
  86. package/src/stores/view/grid/DataCubeGridQueryBuilder.ts +71 -15
  87. package/src/stores/view/grid/DataCubeGridQuerySnapshotBuilder.ts +40 -20
  88. package/src/stores/view/grid/DataCubeGridState.ts +12 -1
  89. package/tsconfig.json +2 -0
@@ -29,6 +29,7 @@ import {
29
29
  DEFAULT_NEGATIVE_FOREGROUND_COLOR,
30
30
  DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR,
31
31
  DEFAULT_ZERO_FOREGROUND_COLOR,
32
+ EMPTY_VALUE_PLACEHOLDER,
32
33
  } from '../../../stores/core/DataCubeQueryEngine.js';
33
34
  import {
34
35
  FormCheckbox,
@@ -40,8 +41,10 @@ import {
40
41
  FormTextInput,
41
42
  FormNumberInput,
42
43
  FormBadge_WIP,
44
+ FormDocumentation,
43
45
  } from '../../core/DataCubeFormUtils.js';
44
46
  import type { DataCubeViewState } from '../../../stores/view/DataCubeViewState.js';
47
+ import { DocumentationKey } from '../../../__lib__/DataCubeDocumentation.js';
45
48
 
46
49
  export const DataCubeEditorGeneralPropertiesPanel = observer(
47
50
  (props: { view: DataCubeViewState }) => {
@@ -117,9 +120,7 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
117
120
  !configuration.showRootAggregation,
118
121
  )
119
122
  }
120
- disabled={true}
121
123
  />
122
- <FormBadge_WIP />
123
124
  <FormCheckbox
124
125
  className="ml-3"
125
126
  label="Show leaf count"
@@ -128,7 +129,11 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
128
129
  configuration.setShowLeafCount(!configuration.showLeafCount)
129
130
  }
130
131
  />
131
- <div className="ml-3 mr-1.5 flex h-full flex-shrink-0 items-center text-sm">
132
+ </div>
133
+
134
+ <div className="mt-2 flex h-5 w-full items-center">
135
+ <div className="flex h-full w-32 flex-shrink-0 items-center text-sm" />
136
+ <div className="mr-1 flex h-full flex-shrink-0 items-center text-sm">
132
137
  Sort:
133
138
  </div>
134
139
  <FormDropdownMenuTrigger
@@ -160,19 +165,18 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
160
165
  </FormDropdownMenuItem>
161
166
  ))}
162
167
  </FormDropdownMenu>
163
- </div>
164
-
165
- <div className="mt-2 flex h-5 w-full items-center">
166
- <div className="flex h-full w-32 flex-shrink-0 items-center text-sm" />
167
- <div className="mr-1.5 flex h-full flex-shrink-0 items-center text-sm">
168
+ <div className="ml-3 mr-1 flex h-full flex-shrink-0 items-center text-sm">
168
169
  Initially expand to level:
169
170
  </div>
170
171
  <FormDropdownMenuTrigger
171
172
  className="w-16"
172
173
  onClick={openInitialExpandLevelDropdown}
173
174
  open={initialExpandLevelDropdownPropsOpen}
175
+ showAsPlaceholder={
176
+ configuration.initialExpandLevel === undefined
177
+ }
174
178
  >
175
- {configuration.initialExpandLevel ?? '(None)'}
179
+ {configuration.initialExpandLevel ?? EMPTY_VALUE_PLACEHOLDER}
176
180
  </FormDropdownMenuTrigger>
177
181
  <FormDropdownMenu
178
182
  className="w-16"
@@ -187,7 +191,7 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
187
191
  }}
188
192
  autoFocus={level === configuration.initialExpandLevel}
189
193
  >
190
- {level ?? '(None)'}
194
+ {level ?? EMPTY_VALUE_PLACEHOLDER}
191
195
  </FormDropdownMenuItem>
192
196
  ))}
193
197
  </FormDropdownMenu>
@@ -196,20 +200,22 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
196
200
  <div className="mt-2 flex h-5 w-full items-center">
197
201
  <div className="flex h-full w-32 flex-shrink-0 items-center text-sm">
198
202
  Row Limit:
203
+ <FormDocumentation
204
+ className="ml-1"
205
+ documentationKey={
206
+ DocumentationKey.DATA_CUBE_GRID_CONFIGURATION_ROW_LIMIT
207
+ }
208
+ />
199
209
  </div>
200
210
  <FormNumberInput
201
211
  className="w-16 text-sm"
202
212
  value={panel.limit}
203
- min={-1}
213
+ min={0}
204
214
  step={1}
205
- defaultValue={-1}
206
- isValid={(value) => value !== undefined && value >= -1}
207
- setValue={(value) => panel.setLimit(value ?? -1)}
215
+ defaultValue={undefined}
216
+ placeholder={EMPTY_VALUE_PLACEHOLDER}
217
+ setValue={(value) => panel.setLimit(value)}
208
218
  />
209
- <div className="flex-shrink-0 pl-1 text-sm italic text-neutral-500">
210
- Truncate result to this many rows at every level. Use -1 for
211
- unlimited.
212
- </div>
213
219
  </div>
214
220
 
215
221
  <div className="mt-2 flex h-5 w-full items-center">
@@ -264,9 +270,10 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
264
270
  )
265
271
  }
266
272
  />
267
- <div className="ml-2 h-[1px] w-2 flex-shrink-0 bg-neutral-400" />
273
+ <div className="ml-3 mr-1 flex h-full flex-shrink-0 items-center text-sm">
274
+ Color:
275
+ </div>
268
276
  <FormColorPickerButton
269
- className="ml-2"
270
277
  color={configuration.gridLineColor}
271
278
  defaultColor={DEFAULT_GRID_LINE_COLOR}
272
279
  onChange={(value) => configuration.setGridLineColor(value)}
@@ -291,7 +298,6 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
291
298
  />
292
299
  <FormCheckbox
293
300
  className="ml-3"
294
- label="Custom: Alternate color every"
295
301
  checked={configuration.alternateRows}
296
302
  onChange={() => {
297
303
  if (configuration.alternateRows) {
@@ -302,8 +308,20 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
302
308
  }
303
309
  }}
304
310
  />
311
+ <div className="ml-1 mr-1 flex h-full flex-shrink-0 items-center text-sm">
312
+ Custom: Alternate color:
313
+ </div>
314
+ <FormColorPickerButton
315
+ disabled={!configuration.alternateRows}
316
+ color={configuration.alternateRowsColor}
317
+ defaultColor={DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR}
318
+ onChange={(value) => configuration.setAlternateRowsColor(value)}
319
+ />
320
+ <div className="ml-1 flex h-full flex-shrink-0 items-center text-sm">
321
+ every:
322
+ </div>
305
323
  <FormNumberInput
306
- className="ml-1.5 w-16 text-sm"
324
+ className="ml-1 w-16 text-sm"
307
325
  disabled={!configuration.alternateRows}
308
326
  min={1}
309
327
  step={1}
@@ -314,14 +332,7 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(
314
332
  configuration.setAlternateRowsCount(value ?? 1)
315
333
  }
316
334
  />
317
- <div className="ml-1.5 flex-shrink-0 text-sm">{`row(s)`}</div>
318
- <FormColorPickerButton
319
- className="ml-[5px]"
320
- disabled={!configuration.alternateRows}
321
- color={configuration.alternateRowsColor}
322
- defaultColor={DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR}
323
- onChange={(value) => configuration.setAlternateRowsColor(value)}
324
- />
335
+ <div className="ml-1 flex-shrink-0 text-sm">rows</div>
325
336
  </div>
326
337
 
327
338
  <div className="my-2 h-[1px] w-full bg-neutral-200" />
@@ -46,6 +46,7 @@ import {
46
46
  _primitiveValue,
47
47
  _var,
48
48
  _functionCompositionProcessor,
49
+ _extendRootAggregation,
49
50
  } from './DataCubeQueryBuilderUtils.js';
50
51
  import type { DataCubeQueryAggregateOperation } from './aggregation/DataCubeQueryAggregateOperation.js';
51
52
  import type { DataCubeSource } from './models/DataCubeSource.js';
@@ -67,6 +68,11 @@ export function buildExecutableQuery(
67
68
  filterOperations: DataCubeQueryFilterOperation[],
68
69
  aggregateOperations: DataCubeQueryAggregateOperation[],
69
70
  ) => void;
71
+ rootAggregation?:
72
+ | {
73
+ columnName: string;
74
+ }
75
+ | undefined;
70
76
  pagination?:
71
77
  | {
72
78
  start: number;
@@ -91,6 +97,8 @@ export function buildExecutableQuery(
91
97
  _col(col.name, _deserializeLambda(col.mapFn)),
92
98
  ]),
93
99
  );
100
+ // instead of batching all the extend() functions, we sequence them, and reference the first one
101
+ // in the function map
94
102
  _process('leafExtend', guaranteeNonNullable(leafExtendedFuncs[0]));
95
103
  leafExtendedFuncs.slice(1).forEach((func) => {
96
104
  sequence.push(func);
@@ -163,6 +171,9 @@ export function buildExecutableQuery(
163
171
 
164
172
  if (data.groupBy) {
165
173
  const groupBy = data.groupBy;
174
+ if (configuration.showRootAggregation && options?.rootAggregation) {
175
+ sequence.push(_extendRootAggregation(options.rootAggregation.columnName));
176
+ }
166
177
  _process(
167
178
  'groupBy',
168
179
  _function(DataCubeFunction.GROUP_BY, [
@@ -203,6 +214,8 @@ export function buildExecutableQuery(
203
214
  _col(col.name, _deserializeLambda(col.mapFn)),
204
215
  ]),
205
216
  );
217
+ // instead of batching all the extend() functions, we sequence them, and reference the first one
218
+ // in the function map
206
219
  _process('groupExtend', guaranteeNonNullable(groupExtendedFuncs[0]));
207
220
  groupExtendedFuncs.slice(1).forEach((func) => {
208
221
  sequence.push(func);
@@ -49,6 +49,10 @@ import {
49
49
  extractPackagePathFromPath,
50
50
  CORE_PURE_PATH,
51
51
  V1_GenericTypeInstance,
52
+ V1_createGenericTypeWithElementPath,
53
+ V1_createGenericTypeWithRawType,
54
+ V1_createRelationType,
55
+ V1_createRelationTypeColumn,
52
56
  } from '@finos/legend-graph';
53
57
  import {
54
58
  type DataCubeQuerySnapshotFilterCondition,
@@ -75,6 +79,7 @@ import {
75
79
  type DataCubeQueryFunctionMap,
76
80
  isPivotResultColumnName,
77
81
  getPivotResultColumnBaseColumnName,
82
+ DEFAULT_ROOT_AGGREGATION_COLUMN_VALUE,
78
83
  } from './DataCubeQueryEngine.js';
79
84
  import type { DataCubeQueryFilterOperation } from './filter/DataCubeQueryFilterOperation.js';
80
85
  import type { DataCubeQueryAggregateOperation } from './aggregation/DataCubeQueryAggregateOperation.js';
@@ -380,14 +385,18 @@ export function _pivotAggCols(
380
385
  }
381
386
 
382
387
  export function _castCols(columns: DataCubeColumn[]) {
383
- const genericType = new V1_GenericTypeInstance();
384
- genericType.fullPath = CORE_PURE_PATH.RELATION;
385
- genericType.typeArguments = [
386
- _cols(
387
- columns.map((col) => _colSpec(col.name, undefined, undefined, col.type)),
388
+ const genericTypeInstance = new V1_GenericTypeInstance();
389
+ genericTypeInstance.genericType = V1_createGenericTypeWithElementPath(
390
+ CORE_PURE_PATH.RELATION,
391
+ );
392
+ genericTypeInstance.genericType.typeArguments = [
393
+ V1_createGenericTypeWithRawType(
394
+ V1_createRelationType(
395
+ columns.map((col) => V1_createRelationTypeColumn(col.name, col.type)),
396
+ ),
388
397
  ),
389
398
  ];
390
- return genericType;
399
+ return genericTypeInstance;
391
400
  }
392
401
 
393
402
  export function _groupByAggCols(
@@ -487,6 +496,23 @@ export function _groupByAggCols(
487
496
  ]);
488
497
  }
489
498
 
499
+ export function _extendRootAggregation(columnName: string) {
500
+ return _function(DataCubeFunction.EXTEND, [
501
+ _col(
502
+ columnName,
503
+ _lambda(
504
+ [_var()],
505
+ [
506
+ _primitiveValue(
507
+ PRIMITIVE_TYPE.STRING,
508
+ DEFAULT_ROOT_AGGREGATION_COLUMN_VALUE,
509
+ ),
510
+ ],
511
+ ),
512
+ ),
513
+ ]);
514
+ }
515
+
490
516
  export function _filter(
491
517
  filter: DataCubeQuerySnapshotFilter | DataCubeQuerySnapshotFilterCondition,
492
518
  filterOperations: DataCubeQueryFilterOperation[],
@@ -305,6 +305,7 @@ export const DEFAULT_REPORT_NAME = 'New Report';
305
305
  export const DEFAULT_TREE_COLUMN_SORT_DIRECTION =
306
306
  DataCubeQuerySortDirection.ASCENDING;
307
307
  export const DEFAULT_PIVOT_STATISTIC_COLUMN_NAME = 'Total';
308
+ export const DEFAULT_ROOT_AGGREGATION_COLUMN_VALUE = '[ROOT]';
308
309
 
309
310
  export const DEFAULT_URL_LABEL_QUERY_PARAM = 'dataCube.linkLabel';
310
311
  export const DEFAULT_MISSING_VALUE_DISPLAY_TEXT = '';
@@ -330,3 +331,10 @@ export const DEFAULT_ROW_BACKGROUND_COLOR = TailwindCSSPalette.white;
330
331
  export const DEFAULT_NEGATIVE_FOREGROUND_COLOR = TailwindCSSPalette.red[500];
331
332
  export const DEFAULT_ZERO_FOREGROUND_COLOR = TailwindCSSPalette.neutral[400];
332
333
  export const DEFAULT_ERROR_FOREGROUND_COLOR = TailwindCSSPalette.blue[600];
334
+
335
+ // settings
336
+ export const TAB_SIZE = 2;
337
+ export const DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
338
+ export const TYPEAHEAD_SEARCH_MINIMUM_SEARCH_LENGTH = 3;
339
+ export const TYPEAHEAD_SEARCH_LIMIT = 10;
340
+ export const EMPTY_VALUE_PLACEHOLDER = '(None)';
@@ -63,6 +63,7 @@ export class DataCubeColumnConfiguration {
63
63
  negativeNumberInParens = false;
64
64
  numberScale?: DataCubeNumberScale | undefined;
65
65
  missingValueDisplayText?: string | undefined;
66
+ unit?: string | undefined;
66
67
 
67
68
  fontFamily?: DataCubeFont | undefined;
68
69
  fontSize?: number | undefined;
@@ -154,6 +155,7 @@ export class DataCubeColumnConfiguration {
154
155
  pivotStatisticColumnFunction: optional(primitive()),
155
156
  textAlign: optional(primitive()),
156
157
  type: primitive(),
158
+ unit: optional(primitive()),
157
159
  zeroBackgroundColor: optional(primitive()),
158
160
  zeroForegroundColor: optional(primitive()),
159
161
  }),
@@ -28,7 +28,7 @@ export class DataCubeEditorGeneralPropertiesPanelState
28
28
  readonly view!: DataCubeViewState;
29
29
  readonly editor!: DataCubeEditorState;
30
30
 
31
- limit = -1;
31
+ limit: number | undefined = undefined;
32
32
  configuration = new DataCubeEditorMutableConfiguration();
33
33
 
34
34
  constructor(editor: DataCubeEditorState) {
@@ -45,7 +45,7 @@ export class DataCubeEditorGeneralPropertiesPanelState
45
45
  this.view = editor.view;
46
46
  }
47
47
 
48
- setLimit(val: number) {
48
+ setLimit(val: number | undefined): void {
49
49
  this.limit = val;
50
50
  }
51
51
 
@@ -56,7 +56,7 @@ export class DataCubeEditorGeneralPropertiesPanelState
56
56
  this.setLimit(
57
57
  snapshot.data.limit !== undefined && snapshot.data.limit >= 0
58
58
  ? snapshot.data.limit
59
- : -1,
59
+ : undefined,
60
60
  );
61
61
  this.configuration = DataCubeEditorMutableConfiguration.create(
62
62
  snapshot.data.configuration,
@@ -68,7 +68,8 @@ export class DataCubeEditorGeneralPropertiesPanelState
68
68
  baseSnapshot: DataCubeQuerySnapshot,
69
69
  ) {
70
70
  const data = newSnapshot.data;
71
- data.limit = this.limit < 0 ? undefined : this.limit;
71
+ data.limit =
72
+ this.limit === undefined || this.limit < 0 ? undefined : this.limit;
72
73
  data.configuration = this.configuration.serialize();
73
74
  }
74
75
  }
@@ -96,6 +96,9 @@ export class DataCubeEditorMutableColumnConfiguration extends DataCubeColumnConf
96
96
  missingValueDisplayText: observable,
97
97
  setMissingValueDisplayText: action,
98
98
 
99
+ unit: observable,
100
+ setUnit: action,
101
+
99
102
  fontFamily: observable,
100
103
  setFontFamily: action,
101
104
 
@@ -273,6 +276,10 @@ export class DataCubeEditorMutableColumnConfiguration extends DataCubeColumnConf
273
276
  this.missingValueDisplayText = value;
274
277
  }
275
278
 
279
+ setUnit(value: string | undefined) {
280
+ this.unit = value;
281
+ }
282
+
276
283
  setFontFamily(value: DataCubeFont | undefined) {
277
284
  this.fontFamily = value;
278
285
  }
@@ -32,7 +32,6 @@ import {
32
32
  } from '@finos/legend-shared';
33
33
  import { buildExecutableQuery } from '../../core/DataCubeQueryBuilder.js';
34
34
  import { type TabularDataSet, V1_Lambda } from '@finos/legend-graph';
35
- import { generateRowGroupingDrilldownExecutableQueryPostProcessor } from './DataCubeGridQueryBuilder.js';
36
35
  import { makeObservable, observable, runInAction } from 'mobx';
37
36
  import type {
38
37
  DataCubeConfiguration,
@@ -49,10 +48,15 @@ import { buildQuerySnapshot } from './DataCubeGridQuerySnapshotBuilder.js';
49
48
  import { AlertType } from '../../../components/core/DataCubeAlert.js';
50
49
  import { sum } from 'mathjs';
51
50
  import type { DataCubeViewState } from '../DataCubeViewState.js';
52
-
53
- type GridClientCellValue = string | number | boolean | null | undefined;
54
- type GridClientRowData = {
55
- [key: string]: GridClientCellValue;
51
+ import {
52
+ _aggCountCol,
53
+ buildGridDataFetchExecutableQuery,
54
+ } from './DataCubeGridQueryBuilder.js';
55
+ import { _colSpecArrayParam } from '../../core/DataCubeQuerySnapshotBuilderUtils.js';
56
+
57
+ type DataCubeGridClientCellValue = string | number | boolean | null | undefined;
58
+ type DataCubeGridClientRowData = {
59
+ [key: string]: DataCubeGridClientCellValue;
56
60
  };
57
61
 
58
62
  export enum DataCubeGridClientExportFormat {
@@ -128,17 +132,19 @@ export const INTERNAL__GRID_CLIENT_TOOLTIP_SHOW_DELAY = 1500;
128
132
  export const INTERNAL__GRID_CLIENT_AUTO_RESIZE_PADDING = 10;
129
133
  export const INTERNAL__GRID_CLIENT_MISSING_VALUE = '__MISSING';
130
134
  export const INTERNAL__GRID_CLIENT_TREE_COLUMN_ID = 'ag-Grid-AutoColumn';
135
+ export const INTERNAL__GRID_CLIENT_ROOT_AGGREGATION_COLUMN_ID =
136
+ 'INTERNAL__rootAggregation';
131
137
  export const INTERNAL__GRID_CLIENT_DATA_FETCH_MANUAL_TRIGGER_COLUMN_ID =
132
138
  'INTERNAL__dataFetchManualTrigger';
133
139
  export const INTERNAL__GRID_CLIENT_ROW_GROUPING_COUNT_AGG_COLUMN_ID =
134
140
  'INTERNAL__count';
135
141
 
136
- export enum GridClientPinnedAlignement {
142
+ export enum DataCubeGridClientPinnedAlignement {
137
143
  LEFT = 'left',
138
144
  RIGHT = 'right',
139
145
  }
140
146
 
141
- export enum GridClientSortDirection {
147
+ export enum DataCubeGridClientSortDirection {
142
148
  ASCENDING = 'asc',
143
149
  DESCENDING = 'desc',
144
150
  }
@@ -209,9 +215,9 @@ export function computeHashCodeForDataFetchManualTrigger(
209
215
  function buildRowData(
210
216
  result: TabularDataSet,
211
217
  snapshot: DataCubeQuerySnapshot,
212
- ): GridClientRowData[] {
218
+ ): DataCubeGridClientRowData[] {
213
219
  return result.rows.map((_row, rowIdx) => {
214
- const row: GridClientRowData = {};
220
+ const row: DataCubeGridClientRowData = {};
215
221
  const cols = result.columns;
216
222
  _row.values.forEach((value, colIdx) => {
217
223
  // `ag-grid` shows `false` value as empty string so we have
@@ -281,6 +287,12 @@ async function getCastColumns(
281
287
  if (funcMap.groupExtend) {
282
288
  _unprocess('groupExtend');
283
289
  }
290
+
291
+ // when both pivot and groupBy present, we need to account for the count column
292
+ // in the cast expression
293
+ if (funcMap.pivot && currentSnapshot.data.groupBy) {
294
+ _colSpecArrayParam(funcMap.pivot, 1).colSpecs.push(_aggCountCol());
295
+ }
284
296
  },
285
297
  },
286
298
  );
@@ -299,6 +311,18 @@ async function getCastColumns(
299
311
  }));
300
312
  }
301
313
 
314
+ export type DataCubeGridClientDataFetchRequest = {
315
+ startRow: number | undefined;
316
+ endRow: number | undefined;
317
+ rowGroupColumns: string[];
318
+ groupKeys: (string | null | undefined)[]; // NOTE: it's possible for these values to be nullish depending on row data
319
+ pivotColumns: string[];
320
+ sortColumns: {
321
+ name: string;
322
+ direction: DataCubeGridClientSortDirection;
323
+ }[];
324
+ };
325
+
302
326
  export class DataCubeGridClientServerSideDataSource
303
327
  implements IServerSideDatasource
304
328
  {
@@ -316,39 +340,34 @@ export class DataCubeGridClientServerSideDataSource
316
340
  async fetchRows(params: IServerSideGetRowsParams<unknown, unknown>) {
317
341
  const task = this.grid.view.newTask('Fetching data');
318
342
 
319
- // ------------------------------ GRID OPTIONS ------------------------------
320
- // Here, we make adjustments to the grid display in response to the new
321
- // request, in case the grid action has not impacted the layout in an
322
- // adequate way.
323
-
324
- // Toggle the visibility of the tree column based on the presence of row-group columns
325
- if (params.request.rowGroupCols.length) {
326
- params.api.setColumnsVisible(
327
- [INTERNAL__GRID_CLIENT_TREE_COLUMN_ID],
328
- true,
329
- );
330
- } else {
331
- params.api.setColumnsVisible(
332
- [INTERNAL__GRID_CLIENT_TREE_COLUMN_ID],
333
- false,
334
- );
335
- }
336
-
337
343
  // ------------------------------ SNAPSHOT ------------------------------
338
344
 
339
345
  const currentSnapshot = guaranteeNonNullable(this.grid.getLatestSnapshot());
346
+ const request: DataCubeGridClientDataFetchRequest = {
347
+ startRow: params.request.startRow,
348
+ endRow: params.request.endRow,
349
+ rowGroupColumns: params.request.rowGroupCols.map((col) => col.id),
350
+ groupKeys: params.request.groupKeys.map((key) => key),
351
+ pivotColumns: params.request.pivotCols.map((col) => col.id),
352
+ sortColumns: params.request.sortModel.map((item) => ({
353
+ name: item.colId,
354
+ direction: item.sort as DataCubeGridClientSortDirection,
355
+ })),
356
+ };
357
+ const isTopLevelRequest = request.groupKeys.length === 0;
358
+
340
359
  let newSnapshot = currentSnapshot;
341
360
 
342
361
  // only recompute the snapshot if this is not a drilldown request
343
- if (params.request.groupKeys.length === 0) {
344
- newSnapshot = buildQuerySnapshot(params.request, currentSnapshot);
362
+ if (isTopLevelRequest) {
363
+ newSnapshot = buildQuerySnapshot(request, currentSnapshot);
345
364
 
346
365
  // NOTE: if h-pivot is enabled, update the cast columns
347
366
  // and panels which might be affected by this (e.g. sorts)
348
367
  // Because is an expensive operation in certain case, we only
349
368
  // recompute when it's not a drilldown or paging request,
350
369
  // but we could still optimize further if needed.
351
- if (!this.grid.isPaginationEnabled || params.request.startRow === 0) {
370
+ if (!this.grid.isPaginationEnabled || request.startRow === 0) {
352
371
  if (newSnapshot.data.pivot) {
353
372
  try {
354
373
  const castColumns = await getCastColumns(
@@ -390,27 +409,14 @@ export class DataCubeGridClientServerSideDataSource
390
409
  // ------------------------------ DATA ------------------------------
391
410
 
392
411
  try {
393
- const executableQuery = buildExecutableQuery(
412
+ const executableQuery = buildGridDataFetchExecutableQuery(
413
+ request,
394
414
  newSnapshot,
395
415
  this.grid.view.source,
396
416
  (source) => this.grid.view.engine.buildExecutionContext(source),
397
417
  this.grid.view.engine.filterOperations,
398
418
  this.grid.view.engine.aggregateOperations,
399
- {
400
- postProcessor:
401
- generateRowGroupingDrilldownExecutableQueryPostProcessor(
402
- params.request.groupKeys,
403
- ),
404
- pagination:
405
- this.grid.isPaginationEnabled &&
406
- params.request.startRow !== undefined &&
407
- params.request.endRow !== undefined
408
- ? {
409
- start: params.request.startRow,
410
- end: params.request.endRow,
411
- }
412
- : undefined,
413
- },
419
+ this.grid.isPaginationEnabled,
414
420
  );
415
421
  const lambda = new V1_Lambda();
416
422
  lambda.body.push(executableQuery);
@@ -436,16 +442,16 @@ export class DataCubeGridClientServerSideDataSource
436
442
  if (this.grid.isPaginationEnabled) {
437
443
  params.success({ rowData });
438
444
  // only update row count when loading the top-level drilldown data
439
- if (params.request.groupKeys.length === 0) {
445
+ if (isTopLevelRequest) {
440
446
  runInAction(() => {
441
- this.rowCount = (params.request.startRow ?? 0) + rowData.length;
447
+ this.rowCount = (request.startRow ?? 0) + rowData.length;
442
448
  });
443
449
  }
444
450
 
445
451
  // toggle no-rows overlay
446
452
  if (
447
- params.request.groupKeys.length === 0 &&
448
- params.request.startRow === 0 &&
453
+ isTopLevelRequest &&
454
+ request.startRow === 0 &&
449
455
  rowData.length === 0
450
456
  ) {
451
457
  this.grid.client.showNoRowsOverlay();
@@ -506,19 +512,28 @@ export class DataCubeGridClientServerSideDataSource
506
512
  });
507
513
 
508
514
  // only update row count when loading the top-level drilldown data
509
- if (params.request.groupKeys.length === 0) {
515
+ if (isTopLevelRequest) {
510
516
  runInAction(() => {
511
517
  this.rowCount = rowData.length;
512
518
  });
513
519
  }
514
520
 
515
521
  // toggle no-rows overlay
516
- if (params.request.groupKeys.length === 0 && rowData.length === 0) {
522
+ if (isTopLevelRequest && rowData.length === 0) {
517
523
  this.grid.client.showNoRowsOverlay();
518
524
  } else {
519
525
  this.grid.client.hideOverlay();
520
526
  }
521
527
  }
528
+
529
+ // Resize columns to fit content on all actions (drilldown, changing queries, fetching new page, etc.)
530
+ // NOTE: we might want to restrict this to only happen on certain actions
531
+ //
532
+ // `setTimeout()` is need to ensure this runs after the grid has been updated with the new data
533
+ // since ag-grid does not provide an event hook for this action for server-side row model.
534
+ setTimeout(() => {
535
+ this.grid.client.autoSizeAllColumns();
536
+ }, 0);
522
537
  } catch (error) {
523
538
  assertErrorThrown(error);
524
539
  this.grid.view.engine.alertError(error, {