@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
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
UNSAVED_CUBE_NAME,
|
|
38
38
|
} from '../__lib__/LegendCubeLabels.js';
|
|
39
39
|
import { LEGEND_CUBE_TEST_ID } from '../__lib__/LegendCubeTesting.js';
|
|
40
|
+
import { CUBE_TOUR_ANCHOR, cubeTourAnchor } from '../__lib__/LegendCubeTour.js';
|
|
40
41
|
import { CubeEditorState } from '../stores/CubeEditorState.js';
|
|
41
42
|
import {
|
|
42
43
|
type CubeEntrySource,
|
|
@@ -53,11 +54,14 @@ import { CubeGridRegion } from './grid/CubeGridRegion.js';
|
|
|
53
54
|
import { CubePalette } from './palette/CubePalette.js';
|
|
54
55
|
import { CubeDevSettingsDialog } from './dev-settings/CubeDevSettings.js';
|
|
55
56
|
import { CubeShowPureDialog } from './show-pure/CubeShowPureDialog.js';
|
|
57
|
+
import { CubeJsonViewerDialog } from './grid/CubeJsonViewerDialog.js';
|
|
58
|
+
import { CubeJsonExplorerDialog } from './grid/CubeJsonExplorerDialog.js';
|
|
56
59
|
import { CubeExamplesDialog } from './examples/CubeExamplesDialog.js';
|
|
57
60
|
import { CubeFileMenu } from './CubeFileMenu.js';
|
|
58
61
|
import { CubeSourcePicker } from './source-picker/CubeSourcePicker.js';
|
|
59
62
|
import { CubeSpecTransferDialog } from './spec-transfer/CubeSpecTransferDialog.js';
|
|
60
63
|
import { CubeImportPureDialog } from './spec-transfer/CubeImportPureDialog.js';
|
|
64
|
+
import { CubeTour } from './tour/CubeTour.js';
|
|
61
65
|
|
|
62
66
|
/** The graph never takes more of the window's height than this, so the results stay in view (spec §17.3) */
|
|
63
67
|
const MAX_GRAPH_SHARE_OF_WINDOW = 0.6;
|
|
@@ -115,12 +119,14 @@ const CubeGraphHeader = observer(
|
|
|
115
119
|
{isCubeSampleDataOffered(editorState.host) && (
|
|
116
120
|
<CubeButton
|
|
117
121
|
title="Open an example cube, or start one on sample data"
|
|
122
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.EXAMPLES)}
|
|
118
123
|
onClick={() => editorState.examples.open()}
|
|
119
124
|
>
|
|
120
125
|
Examples
|
|
121
126
|
</CubeButton>
|
|
122
127
|
)}
|
|
123
128
|
<CubeButton
|
|
129
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
|
|
124
130
|
title={
|
|
125
131
|
readOnly
|
|
126
132
|
? READ_ONLY_CUBE_TITLE
|
|
@@ -132,6 +138,7 @@ const CubeGraphHeader = observer(
|
|
|
132
138
|
Undo
|
|
133
139
|
</CubeButton>
|
|
134
140
|
<CubeButton
|
|
141
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
|
|
135
142
|
title={`Redo the last change undone (${REDO_SHORTCUT_LABEL})`}
|
|
136
143
|
disabled={!editorState.canRedo}
|
|
137
144
|
onClick={() => editorState.redoEdited()}
|
|
@@ -139,6 +146,7 @@ const CubeGraphHeader = observer(
|
|
|
139
146
|
Redo
|
|
140
147
|
</CubeButton>
|
|
141
148
|
<CubeButton
|
|
149
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
|
|
142
150
|
title={
|
|
143
151
|
editorState.execution.canExecute
|
|
144
152
|
? 'Show the Pure query that Execute runs'
|
|
@@ -158,6 +166,7 @@ const CubeGraphHeader = observer(
|
|
|
158
166
|
Show Pure
|
|
159
167
|
</CubeButton>
|
|
160
168
|
<CubeButton
|
|
169
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.VIEW)}
|
|
161
170
|
title={
|
|
162
171
|
showGraph
|
|
163
172
|
? 'Hide the graph, leaving more room for the results'
|
|
@@ -206,6 +215,8 @@ const CubeGraphHeader = observer(
|
|
|
206
215
|
<CubeSpecTransferDialog editorState={editorState} />
|
|
207
216
|
<CubeImportPureDialog editorState={editorState} />
|
|
208
217
|
<CubeShowPureDialog editorState={editorState} />
|
|
218
|
+
<CubeJsonViewerDialog editorState={editorState} />
|
|
219
|
+
<CubeJsonExplorerDialog editorState={editorState} />
|
|
209
220
|
<CubeDevSettingsDialog editorState={editorState} />
|
|
210
221
|
</div>
|
|
211
222
|
);
|
|
@@ -260,9 +271,12 @@ export const CubeEditor = observer(
|
|
|
260
271
|
// hiding the graph collapses its panel: the header and the results stay
|
|
261
272
|
// where they are, so the grid keeps its state
|
|
262
273
|
const graphPanel = getCollapsiblePanelGroupProps(!showGraph);
|
|
274
|
+
// the page, whose controls the tour points at
|
|
275
|
+
const [page, setPage] = useState<HTMLDivElement | null>(null);
|
|
263
276
|
|
|
264
277
|
return (
|
|
265
278
|
<div
|
|
279
|
+
ref={setPage}
|
|
266
280
|
className="legend-cube flex bg-[var(--color-bg-app)] text-[var(--color-text-primary)]"
|
|
267
281
|
data-testid={LEGEND_CUBE_TEST_ID.EDITOR}
|
|
268
282
|
>
|
|
@@ -288,6 +302,7 @@ export const CubeEditor = observer(
|
|
|
288
302
|
</ResizablePanel>
|
|
289
303
|
</ResizablePanelGroup>
|
|
290
304
|
</div>
|
|
305
|
+
<CubeTour editorState={editorState} root={page} />
|
|
291
306
|
</div>
|
|
292
307
|
);
|
|
293
308
|
},
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '@finos/legend-art';
|
|
24
24
|
import { observer } from 'mobx-react-lite';
|
|
25
25
|
import { READ_ONLY_CUBE_TITLE } from '../__lib__/LegendCubeLabels.js';
|
|
26
|
+
import { CUBE_TOUR_ANCHOR, cubeTourAnchor } from '../__lib__/LegendCubeTour.js';
|
|
26
27
|
import type { CubeEditorState } from '../stores/CubeEditorState.js';
|
|
27
28
|
|
|
28
29
|
interface CubeFileMenuItem {
|
|
@@ -83,6 +84,7 @@ export const CubeFileMenu = observer(
|
|
|
83
84
|
type="button"
|
|
84
85
|
className="flex h-6 shrink-0 items-center gap-1 rounded-sm border border-[var(--color-border-default)] bg-[var(--color-bg-elevated)] px-2 text-base text-[var(--color-text-primary)] enabled:cursor-pointer enabled:hover:bg-[var(--color-bg-hover)]"
|
|
85
86
|
title="New, Import, Import Pure and Export"
|
|
87
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.CUBE_MENU)}
|
|
86
88
|
aria-haspopup="menu"
|
|
87
89
|
aria-expanded={isOpen}
|
|
88
90
|
onClick={openMenu}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
AlignMiddleIcon,
|
|
20
20
|
AlignTopIcon,
|
|
21
21
|
ArrowsJoinIcon,
|
|
22
|
+
ArrowsSplitIcon,
|
|
22
23
|
CalculatorIcon,
|
|
23
24
|
CodeIcon,
|
|
24
25
|
CompareIcon,
|
|
@@ -53,6 +54,7 @@ const NODE_ICONS: Readonly<
|
|
|
53
54
|
concat: LayerGroupIcon,
|
|
54
55
|
difference: CompareIcon,
|
|
55
56
|
extend: CalculatorIcon,
|
|
57
|
+
explode: ArrowsSplitIcon,
|
|
56
58
|
limit: AlignTopIcon,
|
|
57
59
|
drop: AlignBottomIcon,
|
|
58
60
|
slice: AlignMiddleIcon,
|
|
@@ -36,6 +36,11 @@ import { observer } from 'mobx-react-lite';
|
|
|
36
36
|
import { useEffect, useMemo, useRef } from 'react';
|
|
37
37
|
import { useDrop } from 'react-dnd';
|
|
38
38
|
import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
|
|
39
|
+
import {
|
|
40
|
+
CUBE_TOUR_ANCHOR,
|
|
41
|
+
CUBE_TOUR_LABEL,
|
|
42
|
+
cubeTourAnchor,
|
|
43
|
+
} from '../../__lib__/LegendCubeTour.js';
|
|
39
44
|
import { canAddCubeNode } from '../../stores/CubeAddPlacement.js';
|
|
40
45
|
import type { CubeEditorState } from '../../stores/CubeEditorState.js';
|
|
41
46
|
import { isCubeSampleDataOffered } from '../../stores/CubeHost.js';
|
|
@@ -284,6 +289,7 @@ export const CubeCanvas = observer(
|
|
|
284
289
|
ref={ref}
|
|
285
290
|
className="relative h-full w-full"
|
|
286
291
|
data-testid={LEGEND_CUBE_TEST_ID.CANVAS}
|
|
292
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.CANVAS)}
|
|
287
293
|
>
|
|
288
294
|
<ContextMenu
|
|
289
295
|
className="h-full w-full"
|
|
@@ -293,7 +299,7 @@ export const CubeCanvas = observer(
|
|
|
293
299
|
menuProps={{ elevation: 7 }}
|
|
294
300
|
>
|
|
295
301
|
{editorState.document.query.isEmpty ? (
|
|
296
|
-
<div className="flex h-full items-center justify-center p-4 text-base text-[var(--color-text-secondary)]">
|
|
302
|
+
<div className="flex h-full flex-col items-center justify-center gap-2 p-4 text-base text-[var(--color-text-secondary)]">
|
|
297
303
|
<span>
|
|
298
304
|
<button
|
|
299
305
|
className="text-[var(--color-accent)] underline disabled:text-[var(--color-text-disabled)] disabled:no-underline"
|
|
@@ -321,6 +327,16 @@ export const CubeCanvas = observer(
|
|
|
321
327
|
' to start a new one.'
|
|
322
328
|
)}
|
|
323
329
|
</span>
|
|
330
|
+
<span>
|
|
331
|
+
{CUBE_TOUR_LABEL.OFFER}{' '}
|
|
332
|
+
<button
|
|
333
|
+
className="text-[var(--color-accent)] underline"
|
|
334
|
+
onClick={() => editorState.tour.start()}
|
|
335
|
+
>
|
|
336
|
+
{CUBE_TOUR_LABEL.START}
|
|
337
|
+
</button>
|
|
338
|
+
.
|
|
339
|
+
</span>
|
|
324
340
|
</div>
|
|
325
341
|
) : (
|
|
326
342
|
<CubeCanvasContext.Provider value={editorState}>
|
|
@@ -21,6 +21,10 @@ import { useRef } from 'react';
|
|
|
21
21
|
import { RUNNING_NODE_LABEL } from '../../__lib__/LegendCubeHelpText.js';
|
|
22
22
|
import { useDrag, useDrop } from 'react-dnd';
|
|
23
23
|
import { LEGEND_CUBE_TEST_ID } from '../../__lib__/LegendCubeTesting.js';
|
|
24
|
+
import {
|
|
25
|
+
CUBE_TOUR_ANCHOR,
|
|
26
|
+
cubeTourAnchor,
|
|
27
|
+
} from '../../__lib__/LegendCubeTour.js';
|
|
24
28
|
import { CubeNodeIcon } from '../CubeNodeIcon.js';
|
|
25
29
|
import {
|
|
26
30
|
CUBE_OUTPUT_HANDLE_ID,
|
|
@@ -144,6 +148,9 @@ export const CubeCanvasNode = observer(
|
|
|
144
148
|
aria-description={status.tooltip}
|
|
145
149
|
aria-current={status.isCapture}
|
|
146
150
|
data-testid={LEGEND_CUBE_TEST_ID.CANVAS_NODE}
|
|
151
|
+
{...(status.isCapture
|
|
152
|
+
? cubeTourAnchor(CUBE_TOUR_ANCHOR.RUNNING_STEP)
|
|
153
|
+
: {})}
|
|
147
154
|
>
|
|
148
155
|
<CubeNodeIcon
|
|
149
156
|
icon={editorState.registry.get(node.type)?.icon}
|
|
@@ -30,6 +30,11 @@ import {
|
|
|
30
30
|
useDropdownMenu,
|
|
31
31
|
} from '@finos/legend-art';
|
|
32
32
|
import { observer } from 'mobx-react-lite';
|
|
33
|
+
import {
|
|
34
|
+
CUBE_TOUR_ANCHOR,
|
|
35
|
+
CUBE_TOUR_LABEL,
|
|
36
|
+
cubeTourAnchor,
|
|
37
|
+
} from '../../__lib__/LegendCubeTour.js';
|
|
33
38
|
import type { CubeEditorState } from '../../stores/CubeEditorState.js';
|
|
34
39
|
|
|
35
40
|
/** Why Dev Settings can't change anything */
|
|
@@ -37,8 +42,8 @@ const DEV_SETTINGS_UNAVAILABLE =
|
|
|
37
42
|
"This Legend deployment doesn't let Legend Cube's engine clients be changed.";
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
|
-
* The page's menu, at the top left, as Legend Query's editor has one:
|
|
41
|
-
* Settings (PLAN §11.16)
|
|
45
|
+
* The page's menu, at the top left, as Legend Query's editor has one: the
|
|
46
|
+
* tour (PLAN §11.21) and Dev Settings (PLAN §11.16)
|
|
42
47
|
*/
|
|
43
48
|
export const CubePageMenu = observer(
|
|
44
49
|
(props: { editorState: CubeEditorState }) => {
|
|
@@ -50,6 +55,7 @@ export const CubePageMenu = observer(
|
|
|
50
55
|
type="button"
|
|
51
56
|
className="flex h-6 w-6 shrink-0 items-center justify-center text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"
|
|
52
57
|
title="Menu"
|
|
58
|
+
{...cubeTourAnchor(CUBE_TOUR_ANCHOR.PAGE_MENU)}
|
|
53
59
|
aria-haspopup="menu"
|
|
54
60
|
aria-expanded={isOpen}
|
|
55
61
|
onClick={openMenu}
|
|
@@ -65,6 +71,15 @@ export const CubePageMenu = observer(
|
|
|
65
71
|
},
|
|
66
72
|
}}
|
|
67
73
|
>
|
|
74
|
+
<DropdownMenuItem
|
|
75
|
+
className="px-2 text-base"
|
|
76
|
+
onClick={() => {
|
|
77
|
+
closeMenu();
|
|
78
|
+
editorState.tour.start();
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{CUBE_TOUR_LABEL.START}
|
|
82
|
+
</DropdownMenuItem>
|
|
68
83
|
<DropdownMenuItem
|
|
69
84
|
className="px-2 text-base"
|
|
70
85
|
onClick={() => {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
HashtagIcon,
|
|
21
21
|
ListIcon,
|
|
22
22
|
QuestionIcon,
|
|
23
|
+
SitemapIcon,
|
|
23
24
|
StringTypeIcon,
|
|
24
25
|
ToggleIcon,
|
|
25
26
|
} from '@finos/legend-art';
|
|
@@ -47,6 +48,8 @@ const FAMILY_ICONS: Readonly<
|
|
|
47
48
|
[TypeFamily.DATETIME]: CalendarIcon,
|
|
48
49
|
[TypeFamily.DATE]: CalendarIcon,
|
|
49
50
|
[TypeFamily.ENUM]: ListIcon,
|
|
51
|
+
// a JSON document: its fields as a tree (PLAN §11.20)
|
|
52
|
+
[TypeFamily.VARIANT]: SitemapIcon,
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
/** The icon of a column type's family: text, number, date, ...; a question mark for the rest */
|
|
@@ -0,0 +1,207 @@
|
|
|
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 { clsx, TimesIcon } from '@finos/legend-art';
|
|
18
|
+
import { type Schema, TypeFamily } from '@finos/legend-cube';
|
|
19
|
+
import { guaranteeType } from '@finos/legend-shared';
|
|
20
|
+
import { flowResult } from 'mobx';
|
|
21
|
+
import { observer } from 'mobx-react-lite';
|
|
22
|
+
import {
|
|
23
|
+
COLUMN_NAME_RULES_HINT,
|
|
24
|
+
EXPLODE_COLUMN_DISABLED_REASON,
|
|
25
|
+
EXPLODE_EDITOR_NOTES,
|
|
26
|
+
EXPLODE_NO_ARRAYS_NOTICE,
|
|
27
|
+
} from '../../__lib__/LegendCubeLabels.js';
|
|
28
|
+
import { CubeExplodeDraft } from '../../stores/editors/CubeExplodeDraft.js';
|
|
29
|
+
import { CubeButton } from '../CubeButton.js';
|
|
30
|
+
import { CubeColumnPicker } from './CubeColumnPicker.js';
|
|
31
|
+
import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
|
|
32
|
+
|
|
33
|
+
const FIELD =
|
|
34
|
+
'h-6 min-w-0 flex-1 rounded-sm border border-[var(--color-border-default)] bg-[var(--color-bg-input)] px-1 text-base';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The Explode Array editor (PLAN §11.20): the Variant column; the keys to its
|
|
38
|
+
* array, typed or picked from the arrays a sample of the column holds; and
|
|
39
|
+
* the new column's name, which follows the keys until typed. The node's
|
|
40
|
+
* problems show under it, as for any node.
|
|
41
|
+
*/
|
|
42
|
+
export const CubeExplodeEditor = observer((props: CubeNodeEditorProps) => {
|
|
43
|
+
const { readOnly } = props;
|
|
44
|
+
const draft = guaranteeType(props.draft, CubeExplodeDraft);
|
|
45
|
+
const [schema] = props.inputSchemas as [Schema];
|
|
46
|
+
const { arrays } = draft;
|
|
47
|
+
const findArrays = (): void => {
|
|
48
|
+
flowResult(draft.readSample()).catch(
|
|
49
|
+
draft.editorState.host.applicationStore.alertUnhandledError,
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex flex-col gap-2 text-base">
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<span className="w-16 shrink-0">Column</span>
|
|
56
|
+
<CubeColumnPicker
|
|
57
|
+
label="Explode column"
|
|
58
|
+
schema={schema}
|
|
59
|
+
value={draft.column}
|
|
60
|
+
disabled={readOnly}
|
|
61
|
+
onChange={(name) => draft.setColumn(name)}
|
|
62
|
+
isColumnDisabled={(column) =>
|
|
63
|
+
column.type.family === TypeFamily.VARIANT
|
|
64
|
+
? undefined
|
|
65
|
+
: EXPLODE_COLUMN_DISABLED_REASON.NOT_JSON
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="flex flex-col gap-1">
|
|
70
|
+
<div className="flex items-center gap-2">
|
|
71
|
+
<span className="w-16 shrink-0">Array</span>
|
|
72
|
+
<span className="min-w-0 flex-1 break-all font-mono text-sm text-[var(--color-text-secondary)]">
|
|
73
|
+
{[draft.column || '…', ...draft.path].join(' › ')}
|
|
74
|
+
</span>
|
|
75
|
+
<CubeButton
|
|
76
|
+
title={
|
|
77
|
+
draft.sampleProblem ??
|
|
78
|
+
"Read the column's first values, and list the arrays they hold"
|
|
79
|
+
}
|
|
80
|
+
disabled={
|
|
81
|
+
readOnly ||
|
|
82
|
+
!draft.column ||
|
|
83
|
+
draft.isSampling ||
|
|
84
|
+
draft.sampleProblem !== undefined
|
|
85
|
+
}
|
|
86
|
+
onClick={findArrays}
|
|
87
|
+
>
|
|
88
|
+
{draft.isSampling ? 'Reading…' : 'Find arrays'}
|
|
89
|
+
</CubeButton>
|
|
90
|
+
</div>
|
|
91
|
+
{draft.sampleError && (
|
|
92
|
+
<div className="break-words text-sm text-[var(--color-status-error)]">
|
|
93
|
+
{draft.sampleError}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
{arrays && arrays.length === 0 && (
|
|
97
|
+
<div className="text-sm text-[var(--color-text-secondary)]">
|
|
98
|
+
{EXPLODE_NO_ARRAYS_NOTICE}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
{arrays && arrays.length > 0 && (
|
|
102
|
+
<ul
|
|
103
|
+
aria-label="Arrays found"
|
|
104
|
+
className="rounded-sm border border-[var(--color-border-subtle)]"
|
|
105
|
+
>
|
|
106
|
+
{arrays.map((array) => {
|
|
107
|
+
const picked =
|
|
108
|
+
array.path.length === draft.path.length &&
|
|
109
|
+
array.path.every((key, index) => key === draft.path[index]);
|
|
110
|
+
const label = [draft.column, ...array.path].join(' › ');
|
|
111
|
+
const share = Math.round(array.presence * 100);
|
|
112
|
+
return (
|
|
113
|
+
<li key={JSON.stringify(array.path)}>
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
aria-label={`Array ${label}`}
|
|
117
|
+
aria-pressed={picked}
|
|
118
|
+
disabled={readOnly}
|
|
119
|
+
className={clsx(
|
|
120
|
+
'flex w-full items-baseline gap-2 px-2 py-0.5 text-left enabled:hover:bg-[var(--color-bg-hover)]',
|
|
121
|
+
picked && 'bg-[var(--color-bg-selected)]',
|
|
122
|
+
)}
|
|
123
|
+
onClick={() => draft.pickArray(array.path)}
|
|
124
|
+
>
|
|
125
|
+
<span className="min-w-0 break-all font-mono text-sm">
|
|
126
|
+
{label}
|
|
127
|
+
</span>
|
|
128
|
+
<span className="shrink-0 text-xs text-[var(--color-text-muted)]">
|
|
129
|
+
{array.description}
|
|
130
|
+
{share < 100 ? ` · ${share}%` : ''}
|
|
131
|
+
</span>
|
|
132
|
+
</button>
|
|
133
|
+
</li>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
</ul>
|
|
137
|
+
)}
|
|
138
|
+
<ul aria-label="Path keys" className="flex flex-col gap-1">
|
|
139
|
+
{draft.path.map((key, index) => {
|
|
140
|
+
const position = index + 1;
|
|
141
|
+
return (
|
|
142
|
+
// keys are edited in place: their position is their identity
|
|
143
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
144
|
+
<li key={index} className="flex items-center gap-1">
|
|
145
|
+
<span className="w-16 shrink-0 text-sm text-[var(--color-text-secondary)]">
|
|
146
|
+
{`Key ${position}`}
|
|
147
|
+
</span>
|
|
148
|
+
<input
|
|
149
|
+
aria-label={`Path key ${position}`}
|
|
150
|
+
aria-invalid={key === ''}
|
|
151
|
+
className={clsx(
|
|
152
|
+
FIELD,
|
|
153
|
+
key === '' && 'border-[var(--color-status-error)]',
|
|
154
|
+
)}
|
|
155
|
+
type="text"
|
|
156
|
+
spellCheck={false}
|
|
157
|
+
value={key}
|
|
158
|
+
disabled={readOnly}
|
|
159
|
+
onChange={(event) => draft.setKey(index, event.target.value)}
|
|
160
|
+
/>
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
className="flex h-6 w-6 items-center justify-center text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] disabled:text-[var(--color-text-disabled)]"
|
|
164
|
+
aria-label={`Remove path key ${position}`}
|
|
165
|
+
title="Remove this key"
|
|
166
|
+
disabled={readOnly}
|
|
167
|
+
onClick={() => draft.removeKey(index)}
|
|
168
|
+
>
|
|
169
|
+
<TimesIcon />
|
|
170
|
+
</button>
|
|
171
|
+
</li>
|
|
172
|
+
);
|
|
173
|
+
})}
|
|
174
|
+
</ul>
|
|
175
|
+
<div>
|
|
176
|
+
<CubeButton
|
|
177
|
+
title="Add a key: the field of the object the keys before it reach"
|
|
178
|
+
disabled={readOnly || !draft.column}
|
|
179
|
+
onClick={() => draft.addKey()}
|
|
180
|
+
>
|
|
181
|
+
Add key
|
|
182
|
+
</CubeButton>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div className="flex items-center gap-2">
|
|
186
|
+
<span className="w-16 shrink-0">Into</span>
|
|
187
|
+
<input
|
|
188
|
+
aria-label="New column name"
|
|
189
|
+
className={FIELD}
|
|
190
|
+
type="text"
|
|
191
|
+
spellCheck={false}
|
|
192
|
+
value={draft.name}
|
|
193
|
+
disabled={readOnly}
|
|
194
|
+
onChange={(event) => draft.setName(event.target.value)}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<div className="text-sm text-[var(--color-text-secondary)]">
|
|
198
|
+
{COLUMN_NAME_RULES_HINT}
|
|
199
|
+
</div>
|
|
200
|
+
<ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
|
|
201
|
+
{EXPLODE_EDITOR_NOTES.map((note) => (
|
|
202
|
+
<li key={note}>{note}</li>
|
|
203
|
+
))}
|
|
204
|
+
</ul>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
});
|
|
@@ -33,7 +33,12 @@ import {
|
|
|
33
33
|
} from '../../stores/editors/CubeExpressionFilterDraft.js';
|
|
34
34
|
import { CubeButton } from '../CubeButton.js';
|
|
35
35
|
import { CubeColumnTypeIcon } from './CubeColumnPicker.js';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
CubeJsonFieldsToggle,
|
|
38
|
+
CubeJsonFieldsTree,
|
|
39
|
+
isCubeJsonColumn,
|
|
40
|
+
} from './CubeJsonFieldsTree.js';
|
|
41
|
+
import { columnAccessOf } from '../../stores/CubePureText.js';
|
|
37
42
|
import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
|
|
38
43
|
|
|
39
44
|
/** The code editor's language: legend-code-editor's `CODE_EDITOR_LANGUAGE.PURE` */
|
|
@@ -108,6 +113,11 @@ export const CubeExpressionFilterEditor = observer(
|
|
|
108
113
|
const [schema] = inputSchemas as [Schema];
|
|
109
114
|
const [expanded, setExpanded] = useState(false);
|
|
110
115
|
const disabled = readOnly || draft.validating;
|
|
116
|
+
/** Writes text at the end of the condition, as the chips do */
|
|
117
|
+
const write = (text: string): void => {
|
|
118
|
+
const separator = /\s$/u.test(draft.code) ? '' : ' ';
|
|
119
|
+
draft.setCode(`${draft.code}${separator}${text}`);
|
|
120
|
+
};
|
|
111
121
|
return (
|
|
112
122
|
<div className="flex flex-col gap-2 text-base">
|
|
113
123
|
<div className="flex items-center gap-1">
|
|
@@ -164,24 +174,31 @@ export const CubeExpressionFilterEditor = observer(
|
|
|
164
174
|
<span>Input columns</span>
|
|
165
175
|
<ul aria-label="Input columns" className="flex flex-wrap gap-1">
|
|
166
176
|
{schema.columns.map((column) => (
|
|
167
|
-
<li key={column.name}>
|
|
177
|
+
<li key={column.name} className="flex items-stretch gap-0.5">
|
|
168
178
|
<button
|
|
169
179
|
className="flex items-center gap-1 rounded-sm border border-[var(--color-border-subtle)] px-1 text-sm hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
|
|
170
180
|
title={`Write ${columnAccessOf(column.name)} at the end of the condition (${getExactColumnTypeLabel(column)})`}
|
|
171
181
|
disabled={disabled}
|
|
172
|
-
onClick={() =>
|
|
173
|
-
const separator = /\s$/u.test(draft.code) ? '' : ' ';
|
|
174
|
-
draft.setCode(
|
|
175
|
-
`${draft.code}${separator}${columnAccessOf(column.name)}`,
|
|
176
|
-
);
|
|
177
|
-
}}
|
|
182
|
+
onClick={() => write(columnAccessOf(column.name))}
|
|
178
183
|
>
|
|
179
184
|
<CubeColumnTypeIcon type={column.type} />
|
|
180
185
|
{column.name}
|
|
181
186
|
</button>
|
|
187
|
+
{isCubeJsonColumn(column) && (
|
|
188
|
+
<CubeJsonFieldsToggle
|
|
189
|
+
fields={draft.jsonFields}
|
|
190
|
+
column={column}
|
|
191
|
+
disabled={readOnly}
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
182
194
|
</li>
|
|
183
195
|
))}
|
|
184
196
|
</ul>
|
|
197
|
+
<CubeJsonFieldsTree
|
|
198
|
+
fields={draft.jsonFields}
|
|
199
|
+
disabled={disabled}
|
|
200
|
+
onInsert={write}
|
|
201
|
+
/>
|
|
185
202
|
<ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
|
|
186
203
|
{EXPRESSION_FILTER_EDITOR_NOTES.map((note) => (
|
|
187
204
|
<li key={note}>{note}</li>
|
|
@@ -38,19 +38,19 @@ import {
|
|
|
38
38
|
type CubeExtendRow,
|
|
39
39
|
CubeExtendDraft,
|
|
40
40
|
} from '../../stores/editors/CubeExtendDraft.js';
|
|
41
|
+
import { columnAccessOf } from '../../stores/CubePureText.js';
|
|
41
42
|
import { CubeButton } from '../CubeButton.js';
|
|
42
43
|
import { CubeColumnTypeIcon } from './CubeColumnPicker.js';
|
|
44
|
+
import {
|
|
45
|
+
CubeJsonFieldsToggle,
|
|
46
|
+
CubeJsonFieldsTree,
|
|
47
|
+
isCubeJsonColumn,
|
|
48
|
+
} from './CubeJsonFieldsTree.js';
|
|
43
49
|
import type { CubeNodeEditorProps } from './CubeNodeEditorRegistry.js';
|
|
44
50
|
|
|
45
51
|
/** The code editor's language: legend-code-editor's `CODE_EDITOR_LANGUAGE.PURE` */
|
|
46
52
|
const PURE = 'pure';
|
|
47
53
|
|
|
48
|
-
/** How an expression reads a column: `$x.NAME`, or `$x.'a name'` for one that isn't an identifier */
|
|
49
|
-
export const columnAccessOf = (name: string): string =>
|
|
50
|
-
/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)
|
|
51
|
-
? `$x.${name}`
|
|
52
|
-
: `$x.'${name.replace(/\\/gu, '\\\\').replace(/'/gu, "\\'")}'`;
|
|
53
|
-
|
|
54
54
|
/** The error the code editor marks, from a row's problem located in its code */
|
|
55
55
|
const markerOf = (row: CubeExtendRow): CompilationError | undefined => {
|
|
56
56
|
const { problem } = row;
|
|
@@ -94,8 +94,8 @@ const CubeExtendRowStatus = observer((props: { row: CubeExtendRow }) => {
|
|
|
94
94
|
return row.type ? (
|
|
95
95
|
<div className="flex items-center gap-1 text-sm text-[var(--color-text-secondary)]">
|
|
96
96
|
<CubeColumnTypeIcon type={row.type} />
|
|
97
|
-
{/* every new column
|
|
98
|
-
{`${row.type.displayName},
|
|
97
|
+
{/* every new column is optional (PLAN §11.7) */}
|
|
98
|
+
{`${row.type.displayName}, optional`}
|
|
99
99
|
</div>
|
|
100
100
|
) : null;
|
|
101
101
|
});
|
|
@@ -200,6 +200,14 @@ export const CubeExtendEditor = observer((props: CubeNodeEditorProps) => {
|
|
|
200
200
|
const [focusedKey, setFocusedKey] = useState<number | undefined>();
|
|
201
201
|
const target =
|
|
202
202
|
draft.rows.find((row) => row.key === focusedKey) ?? draft.rows.at(-1);
|
|
203
|
+
const chipsDisabled = readOnly || draft.validating || !target;
|
|
204
|
+
/** Writes text at the end of the expression last used, as the chips do */
|
|
205
|
+
const write = (text: string): void => {
|
|
206
|
+
if (target) {
|
|
207
|
+
const separator = /\s$/u.test(target.code) ? '' : ' ';
|
|
208
|
+
draft.setCode(target.key, `${target.code}${separator}${text}`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
203
211
|
return (
|
|
204
212
|
<div className="flex flex-col gap-2 text-base">
|
|
205
213
|
<span>New columns</span>
|
|
@@ -240,27 +248,32 @@ export const CubeExtendEditor = observer((props: CubeNodeEditorProps) => {
|
|
|
240
248
|
<span>Input columns</span>
|
|
241
249
|
<ul aria-label="Input columns" className="flex flex-wrap gap-1">
|
|
242
250
|
{schema.columns.map((column) => (
|
|
243
|
-
<li key={column.name}>
|
|
251
|
+
<li key={column.name} className="flex items-stretch gap-0.5">
|
|
244
252
|
<button
|
|
245
253
|
className="flex items-center gap-1 rounded-sm border border-[var(--color-border-subtle)] px-1 text-sm hover:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-disabled)]"
|
|
246
254
|
title={`Write ${columnAccessOf(column.name)} at the end of the expression last used (${getExactColumnTypeLabel(column)})`}
|
|
247
|
-
disabled={
|
|
248
|
-
onClick={() =>
|
|
249
|
-
if (target) {
|
|
250
|
-
const separator = /\s$/u.test(target.code) ? '' : ' ';
|
|
251
|
-
draft.setCode(
|
|
252
|
-
target.key,
|
|
253
|
-
`${target.code}${separator}${columnAccessOf(column.name)}`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}}
|
|
255
|
+
disabled={chipsDisabled}
|
|
256
|
+
onClick={() => write(columnAccessOf(column.name))}
|
|
257
257
|
>
|
|
258
258
|
<CubeColumnTypeIcon type={column.type} />
|
|
259
259
|
{column.name}
|
|
260
260
|
</button>
|
|
261
|
+
{isCubeJsonColumn(column) && (
|
|
262
|
+
<CubeJsonFieldsToggle
|
|
263
|
+
fields={draft.jsonFields}
|
|
264
|
+
column={column}
|
|
265
|
+
disabled={readOnly}
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
261
268
|
</li>
|
|
262
269
|
))}
|
|
263
270
|
</ul>
|
|
271
|
+
<CubeJsonFieldsTree
|
|
272
|
+
fields={draft.jsonFields}
|
|
273
|
+
disabled={chipsDisabled}
|
|
274
|
+
onInsert={write}
|
|
275
|
+
onAddColumn={(name, code) => draft.addColumn(name, code)}
|
|
276
|
+
/>
|
|
264
277
|
<ul className="flex list-disc flex-col gap-1 pl-4 text-sm text-[var(--color-text-secondary)]">
|
|
265
278
|
{[...EXTEND_EDITOR_NOTES, COLUMN_NAME_RULES_HINT].map((note) => (
|
|
266
279
|
<li key={note}>{note}</li>
|