@finos/legend-data-cube 0.2.0 → 0.2.2

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/lib/components/view/editor/DataCubeEditor.d.ts.map +1 -1
  2. package/lib/components/view/editor/DataCubeEditor.js +7 -0
  3. package/lib/components/view/editor/DataCubeEditor.js.map +1 -1
  4. package/lib/index.css +1 -1
  5. package/lib/package.json +1 -1
  6. package/lib/stores/DataCubeAPI.d.ts +0 -6
  7. package/lib/stores/DataCubeAPI.d.ts.map +1 -1
  8. package/lib/stores/DataCubeAPI.js +0 -4
  9. package/lib/stores/DataCubeAPI.js.map +1 -1
  10. package/lib/stores/core/DataCubeQueryBuilder.js +1 -1
  11. package/lib/stores/core/DataCubeQueryBuilder.js.map +1 -1
  12. package/lib/stores/core/DataCubeQueryEngine.d.ts +2 -1
  13. package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
  14. package/lib/stores/core/DataCubeQueryEngine.js +1 -0
  15. package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
  16. package/lib/stores/core/DataCubeSnapshot.d.ts +1 -1
  17. package/lib/stores/core/DataCubeSnapshot.d.ts.map +1 -1
  18. package/lib/stores/core/DataCubeSnapshotBuilder.js +1 -0
  19. package/lib/stores/core/DataCubeSnapshotBuilder.js.map +1 -1
  20. package/lib/stores/core/model/DataCubeSpecification.d.ts +2 -0
  21. package/lib/stores/core/model/DataCubeSpecification.d.ts.map +1 -1
  22. package/lib/stores/core/model/DataCubeSpecification.js +3 -0
  23. package/lib/stores/core/model/DataCubeSpecification.js.map +1 -1
  24. package/lib/stores/view/DataCubeViewState.d.ts.map +1 -1
  25. package/lib/stores/view/DataCubeViewState.js +1 -0
  26. package/lib/stores/view/DataCubeViewState.js.map +1 -1
  27. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts.map +1 -1
  28. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js +1 -0
  29. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js.map +1 -1
  30. package/lib/stores/view/editor/DataCubeEditorState.d.ts.map +1 -1
  31. package/lib/stores/view/editor/DataCubeEditorState.js +1 -0
  32. package/lib/stores/view/editor/DataCubeEditorState.js.map +1 -1
  33. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts +2 -2
  34. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
  35. package/lib/stores/view/grid/DataCubeGridClientEngine.js +13 -6
  36. package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
  37. package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts +15 -9
  38. package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts.map +1 -1
  39. package/lib/stores/view/grid/DataCubeGridDimensionalTree.js +116 -20
  40. package/lib/stores/view/grid/DataCubeGridDimensionalTree.js.map +1 -1
  41. package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts.map +1 -1
  42. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js +10 -1
  43. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js.map +1 -1
  44. package/lib/stores/view/grid/DataCubeGridState.d.ts +3 -1
  45. package/lib/stores/view/grid/DataCubeGridState.d.ts.map +1 -1
  46. package/lib/stores/view/grid/DataCubeGridState.js +21 -7
  47. package/lib/stores/view/grid/DataCubeGridState.js.map +1 -1
  48. package/package.json +4 -4
  49. package/src/components/view/editor/DataCubeEditor.tsx +8 -0
  50. package/src/stores/DataCubeAPI.ts +0 -10
  51. package/src/stores/core/DataCubeQueryBuilder.ts +1 -1
  52. package/src/stores/core/DataCubeQueryEngine.ts +1 -0
  53. package/src/stores/core/DataCubeSnapshot.ts +1 -1
  54. package/src/stores/core/DataCubeSnapshotBuilder.ts +1 -1
  55. package/src/stores/core/model/DataCubeSpecification.ts +5 -0
  56. package/src/stores/view/DataCubeViewState.ts +1 -0
  57. package/src/stores/view/editor/DataCubeEditorDimensionsPanelState.ts +1 -0
  58. package/src/stores/view/editor/DataCubeEditorState.tsx +1 -0
  59. package/src/stores/view/grid/DataCubeGridClientEngine.ts +21 -6
  60. package/src/stores/view/grid/DataCubeGridDimensionalTree.ts +189 -19
  61. package/src/stores/view/grid/DataCubeGridMenuBuilder.tsx +17 -0
  62. package/src/stores/view/grid/DataCubeGridState.ts +53 -13
@@ -17,11 +17,23 @@
17
17
  import type { CellDoubleClickedEvent } from 'ag-grid-community';
18
18
  import type { DataCubeConfiguration } from '../../core/model/DataCubeConfiguration.js';
19
19
  import {
20
+ customList,
20
21
  guaranteeNonNullable,
21
22
  guaranteeType,
22
23
  hashArray,
24
+ SerializationFactory,
25
+ usingModelSchema,
23
26
  type Hashable,
27
+ type PlainObject,
28
+ type Writable,
24
29
  } from '@finos/legend-shared';
30
+ import {
31
+ createModelSchema,
32
+ deserialize,
33
+ list,
34
+ primitive,
35
+ serialize,
36
+ } from 'serializr';
25
37
 
26
38
  export const DIMENSIONAL_L0_COLUMN = 'ALL';
27
39
 
@@ -31,20 +43,33 @@ enum DimensionalNodeHashType {
31
43
  DIMENSIONAL_TREE = 'dimensionalTree',
32
44
  }
33
45
 
34
- export class DataCubeDimensionalTree implements Hashable {
35
- rootNodes: DataCubeDimensionalNode[];
46
+ export class DataCubeDimensionalGroupByNode implements Hashable {
47
+ column: string;
48
+ filter: string;
49
+ dimension: string;
36
50
 
37
- constructor(rootNodes: DataCubeDimensionalNode[]) {
38
- this.rootNodes = rootNodes;
51
+ constructor(column: string, filter: string, dimension: string) {
52
+ this.column = column;
53
+ this.filter = filter;
54
+ this.dimension = dimension;
39
55
  }
40
56
 
41
- // TODO: improve hashing
42
57
  get hashCode(): string {
43
58
  return hashArray([
44
- DimensionalNodeHashType.DIMENSIONAL_TREE,
45
- hashArray(this.rootNodes),
59
+ DimensionalNodeHashType.GROUP_BY_NODE,
60
+ this.column,
61
+ this.filter,
62
+ this.dimension,
46
63
  ]);
47
64
  }
65
+
66
+ static readonly serialization = new SerializationFactory(
67
+ createModelSchema(DataCubeDimensionalGroupByNode, {
68
+ column: primitive(),
69
+ filter: primitive(),
70
+ dimension: primitive(),
71
+ }),
72
+ );
48
73
  }
49
74
 
50
75
  export class DataCubeDimensionalNode implements Hashable {
@@ -72,27 +97,79 @@ export class DataCubeDimensionalNode implements Hashable {
72
97
  hashArray(this.groupByNodes),
73
98
  ]);
74
99
  }
100
+
101
+ static readonly serialization = new SerializationFactory(
102
+ createModelSchema(DataCubeDimensionalNode, {
103
+ column: primitive(),
104
+ dimension: primitive(),
105
+ childNodes: customList(
106
+ serializeDataCubeDimensionalNode,
107
+ deSerializeDataCubeDimensionalNode,
108
+ ),
109
+ groupByNodes: list(
110
+ usingModelSchema(DataCubeDimensionalGroupByNode.serialization.schema),
111
+ ),
112
+ }),
113
+ );
75
114
  }
76
115
 
77
- export class DataCubeDimensionalGroupByNode implements Hashable {
78
- column: string;
79
- filter: string;
80
- dimension: string;
116
+ const dataCubeDimensionalNodeModelSchema = createModelSchema(
117
+ DataCubeDimensionalNode,
118
+ {
119
+ column: primitive(),
120
+ dimension: primitive(),
121
+ childNodes: customList(
122
+ serializeDataCubeDimensionalNode,
123
+ deSerializeDataCubeDimensionalNode,
124
+ ),
125
+ groupByNodes: list(
126
+ usingModelSchema(DataCubeDimensionalGroupByNode.serialization.schema),
127
+ ),
128
+ },
129
+ );
81
130
 
82
- constructor(column: string, filter: string, dimension: string) {
83
- this.column = column;
84
- this.filter = filter;
85
- this.dimension = dimension;
131
+ function serializeDataCubeDimensionalNode(
132
+ protocol: DataCubeDimensionalNode,
133
+ ): PlainObject<DataCubeDimensionalNode> {
134
+ return serialize(dataCubeDimensionalNodeModelSchema, protocol);
135
+ }
136
+
137
+ function deSerializeDataCubeDimensionalNode(
138
+ plainObject: PlainObject<DataCubeDimensionalNode>,
139
+ ): DataCubeDimensionalNode {
140
+ return deserialize(dataCubeDimensionalNodeModelSchema, plainObject);
141
+ }
142
+
143
+ export class DataCubeDimensionalTree implements Hashable {
144
+ rootNodes: DataCubeDimensionalNode[];
145
+
146
+ constructor(rootNodes: DataCubeDimensionalNode[]) {
147
+ this.rootNodes = rootNodes;
86
148
  }
87
149
 
150
+ // TODO: improve hashing
88
151
  get hashCode(): string {
89
152
  return hashArray([
90
- DimensionalNodeHashType.GROUP_BY_NODE,
91
- this.column,
92
- this.filter,
93
- this.dimension,
153
+ DimensionalNodeHashType.DIMENSIONAL_TREE,
154
+ hashArray(this.rootNodes),
94
155
  ]);
95
156
  }
157
+
158
+ static readonly serialization = new SerializationFactory(
159
+ createModelSchema(DataCubeDimensionalTree, {
160
+ rootNodes: list(
161
+ usingModelSchema(DataCubeDimensionalNode.serialization.schema),
162
+ ),
163
+ }),
164
+ );
165
+ }
166
+
167
+ export function cloneDimensionalTree(tree: DataCubeDimensionalTree) {
168
+ let clone = new DataCubeDimensionalTree([]);
169
+ (clone as Writable<DataCubeDimensionalTree>) = JSON.parse(
170
+ JSON.stringify(tree),
171
+ ) as DataCubeDimensionalTree;
172
+ return clone;
96
173
  }
97
174
 
98
175
  export class DataCubeDimensionalMetadata {
@@ -241,6 +318,74 @@ export function hydrateDataCubeDimensionalTree(
241
318
  return safeTree;
242
319
  }
243
320
 
321
+ export function removeSubtreeNode(
322
+ tree: DataCubeDimensionalTree,
323
+ targetDimension: string,
324
+ targetColumn: string,
325
+ targetGroupByNodes: DataCubeDimensionalGroupByNode[],
326
+ ): boolean {
327
+ // Iterate only the roots of the matching dimension
328
+ for (const root of tree.rootNodes) {
329
+ if (root.dimension !== targetDimension) {
330
+ continue;
331
+ }
332
+
333
+ const found = findNodeWithParent(
334
+ root,
335
+ targetColumn,
336
+ targetGroupByNodes,
337
+ null,
338
+ );
339
+ if (!found) {
340
+ continue;
341
+ }
342
+
343
+ const { parent, index } = found;
344
+ if (parent) {
345
+ // remove from its parent’s childNodes
346
+ parent.childNodes.splice(index, 1);
347
+ } else {
348
+ // remove a root‐level node
349
+ // TODO: do nothing
350
+ }
351
+ return true; // removed successfully
352
+ }
353
+
354
+ return false; // no matching node found
355
+ }
356
+
357
+ function findNodeWithParent(
358
+ current: DataCubeDimensionalNode,
359
+ targetColumn: string,
360
+ targetGroupByNodes: DataCubeDimensionalGroupByNode[],
361
+ parent: DataCubeDimensionalNode | null = null,
362
+ ): { parent: DataCubeDimensionalNode | null; index: number } | null {
363
+ if (
364
+ current.column === targetColumn &&
365
+ findNodeByColumnOrGroup(current, targetColumn, targetGroupByNodes)
366
+ ) {
367
+ if (parent) {
368
+ const idx = parent.childNodes.findIndex((c) => c === current);
369
+ return { parent, index: idx };
370
+ } else {
371
+ // matched a root node
372
+ return { parent: null, index: -1 };
373
+ }
374
+ }
375
+ for (const child of current.childNodes) {
376
+ const found = findNodeWithParent(
377
+ child,
378
+ targetColumn,
379
+ targetGroupByNodes,
380
+ current,
381
+ );
382
+ if (found) {
383
+ return found;
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+
244
389
  function findNodeByColumnOrGroup(
245
390
  node: DataCubeDimensionalNode,
246
391
  targetColumn: string,
@@ -266,3 +411,28 @@ function findNodeByColumnOrGroup(
266
411
 
267
412
  return undefined;
268
413
  }
414
+
415
+ function pathSignature(path: DataCubeDimensionalNode[]): string {
416
+ return path
417
+ .map((node) => {
418
+ const base = `${node.dimension}:${node.column}`;
419
+ if (node.groupByNodes.length > 0) {
420
+ const filters = node.groupByNodes
421
+ .map((g) => `${g.dimension}:${g.column}:${g.filter}`)
422
+ .join(',');
423
+ return `${base}->[${filters}]`;
424
+ } else {
425
+ return `${base}->[NO_FILTER]`;
426
+ }
427
+ })
428
+ .join('|');
429
+ }
430
+
431
+ //TODO: we can add oldPath signature directly in the snapshot
432
+ export function findExtraPaths(
433
+ oldPaths: DataCubeDimensionalNode[][],
434
+ newPaths: DataCubeDimensionalNode[][],
435
+ ): DataCubeDimensionalNode[][] {
436
+ const oldSignatures = new Set(oldPaths.map(pathSignature));
437
+ return newPaths.filter((path) => !oldSignatures.has(pathSignature(path)));
438
+ }
@@ -34,6 +34,7 @@ import {
34
34
  } from '../../core/DataCubeQueryEngine.js';
35
35
  import {
36
36
  guaranteeIsNumber,
37
+ guaranteeNonNullable,
37
38
  isNonNullable,
38
39
  UnsupportedOperationError,
39
40
  } from '@finos/legend-shared';
@@ -51,6 +52,7 @@ import { _findCol } from '../../core/model/DataCubeColumn.js';
51
52
  import { useGridMenuItem, type CustomMenuItemProps } from 'ag-grid-react';
52
53
  import { FormBadge_WIP } from '../../../components/core/DataCubeFormUtils.js';
53
54
  import { DataCubeEvent } from '../../../__lib__/DataCubeEvent.js';
55
+ import type { DataCubeDimensionalMetadata } from './DataCubeGridDimensionalTree.js';
54
56
 
55
57
  export function WIP_GridMenuItem({
56
58
  name,
@@ -1040,6 +1042,21 @@ export function generateDimensionalMenuBuilder(
1040
1042
 
1041
1043
  //TODO: add more menu items like sort,filter...
1042
1044
  return [
1045
+ {
1046
+ name: 'Zoom Out',
1047
+ action: (param) => {
1048
+ const data = param.node?.data.metadata as Map<
1049
+ string,
1050
+ DataCubeDimensionalMetadata
1051
+ >;
1052
+ view.grid
1053
+ .retrieveDrilloutData(
1054
+ data,
1055
+ guaranteeNonNullable(params.column?.getColId()),
1056
+ )
1057
+ .catch((error) => dataCube.alertService.alertUnhandledError(error));
1058
+ },
1059
+ },
1043
1060
  {
1044
1061
  name: 'Export',
1045
1062
  subMenu: [
@@ -47,7 +47,13 @@ import {
47
47
  DataCubeGridMode,
48
48
  } from '../../core/DataCubeQueryEngine.js';
49
49
  import { DataCubeEvent } from '../../../__lib__/DataCubeEvent.js';
50
- import { hydrateDataCubeDimensionalTree } from './DataCubeGridDimensionalTree.js';
50
+ import {
51
+ cloneDimensionalTree,
52
+ hydrateDataCubeDimensionalTree,
53
+ removeSubtreeNode,
54
+ type DataCubeDimensionalTree,
55
+ type DataCubeDimensionalMetadata,
56
+ } from './DataCubeGridDimensionalTree.js';
51
57
 
52
58
  /**
53
59
  * This query editor state is responsible for syncing the internal state of ag-grid
@@ -286,30 +292,60 @@ export class DataCubeGridState extends DataCubeSnapshotController {
286
292
  const latestSnapshot = this.getLatestSnapshot();
287
293
 
288
294
  if (latestSnapshot) {
289
- latestSnapshot.data.tree = hydrateDataCubeDimensionalTree(
290
- DataCubeConfiguration.serialization.fromJson(
291
- latestSnapshot.data.configuration,
292
- ),
293
- );
295
+ if (!latestSnapshot.data.dimensionalTree) {
296
+ latestSnapshot.data.dimensionalTree = hydrateDataCubeDimensionalTree(
297
+ DataCubeConfiguration.serialization.fromJson(
298
+ latestSnapshot.data.configuration,
299
+ ),
300
+ );
301
+ }
294
302
  await this._applyDimensionalSnapshot(latestSnapshot);
295
303
  }
296
304
  }
297
305
 
298
- async retrieveDrilldownData(data: CellDoubleClickedEvent) {
306
+ async retrieveDrilloutData(
307
+ metadata: Map<string, DataCubeDimensionalMetadata>,
308
+ dimension: string,
309
+ ) {
299
310
  const latestSnapshot = guaranteeNonNullable(this.getLatestSnapshot());
300
- latestSnapshot.data.tree = hydrateDataCubeDimensionalTree(
311
+ const previousTree = cloneDimensionalTree(
312
+ guaranteeNonNullable(latestSnapshot.data.dimensionalTree),
313
+ );
314
+ removeSubtreeNode(
315
+ guaranteeNonNullable(latestSnapshot.data.dimensionalTree),
316
+ dimension,
317
+ guaranteeNonNullable(metadata.get(dimension)?.column),
318
+ guaranteeNonNullable(metadata.get(dimension)?.groupByNodes),
319
+ );
320
+ await this._applyDimensionalSnapshot(latestSnapshot, previousTree);
321
+ }
322
+
323
+ async retrieveDrilldownData(data?: CellDoubleClickedEvent) {
324
+ const latestSnapshot = guaranteeNonNullable(this.getLatestSnapshot());
325
+ const previousTree = cloneDimensionalTree(
326
+ guaranteeNonNullable(latestSnapshot.data.dimensionalTree),
327
+ );
328
+ latestSnapshot.data.dimensionalTree = hydrateDataCubeDimensionalTree(
301
329
  DataCubeConfiguration.serialization.fromJson(
302
330
  latestSnapshot.data.configuration,
303
331
  ),
304
332
  data,
305
- latestSnapshot.data.tree,
333
+ latestSnapshot.data.dimensionalTree,
306
334
  );
307
- await this._applyDimensionalSnapshot(latestSnapshot);
335
+ await this._applyDimensionalSnapshot(latestSnapshot, previousTree);
308
336
  }
309
337
 
310
- private async _applyDimensionalSnapshot(snapshot: DataCubeSnapshot) {
311
- //TODO: only trigger when there is an actual change in tree
312
- const result = await fetchDimensionalQueryRows(snapshot, this._view);
338
+ private async _applyDimensionalSnapshot(
339
+ snapshot: DataCubeSnapshot,
340
+ previousTree?: DataCubeDimensionalTree,
341
+ ) {
342
+ this._client?.hideOverlay();
343
+ this._client?.setGridOption(DataCubeClientModelOption.LOADING, true);
344
+ const result = await fetchDimensionalQueryRows(
345
+ snapshot,
346
+ this._view,
347
+ previousTree,
348
+ );
313
349
  this._client?.setGridOption(
314
350
  DataCubeClientModelOption.ROW_DATA,
315
351
  result.rowData,
@@ -318,6 +354,10 @@ export class DataCubeGridState extends DataCubeSnapshotController {
318
354
  DataCubeClientModelOption.COLUMN_DEFS,
319
355
  result.columnDefs,
320
356
  );
357
+ if (result.rowData.length === 0) {
358
+ this._client?.showNoRowsOverlay();
359
+ }
360
+ this._client?.setGridOption(DataCubeClientModelOption.LOADING, false);
321
361
  this.publishSnapshot(snapshot);
322
362
  }
323
363