@finos/legend-data-cube 0.1.31 → 0.2.0
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.
- package/lib/components/DataCubeStatusBar.d.ts.map +1 -1
- package/lib/components/DataCubeStatusBar.js +4 -2
- package/lib/components/DataCubeStatusBar.js.map +1 -1
- package/lib/components/view/editor/DataCubeEditor.d.ts.map +1 -1
- package/lib/components/view/editor/DataCubeEditor.js +5 -1
- package/lib/components/view/editor/DataCubeEditor.js.map +1 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.d.ts.map +1 -1
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js +40 -2
- package/lib/components/view/grid/DataCubeMultidimensionalGrid.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/DataCubeAPI.d.ts +6 -0
- package/lib/stores/DataCubeAPI.d.ts.map +1 -1
- package/lib/stores/DataCubeAPI.js +4 -0
- package/lib/stores/DataCubeAPI.js.map +1 -1
- package/lib/stores/core/DataCubeQueryBuilder.d.ts +12 -0
- package/lib/stores/core/DataCubeQueryBuilder.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryBuilder.js +141 -4
- package/lib/stores/core/DataCubeQueryBuilder.js.map +1 -1
- package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts +2 -0
- package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryBuilderUtils.js +18 -1
- package/lib/stores/core/DataCubeQueryBuilderUtils.js.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.d.ts +5 -0
- package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
- package/lib/stores/core/DataCubeQueryEngine.js +8 -0
- package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
- package/lib/stores/core/DataCubeSnapshot.d.ts +2 -0
- package/lib/stores/core/DataCubeSnapshot.d.ts.map +1 -1
- package/lib/stores/core/DataCubeSnapshot.js.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilder.d.ts.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilder.js +2 -2
- package/lib/stores/core/DataCubeSnapshotBuilder.js.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.d.ts +2 -1
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.d.ts.map +1 -1
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.js +3 -3
- package/lib/stores/core/DataCubeSnapshotBuilderUtils.js.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.d.ts.map +1 -1
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js +7 -4
- package/lib/stores/view/editor/DataCubeEditorDimensionsPanelState.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts +163 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridClientEngine.js +134 -2
- package/lib/stores/view/grid/DataCubeGridClientEngine.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts +155 -1
- package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js +170 -2
- package/lib/stores/view/grid/DataCubeGridConfigurationBuilder.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridControllerState.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridControllerState.js +5 -3
- package/lib/stores/view/grid/DataCubeGridControllerState.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts +48 -0
- package/lib/stores/view/grid/DataCubeGridDimensionalTree.d.ts.map +1 -0
- package/lib/stores/view/grid/DataCubeGridDimensionalTree.js +168 -0
- package/lib/stores/view/grid/DataCubeGridDimensionalTree.js.map +1 -0
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts +1 -0
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.js +197 -0
- package/lib/stores/view/grid/DataCubeGridMenuBuilder.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts +2 -0
- package/lib/stores/view/grid/DataCubeGridQueryBuilder.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridQueryBuilder.js +7 -1
- package/lib/stores/view/grid/DataCubeGridQueryBuilder.js.map +1 -1
- package/lib/stores/view/grid/DataCubeGridState.d.ts +4 -1
- package/lib/stores/view/grid/DataCubeGridState.d.ts.map +1 -1
- package/lib/stores/view/grid/DataCubeGridState.js +24 -2
- package/lib/stores/view/grid/DataCubeGridState.js.map +1 -1
- package/package.json +4 -4
- package/src/components/DataCubeStatusBar.tsx +9 -2
- package/src/components/view/editor/DataCubeEditor.tsx +8 -1
- package/src/components/view/grid/DataCubeMultidimensionalGrid.tsx +63 -1
- package/src/stores/DataCubeAPI.ts +10 -0
- package/src/stores/core/DataCubeQueryBuilder.ts +256 -2
- package/src/stores/core/DataCubeQueryBuilderUtils.ts +22 -0
- package/src/stores/core/DataCubeQueryEngine.ts +9 -0
- package/src/stores/core/DataCubeSnapshot.ts +2 -0
- package/src/stores/core/DataCubeSnapshotBuilder.ts +2 -0
- package/src/stores/core/DataCubeSnapshotBuilderUtils.ts +6 -2
- package/src/stores/view/editor/DataCubeEditorDimensionsPanelState.ts +8 -7
- package/src/stores/view/grid/DataCubeGridClientEngine.ts +252 -5
- package/src/stores/view/grid/DataCubeGridConfigurationBuilder.tsx +244 -3
- package/src/stores/view/grid/DataCubeGridControllerState.ts +10 -2
- package/src/stores/view/grid/DataCubeGridDimensionalTree.ts +268 -0
- package/src/stores/view/grid/DataCubeGridMenuBuilder.tsx +240 -0
- package/src/stores/view/grid/DataCubeGridQueryBuilder.ts +22 -1
- package/src/stores/view/grid/DataCubeGridState.ts +52 -2
- package/tsconfig.json +1 -0
|
@@ -226,13 +226,14 @@ export class DataCubeEditorDimensionsPanelState
|
|
|
226
226
|
name: dimension.name,
|
|
227
227
|
columns: dimension.columns.map((column) => column.name),
|
|
228
228
|
}));
|
|
229
|
+
newSnapshot.data.configuration = {
|
|
230
|
+
...newSnapshot.data.configuration,
|
|
231
|
+
dimensions: DataCubeDimensionsConfiguration.serialization.toJson(
|
|
232
|
+
dimensionsConfiguration,
|
|
233
|
+
),
|
|
234
|
+
};
|
|
235
|
+
} else {
|
|
236
|
+
newSnapshot = baseSnapshot;
|
|
229
237
|
}
|
|
230
|
-
|
|
231
|
-
newSnapshot.data.configuration = {
|
|
232
|
-
...newSnapshot.data.configuration,
|
|
233
|
-
dimensions: DataCubeDimensionsConfiguration.serialization.toJson(
|
|
234
|
-
dimensionsConfiguration,
|
|
235
|
-
),
|
|
236
|
-
};
|
|
237
238
|
}
|
|
238
239
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
IllegalStateError,
|
|
26
26
|
assertErrorThrown,
|
|
27
27
|
guaranteeNonNullable,
|
|
28
|
+
guaranteeType,
|
|
28
29
|
hashObject,
|
|
29
30
|
isBoolean,
|
|
30
31
|
isNonNullable,
|
|
@@ -35,11 +36,13 @@ import {
|
|
|
35
36
|
PRIMITIVE_TYPE,
|
|
36
37
|
PureClientVersion,
|
|
37
38
|
type TabularDataSet,
|
|
39
|
+
type TDSRow,
|
|
38
40
|
} from '@finos/legend-graph';
|
|
39
41
|
import { makeObservable, observable, runInAction } from 'mobx';
|
|
40
|
-
import
|
|
42
|
+
import {
|
|
41
43
|
DataCubeConfiguration,
|
|
42
|
-
DataCubeConfigurationColorKey,
|
|
44
|
+
type DataCubeConfigurationColorKey,
|
|
45
|
+
type DataCubeDimensionConfiguration,
|
|
43
46
|
} from '../../core/model/DataCubeConfiguration.js';
|
|
44
47
|
import { type DataCubeSnapshot } from '../../core/DataCubeSnapshot.js';
|
|
45
48
|
import { _findCol, _sortByColName } from '../../core/model/DataCubeColumn.js';
|
|
@@ -47,7 +50,10 @@ import { isPivotResultColumnName } from '../../core/DataCubeQueryEngine.js';
|
|
|
47
50
|
import { buildSnapshotFromGridState } from './DataCubeGridSnapshotBuilder.js';
|
|
48
51
|
import { AlertType } from '../../services/DataCubeAlertService.js';
|
|
49
52
|
import type { DataCubeViewState } from '../DataCubeViewState.js';
|
|
50
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
buildDimensionalGridDataFetchExecutableQuery,
|
|
55
|
+
buildGridDataFetchExecutableQuery,
|
|
56
|
+
} from './DataCubeGridQueryBuilder.js';
|
|
51
57
|
import { DataCubeSettingKey } from '../../../__lib__/DataCubeSetting.js';
|
|
52
58
|
import { DEFAULT_ALERT_WINDOW_CONFIG } from '../../services/DataCubeLayoutService.js';
|
|
53
59
|
import {
|
|
@@ -57,8 +63,22 @@ import {
|
|
|
57
63
|
import { _lambda } from '../../core/DataCubeQueryBuilderUtils.js';
|
|
58
64
|
import { sum } from 'mathjs';
|
|
59
65
|
import { DataCubeEvent } from '../../../__lib__/DataCubeEvent.js';
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
import { generateColumnDefsForDimensions } from './DataCubeGridConfigurationBuilder.js';
|
|
67
|
+
import {
|
|
68
|
+
DataCubeDimensionalMetadata,
|
|
69
|
+
generateDimensionalPaths,
|
|
70
|
+
type DataCubeDimensionalNode,
|
|
71
|
+
type DataCubeDimensionalGroupByNode,
|
|
72
|
+
DIMENSIONAL_L0_COLUMN,
|
|
73
|
+
} from './DataCubeGridDimensionalTree.js';
|
|
74
|
+
|
|
75
|
+
type DataCubeGridClientCellValue =
|
|
76
|
+
| string
|
|
77
|
+
| number
|
|
78
|
+
| boolean
|
|
79
|
+
| Map<string, DataCubeDimensionalMetadata>
|
|
80
|
+
| null
|
|
81
|
+
| undefined;
|
|
62
82
|
type DataCubeGridClientRowData = {
|
|
63
83
|
[key: string]: DataCubeGridClientCellValue;
|
|
64
84
|
};
|
|
@@ -259,6 +279,136 @@ function buildRowData(
|
|
|
259
279
|
});
|
|
260
280
|
}
|
|
261
281
|
|
|
282
|
+
function buildDimensionalRowData(
|
|
283
|
+
result: TabularDataSet,
|
|
284
|
+
snapshot: DataCubeSnapshot,
|
|
285
|
+
dimensionalNodes: DataCubeDimensionalNode[],
|
|
286
|
+
rowData: DataCubeGridClientRowData[],
|
|
287
|
+
dimensions: DataCubeDimensionConfiguration[],
|
|
288
|
+
): DataCubeGridClientRowData[] {
|
|
289
|
+
const newRows = result.rows.map((rawRow) =>
|
|
290
|
+
_createClientRow(rawRow, result.columns, dimensionalNodes, dimensions),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const updated = [...rowData];
|
|
294
|
+
for (const newRow of newRows) {
|
|
295
|
+
const insertIndex = _findInsertIndex(newRow, updated, dimensionalNodes);
|
|
296
|
+
updated.splice(insertIndex, 0, newRow);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return updated;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function _createClientRow(
|
|
303
|
+
rawRow: TDSRow,
|
|
304
|
+
columns: string[],
|
|
305
|
+
nodes: DataCubeDimensionalNode[],
|
|
306
|
+
configs: DataCubeDimensionConfiguration[],
|
|
307
|
+
): DataCubeGridClientRowData {
|
|
308
|
+
const row: DataCubeGridClientRowData = {};
|
|
309
|
+
rawRow.values.forEach((value, i) => {
|
|
310
|
+
row[guaranteeNonNullable(columns[i])] = isBoolean(value)
|
|
311
|
+
? String(value)
|
|
312
|
+
: isNonNullable(value)
|
|
313
|
+
? value
|
|
314
|
+
: INTERNAL__GRID_CLIENT_MISSING_VALUE;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const metadata = new Map<string, DataCubeDimensionalMetadata>();
|
|
318
|
+
for (const node of nodes) {
|
|
319
|
+
const config = guaranteeNonNullable(
|
|
320
|
+
configs.find((d) => d.name === node.dimension),
|
|
321
|
+
);
|
|
322
|
+
metadata.set(
|
|
323
|
+
node.dimension,
|
|
324
|
+
new DataCubeDimensionalMetadata(
|
|
325
|
+
node.column,
|
|
326
|
+
config.columns.indexOf(node.column),
|
|
327
|
+
node.groupByNodes,
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
row.metadata = metadata;
|
|
332
|
+
return row;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function _findInsertIndex(
|
|
336
|
+
newRow: DataCubeGridClientRowData,
|
|
337
|
+
existing: DataCubeGridClientRowData[],
|
|
338
|
+
dimensionalNodes: DataCubeDimensionalNode[],
|
|
339
|
+
): number {
|
|
340
|
+
const metadata = guaranteeType(
|
|
341
|
+
newRow.metadata,
|
|
342
|
+
Map<string, DataCubeDimensionalMetadata>,
|
|
343
|
+
);
|
|
344
|
+
const groupByNodes = [...metadata.values()].flatMap((m) => m.groupByNodes);
|
|
345
|
+
const dimensions = dimensionalNodes.map((d) => d.dimension);
|
|
346
|
+
const latest = _latestGroupByFilters(groupByNodes);
|
|
347
|
+
|
|
348
|
+
for (let i = existing.length - 1; i >= 0; i--) {
|
|
349
|
+
const existingRow = guaranteeNonNullable(existing[i]);
|
|
350
|
+
if (
|
|
351
|
+
_isParentRow(existingRow, groupByNodes.length, newRow, latest, dimensions)
|
|
352
|
+
) {
|
|
353
|
+
return i + 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return existing.length;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function _isParentRow(
|
|
361
|
+
existing: DataCubeGridClientRowData,
|
|
362
|
+
groupByCount: number,
|
|
363
|
+
newRow: DataCubeGridClientRowData,
|
|
364
|
+
filters: DataCubeDimensionalGroupByNode[],
|
|
365
|
+
dimensions: string[],
|
|
366
|
+
): boolean {
|
|
367
|
+
if (groupByCount === 0) {
|
|
368
|
+
let allCount = 0;
|
|
369
|
+
const match = dimensions.every((d) => {
|
|
370
|
+
const e = existing[d],
|
|
371
|
+
n = newRow[d];
|
|
372
|
+
if (e === null || n === null) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (String(e) === DIMENSIONAL_L0_COLUMN) {
|
|
376
|
+
allCount++;
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
return String(e) === String(n);
|
|
380
|
+
});
|
|
381
|
+
return match && allCount >= 1 && allCount < dimensions.length;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return dimensions.every((d) => {
|
|
385
|
+
const filter = filters.find((f) => f.dimension === d);
|
|
386
|
+
const e = existing[d],
|
|
387
|
+
n = newRow[d];
|
|
388
|
+
if (e === null || n === null) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
if (filter) {
|
|
392
|
+
return (
|
|
393
|
+
String(e) === String(filter.filter) ||
|
|
394
|
+
String(e) === String(n) ||
|
|
395
|
+
String(e) === DIMENSIONAL_L0_COLUMN
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return String(e) === String(n);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function _latestGroupByFilters(
|
|
403
|
+
nodes: DataCubeDimensionalGroupByNode[],
|
|
404
|
+
): DataCubeDimensionalGroupByNode[] {
|
|
405
|
+
const map = new Map<string, DataCubeDimensionalGroupByNode>();
|
|
406
|
+
for (const node of nodes) {
|
|
407
|
+
map.set(node.dimension, node);
|
|
408
|
+
}
|
|
409
|
+
return [...map.values()];
|
|
410
|
+
}
|
|
411
|
+
|
|
262
412
|
async function getCastColumns(
|
|
263
413
|
currentSnapshot: DataCubeSnapshot,
|
|
264
414
|
view: DataCubeViewState,
|
|
@@ -302,6 +452,103 @@ async function getCastColumns(
|
|
|
302
452
|
});
|
|
303
453
|
}
|
|
304
454
|
|
|
455
|
+
export async function fetchDimensionalQueryRows(
|
|
456
|
+
snapshot: DataCubeSnapshot,
|
|
457
|
+
view: DataCubeViewState,
|
|
458
|
+
) {
|
|
459
|
+
const task = view.taskService.newTask('Fetching data...');
|
|
460
|
+
|
|
461
|
+
let rowData: DataCubeGridClientRowData[] = [];
|
|
462
|
+
|
|
463
|
+
const configuration = DataCubeConfiguration.serialization.fromJson(
|
|
464
|
+
snapshot.data.configuration,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// upgrade here by not shooting extra sqls
|
|
468
|
+
const paths = generateDimensionalPaths(
|
|
469
|
+
guaranteeNonNullable(snapshot.data.tree),
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
for (const path of paths) {
|
|
473
|
+
const executableQuery = buildDimensionalGridDataFetchExecutableQuery(
|
|
474
|
+
snapshot,
|
|
475
|
+
view.source,
|
|
476
|
+
view.engine,
|
|
477
|
+
path,
|
|
478
|
+
);
|
|
479
|
+
let result: DataCubeExecutionResult;
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
result = await view.engine.executeQuery(
|
|
483
|
+
_lambda([], [executableQuery]),
|
|
484
|
+
view.source,
|
|
485
|
+
{
|
|
486
|
+
debug: view.settingService.getBooleanValue(
|
|
487
|
+
DataCubeSettingKey.DEBUGGER__ENABLE_DEBUG_MODE,
|
|
488
|
+
),
|
|
489
|
+
clientVersion: view.settingService.getBooleanValue(
|
|
490
|
+
DataCubeSettingKey.DEBUGGER__USE_DEV_CLIENT_PROTOCOL_VERSION,
|
|
491
|
+
)
|
|
492
|
+
? PureClientVersion.VX_X_X
|
|
493
|
+
: undefined,
|
|
494
|
+
},
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
rowData = buildDimensionalRowData(
|
|
498
|
+
result.result.result,
|
|
499
|
+
snapshot,
|
|
500
|
+
path,
|
|
501
|
+
rowData,
|
|
502
|
+
configuration.dimensions.dimensions,
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
view.taskService.endTask(task);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
assertErrorThrown(error);
|
|
508
|
+
if (error instanceof DataCubeExecutionError) {
|
|
509
|
+
view.alertService.alertExecutionError(error, {
|
|
510
|
+
message: `Data Fetch Failure: Can't execute query.`,
|
|
511
|
+
text: `Error: ${error.message}`,
|
|
512
|
+
});
|
|
513
|
+
} else {
|
|
514
|
+
view.alertService.alertError(error, {
|
|
515
|
+
message: `Data Fetch Failure: ${error.message}`,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
view.taskService.endTask(task);
|
|
519
|
+
return {
|
|
520
|
+
rowData: [],
|
|
521
|
+
columnDefs: [],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (
|
|
526
|
+
view.settingService.getBooleanValue(
|
|
527
|
+
DataCubeSettingKey.DEBUGGER__ENABLE_DEBUG_MODE,
|
|
528
|
+
)
|
|
529
|
+
) {
|
|
530
|
+
view.engine.debugProcess(
|
|
531
|
+
`Execution`,
|
|
532
|
+
['Query', result.executedQuery],
|
|
533
|
+
[
|
|
534
|
+
'Stats',
|
|
535
|
+
`${rowData.length} rows, ${result.result.result.columns.length} columns`,
|
|
536
|
+
],
|
|
537
|
+
[
|
|
538
|
+
'SQL',
|
|
539
|
+
result.executedSQL ?? `-- Error: failed to extract executed SQL`,
|
|
540
|
+
],
|
|
541
|
+
['SQL Execution Time', result.executionTime],
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
rowData: rowData,
|
|
548
|
+
columnDefs: generateColumnDefsForDimensions(snapshot, configuration),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
305
552
|
export type DataCubeGridClientDataFetchRequest = {
|
|
306
553
|
startRow: number | undefined;
|
|
307
554
|
endRow: number | undefined;
|
|
@@ -69,10 +69,12 @@ import {
|
|
|
69
69
|
isNumber,
|
|
70
70
|
isValidUrl,
|
|
71
71
|
assertTrue,
|
|
72
|
+
guaranteeNonNullable,
|
|
72
73
|
} from '@finos/legend-shared';
|
|
73
|
-
import
|
|
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:
|
|
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 {
|
|
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 =
|
|
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) {
|