@finos/legend-application-studio 28.21.51 → 28.21.52

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 +17 -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 +177 -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 +81 -0
  25. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.d.ts.map +1 -0
  26. package/lib/stores/editor/embedded-cube/EmbeddedCubeState.js +186 -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 +65 -0
  51. package/src/components/editor/embedded-cube/EmbeddedCube.tsx +57 -0
  52. package/src/components/editor/embedded-cube/EmbeddedCubeEditor.tsx +448 -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 +251 -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,448 @@
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 of the step that
94
+ * runs, kept in the order of the columns
95
+ */
96
+ const MaterializedViewPrimaryKey: React.FC<{
97
+ columns: readonly string[];
98
+ primaryKey: readonly string[];
99
+ setPrimaryKey: (primaryKey: string[]) => void;
100
+ }> = ({ columns, primaryKey, setPrimaryKey }) => (
101
+ <fieldset className="flex min-w-0 flex-col gap-1 text-base">
102
+ <legend>Primary key</legend>
103
+ <span className="text-sm text-[var(--color-text-muted)]">
104
+ The columns that identify a row, if any
105
+ </span>
106
+ <ul className="max-h-40 overflow-auto" aria-label="Primary key columns">
107
+ {columns.map((column) => (
108
+ <li key={column}>
109
+ <label className="flex items-center gap-2">
110
+ <input
111
+ type="checkbox"
112
+ checked={primaryKey.includes(column)}
113
+ onChange={() =>
114
+ setPrimaryKey(
115
+ primaryKey.includes(column)
116
+ ? primaryKey.filter((each) => each !== column)
117
+ : columns.filter(
118
+ (each) => each === column || primaryKey.includes(each),
119
+ ),
120
+ )
121
+ }
122
+ />
123
+ <span className="min-w-0 truncate">{column}</span>
124
+ </label>
125
+ </li>
126
+ ))}
127
+ </ul>
128
+ </fieldset>
129
+ );
130
+
131
+ /** What a header button gives its dialog */
132
+ interface MaterializedViewDialogInput {
133
+ /** The step that runs, as a lambda with no runtime and no row limit */
134
+ readonly lambda: PlainObject;
135
+ /** The columns of the step that runs, the view's; none when Cube doesn't know them */
136
+ readonly columns: readonly string[] | undefined;
137
+ /** Why Save MatView asks again; none to add a new view */
138
+ readonly saveProblems?: readonly string[] | undefined;
139
+ }
140
+
141
+ /**
142
+ * Names the materialized view, classifies its data and picks its primary
143
+ * key, then adds it to the ingest definition (Add MatView). For the view
144
+ * opened to edit, asks again for what it can't be saved with as it was, the
145
+ * classification of its data or a primary key whose column is gone, then
146
+ * saves it in place (Save MatView)
147
+ */
148
+ const MaterializedViewDialog = observer(
149
+ (props: {
150
+ embeddedCubeState: EmbeddedCubeState;
151
+ input: MaterializedViewDialogInput;
152
+ close: () => void;
153
+ }) => {
154
+ const { embeddedCubeState, input, close } = props;
155
+ const { lambda, saveProblems } = input;
156
+ const saved = saveProblems ? embeddedCubeState.matViewDataSet : undefined;
157
+ // a view saved when Cube doesn't know the step's columns keeps its key
158
+ const columns = input.columns ?? saved?.primaryKey ?? [];
159
+ // as Cube's dialogs: the dialog follows the theme, and its primary button
160
+ // is an accent button in every theme (legend-art's light one is grey)
161
+ const darkMode =
162
+ !embeddedCubeState.editorStore.applicationStore.layoutService
163
+ .TEMPORARY__isLightColorThemeEnabled;
164
+ const [name, setName] = useState('');
165
+ // a view saved again starts on its own classification, if it can keep it,
166
+ // and on the columns of its key the step still has
167
+ const [sensitivity, setSensitivity] = useState(() =>
168
+ saved?.sensitivity !== undefined &&
169
+ embeddedCubeState.getMaterializedViewClassificationProblem(
170
+ saved.sensitivity,
171
+ ) === undefined
172
+ ? saved.sensitivity
173
+ : '',
174
+ );
175
+ const [primaryKey, setPrimaryKey] = useState(() =>
176
+ columns.filter((column) => saved?.primaryKey.includes(column)),
177
+ );
178
+ const nameProblem = saveProblems
179
+ ? undefined
180
+ : embeddedCubeState.getMaterializedViewNameProblem(name);
181
+ const problem =
182
+ nameProblem ??
183
+ embeddedCubeState.getMaterializedViewClassificationProblem(sensitivity);
184
+ const verb = saveProblems ? 'Save' : 'Add';
185
+ const confirm = (): void => {
186
+ if (problem !== undefined) {
187
+ return;
188
+ }
189
+ if (saveProblems) {
190
+ embeddedCubeState.saveMaterializedView(lambda, {
191
+ primaryKey,
192
+ sensitivity,
193
+ });
194
+ } else {
195
+ embeddedCubeState.addMaterializedView(name, lambda, {
196
+ primaryKey,
197
+ sensitivity,
198
+ });
199
+ }
200
+ };
201
+ return (
202
+ <Dialog open={true} onClose={close}>
203
+ <Modal darkMode={darkMode} className="w-[440px] max-w-full">
204
+ <ModalHeader>
205
+ <div className="modal__title">{`${verb} MatView`}</div>
206
+ </ModalHeader>
207
+ <ModalBody>
208
+ <form
209
+ className="flex flex-col gap-2"
210
+ data-testid={
211
+ saveProblems
212
+ ? EMBEDDED_CUBE_TEST_ID.SAVE_MATERIALIZED_VIEW
213
+ : EMBEDDED_CUBE_TEST_ID.ADD_MATERIALIZED_VIEW
214
+ }
215
+ onSubmit={(event) => {
216
+ event.preventDefault();
217
+ confirm();
218
+ }}
219
+ >
220
+ {saveProblems ? (
221
+ <>
222
+ <div className="text-base">
223
+ {`${embeddedCubeState.matView} of ${embeddedCubeState.ingest?.name ?? 'the ingest definition'} can't be saved as it was:`}
224
+ </div>
225
+ <ul
226
+ aria-label="Why Save MatView asks"
227
+ className="list-disc pl-5 text-sm text-[var(--color-status-warn)]"
228
+ >
229
+ {saveProblems.map((each) => (
230
+ <li key={each}>{each}</li>
231
+ ))}
232
+ </ul>
233
+ </>
234
+ ) : (
235
+ <>
236
+ <label className="flex flex-col gap-1 text-base">
237
+ {`A data set of ${embeddedCubeState.ingest?.name ?? 'the ingest definition'}, whose rows are the cube's`}
238
+ <input
239
+ className="input--dark h-8 rounded-sm px-2"
240
+ aria-label="Materialized view name"
241
+ placeholder="Name"
242
+ spellCheck={false}
243
+ autoFocus={true}
244
+ value={name}
245
+ onChange={(event) => setName(event.target.value)}
246
+ />
247
+ </label>
248
+ {name.trim() && nameProblem && (
249
+ <div className="text-sm text-[var(--color-status-error)]">
250
+ {nameProblem}
251
+ </div>
252
+ )}
253
+ </>
254
+ )}
255
+ <MaterializedViewClassification
256
+ embeddedCubeState={embeddedCubeState}
257
+ sensitivity={sensitivity}
258
+ setSensitivity={setSensitivity}
259
+ />
260
+ <MaterializedViewPrimaryKey
261
+ columns={columns}
262
+ primaryKey={primaryKey}
263
+ setPrimaryKey={setPrimaryKey}
264
+ />
265
+ </form>
266
+ </ModalBody>
267
+ <ModalFooter>
268
+ <ModalFooterButton
269
+ darkMode={true}
270
+ formatText={false}
271
+ text={verb}
272
+ title={problem ?? `${verb} the materialized view`}
273
+ disabled={problem !== undefined}
274
+ onClick={confirm}
275
+ />
276
+ <ModalFooterButton
277
+ darkMode={darkMode}
278
+ formatText={false}
279
+ type="secondary"
280
+ text="Cancel"
281
+ onClick={close}
282
+ />
283
+ </ModalFooter>
284
+ </Modal>
285
+ </Dialog>
286
+ );
287
+ },
288
+ );
289
+
290
+ /**
291
+ * Cube's header button: the step that runs, as a lambda with no runtime and
292
+ * no row limit, becomes a materialized view of the ingest definition (Add
293
+ * MatView, named, classified and keyed in a dialog), or replaces the one
294
+ * opened to edit (Save MatView), at once unless its classification or its
295
+ * key must be asked again
296
+ */
297
+ const MaterializedViewButton = observer(
298
+ (props: {
299
+ embeddedCubeState: EmbeddedCubeState;
300
+ editorState: CubeEditorState;
301
+ }) => {
302
+ const { embeddedCubeState, editorState } = props;
303
+ const { execution } = editorState;
304
+ const { matView } = embeddedCubeState;
305
+ const [dialogInput, setDialogInput] = useState<
306
+ MaterializedViewDialogInput | undefined
307
+ >();
308
+ const [isBuilding, setIsBuilding] = useState(false);
309
+ const start = (): void => {
310
+ // the open step editor's edits go in first, as for Execute
311
+ if (!editorState.nodeEditor.finishApplied()) {
312
+ return;
313
+ }
314
+ // the columns of the step that runs, read with its lambda
315
+ const selected = editorState.document.query.selected;
316
+ const columns = (
317
+ selected === undefined
318
+ ? undefined
319
+ : editorState.analysis.schemas.get(selected)
320
+ )?.columns.map((column) => column.name);
321
+ setIsBuilding(true);
322
+ buildCubeRunningStepLambda(editorState)
323
+ .then((lambda) => {
324
+ if (matView === undefined) {
325
+ setDialogInput({ lambda, columns });
326
+ return;
327
+ }
328
+ const saveProblems =
329
+ embeddedCubeState.getMaterializedViewSaveProblems(columns);
330
+ if (saveProblems.length) {
331
+ setDialogInput({ lambda, columns, saveProblems });
332
+ } else {
333
+ embeddedCubeState.saveMaterializedView(lambda);
334
+ }
335
+ })
336
+ .catch(editorState.host.applicationStore.alertUnhandledError)
337
+ .finally(() => setIsBuilding(false));
338
+ };
339
+ return (
340
+ <>
341
+ <CubeButton
342
+ primary={true}
343
+ title={
344
+ !execution.canExecute
345
+ ? formatDisabledReasons(execution.disabledReasons)
346
+ : matView === undefined
347
+ ? 'Add the step that runs to the ingest definition, as a materialized view'
348
+ : `Replace materialized view ${matView} with the step that runs`
349
+ }
350
+ disabled={!execution.canExecute || isBuilding}
351
+ onClick={start}
352
+ >
353
+ {matView === undefined ? 'Add MatView' : 'Save MatView'}
354
+ </CubeButton>
355
+ {dialogInput && (
356
+ <MaterializedViewDialog
357
+ embeddedCubeState={embeddedCubeState}
358
+ input={dialogInput}
359
+ close={() => setDialogInput(undefined)}
360
+ />
361
+ )}
362
+ </>
363
+ );
364
+ },
365
+ );
366
+
367
+ /**
368
+ * Legend Cube, full screen over the editor, building a materialized view of
369
+ * the ingest definition it was opened from (Create MatView), or
370
+ * editing one of its views, read back into the cube (Edit MatView). Loaded
371
+ * on the first opening, with Cube (`EmbeddedCube`)
372
+ */
373
+ export const EmbeddedCubeEditor = observer(
374
+ (props: {
375
+ embeddedCubeState: EmbeddedCubeState;
376
+ ingest: IngestDefinition;
377
+ }) => {
378
+ const { embeddedCubeState, ingest } = props;
379
+ const { editorStore } = embeddedCubeState;
380
+ // a new host for each opening, so each starts on an empty cube
381
+ const [host] = useState(() => new LegendStudioCubeHost(editorStore));
382
+ const { matView } = embeddedCubeState;
383
+ // the view as it was when opened: the cube is read from it once
384
+ const [initialLambda] = useState(() => {
385
+ const lambda = embeddedCubeState.matViewLambda;
386
+ return lambda && matView !== undefined
387
+ ? { lambda, label: `materialized view ${matView}` }
388
+ : undefined;
389
+ });
390
+ const darkMode =
391
+ !editorStore.applicationStore.layoutService
392
+ .TEMPORARY__isLightColorThemeEnabled;
393
+ return (
394
+ <Dialog
395
+ open={true}
396
+ onClose={noop} // closed by its own button only, so a cube isn't lost by Escape
397
+ classes={{
398
+ root: 'editor-modal__root-container',
399
+ container: 'editor-modal__container',
400
+ paper:
401
+ 'editor-modal__content query-builder__dialog__container__content',
402
+ }}
403
+ >
404
+ <Modal
405
+ darkMode={darkMode}
406
+ className="editor-modal query-builder__dialog query-builder__dialog--expanded"
407
+ >
408
+ <div
409
+ className="query-builder__dialog__header"
410
+ data-testid={EMBEDDED_CUBE_TEST_ID.DIALOG}
411
+ >
412
+ <div className="min-w-0 flex-1 truncate px-4 text-lg font-medium text-[var(--color-state-disabled-on-accent)]">
413
+ {matView === undefined
414
+ ? `Create MatView for ${ingest.path} with Cube`
415
+ : `Edit MatView ${matView} of ${ingest.path} with Cube`}
416
+ </div>
417
+ <div className="query-builder__dialog__header__actions">
418
+ <button
419
+ className="query-builder__dialog__header__action"
420
+ tabIndex={-1}
421
+ onClick={() => embeddedCubeState.close()}
422
+ title={
423
+ matView === undefined
424
+ ? 'Close Cube, adding nothing'
425
+ : 'Close Cube, saving nothing'
426
+ }
427
+ >
428
+ <TimesIcon />
429
+ </button>
430
+ </div>
431
+ </div>
432
+ <div className="query-builder__dialog__content">
433
+ <CubeEditor
434
+ host={host}
435
+ initialLambda={initialLambda}
436
+ headerActions={(editorState) => (
437
+ <MaterializedViewButton
438
+ embeddedCubeState={embeddedCubeState}
439
+ editorState={editorState}
440
+ />
441
+ )}
442
+ />
443
+ </div>
444
+ </Modal>
445
+ </Dialog>
446
+ );
447
+ },
448
+ );
@@ -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