@finos/legend-application-studio 28.21.23 → 28.21.25

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 (92) hide show
  1. package/lib/__lib__/LegendStudioDocumentation.d.ts +2 -2
  2. package/lib/__lib__/LegendStudioDocumentation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioDocumentation.js +1 -1
  4. package/lib/__lib__/LegendStudioDocumentation.js.map +1 -1
  5. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  6. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +2 -5
  7. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  8. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +13 -239
  10. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  11. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
  12. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +17 -3
  13. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
  14. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts +22 -0
  15. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts.map +1 -0
  16. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js +134 -0
  17. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js.map +1 -0
  18. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts +89 -0
  19. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts.map +1 -0
  20. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js +144 -0
  21. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js.map +1 -0
  22. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  23. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +14 -8
  24. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  25. package/lib/components/editor/ingest-definition-editor.css +1 -1
  26. package/lib/components/editor/ingest-definition-editor.css.map +1 -1
  27. package/lib/components/project-reviewer/ProjectReviewer.d.ts.map +1 -1
  28. package/lib/components/project-reviewer/ProjectReviewer.js +2 -1
  29. package/lib/components/project-reviewer/ProjectReviewer.js.map +1 -1
  30. package/lib/components/project-view/ProjectViewer.d.ts.map +1 -1
  31. package/lib/components/project-view/ProjectViewer.js +2 -2
  32. package/lib/components/project-view/ProjectViewer.js.map +1 -1
  33. package/lib/components/showcase/ShowcaseViewer.d.ts.map +1 -1
  34. package/lib/components/showcase/ShowcaseViewer.js +2 -2
  35. package/lib/components/showcase/ShowcaseViewer.js.map +1 -1
  36. package/lib/components/workspace-setup/WorkspaceSetup.d.ts +1 -1
  37. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  38. package/lib/components/workspace-setup/WorkspaceSetup.js +98 -43
  39. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  40. package/lib/index.css +2 -2
  41. package/lib/index.css.map +1 -1
  42. package/lib/package.json +1 -1
  43. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  44. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +1 -2
  45. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  46. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  47. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +1 -0
  48. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  49. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +8 -30
  50. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  51. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +7 -147
  52. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  53. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  54. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +32 -28
  55. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  56. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts +8 -0
  57. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts.map +1 -1
  58. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js +15 -0
  59. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js.map +1 -1
  60. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts +86 -0
  61. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts.map +1 -0
  62. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js +420 -0
  63. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js.map +1 -0
  64. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts +62 -0
  65. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts.map +1 -0
  66. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js +138 -0
  67. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js.map +1 -0
  68. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +10 -1
  69. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  70. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +105 -12
  71. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  72. package/package.json +11 -11
  73. package/src/__lib__/LegendStudioDocumentation.ts +1 -1
  74. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +2 -5
  75. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +19 -869
  76. package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +90 -34
  77. package/src/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.tsx +418 -0
  78. package/src/components/editor/editor-group/testable/LakehouseTestableEditor.tsx +564 -0
  79. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +17 -9
  80. package/src/components/project-reviewer/ProjectReviewer.tsx +2 -0
  81. package/src/components/project-view/ProjectViewer.tsx +2 -1
  82. package/src/components/showcase/ShowcaseViewer.tsx +2 -1
  83. package/src/components/workspace-setup/WorkspaceSetup.tsx +169 -75
  84. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +1 -2
  85. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +1 -0
  86. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +18 -204
  87. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +41 -36
  88. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.ts +16 -0
  89. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.ts +654 -0
  90. package/src/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.ts +224 -0
  91. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +125 -8
  92. package/tsconfig.json +4 -0
@@ -0,0 +1,564 @@
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
+ /**
18
+ * Shared React components for Lakehouse test editors (DataProduct and Ingest).
19
+ *
20
+ * Exports:
21
+ * - LakehouseElementTestDataItem — sidebar row for a single element's test data
22
+ * - LakehouseElementTestDataEditor — editor for a selected element's RelationElementsData
23
+ * - LakehouseAddElementModal — modal to add a new element to suite test data
24
+ * - LakehouseTestDataEditor — full test-data panel (element list + editor)
25
+ * - LakehouseTestEditor — assertion viewer for a single test
26
+ * - LakehouseTestsEditor — tests list + test-detail panel
27
+ * - LakehouseTestSuiteEditor — horizontal split: test data + tests
28
+ * - LakehouseSuiteStateForEditor — interface for suite state passed to shared components
29
+ * - LakehouseTestableStateForEditor — interface for top-level testable state
30
+ */
31
+
32
+ import { observer } from 'mobx-react-lite';
33
+ import { flowResult } from 'mobx';
34
+ import {
35
+ BlankPanelContent,
36
+ BlankPanelPlaceholder,
37
+ clsx,
38
+ CustomSelectorInput,
39
+ Dialog,
40
+ ErrorWarnIcon,
41
+ Modal,
42
+ ModalBody,
43
+ ModalFooter,
44
+ ModalFooterButton,
45
+ ModalHeader,
46
+ ModalTitle,
47
+ PlusIcon,
48
+ PlayIcon,
49
+ ResizablePanel,
50
+ ResizablePanelGroup,
51
+ ResizablePanelSplitter,
52
+ ResizablePanelSplitterLine,
53
+ RunAllIcon,
54
+ TimesIcon,
55
+ } from '@finos/legend-art';
56
+ import type { AtomicTest, TestSuite } from '@finos/legend-graph';
57
+ import type { ActionState, GeneratorFn } from '@finos/legend-shared';
58
+ import { useState } from 'react';
59
+ import type { TestableTestEditorState } from '../../../../stores/editor/editor-state/element-editor-state/testable/TestableEditorState.js';
60
+ import {
61
+ TESTABLE_RESULT,
62
+ getTestableResultFromTestResult,
63
+ } from '../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
64
+ import { getTestableResultIcon } from '../../side-bar/testable/GlobalTestRunner.js';
65
+ import type {
66
+ LakehouseElementTestDataState,
67
+ LakehouseTestDataStateBase,
68
+ } from '../../../../stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js';
69
+ import { RelationElementsDataEditor } from '../data-editor/RelationElementsDataEditor.js';
70
+ import { TestAssertionEditor } from './TestableSharedComponents.js';
71
+ import type { EditorStore } from '../../../../stores/editor/EditorStore.js';
72
+
73
+ // ─── Public interfaces ────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Minimal interface for a suite state object accepted by LakehouseTestsEditor
77
+ * and LakehouseTestSuiteEditor. Satisfied by both IngestTestSuiteState and
78
+ * DataProductTestSuiteState.
79
+ */
80
+ export interface LakehouseSuiteStateForEditor {
81
+ testDataState: LakehouseTestDataStateBase;
82
+ testStates: TestableTestEditorState[];
83
+ selectTestState: TestableTestEditorState | undefined;
84
+ runningSuiteState: ActionState;
85
+ runSuite(): GeneratorFn<void>;
86
+ changeTest(test: AtomicTest): void;
87
+ deleteTest(test: AtomicTest): void;
88
+ suite: TestSuite;
89
+ editorStore: EditorStore;
90
+ }
91
+
92
+ /**
93
+ * Minimal interface for the top-level testable state passed to the tests editor.
94
+ * Satisfied by both IngestTestableState and DataProductTestableState.
95
+ */
96
+ export interface LakehouseTestableStateForEditor {
97
+ setShowCreateTestModal(val: boolean): void;
98
+ editorStore: EditorStore;
99
+ }
100
+
101
+ // ─── Element test data: list item ─────────────────────────────────────────────
102
+
103
+ export const LakehouseElementTestDataItem = observer(
104
+ (props: {
105
+ elementState: LakehouseElementTestDataState;
106
+ testDataState: LakehouseTestDataStateBase;
107
+ isReadOnly: boolean;
108
+ }) => {
109
+ const { elementState, testDataState, isReadOnly } = props;
110
+ const isActive =
111
+ testDataState.selectedElementTestDataState === elementState;
112
+
113
+ const select = (): void =>
114
+ testDataState.setSelectedElementTestDataState(elementState);
115
+
116
+ return (
117
+ <div
118
+ className={clsx('testable-test-explorer__item', {
119
+ 'testable-test-explorer__item--active': isActive,
120
+ })}
121
+ >
122
+ <div
123
+ className="testable-test-explorer__item__label"
124
+ onClick={select}
125
+ tabIndex={-1}
126
+ >
127
+ <div className="testable-test-explorer__item__label__text">
128
+ <span title={elementState.element.path}>
129
+ {elementState.element.name}
130
+ </span>
131
+ </div>
132
+ {!isReadOnly && (
133
+ <div className="mapping-test-explorer__item__actions">
134
+ <button
135
+ className="mapping-test-explorer__item__action"
136
+ onClick={(e): void => {
137
+ e.stopPropagation();
138
+ testDataState.deleteElement(elementState);
139
+ }}
140
+ tabIndex={-1}
141
+ title="Delete"
142
+ >
143
+ <TimesIcon />
144
+ </button>
145
+ </div>
146
+ )}
147
+ </div>
148
+ </div>
149
+ );
150
+ },
151
+ );
152
+
153
+ // ─── Element test data: editor ────────────────────────────────────────────────
154
+
155
+ export const LakehouseElementTestDataEditor = observer(
156
+ (props: {
157
+ elementState: LakehouseElementTestDataState;
158
+ isReadOnly: boolean;
159
+ }) => {
160
+ const { elementState, isReadOnly } = props;
161
+ const dataState = elementState.relationElementsDataState;
162
+
163
+ if (!dataState) {
164
+ return (
165
+ <BlankPanelContent>No relation data for this element</BlankPanelContent>
166
+ );
167
+ }
168
+
169
+ return (
170
+ <RelationElementsDataEditor
171
+ dataState={dataState}
172
+ isReadOnly={isReadOnly}
173
+ hideColumnDefinitions={true}
174
+ />
175
+ );
176
+ },
177
+ );
178
+
179
+ // ─── Add element modal ────────────────────────────────────────────────────────
180
+
181
+ export const LakehouseAddElementModal = observer(
182
+ (props: { testDataState: LakehouseTestDataStateBase }) => {
183
+ const { testDataState } = props;
184
+ const applicationStore = testDataState.editorStore.applicationStore;
185
+ const options = testDataState.availableElementsToAdd.map((e) => ({
186
+ value: e.path,
187
+ label: e.path,
188
+ }));
189
+ const [selectedPath, setSelectedPath] = useState<string | undefined>(
190
+ options[0]?.value,
191
+ );
192
+ const close = (): void => testDataState.setShowAddElementModal(false);
193
+ const add = (): void => {
194
+ if (selectedPath) {
195
+ testDataState.addElement(selectedPath);
196
+ close();
197
+ }
198
+ };
199
+ const onChange = (val: { label: string; value: string } | null): void => {
200
+ setSelectedPath(val?.value);
201
+ };
202
+
203
+ return (
204
+ <Dialog
205
+ open={testDataState.showAddElementModal}
206
+ onClose={close}
207
+ classes={{ container: 'search-modal__container' }}
208
+ slotProps={{
209
+ paper: { classes: { root: 'search-modal__inner-container' } },
210
+ }}
211
+ >
212
+ <Modal
213
+ darkMode={
214
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
215
+ }
216
+ >
217
+ <ModalHeader>
218
+ <ModalTitle title="Add Element" />
219
+ </ModalHeader>
220
+ <ModalBody>
221
+ <CustomSelectorInput
222
+ className="panel__content__form__section__dropdown"
223
+ options={options}
224
+ onChange={onChange}
225
+ value={
226
+ selectedPath
227
+ ? { value: selectedPath, label: selectedPath }
228
+ : null
229
+ }
230
+ placeholder="Select element..."
231
+ darkMode={
232
+ !applicationStore.layoutService
233
+ .TEMPORARY__isLightColorThemeEnabled
234
+ }
235
+ />
236
+ </ModalBody>
237
+ <ModalFooter>
238
+ <ModalFooterButton
239
+ disabled={!selectedPath}
240
+ onClick={add}
241
+ text="Add"
242
+ />
243
+ <ModalFooterButton onClick={close} text="Close" type="secondary" />
244
+ </ModalFooter>
245
+ </Modal>
246
+ </Dialog>
247
+ );
248
+ },
249
+ );
250
+
251
+ // ─── Test data editor (full panel: element list + per-element editor) ─────────
252
+
253
+ export const LakehouseTestDataEditor = observer(
254
+ (props: {
255
+ testDataState: LakehouseTestDataStateBase;
256
+ isReadOnly: boolean;
257
+ }) => {
258
+ const { testDataState, isReadOnly } = props;
259
+
260
+ const addElement = (): void => {
261
+ if (testDataState.availableElementsToAdd.length === 0) {
262
+ testDataState.editorStore.applicationStore.notificationService.notifyWarning(
263
+ 'No elements available to add',
264
+ );
265
+ return;
266
+ }
267
+ testDataState.setShowAddElementModal(true);
268
+ };
269
+
270
+ const hasTestData = testDataState.elementTestDataStates.length > 0;
271
+
272
+ return (
273
+ <div
274
+ className={clsx('service-test-data-editor panel', {
275
+ 'service-test-data-editor--no-data': !hasTestData,
276
+ })}
277
+ >
278
+ <div className="service-test-data-editor__data">
279
+ <ResizablePanelGroup orientation="vertical">
280
+ <ResizablePanel minSize={100} size={180}>
281
+ <div className="binding-editor__header">
282
+ <div className="binding-editor__header__title">
283
+ <div className="panel__header__title__content">Test Data</div>
284
+ </div>
285
+ {!isReadOnly && (
286
+ <div className="panel__header__actions">
287
+ <button
288
+ className="panel__header__action"
289
+ tabIndex={-1}
290
+ onClick={addElement}
291
+ title="Add Element"
292
+ >
293
+ <PlusIcon />
294
+ </button>
295
+ </div>
296
+ )}
297
+ </div>
298
+ {!hasTestData ? (
299
+ <div className="service-test-data-editor__warning">
300
+ <ErrorWarnIcon />
301
+ <span>Add an element to configure test data</span>
302
+ </div>
303
+ ) : (
304
+ <div>
305
+ {testDataState.elementTestDataStates.map((elementState) => (
306
+ <LakehouseElementTestDataItem
307
+ key={elementState.element.path}
308
+ elementState={elementState}
309
+ testDataState={testDataState}
310
+ isReadOnly={isReadOnly}
311
+ />
312
+ ))}
313
+ </div>
314
+ )}
315
+ </ResizablePanel>
316
+ <ResizablePanelSplitter>
317
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
318
+ </ResizablePanelSplitter>
319
+ <ResizablePanel minSize={200}>
320
+ {testDataState.selectedElementTestDataState ? (
321
+ <LakehouseElementTestDataEditor
322
+ elementState={testDataState.selectedElementTestDataState}
323
+ isReadOnly={isReadOnly}
324
+ />
325
+ ) : (
326
+ <BlankPanelContent>
327
+ Select an element to configure its test data
328
+ </BlankPanelContent>
329
+ )}
330
+ </ResizablePanel>
331
+ </ResizablePanelGroup>
332
+ </div>
333
+ {testDataState.showAddElementModal && (
334
+ <LakehouseAddElementModal testDataState={testDataState} />
335
+ )}
336
+ </div>
337
+ );
338
+ },
339
+ );
340
+
341
+ // ─── Test editor: assertion viewer ────────────────────────────────────────────
342
+
343
+ export const LakehouseTestEditor = observer(
344
+ (props: { testState: TestableTestEditorState; isReadOnly: boolean }) => {
345
+ const { testState } = props;
346
+ const selectedAssertion = testState.selectedAsertionState;
347
+
348
+ return (
349
+ <div className="function-test-editor panel">
350
+ <div className="panel__header">
351
+ <div className="panel__header service-test-editor__header--with-tabs">
352
+ <div className="panel__header__title__content">Assertion</div>
353
+ </div>
354
+ </div>
355
+ <div className="panel">
356
+ {selectedAssertion && (
357
+ <TestAssertionEditor testAssertionState={selectedAssertion} />
358
+ )}
359
+ {!selectedAssertion && (
360
+ <BlankPanelPlaceholder
361
+ text="No assertion"
362
+ tooltipText="No assertion configured for this test"
363
+ />
364
+ )}
365
+ </div>
366
+ </div>
367
+ );
368
+ },
369
+ );
370
+
371
+ // ─── Test item: list row ──────────────────────────────────────────────────────
372
+
373
+ const LakehouseTestItem = observer(
374
+ (props: {
375
+ testState: TestableTestEditorState;
376
+ suiteState: LakehouseSuiteStateForEditor;
377
+ isReadOnly: boolean;
378
+ }) => {
379
+ const { testState, suiteState, isReadOnly } = props;
380
+ const isActive = suiteState.selectTestState === testState;
381
+ const isRunning = testState.runningTestAction.isInProgress;
382
+ const _testableResult = getTestableResultFromTestResult(
383
+ testState.testResultState.result,
384
+ );
385
+ const testResult = isRunning
386
+ ? TESTABLE_RESULT.IN_PROGRESS
387
+ : _testableResult;
388
+
389
+ const select = (): void => suiteState.changeTest(testState.test);
390
+ const runTest = (): void => {
391
+ flowResult(testState.runTest()).catch(
392
+ testState.editorStore.applicationStore.alertUnhandledError,
393
+ );
394
+ };
395
+ const deleteTest = (): void => {
396
+ if (!isReadOnly) {
397
+ suiteState.deleteTest(testState.test);
398
+ }
399
+ };
400
+
401
+ return (
402
+ <div
403
+ className={clsx('testable-test-explorer__item', {
404
+ 'testable-test-explorer__item--active': isActive,
405
+ })}
406
+ >
407
+ <div
408
+ className="testable-test-explorer__item__label"
409
+ onClick={select}
410
+ tabIndex={-1}
411
+ >
412
+ <div className="testable-test-explorer__item__label__icon">
413
+ {getTestableResultIcon(testResult)}
414
+ </div>
415
+ <div className="testable-test-explorer__item__label__text">
416
+ {testState.test.id}
417
+ </div>
418
+ </div>
419
+ <div className="mapping-test-explorer__item__actions">
420
+ <button
421
+ className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
422
+ onClick={runTest}
423
+ disabled={isRunning}
424
+ tabIndex={-1}
425
+ title={`Run test ${testState.test.id}`}
426
+ >
427
+ <PlayIcon />
428
+ </button>
429
+ {!isReadOnly && (
430
+ <button
431
+ className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
432
+ onClick={deleteTest}
433
+ tabIndex={-1}
434
+ title={`Delete test ${testState.test.id}`}
435
+ >
436
+ <TimesIcon />
437
+ </button>
438
+ )}
439
+ </div>
440
+ </div>
441
+ );
442
+ },
443
+ );
444
+
445
+ // ─── Tests editor: test list + test detail ────────────────────────────────────
446
+
447
+ export const LakehouseTestsEditor = observer(
448
+ (props: {
449
+ suiteState: LakehouseSuiteStateForEditor;
450
+ testableState: LakehouseTestableStateForEditor;
451
+ isReadOnly: boolean;
452
+ }) => {
453
+ const { suiteState, testableState, isReadOnly } = props;
454
+ const selectedTest = suiteState.selectTestState;
455
+
456
+ return (
457
+ <div className="panel service-test-editor">
458
+ <div className="service-test-editor__content">
459
+ <ResizablePanelGroup orientation="vertical">
460
+ <ResizablePanel minSize={100} size={200}>
461
+ <div className="binding-editor__header">
462
+ <div className="binding-editor__header__title">
463
+ <div className="panel__header__title__content">Tests</div>
464
+ </div>
465
+ <div className="panel__header__actions">
466
+ <button
467
+ className="panel__header__action testable-test-explorer__play__all__icon"
468
+ tabIndex={-1}
469
+ onClick={(): void => {
470
+ flowResult(suiteState.runSuite()).catch(
471
+ testableState.editorStore.applicationStore
472
+ .alertUnhandledError,
473
+ );
474
+ }}
475
+ disabled={
476
+ suiteState.runningSuiteState.isInProgress ||
477
+ suiteState.suite.tests.length === 0
478
+ }
479
+ title="Run all tests in this suite"
480
+ >
481
+ <RunAllIcon />
482
+ </button>
483
+ {!isReadOnly && (
484
+ <button
485
+ className="panel__header__action"
486
+ tabIndex={-1}
487
+ onClick={(): void =>
488
+ testableState.setShowCreateTestModal(true)
489
+ }
490
+ title="Add test to this suite"
491
+ >
492
+ <PlusIcon />
493
+ </button>
494
+ )}
495
+ </div>
496
+ </div>
497
+ <div>
498
+ {suiteState.testStates.map((testState) => (
499
+ <LakehouseTestItem
500
+ key={testState.test.id}
501
+ testState={testState}
502
+ suiteState={suiteState}
503
+ isReadOnly={isReadOnly}
504
+ />
505
+ ))}
506
+ </div>
507
+ </ResizablePanel>
508
+ <ResizablePanelSplitter>
509
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
510
+ </ResizablePanelSplitter>
511
+ <ResizablePanel minSize={56}>
512
+ {selectedTest ? (
513
+ <LakehouseTestEditor
514
+ testState={selectedTest}
515
+ isReadOnly={isReadOnly}
516
+ />
517
+ ) : (
518
+ <BlankPanelPlaceholder
519
+ text="Select a test"
520
+ tooltipText="Select a test from the list above"
521
+ />
522
+ )}
523
+ </ResizablePanel>
524
+ </ResizablePanelGroup>
525
+ </div>
526
+ </div>
527
+ );
528
+ },
529
+ );
530
+
531
+ // ─── Suite editor: horizontal split (test data + tests) ───────────────────────
532
+
533
+ export const LakehouseTestSuiteEditor = observer(
534
+ (props: {
535
+ suiteState: LakehouseSuiteStateForEditor;
536
+ testableState: LakehouseTestableStateForEditor;
537
+ isReadOnly: boolean;
538
+ }) => {
539
+ const { suiteState, testableState, isReadOnly } = props;
540
+
541
+ return (
542
+ <div className="service-test-suite-editor">
543
+ <ResizablePanelGroup orientation="horizontal">
544
+ <ResizablePanel size={580} minSize={28}>
545
+ <LakehouseTestDataEditor
546
+ testDataState={suiteState.testDataState}
547
+ isReadOnly={isReadOnly}
548
+ />
549
+ </ResizablePanel>
550
+ <ResizablePanelSplitter>
551
+ <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
552
+ </ResizablePanelSplitter>
553
+ <ResizablePanel minSize={56}>
554
+ <LakehouseTestsEditor
555
+ suiteState={suiteState}
556
+ testableState={testableState}
557
+ isReadOnly={isReadOnly}
558
+ />
559
+ </ResizablePanel>
560
+ </ResizablePanelGroup>
561
+ </div>
562
+ );
563
+ },
564
+ );
@@ -40,6 +40,7 @@ import {
40
40
  import {
41
41
  type DataElement,
42
42
  DataProduct,
43
+ IngestDefinition,
43
44
  type ValueSpecification,
44
45
  type VariableExpression,
45
46
  type PrimitiveInstanceValue,
@@ -87,6 +88,7 @@ import {
87
88
  } from '@finos/legend-query-builder';
88
89
  import { useApplicationStore } from '@finos/legend-application';
89
90
  import type { DataProductTestState } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js';
91
+ import type { IngestTestState } from '../../../../stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js';
90
92
 
91
93
  export const SharedDataElementModal = observer(
92
94
  (props: {
@@ -601,11 +603,14 @@ const EqualToRelationAsssertionEditor = observer(
601
603
  },
602
604
  );
603
605
 
604
- const DataProductEqualToRelationAssertionEditor = observer(
606
+ const LakehouseEqualToRelationAssertionEditor = observer(
605
607
  (props: { testAssertionEditorState: TestAssertionEditorState }) => {
606
608
  const { testAssertionEditorState } = props;
607
- const testState =
608
- testAssertionEditorState.testState as DataProductTestState;
609
+ const isDataProductTest =
610
+ testAssertionEditorState.testState.testable instanceof DataProduct;
611
+ const testState = isDataProductTest
612
+ ? (testAssertionEditorState.testState as DataProductTestState)
613
+ : (testAssertionEditorState.testState as IngestTestState);
609
614
  const isReadOnly = testAssertionEditorState.testState.isReadOnly;
610
615
  const relationElementState = testState.testDataRelationState;
611
616
 
@@ -613,7 +618,9 @@ const DataProductEqualToRelationAssertionEditor = observer(
613
618
  <div className="service-test-data-editor panel">
614
619
  <div className="panel__content__form__section">
615
620
  <div className="panel__content__form__section__header__label">
616
- Access Point: {testState.accessPointLabel}
621
+ {isDataProductTest
622
+ ? `Access Point: ${(testState as DataProductTestState).accessPointLabel}`
623
+ : `Dataset: ${(testState as IngestTestState).datasetLabel}`}
617
624
  </div>
618
625
  </div>
619
626
  {relationElementState ? (
@@ -977,8 +984,9 @@ export const TestAssertionEditor = observer(
977
984
  const { testAssertionState } = props;
978
985
  const selectedTab = testAssertionState.selectedTab;
979
986
  const isReadOnly = testAssertionState.testState.isReadOnly;
980
- const isDataProductTest =
981
- testAssertionState.testState.testable instanceof DataProduct;
987
+ const isLakehouseTest =
988
+ testAssertionState.testState.testable instanceof DataProduct ||
989
+ testAssertionState.testState.testable instanceof IngestDefinition;
982
990
  const isDisabled =
983
991
  isReadOnly ||
984
992
  !testAssertionState.assertionState.supportsGeneratingAssertion ||
@@ -1001,9 +1009,9 @@ export const TestAssertionEditor = observer(
1001
1009
  />
1002
1010
  );
1003
1011
  } else if (state instanceof EqualToRelationAssertionState) {
1004
- if (isDataProductTest) {
1012
+ if (isLakehouseTest) {
1005
1013
  return (
1006
- <DataProductEqualToRelationAssertionEditor
1014
+ <LakehouseEqualToRelationAssertionEditor
1007
1015
  testAssertionEditorState={testAssertionState}
1008
1016
  />
1009
1017
  );
@@ -1045,7 +1053,7 @@ export const TestAssertionEditor = observer(
1045
1053
  </div>
1046
1054
  ))}
1047
1055
  </div>
1048
- {!isDataProductTest && (
1056
+ {!isLakehouseTest && (
1049
1057
  <div className="testable-test-assertion-editor__header__actions">
1050
1058
  <button
1051
1059
  className="panel__header__action service-execution-editor__test-data__generate-btn"
@@ -44,6 +44,7 @@ import {
44
44
  useEditorStore,
45
45
  withEditorStore,
46
46
  } from '../editor/EditorStoreProvider.js';
47
+ import { ColorThemeToggle } from '../editor/ActivityBar.js';
47
48
  import { useApplicationStore } from '@finos/legend-application';
48
49
  import { useParams } from '@finos/legend-application/browser';
49
50
  import { guaranteeNonNullable } from '@finos/legend-shared';
@@ -220,6 +221,7 @@ export const ProjectReviewer = withEditorStore(
220
221
  <CheckListIcon />
221
222
  </button>
222
223
  </div>
224
+ <ColorThemeToggle />
223
225
  </div>
224
226
  <div className="workspace-review__content-container">
225
227
  <div className="workspace-review__content">
@@ -71,7 +71,7 @@ import {
71
71
  } from '../LegendStudioFrameworkProvider.js';
72
72
  import { EmbeddedQueryBuilder } from '../editor/EmbeddedQueryBuilder.js';
73
73
  import type { ActivityBarItemConfig } from '@finos/legend-lego/application';
74
- import { ActivityBarMenu } from '../editor/ActivityBar.js';
74
+ import { ActivityBarMenu, ColorThemeToggle } from '../editor/ActivityBar.js';
75
75
  import { PanelGroup } from '../editor/panel-group/PanelGroup.js';
76
76
  import { StoreProjectData } from '@finos/legend-server-depot';
77
77
  import { generateGAVCoordinates } from '@finos/legend-storage';
@@ -425,6 +425,7 @@ const ProjectViewerActivityBar = observer(() => {
425
425
  </button>
426
426
  ))}
427
427
  </div>
428
+ <ColorThemeToggle />
428
429
  </div>
429
430
  );
430
431
  });
@@ -55,7 +55,7 @@ import {
55
55
  ACTIVITY_MODE,
56
56
  GRAPH_EDITOR_MODE,
57
57
  } from '../../stores/editor/EditorConfig.js';
58
- import { ActivityBarMenu } from '../editor/ActivityBar.js';
58
+ import { ActivityBarMenu, ColorThemeToggle } from '../editor/ActivityBar.js';
59
59
  import type { ActivityBarItemConfig } from '@finos/legend-lego/application';
60
60
  import { useLegendStudioApplicationStore } from '../LegendStudioFrameworkProvider.js';
61
61
  import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js';
@@ -253,6 +253,7 @@ const ShowcaseViewerActivityBar = observer(() => {
253
253
  </button>
254
254
  ))}
255
255
  </div>
256
+ <ColorThemeToggle />
256
257
  </div>
257
258
  );
258
259
  });