@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
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
canFixJoinDuplicates,
|
|
19
|
+
fixJoinDuplicates,
|
|
20
|
+
type NodeRegistry,
|
|
21
|
+
type Query,
|
|
22
|
+
type QueryNode,
|
|
23
|
+
type Schema,
|
|
24
|
+
} from '@finos/legend-cube';
|
|
18
25
|
import {
|
|
19
26
|
action,
|
|
20
27
|
computed,
|
|
@@ -87,8 +94,10 @@ export class CubeNodeEditorState {
|
|
|
87
94
|
hasChanges: computed,
|
|
88
95
|
open: action,
|
|
89
96
|
canSwapInputs: computed,
|
|
97
|
+
canRenameDuplicateColumns: computed,
|
|
90
98
|
apply: action,
|
|
91
99
|
swapInputs: action,
|
|
100
|
+
renameDuplicateColumns: action,
|
|
92
101
|
close: action,
|
|
93
102
|
cancel: action,
|
|
94
103
|
discard: action,
|
|
@@ -190,6 +199,66 @@ export class CubeNodeEditorState {
|
|
|
190
199
|
this.bind(query.getNode(node.id) ?? node);
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
/**
|
|
203
|
+
* The cube's query with the draft in place of its node, when it has
|
|
204
|
+
* changes, and the schemas of the node's inputs, which the draft doesn't
|
|
205
|
+
* change
|
|
206
|
+
*/
|
|
207
|
+
private get queryWithEdits(): {
|
|
208
|
+
query: Query;
|
|
209
|
+
inputSchemas: (Schema | undefined)[];
|
|
210
|
+
} {
|
|
211
|
+
const { node, edited } = this;
|
|
212
|
+
let { query } = this.editorState.document;
|
|
213
|
+
if (edited && this.hasChanges && query.canReplace(edited)) {
|
|
214
|
+
query = query.replace(edited);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
query,
|
|
218
|
+
inputSchemas: node
|
|
219
|
+
? query
|
|
220
|
+
.getInputIds(node.id)
|
|
221
|
+
.map((inputId) =>
|
|
222
|
+
inputId === undefined
|
|
223
|
+
? undefined
|
|
224
|
+
: this.editorState.analysis.schemas.get(inputId),
|
|
225
|
+
)
|
|
226
|
+
: [],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Whether the join being edited, edits included, can have the columns its
|
|
232
|
+
* inputs share renamed (spec §7.11's autofix, PLAN §11.4): its only problem
|
|
233
|
+
* is the duplicate rule, and the cube isn't read-only
|
|
234
|
+
*/
|
|
235
|
+
get canRenameDuplicateColumns(): boolean {
|
|
236
|
+
const { node } = this;
|
|
237
|
+
if (!node || node.key !== this.nodeKey || this.editorState.readOnly) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const { query, inputSchemas } = this.queryWithEdits;
|
|
241
|
+
const [leftSchema, rightSchema] = inputSchemas;
|
|
242
|
+
return canFixJoinDuplicates(query, node.id, leftSchema, rightSchema);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Renames the columns the join's inputs share with a Rename before each
|
|
247
|
+
* input, applying the draft first, as one undo step, then goes on editing
|
|
248
|
+
* the join, whose keys follow the renames
|
|
249
|
+
*/
|
|
250
|
+
renameDuplicateColumns(): void {
|
|
251
|
+
const { node } = this;
|
|
252
|
+
if (!node || !this.canRenameDuplicateColumns) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const { query, inputSchemas } = this.queryWithEdits;
|
|
256
|
+
const [leftSchema, rightSchema] = inputSchemas;
|
|
257
|
+
const fixed = fixJoinDuplicates(query, node.id, leftSchema, rightSchema);
|
|
258
|
+
this.editorState.applyQuery(fixed);
|
|
259
|
+
this.bind(fixed.getNode(node.id) ?? node);
|
|
260
|
+
}
|
|
261
|
+
|
|
193
262
|
/** Applies the draft, then closes the panel (spec §17.5: edits commit on close) */
|
|
194
263
|
close(): void {
|
|
195
264
|
this.apply();
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { needsDatabaseType } from '@finos/legend-cube';
|
|
17
18
|
import type { GeneratorFn } from '@finos/legend-shared';
|
|
18
|
-
import { action, flow, makeObservable, observable } from 'mobx';
|
|
19
|
+
import { action, flow, flowResult, makeObservable, observable } from 'mobx';
|
|
19
20
|
import {
|
|
20
21
|
CubeEngineError,
|
|
21
22
|
CubeEngineErrorKind,
|
|
@@ -50,11 +51,18 @@ export class CubeShowPureState {
|
|
|
50
51
|
this.editorState = editorState;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Opens the dialog and renders the query, written for the database it runs
|
|
56
|
+
* on, as Execute writes it; does nothing when Execute can't run
|
|
57
|
+
*/
|
|
54
58
|
*open(): GeneratorFn<void> {
|
|
55
59
|
const { execution, document, emitter, rowLimit, host } = this.editorState;
|
|
56
|
-
const
|
|
57
|
-
|
|
60
|
+
const { query, context } = document;
|
|
61
|
+
const runtime = context?.runtime;
|
|
62
|
+
const model = context?.model;
|
|
63
|
+
// checked by canExecute
|
|
64
|
+
const captureId = query.selected as string;
|
|
65
|
+
if (!execution.canExecute || runtime === undefined || model === undefined) {
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
68
|
const request = ++this.request;
|
|
@@ -63,8 +71,22 @@ export class CubeShowPureState {
|
|
|
63
71
|
this.error = undefined;
|
|
64
72
|
this.isRendering = true;
|
|
65
73
|
try {
|
|
74
|
+
let databaseType: string | undefined;
|
|
75
|
+
if (needsDatabaseType(query, captureId)) {
|
|
76
|
+
yield flowResult(this.editorState.loadModelOutline(model));
|
|
77
|
+
// closed, or opened again, while the outline loaded
|
|
78
|
+
if (request !== this.request) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
databaseType = this.editorState.getRunDatabaseType(
|
|
82
|
+
query,
|
|
83
|
+
captureId,
|
|
84
|
+
model,
|
|
85
|
+
runtime,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
66
88
|
const text = (yield host.engine.renderPure(
|
|
67
|
-
emitter.emitExecutionLambda({ rowLimit, runtime }),
|
|
89
|
+
emitter.emitExecutionLambda({ rowLimit, runtime, databaseType }),
|
|
68
90
|
)) as string;
|
|
69
91
|
if (request === this.request) {
|
|
70
92
|
this.text = text;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
/** Whole-number text: an optional sign, then digits */
|
|
18
|
+
const WHOLE_NUMBER_TEXT = /^[+-]?\d+$/u;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The number a size or row index field holds. Text that isn't a whole number,
|
|
22
|
+
* empty text included, gives `undefined`, which the node then reports: never
|
|
23
|
+
* a default. It doesn't use `Number()` on the raw text, which reads `''` as 0,
|
|
24
|
+
* `0x10` as 16 and `1e3` as 1000. Spaces around the digits are allowed; a
|
|
25
|
+
* number too long for a double gives `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
export const parseWholeNumberText = (text: string): number | undefined => {
|
|
28
|
+
const trimmed = text.trim();
|
|
29
|
+
if (!WHOLE_NUMBER_TEXT.test(trimmed)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const value = Number(trimmed);
|
|
33
|
+
return Number.isFinite(value) ? value : undefined;
|
|
34
|
+
};
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
JoinType,
|
|
21
21
|
type Query,
|
|
22
22
|
RelationalTableSource,
|
|
23
|
+
Rename,
|
|
23
24
|
type SchemaInferenceResult,
|
|
24
25
|
} from '@finos/legend-cube';
|
|
25
26
|
import { action, makeObservable, observable } from 'mobx';
|
|
@@ -138,28 +139,42 @@ export class CubeJoinDraft extends CubeNodeDraft<Join> {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
/** Where a column of a node's output comes from: a table, and the column's name in it */
|
|
143
|
+
export interface CubeColumnOrigin {
|
|
144
|
+
readonly source: RelationalTableSource;
|
|
145
|
+
readonly column: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
141
148
|
/**
|
|
142
|
-
* The table
|
|
143
|
-
* through the inputs whose output has
|
|
144
|
-
* on both sides comes from the side the
|
|
145
|
-
*
|
|
146
|
-
* merges them.
|
|
149
|
+
* The table columns a column of a node's output comes from: followed back
|
|
150
|
+
* through the inputs whose output has it, and through a Rename to its old
|
|
151
|
+
* name. A join key of the same name on both sides comes from the side the
|
|
152
|
+
* join keeps (the left for INNER and LEFT OUTER, the right for RIGHT OUTER),
|
|
153
|
+
* and from both for FULL OUTER, which merges them. A column the node's output
|
|
154
|
+
* doesn't have, e.g. one a Restrict dropped, comes from nowhere.
|
|
147
155
|
*/
|
|
148
|
-
export const
|
|
156
|
+
export const findColumnOrigins = (
|
|
149
157
|
query: Query,
|
|
150
158
|
analysis: SchemaInferenceResult,
|
|
151
159
|
nodeId: string,
|
|
152
160
|
columnName: string,
|
|
153
|
-
):
|
|
161
|
+
): CubeColumnOrigin[] => {
|
|
154
162
|
const node = query.getNode(nodeId);
|
|
155
163
|
if (node instanceof RelationalTableSource) {
|
|
156
164
|
return node.resolution.kind === 'resolved' &&
|
|
157
165
|
node.resolution.schema.lookup(columnName)
|
|
158
|
-
? [node]
|
|
166
|
+
? [{ source: node, column: columnName }]
|
|
159
167
|
: [];
|
|
160
168
|
}
|
|
169
|
+
if (!node || !analysis.schemas.get(nodeId)?.lookup(columnName)) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
161
172
|
const inputIds = query.getInputIds(nodeId);
|
|
162
173
|
const [leftId, rightId] = inputIds;
|
|
174
|
+
const inputName =
|
|
175
|
+
node instanceof Rename
|
|
176
|
+
? (node.mappings.find(({ to }) => to === columnName)?.from ?? columnName)
|
|
177
|
+
: columnName;
|
|
163
178
|
const followed =
|
|
164
179
|
node instanceof Join &&
|
|
165
180
|
node.joinType !== JoinType.FULL_OUTER &&
|
|
@@ -169,12 +184,23 @@ export const findColumnSources = (
|
|
|
169
184
|
? [node.joinType === JoinType.RIGHT_OUTER ? rightId : leftId]
|
|
170
185
|
: inputIds;
|
|
171
186
|
return followed.flatMap((inputId) =>
|
|
172
|
-
inputId !== undefined && analysis.schemas.get(inputId)?.lookup(
|
|
173
|
-
?
|
|
187
|
+
inputId !== undefined && analysis.schemas.get(inputId)?.lookup(inputName)
|
|
188
|
+
? findColumnOrigins(query, analysis, inputId, inputName)
|
|
174
189
|
: [],
|
|
175
190
|
);
|
|
176
191
|
};
|
|
177
192
|
|
|
193
|
+
/** The table sources a column of a node's output comes from (`findColumnOrigins`) */
|
|
194
|
+
export const findColumnSources = (
|
|
195
|
+
query: Query,
|
|
196
|
+
analysis: SchemaInferenceResult,
|
|
197
|
+
nodeId: string,
|
|
198
|
+
columnName: string,
|
|
199
|
+
): RelationalTableSource[] =>
|
|
200
|
+
findColumnOrigins(query, analysis, nodeId, columnName).map(
|
|
201
|
+
({ source }) => source,
|
|
202
|
+
);
|
|
203
|
+
|
|
178
204
|
/**
|
|
179
205
|
* Whether the column of the node's output comes from a table column whose
|
|
180
206
|
* type Cube can't read (OTHER or ARRAY), so it was typed as a bare String
|
|
@@ -188,10 +214,11 @@ export const isUntypedColumn = (
|
|
|
188
214
|
columnName: string,
|
|
189
215
|
): boolean =>
|
|
190
216
|
outline !== undefined &&
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
findColumnOrigins(query, analysis, nodeId, columnName).some(
|
|
218
|
+
({ source, column }) =>
|
|
219
|
+
outline.databases
|
|
220
|
+
.find((database) => database.path === source.database)
|
|
221
|
+
?.schemas.find((schema) => schema.name === source.schema)
|
|
222
|
+
?.tables.find((table) => table.name === source.table)
|
|
223
|
+
?.untypedColumns.includes(column),
|
|
197
224
|
);
|
|
@@ -14,12 +14,28 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
Distinct,
|
|
19
|
+
Drop,
|
|
20
|
+
Filter,
|
|
21
|
+
Join,
|
|
22
|
+
Limit,
|
|
23
|
+
type QueryNode,
|
|
24
|
+
Rename,
|
|
25
|
+
Restrict,
|
|
26
|
+
Slice,
|
|
27
|
+
Sort,
|
|
28
|
+
} from '@finos/legend-cube';
|
|
18
29
|
import { guaranteeType } from '@finos/legend-shared';
|
|
19
30
|
import type { CubeEditorState } from '../CubeEditorState.js';
|
|
20
31
|
import { CubeFilterDraft } from './CubeFilterDraft.js';
|
|
21
32
|
import { CubeJoinDraft } from './CubeJoinDraft.js';
|
|
22
33
|
import { type CubeNodeDraft, CubeReadOnlyNodeDraft } from './CubeNodeDraft.js';
|
|
34
|
+
import { CubeRenameDraft } from './CubeRenameDraft.js';
|
|
35
|
+
import { CubeRestrictDraft } from './CubeRestrictDraft.js';
|
|
36
|
+
import { CubeRowCountDraft } from './CubeRowCountDraft.js';
|
|
37
|
+
import { CubeSliceDraft } from './CubeSliceDraft.js';
|
|
38
|
+
import { CubeSortDraft } from './CubeSortDraft.js';
|
|
23
39
|
|
|
24
40
|
/** Makes the draft of a node of one type */
|
|
25
41
|
export type CubeNodeDraftFactory = (
|
|
@@ -36,10 +52,28 @@ export const CUBE_NODE_DRAFT_FACTORIES: ReadonlyMap<
|
|
|
36
52
|
string,
|
|
37
53
|
CubeNodeDraftFactory
|
|
38
54
|
> = new Map<string, CubeNodeDraftFactory>([
|
|
55
|
+
[Sort.TYPE, (node) => new CubeSortDraft(guaranteeType(node, Sort))],
|
|
39
56
|
[Filter.TYPE, (node) => new CubeFilterDraft(guaranteeType(node, Filter))],
|
|
57
|
+
[
|
|
58
|
+
Restrict.TYPE,
|
|
59
|
+
(node) => new CubeRestrictDraft(guaranteeType(node, Restrict)),
|
|
60
|
+
],
|
|
61
|
+
[Rename.TYPE, (node) => new CubeRenameDraft(guaranteeType(node, Rename))],
|
|
40
62
|
[Join.TYPE, (node) => new CubeJoinDraft(guaranteeType(node, Join))],
|
|
63
|
+
[Drop.TYPE, (node) => new CubeRowCountDraft(guaranteeType(node, Drop))],
|
|
64
|
+
[Limit.TYPE, (node) => new CubeRowCountDraft(guaranteeType(node, Limit))],
|
|
65
|
+
[Slice.TYPE, (node) => new CubeSliceDraft(guaranteeType(node, Slice))],
|
|
41
66
|
]);
|
|
42
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The transforms with nothing to set (spec §17.6: Distinct is "description
|
|
70
|
+
* only"): an editor in `CUBE_NODE_EDITORS` and no draft factory, so the panel
|
|
71
|
+
* shows no Apply or Cancel (PLAN §7.4 item 2)
|
|
72
|
+
*/
|
|
73
|
+
export const CUBE_NODE_TYPES_WITHOUT_SETTINGS: readonly string[] = [
|
|
74
|
+
Distinct.TYPE,
|
|
75
|
+
];
|
|
76
|
+
|
|
43
77
|
/** The node's draft; a node with nothing to edit gets a read-only one */
|
|
44
78
|
export const createCubeNodeDraft = (
|
|
45
79
|
node: QueryNode,
|
|
@@ -0,0 +1,110 @@
|
|
|
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 type { Rename, RenameMapping } from '@finos/legend-cube';
|
|
18
|
+
import { action, makeObservable, observable } from 'mobx';
|
|
19
|
+
import { CubeNodeDraft } from './CubeNodeDraft.js';
|
|
20
|
+
|
|
21
|
+
/** One row of the Rename editor: an old column and its new name, `''` until picked or typed */
|
|
22
|
+
export interface CubeRenameRow {
|
|
23
|
+
/** Identifies the row in the editor */
|
|
24
|
+
readonly key: number;
|
|
25
|
+
readonly from: string;
|
|
26
|
+
readonly to: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let nextRowKey = 1;
|
|
30
|
+
|
|
31
|
+
const blankRow = (): CubeRenameRow => ({ key: nextRowKey++, from: '', to: '' });
|
|
32
|
+
|
|
33
|
+
const isSameMappings = (
|
|
34
|
+
a: readonly RenameMapping[],
|
|
35
|
+
b: readonly RenameMapping[],
|
|
36
|
+
): boolean =>
|
|
37
|
+
a.length === b.length &&
|
|
38
|
+
a.every(
|
|
39
|
+
(mapping, index) =>
|
|
40
|
+
mapping.from === b[index]?.from && mapping.to === b[index]?.to,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The Rename editor's draft (spec §17.6: a list of old column and new name
|
|
45
|
+
* rows). A new name is kept exactly as typed, never trimmed. A row with
|
|
46
|
+
* neither name is left out when built, as a Filter's blank rows are; a row
|
|
47
|
+
* with only one is kept, so validation reports it.
|
|
48
|
+
*/
|
|
49
|
+
export class CubeRenameDraft extends CubeNodeDraft<Rename> {
|
|
50
|
+
rows: readonly CubeRenameRow[];
|
|
51
|
+
/** Anything was changed; until then, `build()` gives the original back */
|
|
52
|
+
private touched = false;
|
|
53
|
+
|
|
54
|
+
constructor(original: Rename) {
|
|
55
|
+
super(original);
|
|
56
|
+
makeObservable<CubeRenameDraft, 'touched'>(this, {
|
|
57
|
+
rows: observable.ref,
|
|
58
|
+
touched: observable,
|
|
59
|
+
addRow: action,
|
|
60
|
+
removeRow: action,
|
|
61
|
+
setFrom: action,
|
|
62
|
+
setTo: action,
|
|
63
|
+
});
|
|
64
|
+
this.rows = original.mappings.length
|
|
65
|
+
? original.mappings.map(({ from, to }) => ({
|
|
66
|
+
key: nextRowKey++,
|
|
67
|
+
from,
|
|
68
|
+
to,
|
|
69
|
+
}))
|
|
70
|
+
: [blankRow()];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The mappings the rows build, in order: a row with neither name is left out */
|
|
74
|
+
get mappings(): RenameMapping[] {
|
|
75
|
+
return this.rows
|
|
76
|
+
.filter((row) => row.from || row.to)
|
|
77
|
+
.map(({ from, to }) => ({ from, to }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
addRow(): void {
|
|
81
|
+
this.rows = [...this.rows, blankRow()];
|
|
82
|
+
this.touched = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
removeRow(key: number): void {
|
|
86
|
+
this.rows = this.rows.filter((row) => row.key !== key);
|
|
87
|
+
this.touched = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setFrom(key: number, from: string): void {
|
|
91
|
+
this.rows = this.rows.map((row) =>
|
|
92
|
+
row.key === key ? { ...row, from } : row,
|
|
93
|
+
);
|
|
94
|
+
this.touched = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setTo(key: number, to: string): void {
|
|
98
|
+
this.rows = this.rows.map((row) =>
|
|
99
|
+
row.key === key ? { ...row, to } : row,
|
|
100
|
+
);
|
|
101
|
+
this.touched = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
build(): Rename {
|
|
105
|
+
const { original, mappings } = this;
|
|
106
|
+
return !this.touched || isSameMappings(mappings, original.mappings)
|
|
107
|
+
? original
|
|
108
|
+
: original.withMappings(mappings);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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 type { Restrict, Schema } from '@finos/legend-cube';
|
|
18
|
+
import { action, makeObservable, observable } from 'mobx';
|
|
19
|
+
import { CubeNodeDraft } from './CubeNodeDraft.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The columns in the input's order, then those the input doesn't have, in
|
|
23
|
+
* the order they were picked: a saved column the input lost stays listed
|
|
24
|
+
* until it is unticked
|
|
25
|
+
*/
|
|
26
|
+
const inInputOrder = (names: readonly string[], schema: Schema): string[] => [
|
|
27
|
+
...schema.names().filter((name) => names.includes(name)),
|
|
28
|
+
...names.filter((name) => schema.lookup(name) === undefined),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const isSameSet = (a: readonly string[], b: readonly string[]): boolean =>
|
|
32
|
+
a.length === b.length && a.every((name) => b.includes(name));
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The Restrict editor's draft (spec §17.6: a multi-select of the columns to
|
|
36
|
+
* keep). It keeps the picked columns in the input's order, as the node's
|
|
37
|
+
* output has them.
|
|
38
|
+
*/
|
|
39
|
+
export class CubeRestrictDraft extends CubeNodeDraft<Restrict> {
|
|
40
|
+
columns: readonly string[];
|
|
41
|
+
/** Anything was changed; until then, `build()` gives the original back */
|
|
42
|
+
private touched = false;
|
|
43
|
+
|
|
44
|
+
constructor(original: Restrict) {
|
|
45
|
+
super(original);
|
|
46
|
+
makeObservable<CubeRestrictDraft, 'touched'>(this, {
|
|
47
|
+
columns: observable.ref,
|
|
48
|
+
touched: observable,
|
|
49
|
+
toggleColumn: action,
|
|
50
|
+
selectAll: action,
|
|
51
|
+
clear: action,
|
|
52
|
+
});
|
|
53
|
+
this.columns = original.columns;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Ticks or unticks the column */
|
|
57
|
+
toggleColumn(name: string, schema: Schema): void {
|
|
58
|
+
this.columns = this.columns.includes(name)
|
|
59
|
+
? this.columns.filter((picked) => picked !== name)
|
|
60
|
+
: inInputOrder([...this.columns, name], schema);
|
|
61
|
+
this.touched = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Ticks every column of the input */
|
|
65
|
+
selectAll(schema: Schema): void {
|
|
66
|
+
this.columns = inInputOrder([...schema.names(), ...this.columns], schema);
|
|
67
|
+
this.touched = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Unticks every column */
|
|
71
|
+
clear(): void {
|
|
72
|
+
this.columns = [];
|
|
73
|
+
this.touched = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The original while the same columns are picked, whatever their order */
|
|
77
|
+
build(): Restrict {
|
|
78
|
+
const { original, columns } = this;
|
|
79
|
+
return !this.touched || isSameSet(columns, original.columns)
|
|
80
|
+
? original
|
|
81
|
+
: original.withColumns(columns);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 type { QueryNode } from '@finos/legend-cube';
|
|
18
|
+
import { action, makeObservable, observable } from 'mobx';
|
|
19
|
+
import { parseWholeNumberText } from './CubeIntegerText.js';
|
|
20
|
+
import { CubeNodeDraft } from './CubeNodeDraft.js';
|
|
21
|
+
|
|
22
|
+
/** A node whose one setting is a number of rows: a Limit or a Drop */
|
|
23
|
+
export interface CubeRowCountNode<N extends QueryNode> extends QueryNode {
|
|
24
|
+
readonly size: number | undefined;
|
|
25
|
+
withSize(size: number | undefined): N;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The draft of a node whose one setting is a number of rows (spec §17.6: "one
|
|
30
|
+
* integer field"). It keeps the text as typed. Text that isn't a whole
|
|
31
|
+
* number, empty text included, builds a cleared size, which the node reports:
|
|
32
|
+
* never the default (spec §7.7). The text it opened with gives the original
|
|
33
|
+
* back, so a saved size the field can't read (`1.5` from an imported spec) is
|
|
34
|
+
* kept unless the user types something else.
|
|
35
|
+
*/
|
|
36
|
+
export class CubeRowCountDraft<
|
|
37
|
+
N extends CubeRowCountNode<N>,
|
|
38
|
+
> extends CubeNodeDraft<N> {
|
|
39
|
+
sizeText: string;
|
|
40
|
+
/** The text the field opened with, which builds the original */
|
|
41
|
+
private readonly initialText: string;
|
|
42
|
+
|
|
43
|
+
constructor(original: N) {
|
|
44
|
+
super(original);
|
|
45
|
+
makeObservable(this, {
|
|
46
|
+
sizeText: observable,
|
|
47
|
+
setSizeText: action,
|
|
48
|
+
});
|
|
49
|
+
this.initialText = original.size === undefined ? '' : String(original.size);
|
|
50
|
+
this.sizeText = this.initialText;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The size the text gives: `undefined` unless it is a whole number */
|
|
54
|
+
get size(): number | undefined {
|
|
55
|
+
return parseWholeNumberText(this.sizeText);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setSizeText(text: string): void {
|
|
59
|
+
this.sizeText = text;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The original while the field holds its opening text or the same size */
|
|
63
|
+
build(): N {
|
|
64
|
+
const { original, size } = this;
|
|
65
|
+
return this.sizeText.trim() === this.initialText || size === original.size
|
|
66
|
+
? original
|
|
67
|
+
: original.withSize(size);
|
|
68
|
+
}
|
|
69
|
+
}
|