@finos/legend-cube-builder 0.0.2 → 0.0.3
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/README.md +60 -1
- package/docs/adding-an-operation.md +92 -0
- package/docs/hosting.md +79 -0
- package/docs/testing.md +83 -0
- package/lib/__lib__/LegendCubeHelpText.d.ts.map +1 -1
- package/lib/__lib__/LegendCubeHelpText.js +11 -3
- package/lib/__lib__/LegendCubeHelpText.js.map +1 -1
- package/lib/__lib__/LegendCubeLabels.d.ts +19 -0
- package/lib/__lib__/LegendCubeLabels.d.ts.map +1 -1
- package/lib/__lib__/LegendCubeLabels.js +19 -0
- package/lib/__lib__/LegendCubeLabels.js.map +1 -1
- package/lib/__test-utils__/CubeEngineTestSupport.d.ts +6 -0
- package/lib/__test-utils__/CubeEngineTestSupport.d.ts.map +1 -1
- package/lib/__test-utils__/CubeEngineTestSupport.js +30 -0
- package/lib/__test-utils__/CubeEngineTestSupport.js.map +1 -1
- package/lib/__test-utils__/CubeOperationsTestUtils.d.ts +42 -0
- package/lib/__test-utils__/CubeOperationsTestUtils.d.ts.map +1 -0
- package/lib/__test-utils__/CubeOperationsTestUtils.js +92 -0
- package/lib/__test-utils__/CubeOperationsTestUtils.js.map +1 -0
- package/lib/__test-utils__/FakeCubeEngine.d.ts.map +1 -1
- package/lib/__test-utils__/FakeCubeEngine.js +7 -1
- package/lib/__test-utils__/FakeCubeEngine.js.map +1 -1
- package/lib/components/CubeNodeIcon.d.ts.map +1 -1
- package/lib/components/CubeNodeIcon.js +12 -2
- package/lib/components/CubeNodeIcon.js.map +1 -1
- package/lib/components/canvas/CubeCanvas.d.ts.map +1 -1
- package/lib/components/canvas/CubeCanvas.js +5 -3
- package/lib/components/canvas/CubeCanvas.js.map +1 -1
- package/lib/components/canvas/CubeCanvasElements.d.ts +2 -0
- package/lib/components/canvas/CubeCanvasElements.d.ts.map +1 -1
- package/lib/components/canvas/CubeCanvasElements.js +2 -1
- package/lib/components/canvas/CubeCanvasElements.js.map +1 -1
- package/lib/components/canvas/CubeCanvasNode.d.ts.map +1 -1
- package/lib/components/canvas/CubeCanvasNode.js +5 -2
- package/lib/components/canvas/CubeCanvasNode.js.map +1 -1
- package/lib/components/editors/CubeColumnPicker.d.ts +9 -2
- package/lib/components/editors/CubeColumnPicker.d.ts.map +1 -1
- package/lib/components/editors/CubeColumnPicker.js +9 -4
- package/lib/components/editors/CubeColumnPicker.js.map +1 -1
- package/lib/components/editors/CubeDistinctEditor.d.ts +23 -0
- package/lib/components/editors/CubeDistinctEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeDistinctEditor.js +24 -0
- package/lib/components/editors/CubeDistinctEditor.js.map +1 -0
- package/lib/components/editors/CubeIntegerField.d.ts +29 -0
- package/lib/components/editors/CubeIntegerField.d.ts.map +1 -0
- package/lib/components/editors/CubeIntegerField.js +30 -0
- package/lib/components/editors/CubeIntegerField.js.map +1 -0
- package/lib/components/editors/CubeJoinEditor.d.ts.map +1 -1
- package/lib/components/editors/CubeJoinEditor.js +11 -3
- package/lib/components/editors/CubeJoinEditor.js.map +1 -1
- package/lib/components/editors/CubeNodeEditorPanel.d.ts.map +1 -1
- package/lib/components/editors/CubeNodeEditorPanel.js +3 -2
- package/lib/components/editors/CubeNodeEditorPanel.js.map +1 -1
- package/lib/components/editors/CubeNodeEditorRegistry.d.ts.map +1 -1
- package/lib/components/editors/CubeNodeEditorRegistry.js +14 -1
- package/lib/components/editors/CubeNodeEditorRegistry.js.map +1 -1
- package/lib/components/editors/CubeRenameEditor.d.ts +26 -0
- package/lib/components/editors/CubeRenameEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeRenameEditor.js +58 -0
- package/lib/components/editors/CubeRenameEditor.js.map +1 -0
- package/lib/components/editors/CubeRestrictEditor.d.ts +26 -0
- package/lib/components/editors/CubeRestrictEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeRestrictEditor.js +23 -0
- package/lib/components/editors/CubeRestrictEditor.js.map +1 -0
- package/lib/components/editors/CubeRowCountEditor.d.ts +25 -0
- package/lib/components/editors/CubeRowCountEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeRowCountEditor.js +38 -0
- package/lib/components/editors/CubeRowCountEditor.js.map +1 -0
- package/lib/components/editors/CubeSliceEditor.d.ts +25 -0
- package/lib/components/editors/CubeSliceEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeSliceEditor.js +40 -0
- package/lib/components/editors/CubeSliceEditor.js.map +1 -0
- package/lib/components/editors/CubeSortEditor.d.ts +27 -0
- package/lib/components/editors/CubeSortEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeSortEditor.js +72 -0
- package/lib/components/editors/CubeSortEditor.js.map +1 -0
- package/lib/components/editors/CubeSourceEditor.d.ts.map +1 -1
- package/lib/components/editors/CubeSourceEditor.js +1 -2
- package/lib/components/editors/CubeSourceEditor.js.map +1 -1
- package/lib/components/editors/CubeValueEditor.d.ts.map +1 -1
- package/lib/components/editors/CubeValueEditor.js +2 -1
- package/lib/components/editors/CubeValueEditor.js.map +1 -1
- package/lib/components/grid/CubeGridRegion.d.ts.map +1 -1
- package/lib/components/grid/CubeGridRegion.js +1 -1
- package/lib/components/grid/CubeGridRegion.js.map +1 -1
- package/lib/components/grid/CubeResultGrid.d.ts +14 -1
- package/lib/components/grid/CubeResultGrid.d.ts.map +1 -1
- package/lib/components/grid/CubeResultGrid.js +41 -5
- package/lib/components/grid/CubeResultGrid.js.map +1 -1
- package/lib/graph-manager/CubeEngine.d.ts +13 -0
- package/lib/graph-manager/CubeEngine.d.ts.map +1 -1
- package/lib/graph-manager/CubeEngine.js.map +1 -1
- package/lib/graph-manager/CubeModelOutlineHelper.d.ts +8 -0
- package/lib/graph-manager/CubeModelOutlineHelper.d.ts.map +1 -1
- package/lib/graph-manager/CubeModelOutlineHelper.js +21 -0
- package/lib/graph-manager/CubeModelOutlineHelper.js.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/V1_CubeModelOutlineBuilder.d.ts.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/V1_CubeModelOutlineBuilder.js +56 -3
- package/lib/graph-manager/protocol/pure/v1/V1_CubeModelOutlineBuilder.js.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/__test-utils__/V1_CubeEngineTestUtils.d.ts.map +1 -1
- package/lib/graph-manager/protocol/pure/v1/__test-utils__/V1_CubeEngineTestUtils.js +3 -3
- package/lib/graph-manager/protocol/pure/v1/__test-utils__/V1_CubeEngineTestUtils.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/stores/CubeEditorState.d.ts +34 -4
- package/lib/stores/CubeEditorState.d.ts.map +1 -1
- package/lib/stores/CubeEditorState.js +87 -6
- package/lib/stores/CubeEditorState.js.map +1 -1
- package/lib/stores/CubeExecutionState.d.ts.map +1 -1
- package/lib/stores/CubeExecutionState.js +19 -3
- package/lib/stores/CubeExecutionState.js.map +1 -1
- package/lib/stores/CubeGridQuickActions.d.ts +40 -0
- package/lib/stores/CubeGridQuickActions.d.ts.map +1 -0
- package/lib/stores/CubeGridQuickActions.js +86 -0
- package/lib/stores/CubeGridQuickActions.js.map +1 -0
- package/lib/stores/CubeNodeEditorState.d.ts +19 -1
- package/lib/stores/CubeNodeEditorState.d.ts.map +1 -1
- package/lib/stores/CubeNodeEditorState.js +55 -0
- package/lib/stores/CubeNodeEditorState.js.map +1 -1
- package/lib/stores/CubeShowPureState.d.ts +4 -1
- package/lib/stores/CubeShowPureState.d.ts.map +1 -1
- package/lib/stores/CubeShowPureState.js +22 -5
- package/lib/stores/CubeShowPureState.js.map +1 -1
- package/lib/stores/editors/CubeIntegerText.d.ts +24 -0
- package/lib/stores/editors/CubeIntegerText.d.ts.map +1 -0
- package/lib/stores/editors/CubeIntegerText.js +33 -0
- package/lib/stores/editors/CubeIntegerText.js.map +1 -0
- package/lib/stores/editors/CubeJoinDraft.d.ts +13 -5
- package/lib/stores/editors/CubeJoinDraft.d.ts.map +1 -1
- package/lib/stores/editors/CubeJoinDraft.js +21 -12
- package/lib/stores/editors/CubeJoinDraft.js.map +1 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.d.ts +6 -0
- package/lib/stores/editors/CubeNodeDraftRegistry.d.ts.map +1 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.js +23 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.js.map +1 -1
- package/lib/stores/editors/CubeRenameDraft.d.ts +44 -0
- package/lib/stores/editors/CubeRenameDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeRenameDraft.js +79 -0
- package/lib/stores/editors/CubeRenameDraft.js.map +1 -0
- package/lib/stores/editors/CubeRestrictDraft.d.ts +37 -0
- package/lib/stores/editors/CubeRestrictDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeRestrictDraft.js +73 -0
- package/lib/stores/editors/CubeRestrictDraft.js.map +1 -0
- package/lib/stores/editors/CubeRowCountDraft.d.ts +42 -0
- package/lib/stores/editors/CubeRowCountDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeRowCountDraft.js +55 -0
- package/lib/stores/editors/CubeRowCountDraft.js.map +1 -0
- package/lib/stores/editors/CubeSliceDraft.d.ts +42 -0
- package/lib/stores/editors/CubeSliceDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeSliceDraft.js +72 -0
- package/lib/stores/editors/CubeSliceDraft.js.map +1 -0
- package/lib/stores/editors/CubeSortDraft.d.ts +49 -0
- package/lib/stores/editors/CubeSortDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeSortDraft.js +99 -0
- package/lib/stores/editors/CubeSortDraft.js.map +1 -0
- package/package.json +8 -8
- package/src/__lib__/LegendCubeHelpText.ts +22 -2
- package/src/__lib__/LegendCubeLabels.ts +30 -0
- package/src/__test-utils__/CubeEngineTestSupport.ts +35 -0
- package/src/__test-utils__/CubeOperationsTestUtils.ts +177 -0
- package/src/__test-utils__/FakeCubeEngine.ts +7 -1
- package/src/components/CubeNodeIcon.tsx +18 -1
- package/src/components/canvas/CubeCanvas.tsx +5 -3
- package/src/components/canvas/CubeCanvasElements.ts +4 -1
- package/src/components/canvas/CubeCanvasNode.tsx +9 -1
- package/src/components/editors/CubeColumnPicker.tsx +36 -12
- package/src/components/editors/CubeDistinctEditor.tsx +28 -0
- package/src/components/editors/CubeIntegerField.tsx +51 -0
- package/src/components/editors/CubeJoinEditor.tsx +26 -3
- package/src/components/editors/CubeNodeEditorPanel.tsx +12 -1
- package/src/components/editors/CubeNodeEditorRegistry.ts +20 -0
- package/src/components/editors/CubeRenameEditor.tsx +143 -0
- package/src/components/editors/CubeRestrictEditor.tsx +103 -0
- package/src/components/editors/CubeRowCountEditor.tsx +53 -0
- package/src/components/editors/CubeSliceEditor.tsx +69 -0
- package/src/components/editors/CubeSortEditor.tsx +206 -0
- package/src/components/editors/CubeSourceEditor.tsx +0 -10
- package/src/components/editors/CubeValueEditor.tsx +2 -1
- package/src/components/grid/CubeGridRegion.tsx +1 -0
- package/src/components/grid/CubeResultGrid.tsx +59 -5
- package/src/graph-manager/CubeEngine.ts +14 -0
- package/src/graph-manager/CubeModelOutlineHelper.ts +26 -0
- package/src/graph-manager/protocol/pure/v1/V1_CubeModelOutlineBuilder.ts +89 -3
- package/src/graph-manager/protocol/pure/v1/__test-utils__/V1_CubeEngineTestUtils.ts +6 -3
- package/src/stores/CubeEditorState.ts +110 -5
- package/src/stores/CubeExecutionState.ts +36 -3
- package/src/stores/CubeGridQuickActions.ts +129 -0
- package/src/stores/CubeNodeEditorState.ts +70 -1
- package/src/stores/CubeShowPureState.ts +27 -5
- package/src/stores/editors/CubeIntegerText.ts +34 -0
- package/src/stores/editors/CubeJoinDraft.ts +43 -16
- package/src/stores/editors/CubeNodeDraftRegistry.ts +35 -1
- package/src/stores/editors/CubeRenameDraft.ts +110 -0
- package/src/stores/editors/CubeRestrictDraft.ts +83 -0
- package/src/stores/editors/CubeRowCountDraft.ts +69 -0
- package/src/stores/editors/CubeSliceDraft.ts +87 -0
- package/src/stores/editors/CubeSortDraft.ts +135 -0
- package/tsconfig.json +15 -0
|
@@ -19,16 +19,59 @@ import {
|
|
|
19
19
|
DataGrid,
|
|
20
20
|
type DataGridCellRendererParams,
|
|
21
21
|
type DataGridColumnDefinition,
|
|
22
|
+
type DataGridDefaultMenuItem,
|
|
23
|
+
type DataGridGetContextMenuItemsParams,
|
|
24
|
+
type DataGridMenuItemDef,
|
|
22
25
|
} from '@finos/legend-lego/data-grid';
|
|
23
|
-
import { memo, useMemo } from 'react';
|
|
26
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
24
27
|
import { NULL_CELL_TEXT } from '../../__lib__/LegendCubeLabels.js';
|
|
25
28
|
import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
|
|
26
29
|
import type { CubeResultValue } from '../../graph-manager/CubeEngine.js';
|
|
30
|
+
import type { CubeEditorState } from '../../stores/CubeEditorState.js';
|
|
27
31
|
import type { CubeExecutionResult } from '../../stores/CubeExecutionState.js';
|
|
32
|
+
import { getCubeGridQuickActions } from '../../stores/CubeGridQuickActions.js';
|
|
28
33
|
import { compareCellValues, isNumericFamily } from './CubeGridValues.js';
|
|
29
34
|
|
|
30
35
|
type CubeRow = readonly CubeResultValue[];
|
|
31
36
|
|
|
37
|
+
/** A column's id in the grid: its position, as `c<position>` */
|
|
38
|
+
const COLUMN_ID_PREFIX = 'c';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The grid's context menu (spec §12.4): on a cell, its quick actions, then
|
|
42
|
+
* ag-grid's own items, which M7 curates; elsewhere ag-grid's only. Read when
|
|
43
|
+
* the menu opens, so it reflects the cube and the run as they are then.
|
|
44
|
+
*/
|
|
45
|
+
export const getCubeGridContextMenuItems = (
|
|
46
|
+
editorState: CubeEditorState,
|
|
47
|
+
params: DataGridGetContextMenuItemsParams<CubeRow>,
|
|
48
|
+
): (DataGridDefaultMenuItem | DataGridMenuItemDef<CubeRow>)[] => {
|
|
49
|
+
const defaultItems = params.defaultItems ?? [];
|
|
50
|
+
const colId = params.column?.getColId();
|
|
51
|
+
if (!colId?.startsWith(COLUMN_ID_PREFIX)) {
|
|
52
|
+
return defaultItems;
|
|
53
|
+
}
|
|
54
|
+
const position = Number(colId.slice(COLUMN_ID_PREFIX.length));
|
|
55
|
+
const cell = params.node?.data?.[position] ?? null;
|
|
56
|
+
const actions = getCubeGridQuickActions(editorState, position, cell);
|
|
57
|
+
if (!actions.length) {
|
|
58
|
+
return defaultItems;
|
|
59
|
+
}
|
|
60
|
+
return [
|
|
61
|
+
...actions.map((quickAction): DataGridMenuItemDef<CubeRow> => {
|
|
62
|
+
const tooltip = quickAction.disabledReason ?? quickAction.hint;
|
|
63
|
+
return {
|
|
64
|
+
name: quickAction.label,
|
|
65
|
+
disabled: quickAction.disabledReason !== undefined,
|
|
66
|
+
...(tooltip === undefined ? {} : { tooltip }),
|
|
67
|
+
action: () => quickAction.apply(),
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
'separator',
|
|
71
|
+
...defaultItems,
|
|
72
|
+
];
|
|
73
|
+
};
|
|
74
|
+
|
|
32
75
|
const CubeCell: React.FC<
|
|
33
76
|
DataGridCellRendererParams<CubeRow, CubeResultValue>
|
|
34
77
|
> = (props) =>
|
|
@@ -41,17 +84,27 @@ const CubeCell: React.FC<
|
|
|
41
84
|
/**
|
|
42
85
|
* The rows of a run (PLAN §9): columns from Cube's own schema of the capture
|
|
43
86
|
* node, by position, so any column name works. Numbers are right-aligned and
|
|
44
|
-
* shown and sorted exactly as the engine wrote them.
|
|
87
|
+
* shown and sorted exactly as the engine wrote them. A cell's context menu
|
|
88
|
+
* offers the quick actions (`getCubeGridContextMenuItems`).
|
|
45
89
|
*/
|
|
46
90
|
export const CubeResultGrid = memo(
|
|
47
|
-
(props: {
|
|
48
|
-
|
|
91
|
+
(props: {
|
|
92
|
+
editorState: CubeEditorState;
|
|
93
|
+
result: CubeExecutionResult;
|
|
94
|
+
darkMode: boolean;
|
|
95
|
+
}) => {
|
|
96
|
+
const { editorState, result, darkMode } = props;
|
|
97
|
+
const getContextMenuItems = useCallback(
|
|
98
|
+
(params: DataGridGetContextMenuItemsParams<CubeRow>) =>
|
|
99
|
+
getCubeGridContextMenuItems(editorState, params),
|
|
100
|
+
[editorState],
|
|
101
|
+
);
|
|
49
102
|
const columnDefs = useMemo(
|
|
50
103
|
(): DataGridColumnDefinition<CubeRow, CubeResultValue>[] =>
|
|
51
104
|
result.schema.columns.map((column, position) => {
|
|
52
105
|
const numeric = isNumericFamily(column.type.family);
|
|
53
106
|
return {
|
|
54
|
-
colId:
|
|
107
|
+
colId: `${COLUMN_ID_PREFIX}${position}`,
|
|
55
108
|
headerName: column.name,
|
|
56
109
|
headerTooltip: `${column.type.displayName}${column.nullable ? '?' : ''} (${column.type.fullName})`,
|
|
57
110
|
valueGetter: (params) => params.data?.[position] ?? null,
|
|
@@ -76,6 +129,7 @@ export const CubeResultGrid = memo(
|
|
|
76
129
|
columnDefs={columnDefs}
|
|
77
130
|
suppressFieldDotNotation={true}
|
|
78
131
|
tooltipShowDelay={500}
|
|
132
|
+
getContextMenuItems={getContextMenuItems}
|
|
79
133
|
/>
|
|
80
134
|
</div>
|
|
81
135
|
);
|
|
@@ -64,10 +64,24 @@ export interface CubeOutlineDatabase {
|
|
|
64
64
|
readonly schemas: readonly CubeOutlineSchema[];
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/** A relational connection of a runtime, for the operations some databases take another way (PLAN §11.4) */
|
|
68
|
+
export interface CubeOutlineConnection {
|
|
69
|
+
/** The store it is keyed by */
|
|
70
|
+
readonly storePath: string;
|
|
71
|
+
/** The engine's name for its database type, e.g. `H2` or `SqlServer` */
|
|
72
|
+
readonly databaseType: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
export interface CubeOutlineRuntime {
|
|
68
76
|
readonly path: string;
|
|
69
77
|
/** The stores its connections are keyed by, exactly; includes are not followed (PLAN §6.2.5) */
|
|
70
78
|
readonly storePaths: readonly string[];
|
|
79
|
+
/**
|
|
80
|
+
* Its relational connections that name a database type, a list rather
|
|
81
|
+
* than a record keyed by a store path from the model; optional, as an
|
|
82
|
+
* outline from before M2 has none
|
|
83
|
+
*/
|
|
84
|
+
readonly connections?: readonly CubeOutlineConnection[];
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
/** What a model offers the source picker: its databases and runtimes, as plain data */
|
|
@@ -30,3 +30,29 @@ export const getRuntimesForDatabase = (
|
|
|
30
30
|
outline.runtimes.filter((runtime) =>
|
|
31
31
|
runtime.storePaths.includes(databasePath),
|
|
32
32
|
);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The database type a query on these databases runs on with the runtime, for
|
|
36
|
+
* the operations some databases take another way (PLAN §11.4): the type of
|
|
37
|
+
* the runtime's connections to them. None when the runtime isn't in the
|
|
38
|
+
* outline, a database has no typed connection, or the databases' types
|
|
39
|
+
* differ: the native forms are written then.
|
|
40
|
+
*/
|
|
41
|
+
export const getDatabaseType = (
|
|
42
|
+
outline: CubeModelOutline,
|
|
43
|
+
runtimePath: string,
|
|
44
|
+
databasePaths: readonly string[],
|
|
45
|
+
): string | undefined => {
|
|
46
|
+
const runtime = outline.runtimes.find(({ path }) => path === runtimePath);
|
|
47
|
+
const types = new Set<string>();
|
|
48
|
+
for (const databasePath of new Set(databasePaths)) {
|
|
49
|
+
const databaseTypes = (runtime?.connections ?? [])
|
|
50
|
+
.filter(({ storePath }) => storePath === databasePath)
|
|
51
|
+
.map(({ databaseType }) => databaseType);
|
|
52
|
+
if (!databaseTypes.length) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
databaseTypes.forEach((databaseType) => types.add(databaseType));
|
|
56
|
+
}
|
|
57
|
+
return types.size === 1 ? [...types][0] : undefined;
|
|
58
|
+
};
|
|
@@ -19,6 +19,7 @@ import type { PlainObject } from '@finos/legend-shared';
|
|
|
19
19
|
import {
|
|
20
20
|
CubeTableFlag,
|
|
21
21
|
type CubeModelOutline,
|
|
22
|
+
type CubeOutlineConnection,
|
|
22
23
|
type CubeOutlineDatabase,
|
|
23
24
|
type CubeOutlineRuntime,
|
|
24
25
|
type CubeOutlineSchema,
|
|
@@ -32,6 +33,9 @@ import {
|
|
|
32
33
|
|
|
33
34
|
const DATABASE_TYPE = 'relational';
|
|
34
35
|
const RUNTIME_TYPE = 'runtime';
|
|
36
|
+
const CONNECTION_TYPE = 'connection';
|
|
37
|
+
const CONNECTION_POINTER_TYPE = 'connectionPointer';
|
|
38
|
+
const RELATIONAL_CONNECTION_TYPE = 'RelationalDatabaseConnection';
|
|
35
39
|
/** The only runtime value whose store keys Cube can read; others are hidden */
|
|
36
40
|
const ENGINE_RUNTIME_TYPE = 'engineRuntime';
|
|
37
41
|
|
|
@@ -117,8 +121,34 @@ const buildDatabase = (element: PlainObject): CubeOutlineDatabase => ({
|
|
|
117
121
|
schemas: asList(element.schemas).map(buildSchema),
|
|
118
122
|
});
|
|
119
123
|
|
|
124
|
+
/** The database type of a relational connection value: its `type`, else its `databaseType` */
|
|
125
|
+
const getRelationalDatabaseType = (value: PlainObject): string | undefined =>
|
|
126
|
+
value._type === RELATIONAL_CONNECTION_TYPE
|
|
127
|
+
? (asString(value.type) ?? asString(value.databaseType))
|
|
128
|
+
: undefined;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The database type of a runtime's connection, embedded or a pointer to a
|
|
132
|
+
* connection element (`connectionTypes`, by path); none for one that isn't
|
|
133
|
+
* relational
|
|
134
|
+
*/
|
|
135
|
+
const getConnectionDatabaseType = (
|
|
136
|
+
json: unknown,
|
|
137
|
+
connectionTypes: ReadonlyMap<string, string>,
|
|
138
|
+
): string | undefined => {
|
|
139
|
+
const connection = asObject(json);
|
|
140
|
+
if (connection._type === CONNECTION_POINTER_TYPE) {
|
|
141
|
+
const path = asString(connection.connection);
|
|
142
|
+
return path === undefined ? undefined : connectionTypes.get(path);
|
|
143
|
+
}
|
|
144
|
+
return getRelationalDatabaseType(connection);
|
|
145
|
+
};
|
|
146
|
+
|
|
120
147
|
/** The stores a runtime's connections are keyed by, in both syntaxes; none when it can't be read */
|
|
121
|
-
const buildRuntime = (
|
|
148
|
+
const buildRuntime = (
|
|
149
|
+
element: PlainObject,
|
|
150
|
+
connectionTypes: ReadonlyMap<string, string>,
|
|
151
|
+
): CubeOutlineRuntime | undefined => {
|
|
122
152
|
const value = asObject(element.runtimeValue);
|
|
123
153
|
if (value._type !== ENGINE_RUNTIME_TYPE) {
|
|
124
154
|
return undefined;
|
|
@@ -135,7 +165,51 @@ const buildRuntime = (element: PlainObject): CubeOutlineRuntime | undefined => {
|
|
|
135
165
|
),
|
|
136
166
|
),
|
|
137
167
|
].filter((path): path is string => path !== undefined);
|
|
138
|
-
|
|
168
|
+
const connections: CubeOutlineConnection[] = [];
|
|
169
|
+
const addConnection = (
|
|
170
|
+
storePath: string | undefined,
|
|
171
|
+
databaseType: string | undefined,
|
|
172
|
+
): void => {
|
|
173
|
+
if (
|
|
174
|
+
storePath !== undefined &&
|
|
175
|
+
databaseType !== undefined &&
|
|
176
|
+
!connections.some(
|
|
177
|
+
(connection) =>
|
|
178
|
+
connection.storePath === storePath &&
|
|
179
|
+
connection.databaseType === databaseType,
|
|
180
|
+
)
|
|
181
|
+
) {
|
|
182
|
+
connections.push({ storePath, databaseType });
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
asList(value.connections).forEach((json) => {
|
|
186
|
+
const storeConnections = asObject(json);
|
|
187
|
+
const storePath = asString(asObject(storeConnections.store).path);
|
|
188
|
+
asList(storeConnections.storeConnections).forEach((identified) =>
|
|
189
|
+
addConnection(
|
|
190
|
+
storePath,
|
|
191
|
+
getConnectionDatabaseType(
|
|
192
|
+
asObject(identified).connection,
|
|
193
|
+
connectionTypes,
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
asList(value.connectionStores).forEach((json) => {
|
|
199
|
+
const connectionStore = asObject(json);
|
|
200
|
+
const databaseType = getConnectionDatabaseType(
|
|
201
|
+
connectionStore.connectionPointer,
|
|
202
|
+
connectionTypes,
|
|
203
|
+
);
|
|
204
|
+
asList(connectionStore.storePointers).forEach((pointer) =>
|
|
205
|
+
addConnection(asString(asObject(pointer).path), databaseType),
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
path: elementPath(element),
|
|
210
|
+
storePaths: [...new Set(storePaths)],
|
|
211
|
+
connections,
|
|
212
|
+
};
|
|
139
213
|
};
|
|
140
214
|
|
|
141
215
|
/** The outline of a parsed model: its Database elements and its readable runtimes, in model order */
|
|
@@ -143,13 +217,25 @@ export const V1_buildCubeModelOutline = (
|
|
|
143
217
|
modelData: PlainObject,
|
|
144
218
|
): CubeModelOutline => {
|
|
145
219
|
const elements = asList(modelData.elements).map(asObject);
|
|
220
|
+
// the database type of each relational connection element, by path
|
|
221
|
+
const connectionTypes = new Map<string, string>();
|
|
222
|
+
elements
|
|
223
|
+
.filter((element) => element._type === CONNECTION_TYPE)
|
|
224
|
+
.forEach((element) => {
|
|
225
|
+
const databaseType = getRelationalDatabaseType(
|
|
226
|
+
asObject(element.connectionValue),
|
|
227
|
+
);
|
|
228
|
+
if (databaseType !== undefined) {
|
|
229
|
+
connectionTypes.set(elementPath(element), databaseType);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
146
232
|
return {
|
|
147
233
|
databases: elements
|
|
148
234
|
.filter((element) => element._type === DATABASE_TYPE)
|
|
149
235
|
.map(buildDatabase),
|
|
150
236
|
runtimes: elements
|
|
151
237
|
.filter((element) => element._type === RUNTIME_TYPE)
|
|
152
|
-
.map(buildRuntime)
|
|
238
|
+
.map((element) => buildRuntime(element, connectionTypes))
|
|
153
239
|
.filter(
|
|
154
240
|
(runtime): runtime is CubeOutlineRuntime => runtime !== undefined,
|
|
155
241
|
),
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { jest } from '@jest/globals';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ENGINE_TEST_SUPPORT__grammarToJSON_model,
|
|
20
|
+
ENGINE_TEST_SUPPORT_API_URL,
|
|
21
|
+
} from '@finos/legend-graph/test';
|
|
19
22
|
import {
|
|
20
23
|
NetworkClientError,
|
|
21
24
|
type PlainObject,
|
|
@@ -40,7 +43,7 @@ const clientError = (status: number, payload: unknown): NetworkClientError =>
|
|
|
40
43
|
{
|
|
41
44
|
status,
|
|
42
45
|
statusText: String(status),
|
|
43
|
-
url:
|
|
46
|
+
url: ENGINE_TEST_SUPPORT_API_URL,
|
|
44
47
|
} as Response,
|
|
45
48
|
payload as NetworkClientError['payload'],
|
|
46
49
|
);
|
|
@@ -74,7 +77,7 @@ export const V1_createEngineBackedCubeEngine = (): {
|
|
|
74
77
|
>;
|
|
75
78
|
} => {
|
|
76
79
|
const engine = new V1_LegendCubeEngine(
|
|
77
|
-
{ baseUrl:
|
|
80
|
+
{ baseUrl: ENGINE_TEST_SUPPORT_API_URL },
|
|
78
81
|
new TracerService(),
|
|
79
82
|
);
|
|
80
83
|
const { client } = engine;
|
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
createNodeRegistry,
|
|
20
20
|
CubeDocument,
|
|
21
21
|
diffSchemas,
|
|
22
|
+
findLostSortOrders,
|
|
23
|
+
MESSAGE_SORT_COLUMNS_CUT,
|
|
24
|
+
MESSAGE_SORT_COLUMNS_DROPPED,
|
|
25
|
+
MESSAGE_SORT_ORDER_LOST,
|
|
22
26
|
type ModelContext,
|
|
23
27
|
type NodeRegistry,
|
|
24
28
|
type Query,
|
|
@@ -53,6 +57,7 @@ import {
|
|
|
53
57
|
CubeEngineErrorKind,
|
|
54
58
|
type CubeModelOutline,
|
|
55
59
|
} from '../graph-manager/CubeEngine.js';
|
|
60
|
+
import { getDatabaseType } from '../graph-manager/CubeModelOutlineHelper.js';
|
|
56
61
|
import { CubeExecutionState } from './CubeExecutionState.js';
|
|
57
62
|
import type { CubeHost } from './CubeHost.js';
|
|
58
63
|
import { CubeNodeEditorState } from './CubeNodeEditorState.js';
|
|
@@ -138,6 +143,7 @@ export class CubeEditorState implements CommandRegistrar {
|
|
|
138
143
|
history: observable.ref,
|
|
139
144
|
hostIssues: observable.ref,
|
|
140
145
|
warnings: observable.ref,
|
|
146
|
+
derivedWarnings: computed,
|
|
141
147
|
isResolvingSources: computed,
|
|
142
148
|
rowLimit: observable,
|
|
143
149
|
isPaletteCollapsed: observable,
|
|
@@ -155,6 +161,7 @@ export class CubeEditorState implements CommandRegistrar {
|
|
|
155
161
|
select: action,
|
|
156
162
|
connect: action,
|
|
157
163
|
addNode: action,
|
|
164
|
+
addConfiguredNode: action,
|
|
158
165
|
dropNode: action,
|
|
159
166
|
removeNode: action,
|
|
160
167
|
swapInputs: action,
|
|
@@ -208,6 +215,52 @@ export class CubeEditorState implements CommandRegistrar {
|
|
|
208
215
|
return this.pendingSources.has(node);
|
|
209
216
|
}
|
|
210
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Warnings worked out from the query, by node id: a Sort whose order is
|
|
220
|
+
* lost before it is used (PLAN §11.4). Never stored and never errors, so
|
|
221
|
+
* Execute stays enabled; they go as soon as the query no longer loses it.
|
|
222
|
+
* A partial loss names each node that removes some of the Sort's columns,
|
|
223
|
+
* then the columns that came after a removed one. A loss waits until the
|
|
224
|
+
* Sort and every node it names have no errors, e.g. a Restrict just added,
|
|
225
|
+
* with no column yet: their own errors come first.
|
|
226
|
+
*/
|
|
227
|
+
get derivedWarnings(): ReadonlyMap<string, readonly string[]> {
|
|
228
|
+
const { validity, schemas } = this.analysis;
|
|
229
|
+
const isValid = (nodeId: string): boolean =>
|
|
230
|
+
validity.get(nodeId)?.length === 0;
|
|
231
|
+
const { query } = this.document;
|
|
232
|
+
return new Map(
|
|
233
|
+
Array.from(findLostSortOrders(query, undefined, schemas))
|
|
234
|
+
.filter(
|
|
235
|
+
([sortId, loss]) =>
|
|
236
|
+
isValid(sortId) &&
|
|
237
|
+
isValid(loss.nodeId) &&
|
|
238
|
+
(loss.removals ?? []).every(({ nodeId }) => isValid(nodeId)),
|
|
239
|
+
)
|
|
240
|
+
.map(([sortId, loss]) => [
|
|
241
|
+
sortId,
|
|
242
|
+
loss.removals
|
|
243
|
+
? [
|
|
244
|
+
...loss.removals.map(({ nodeId, columns }) =>
|
|
245
|
+
MESSAGE_SORT_COLUMNS_DROPPED(columns, nodeId),
|
|
246
|
+
),
|
|
247
|
+
...(loss.cutColumns?.length
|
|
248
|
+
? [MESSAGE_SORT_COLUMNS_CUT(loss.cutColumns)]
|
|
249
|
+
: []),
|
|
250
|
+
]
|
|
251
|
+
: [MESSAGE_SORT_ORDER_LOST(loss.nodeId)],
|
|
252
|
+
]),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** A node's warnings: the stored ones (by key), then the derived ones (by id) */
|
|
257
|
+
getNodeWarnings(node: QueryNode): readonly string[] {
|
|
258
|
+
return [
|
|
259
|
+
...(this.warnings.get(node.key) ?? []),
|
|
260
|
+
...(this.derivedWarnings.get(node.id) ?? []),
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
211
264
|
/**
|
|
212
265
|
* The node's errors, each once: its own and those of the query rules, then
|
|
213
266
|
* the first line of the engine's error on it
|
|
@@ -225,15 +278,53 @@ export class CubeEditorState implements CommandRegistrar {
|
|
|
225
278
|
/** The outline of the cube's model, once an editor has loaded it */
|
|
226
279
|
get modelOutline(): CubeModelOutline | undefined {
|
|
227
280
|
const model = this.document.context?.model;
|
|
228
|
-
return model === undefined ? undefined : this.
|
|
281
|
+
return model === undefined ? undefined : this.getModelOutline(model);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** The outline of a model, once loaded */
|
|
285
|
+
getModelOutline(model: ModelContext): CubeModelOutline | undefined {
|
|
286
|
+
return this.modelOutlines.get(model);
|
|
229
287
|
}
|
|
230
288
|
|
|
231
289
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
290
|
+
* The database type a run of the query up to the node needs, with the
|
|
291
|
+
* model's runtime (PLAN §11.4), from the model's outline once it is loaded
|
|
292
|
+
* (`loadModelOutline`): the type of the runtime's connections to the
|
|
293
|
+
* databases the node reads. None without an outline, which writes every
|
|
294
|
+
* operation the native way.
|
|
234
295
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
296
|
+
getRunDatabaseType(
|
|
297
|
+
query: Query,
|
|
298
|
+
nodeId: string,
|
|
299
|
+
model: ModelContext,
|
|
300
|
+
runtime: string,
|
|
301
|
+
): string | undefined {
|
|
302
|
+
const outline = this.getModelOutline(model);
|
|
303
|
+
if (!outline) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
const databases = new Set<string>();
|
|
307
|
+
const visit = (id: string): void => {
|
|
308
|
+
const node = query.getNode(id);
|
|
309
|
+
if (node instanceof RelationalTableSource) {
|
|
310
|
+
databases.add(node.database);
|
|
311
|
+
}
|
|
312
|
+
query
|
|
313
|
+
.getInputIds(id)
|
|
314
|
+
.forEach((inputId) => inputId !== undefined && visit(inputId));
|
|
315
|
+
};
|
|
316
|
+
visit(nodeId);
|
|
317
|
+
return getDatabaseType(outline, runtime, [...databases]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Loads the outline of a model, the cube's by default, once per model. It
|
|
322
|
+
* only adds warnings and database types, so a model that fails to load
|
|
323
|
+
* shows none and runs every operation the native way.
|
|
324
|
+
*/
|
|
325
|
+
*loadModelOutline(
|
|
326
|
+
model: ModelContext | undefined = this.document.context?.model,
|
|
327
|
+
): GeneratorFn<void> {
|
|
237
328
|
if (model === undefined || this.modelOutlines.has(model)) {
|
|
238
329
|
return;
|
|
239
330
|
}
|
|
@@ -585,6 +676,20 @@ export class CubeEditorState implements CommandRegistrar {
|
|
|
585
676
|
);
|
|
586
677
|
}
|
|
587
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Adds a node made elsewhere, with its settings, as the grid's quick
|
|
681
|
+
* actions do (spec §12.4): spliced in after `afterId`, so it becomes the
|
|
682
|
+
* node that runs when that one did, as one undo step. Does nothing in a
|
|
683
|
+
* read-only cube, or when the query can't take it.
|
|
684
|
+
*/
|
|
685
|
+
addConfiguredNode(node: QueryNode, afterId?: string): void {
|
|
686
|
+
const { query } = this.document;
|
|
687
|
+
if (this.readOnly || !query.canAdd(node, afterId)) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.applyQuery(query.add(node, afterId));
|
|
691
|
+
}
|
|
692
|
+
|
|
588
693
|
/** Whether dropping a node on another does anything: connect it, or else move it after it */
|
|
589
694
|
canDropNode(nodeId: string, targetId: string): boolean {
|
|
590
695
|
const { query } = this.document;
|
|
@@ -14,9 +14,21 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isSchemasError,
|
|
19
|
+
needsDatabaseType,
|
|
20
|
+
type Query,
|
|
21
|
+
type Schema,
|
|
22
|
+
} from '@finos/legend-cube';
|
|
18
23
|
import { assertErrorThrown, type GeneratorFn } from '@finos/legend-shared';
|
|
19
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
action,
|
|
26
|
+
computed,
|
|
27
|
+
flow,
|
|
28
|
+
flowResult,
|
|
29
|
+
makeObservable,
|
|
30
|
+
observable,
|
|
31
|
+
} from 'mobx';
|
|
20
32
|
import {
|
|
21
33
|
CubeEngineError,
|
|
22
34
|
CubeEngineErrorKind,
|
|
@@ -162,12 +174,33 @@ export class CubeExecutionState {
|
|
|
162
174
|
if (!model || !runtime || !schema) {
|
|
163
175
|
return;
|
|
164
176
|
}
|
|
165
|
-
|
|
177
|
+
// the run starts before any wait, so Stop works and a second F9 is ignored
|
|
166
178
|
const controller = new AbortController();
|
|
167
179
|
this.runController = controller;
|
|
168
180
|
this.error = undefined;
|
|
169
181
|
this.editorState.clearHostIssues();
|
|
170
182
|
try {
|
|
183
|
+
// a Drop, Slice, Limit or Distinct is written for the database it runs on
|
|
184
|
+
// (PLAN §11.4), which the model's outline gives: loaded once, waited
|
|
185
|
+
// for only then; the query, model and runtime are the ones captured
|
|
186
|
+
let databaseType: string | undefined;
|
|
187
|
+
if (needsDatabaseType(query, captureId)) {
|
|
188
|
+
yield flowResult(this.editorState.loadModelOutline(model));
|
|
189
|
+
if (this.runController !== controller || controller.signal.aborted) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
databaseType = this.editorState.getRunDatabaseType(
|
|
193
|
+
query,
|
|
194
|
+
captureId,
|
|
195
|
+
model,
|
|
196
|
+
runtime,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
const lambda = emitter.emitExecutionLambda({
|
|
200
|
+
rowLimit,
|
|
201
|
+
runtime,
|
|
202
|
+
databaseType,
|
|
203
|
+
});
|
|
171
204
|
const response = (yield host.engine.execute(model, lambda, {
|
|
172
205
|
abortController: controller,
|
|
173
206
|
})) as CubeResult;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
buildQuickFilterRule,
|
|
19
|
+
FilterOperator,
|
|
20
|
+
Filter,
|
|
21
|
+
isOperatorAvailable,
|
|
22
|
+
isSortableType,
|
|
23
|
+
Sort,
|
|
24
|
+
TypeFamily,
|
|
25
|
+
} from '@finos/legend-cube';
|
|
26
|
+
import {
|
|
27
|
+
CUBE_QUICK_ACTION_DISABLED_REASON,
|
|
28
|
+
FILTER_FLOAT_COMPARISON_HINT,
|
|
29
|
+
READ_ONLY_CUBE_TITLE,
|
|
30
|
+
} from '../__lib__/LegendCubeLabels.js';
|
|
31
|
+
import type { CubeResultValue } from '../graph-manager/CubeEngine.js';
|
|
32
|
+
import type { CubeEditorState } from './CubeEditorState.js';
|
|
33
|
+
|
|
34
|
+
/** One of the grid's quick actions on a cell (spec §12.4) */
|
|
35
|
+
export interface CubeGridQuickAction {
|
|
36
|
+
/** e.g. `Sort by "SHIP_COUNTRY"` */
|
|
37
|
+
readonly label: string;
|
|
38
|
+
/** Why it can't be used, its tooltip then; `undefined` when it can */
|
|
39
|
+
readonly disabledReason: string | undefined;
|
|
40
|
+
/** A note shown as its tooltip when it can be used, e.g. on comparing floats */
|
|
41
|
+
readonly hint: string | undefined;
|
|
42
|
+
/** Adds its node after the node that runs, as one undo step; nothing runs */
|
|
43
|
+
readonly apply: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The quick actions on a cell of the shown rows (spec §12.4, PLAN §11.4):
|
|
48
|
+
* "Sort by" adds an ascending Sort on the cell's column, and "Filter by" an
|
|
49
|
+
* Equal on its value (Is Empty on a null), after the node that runs, which
|
|
50
|
+
* they replace as the node that runs. Both need the rows to be from the
|
|
51
|
+
* current query, no run in flight, and a cube that can change; "Sort by" a
|
|
52
|
+
* type that sorts, and "Filter by" a value its column's type can compare.
|
|
53
|
+
* Read when the menu opens; `[]` when the position is not a column of the
|
|
54
|
+
* shown rows.
|
|
55
|
+
*/
|
|
56
|
+
export const getCubeGridQuickActions = (
|
|
57
|
+
editorState: CubeEditorState,
|
|
58
|
+
position: number,
|
|
59
|
+
cell: CubeResultValue,
|
|
60
|
+
): CubeGridQuickAction[] => {
|
|
61
|
+
const { execution, document } = editorState;
|
|
62
|
+
const { result } = execution;
|
|
63
|
+
const column = result?.schema.columns[position];
|
|
64
|
+
if (!result || !column) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const { query } = document;
|
|
68
|
+
const afterId = query.selected;
|
|
69
|
+
const typeName = column.type.displayName;
|
|
70
|
+
const rule = buildQuickFilterRule(column.name, column.type, cell);
|
|
71
|
+
// what keeps both from being used, in order
|
|
72
|
+
const blocked =
|
|
73
|
+
result.query !== query
|
|
74
|
+
? CUBE_QUICK_ACTION_DISABLED_REASON.STALE_ROWS
|
|
75
|
+
: execution.isRunning
|
|
76
|
+
? CUBE_QUICK_ACTION_DISABLED_REASON.RUNNING
|
|
77
|
+
: editorState.readOnly
|
|
78
|
+
? READ_ONLY_CUBE_TITLE
|
|
79
|
+
: undefined;
|
|
80
|
+
const sortReason =
|
|
81
|
+
blocked ??
|
|
82
|
+
(isSortableType(column.type)
|
|
83
|
+
? undefined
|
|
84
|
+
: CUBE_QUICK_ACTION_DISABLED_REASON.notSortable(typeName));
|
|
85
|
+
const filterReason =
|
|
86
|
+
blocked ??
|
|
87
|
+
(rule
|
|
88
|
+
? undefined
|
|
89
|
+
: isOperatorAvailable(FilterOperator.EQUAL, column.type)
|
|
90
|
+
? CUBE_QUICK_ACTION_DISABLED_REASON.UNREADABLE_VALUE
|
|
91
|
+
: CUBE_QUICK_ACTION_DISABLED_REASON.notComparable(typeName));
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
label: `Sort by "${column.name}"`,
|
|
95
|
+
disabledReason: sortReason,
|
|
96
|
+
hint: undefined,
|
|
97
|
+
apply: () => {
|
|
98
|
+
// as the menu found it: nothing changed since it opened
|
|
99
|
+
if (sortReason === undefined && editorState.document.query === query) {
|
|
100
|
+
editorState.addConfiguredNode(
|
|
101
|
+
Sort.byColumn(query.generateId(Sort.TYPE), column.name),
|
|
102
|
+
afterId,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: `Filter by "${column.name}"`,
|
|
109
|
+
disabledReason: filterReason,
|
|
110
|
+
hint:
|
|
111
|
+
rule?.operator === FilterOperator.EQUAL &&
|
|
112
|
+
column.type.family === TypeFamily.FLOAT
|
|
113
|
+
? FILTER_FLOAT_COMPARISON_HINT
|
|
114
|
+
: undefined,
|
|
115
|
+
apply: () => {
|
|
116
|
+
if (
|
|
117
|
+
filterReason === undefined &&
|
|
118
|
+
rule &&
|
|
119
|
+
editorState.document.query === query
|
|
120
|
+
) {
|
|
121
|
+
editorState.addConfiguredNode(
|
|
122
|
+
new Filter(query.generateId(Filter.TYPE), rule),
|
|
123
|
+
afterId,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
};
|