@finos/legend-cube-builder 0.0.5 → 0.0.6
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/docs/adding-an-operation.md +4 -2
- package/docs/demo/README.md +1 -1
- package/docs/hosting.md +40 -9
- package/lib/__lib__/LegendCubeHelpText.d.ts.map +1 -1
- package/lib/__lib__/LegendCubeHelpText.js +3 -1
- package/lib/__lib__/LegendCubeHelpText.js.map +1 -1
- package/lib/__lib__/LegendCubeLabels.d.ts +34 -5
- package/lib/__lib__/LegendCubeLabels.d.ts.map +1 -1
- package/lib/__lib__/LegendCubeLabels.js +45 -8
- package/lib/__lib__/LegendCubeLabels.js.map +1 -1
- package/lib/__lib__/LegendCubeNavigation.d.ts +23 -0
- package/lib/__lib__/LegendCubeNavigation.d.ts.map +1 -0
- package/lib/__lib__/LegendCubeNavigation.js +33 -0
- package/lib/__lib__/LegendCubeNavigation.js.map +1 -0
- package/lib/__lib__/LegendCubeTesting.d.ts +3 -1
- package/lib/__lib__/LegendCubeTesting.d.ts.map +1 -1
- package/lib/__lib__/LegendCubeTesting.js +2 -0
- package/lib/__lib__/LegendCubeTesting.js.map +1 -1
- package/lib/__lib__/LegendCubeTour.d.ts +62 -0
- package/lib/__lib__/LegendCubeTour.d.ts.map +1 -0
- package/lib/__lib__/LegendCubeTour.js +149 -0
- package/lib/__lib__/LegendCubeTour.js.map +1 -0
- package/lib/__test-utils__/CubeExampleTestUtils.d.ts +34 -0
- package/lib/__test-utils__/CubeExampleTestUtils.d.ts.map +1 -0
- package/lib/__test-utils__/CubeExampleTestUtils.js +72 -0
- package/lib/__test-utils__/CubeExampleTestUtils.js.map +1 -0
- package/lib/__test-utils__/CubeVariantTestUtils.d.ts +16 -0
- package/lib/__test-utils__/CubeVariantTestUtils.d.ts.map +1 -0
- package/lib/__test-utils__/CubeVariantTestUtils.js +66 -0
- package/lib/__test-utils__/CubeVariantTestUtils.js.map +1 -0
- package/lib/components/CubeAddItems.d.ts.map +1 -1
- package/lib/components/CubeAddItems.js +2 -1
- package/lib/components/CubeAddItems.js.map +1 -1
- package/lib/components/CubeEditor.d.ts.map +1 -1
- package/lib/components/CubeEditor.js +11 -5
- package/lib/components/CubeEditor.js.map +1 -1
- package/lib/components/CubeFileMenu.d.ts.map +1 -1
- package/lib/components/CubeFileMenu.js +2 -1
- package/lib/components/CubeFileMenu.js.map +1 -1
- package/lib/components/CubeNodeIcon.d.ts.map +1 -1
- package/lib/components/CubeNodeIcon.js +2 -1
- package/lib/components/CubeNodeIcon.js.map +1 -1
- package/lib/components/canvas/CubeCanvas.d.ts.map +1 -1
- package/lib/components/canvas/CubeCanvas.js +3 -2
- package/lib/components/canvas/CubeCanvas.js.map +1 -1
- package/lib/components/canvas/CubeCanvasNode.d.ts.map +1 -1
- package/lib/components/canvas/CubeCanvasNode.js +4 -1
- package/lib/components/canvas/CubeCanvasNode.js.map +1 -1
- package/lib/components/dev-settings/CubeDevSettings.d.ts +2 -2
- package/lib/components/dev-settings/CubeDevSettings.d.ts.map +1 -1
- package/lib/components/dev-settings/CubeDevSettings.js +12 -8
- package/lib/components/dev-settings/CubeDevSettings.js.map +1 -1
- package/lib/components/editors/CubeColumnPicker.d.ts.map +1 -1
- package/lib/components/editors/CubeColumnPicker.js +3 -1
- package/lib/components/editors/CubeColumnPicker.js.map +1 -1
- package/lib/components/editors/CubeExplodeEditor.d.ts +26 -0
- package/lib/components/editors/CubeExplodeEditor.d.ts.map +1 -0
- package/lib/components/editors/CubeExplodeEditor.js +61 -0
- package/lib/components/editors/CubeExplodeEditor.js.map +1 -0
- package/lib/components/editors/CubeExpressionFilterEditor.d.ts.map +1 -1
- package/lib/components/editors/CubeExpressionFilterEditor.js +8 -5
- package/lib/components/editors/CubeExpressionFilterEditor.js.map +1 -1
- package/lib/components/editors/CubeExtendEditor.d.ts +0 -2
- package/lib/components/editors/CubeExtendEditor.d.ts.map +1 -1
- package/lib/components/editors/CubeExtendEditor.js +12 -11
- package/lib/components/editors/CubeExtendEditor.js.map +1 -1
- package/lib/components/editors/CubeJsonFieldsTree.d.ts +46 -0
- package/lib/components/editors/CubeJsonFieldsTree.d.ts.map +1 -0
- package/lib/components/editors/CubeJsonFieldsTree.js +93 -0
- package/lib/components/editors/CubeJsonFieldsTree.js.map +1 -0
- package/lib/components/editors/CubeNodeEditorRegistry.d.ts.map +1 -1
- package/lib/components/editors/CubeNodeEditorRegistry.js +3 -1
- package/lib/components/editors/CubeNodeEditorRegistry.js.map +1 -1
- package/lib/components/grid/CubeGridRegion.d.ts.map +1 -1
- package/lib/components/grid/CubeGridRegion.js +5 -4
- package/lib/components/grid/CubeGridRegion.js.map +1 -1
- package/lib/components/grid/CubeGridValues.d.ts +5 -3
- package/lib/components/grid/CubeGridValues.d.ts.map +1 -1
- package/lib/components/grid/CubeGridValues.js +8 -3
- package/lib/components/grid/CubeGridValues.js.map +1 -1
- package/lib/components/grid/CubeJsonExplorerDialog.d.ts +27 -0
- package/lib/components/grid/CubeJsonExplorerDialog.d.ts.map +1 -0
- package/lib/components/grid/CubeJsonExplorerDialog.js +102 -0
- package/lib/components/grid/CubeJsonExplorerDialog.js.map +1 -0
- package/lib/components/grid/CubeJsonViewerDialog.d.ts +27 -0
- package/lib/components/grid/CubeJsonViewerDialog.d.ts.map +1 -0
- package/lib/components/grid/CubeJsonViewerDialog.js +68 -0
- package/lib/components/grid/CubeJsonViewerDialog.js.map +1 -0
- package/lib/components/grid/CubeResultGrid.d.ts +15 -3
- package/lib/components/grid/CubeResultGrid.d.ts.map +1 -1
- package/lib/components/grid/CubeResultGrid.js +95 -3
- package/lib/components/grid/CubeResultGrid.js.map +1 -1
- package/lib/components/grid/CubeServerSideGrid.d.ts.map +1 -1
- package/lib/components/grid/CubeServerSideGrid.js +4 -2
- package/lib/components/grid/CubeServerSideGrid.js.map +1 -1
- package/lib/components/palette/CubePalette.d.ts.map +1 -1
- package/lib/components/palette/CubePalette.js +4 -1
- package/lib/components/palette/CubePalette.js.map +1 -1
- package/lib/components/source-picker/CubeIngestTab.d.ts +2 -2
- package/lib/components/source-picker/CubeIngestTab.d.ts.map +1 -1
- package/lib/components/source-picker/CubeIngestTab.js +19 -13
- package/lib/components/source-picker/CubeIngestTab.js.map +1 -1
- package/lib/components/tour/CubeTour.d.ts +36 -0
- package/lib/components/tour/CubeTour.d.ts.map +1 -0
- package/lib/components/tour/CubeTour.js +183 -0
- package/lib/components/tour/CubeTour.js.map +1 -0
- package/lib/index.css +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/stores/CubeEditorState.d.ts +17 -1
- package/lib/stores/CubeEditorState.d.ts.map +1 -1
- package/lib/stores/CubeEditorState.js +35 -5
- package/lib/stores/CubeEditorState.js.map +1 -1
- package/lib/stores/CubeExamples.d.ts.map +1 -1
- package/lib/stores/CubeExamples.js +101 -4
- package/lib/stores/CubeExamples.js.map +1 -1
- package/lib/stores/CubeExamplesState.d.ts.map +1 -1
- package/lib/stores/CubeExamplesState.js +4 -1
- package/lib/stores/CubeExamplesState.js.map +1 -1
- package/lib/stores/CubeGridQuickActions.d.ts +7 -0
- package/lib/stores/CubeGridQuickActions.d.ts.map +1 -1
- package/lib/stores/CubeGridQuickActions.js +20 -11
- package/lib/stores/CubeGridQuickActions.js.map +1 -1
- package/lib/stores/CubePureText.d.ts +20 -0
- package/lib/stores/CubePureText.d.ts.map +1 -0
- package/lib/stores/CubePureText.js +24 -0
- package/lib/stores/CubePureText.js.map +1 -0
- package/lib/stores/CubeTourState.d.ts +49 -0
- package/lib/stores/CubeTourState.d.ts.map +1 -0
- package/lib/stores/CubeTourState.js +92 -0
- package/lib/stores/CubeTourState.js.map +1 -0
- package/lib/stores/LegendCubeHost.d.ts +88 -0
- package/lib/stores/LegendCubeHost.d.ts.map +1 -0
- package/lib/stores/LegendCubeHost.js +149 -0
- package/lib/stores/LegendCubeHost.js.map +1 -0
- package/lib/stores/editors/CubeExplodeDraft.d.ts +87 -0
- package/lib/stores/editors/CubeExplodeDraft.d.ts.map +1 -0
- package/lib/stores/editors/CubeExplodeDraft.js +221 -0
- package/lib/stores/editors/CubeExplodeDraft.js.map +1 -0
- package/lib/stores/editors/CubeExpressionFilterDraft.d.ts +3 -0
- package/lib/stores/editors/CubeExpressionFilterDraft.d.ts.map +1 -1
- package/lib/stores/editors/CubeExpressionFilterDraft.js +4 -0
- package/lib/stores/editors/CubeExpressionFilterDraft.js.map +1 -1
- package/lib/stores/editors/CubeExtendDraft.d.ts +9 -0
- package/lib/stores/editors/CubeExtendDraft.d.ts.map +1 -1
- package/lib/stores/editors/CubeExtendDraft.js +26 -0
- package/lib/stores/editors/CubeExtendDraft.js.map +1 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.d.ts.map +1 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.js +6 -1
- package/lib/stores/editors/CubeNodeDraftRegistry.js.map +1 -1
- package/lib/stores/fixtures/CubeSalesDuckDbModel.d.ts +1 -0
- package/lib/stores/fixtures/CubeSalesDuckDbModel.d.ts.map +1 -1
- package/lib/stores/fixtures/CubeSalesDuckDbModel.js +8 -2
- package/lib/stores/fixtures/CubeSalesDuckDbModel.js.map +1 -1
- package/lib/stores/source-picker/CubeIngestTabState.d.ts +48 -14
- package/lib/stores/source-picker/CubeIngestTabState.d.ts.map +1 -1
- package/lib/stores/source-picker/CubeIngestTabState.js +159 -62
- package/lib/stores/source-picker/CubeIngestTabState.js.map +1 -1
- package/lib/stores/variant/CubeJson.d.ts +48 -0
- package/lib/stores/variant/CubeJson.d.ts.map +1 -0
- package/lib/stores/variant/CubeJson.js +207 -0
- package/lib/stores/variant/CubeJson.js.map +1 -0
- package/lib/stores/variant/CubeJsonExplorerState.d.ts +88 -0
- package/lib/stores/variant/CubeJsonExplorerState.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonExplorerState.js +297 -0
- package/lib/stores/variant/CubeJsonExplorerState.js.map +1 -0
- package/lib/stores/variant/CubeJsonFieldsState.d.ts +83 -0
- package/lib/stores/variant/CubeJsonFieldsState.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonFieldsState.js +207 -0
- package/lib/stores/variant/CubeJsonFieldsState.js.map +1 -0
- package/lib/stores/variant/CubeJsonOffers.d.ts +76 -0
- package/lib/stores/variant/CubeJsonOffers.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonOffers.js +250 -0
- package/lib/stores/variant/CubeJsonOffers.js.map +1 -0
- package/lib/stores/variant/CubeJsonParse.d.ts +28 -0
- package/lib/stores/variant/CubeJsonParse.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonParse.js +53 -0
- package/lib/stores/variant/CubeJsonParse.js.map +1 -0
- package/lib/stores/variant/CubeJsonSampling.d.ts +41 -0
- package/lib/stores/variant/CubeJsonSampling.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonSampling.js +59 -0
- package/lib/stores/variant/CubeJsonSampling.js.map +1 -0
- package/lib/stores/variant/CubeJsonShape.d.ts +87 -0
- package/lib/stores/variant/CubeJsonShape.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonShape.js +209 -0
- package/lib/stores/variant/CubeJsonShape.js.map +1 -0
- package/lib/stores/variant/CubeJsonViewerState.d.ts +33 -0
- package/lib/stores/variant/CubeJsonViewerState.d.ts.map +1 -0
- package/lib/stores/variant/CubeJsonViewerState.js +53 -0
- package/lib/stores/variant/CubeJsonViewerState.js.map +1 -0
- package/package.json +2 -2
- package/src/__lib__/LegendCubeHelpText.ts +4 -0
- package/src/__lib__/LegendCubeLabels.ts +60 -8
- package/src/__lib__/LegendCubeNavigation.ts +48 -0
- package/src/__lib__/LegendCubeTesting.ts +2 -0
- package/src/__lib__/LegendCubeTour.ts +173 -0
- package/src/__test-utils__/CubeExampleTestUtils.ts +119 -0
- package/src/__test-utils__/CubeVariantTestUtils.ts +103 -0
- package/src/components/CubeAddItems.tsx +2 -0
- package/src/components/CubeEditor.tsx +15 -0
- package/src/components/CubeFileMenu.tsx +2 -0
- package/src/components/CubeNodeIcon.tsx +2 -0
- package/src/components/canvas/CubeCanvas.tsx +17 -1
- package/src/components/canvas/CubeCanvasNode.tsx +7 -0
- package/src/components/dev-settings/CubeDevSettings.tsx +17 -2
- package/src/components/editors/CubeColumnPicker.tsx +3 -0
- package/src/components/editors/CubeExplodeEditor.tsx +207 -0
- package/src/components/editors/CubeExpressionFilterEditor.tsx +25 -8
- package/src/components/editors/CubeExtendEditor.tsx +32 -19
- package/src/components/editors/CubeJsonFieldsTree.tsx +316 -0
- package/src/components/editors/CubeNodeEditorRegistry.ts +3 -0
- package/src/components/grid/CubeGridRegion.tsx +21 -3
- package/src/components/grid/CubeGridValues.ts +8 -3
- package/src/components/grid/CubeJsonExplorerDialog.tsx +304 -0
- package/src/components/grid/CubeJsonViewerDialog.tsx +190 -0
- package/src/components/grid/CubeResultGrid.tsx +144 -2
- package/src/components/grid/CubeServerSideGrid.tsx +13 -0
- package/src/components/palette/CubePalette.tsx +9 -0
- package/src/components/source-picker/CubeIngestTab.tsx +62 -30
- package/src/components/tour/CubeTour.tsx +317 -0
- package/src/index.ts +7 -0
- package/src/stores/CubeEditorState.ts +36 -5
- package/src/stores/CubeExamples.ts +120 -2
- package/src/stores/CubeExamplesState.ts +9 -1
- package/src/stores/CubeGridQuickActions.ts +23 -12
- package/src/stores/CubePureText.ts +28 -0
- package/src/stores/CubeTourState.ts +116 -0
- package/src/stores/LegendCubeHost.ts +243 -0
- package/src/stores/editors/CubeExplodeDraft.ts +272 -0
- package/src/stores/editors/CubeExpressionFilterDraft.ts +4 -0
- package/src/stores/editors/CubeExtendDraft.ts +29 -0
- package/src/stores/editors/CubeNodeDraftRegistry.ts +7 -0
- package/src/stores/fixtures/CubeSalesDuckDbModel.ts +8 -2
- package/src/stores/source-picker/CubeIngestTabState.ts +253 -102
- package/src/stores/variant/CubeJson.ts +229 -0
- package/src/stores/variant/CubeJsonExplorerState.ts +378 -0
- package/src/stores/variant/CubeJsonFieldsState.ts +252 -0
- package/src/stores/variant/CubeJsonOffers.ts +359 -0
- package/src/stores/variant/CubeJsonParse.ts +75 -0
- package/src/stores/variant/CubeJsonSampling.ts +90 -0
- package/src/stores/variant/CubeJsonShape.ts +279 -0
- package/src/stores/variant/CubeJsonViewerState.ts +59 -0
- package/tsconfig.json +22 -1
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
// A Variant cell's JSON document (PLAN §11.20), as the engine sends it: its
|
|
18
|
+
// compact text. Read here with each number kept as written, so `1` and `1.0`
|
|
19
|
+
// stay apart and an id past 2^53 keeps its digits, and each object's keys in
|
|
20
|
+
// the order written (a JS object would put keys that look like integers first).
|
|
21
|
+
|
|
22
|
+
/** A JSON value, numbers kept as written and keys in their order */
|
|
23
|
+
export type CubeJsonNode =
|
|
24
|
+
| {
|
|
25
|
+
readonly kind: 'object';
|
|
26
|
+
readonly entries: readonly (readonly [string, CubeJsonNode])[];
|
|
27
|
+
}
|
|
28
|
+
| { readonly kind: 'array'; readonly items: readonly CubeJsonNode[] }
|
|
29
|
+
| { readonly kind: 'number'; readonly text: string }
|
|
30
|
+
| { readonly kind: 'string'; readonly value: string }
|
|
31
|
+
| { readonly kind: 'boolean'; readonly value: boolean }
|
|
32
|
+
| { readonly kind: 'null' };
|
|
33
|
+
|
|
34
|
+
const WHITESPACE = new Set([' ', '\t', '\n', '\r']);
|
|
35
|
+
const NUMBER = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/uy;
|
|
36
|
+
|
|
37
|
+
/** A JSON document; throws a `SyntaxError` on text that isn't one */
|
|
38
|
+
export const parseCubeJson = (text: string): CubeJsonNode => {
|
|
39
|
+
let at = 0;
|
|
40
|
+
const fail = (what: string): never => {
|
|
41
|
+
throw new SyntaxError(`${what} at ${at} in JSON`);
|
|
42
|
+
};
|
|
43
|
+
const skipWhitespace = (): void => {
|
|
44
|
+
while (at < text.length && WHITESPACE.has(text.charAt(at))) {
|
|
45
|
+
at += 1;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const readString = (): string => {
|
|
49
|
+
const start = at;
|
|
50
|
+
at += 1;
|
|
51
|
+
while (at < text.length && text.charAt(at) !== '"') {
|
|
52
|
+
at += text.charAt(at) === '\\' ? 2 : 1;
|
|
53
|
+
}
|
|
54
|
+
if (at >= text.length) {
|
|
55
|
+
fail('An unterminated string');
|
|
56
|
+
}
|
|
57
|
+
at += 1;
|
|
58
|
+
// a string loses nothing through JSON.parse: only numbers do
|
|
59
|
+
return JSON.parse(text.slice(start, at)) as string;
|
|
60
|
+
};
|
|
61
|
+
const readValue = (): CubeJsonNode => {
|
|
62
|
+
skipWhitespace();
|
|
63
|
+
const char = text.charAt(at);
|
|
64
|
+
if (char === '{') {
|
|
65
|
+
at += 1;
|
|
66
|
+
const entries: (readonly [string, CubeJsonNode])[] = [];
|
|
67
|
+
skipWhitespace();
|
|
68
|
+
if (text.charAt(at) === '}') {
|
|
69
|
+
at += 1;
|
|
70
|
+
return { kind: 'object', entries };
|
|
71
|
+
}
|
|
72
|
+
for (;;) {
|
|
73
|
+
skipWhitespace();
|
|
74
|
+
if (text.charAt(at) !== '"') {
|
|
75
|
+
fail('A key expected');
|
|
76
|
+
}
|
|
77
|
+
const key = readString();
|
|
78
|
+
skipWhitespace();
|
|
79
|
+
if (text.charAt(at) !== ':') {
|
|
80
|
+
fail('":" expected');
|
|
81
|
+
}
|
|
82
|
+
at += 1;
|
|
83
|
+
entries.push([key, readValue()]);
|
|
84
|
+
skipWhitespace();
|
|
85
|
+
if (text.charAt(at) === ',') {
|
|
86
|
+
at += 1;
|
|
87
|
+
} else if (text.charAt(at) === '}') {
|
|
88
|
+
at += 1;
|
|
89
|
+
return { kind: 'object', entries };
|
|
90
|
+
} else {
|
|
91
|
+
fail('"," or "}" expected');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (char === '[') {
|
|
96
|
+
at += 1;
|
|
97
|
+
const items: CubeJsonNode[] = [];
|
|
98
|
+
skipWhitespace();
|
|
99
|
+
if (text.charAt(at) === ']') {
|
|
100
|
+
at += 1;
|
|
101
|
+
return { kind: 'array', items };
|
|
102
|
+
}
|
|
103
|
+
for (;;) {
|
|
104
|
+
items.push(readValue());
|
|
105
|
+
skipWhitespace();
|
|
106
|
+
if (text.charAt(at) === ',') {
|
|
107
|
+
at += 1;
|
|
108
|
+
} else if (text.charAt(at) === ']') {
|
|
109
|
+
at += 1;
|
|
110
|
+
return { kind: 'array', items };
|
|
111
|
+
} else {
|
|
112
|
+
fail('"," or "]" expected');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (char === '"') {
|
|
117
|
+
return { kind: 'string', value: readString() };
|
|
118
|
+
}
|
|
119
|
+
for (const [word, node] of [
|
|
120
|
+
['true', { kind: 'boolean', value: true }],
|
|
121
|
+
['false', { kind: 'boolean', value: false }],
|
|
122
|
+
['null', { kind: 'null' }],
|
|
123
|
+
] as const) {
|
|
124
|
+
if (text.startsWith(word, at)) {
|
|
125
|
+
at += word.length;
|
|
126
|
+
return node;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
NUMBER.lastIndex = at;
|
|
130
|
+
const number = NUMBER.exec(text);
|
|
131
|
+
if (!number) {
|
|
132
|
+
return fail('A value expected');
|
|
133
|
+
}
|
|
134
|
+
at += number[0].length;
|
|
135
|
+
return { kind: 'number', text: number[0] };
|
|
136
|
+
};
|
|
137
|
+
const value = readValue();
|
|
138
|
+
skipWhitespace();
|
|
139
|
+
if (at < text.length) {
|
|
140
|
+
fail('Text after the value');
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** The document, or `undefined` for text that isn't JSON */
|
|
146
|
+
export const readCubeJson = (text: string): CubeJsonNode | undefined => {
|
|
147
|
+
try {
|
|
148
|
+
return parseCubeJson(text);
|
|
149
|
+
} catch {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A key or a string shown bare when it can't be mistaken for anything else:
|
|
156
|
+
* not empty, none of the notation's punctuation or a line break, no space at
|
|
157
|
+
* either end, and not a number, `true`, `false` or `null`; JSON-quoted
|
|
158
|
+
* otherwise
|
|
159
|
+
*/
|
|
160
|
+
const showText = (text: string): string =>
|
|
161
|
+
text !== '' &&
|
|
162
|
+
!/[,:{}[\]"\n\r\t]/u.test(text) &&
|
|
163
|
+
text === text.trim() &&
|
|
164
|
+
!/^(?:true|false|null|-?\d.*)$/u.test(text)
|
|
165
|
+
? text
|
|
166
|
+
: JSON.stringify(text);
|
|
167
|
+
|
|
168
|
+
const showNode = (node: CubeJsonNode): string => {
|
|
169
|
+
switch (node.kind) {
|
|
170
|
+
case 'object':
|
|
171
|
+
return `{${node.entries.map(([key, value]) => `${showText(key)}: ${showNode(value)}`).join(', ')}}`;
|
|
172
|
+
case 'array':
|
|
173
|
+
return `[${node.items.map(showNode).join(', ')}]`;
|
|
174
|
+
case 'string':
|
|
175
|
+
return showText(node.value);
|
|
176
|
+
case 'number':
|
|
177
|
+
return node.text;
|
|
178
|
+
case 'boolean':
|
|
179
|
+
return String(node.value);
|
|
180
|
+
case 'null':
|
|
181
|
+
return 'null';
|
|
182
|
+
default:
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* A Variant cell on one line, for the eye (PLAN §11.20, as legend-lite's
|
|
189
|
+
* Data Cube shows one): `{name: ann, tier: gold}`, `[a, b]`, numbers as
|
|
190
|
+
* written. Text that isn't JSON is shown as it is; the value itself stays the
|
|
191
|
+
* JSON text, for copying and export.
|
|
192
|
+
*/
|
|
193
|
+
export const formatCubeJsonLine = (text: string): string => {
|
|
194
|
+
const node = readCubeJson(text);
|
|
195
|
+
return node ? showNode(node) : text;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** The document as JSON, indented two spaces, numbers as written */
|
|
199
|
+
export const formatCubeJsonPretty = (node: CubeJsonNode, depth = 0): string => {
|
|
200
|
+
const indent = ' '.repeat(depth + 1);
|
|
201
|
+
const closing = ' '.repeat(depth);
|
|
202
|
+
switch (node.kind) {
|
|
203
|
+
case 'object':
|
|
204
|
+
return node.entries.length === 0
|
|
205
|
+
? '{}'
|
|
206
|
+
: `{\n${node.entries
|
|
207
|
+
.map(
|
|
208
|
+
([key, value]) =>
|
|
209
|
+
`${indent}${JSON.stringify(key)}: ${formatCubeJsonPretty(value, depth + 1)}`,
|
|
210
|
+
)
|
|
211
|
+
.join(',\n')}\n${closing}}`;
|
|
212
|
+
case 'array':
|
|
213
|
+
return node.items.length === 0
|
|
214
|
+
? '[]'
|
|
215
|
+
: `[\n${node.items
|
|
216
|
+
.map((item) => `${indent}${formatCubeJsonPretty(item, depth + 1)}`)
|
|
217
|
+
.join(',\n')}\n${closing}]`;
|
|
218
|
+
case 'string':
|
|
219
|
+
return JSON.stringify(node.value);
|
|
220
|
+
case 'number':
|
|
221
|
+
return node.text;
|
|
222
|
+
case 'boolean':
|
|
223
|
+
return String(node.value);
|
|
224
|
+
case 'null':
|
|
225
|
+
return 'null';
|
|
226
|
+
default:
|
|
227
|
+
return '';
|
|
228
|
+
}
|
|
229
|
+
};
|
|
@@ -0,0 +1,378 @@
|
|
|
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
|
+
type CubeDocument,
|
|
19
|
+
Explode,
|
|
20
|
+
Extend,
|
|
21
|
+
type ExtendColumn,
|
|
22
|
+
getVariantDatabaseProblem,
|
|
23
|
+
type QueryNode,
|
|
24
|
+
type SchemaColumn,
|
|
25
|
+
} from '@finos/legend-cube';
|
|
26
|
+
import { assertErrorThrown, type GeneratorFn } from '@finos/legend-shared';
|
|
27
|
+
import {
|
|
28
|
+
action,
|
|
29
|
+
computed,
|
|
30
|
+
flow,
|
|
31
|
+
flowResult,
|
|
32
|
+
makeObservable,
|
|
33
|
+
observable,
|
|
34
|
+
} from 'mobx';
|
|
35
|
+
import {
|
|
36
|
+
CubeEngineError,
|
|
37
|
+
type CubeParsedExpression,
|
|
38
|
+
} from '../../graph-manager/CubeEngine.js';
|
|
39
|
+
import type { CubeEditorState } from '../CubeEditorState.js';
|
|
40
|
+
import { getCubeGridActionBlocker } from '../CubeGridQuickActions.js';
|
|
41
|
+
import { CubeQueryRunAborted } from '../CubeQueryRun.js';
|
|
42
|
+
import {
|
|
43
|
+
type CubeJsonField,
|
|
44
|
+
type CubeJsonOffer,
|
|
45
|
+
getCubeJsonFields,
|
|
46
|
+
getFreeCubeColumnName,
|
|
47
|
+
} from './CubeJsonOffers.js';
|
|
48
|
+
import { readCubeJsonSample } from './CubeJsonSampling.js';
|
|
49
|
+
import type { CubeJsonSample } from './CubeJsonShape.js';
|
|
50
|
+
|
|
51
|
+
/** A field's path as one key, for ticking */
|
|
52
|
+
const pathKey = (path: readonly string[]): string => JSON.stringify(path);
|
|
53
|
+
|
|
54
|
+
/** A ticked offer, by its field and its key */
|
|
55
|
+
const offerKey = (path: readonly string[], offer: CubeJsonOffer): string =>
|
|
56
|
+
`${pathKey(path)} ${offer.key}`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Explore JSON (PLAN §11.20): a Variant column's documents, sampled through
|
|
60
|
+
* the cube's own query path (the query up to the node that ran, the column
|
|
61
|
+
* alone, its non-empty values), so the sample is listed in Runs and can be
|
|
62
|
+
* stopped; their fields as a tree; and the offers ticked, added after the
|
|
63
|
+
* node that ran as one undo step: an Extend of the columns, then an Explode
|
|
64
|
+
* Array for one row per element of an array. The engine parses each column's
|
|
65
|
+
* text first; the background check types the Extend, as any typed in the
|
|
66
|
+
* editor.
|
|
67
|
+
*/
|
|
68
|
+
export class CubeJsonExplorerState {
|
|
69
|
+
readonly editorState: CubeEditorState;
|
|
70
|
+
|
|
71
|
+
/** The column explored; `undefined` while closed */
|
|
72
|
+
column: SchemaColumn | undefined;
|
|
73
|
+
/** What was read: the first values, or every row */
|
|
74
|
+
sample: CubeJsonSample | undefined;
|
|
75
|
+
isReading = false;
|
|
76
|
+
isAdding = false;
|
|
77
|
+
/** Why the column couldn't be read, or the columns added */
|
|
78
|
+
error: string | undefined;
|
|
79
|
+
search = '';
|
|
80
|
+
/** The field whose offers show */
|
|
81
|
+
selectedPath: readonly string[] = [];
|
|
82
|
+
/** The ticked offers, by field and key */
|
|
83
|
+
ticked = new Map<string, CubeJsonOffer>();
|
|
84
|
+
|
|
85
|
+
/** The cube as it was when the dialog opened, and the node that ran */
|
|
86
|
+
private document: CubeDocument | undefined;
|
|
87
|
+
private afterId: string | undefined;
|
|
88
|
+
private controller: AbortController | undefined;
|
|
89
|
+
|
|
90
|
+
constructor(editorState: CubeEditorState) {
|
|
91
|
+
makeObservable<CubeJsonExplorerState, 'document' | 'afterId'>(this, {
|
|
92
|
+
column: observable.ref,
|
|
93
|
+
sample: observable.ref,
|
|
94
|
+
isReading: observable,
|
|
95
|
+
isAdding: observable,
|
|
96
|
+
error: observable,
|
|
97
|
+
search: observable,
|
|
98
|
+
selectedPath: observable.ref,
|
|
99
|
+
ticked: observable.shallow,
|
|
100
|
+
document: observable.ref,
|
|
101
|
+
afterId: observable,
|
|
102
|
+
isOpen: computed,
|
|
103
|
+
fields: computed,
|
|
104
|
+
selectedField: computed,
|
|
105
|
+
tickedOffers: computed,
|
|
106
|
+
open: flow,
|
|
107
|
+
read: flow,
|
|
108
|
+
add: flow,
|
|
109
|
+
stop: action,
|
|
110
|
+
close: action,
|
|
111
|
+
setSearch: action,
|
|
112
|
+
select: action,
|
|
113
|
+
toggle: action,
|
|
114
|
+
});
|
|
115
|
+
this.editorState = editorState;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get isOpen(): boolean {
|
|
119
|
+
return this.column !== undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The column's fields, from what was read */
|
|
123
|
+
get fields(): CubeJsonField | undefined {
|
|
124
|
+
return this.column && this.sample
|
|
125
|
+
? getCubeJsonFields(this.column.name, this.sample)
|
|
126
|
+
: undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get selectedField(): CubeJsonField | undefined {
|
|
130
|
+
const key = pathKey(this.selectedPath);
|
|
131
|
+
const find = (field: CubeJsonField): CubeJsonField | undefined =>
|
|
132
|
+
pathKey(field.path) === key
|
|
133
|
+
? field
|
|
134
|
+
: field.children.reduce<CubeJsonField | undefined>(
|
|
135
|
+
(found, child) => found ?? find(child),
|
|
136
|
+
undefined,
|
|
137
|
+
);
|
|
138
|
+
return this.fields ? find(this.fields) : undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The offers ticked, in the order ticked */
|
|
142
|
+
get tickedOffers(): CubeJsonOffer[] {
|
|
143
|
+
return [...this.ticked.values()];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
isTicked(path: readonly string[], offer: CubeJsonOffer): boolean {
|
|
147
|
+
return this.ticked.has(offerKey(path, offer));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Why Explore JSON can't be used on the column now: the grid's blockers
|
|
152
|
+
* (stale rows, edits, a run, a read-only cube), or a database without
|
|
153
|
+
* JSON; `undefined` when it can
|
|
154
|
+
*/
|
|
155
|
+
getDisabledReason(): string | undefined {
|
|
156
|
+
const { editorState } = this;
|
|
157
|
+
const blocker = getCubeGridActionBlocker(editorState);
|
|
158
|
+
if (blocker) {
|
|
159
|
+
return blocker;
|
|
160
|
+
}
|
|
161
|
+
const { context, query } = editorState.document;
|
|
162
|
+
const nodeId = query.selected;
|
|
163
|
+
return context?.runtime === undefined || nodeId === undefined
|
|
164
|
+
? undefined
|
|
165
|
+
: getVariantDatabaseProblem(
|
|
166
|
+
editorState.getRunDatabaseType(
|
|
167
|
+
query,
|
|
168
|
+
nodeId,
|
|
169
|
+
context.model,
|
|
170
|
+
context.runtime,
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Opens the dialog on a column of the rows shown, and reads its first values */
|
|
176
|
+
*open(column: SchemaColumn): GeneratorFn<void> {
|
|
177
|
+
const { editorState } = this;
|
|
178
|
+
const { document } = editorState;
|
|
179
|
+
this.stop();
|
|
180
|
+
this.column = column;
|
|
181
|
+
this.document = document;
|
|
182
|
+
this.afterId = document.query.selected;
|
|
183
|
+
this.sample = undefined;
|
|
184
|
+
this.error = undefined;
|
|
185
|
+
this.search = '';
|
|
186
|
+
this.selectedPath = [];
|
|
187
|
+
this.ticked.clear();
|
|
188
|
+
// the database the column's rows run on, which must read JSON: its
|
|
189
|
+
// model's outline may not have been needed by the run that showed them
|
|
190
|
+
const context = document.context;
|
|
191
|
+
const nodeId = this.afterId;
|
|
192
|
+
if (context?.runtime !== undefined && nodeId !== undefined) {
|
|
193
|
+
yield flowResult(editorState.loadModelOutline(context.model));
|
|
194
|
+
const problem = getVariantDatabaseProblem(
|
|
195
|
+
editorState.getRunDatabaseType(
|
|
196
|
+
document.query,
|
|
197
|
+
nodeId,
|
|
198
|
+
context.model,
|
|
199
|
+
context.runtime,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
if (problem) {
|
|
203
|
+
this.error = problem;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
yield flowResult(this.read(false));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Reads the column's first values, or every row: either replaces what was read */
|
|
211
|
+
*read(everyRow: boolean): GeneratorFn<void> {
|
|
212
|
+
const { editorState, column, document, afterId } = this;
|
|
213
|
+
if (
|
|
214
|
+
!column ||
|
|
215
|
+
!document ||
|
|
216
|
+
afterId === undefined ||
|
|
217
|
+
document.context?.runtime === undefined ||
|
|
218
|
+
this.isReading
|
|
219
|
+
) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
this.controller = controller;
|
|
224
|
+
this.isReading = true;
|
|
225
|
+
this.error = undefined;
|
|
226
|
+
try {
|
|
227
|
+
const sample = (yield readCubeJsonSample(editorState, {
|
|
228
|
+
document,
|
|
229
|
+
afterId,
|
|
230
|
+
column,
|
|
231
|
+
everyRow,
|
|
232
|
+
abortController: controller,
|
|
233
|
+
})) as CubeJsonSample | undefined;
|
|
234
|
+
if (this.controller !== controller) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.sample = sample;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (
|
|
240
|
+
this.controller !== controller ||
|
|
241
|
+
error instanceof CubeQueryRunAborted
|
|
242
|
+
) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
assertErrorThrown(error);
|
|
246
|
+
this.error =
|
|
247
|
+
error instanceof CubeEngineError ? error.firstLine : error.message;
|
|
248
|
+
} finally {
|
|
249
|
+
if (this.controller === controller) {
|
|
250
|
+
this.controller = undefined;
|
|
251
|
+
this.isReading = false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Stops a read in flight, keeping what was read before it */
|
|
257
|
+
stop(): void {
|
|
258
|
+
this.controller?.abort();
|
|
259
|
+
this.controller = undefined;
|
|
260
|
+
this.isReading = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
setSearch(search: string): void {
|
|
264
|
+
this.search = search;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
select(path: readonly string[]): void {
|
|
268
|
+
this.selectedPath = path;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Ticks or unticks an offer. One row per element can be ticked for one
|
|
273
|
+
* array at a time: two would give a row for each pair of their elements.
|
|
274
|
+
*/
|
|
275
|
+
toggle(path: readonly string[], offer: CubeJsonOffer): void {
|
|
276
|
+
const key = offerKey(path, offer);
|
|
277
|
+
if (this.ticked.has(key)) {
|
|
278
|
+
this.ticked.delete(key);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (offer.explode) {
|
|
282
|
+
[...this.ticked].forEach(([other, ticked]) => {
|
|
283
|
+
if (ticked.explode) {
|
|
284
|
+
this.ticked.delete(other);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
this.ticked.set(key, offer);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Adds the ticked offers after the node that ran, one undo step: an Extend
|
|
293
|
+
* of the columns, each under its default name or the next free one, then
|
|
294
|
+
* an Explode Array for the array ticked for one row per element, its
|
|
295
|
+
* element named the same way. The engine parses each column's text first,
|
|
296
|
+
* and the dialog stays open with the error if it can't. Nothing is added if
|
|
297
|
+
* the cube changed meanwhile.
|
|
298
|
+
*/
|
|
299
|
+
*add(): GeneratorFn<void> {
|
|
300
|
+
const { editorState, document, afterId } = this;
|
|
301
|
+
const ticked = this.tickedOffers;
|
|
302
|
+
const offers = ticked.filter(
|
|
303
|
+
(offer): offer is CubeJsonOffer & { code: string } =>
|
|
304
|
+
offer.code !== undefined,
|
|
305
|
+
);
|
|
306
|
+
const exploded = ticked.find((offer) => offer.explode !== undefined);
|
|
307
|
+
if (
|
|
308
|
+
!document ||
|
|
309
|
+
afterId === undefined ||
|
|
310
|
+
(!offers.length && !exploded) ||
|
|
311
|
+
this.isAdding ||
|
|
312
|
+
editorState.document !== document
|
|
313
|
+
) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this.isAdding = true;
|
|
317
|
+
this.error = undefined;
|
|
318
|
+
try {
|
|
319
|
+
const { query } = document;
|
|
320
|
+
const extendId = query.generateId(Extend.TYPE);
|
|
321
|
+
const taken = (
|
|
322
|
+
editorState.analysis.schemas.get(afterId)?.columns ?? []
|
|
323
|
+
).map((column) => column.name);
|
|
324
|
+
const columns: ExtendColumn[] = [];
|
|
325
|
+
for (const [index, offer] of offers.entries()) {
|
|
326
|
+
const name = getFreeCubeColumnName(offer.name, [
|
|
327
|
+
...taken,
|
|
328
|
+
...columns.map((column) => column.name),
|
|
329
|
+
]);
|
|
330
|
+
const parsed = (yield editorState.host.engine.parseExpression(
|
|
331
|
+
offer.code,
|
|
332
|
+
`${extendId}:${index}`,
|
|
333
|
+
)) as CubeParsedExpression;
|
|
334
|
+
columns.push({ name, code: offer.code, lambda: parsed.lambda });
|
|
335
|
+
}
|
|
336
|
+
// as the dialog found it: nothing changed while the engine parsed
|
|
337
|
+
if (editorState.document !== document) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const nodes: QueryNode[] = columns.length
|
|
341
|
+
? [new Extend(extendId, columns)]
|
|
342
|
+
: [];
|
|
343
|
+
if (exploded?.explode) {
|
|
344
|
+
nodes.push(
|
|
345
|
+
new Explode(
|
|
346
|
+
query.generateId(Explode.TYPE),
|
|
347
|
+
exploded.explode.column,
|
|
348
|
+
exploded.explode.path,
|
|
349
|
+
getFreeCubeColumnName(exploded.name, [
|
|
350
|
+
...taken,
|
|
351
|
+
...columns.map((column) => column.name),
|
|
352
|
+
]),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
editorState.addConfiguredNodes(nodes, afterId);
|
|
357
|
+
this.close();
|
|
358
|
+
} catch (error) {
|
|
359
|
+
assertErrorThrown(error);
|
|
360
|
+
this.error =
|
|
361
|
+
error instanceof CubeEngineError ? error.firstLine : error.message;
|
|
362
|
+
} finally {
|
|
363
|
+
this.isAdding = false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
close(): void {
|
|
368
|
+
this.stop();
|
|
369
|
+
this.column = undefined;
|
|
370
|
+
this.sample = undefined;
|
|
371
|
+
this.document = undefined;
|
|
372
|
+
this.afterId = undefined;
|
|
373
|
+
this.error = undefined;
|
|
374
|
+
this.search = '';
|
|
375
|
+
this.selectedPath = [];
|
|
376
|
+
this.ticked.clear();
|
|
377
|
+
}
|
|
378
|
+
}
|