@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
@@ -30,8 +30,13 @@ import {
30
30
  EyeIcon,
31
31
  ModalHeaderActions,
32
32
  CustomSelectorInput,
33
+ FlaskIcon,
34
+ clsx,
33
35
  } from '@finos/legend-art';
34
- import { IngestDefinitionEditorState } from '../../../../stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js';
36
+ import {
37
+ INGEST_DEFINITION_TAB,
38
+ IngestDefinitionEditorState,
39
+ } from '../../../../stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js';
35
40
  import { CodeEditor } from '@finos/legend-lego/code-editor';
36
41
  import React, { useEffect, useState } from 'react';
37
42
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
@@ -44,6 +49,7 @@ import {
44
49
  LineageViewerContent,
45
50
  } from '@finos/legend-query-builder';
46
51
  import type { MatViewDataSet } from '@finos/legend-graph';
52
+ import { IngestTestableEditor } from './testable/IngestTestableEditor.js';
47
53
 
48
54
  export const IngestLineageModal = observer(
49
55
  (props: { ingestDefinitionEditorState: IngestDefinitionEditorState }) => {
@@ -171,6 +177,19 @@ export const IngestDefinitionEditor = observer(() => {
171
177
  );
172
178
  const ingestDef = ingestDefinitionEditorState.ingest;
173
179
  const isValidForLineage = ingestDefinitionEditorState.validForLineageViewer;
180
+ const selectedTab = ingestDefinitionEditorState.selectedTab;
181
+
182
+ const sidebarTabs = [
183
+ {
184
+ label: INGEST_DEFINITION_TAB.DEFINITION,
185
+ icon: <EyeIcon />,
186
+ },
187
+ {
188
+ label: INGEST_DEFINITION_TAB.TESTING,
189
+ icon: <FlaskIcon />,
190
+ },
191
+ ];
192
+
174
193
  useEffect(() => {
175
194
  ingestDefinitionEditorState.generateElementGrammar();
176
195
  }, [ingestDefinitionEditorState]);
@@ -195,44 +214,81 @@ export const IngestDefinitionEditor = observer(() => {
195
214
  };
196
215
 
197
216
  return (
198
- <div className="data-product-editor">
199
- <PanelHeader
200
- title="Ingest"
201
- titleContent={ingestDef.name}
202
- darkMode={true}
203
- isReadOnly={true}
204
- ></PanelHeader>
205
- <PanelContent>
206
- <PanelHeader title="deployment" darkMode={true}>
207
- <PanelHeaderActions>
208
- <div className="panel__header__actions">
209
- <div className="btn__dropdown-combo btn__dropdown-combo--primary">
217
+ <div className="ingest-definition-editor">
218
+ <div className="panel">
219
+ <PanelHeader
220
+ title="Ingest"
221
+ titleContent={ingestDef.name}
222
+ darkMode={true}
223
+ isReadOnly={true}
224
+ />
225
+ <div className="panel ingest-definition-editor__content-panel">
226
+ <div className="ingest-definition-editor__activity-bar">
227
+ <div className="ingest-definition-editor__activity-bar__items">
228
+ {sidebarTabs.map((activity) => (
210
229
  <button
211
- className="btn__dropdown-combo__label"
212
- onClick={viewLineage}
230
+ key={activity.label}
231
+ className={clsx(
232
+ 'ingest-definition-editor__activity-bar__item',
233
+ {
234
+ 'ingest-definition-editor__activity-bar__item--active':
235
+ selectedTab === activity.label,
236
+ },
237
+ )}
238
+ onClick={() =>
239
+ ingestDefinitionEditorState.setSelectedTab(activity.label)
240
+ }
213
241
  tabIndex={-1}
214
- disabled={!isValidForLineage}
242
+ title={activity.label}
215
243
  >
216
- <EyeIcon className="btn__dropdown-combo__label__icon" />
217
- <div className="btn__dropdown-combo__label__title">
218
- Lineage
219
- </div>
244
+ {activity.icon}
245
+ {activity.label}
220
246
  </button>
221
- </div>
247
+ ))}
222
248
  </div>
223
- </PanelHeaderActions>
224
- </PanelHeader>
225
- <PanelContent>
226
- <CodeEditor
227
- inputValue={ingestDefinitionEditorState.textContent}
228
- isReadOnly={true}
229
- language={CODE_EDITOR_LANGUAGE.PURE}
230
- />
231
- </PanelContent>
232
- <IngestLineageModal
233
- ingestDefinitionEditorState={ingestDefinitionEditorState}
234
- />
235
- </PanelContent>
249
+ </div>
250
+ <div className="panel ingest-definition-editor__main-panel">
251
+ {selectedTab === INGEST_DEFINITION_TAB.DEFINITION && (
252
+ <PanelContent>
253
+ <PanelHeader title="deployment" darkMode={true}>
254
+ <PanelHeaderActions>
255
+ <div className="panel__header__actions">
256
+ <div className="btn__dropdown-combo btn__dropdown-combo--primary">
257
+ <button
258
+ className="btn__dropdown-combo__label"
259
+ onClick={viewLineage}
260
+ tabIndex={-1}
261
+ disabled={!isValidForLineage}
262
+ >
263
+ <EyeIcon className="btn__dropdown-combo__label__icon" />
264
+ <div className="btn__dropdown-combo__label__title">
265
+ Lineage
266
+ </div>
267
+ </button>
268
+ </div>
269
+ </div>
270
+ </PanelHeaderActions>
271
+ </PanelHeader>
272
+ <PanelContent>
273
+ <CodeEditor
274
+ inputValue={ingestDefinitionEditorState.textContent}
275
+ isReadOnly={true}
276
+ language={CODE_EDITOR_LANGUAGE.PURE}
277
+ />
278
+ </PanelContent>
279
+ <IngestLineageModal
280
+ ingestDefinitionEditorState={ingestDefinitionEditorState}
281
+ />
282
+ </PanelContent>
283
+ )}
284
+ {selectedTab === INGEST_DEFINITION_TAB.TESTING && (
285
+ <IngestTestableEditor
286
+ testableState={ingestDefinitionEditorState.ingestTestableState}
287
+ />
288
+ )}
289
+ </div>
290
+ </div>
291
+ </div>
236
292
  </div>
237
293
  );
238
294
  });
@@ -0,0 +1,418 @@
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 { observer } from 'mobx-react-lite';
18
+ import { flowResult } from 'mobx';
19
+ import {
20
+ BlankPanelPlaceholder,
21
+ clsx,
22
+ ContextMenu,
23
+ CustomSelectorInput,
24
+ Dialog,
25
+ MenuContent,
26
+ MenuContentItem,
27
+ Modal,
28
+ ModalBody,
29
+ ModalFooter,
30
+ ModalFooterButton,
31
+ ModalHeader,
32
+ ModalTitle,
33
+ Panel,
34
+ PanelFormTextField,
35
+ PanelHeader,
36
+ PanelHeaderActionItem,
37
+ PanelHeaderActions,
38
+ PlusIcon,
39
+ } from '@finos/legend-art';
40
+ import type {
41
+ IngestTestableState,
42
+ IngestTestSuiteState,
43
+ } from '../../../../../stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js';
44
+ import { forwardRef, useRef, useState } from 'react';
45
+ import { useEditorStore } from '../../../EditorStoreProvider.js';
46
+ import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js';
47
+ import { RenameModal } from '../../testable/TestableSharedComponents.js';
48
+ import type { IngestTestSuite } from '@finos/legend-graph';
49
+ import { guaranteeNonNullable } from '@finos/legend-shared';
50
+ import { testSuite_setId } from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
51
+ import { LakehouseTestSuiteEditor } from '../../testable/LakehouseTestableEditor.js';
52
+
53
+ // ─── Create Suite Modal ───────────────────────────────────────────────────────
54
+
55
+ interface ItemOption {
56
+ value: string;
57
+ label: string;
58
+ }
59
+
60
+ const CreateSuiteModal = observer(
61
+ (props: { testableState: IngestTestableState; onClose: () => void }) => {
62
+ const { testableState, onClose } = props;
63
+ const editorStore = useEditorStore();
64
+ const applicationStore = editorStore.applicationStore;
65
+ const inputRef = useRef<HTMLInputElement>(null);
66
+
67
+ const [testName, setTestName] = useState<string | undefined>(undefined);
68
+ const [datasetName, setDatasetName] = useState<string | undefined>(
69
+ testableState.datasetNames[0],
70
+ );
71
+
72
+ const existingIds = testableState.ingest.tests.map((suite) => suite.id);
73
+ const generateSuiteName = (): string => {
74
+ let idx = 1;
75
+ while (existingIds.includes(`suite_${idx}`)) {
76
+ idx++;
77
+ }
78
+ return `suite_${idx}`;
79
+ };
80
+
81
+ const testError = validateTestableId(testName, undefined);
82
+ const datasetOptions: ItemOption[] = testableState.datasetNames.map(
83
+ (name) => ({
84
+ value: name,
85
+ label: name,
86
+ }),
87
+ );
88
+
89
+ const selectedDatasetOption =
90
+ datasetOptions.find((option) => option.value === datasetName) ?? null;
91
+
92
+ const isValid = testName && !testError && datasetName;
93
+
94
+ const create = (): void => {
95
+ if (!testName || !datasetName) {
96
+ return;
97
+ }
98
+ flowResult(
99
+ testableState.createSuite(generateSuiteName(), testName, datasetName),
100
+ )
101
+ .then((error) => {
102
+ if (error) {
103
+ applicationStore.notificationService.notifyError(error);
104
+ } else {
105
+ onClose();
106
+ }
107
+ })
108
+ .catch(applicationStore.alertUnhandledError);
109
+ };
110
+
111
+ return (
112
+ <Dialog
113
+ open={true}
114
+ onClose={onClose}
115
+ classes={{ container: 'search-modal__container' }}
116
+ slotProps={{
117
+ transition: { onEnter: () => inputRef.current?.focus() },
118
+ paper: { classes: { root: 'search-modal__inner-container' } },
119
+ }}
120
+ >
121
+ <Modal
122
+ darkMode={
123
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
124
+ }
125
+ >
126
+ <ModalHeader>
127
+ <ModalTitle title="Create Test Suite" />
128
+ </ModalHeader>
129
+ <ModalBody>
130
+ <PanelFormTextField
131
+ ref={inputRef}
132
+ name="Test Name"
133
+ prompt="Name for the first test in this suite"
134
+ placeholder="e.g. test_1"
135
+ value={testName}
136
+ update={(value): void => setTestName(value ?? '')}
137
+ errorMessage={testError}
138
+ />
139
+ <div className="panel__content__form__section">
140
+ <div className="panel__content__form__section__header__label">
141
+ MatView Dataset
142
+ </div>
143
+ <div className="panel__content__form__section__header__prompt">
144
+ Select the MatView dataset tested by this first test
145
+ </div>
146
+ <CustomSelectorInput
147
+ options={datasetOptions}
148
+ onChange={(option: ItemOption | null): void =>
149
+ setDatasetName(option?.value)
150
+ }
151
+ value={selectedDatasetOption}
152
+ placeholder="Select dataset..."
153
+ isClearable={false}
154
+ darkMode={true}
155
+ disabled={datasetOptions.length === 0}
156
+ />
157
+ </div>
158
+ </ModalBody>
159
+ <ModalFooter>
160
+ <ModalFooterButton
161
+ disabled={!isValid}
162
+ title={!isValid ? 'Fill in all required fields' : 'Create Suite'}
163
+ onClick={create}
164
+ text="Create"
165
+ />
166
+ <ModalFooterButton
167
+ onClick={onClose}
168
+ text="Close"
169
+ type="secondary"
170
+ />
171
+ </ModalFooter>
172
+ </Modal>
173
+ </Dialog>
174
+ );
175
+ },
176
+ );
177
+
178
+ // ─── Create Test Modal ────────────────────────────────────────────────────────
179
+
180
+ const CreateTestModal = observer(
181
+ (props: { suiteState: IngestTestSuiteState; onClose: () => void }) => {
182
+ const { suiteState, onClose } = props;
183
+ const editorStore = suiteState.editorStore;
184
+ const applicationStore = editorStore.applicationStore;
185
+ const inputRef = useRef<HTMLInputElement>(null);
186
+
187
+ const existingIds = suiteState.suite.tests.map((test) => test.id);
188
+ const [testName, setTestName] = useState<string | undefined>(undefined);
189
+ const [datasetName, setDatasetName] = useState<string | undefined>(
190
+ suiteState.testableState.datasetNames[0],
191
+ );
192
+ const testNameError = validateTestableId(testName, existingIds);
193
+
194
+ const datasetOptions: ItemOption[] =
195
+ suiteState.testableState.datasetNames.map((name) => ({
196
+ value: name,
197
+ label: name,
198
+ }));
199
+ const selectedDatasetOption =
200
+ datasetOptions.find((option) => option.value === datasetName) ?? null;
201
+
202
+ const isValid = testName && !testNameError && datasetName;
203
+
204
+ const create = (): void => {
205
+ if (!testName || !datasetName) {
206
+ return;
207
+ }
208
+ flowResult(suiteState.addNewTest(testName, datasetName))
209
+ .then((error) => {
210
+ if (error) {
211
+ applicationStore.notificationService.notifyError(error);
212
+ } else {
213
+ onClose();
214
+ }
215
+ })
216
+ .catch(applicationStore.alertUnhandledError);
217
+ };
218
+
219
+ return (
220
+ <Dialog
221
+ open={true}
222
+ onClose={onClose}
223
+ classes={{ container: 'search-modal__container' }}
224
+ slotProps={{
225
+ transition: { onEnter: () => inputRef.current?.focus() },
226
+ paper: { classes: { root: 'search-modal__inner-container' } },
227
+ }}
228
+ >
229
+ <Modal
230
+ darkMode={
231
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
232
+ }
233
+ >
234
+ <ModalHeader>
235
+ <ModalTitle title={`Add Test to "${suiteState.suite.id}"`} />
236
+ </ModalHeader>
237
+ <ModalBody>
238
+ <PanelFormTextField
239
+ ref={inputRef}
240
+ name="Test Name"
241
+ prompt="Unique identifier for the test"
242
+ placeholder="e.g. test_1"
243
+ value={testName}
244
+ update={(value): void => setTestName(value ?? '')}
245
+ errorMessage={testNameError}
246
+ />
247
+ <div className="panel__content__form__section">
248
+ <div className="panel__content__form__section__header__label">
249
+ MatView Dataset
250
+ </div>
251
+ <div className="panel__content__form__section__header__prompt">
252
+ Select which MatView dataset this test will verify
253
+ </div>
254
+ <CustomSelectorInput
255
+ options={datasetOptions}
256
+ onChange={(option: ItemOption | null): void =>
257
+ setDatasetName(option?.value)
258
+ }
259
+ value={selectedDatasetOption}
260
+ placeholder="Select dataset..."
261
+ isClearable={false}
262
+ darkMode={true}
263
+ disabled={datasetOptions.length === 0}
264
+ />
265
+ </div>
266
+ </ModalBody>
267
+ <ModalFooter>
268
+ <ModalFooterButton
269
+ disabled={!isValid}
270
+ title={!isValid ? 'Fill in all required fields' : 'Create Test'}
271
+ onClick={create}
272
+ text="Create"
273
+ />
274
+ <ModalFooterButton
275
+ onClick={onClose}
276
+ text="Close"
277
+ type="secondary"
278
+ />
279
+ </ModalFooter>
280
+ </Modal>
281
+ </Dialog>
282
+ );
283
+ },
284
+ );
285
+
286
+ // ─── Suite Tab Context Menu ───────────────────────────────────────────────────
287
+
288
+ const SuiteHeaderTabContextMenu = observer(
289
+ forwardRef<
290
+ HTMLDivElement,
291
+ {
292
+ testSuite: IngestTestSuite;
293
+ testableState: IngestTestableState;
294
+ }
295
+ >(function SuiteHeaderTabContextMenu(props, ref) {
296
+ const { testSuite, testableState } = props;
297
+ const deleteSuite = (): void => {
298
+ const suiteState = testableState.suiteStates.find(
299
+ (state) => state.suite === testSuite,
300
+ );
301
+ if (suiteState) {
302
+ testableState.deleteSuite(suiteState);
303
+ }
304
+ };
305
+ const rename = (): void => testableState.setSuiteToRename(testSuite);
306
+
307
+ return (
308
+ <MenuContent ref={ref}>
309
+ <MenuContentItem onClick={rename}>Rename</MenuContentItem>
310
+ <MenuContentItem onClick={deleteSuite}>Delete</MenuContentItem>
311
+ </MenuContent>
312
+ );
313
+ }),
314
+ );
315
+
316
+ // ─── Main Testing Tab ─────────────────────────────────────────────────────────
317
+
318
+ export const IngestTestableEditor = observer(
319
+ (props: { testableState: IngestTestableState }) => {
320
+ const { testableState } = props;
321
+ const selectedSuiteState = testableState.selectedSuiteState;
322
+ const isReadOnly = testableState.ingestDefinitionEditorState.isReadOnly;
323
+ const ingest = testableState.ingest;
324
+
325
+ const addSuite = (): void => {
326
+ testableState.setShowCreateSuiteModal(true);
327
+ };
328
+
329
+ const changeSuite = (suite: IngestTestSuite): void => {
330
+ testableState.changeSuite(suite);
331
+ };
332
+
333
+ const renameSuite = (value: string): void =>
334
+ testSuite_setId(guaranteeNonNullable(testableState.suiteToRename), value);
335
+
336
+ return (
337
+ <Panel className="service-test-suite-editor">
338
+ {testableState.showCreateSuiteModal && (
339
+ <CreateSuiteModal
340
+ testableState={testableState}
341
+ onClose={(): void => testableState.setShowCreateSuiteModal(false)}
342
+ />
343
+ )}
344
+
345
+ {testableState.showCreateTestModal && selectedSuiteState && (
346
+ <CreateTestModal
347
+ suiteState={selectedSuiteState}
348
+ onClose={(): void => testableState.setShowCreateTestModal(false)}
349
+ />
350
+ )}
351
+
352
+ <PanelHeader>
353
+ {ingest.tests.length ? (
354
+ <PanelHeader className="service-test-suite-editor__header service-test-suite-editor__header--with-tabs">
355
+ <div className="uml-element-editor__tabs">
356
+ {ingest.tests.map((suite) => (
357
+ <div
358
+ key={suite.id}
359
+ onClick={(): void => changeSuite(suite)}
360
+ className={clsx('service-test-suite-editor__tab', {
361
+ 'service-test-suite-editor__tab--active':
362
+ selectedSuiteState?.suite === suite,
363
+ })}
364
+ >
365
+ <ContextMenu
366
+ className="mapping-editor__header__tab__content"
367
+ content={
368
+ <SuiteHeaderTabContextMenu
369
+ testableState={testableState}
370
+ testSuite={suite}
371
+ />
372
+ }
373
+ >
374
+ {suite.id}
375
+ </ContextMenu>
376
+ </div>
377
+ ))}
378
+ </div>
379
+ </PanelHeader>
380
+ ) : (
381
+ <div></div>
382
+ )}
383
+ <PanelHeaderActions>
384
+ <PanelHeaderActionItem onClick={addSuite} title="Add Test Suite">
385
+ <PlusIcon />
386
+ </PanelHeaderActionItem>
387
+ </PanelHeaderActions>
388
+ </PanelHeader>
389
+ <Panel className="service-test-suite-editor">
390
+ {selectedSuiteState && (
391
+ <LakehouseTestSuiteEditor
392
+ suiteState={selectedSuiteState}
393
+ testableState={testableState}
394
+ isReadOnly={isReadOnly}
395
+ />
396
+ )}
397
+ {!ingest.tests.length && (
398
+ <BlankPanelPlaceholder
399
+ text="Add Test Suite"
400
+ onClick={addSuite}
401
+ clickActionType="add"
402
+ tooltipText="Click to add test suite"
403
+ />
404
+ )}
405
+ {testableState.suiteToRename && (
406
+ <RenameModal
407
+ val={testableState.suiteToRename.id}
408
+ isReadOnly={isReadOnly}
409
+ showModal={true}
410
+ closeModal={(): void => testableState.setSuiteToRename(undefined)}
411
+ setValue={renameSuite}
412
+ />
413
+ )}
414
+ </Panel>
415
+ </Panel>
416
+ );
417
+ },
418
+ );