@finos/legend-application-studio 28.21.3 → 28.21.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +72 -52
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +22 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts +23 -0
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +267 -0
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -0
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +39 -5
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
- package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +17 -2
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +56 -49
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +4 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +4 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +113 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +647 -0
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +214 -53
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.d.ts +17 -1
- package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js +46 -1
- package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +55 -0
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
- package/package.json +16 -16
- package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +3 -0
- package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +331 -231
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +32 -0
- package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +935 -0
- package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +160 -15
- package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +82 -51
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +4 -0
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +927 -0
- package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +303 -72
- package/src/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.ts +66 -0
- package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +76 -0
- package/tsconfig.json +2 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-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
|
+
BlankPanelContent,
|
|
22
|
+
clsx,
|
|
23
|
+
ContextMenu,
|
|
24
|
+
CustomSelectorInput,
|
|
25
|
+
Dialog,
|
|
26
|
+
ErrorWarnIcon,
|
|
27
|
+
MenuContent,
|
|
28
|
+
MenuContentItem,
|
|
29
|
+
Modal,
|
|
30
|
+
ModalBody,
|
|
31
|
+
ModalFooter,
|
|
32
|
+
ModalFooterButton,
|
|
33
|
+
ModalHeader,
|
|
34
|
+
ModalTitle,
|
|
35
|
+
Panel,
|
|
36
|
+
PanelFormTextField,
|
|
37
|
+
PanelHeader,
|
|
38
|
+
PanelHeaderActionItem,
|
|
39
|
+
PanelHeaderActions,
|
|
40
|
+
PanelLoadingIndicator,
|
|
41
|
+
PlayIcon,
|
|
42
|
+
PlusIcon,
|
|
43
|
+
ResizablePanel,
|
|
44
|
+
ResizablePanelGroup,
|
|
45
|
+
ResizablePanelSplitter,
|
|
46
|
+
ResizablePanelSplitterLine,
|
|
47
|
+
RunAllIcon,
|
|
48
|
+
TimesIcon,
|
|
49
|
+
} from '@finos/legend-art';
|
|
50
|
+
import type { DataProductTestSuite } from '@finos/legend-graph';
|
|
51
|
+
import type { DataProductEditorState } from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
|
|
52
|
+
import {
|
|
53
|
+
type DataProductTestableState,
|
|
54
|
+
type DataProductTestSuiteState,
|
|
55
|
+
type DataProductTestState,
|
|
56
|
+
type DataProductTestDataState,
|
|
57
|
+
type DataProductElementTestDataState,
|
|
58
|
+
} from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js';
|
|
59
|
+
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
60
|
+
import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js';
|
|
61
|
+
import {
|
|
62
|
+
TESTABLE_RESULT,
|
|
63
|
+
getTestableResultFromTestResult,
|
|
64
|
+
} from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
|
|
65
|
+
import { RelationElementsDataEditor } from '../../data-editor/RelationElementsDataEditor.js';
|
|
66
|
+
import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js';
|
|
67
|
+
import { useEditorStore } from '../../../EditorStoreProvider.js';
|
|
68
|
+
import { guaranteeNonNullable } from '@finos/legend-shared';
|
|
69
|
+
import {
|
|
70
|
+
RenameModal,
|
|
71
|
+
TestAssertionEditor,
|
|
72
|
+
} from '../../testable/TestableSharedComponents.js';
|
|
73
|
+
import { testSuite_setId } from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
|
|
74
|
+
|
|
75
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
// Create Suite Modal — test name + access point (datasets are auto-inferred)
|
|
77
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
interface ItemOption {
|
|
80
|
+
value: string;
|
|
81
|
+
label: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const CreateSuiteModal = observer(
|
|
85
|
+
(props: { testableState: DataProductTestableState; onClose: () => void }) => {
|
|
86
|
+
const { testableState, onClose } = props;
|
|
87
|
+
const editorStore = useEditorStore();
|
|
88
|
+
const applicationStore = editorStore.applicationStore;
|
|
89
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
90
|
+
|
|
91
|
+
const [testName, setTestName] = useState<string | undefined>(undefined);
|
|
92
|
+
const [selectedAccessPointId, setSelectedAccessPointId] = useState<
|
|
93
|
+
string | undefined
|
|
94
|
+
>(undefined);
|
|
95
|
+
|
|
96
|
+
// Auto-generate suite name
|
|
97
|
+
const existingIds = testableState.dataProduct.tests.map((s) => s.id);
|
|
98
|
+
const generateSuiteName = (): string => {
|
|
99
|
+
let idx = 1;
|
|
100
|
+
while (existingIds.includes(`suite_${idx}`)) {
|
|
101
|
+
idx++;
|
|
102
|
+
}
|
|
103
|
+
return `suite_${idx}`;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const testError = validateTestableId(testName, undefined);
|
|
107
|
+
|
|
108
|
+
// Access points on the current DataProduct (for the test target)
|
|
109
|
+
const accessPointOptions: ItemOption[] = testableState.ownAccessPoints.map(
|
|
110
|
+
(ap) => ({
|
|
111
|
+
value: ap.id,
|
|
112
|
+
label: ap.id,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
const selectedApOption =
|
|
116
|
+
accessPointOptions.find((o) => o.value === selectedAccessPointId) ?? null;
|
|
117
|
+
|
|
118
|
+
const isValid = testName && !testError && selectedAccessPointId;
|
|
119
|
+
|
|
120
|
+
const create = (): void => {
|
|
121
|
+
if (!testName || !selectedAccessPointId) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
flowResult(
|
|
125
|
+
testableState.createSuite(
|
|
126
|
+
generateSuiteName(),
|
|
127
|
+
testName,
|
|
128
|
+
selectedAccessPointId,
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
.then((err) => {
|
|
132
|
+
if (err) {
|
|
133
|
+
applicationStore.notificationService.notifyError(err);
|
|
134
|
+
} else {
|
|
135
|
+
onClose();
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch(applicationStore.alertUnhandledError);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Dialog
|
|
143
|
+
open={true}
|
|
144
|
+
onClose={onClose}
|
|
145
|
+
classes={{ container: 'search-modal__container' }}
|
|
146
|
+
slotProps={{
|
|
147
|
+
transition: { onEnter: () => inputRef.current?.focus() },
|
|
148
|
+
paper: { classes: { root: 'search-modal__inner-container' } },
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<Modal
|
|
152
|
+
darkMode={
|
|
153
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
154
|
+
}
|
|
155
|
+
>
|
|
156
|
+
<ModalHeader>
|
|
157
|
+
<ModalTitle title="Create Test Suite" />
|
|
158
|
+
</ModalHeader>
|
|
159
|
+
<ModalBody>
|
|
160
|
+
<PanelFormTextField
|
|
161
|
+
ref={inputRef}
|
|
162
|
+
name="Test Name"
|
|
163
|
+
prompt="Name for the first test in this suite"
|
|
164
|
+
placeholder="e.g. test_1"
|
|
165
|
+
value={testName}
|
|
166
|
+
update={(value): void => setTestName(value ?? '')}
|
|
167
|
+
errorMessage={testError}
|
|
168
|
+
/>
|
|
169
|
+
<div className="panel__content__form__section">
|
|
170
|
+
<div className="panel__content__form__section__header__label">
|
|
171
|
+
Access Point to Test
|
|
172
|
+
</div>
|
|
173
|
+
<div className="panel__content__form__section__header__prompt">
|
|
174
|
+
Select the access point of the current DataProduct that the
|
|
175
|
+
first test in this suite will verify
|
|
176
|
+
</div>
|
|
177
|
+
<CustomSelectorInput
|
|
178
|
+
options={accessPointOptions}
|
|
179
|
+
onChange={(opt: ItemOption | null): void =>
|
|
180
|
+
setSelectedAccessPointId(opt?.value)
|
|
181
|
+
}
|
|
182
|
+
value={selectedApOption}
|
|
183
|
+
placeholder="Select access point..."
|
|
184
|
+
isClearable={false}
|
|
185
|
+
darkMode={true}
|
|
186
|
+
disabled={accessPointOptions.length === 0}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
</ModalBody>
|
|
190
|
+
<ModalFooter>
|
|
191
|
+
<ModalFooterButton
|
|
192
|
+
disabled={!isValid}
|
|
193
|
+
title={!isValid ? 'Fill in all required fields' : 'Create Suite'}
|
|
194
|
+
onClick={create}
|
|
195
|
+
text="Create"
|
|
196
|
+
/>
|
|
197
|
+
<ModalFooterButton
|
|
198
|
+
onClick={onClose}
|
|
199
|
+
text="Close"
|
|
200
|
+
type="secondary"
|
|
201
|
+
/>
|
|
202
|
+
</ModalFooter>
|
|
203
|
+
</Modal>
|
|
204
|
+
</Dialog>
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
210
|
+
// Create Test Modal — asks for test name + access point to test
|
|
211
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
const CreateTestModal = observer(
|
|
214
|
+
(props: { suiteState: DataProductTestSuiteState; onClose: () => void }) => {
|
|
215
|
+
const { suiteState, onClose } = props;
|
|
216
|
+
const editorStore = suiteState.editorStore;
|
|
217
|
+
const applicationStore = editorStore.applicationStore;
|
|
218
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
219
|
+
|
|
220
|
+
const existingIds = suiteState.suite.tests.map((t) => t.id);
|
|
221
|
+
const [testName, setTestName] = useState<string | undefined>(undefined);
|
|
222
|
+
const [selectedAccessPointId, setSelectedAccessPointId] = useState<
|
|
223
|
+
string | undefined
|
|
224
|
+
>(undefined);
|
|
225
|
+
const testNameError = validateTestableId(testName, existingIds);
|
|
226
|
+
|
|
227
|
+
const accessPointOptions: ItemOption[] =
|
|
228
|
+
suiteState.testableState.ownAccessPoints.map((ap) => ({
|
|
229
|
+
value: ap.id,
|
|
230
|
+
label: ap.id,
|
|
231
|
+
}));
|
|
232
|
+
const selectedApOption =
|
|
233
|
+
accessPointOptions.find((o) => o.value === selectedAccessPointId) ?? null;
|
|
234
|
+
|
|
235
|
+
const isValid = testName && !testNameError && selectedAccessPointId;
|
|
236
|
+
|
|
237
|
+
const create = (): void => {
|
|
238
|
+
if (!testName || !selectedAccessPointId) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
flowResult(suiteState.addNewTest(testName, selectedAccessPointId))
|
|
242
|
+
.then((err) => {
|
|
243
|
+
if (err) {
|
|
244
|
+
applicationStore.notificationService.notifyError(err);
|
|
245
|
+
} else {
|
|
246
|
+
onClose();
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
.catch(applicationStore.alertUnhandledError);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<Dialog
|
|
254
|
+
open={true}
|
|
255
|
+
onClose={onClose}
|
|
256
|
+
classes={{ container: 'search-modal__container' }}
|
|
257
|
+
slotProps={{
|
|
258
|
+
transition: { onEnter: () => inputRef.current?.focus() },
|
|
259
|
+
paper: { classes: { root: 'search-modal__inner-container' } },
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
<Modal
|
|
263
|
+
darkMode={
|
|
264
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
<ModalHeader>
|
|
268
|
+
<ModalTitle title={`Add Test to "${suiteState.suite.id}"`} />
|
|
269
|
+
</ModalHeader>
|
|
270
|
+
<ModalBody>
|
|
271
|
+
<PanelFormTextField
|
|
272
|
+
ref={inputRef}
|
|
273
|
+
name="Test Name"
|
|
274
|
+
prompt="Unique identifier for the test"
|
|
275
|
+
placeholder="e.g. test_1"
|
|
276
|
+
value={testName}
|
|
277
|
+
update={(value): void => setTestName(value ?? '')}
|
|
278
|
+
errorMessage={testNameError}
|
|
279
|
+
/>
|
|
280
|
+
<div className="panel__content__form__section">
|
|
281
|
+
<div className="panel__content__form__section__header__label">
|
|
282
|
+
Access Point to Test
|
|
283
|
+
</div>
|
|
284
|
+
<div className="panel__content__form__section__header__prompt">
|
|
285
|
+
Select which access point of the DataProduct this test will
|
|
286
|
+
verify
|
|
287
|
+
</div>
|
|
288
|
+
<CustomSelectorInput
|
|
289
|
+
options={accessPointOptions}
|
|
290
|
+
onChange={(opt: ItemOption | null): void =>
|
|
291
|
+
setSelectedAccessPointId(opt?.value)
|
|
292
|
+
}
|
|
293
|
+
value={selectedApOption}
|
|
294
|
+
placeholder="Select access point..."
|
|
295
|
+
isClearable={false}
|
|
296
|
+
darkMode={true}
|
|
297
|
+
disabled={accessPointOptions.length === 0}
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
</ModalBody>
|
|
301
|
+
<ModalFooter>
|
|
302
|
+
<ModalFooterButton
|
|
303
|
+
disabled={!isValid}
|
|
304
|
+
title={!isValid ? 'Fill in all required fields' : 'Create Test'}
|
|
305
|
+
onClick={create}
|
|
306
|
+
text="Create"
|
|
307
|
+
/>
|
|
308
|
+
<ModalFooterButton
|
|
309
|
+
onClick={onClose}
|
|
310
|
+
text="Close"
|
|
311
|
+
type="secondary"
|
|
312
|
+
/>
|
|
313
|
+
</ModalFooter>
|
|
314
|
+
</Modal>
|
|
315
|
+
</Dialog>
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
321
|
+
// Element Test Data Item (sidebar list row in test data panel)
|
|
322
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
const ElementTestDataItem = observer(
|
|
325
|
+
(props: {
|
|
326
|
+
elementState: DataProductElementTestDataState;
|
|
327
|
+
testDataState: DataProductTestDataState;
|
|
328
|
+
isReadOnly: boolean;
|
|
329
|
+
}) => {
|
|
330
|
+
const { elementState, testDataState, isReadOnly } = props;
|
|
331
|
+
const isActive =
|
|
332
|
+
testDataState.selectedElementTestDataState === elementState;
|
|
333
|
+
|
|
334
|
+
const select = (): void =>
|
|
335
|
+
testDataState.setSelectedElementTestDataState(elementState);
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<div
|
|
339
|
+
className={clsx('testable-test-explorer__item', {
|
|
340
|
+
'testable-test-explorer__item--active': isActive,
|
|
341
|
+
})}
|
|
342
|
+
>
|
|
343
|
+
<div
|
|
344
|
+
className="testable-test-explorer__item__label"
|
|
345
|
+
onClick={select}
|
|
346
|
+
tabIndex={-1}
|
|
347
|
+
>
|
|
348
|
+
<div className="testable-test-explorer__item__label__text">
|
|
349
|
+
<span title={elementState.element.path}>
|
|
350
|
+
{elementState.element.name}
|
|
351
|
+
</span>
|
|
352
|
+
</div>
|
|
353
|
+
{!isReadOnly && (
|
|
354
|
+
<div className="mapping-test-explorer__item__actions">
|
|
355
|
+
<button
|
|
356
|
+
className="mapping-test-explorer__item__action"
|
|
357
|
+
onClick={(e): void => {
|
|
358
|
+
e.stopPropagation();
|
|
359
|
+
testDataState.deleteElement(elementState);
|
|
360
|
+
}}
|
|
361
|
+
tabIndex={-1}
|
|
362
|
+
title="Delete"
|
|
363
|
+
>
|
|
364
|
+
<TimesIcon />
|
|
365
|
+
</button>
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const ElementTestDataEditor = observer(
|
|
375
|
+
(props: {
|
|
376
|
+
elementState: DataProductElementTestDataState;
|
|
377
|
+
isReadOnly: boolean;
|
|
378
|
+
}) => {
|
|
379
|
+
const { elementState, isReadOnly } = props;
|
|
380
|
+
const dataState = elementState.relationElementsDataState;
|
|
381
|
+
|
|
382
|
+
if (!dataState) {
|
|
383
|
+
return (
|
|
384
|
+
<BlankPanelContent>No relation data for this element</BlankPanelContent>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<RelationElementsDataEditor
|
|
390
|
+
dataState={dataState}
|
|
391
|
+
isReadOnly={isReadOnly}
|
|
392
|
+
hideColumnDefinitions={true}
|
|
393
|
+
/>
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
399
|
+
// Test Data Editor (top panel) — always-visible elements sidebar + per-element editor
|
|
400
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
const AddElementModal = observer(
|
|
403
|
+
(props: { testDataState: DataProductTestDataState }) => {
|
|
404
|
+
const { testDataState } = props;
|
|
405
|
+
const applicationStore = testDataState.editorStore.applicationStore;
|
|
406
|
+
const options = testDataState.availableElementsToAdd.map((e) => ({
|
|
407
|
+
value: e.path,
|
|
408
|
+
label: e.path,
|
|
409
|
+
}));
|
|
410
|
+
const [selectedPath, setSelectedPath] = useState<string | undefined>(
|
|
411
|
+
options[0]?.value,
|
|
412
|
+
);
|
|
413
|
+
const close = (): void => testDataState.setShowAddElementModal(false);
|
|
414
|
+
const add = (): void => {
|
|
415
|
+
if (selectedPath) {
|
|
416
|
+
testDataState.addElement(selectedPath);
|
|
417
|
+
close();
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const onChange = (val: { label: string; value: string } | null): void => {
|
|
421
|
+
setSelectedPath(val?.value);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<Dialog
|
|
426
|
+
open={testDataState.showAddElementModal}
|
|
427
|
+
onClose={close}
|
|
428
|
+
classes={{ container: 'search-modal__container' }}
|
|
429
|
+
slotProps={{
|
|
430
|
+
paper: {
|
|
431
|
+
classes: { root: 'search-modal__inner-container' },
|
|
432
|
+
},
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
<Modal
|
|
436
|
+
darkMode={
|
|
437
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
438
|
+
}
|
|
439
|
+
>
|
|
440
|
+
<ModalHeader>
|
|
441
|
+
<ModalTitle title="Add Element" />
|
|
442
|
+
</ModalHeader>
|
|
443
|
+
<ModalBody>
|
|
444
|
+
<CustomSelectorInput
|
|
445
|
+
className="panel__content__form__section__dropdown"
|
|
446
|
+
options={options}
|
|
447
|
+
onChange={onChange}
|
|
448
|
+
value={
|
|
449
|
+
selectedPath
|
|
450
|
+
? { value: selectedPath, label: selectedPath }
|
|
451
|
+
: null
|
|
452
|
+
}
|
|
453
|
+
placeholder="Select element..."
|
|
454
|
+
darkMode={
|
|
455
|
+
!applicationStore.layoutService
|
|
456
|
+
.TEMPORARY__isLightColorThemeEnabled
|
|
457
|
+
}
|
|
458
|
+
/>
|
|
459
|
+
</ModalBody>
|
|
460
|
+
<ModalFooter>
|
|
461
|
+
<ModalFooterButton
|
|
462
|
+
disabled={!selectedPath}
|
|
463
|
+
onClick={add}
|
|
464
|
+
text="Add"
|
|
465
|
+
/>
|
|
466
|
+
<ModalFooterButton onClick={close} text="Close" type="secondary" />
|
|
467
|
+
</ModalFooter>
|
|
468
|
+
</Modal>
|
|
469
|
+
</Dialog>
|
|
470
|
+
);
|
|
471
|
+
},
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const DataProductTestDataEditor = observer(
|
|
475
|
+
(props: { testDataState: DataProductTestDataState; isReadOnly: boolean }) => {
|
|
476
|
+
const { testDataState, isReadOnly } = props;
|
|
477
|
+
|
|
478
|
+
const addElement = (): void => {
|
|
479
|
+
if (testDataState.availableElementsToAdd.length === 0) {
|
|
480
|
+
testDataState.editorStore.applicationStore.notificationService.notifyWarning(
|
|
481
|
+
'No elements available to add',
|
|
482
|
+
);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
testDataState.setShowAddElementModal(true);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const hasTestData = testDataState.elementTestDataStates.length > 0;
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<div
|
|
492
|
+
className={clsx('service-test-data-editor panel', {
|
|
493
|
+
'service-test-data-editor--no-data': !hasTestData,
|
|
494
|
+
})}
|
|
495
|
+
>
|
|
496
|
+
<div className="service-test-data-editor__data">
|
|
497
|
+
<ResizablePanelGroup orientation="vertical">
|
|
498
|
+
{/* Left: elements list — always visible */}
|
|
499
|
+
<ResizablePanel minSize={100} size={180}>
|
|
500
|
+
<div className="binding-editor__header">
|
|
501
|
+
<div className="binding-editor__header__title">
|
|
502
|
+
<div className="panel__header__title__content">Test Data</div>
|
|
503
|
+
</div>
|
|
504
|
+
{!isReadOnly && (
|
|
505
|
+
<div className="panel__header__actions">
|
|
506
|
+
<button
|
|
507
|
+
className="panel__header__action"
|
|
508
|
+
tabIndex={-1}
|
|
509
|
+
onClick={addElement}
|
|
510
|
+
title="Add Element"
|
|
511
|
+
>
|
|
512
|
+
<PlusIcon />
|
|
513
|
+
</button>
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
</div>
|
|
517
|
+
{!hasTestData ? (
|
|
518
|
+
<div className="service-test-data-editor__warning">
|
|
519
|
+
<ErrorWarnIcon />
|
|
520
|
+
<span>Add an element to configure test data</span>
|
|
521
|
+
</div>
|
|
522
|
+
) : (
|
|
523
|
+
<div>
|
|
524
|
+
{testDataState.elementTestDataStates.map((elementState) => (
|
|
525
|
+
<ElementTestDataItem
|
|
526
|
+
key={elementState.element.path}
|
|
527
|
+
elementState={elementState}
|
|
528
|
+
testDataState={testDataState}
|
|
529
|
+
isReadOnly={isReadOnly}
|
|
530
|
+
/>
|
|
531
|
+
))}
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
534
|
+
</ResizablePanel>
|
|
535
|
+
<ResizablePanelSplitter>
|
|
536
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
537
|
+
</ResizablePanelSplitter>
|
|
538
|
+
{/* Right: per-element dataset tabs + CSV editor */}
|
|
539
|
+
<ResizablePanel minSize={200}>
|
|
540
|
+
{testDataState.selectedElementTestDataState ? (
|
|
541
|
+
<ElementTestDataEditor
|
|
542
|
+
elementState={testDataState.selectedElementTestDataState}
|
|
543
|
+
isReadOnly={isReadOnly}
|
|
544
|
+
/>
|
|
545
|
+
) : (
|
|
546
|
+
<BlankPanelContent>
|
|
547
|
+
Select an element to configure its test data
|
|
548
|
+
</BlankPanelContent>
|
|
549
|
+
)}
|
|
550
|
+
</ResizablePanel>
|
|
551
|
+
</ResizablePanelGroup>
|
|
552
|
+
</div>
|
|
553
|
+
{testDataState.showAddElementModal && (
|
|
554
|
+
<AddElementModal testDataState={testDataState} />
|
|
555
|
+
)}
|
|
556
|
+
</div>
|
|
557
|
+
);
|
|
558
|
+
},
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
562
|
+
// Test Item (inside tests panel)
|
|
563
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
const TestItem = observer(
|
|
566
|
+
(props: {
|
|
567
|
+
testState: DataProductTestState;
|
|
568
|
+
suiteState: DataProductTestSuiteState;
|
|
569
|
+
isReadOnly: boolean;
|
|
570
|
+
}) => {
|
|
571
|
+
const { testState, suiteState, isReadOnly } = props;
|
|
572
|
+
const isActive = suiteState.selectTestState === testState;
|
|
573
|
+
const isRunning = testState.runningTestAction.isInProgress;
|
|
574
|
+
const _testableResult = getTestableResultFromTestResult(
|
|
575
|
+
testState.testResultState.result,
|
|
576
|
+
);
|
|
577
|
+
const testResult = isRunning
|
|
578
|
+
? TESTABLE_RESULT.IN_PROGRESS
|
|
579
|
+
: _testableResult;
|
|
580
|
+
|
|
581
|
+
const select = (): void => suiteState.changeTest(testState.test);
|
|
582
|
+
const runTest = (): void => {
|
|
583
|
+
flowResult(testState.runTest()).catch(
|
|
584
|
+
testState.editorStore.applicationStore.alertUnhandledError,
|
|
585
|
+
);
|
|
586
|
+
};
|
|
587
|
+
const deleteTest = (): void => {
|
|
588
|
+
if (!isReadOnly) {
|
|
589
|
+
suiteState.deleteTest(testState.test);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<div
|
|
595
|
+
className={clsx('testable-test-explorer__item', {
|
|
596
|
+
'testable-test-explorer__item--active': isActive,
|
|
597
|
+
})}
|
|
598
|
+
>
|
|
599
|
+
<div
|
|
600
|
+
className="testable-test-explorer__item__label"
|
|
601
|
+
onClick={select}
|
|
602
|
+
tabIndex={-1}
|
|
603
|
+
>
|
|
604
|
+
<div className="testable-test-explorer__item__label__icon">
|
|
605
|
+
{getTestableResultIcon(testResult)}
|
|
606
|
+
</div>
|
|
607
|
+
<div className="testable-test-explorer__item__label__text">
|
|
608
|
+
{testState.test.id}
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
<div className="mapping-test-explorer__item__actions">
|
|
612
|
+
<button
|
|
613
|
+
className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
|
|
614
|
+
onClick={runTest}
|
|
615
|
+
disabled={isRunning}
|
|
616
|
+
tabIndex={-1}
|
|
617
|
+
title={`Run test ${testState.test.id}`}
|
|
618
|
+
>
|
|
619
|
+
<PlayIcon />
|
|
620
|
+
</button>
|
|
621
|
+
{!isReadOnly && (
|
|
622
|
+
<button
|
|
623
|
+
className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
|
|
624
|
+
onClick={deleteTest}
|
|
625
|
+
tabIndex={-1}
|
|
626
|
+
title={`Delete test ${testState.test.id}`}
|
|
627
|
+
>
|
|
628
|
+
<TimesIcon />
|
|
629
|
+
</button>
|
|
630
|
+
)}
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
},
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
638
|
+
// Test Editor — shows SETUP (input data) and ASSERTION (expected + result) tabs
|
|
639
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
const DataProductTestEditor = observer(
|
|
642
|
+
(props: { testState: DataProductTestState; isReadOnly: boolean }) => {
|
|
643
|
+
const { testState } = props;
|
|
644
|
+
const selectedAssertion = testState.selectedAsertionState;
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
<div className="function-test-editor panel">
|
|
648
|
+
<div className="panel__header">
|
|
649
|
+
<div className="panel__header service-test-editor__header--with-tabs">
|
|
650
|
+
<div className="panel__header__title__content">Assertion</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div className="panel">
|
|
654
|
+
{selectedAssertion && (
|
|
655
|
+
<TestAssertionEditor testAssertionState={selectedAssertion} />
|
|
656
|
+
)}
|
|
657
|
+
{!selectedAssertion && (
|
|
658
|
+
<BlankPanelPlaceholder
|
|
659
|
+
text="No assertion"
|
|
660
|
+
tooltipText="No assertion configured for this test"
|
|
661
|
+
/>
|
|
662
|
+
)}
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
);
|
|
666
|
+
},
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
670
|
+
// Tests Editor (bottom panel) — tests list + test detail
|
|
671
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
672
|
+
|
|
673
|
+
const DataProductTestsEditor = observer(
|
|
674
|
+
(props: {
|
|
675
|
+
suiteState: DataProductTestSuiteState;
|
|
676
|
+
testableState: DataProductTestableState;
|
|
677
|
+
isReadOnly: boolean;
|
|
678
|
+
}) => {
|
|
679
|
+
const { suiteState, testableState, isReadOnly } = props;
|
|
680
|
+
const selectedTest = suiteState.selectTestState;
|
|
681
|
+
|
|
682
|
+
return (
|
|
683
|
+
<div className="panel service-test-editor">
|
|
684
|
+
<div className="service-test-editor__content">
|
|
685
|
+
<ResizablePanelGroup orientation="vertical">
|
|
686
|
+
<ResizablePanel minSize={100} size={200}>
|
|
687
|
+
<div className="binding-editor__header">
|
|
688
|
+
<div className="binding-editor__header__title">
|
|
689
|
+
<div className="panel__header__title__content">Tests</div>
|
|
690
|
+
</div>
|
|
691
|
+
<div className="panel__header__actions">
|
|
692
|
+
<button
|
|
693
|
+
className="panel__header__action testable-test-explorer__play__all__icon"
|
|
694
|
+
tabIndex={-1}
|
|
695
|
+
onClick={(): void => {
|
|
696
|
+
flowResult(suiteState.runSuite()).catch(
|
|
697
|
+
testableState.editorStore.applicationStore
|
|
698
|
+
.alertUnhandledError,
|
|
699
|
+
);
|
|
700
|
+
}}
|
|
701
|
+
disabled={
|
|
702
|
+
suiteState.runningSuiteState.isInProgress ||
|
|
703
|
+
suiteState.suite.tests.length === 0
|
|
704
|
+
}
|
|
705
|
+
title="Run all tests in this suite"
|
|
706
|
+
>
|
|
707
|
+
<RunAllIcon />
|
|
708
|
+
</button>
|
|
709
|
+
{!isReadOnly && (
|
|
710
|
+
<button
|
|
711
|
+
className="panel__header__action"
|
|
712
|
+
tabIndex={-1}
|
|
713
|
+
onClick={(): void =>
|
|
714
|
+
testableState.setShowCreateTestModal(true)
|
|
715
|
+
}
|
|
716
|
+
title="Add test to this suite"
|
|
717
|
+
>
|
|
718
|
+
<PlusIcon />
|
|
719
|
+
</button>
|
|
720
|
+
)}
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
<div>
|
|
724
|
+
{suiteState.testStates.map((ts) => (
|
|
725
|
+
<TestItem
|
|
726
|
+
key={ts.test.id}
|
|
727
|
+
testState={ts}
|
|
728
|
+
suiteState={suiteState}
|
|
729
|
+
isReadOnly={isReadOnly}
|
|
730
|
+
/>
|
|
731
|
+
))}
|
|
732
|
+
</div>
|
|
733
|
+
</ResizablePanel>
|
|
734
|
+
<ResizablePanelSplitter>
|
|
735
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
736
|
+
</ResizablePanelSplitter>
|
|
737
|
+
<ResizablePanel minSize={56}>
|
|
738
|
+
{selectedTest ? (
|
|
739
|
+
<DataProductTestEditor
|
|
740
|
+
testState={selectedTest}
|
|
741
|
+
isReadOnly={isReadOnly}
|
|
742
|
+
/>
|
|
743
|
+
) : (
|
|
744
|
+
<BlankPanelPlaceholder
|
|
745
|
+
text="Select a test"
|
|
746
|
+
tooltipText="Select a test from the list above"
|
|
747
|
+
/>
|
|
748
|
+
)}
|
|
749
|
+
</ResizablePanel>
|
|
750
|
+
</ResizablePanelGroup>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
);
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
758
|
+
// Suite Editor — horizontal split: test data (top) + tests (bottom)
|
|
759
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
760
|
+
|
|
761
|
+
const DataProductTestSuiteEditor = observer(
|
|
762
|
+
(props: { suiteState: DataProductTestSuiteState }) => {
|
|
763
|
+
const { suiteState } = props;
|
|
764
|
+
const testableState = suiteState.testableState;
|
|
765
|
+
const isReadOnly = testableState.dataProductEditorState.isReadOnly;
|
|
766
|
+
|
|
767
|
+
return (
|
|
768
|
+
<div className="service-test-suite-editor">
|
|
769
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
770
|
+
<ResizablePanel size={580} minSize={28}>
|
|
771
|
+
<DataProductTestDataEditor
|
|
772
|
+
testDataState={suiteState.testDataState}
|
|
773
|
+
isReadOnly={isReadOnly}
|
|
774
|
+
/>
|
|
775
|
+
</ResizablePanel>
|
|
776
|
+
<ResizablePanelSplitter>
|
|
777
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
778
|
+
</ResizablePanelSplitter>
|
|
779
|
+
<ResizablePanel minSize={56}>
|
|
780
|
+
<DataProductTestsEditor
|
|
781
|
+
suiteState={suiteState}
|
|
782
|
+
testableState={testableState}
|
|
783
|
+
isReadOnly={isReadOnly}
|
|
784
|
+
/>
|
|
785
|
+
</ResizablePanel>
|
|
786
|
+
</ResizablePanelGroup>
|
|
787
|
+
</div>
|
|
788
|
+
);
|
|
789
|
+
},
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
793
|
+
// Suite Tab Context Menu (rename/delete)
|
|
794
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
795
|
+
|
|
796
|
+
const SuiteHeaderTabContextMenu = observer(
|
|
797
|
+
forwardRef<
|
|
798
|
+
HTMLDivElement,
|
|
799
|
+
{
|
|
800
|
+
testSuite: DataProductTestSuite;
|
|
801
|
+
testableState: DataProductTestableState;
|
|
802
|
+
}
|
|
803
|
+
>(function SuiteHeaderTabContextMenu(props, ref) {
|
|
804
|
+
const { testSuite, testableState } = props;
|
|
805
|
+
const deleteSuite = (): void => {
|
|
806
|
+
const suiteState = testableState.suiteStates.find(
|
|
807
|
+
(s) => s.suite === testSuite,
|
|
808
|
+
);
|
|
809
|
+
if (suiteState) {
|
|
810
|
+
testableState.deleteSuite(suiteState);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
const rename = (): void => testableState.setSuiteToRename(testSuite);
|
|
814
|
+
|
|
815
|
+
return (
|
|
816
|
+
<MenuContent ref={ref}>
|
|
817
|
+
<MenuContentItem onClick={rename}>Rename</MenuContentItem>
|
|
818
|
+
<MenuContentItem onClick={deleteSuite}>Delete</MenuContentItem>
|
|
819
|
+
</MenuContent>
|
|
820
|
+
);
|
|
821
|
+
}),
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
825
|
+
// Main Testing Tab — suite tabs at top, suite editor below
|
|
826
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
827
|
+
|
|
828
|
+
export const DataProductTestableEditor = observer(
|
|
829
|
+
(props: {
|
|
830
|
+
dataProductEditorState: DataProductEditorState;
|
|
831
|
+
isReadOnly: boolean;
|
|
832
|
+
}) => {
|
|
833
|
+
const { dataProductEditorState, isReadOnly } = props;
|
|
834
|
+
const testableState = dataProductEditorState.testableState;
|
|
835
|
+
const selectedSuiteState = testableState.selectedSuiteState;
|
|
836
|
+
const dp = testableState.dataProduct;
|
|
837
|
+
|
|
838
|
+
useEffect(() => {
|
|
839
|
+
testableState.init();
|
|
840
|
+
}, [testableState]);
|
|
841
|
+
|
|
842
|
+
const addSuite = (): void => {
|
|
843
|
+
testableState.setShowCreateSuiteModal(true);
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
const changeSuite = (suite: DataProductTestSuite): void => {
|
|
847
|
+
testableState.changeSuite(suite);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const renameSuite = (val: string): void =>
|
|
851
|
+
testSuite_setId(guaranteeNonNullable(testableState.suiteToRename), val);
|
|
852
|
+
|
|
853
|
+
return (
|
|
854
|
+
<Panel className="service-test-suite-editor">
|
|
855
|
+
<PanelLoadingIndicator
|
|
856
|
+
isLoading={testableState.runningAllTestsState.isInProgress}
|
|
857
|
+
/>
|
|
858
|
+
|
|
859
|
+
{testableState.showCreateSuiteModal && (
|
|
860
|
+
<CreateSuiteModal
|
|
861
|
+
testableState={testableState}
|
|
862
|
+
onClose={(): void => testableState.setShowCreateSuiteModal(false)}
|
|
863
|
+
/>
|
|
864
|
+
)}
|
|
865
|
+
|
|
866
|
+
{testableState.showCreateTestModal && selectedSuiteState && (
|
|
867
|
+
<CreateTestModal
|
|
868
|
+
suiteState={selectedSuiteState}
|
|
869
|
+
onClose={(): void => testableState.setShowCreateTestModal(false)}
|
|
870
|
+
/>
|
|
871
|
+
)}
|
|
872
|
+
|
|
873
|
+
<PanelHeader>
|
|
874
|
+
{dp.tests.length ? (
|
|
875
|
+
<PanelHeader className="service-test-suite-editor__header service-test-suite-editor__header--with-tabs">
|
|
876
|
+
<div className="uml-element-editor__tabs">
|
|
877
|
+
{dp.tests.map((suite) => (
|
|
878
|
+
<div
|
|
879
|
+
key={suite.id}
|
|
880
|
+
onClick={(): void => changeSuite(suite)}
|
|
881
|
+
className={clsx('service-test-suite-editor__tab', {
|
|
882
|
+
'service-test-suite-editor__tab--active':
|
|
883
|
+
selectedSuiteState?.suite === suite,
|
|
884
|
+
})}
|
|
885
|
+
>
|
|
886
|
+
<ContextMenu
|
|
887
|
+
className="mapping-editor__header__tab__content"
|
|
888
|
+
content={
|
|
889
|
+
<SuiteHeaderTabContextMenu
|
|
890
|
+
testableState={testableState}
|
|
891
|
+
testSuite={suite}
|
|
892
|
+
/>
|
|
893
|
+
}
|
|
894
|
+
>
|
|
895
|
+
{suite.id}
|
|
896
|
+
</ContextMenu>
|
|
897
|
+
</div>
|
|
898
|
+
))}
|
|
899
|
+
</div>
|
|
900
|
+
</PanelHeader>
|
|
901
|
+
) : (
|
|
902
|
+
<div></div>
|
|
903
|
+
)}
|
|
904
|
+
<PanelHeaderActions>
|
|
905
|
+
<PanelHeaderActionItem onClick={addSuite} title="Add Test Suite">
|
|
906
|
+
<PlusIcon />
|
|
907
|
+
</PanelHeaderActionItem>
|
|
908
|
+
</PanelHeaderActions>
|
|
909
|
+
</PanelHeader>
|
|
910
|
+
<Panel className="service-test-suite-editor">
|
|
911
|
+
{selectedSuiteState && (
|
|
912
|
+
<DataProductTestSuiteEditor suiteState={selectedSuiteState} />
|
|
913
|
+
)}
|
|
914
|
+
{!dp.tests.length && (
|
|
915
|
+
<BlankPanelPlaceholder
|
|
916
|
+
text="Add Test Suite"
|
|
917
|
+
onClick={addSuite}
|
|
918
|
+
clickActionType="add"
|
|
919
|
+
tooltipText="Click to add test suite"
|
|
920
|
+
/>
|
|
921
|
+
)}
|
|
922
|
+
{testableState.suiteToRename && (
|
|
923
|
+
<RenameModal
|
|
924
|
+
val={testableState.suiteToRename.id}
|
|
925
|
+
isReadOnly={isReadOnly}
|
|
926
|
+
showModal={true}
|
|
927
|
+
closeModal={(): void => testableState.setSuiteToRename(undefined)}
|
|
928
|
+
setValue={renameSuite}
|
|
929
|
+
/>
|
|
930
|
+
)}
|
|
931
|
+
</Panel>
|
|
932
|
+
</Panel>
|
|
933
|
+
);
|
|
934
|
+
},
|
|
935
|
+
);
|