@finos/legend-data-cube 0.1.32 → 0.2.1

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/lib/components/DataCubeStatusBar.d.ts.map +1 -1
  2. package/lib/components/DataCubeStatusBar.js +4 -2
  3. package/lib/components/DataCubeStatusBar.js.map +1 -1
  4. package/lib/components/view/editor/DataCubeEditor.d.ts.map +1 -1
  5. package/lib/components/view/editor/DataCubeEditor.js +12 -1
  6. package/lib/components/view/editor/DataCubeEditor.js.map +1 -1
  7. package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts.map +1 -1
  8. package/lib/components/view/grid/DataCubeMultidimensionalGrid.js +40 -2
  9. package/lib/components/view/grid/DataCubeMultidimensionalGrid.js.map +1 -1
  10. package/lib/index.css +1 -1
  11. package/lib/package.json +1 -1
  12. package/lib/stores/core/DataCubeQueryBuilder.d.ts +12 -0
  13. package/lib/stores/core/DataCubeQueryBuilder.d.ts.map +1 -1
  14. package/lib/stores/core/DataCubeQueryBuilder.js +141 -4
  15. package/lib/stores/core/DataCubeQueryBuilder.js.map +1 -1
  16. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts +2 -0
  17. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts.map +1 -1
  18. package/lib/stores/core/DataCubeQueryBuilderUtils.js +18 -1
  19. package/lib/stores/core/DataCubeQueryBuilderUtils.js.map +1 -1
  20. package/lib/stores/core/DataCubeQueryEngine.d.ts +6 -0
  21. package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
  22. package/lib/stores/core/DataCubeQueryEngine.js +9 -0
  23. package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
  24. package/lib/stores/core/DataCubeSnapshot.d.ts +2 -0
  25. package/lib/stores/core/DataCubeSnapshot.d.ts.map +1 -1
  26. package/lib/stores/core/DataCubeSnapshot.js.map +1 -1
  27. package/lib/stores/core/DataCubeSnapshotBuilder.js +1 -0
  28. package/lib/stores/core/DataCubeSnapshotBuilder.js.map +1 -1
  29. package/lib/stores/core/model/DataCubeSpecification.d.ts +2 -0
  30. package/lib/stores/core/model/DataCubeSpecification.d.ts.map +1 -1
  31. package/lib/stores/core/model/DataCubeSpecification.js +3 -0
  32. package/lib/stores/core/model/DataCubeSpecification.js.map +1 -1
  33. package/lib/stores/view/DataCubeViewState.d.ts.map +1 -1
  34. package/lib/stores/view/DataCubeViewState.js +1 -0
  35. package/lib/stores/view/DataCubeViewState.js.map +1 -1
  36. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts.map +1 -1
  37. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js +8 -4
  38. package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js.map +1 -1
  39. package/lib/stores/view/editor/DataCubeEditorState.d.ts.map +1 -1
  40. package/lib/stores/view/editor/DataCubeEditorState.js +1 -0
  41. package/lib/stores/view/editor/DataCubeEditorState.js.map +1 -1
  42. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts +163 -1
  43. package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
  44. package/lib/stores/view/grid/DataCubeGridClientEngine.js +142 -3
  45. package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
  46. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts +155 -1
  47. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts.map +1 -1
  48. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js +170 -2
  49. package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js.map +1 -1
  50. package/lib/stores/view/grid/DataCubeGridControllerState.d.ts.map +1 -1
  51. package/lib/stores/view/grid/DataCubeGridControllerState.js +5 -3
  52. package/lib/stores/view/grid/DataCubeGridControllerState.js.map +1 -1
  53. package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts +54 -0
  54. package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts.map +1 -0
  55. package/lib/stores/view/grid/DataCubeGridDimensionalTree.js +264 -0
  56. package/lib/stores/view/grid/DataCubeGridDimensionalTree.js.map +1 -0
  57. package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts +1 -0
  58. package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts.map +1 -1
  59. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js +207 -1
  60. package/lib/stores/view/grid/DataCubeGridMenuBuilder.js.map +1 -1
  61. package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts +2 -0
  62. package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts.map +1 -1
  63. package/lib/stores/view/grid/DataCubeGridQueryBuilder.js +7 -1
  64. package/lib/stores/view/grid/DataCubeGridQueryBuilder.js.map +1 -1
  65. package/lib/stores/view/grid/DataCubeGridState.d.ts +6 -1
  66. package/lib/stores/view/grid/DataCubeGridState.d.ts.map +1 -1
  67. package/lib/stores/view/grid/DataCubeGridState.js +38 -2
  68. package/lib/stores/view/grid/DataCubeGridState.js.map +1 -1
  69. package/package.json +2 -2
  70. package/src/components/DataCubeStatusBar.tsx +9 -2
  71. package/src/components/view/editor/DataCubeEditor.tsx +16 -1
  72. package/src/components/view/grid/DataCubeMultidimensionalGrid.tsx +63 -1
  73. package/src/stores/core/DataCubeQueryBuilder.ts +256 -2
  74. package/src/stores/core/DataCubeQueryBuilderUtils.ts +22 -0
  75. package/src/stores/core/DataCubeQueryEngine.ts +10 -0
  76. package/src/stores/core/DataCubeSnapshot.ts +2 -0
  77. package/src/stores/core/DataCubeSnapshotBuilder.ts +1 -1
  78. package/src/stores/core/model/DataCubeSpecification.ts +5 -0
  79. package/src/stores/view/DataCubeViewState.ts +1 -0
  80. package/src/stores/view/editor/DataCubeEditorDimensionsPanelState.ts +9 -7
  81. package/src/stores/view/editor/DataCubeEditorState.tsx +1 -0
  82. package/src/stores/view/grid/DataCubeGridClientEngine.ts +268 -6
  83. package/src/stores/view/grid/DataCubeGridConfigurationBuilder.tsx +244 -3
  84. package/src/stores/view/grid/DataCubeGridControllerState.ts +10 -2
  85. package/src/stores/view/grid/DataCubeGridDimensionalTree.ts +438 -0
  86. package/src/stores/view/grid/DataCubeGridMenuBuilder.tsx +257 -0
  87. package/src/stores/view/grid/DataCubeGridQueryBuilder.ts +22 -1
  88. package/src/stores/view/grid/DataCubeGridState.ts +92 -2
  89. package/tsconfig.json +1 -0
@@ -69,10 +69,12 @@ import {
69
69
  isNumber,
70
70
  isValidUrl,
71
71
  assertTrue,
72
+ guaranteeNonNullable,
72
73
  } from '@finos/legend-shared';
73
- import type {
74
+ import {
74
75
  DataCubeColumnConfiguration,
75
- DataCubeConfiguration,
76
+ type DataCubeConfiguration,
77
+ type DataCubeDimensionConfiguration,
76
78
  } from '../../core/model/DataCubeConfiguration.js';
77
79
  import {
78
80
  DataCubeColumnDataType,
@@ -91,6 +93,7 @@ import {
91
93
  import type { CustomLoadingCellRendererProps } from 'ag-grid-react';
92
94
  import { DataCubeIcon } from '@finos/legend-art';
93
95
  import type { DataCubeViewState } from '../DataCubeViewState.js';
96
+ import type { DataCubeDimensionalMetadata } from './DataCubeGridDimensionalTree.js';
94
97
 
95
98
  // --------------------------------- UTILITIES ---------------------------------
96
99
 
@@ -181,6 +184,51 @@ function getCellRenderer(columnData: ColumnData) {
181
184
  return null;
182
185
  }
183
186
 
187
+ function getDimensionCellRenderer(
188
+ columnData: ColumnData,
189
+ dimensions: DataCubeDimensionConfiguration[],
190
+ ) {
191
+ const { column } = columnData;
192
+ const dataType = getDataType(column.type);
193
+ if (
194
+ dataType === DataCubeColumnDataType.TEXT &&
195
+ dimensions.filter((dim) => dim.name === column.name).length === 1
196
+ ) {
197
+ return function ValueRenderer(params: ICellRendererParams) {
198
+ const level = guaranteeNonNullable(
199
+ (params.data.metadata as Map<string, DataCubeDimensionalMetadata>).get(
200
+ column.name,
201
+ )?.level,
202
+ );
203
+ const indent = '\u00A0'.repeat((level + 1) * 2); // 4 non-breaking spaces per level
204
+ return `${indent}${params.value}`;
205
+ };
206
+ } else if (dataType === DataCubeColumnDataType.TEXT && column.displayAsLink) {
207
+ return function LinkRenderer(params: ICellRendererParams) {
208
+ const isUrl = isValidUrl(params.value);
209
+ if (!isUrl) {
210
+ return params.value;
211
+ }
212
+ const url = params.value as string;
213
+ const label = getQueryParameterValue(
214
+ column.linkLabelParameter ?? DEFAULT_URL_LABEL_QUERY_PARAM,
215
+ getQueryParameters(url, true),
216
+ );
217
+ return (
218
+ <a
219
+ href={url}
220
+ target="_blank"
221
+ rel="noreferrer"
222
+ className="text-blue-600 underline"
223
+ >
224
+ {label ?? url}
225
+ </a>
226
+ );
227
+ };
228
+ }
229
+ return null;
230
+ }
231
+
184
232
  function _displaySpec(columnData: ColumnData) {
185
233
  const { name, snapshot, column, configuration } = columnData;
186
234
  const dataType = getDataType(column.type);
@@ -341,7 +389,13 @@ function _displaySpec(columnData: ColumnData) {
341
389
  params.value !== INTERNAL__GRID_CLIENT_MISSING_VALUE
342
390
  ? `Value = ${params.value === '' ? "''" : params.value === true ? 'TRUE' : params.value === false ? 'FALSE' : params.value}`
343
391
  : `Missing Value`,
344
- cellRenderer: getCellRenderer(columnData),
392
+ cellRenderer:
393
+ configuration.dimensions.dimensions.length > 0
394
+ ? getDimensionCellRenderer(
395
+ columnData,
396
+ configuration.dimensions.dimensions,
397
+ )
398
+ : getCellRenderer(columnData),
345
399
  } satisfies ColDef;
346
400
  }
347
401
 
@@ -631,6 +685,121 @@ export function generateBaseGridOptions(view: DataCubeViewState): GridOptions {
631
685
  };
632
686
  }
633
687
 
688
+ export function generateDimensionalBaseGridOptions(
689
+ view: DataCubeViewState,
690
+ ): GridOptions {
691
+ const grid = view.grid;
692
+
693
+ return {
694
+ // -------------------------------------- README --------------------------------------
695
+ // NOTE: we observe performance degradataion when configuring the grid via React component
696
+ // props when the options is non-static, i.e. changed when the query configuration changes.
697
+ // As such, we must ONLY ADD STATIC CONFIGURATION HERE, and dynamic configuration should be
698
+ // programatically updated when the query is modified.
699
+ //
700
+ //
701
+ // -------------------------------------- DISPLAY --------------------------------------
702
+ rowHeight: INTERNAL__GRID_CLIENT_ROW_HEIGHT,
703
+ headerHeight: INTERNAL__GRID_CLIENT_HEADER_HEIGHT,
704
+ noRowsOverlayComponent: () => (
705
+ <div className="flex items-center border-[1.5px] border-neutral-300 p-2 font-medium text-neutral-400">
706
+ <div>
707
+ <DataCubeIcon.WarningCircle className="mr-1 stroke-2 text-lg" />
708
+ </div>
709
+ 0 rows
710
+ </div>
711
+ ),
712
+ loadingOverlayComponent: () => (
713
+ <div className="flex items-center border-[1.5px] border-neutral-300 p-2 font-medium text-neutral-400">
714
+ <div>
715
+ <DataCubeIcon.Loader className="mr-1 animate-spin stroke-2 text-lg" />
716
+ </div>
717
+ Loading...
718
+ </div>
719
+ ),
720
+ // Show cursor position when scrolling
721
+ onBodyScroll: (event) => {
722
+ const rowCount = event.api.getDisplayedRowCount();
723
+ const range = event.api.getVerticalPixelRange();
724
+ const start = Math.max(
725
+ 1,
726
+ Math.ceil(range.top / INTERNAL__GRID_CLIENT_ROW_HEIGHT) + 1,
727
+ );
728
+ const end = Math.min(
729
+ rowCount,
730
+ Math.floor(range.bottom / INTERNAL__GRID_CLIENT_ROW_HEIGHT),
731
+ );
732
+ grid.setScrollHintText(`${start}-${end}/${rowCount}`);
733
+ event.api.hidePopupMenu(); // hide context-menu while scrolling
734
+ },
735
+ onBodyScrollEnd: () => grid.setScrollHintText(undefined),
736
+ // -------------------------------------- CONTEXT MENU --------------------------------------
737
+ // figure out the context menu
738
+ preventDefaultOnContextMenu: true, // prevent showing the browser's context menu
739
+ columnMenu: 'new', // ensure context menu works on header
740
+ // NOTE: dynamically generate the content of the context menu to make sure the items are not stale
741
+ getContextMenuItems: (params) =>
742
+ grid.controller.menuBuilder?.(params, false) ?? [],
743
+ getMainMenuItems: (params) =>
744
+ grid.controller.menuBuilder?.(params, true) ?? [],
745
+ // NOTE: when right-clicking empty space in the header, a menu will show up
746
+ // with 2 default options: 'Choose Columns` and `Reset Columns`, which is not
747
+ // a desired behavior, so we hide the popup menu immediately
748
+ onColumnMenuVisibleChanged: (event) => {
749
+ if (!event.column) {
750
+ const menuElement = document.querySelector(
751
+ `.${INTERNAL__GridClientUtilityCssClassName.ROOT} .ag-popup .ag-menu`,
752
+ ) as HTMLElement | undefined;
753
+ if (menuElement) {
754
+ menuElement.style.display = 'none';
755
+ }
756
+ event.api.hidePopupMenu();
757
+ }
758
+ },
759
+ // -------------------------------------- COLUMN SIZING --------------------------------------
760
+ autoSizePadding: INTERNAL__GRID_CLIENT_AUTO_RESIZE_PADDING,
761
+ autoSizeStrategy: {
762
+ type: 'fitCellContents',
763
+ },
764
+ defaultColDef: {
765
+ sortable: false,
766
+ },
767
+ // -------------------------------------- TOOLTIP --------------------------------------
768
+ tooltipShowDelay: INTERNAL__GRID_CLIENT_TOOLTIP_SHOW_DELAY,
769
+ // though this is a nice behavior to have enabled, ag-grid not dismissing tooltip
770
+ // when context-menu is triggered makes it an undesirable interaction.
771
+ tooltipInteraction: false,
772
+ // -------------------------------------- COLUMN MOVING --------------------------------------
773
+ suppressDragLeaveHidesColumns: true, // disable this since it's quite easy to accidentally hide columns while moving
774
+ // -------------------------------------- SELECTION --------------------------------------
775
+ cellSelection: true,
776
+ // -------------------------------------- SIDEBAR --------------------------------------
777
+ sideBar: {
778
+ toolPanels: [
779
+ {
780
+ id: 'columns',
781
+ labelDefault: 'Columns',
782
+ labelKey: 'columns',
783
+ iconKey: 'columns',
784
+ toolPanel: 'agColumnsToolPanel',
785
+ minWidth: INTERNAL__GRID_CLIENT_SIDE_BAR_WIDTH,
786
+ width: INTERNAL__GRID_CLIENT_SIDE_BAR_WIDTH,
787
+ toolPanelParams: {
788
+ suppressValues: true,
789
+ suppressPivotMode: true,
790
+ suppressRowGroups: true,
791
+ },
792
+ },
793
+ ],
794
+ position: 'right',
795
+ },
796
+ allowDragFromColumnsToolPanel: true,
797
+ // -------------------------------------- PERFORMANCE --------------------------------------
798
+ animateRows: false, // improve performance
799
+ suppressColumnMoveAnimation: true, // improve performance
800
+ };
801
+ }
802
+
634
803
  function generatePivotResultColumnHeaderTooltip(
635
804
  id: string,
636
805
  snapshot: DataCubeSnapshot,
@@ -922,6 +1091,78 @@ export function generateColumnDefs(
922
1091
  return columnDefs;
923
1092
  }
924
1093
 
1094
+ export function generateColumnDefsForDimensions(
1095
+ snapshot: DataCubeSnapshot,
1096
+ configuration: DataCubeConfiguration,
1097
+ ) {
1098
+ // NOTE: only show columns which are fetched in select() or are dimensions as we
1099
+ // can't solely rely on column selection because of certain restrictions
1100
+ // from ag-grid, e.g. in the case of row grouping tree column: the columns
1101
+ // which are grouped must be present in the column definitions, so even
1102
+ // when some of these might not be selected explicitly by the users, they
1103
+ // must still be included in the column definitions, and made hidden instead.
1104
+ const dimensionColNames = configuration.dimensions.dimensions.flatMap(
1105
+ (col) => col.columns,
1106
+ );
1107
+ const dimCols = configuration.dimensions.dimensions.map(
1108
+ (col) => new DataCubeColumnConfiguration(col.name, 'String'),
1109
+ );
1110
+
1111
+ let columns = configuration.columns.filter(
1112
+ (col) =>
1113
+ _findCol(snapshot.data.selectColumns, col.name) ??
1114
+ _findCol(snapshot.data.groupExtendedColumns, col.name),
1115
+ );
1116
+ columns = columns.filter((col) => !dimensionColNames.includes(col.name));
1117
+ columns.unshift(...dimCols);
1118
+
1119
+ const columnDefs = [
1120
+ ...(configuration.showRootAggregation
1121
+ ? [
1122
+ {
1123
+ colId: INTERNAL__GRID_CLIENT_ROOT_AGGREGATION_COLUMN_ID,
1124
+ headerName: 'Root',
1125
+ field: INTERNAL__GRID_CLIENT_ROOT_AGGREGATION_COLUMN_ID,
1126
+ hide: true,
1127
+ enableValue: false, // disable GUI interactions to modify this column's aggregate function
1128
+ allowedAggFuncs: [], // disable GUI for options of the agg functions
1129
+ enablePivot: false,
1130
+ enableRowGroup: false,
1131
+
1132
+ suppressColumnsToolPanel: true,
1133
+ rowGroup: true,
1134
+ rowGroupIndex: 0,
1135
+ } satisfies ColDef,
1136
+ ]
1137
+ : []),
1138
+ ...columns.map((column) => {
1139
+ const columnData = {
1140
+ name: column.name,
1141
+ snapshot,
1142
+ column,
1143
+ configuration,
1144
+ };
1145
+ return {
1146
+ headerName: column.displayName ?? column.name,
1147
+ headerTooltip: `Column = ${
1148
+ column.displayName
1149
+ ? `${column.displayName} (${column.name})`
1150
+ : column.name
1151
+ }`,
1152
+ suppressSpanHeaderHeight: true,
1153
+ colId: column.name,
1154
+ field: column.name,
1155
+ menuTabs: [],
1156
+
1157
+ ..._displaySpec(columnData),
1158
+ ..._sizeSpec(columnData),
1159
+ } satisfies ColDef;
1160
+ }),
1161
+ ] satisfies (ColDef | ColGroupDef)[];
1162
+
1163
+ return columnDefs;
1164
+ }
1165
+
925
1166
  export function generateGridOptionsFromSnapshot(
926
1167
  snapshot: DataCubeSnapshot,
927
1168
  configuration: DataCubeConfiguration,
@@ -38,6 +38,7 @@ import {
38
38
  DataCubeQuerySortDirection,
39
39
  isPivotResultColumnName,
40
40
  getPivotResultColumnBaseColumnName,
41
+ isDimensionalGridMode,
41
42
  } from '../../core/DataCubeQueryEngine.js';
42
43
  import type {
43
44
  DefaultMenuItem,
@@ -46,7 +47,10 @@ import type {
46
47
  MenuItemDef,
47
48
  } from 'ag-grid-community';
48
49
  import type { DataCubeViewState } from '../DataCubeViewState.js';
49
- import { generateMenuBuilder } from './DataCubeGridMenuBuilder.js';
50
+ import {
51
+ generateDimensionalMenuBuilder,
52
+ generateMenuBuilder,
53
+ } from './DataCubeGridMenuBuilder.js';
50
54
  import {
51
55
  buildFilterEditorTree,
52
56
  buildFilterSnapshot,
@@ -427,7 +431,11 @@ export class DataCubeGridControllerState extends DataCubeSnapshotController {
427
431
 
428
432
  this.sortColumns = snapshot.data.sortColumns;
429
433
 
430
- this.menuBuilder = generateMenuBuilder(this);
434
+ this.menuBuilder = isDimensionalGridMode(
435
+ this.view.info.configuration.gridMode,
436
+ )
437
+ ? generateDimensionalMenuBuilder(this)
438
+ : generateMenuBuilder(this);
431
439
  }
432
440
 
433
441
  private propagateChanges(baseSnapshot: DataCubeSnapshot) {