@finos/legend-application-studio 28.21.51 → 28.21.53

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.
Files changed (61) hide show
  1. package/lib/components/editor/Editor.d.ts.map +1 -1
  2. package/lib/components/editor/Editor.js +2 -1
  3. package/lib/components/editor/Editor.js.map +1 -1
  4. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
  5. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +14 -2
  6. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
  7. package/lib/components/editor/embedded-cube/EmbeddedCube.d.ts +23 -0
  8. package/lib/components/editor/embedded-cube/EmbeddedCube.d.ts.map +1 -0
  9. package/lib/components/editor/embedded-cube/EmbeddedCube.js +38 -0
  10. package/lib/components/editor/embedded-cube/EmbeddedCube.js.map +1 -0
  11. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.d.ts +35 -0
  12. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.d.ts.map +1 -0
  13. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.js +179 -0
  14. package/lib/components/editor/embedded-cube/EmbeddedCubeEditor.js.map +1 -0
  15. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.d.ts.map +1 -1
  16. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js +4 -0
  17. package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js.map +1 -1
  18. package/lib/index.css +1 -1
  19. package/lib/package.json +3 -1
  20. package/lib/stores/editor/EditorStore.d.ts +2 -0
  21. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  22. package/lib/stores/editor/EditorStore.js +6 -1
  23. package/lib/stores/editor/EditorStore.js.map +1 -1
  24. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.d.ts +86 -0
  25. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.d.ts.map +1 -0
  26. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.js +206 -0
  27. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.js.map +1 -0
  28. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.d.ts +51 -0
  29. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.d.ts.map +1 -0
  30. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.js +148 -0
  31. package/lib/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.js.map +1 -0
  32. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.d.ts +39 -0
  33. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.d.ts.map +1 -0
  34. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.js +60 -0
  35. package/lib/stores/editor/embedded-cube/LegendStudioCubeHost.js.map +1 -0
  36. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.d.ts +57 -0
  37. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.d.ts.map +1 -0
  38. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.js +148 -0
  39. package/lib/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.js.map +1 -0
  40. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.d.ts +58 -0
  41. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.d.ts.map +1 -0
  42. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.js +94 -0
  43. package/lib/stores/editor/embedded-cube/LegendStudioCubeWorkspace.js.map +1 -0
  44. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.d.ts +57 -0
  45. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.d.ts.map +1 -0
  46. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.js +151 -0
  47. package/lib/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.js.map +1 -0
  48. package/package.json +13 -11
  49. package/src/components/editor/Editor.tsx +2 -0
  50. package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +62 -0
  51. package/src/components/editor/embedded-cube/EmbeddedCube.tsx +57 -0
  52. package/src/components/editor/embedded-cube/EmbeddedCubeEditor.tsx +452 -0
  53. package/src/components/extensions/Core_LegendStudioApplicationPlugin.tsx +4 -0
  54. package/src/stores/editor/EditorStore.ts +6 -1
  55. package/src/stores/editor/embedded-cube/EmbeddedCubeState.ts +284 -0
  56. package/src/stores/editor/embedded-cube/LegendStudioCubeDataProductCatalog.ts +242 -0
  57. package/src/stores/editor/embedded-cube/LegendStudioCubeHost.ts +82 -0
  58. package/src/stores/editor/embedded-cube/LegendStudioCubeIngestCatalog.ts +235 -0
  59. package/src/stores/editor/embedded-cube/LegendStudioCubeWorkspace.ts +151 -0
  60. package/src/stores/graph-modifier/DSL_Ingest_GraphModifierHelper.ts +214 -0
  61. package/tsconfig.json +8 -0
@@ -0,0 +1,452 @@
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
+ Dialog,
19
+ Modal,
20
+ ModalBody,
21
+ ModalFooter,
22
+ ModalFooterButton,
23
+ ModalHeader,
24
+ TimesIcon,
25
+ } from '@finos/legend-art';
26
+ import {
27
+ buildCubeRunningStepLambda,
28
+ CubeButton,
29
+ CubeEditor,
30
+ formatDisabledReasons,
31
+ type CubeEditorState,
32
+ } from '@finos/legend-cube-builder';
33
+ import type { IngestDefinition } from '@finos/legend-graph';
34
+ import { noop, type PlainObject } from '@finos/legend-shared';
35
+ import { observer } from 'mobx-react-lite';
36
+ import { useState } from 'react';
37
+ import type { EmbeddedCubeState } from '../../../stores/editor/embedded-cube/EmbeddedCubeState.js';
38
+ import { LegendStudioCubeHost } from '../../../stores/editor/embedded-cube/LegendStudioCubeHost.js';
39
+
40
+ export const EMBEDDED_CUBE_TEST_ID = {
41
+ DIALOG: 'embedded-cube__dialog',
42
+ ADD_MATERIALIZED_VIEW: 'embedded-cube__add-materialized-view',
43
+ SAVE_MATERIALIZED_VIEW: 'embedded-cube__save-materialized-view',
44
+ };
45
+
46
+ /**
47
+ * The classification of the view's data: one of the classifications a data
48
+ * product's access points offer, or typed when Studio's config gives none
49
+ */
50
+ const MaterializedViewClassification = observer(
51
+ (props: {
52
+ embeddedCubeState: EmbeddedCubeState;
53
+ sensitivity: string;
54
+ setSensitivity: (sensitivity: string) => void;
55
+ }) => {
56
+ const { embeddedCubeState, sensitivity, setSensitivity } = props;
57
+ const { classifications } = embeddedCubeState;
58
+ return (
59
+ <label className="flex flex-col gap-1 text-base">
60
+ Data classification
61
+ {classifications.length ? (
62
+ <select
63
+ className="input--dark h-8 rounded-sm px-2"
64
+ aria-label="Data classification"
65
+ value={sensitivity}
66
+ onChange={(event) => setSensitivity(event.target.value)}
67
+ >
68
+ <option value="" disabled={true}>
69
+ Pick a classification
70
+ </option>
71
+ {classifications.map((classification) => (
72
+ <option key={classification} value={classification}>
73
+ {classification}
74
+ </option>
75
+ ))}
76
+ </select>
77
+ ) : (
78
+ <input
79
+ className="input--dark h-8 rounded-sm px-2"
80
+ aria-label="Data classification"
81
+ placeholder="Classification"
82
+ spellCheck={false}
83
+ value={sensitivity}
84
+ onChange={(event) => setSensitivity(event.target.value)}
85
+ />
86
+ )}
87
+ </label>
88
+ );
89
+ },
90
+ );
91
+
92
+ /**
93
+ * The view's primary key: none or some of the columns it can take, those of
94
+ * the step that runs but the milestoning ones, kept in the order of the
95
+ * columns
96
+ */
97
+ const MaterializedViewPrimaryKey: React.FC<{
98
+ columns: readonly string[];
99
+ primaryKey: readonly string[];
100
+ setPrimaryKey: (primaryKey: string[]) => void;
101
+ }> = ({ columns, primaryKey, setPrimaryKey }) => (
102
+ <fieldset className="flex min-w-0 flex-col gap-1 text-base">
103
+ <legend>Primary key</legend>
104
+ <span className="text-sm text-[var(--color-text-muted)]">
105
+ The columns that identify a row, if any
106
+ </span>
107
+ <ul className="max-h-40 overflow-auto" aria-label="Primary key columns">
108
+ {columns.map((column) => (
109
+ <li key={column}>
110
+ <label className="flex items-center gap-2">
111
+ <input
112
+ type="checkbox"
113
+ checked={primaryKey.includes(column)}
114
+ onChange={() =>
115
+ setPrimaryKey(
116
+ primaryKey.includes(column)
117
+ ? primaryKey.filter((each) => each !== column)
118
+ : columns.filter(
119
+ (each) => each === column || primaryKey.includes(each),
120
+ ),
121
+ )
122
+ }
123
+ />
124
+ <span className="min-w-0 truncate">{column}</span>
125
+ </label>
126
+ </li>
127
+ ))}
128
+ </ul>
129
+ </fieldset>
130
+ );
131
+
132
+ /** What a header button gives its dialog */
133
+ interface MaterializedViewDialogInput {
134
+ /** The step that runs, as a lambda with no runtime and no row limit */
135
+ readonly lambda: PlainObject;
136
+ /** The columns of the step that runs, the view's; none when Cube doesn't know them */
137
+ readonly columns: readonly string[] | undefined;
138
+ /** Why Save MatView asks again; none to add a new view */
139
+ readonly saveProblems?: readonly string[] | undefined;
140
+ }
141
+
142
+ /**
143
+ * Names the materialized view, classifies its data and picks its primary
144
+ * key, then adds it to the ingest definition (Add MatView). For the view
145
+ * opened to edit, asks again for what it can't be saved with as it was, the
146
+ * classification of its data or a primary key whose column is gone, then
147
+ * saves it in place (Save MatView)
148
+ */
149
+ const MaterializedViewDialog = observer(
150
+ (props: {
151
+ embeddedCubeState: EmbeddedCubeState;
152
+ input: MaterializedViewDialogInput;
153
+ close: () => void;
154
+ }) => {
155
+ const { embeddedCubeState, input, close } = props;
156
+ const { lambda, saveProblems } = input;
157
+ const saved = saveProblems ? embeddedCubeState.matViewDataSet : undefined;
158
+ // the columns the key can take; a view saved when Cube doesn't know the
159
+ // step's columns keeps its key
160
+ const columns = embeddedCubeState.getMaterializedViewKeyColumns(
161
+ input.columns ?? saved?.primaryKey ?? [],
162
+ );
163
+ // as Cube's dialogs: the dialog follows the theme, and its primary button
164
+ // is an accent button in every theme (legend-art's light one is grey)
165
+ const darkMode =
166
+ !embeddedCubeState.editorStore.applicationStore.layoutService
167
+ .TEMPORARY__isLightColorThemeEnabled;
168
+ const [name, setName] = useState('');
169
+ // a view saved again starts on its own classification, if it can keep it,
170
+ // and on the columns of its key the step still has
171
+ const [sensitivity, setSensitivity] = useState(() =>
172
+ saved?.sensitivity !== undefined &&
173
+ embeddedCubeState.getMaterializedViewClassificationProblem(
174
+ saved.sensitivity,
175
+ ) === undefined
176
+ ? saved.sensitivity
177
+ : '',
178
+ );
179
+ const [primaryKey, setPrimaryKey] = useState(() =>
180
+ columns.filter((column) => saved?.primaryKey.includes(column)),
181
+ );
182
+ const nameProblem = saveProblems
183
+ ? undefined
184
+ : embeddedCubeState.getMaterializedViewNameProblem(name);
185
+ const problem =
186
+ nameProblem ??
187
+ embeddedCubeState.getMaterializedViewClassificationProblem(sensitivity);
188
+ const verb = saveProblems ? 'Save' : 'Add';
189
+ const confirm = (): void => {
190
+ if (problem !== undefined) {
191
+ return;
192
+ }
193
+ if (saveProblems) {
194
+ embeddedCubeState.saveMaterializedView(lambda, {
195
+ primaryKey,
196
+ sensitivity,
197
+ });
198
+ } else {
199
+ embeddedCubeState.addMaterializedView(name, lambda, {
200
+ primaryKey,
201
+ sensitivity,
202
+ });
203
+ }
204
+ };
205
+ return (
206
+ <Dialog open={true} onClose={close}>
207
+ <Modal darkMode={darkMode} className="w-[440px] max-w-full">
208
+ <ModalHeader>
209
+ <div className="modal__title">{`${verb} MatView`}</div>
210
+ </ModalHeader>
211
+ <ModalBody>
212
+ <form
213
+ className="flex flex-col gap-2"
214
+ data-testid={
215
+ saveProblems
216
+ ? EMBEDDED_CUBE_TEST_ID.SAVE_MATERIALIZED_VIEW
217
+ : EMBEDDED_CUBE_TEST_ID.ADD_MATERIALIZED_VIEW
218
+ }
219
+ onSubmit={(event) => {
220
+ event.preventDefault();
221
+ confirm();
222
+ }}
223
+ >
224
+ {saveProblems ? (
225
+ <>
226
+ <div className="text-base">
227
+ {`${embeddedCubeState.matView} of ${embeddedCubeState.ingest?.name ?? 'the ingest definition'} can't be saved as it was:`}
228
+ </div>
229
+ <ul
230
+ aria-label="Why Save MatView asks"
231
+ className="list-disc pl-5 text-sm text-[var(--color-status-warn)]"
232
+ >
233
+ {saveProblems.map((each) => (
234
+ <li key={each}>{each}</li>
235
+ ))}
236
+ </ul>
237
+ </>
238
+ ) : (
239
+ <>
240
+ <label className="flex flex-col gap-1 text-base">
241
+ {`A data set of ${embeddedCubeState.ingest?.name ?? 'the ingest definition'}, whose rows are the cube's`}
242
+ <input
243
+ className="input--dark h-8 rounded-sm px-2"
244
+ aria-label="Materialized view name"
245
+ placeholder="Name"
246
+ spellCheck={false}
247
+ autoFocus={true}
248
+ value={name}
249
+ onChange={(event) => setName(event.target.value)}
250
+ />
251
+ </label>
252
+ {name.trim() && nameProblem && (
253
+ <div className="text-sm text-[var(--color-status-error)]">
254
+ {nameProblem}
255
+ </div>
256
+ )}
257
+ </>
258
+ )}
259
+ <MaterializedViewClassification
260
+ embeddedCubeState={embeddedCubeState}
261
+ sensitivity={sensitivity}
262
+ setSensitivity={setSensitivity}
263
+ />
264
+ <MaterializedViewPrimaryKey
265
+ columns={columns}
266
+ primaryKey={primaryKey}
267
+ setPrimaryKey={setPrimaryKey}
268
+ />
269
+ </form>
270
+ </ModalBody>
271
+ <ModalFooter>
272
+ <ModalFooterButton
273
+ darkMode={true}
274
+ formatText={false}
275
+ text={verb}
276
+ title={problem ?? `${verb} the materialized view`}
277
+ disabled={problem !== undefined}
278
+ onClick={confirm}
279
+ />
280
+ <ModalFooterButton
281
+ darkMode={darkMode}
282
+ formatText={false}
283
+ type="secondary"
284
+ text="Cancel"
285
+ onClick={close}
286
+ />
287
+ </ModalFooter>
288
+ </Modal>
289
+ </Dialog>
290
+ );
291
+ },
292
+ );
293
+
294
+ /**
295
+ * Cube's header button: the step that runs, as a lambda with no runtime and
296
+ * no row limit, becomes a materialized view of the ingest definition (Add
297
+ * MatView, named, classified and keyed in a dialog), or replaces the one
298
+ * opened to edit (Save MatView), at once unless its classification or its
299
+ * key must be asked again
300
+ */
301
+ const MaterializedViewButton = observer(
302
+ (props: {
303
+ embeddedCubeState: EmbeddedCubeState;
304
+ editorState: CubeEditorState;
305
+ }) => {
306
+ const { embeddedCubeState, editorState } = props;
307
+ const { execution } = editorState;
308
+ const { matView } = embeddedCubeState;
309
+ const [dialogInput, setDialogInput] = useState<
310
+ MaterializedViewDialogInput | undefined
311
+ >();
312
+ const [isBuilding, setIsBuilding] = useState(false);
313
+ const start = (): void => {
314
+ // the open step editor's edits go in first, as for Execute
315
+ if (!editorState.nodeEditor.finishApplied()) {
316
+ return;
317
+ }
318
+ // the columns of the step that runs, read with its lambda
319
+ const selected = editorState.document.query.selected;
320
+ const columns = (
321
+ selected === undefined
322
+ ? undefined
323
+ : editorState.analysis.schemas.get(selected)
324
+ )?.columns.map((column) => column.name);
325
+ setIsBuilding(true);
326
+ buildCubeRunningStepLambda(editorState)
327
+ .then((lambda) => {
328
+ if (matView === undefined) {
329
+ setDialogInput({ lambda, columns });
330
+ return;
331
+ }
332
+ const saveProblems =
333
+ embeddedCubeState.getMaterializedViewSaveProblems(columns);
334
+ if (saveProblems.length) {
335
+ setDialogInput({ lambda, columns, saveProblems });
336
+ } else {
337
+ embeddedCubeState.saveMaterializedView(lambda);
338
+ }
339
+ })
340
+ .catch(editorState.host.applicationStore.alertUnhandledError)
341
+ .finally(() => setIsBuilding(false));
342
+ };
343
+ return (
344
+ <>
345
+ <CubeButton
346
+ primary={true}
347
+ title={
348
+ !execution.canExecute
349
+ ? formatDisabledReasons(execution.disabledReasons)
350
+ : matView === undefined
351
+ ? 'Add the step that runs to the ingest definition, as a materialized view'
352
+ : `Replace materialized view ${matView} with the step that runs`
353
+ }
354
+ disabled={!execution.canExecute || isBuilding}
355
+ onClick={start}
356
+ >
357
+ {matView === undefined ? 'Add MatView' : 'Save MatView'}
358
+ </CubeButton>
359
+ {dialogInput && (
360
+ <MaterializedViewDialog
361
+ embeddedCubeState={embeddedCubeState}
362
+ input={dialogInput}
363
+ close={() => setDialogInput(undefined)}
364
+ />
365
+ )}
366
+ </>
367
+ );
368
+ },
369
+ );
370
+
371
+ /**
372
+ * Legend Cube, full screen over the editor, building a materialized view of
373
+ * the ingest definition it was opened from (Create MatView), or
374
+ * editing one of its views, read back into the cube (Edit MatView). Loaded
375
+ * on the first opening, with Cube (`EmbeddedCube`)
376
+ */
377
+ export const EmbeddedCubeEditor = observer(
378
+ (props: {
379
+ embeddedCubeState: EmbeddedCubeState;
380
+ ingest: IngestDefinition;
381
+ }) => {
382
+ const { embeddedCubeState, ingest } = props;
383
+ const { editorStore } = embeddedCubeState;
384
+ // a new host for each opening, so each starts on an empty cube
385
+ const [host] = useState(() => new LegendStudioCubeHost(editorStore));
386
+ const { matView } = embeddedCubeState;
387
+ // the view as it was when opened: the cube is read from it once
388
+ const [initialLambda] = useState(() => {
389
+ const lambda = embeddedCubeState.matViewLambda;
390
+ return lambda && matView !== undefined
391
+ ? { lambda, label: `materialized view ${matView}` }
392
+ : undefined;
393
+ });
394
+ const darkMode =
395
+ !editorStore.applicationStore.layoutService
396
+ .TEMPORARY__isLightColorThemeEnabled;
397
+ return (
398
+ <Dialog
399
+ open={true}
400
+ onClose={noop} // closed by its own button only, so a cube isn't lost by Escape
401
+ classes={{
402
+ root: 'editor-modal__root-container',
403
+ container: 'editor-modal__container',
404
+ paper:
405
+ 'editor-modal__content query-builder__dialog__container__content',
406
+ }}
407
+ >
408
+ <Modal
409
+ darkMode={darkMode}
410
+ className="editor-modal query-builder__dialog query-builder__dialog--expanded"
411
+ >
412
+ <div
413
+ className="query-builder__dialog__header"
414
+ data-testid={EMBEDDED_CUBE_TEST_ID.DIALOG}
415
+ >
416
+ <div className="min-w-0 flex-1 truncate px-4 text-lg font-medium text-[var(--color-state-disabled-on-accent)]">
417
+ {matView === undefined
418
+ ? `Create MatView for ${ingest.path} with Cube`
419
+ : `Edit MatView ${matView} of ${ingest.path} with Cube`}
420
+ </div>
421
+ <div className="query-builder__dialog__header__actions">
422
+ <button
423
+ className="query-builder__dialog__header__action"
424
+ tabIndex={-1}
425
+ onClick={() => embeddedCubeState.close()}
426
+ title={
427
+ matView === undefined
428
+ ? 'Close Cube, adding nothing'
429
+ : 'Close Cube, saving nothing'
430
+ }
431
+ >
432
+ <TimesIcon />
433
+ </button>
434
+ </div>
435
+ </div>
436
+ <div className="query-builder__dialog__content">
437
+ <CubeEditor
438
+ host={host}
439
+ initialLambda={initialLambda}
440
+ headerActions={(editorState) => (
441
+ <MaterializedViewButton
442
+ embeddedCubeState={embeddedCubeState}
443
+ editorState={editorState}
444
+ />
445
+ )}
446
+ />
447
+ </div>
448
+ </Modal>
449
+ </Dialog>
450
+ );
451
+ },
452
+ );
@@ -40,6 +40,7 @@ import { LEGEND_STUDIO_SETTING_CONFIG } from '../../__lib__/LegendStudioSetting.
40
40
  import { setupPureLanguageService } from '@finos/legend-code-editor';
41
41
  import { configureCodeEditorComponent } from '@finos/legend-lego/code-editor';
42
42
  import { STO_RELATIONAL_LEGEND_STUDIO_COMMAND_CONFIG } from '../../__lib__/STO_Relational_LegendStudioCommand.js';
43
+ import { LEGEND_CUBE_COMMAND_CONFIG } from '@finos/legend-cube-builder';
43
44
  import { ShowcaseManager } from '../ShowcaseManager.js';
44
45
  import { CabinetIcon } from '@finos/legend-art';
45
46
  import { ShowcaseManagerState } from '../../stores/ShowcaseManagerState.js';
@@ -120,6 +121,9 @@ export class Core_LegendStudioApplicationPlugin extends LegendStudioApplicationP
120
121
  ...collectKeyedCommandConfigEntriesFromConfig(
121
122
  STO_RELATIONAL_LEGEND_STUDIO_COMMAND_CONFIG,
122
123
  ),
124
+ // Legend Cube's shortcuts (F9, Ctrl/Cmd+Z), for Create MatView with
125
+ // Cube: Studio's own are off while the cube is open
126
+ ...collectKeyedCommandConfigEntriesFromConfig(LEGEND_CUBE_COMMAND_CONFIG),
123
127
  ];
124
128
  }
125
129
 
@@ -111,6 +111,7 @@ import {
111
111
  import { GlobalTestRunnerState } from './sidebar-state/testable/GlobalTestRunnerState.js';
112
112
  import type { LegendStudioApplicationStore } from '../LegendStudioBaseStore.js';
113
113
  import { EmbeddedQueryBuilderState } from './EmbeddedQueryBuilderState.js';
114
+ import { EmbeddedCubeState } from './embedded-cube/EmbeddedCubeState.js';
114
115
  import { LEGEND_STUDIO_COMMAND_KEY } from '../../__lib__/LegendStudioCommand.js';
115
116
  import { EditorTabManagerState } from './EditorTabManagerState.js';
116
117
  import { GraphEditFormModeState } from './GraphEditFormModeState.js';
@@ -198,6 +199,7 @@ export class EditorStore implements CommandRegistrar {
198
199
  modelImporterState: ModelImporterState;
199
200
  projectConfigurationEditorState: ProjectConfigurationEditorState;
200
201
  embeddedQueryBuilderState: EmbeddedQueryBuilderState;
202
+ embeddedCubeState: EmbeddedCubeState;
201
203
  embeddedDataCubeViewerState: QueryBuilderDataCubeViewerState | undefined;
202
204
  newElementState: NewElementState;
203
205
  /**
@@ -297,6 +299,7 @@ export class EditorStore implements CommandRegistrar {
297
299
  this,
298
300
  );
299
301
  this.embeddedQueryBuilderState = new EmbeddedQueryBuilderState(this);
302
+ this.embeddedCubeState = new EmbeddedCubeState(this);
300
303
  // side bar panels
301
304
  this.explorerTreeState = new ExplorerTreeState(this);
302
305
  this.projectOverviewState = new ProjectOverviewState(this, this.sdlcState);
@@ -445,9 +448,11 @@ export class EditorStore implements CommandRegistrar {
445
448
  */
446
449
  createEditorCommandTrigger(additionalChecker?: () => boolean): () => boolean {
447
450
  return (): boolean =>
448
- // we don't want to leak any hotkeys when we have embedded query builder open
451
+ // we don't want to leak any hotkeys when we have embedded query builder
452
+ // or Legend Cube open, e.g. Studio's F9 compiles where Cube's executes
449
453
  // TODO?: we probably should come up with a more generic mechanism for this
450
454
  !this.embeddedQueryBuilderState.queryBuilderState &&
455
+ !this.embeddedCubeState.isOpen &&
451
456
  (!additionalChecker || additionalChecker());
452
457
  }
453
458