@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
package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx
CHANGED
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
import { observer } from 'mobx-react-lite';
|
|
18
18
|
import {
|
|
19
19
|
BlankPanelPlaceholder,
|
|
20
|
+
BlankPanelContent,
|
|
20
21
|
ContextMenu,
|
|
21
22
|
CustomSelectorInput,
|
|
22
23
|
Dialog,
|
|
24
|
+
ErrorWarnIcon,
|
|
23
25
|
FilledWindowMaximizeIcon,
|
|
24
26
|
MenuContent,
|
|
25
27
|
MenuContentItem,
|
|
@@ -29,8 +31,11 @@ import {
|
|
|
29
31
|
ModalFooterButton,
|
|
30
32
|
ModalHeader,
|
|
31
33
|
ModalTitle,
|
|
32
|
-
|
|
34
|
+
Panel,
|
|
33
35
|
PanelFormTextField,
|
|
36
|
+
PanelHeader,
|
|
37
|
+
PanelHeaderActionItem,
|
|
38
|
+
PanelHeaderActions,
|
|
34
39
|
PlayIcon,
|
|
35
40
|
PlusIcon,
|
|
36
41
|
RefreshIcon,
|
|
@@ -68,7 +73,6 @@ import {
|
|
|
68
73
|
import {
|
|
69
74
|
TESTABLE_RESULT,
|
|
70
75
|
getTestableResultFromTestResult,
|
|
71
|
-
getTestableResultFromTestResults,
|
|
72
76
|
} from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
|
|
73
77
|
import { flowResult } from 'mobx';
|
|
74
78
|
import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js';
|
|
@@ -131,97 +135,6 @@ const FunctionTestableContextMenu = observer(
|
|
|
131
135
|
}),
|
|
132
136
|
);
|
|
133
137
|
|
|
134
|
-
const FunctionTestSuiteItem = observer(
|
|
135
|
-
(props: {
|
|
136
|
-
suite: FunctionTestSuite;
|
|
137
|
-
functionTestableState: FunctionTestableState;
|
|
138
|
-
}) => {
|
|
139
|
-
const { suite, functionTestableState } = props;
|
|
140
|
-
const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] =
|
|
141
|
-
useState(false);
|
|
142
|
-
const isReadOnly = functionTestableState.functionEditorState.isReadOnly;
|
|
143
|
-
const openSuite = (): void => functionTestableState.changeSuite(suite);
|
|
144
|
-
const results = functionTestableState.testableResults?.filter(
|
|
145
|
-
(t) => t.parentSuite?.id === suite.id,
|
|
146
|
-
);
|
|
147
|
-
const isRunning =
|
|
148
|
-
functionTestableState.isRunningTestableSuitesState.isInProgress ||
|
|
149
|
-
(functionTestableState.isRunningFailingSuitesState.isInProgress &&
|
|
150
|
-
functionTestableState.failingSuites.includes(suite)) ||
|
|
151
|
-
functionTestableState.runningSuite === suite;
|
|
152
|
-
const isActive = functionTestableState.selectedTestSuite?.suite === suite;
|
|
153
|
-
const _testableResult = getTestableResultFromTestResults(results);
|
|
154
|
-
const testableResult = isRunning
|
|
155
|
-
? TESTABLE_RESULT.IN_PROGRESS
|
|
156
|
-
: _testableResult;
|
|
157
|
-
const resultIcon = getTestableResultIcon(testableResult);
|
|
158
|
-
const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
|
|
159
|
-
const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false);
|
|
160
|
-
const add = (): void => {
|
|
161
|
-
// TODO
|
|
162
|
-
};
|
|
163
|
-
const _delete = (): void => {
|
|
164
|
-
functionTestableState.deleteTestSuite(suite);
|
|
165
|
-
};
|
|
166
|
-
const rename = (): void => {
|
|
167
|
-
functionTestableState.setRenameComponent(suite);
|
|
168
|
-
};
|
|
169
|
-
const runSuite = (): void => {
|
|
170
|
-
flowResult(functionTestableState.runSuite(suite)).catch(
|
|
171
|
-
functionTestableState.editorStore.applicationStore.alertUnhandledError,
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
return (
|
|
175
|
-
<ContextMenu
|
|
176
|
-
className={clsx(
|
|
177
|
-
'testable-test-explorer__item',
|
|
178
|
-
{
|
|
179
|
-
'testable-test-explorer__item--selected-from-context-menu':
|
|
180
|
-
!isActive && isSelectedFromContextMenu,
|
|
181
|
-
},
|
|
182
|
-
{ 'testable-test-explorer__item--active': isActive },
|
|
183
|
-
)}
|
|
184
|
-
disabled={isReadOnly}
|
|
185
|
-
content={
|
|
186
|
-
<FunctionTestableContextMenu
|
|
187
|
-
addName="Suite"
|
|
188
|
-
add={add}
|
|
189
|
-
_delete={_delete}
|
|
190
|
-
rename={rename}
|
|
191
|
-
/>
|
|
192
|
-
}
|
|
193
|
-
menuProps={{ elevation: 7 }}
|
|
194
|
-
onOpen={onContextMenuOpen}
|
|
195
|
-
onClose={onContextMenuClose}
|
|
196
|
-
>
|
|
197
|
-
<div
|
|
198
|
-
className={clsx('testable-test-explorer__item__label')}
|
|
199
|
-
onClick={openSuite}
|
|
200
|
-
tabIndex={-1}
|
|
201
|
-
>
|
|
202
|
-
<div className="testable-test-explorer__item__label__icon">
|
|
203
|
-
{resultIcon}
|
|
204
|
-
</div>
|
|
205
|
-
<div className="testable-test-explorer__item__label__text">
|
|
206
|
-
{suite.id}
|
|
207
|
-
</div>
|
|
208
|
-
<div className="mapping-test-explorer__item__actions">
|
|
209
|
-
<button
|
|
210
|
-
className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
|
|
211
|
-
onClick={runSuite}
|
|
212
|
-
disabled={isRunning}
|
|
213
|
-
tabIndex={-1}
|
|
214
|
-
title={`Run ${suite.id}`}
|
|
215
|
-
>
|
|
216
|
-
{<PlayIcon />}
|
|
217
|
-
</button>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
</ContextMenu>
|
|
221
|
-
);
|
|
222
|
-
},
|
|
223
|
-
);
|
|
224
|
-
|
|
225
138
|
const FunctionTestDataStateEditor = observer(
|
|
226
139
|
(props: {
|
|
227
140
|
functionTestSuiteState: FunctionTestSuiteState;
|
|
@@ -813,8 +726,11 @@ const CreateTestModal = observer(
|
|
|
813
726
|
const close = (): void => functionSuiteState.setShowModal(false);
|
|
814
727
|
const create = (): void => {
|
|
815
728
|
if (id) {
|
|
816
|
-
functionSuiteState.addNewTest(id)
|
|
817
|
-
|
|
729
|
+
flowResult(functionSuiteState.addNewTest(id))
|
|
730
|
+
.then(() => close())
|
|
731
|
+
.catch(
|
|
732
|
+
functionSuiteState.editorStore.applicationStore.alertUnhandledError,
|
|
733
|
+
);
|
|
818
734
|
}
|
|
819
735
|
};
|
|
820
736
|
|
|
@@ -859,11 +775,215 @@ const CreateTestModal = observer(
|
|
|
859
775
|
);
|
|
860
776
|
},
|
|
861
777
|
);
|
|
862
|
-
|
|
778
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
779
|
+
// Add Element Modal
|
|
780
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
781
|
+
|
|
782
|
+
const AddDataElementModal = observer(
|
|
783
|
+
(props: { functionTestSuiteState: FunctionTestSuiteState }) => {
|
|
784
|
+
const { functionTestSuiteState } = props;
|
|
785
|
+
const dataState = functionTestSuiteState.dataState;
|
|
786
|
+
const applicationStore =
|
|
787
|
+
functionTestSuiteState.editorStore.applicationStore;
|
|
788
|
+
const options = dataState.availableElementsToAdd.map((e) => ({
|
|
789
|
+
value: e.path,
|
|
790
|
+
label: e.path,
|
|
791
|
+
}));
|
|
792
|
+
const [selectedPath, setSelectedPath] = useState<string | undefined>(
|
|
793
|
+
options[0]?.value,
|
|
794
|
+
);
|
|
795
|
+
const close = (): void => dataState.setShowAddElementModal(false);
|
|
796
|
+
const add = (): void => {
|
|
797
|
+
if (selectedPath) {
|
|
798
|
+
dataState.addDataElement(selectedPath);
|
|
799
|
+
close();
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
const onChange = (val: { label: string; value: string } | null): void => {
|
|
803
|
+
setSelectedPath(val?.value);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
return (
|
|
807
|
+
<Dialog
|
|
808
|
+
open={dataState.showAddElementModal}
|
|
809
|
+
onClose={close}
|
|
810
|
+
classes={{ container: 'search-modal__container' }}
|
|
811
|
+
slotProps={{
|
|
812
|
+
paper: {
|
|
813
|
+
classes: { root: 'search-modal__inner-container' },
|
|
814
|
+
},
|
|
815
|
+
}}
|
|
816
|
+
>
|
|
817
|
+
<Modal
|
|
818
|
+
darkMode={
|
|
819
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
820
|
+
}
|
|
821
|
+
>
|
|
822
|
+
<ModalHeader>
|
|
823
|
+
<ModalTitle title="Add Element" />
|
|
824
|
+
</ModalHeader>
|
|
825
|
+
<ModalBody>
|
|
826
|
+
<CustomSelectorInput
|
|
827
|
+
className="panel__content__form__section__dropdown"
|
|
828
|
+
options={options}
|
|
829
|
+
onChange={onChange}
|
|
830
|
+
value={
|
|
831
|
+
selectedPath
|
|
832
|
+
? { value: selectedPath, label: selectedPath }
|
|
833
|
+
: null
|
|
834
|
+
}
|
|
835
|
+
placeholder="Select element..."
|
|
836
|
+
darkMode={
|
|
837
|
+
!applicationStore.layoutService
|
|
838
|
+
.TEMPORARY__isLightColorThemeEnabled
|
|
839
|
+
}
|
|
840
|
+
/>
|
|
841
|
+
</ModalBody>
|
|
842
|
+
<ModalFooter>
|
|
843
|
+
<ModalFooterButton
|
|
844
|
+
disabled={!selectedPath}
|
|
845
|
+
onClick={add}
|
|
846
|
+
text="Add"
|
|
847
|
+
/>
|
|
848
|
+
<ModalFooterButton onClick={close} text="Close" type="secondary" />
|
|
849
|
+
</ModalFooter>
|
|
850
|
+
</Modal>
|
|
851
|
+
</Dialog>
|
|
852
|
+
);
|
|
853
|
+
},
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
857
|
+
// Test Data Editor (left panel) — elements list + per-element data editor
|
|
858
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
859
|
+
|
|
860
|
+
const FunctionTestDataPanel = observer(
|
|
861
|
+
(props: { functionTestSuiteState: FunctionTestSuiteState }) => {
|
|
862
|
+
const { functionTestSuiteState } = props;
|
|
863
|
+
const dataState = functionTestSuiteState.dataState;
|
|
864
|
+
const hasIngestOrDataProductAccessors =
|
|
865
|
+
functionTestSuiteState.functionTestableState
|
|
866
|
+
.resolvedIngestOrDataProductAccessors.length > 0;
|
|
867
|
+
const hasTestData = Boolean(dataState.dataHolder.testData?.length);
|
|
868
|
+
|
|
869
|
+
const addStoreTestData = (): void => {
|
|
870
|
+
if (!dataState.availableElementsToAdd.length) {
|
|
871
|
+
functionTestSuiteState.editorStore.applicationStore.notificationService.notifyWarning(
|
|
872
|
+
hasIngestOrDataProductAccessors
|
|
873
|
+
? `All referenced elements' data is already present`
|
|
874
|
+
: `No elements available to add`,
|
|
875
|
+
);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
dataState.setShowAddElementModal(true);
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
return (
|
|
882
|
+
<div
|
|
883
|
+
className={clsx('service-test-data-editor panel', {
|
|
884
|
+
'service-test-data-editor--no-data': !hasTestData,
|
|
885
|
+
})}
|
|
886
|
+
>
|
|
887
|
+
<div className="service-test-data-editor__data">
|
|
888
|
+
<ResizablePanelGroup orientation="vertical">
|
|
889
|
+
<ResizablePanel minSize={100} size={180}>
|
|
890
|
+
<div className="binding-editor__header">
|
|
891
|
+
<div className="binding-editor__header__title">
|
|
892
|
+
<div className="panel__header__title__content">Test Data</div>
|
|
893
|
+
</div>
|
|
894
|
+
<div className="panel__header__actions">
|
|
895
|
+
<button
|
|
896
|
+
className="panel__header__action"
|
|
897
|
+
tabIndex={-1}
|
|
898
|
+
onClick={addStoreTestData}
|
|
899
|
+
title="Add Element"
|
|
900
|
+
>
|
|
901
|
+
<PlusIcon />
|
|
902
|
+
</button>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
{!hasTestData ? (
|
|
906
|
+
<div className="service-test-data-editor__warning">
|
|
907
|
+
<ErrorWarnIcon />
|
|
908
|
+
<span>Add an element to configure test data</span>
|
|
909
|
+
</div>
|
|
910
|
+
) : (
|
|
911
|
+
<div>
|
|
912
|
+
{(dataState.dataHolder.testData ?? []).map((td) => (
|
|
913
|
+
<div
|
|
914
|
+
key={td.element.value.path}
|
|
915
|
+
className={clsx('testable-test-explorer__item', {
|
|
916
|
+
'testable-test-explorer__item--active':
|
|
917
|
+
dataState.selectedDataState?.storeTestData === td,
|
|
918
|
+
})}
|
|
919
|
+
>
|
|
920
|
+
<div
|
|
921
|
+
className="testable-test-explorer__item__label"
|
|
922
|
+
onClick={(): void => dataState.openStoreTestData(td)}
|
|
923
|
+
tabIndex={-1}
|
|
924
|
+
>
|
|
925
|
+
<div className="testable-test-explorer__item__label__text">
|
|
926
|
+
{td.element.value.path}
|
|
927
|
+
</div>
|
|
928
|
+
<div className="mapping-test-explorer__item__actions">
|
|
929
|
+
<button
|
|
930
|
+
className="mapping-test-explorer__item__action"
|
|
931
|
+
onClick={(e): void => {
|
|
932
|
+
e.stopPropagation();
|
|
933
|
+
dataState.deleteStoreTestData(td);
|
|
934
|
+
}}
|
|
935
|
+
tabIndex={-1}
|
|
936
|
+
title="Delete"
|
|
937
|
+
>
|
|
938
|
+
<TimesIcon />
|
|
939
|
+
</button>
|
|
940
|
+
</div>
|
|
941
|
+
</div>
|
|
942
|
+
</div>
|
|
943
|
+
))}
|
|
944
|
+
</div>
|
|
945
|
+
)}
|
|
946
|
+
</ResizablePanel>
|
|
947
|
+
<ResizablePanelSplitter>
|
|
948
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
949
|
+
</ResizablePanelSplitter>
|
|
950
|
+
<ResizablePanel minSize={200}>
|
|
951
|
+
{dataState.selectedDataState ? (
|
|
952
|
+
<FunctionTestDataStateEditor
|
|
953
|
+
functionTestSuiteState={functionTestSuiteState}
|
|
954
|
+
storeTestDataState={dataState.selectedDataState}
|
|
955
|
+
/>
|
|
956
|
+
) : (
|
|
957
|
+
<BlankPanelContent>
|
|
958
|
+
Select an element to configure its test data
|
|
959
|
+
</BlankPanelContent>
|
|
960
|
+
)}
|
|
961
|
+
</ResizablePanel>
|
|
962
|
+
</ResizablePanelGroup>
|
|
963
|
+
</div>
|
|
964
|
+
{dataState.showAddElementModal && (
|
|
965
|
+
<AddDataElementModal
|
|
966
|
+
functionTestSuiteState={functionTestSuiteState}
|
|
967
|
+
/>
|
|
968
|
+
)}
|
|
969
|
+
</div>
|
|
970
|
+
);
|
|
971
|
+
},
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
975
|
+
// Tests Editor (right panel) — tests list + test detail (assertion/setup)
|
|
976
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
977
|
+
|
|
978
|
+
const FunctionTestsPanel = observer(
|
|
863
979
|
(props: { functionTestSuiteState: FunctionTestSuiteState }) => {
|
|
864
980
|
const { functionTestSuiteState } = props;
|
|
865
981
|
const editorStore = functionTestSuiteState.editorStore;
|
|
982
|
+
const isReadOnly =
|
|
983
|
+
functionTestSuiteState.functionTestableState.functionEditorState
|
|
984
|
+
.isReadOnly;
|
|
866
985
|
const selectedTestState = functionTestSuiteState.selectTestState;
|
|
986
|
+
|
|
867
987
|
const addTest = (): void => {
|
|
868
988
|
functionTestSuiteState.setShowModal(true);
|
|
869
989
|
};
|
|
@@ -877,134 +997,160 @@ const FunctionTestSuiteEditorInner = observer(
|
|
|
877
997
|
editorStore.applicationStore.alertUnhandledError,
|
|
878
998
|
);
|
|
879
999
|
};
|
|
880
|
-
const renderFunctionTestEditor = (): React.ReactNode => {
|
|
881
|
-
if (selectedTestState) {
|
|
882
|
-
return <FunctionTestEditor functionTestState={selectedTestState} />;
|
|
883
|
-
} else if (!functionTestSuiteState.suite.tests.length) {
|
|
884
|
-
return (
|
|
885
|
-
<BlankPanelPlaceholder
|
|
886
|
-
text="Add Function Test"
|
|
887
|
-
onClick={addTest}
|
|
888
|
-
clickActionType="add"
|
|
889
|
-
tooltipText="Click to add function test"
|
|
890
|
-
/>
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
return null;
|
|
894
|
-
};
|
|
895
1000
|
|
|
896
1001
|
return (
|
|
897
|
-
<
|
|
898
|
-
<
|
|
899
|
-
<
|
|
900
|
-
<
|
|
901
|
-
<div className="
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
className="
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1002
|
+
<div className="panel service-test-editor">
|
|
1003
|
+
<div className="service-test-editor__content">
|
|
1004
|
+
<ResizablePanelGroup orientation="vertical">
|
|
1005
|
+
<ResizablePanel minSize={100} size={200}>
|
|
1006
|
+
<div className="binding-editor__header">
|
|
1007
|
+
<div className="binding-editor__header__title">
|
|
1008
|
+
<div className="panel__header__title__content">Tests</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
<div className="panel__header__actions">
|
|
1011
|
+
<button
|
|
1012
|
+
className="panel__header__action testable-test-explorer__play__all__icon"
|
|
1013
|
+
tabIndex={-1}
|
|
1014
|
+
onClick={runTests}
|
|
1015
|
+
title="Run All Tests"
|
|
1016
|
+
>
|
|
1017
|
+
<RunAllIcon />
|
|
1018
|
+
</button>
|
|
1019
|
+
<button
|
|
1020
|
+
className="panel__header__action testable-test-explorer__play__all__icon"
|
|
1021
|
+
tabIndex={-1}
|
|
1022
|
+
onClick={runFailingTests}
|
|
1023
|
+
title="Run All Failing Tests"
|
|
1024
|
+
>
|
|
1025
|
+
<RunErrorsIcon />
|
|
1026
|
+
</button>
|
|
1027
|
+
{!isReadOnly && (
|
|
1028
|
+
<button
|
|
1029
|
+
className="panel__header__action"
|
|
1030
|
+
tabIndex={-1}
|
|
1031
|
+
onClick={addTest}
|
|
1032
|
+
title="Add Function Test"
|
|
1033
|
+
>
|
|
1034
|
+
<PlusIcon />
|
|
1035
|
+
</button>
|
|
1036
|
+
)}
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div>
|
|
1040
|
+
{functionTestSuiteState.testStates.map((test) => (
|
|
1041
|
+
<FunctionTestItem
|
|
1042
|
+
key={test.uuid}
|
|
1043
|
+
functionTestState={test}
|
|
1044
|
+
suiteState={functionTestSuiteState}
|
|
1045
|
+
/>
|
|
1046
|
+
))}
|
|
1047
|
+
</div>
|
|
1048
|
+
</ResizablePanel>
|
|
1049
|
+
<ResizablePanelSplitter>
|
|
1050
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
1051
|
+
</ResizablePanelSplitter>
|
|
1052
|
+
<ResizablePanel minSize={56}>
|
|
1053
|
+
{selectedTestState ? (
|
|
1054
|
+
<FunctionTestEditor functionTestState={selectedTestState} />
|
|
1055
|
+
) : (
|
|
1056
|
+
<BlankPanelPlaceholder
|
|
1057
|
+
text="Select a test"
|
|
1058
|
+
tooltipText="Select a test from the list above"
|
|
1059
|
+
/>
|
|
1060
|
+
)}
|
|
1061
|
+
</ResizablePanel>
|
|
1062
|
+
</ResizablePanelGroup>
|
|
1063
|
+
</div>
|
|
1064
|
+
{functionTestSuiteState.showCreateModal && (
|
|
1065
|
+
<CreateTestModal functionSuiteState={functionTestSuiteState} />
|
|
1066
|
+
)}
|
|
1067
|
+
</div>
|
|
950
1068
|
);
|
|
951
1069
|
},
|
|
952
1070
|
);
|
|
953
1071
|
|
|
1072
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1073
|
+
// Suite Editor — horizontal split: test data (left) + tests (right)
|
|
1074
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1075
|
+
|
|
954
1076
|
const FunctionTestSuiteEditor = observer(
|
|
955
1077
|
(props: { functionTestSuiteState: FunctionTestSuiteState }) => {
|
|
956
1078
|
const { functionTestSuiteState } = props;
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1079
|
+
const hasTestData = Boolean(
|
|
1080
|
+
functionTestSuiteState.dataState.dataHolder.testData?.length,
|
|
1081
|
+
);
|
|
1082
|
+
const hasIngestOrDataProductAccessors =
|
|
1083
|
+
functionTestSuiteState.functionTestableState
|
|
1084
|
+
.resolvedIngestOrDataProductAccessors.length > 0;
|
|
1085
|
+
const showTestDataPanel =
|
|
1086
|
+
functionTestSuiteState.functionTestableState.containsRuntime ||
|
|
1087
|
+
hasIngestOrDataProductAccessors ||
|
|
1088
|
+
hasTestData;
|
|
961
1089
|
|
|
962
|
-
if (!
|
|
1090
|
+
if (!showTestDataPanel) {
|
|
1091
|
+
// No test data — just show the tests panel full width
|
|
963
1092
|
return (
|
|
964
|
-
<
|
|
965
|
-
functionTestSuiteState={functionTestSuiteState}
|
|
966
|
-
|
|
1093
|
+
<div className="service-test-suite-editor">
|
|
1094
|
+
<FunctionTestsPanel functionTestSuiteState={functionTestSuiteState} />
|
|
1095
|
+
</div>
|
|
967
1096
|
);
|
|
968
1097
|
}
|
|
1098
|
+
|
|
969
1099
|
return (
|
|
970
|
-
<
|
|
971
|
-
<
|
|
972
|
-
<
|
|
973
|
-
|
|
974
|
-
<>
|
|
975
|
-
{dataState.selectedDataState && (
|
|
976
|
-
<FunctionTestDataStateEditor
|
|
977
|
-
functionTestSuiteState={functionTestSuiteState}
|
|
978
|
-
storeTestDataState={dataState.selectedDataState}
|
|
979
|
-
/>
|
|
980
|
-
)}
|
|
981
|
-
</>
|
|
982
|
-
) : (
|
|
983
|
-
<BlankPanelPlaceholder
|
|
984
|
-
text="Add Store Test Data"
|
|
985
|
-
onClick={addStoreTestData}
|
|
986
|
-
clickActionType="add"
|
|
987
|
-
tooltipText="Click to add store test data"
|
|
988
|
-
/>
|
|
989
|
-
)}
|
|
990
|
-
</div>
|
|
991
|
-
</ResizablePanel>
|
|
992
|
-
<ResizablePanelSplitter>
|
|
993
|
-
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
994
|
-
</ResizablePanelSplitter>
|
|
995
|
-
<ResizablePanel minSize={56}>
|
|
996
|
-
{
|
|
997
|
-
<FunctionTestSuiteEditorInner
|
|
1100
|
+
<div className="service-test-suite-editor">
|
|
1101
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
1102
|
+
<ResizablePanel size={300} minSize={28}>
|
|
1103
|
+
<FunctionTestDataPanel
|
|
998
1104
|
functionTestSuiteState={functionTestSuiteState}
|
|
999
1105
|
/>
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1106
|
+
</ResizablePanel>
|
|
1107
|
+
<ResizablePanelSplitter>
|
|
1108
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
1109
|
+
</ResizablePanelSplitter>
|
|
1110
|
+
<ResizablePanel minSize={56}>
|
|
1111
|
+
<FunctionTestsPanel
|
|
1112
|
+
functionTestSuiteState={functionTestSuiteState}
|
|
1113
|
+
/>
|
|
1114
|
+
</ResizablePanel>
|
|
1115
|
+
</ResizablePanelGroup>
|
|
1116
|
+
</div>
|
|
1003
1117
|
);
|
|
1004
1118
|
},
|
|
1005
1119
|
);
|
|
1006
1120
|
|
|
1007
|
-
|
|
1121
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1122
|
+
// Suite Tab Context Menu (rename/delete)
|
|
1123
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1124
|
+
|
|
1125
|
+
const SuiteHeaderTabContextMenu = observer(
|
|
1126
|
+
forwardRef<
|
|
1127
|
+
HTMLDivElement,
|
|
1128
|
+
{
|
|
1129
|
+
suite: FunctionTestSuite;
|
|
1130
|
+
functionTestableState: FunctionTestableState;
|
|
1131
|
+
}
|
|
1132
|
+
>(function SuiteHeaderTabContextMenu(props, ref) {
|
|
1133
|
+
const { suite, functionTestableState } = props;
|
|
1134
|
+
const deleteSuite = (): void => {
|
|
1135
|
+
functionTestableState.deleteTestSuite(suite);
|
|
1136
|
+
};
|
|
1137
|
+
const rename = (): void => {
|
|
1138
|
+
functionTestableState.setRenameComponent(suite);
|
|
1139
|
+
};
|
|
1140
|
+
return (
|
|
1141
|
+
<MenuContent ref={ref}>
|
|
1142
|
+
<MenuContentItem onClick={rename}>Rename</MenuContentItem>
|
|
1143
|
+
<MenuContentItem onClick={deleteSuite}>Delete</MenuContentItem>
|
|
1144
|
+
</MenuContent>
|
|
1145
|
+
);
|
|
1146
|
+
}),
|
|
1147
|
+
);
|
|
1148
|
+
|
|
1149
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1150
|
+
// Create Suite Modal
|
|
1151
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1152
|
+
|
|
1153
|
+
const CreateFunctionTestSuiteModal = observer(
|
|
1008
1154
|
(props: { functionTestableEditorState: FunctionTestableState }) => {
|
|
1009
1155
|
const { functionTestableEditorState } = props;
|
|
1010
1156
|
const applicationStore =
|
|
@@ -1015,11 +1161,15 @@ const CreateFucntionTestSuiteModal = observer(
|
|
|
1015
1161
|
const [testName, setTestName] = useState<string | undefined>(undefined);
|
|
1016
1162
|
const isValid = suiteName && testName;
|
|
1017
1163
|
|
|
1018
|
-
// model
|
|
1019
1164
|
const close = (): void => functionTestableEditorState.setCreateSuite(false);
|
|
1020
1165
|
const create = (): void => {
|
|
1021
1166
|
if (suiteName && testName) {
|
|
1022
|
-
|
|
1167
|
+
flowResult(
|
|
1168
|
+
functionTestableEditorState.createSuite(suiteName, testName),
|
|
1169
|
+
).catch(
|
|
1170
|
+
functionTestableEditorState.editorStore.applicationStore
|
|
1171
|
+
.alertUnhandledError,
|
|
1172
|
+
);
|
|
1023
1173
|
}
|
|
1024
1174
|
};
|
|
1025
1175
|
return (
|
|
@@ -1086,6 +1236,10 @@ const CreateFucntionTestSuiteModal = observer(
|
|
|
1086
1236
|
},
|
|
1087
1237
|
);
|
|
1088
1238
|
|
|
1239
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1240
|
+
// Main Testing Tab — suite tabs at top, suite editor below
|
|
1241
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
1242
|
+
|
|
1089
1243
|
export const FunctionTestableEditor = observer(
|
|
1090
1244
|
(props: { functionTestableState: FunctionTestableState }) => {
|
|
1091
1245
|
const { functionTestableState } = props;
|
|
@@ -1093,128 +1247,91 @@ export const FunctionTestableEditor = observer(
|
|
|
1093
1247
|
const functionEditorState = functionTestableState.functionEditorState;
|
|
1094
1248
|
const isReadOnly = functionEditorState.isReadOnly;
|
|
1095
1249
|
const selectedSuiteState = functionTestableState.selectedTestSuite;
|
|
1096
|
-
|
|
1250
|
+
|
|
1097
1251
|
useEffect(() => {
|
|
1098
1252
|
functionTestableState.init();
|
|
1099
1253
|
}, [functionTestableState]);
|
|
1100
1254
|
|
|
1101
|
-
const runSuites = (): void => {
|
|
1102
|
-
functionTestableState.runTestable();
|
|
1103
|
-
};
|
|
1104
|
-
|
|
1105
|
-
const runFailingTests = (): void => {
|
|
1106
|
-
functionTestableState.runAllFailingSuites();
|
|
1107
|
-
};
|
|
1108
1255
|
const addSuite = (): void => {
|
|
1109
1256
|
functionTestableState.setCreateSuite(true);
|
|
1110
1257
|
};
|
|
1111
1258
|
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
);
|
|
1119
|
-
} else if (!suites.length) {
|
|
1120
|
-
return (
|
|
1121
|
-
<BlankPanelPlaceholder
|
|
1122
|
-
text="Add Test Suite"
|
|
1123
|
-
onClick={addSuite}
|
|
1124
|
-
clickActionType="add"
|
|
1125
|
-
tooltipText="Click to add test suite"
|
|
1126
|
-
/>
|
|
1127
|
-
);
|
|
1128
|
-
}
|
|
1129
|
-
return null;
|
|
1259
|
+
const changeSuite = (suite: FunctionTestSuite): void => {
|
|
1260
|
+
functionTestableState.changeSuite(suite);
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
const runSuites = (): void => {
|
|
1264
|
+
functionTestableState.runTestable();
|
|
1130
1265
|
};
|
|
1131
1266
|
|
|
1132
1267
|
const renameTestingComponent = (val: string): void => {
|
|
1133
1268
|
functionTestableState.renameTestableComponent(val);
|
|
1134
1269
|
};
|
|
1270
|
+
|
|
1135
1271
|
useApplicationNavigationContext(
|
|
1136
1272
|
LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.FUNCTION_EDITOR_TEST,
|
|
1137
1273
|
);
|
|
1138
1274
|
|
|
1139
1275
|
return (
|
|
1140
|
-
<
|
|
1141
|
-
|
|
1142
|
-
<
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
<button
|
|
1152
|
-
className="panel__header__action testable-test-explorer__play__all__icon"
|
|
1153
|
-
tabIndex={-1}
|
|
1154
|
-
onClick={runSuites}
|
|
1155
|
-
title="Run All Suites"
|
|
1156
|
-
>
|
|
1157
|
-
<RunAllIcon />
|
|
1158
|
-
</button>
|
|
1159
|
-
<button
|
|
1160
|
-
className="panel__header__action testable-test-explorer__play__all__icon"
|
|
1161
|
-
tabIndex={-1}
|
|
1162
|
-
onClick={runFailingTests}
|
|
1163
|
-
title="Run All Failing Tests"
|
|
1164
|
-
>
|
|
1165
|
-
<RunErrorsIcon />
|
|
1166
|
-
</button>
|
|
1167
|
-
<button
|
|
1168
|
-
className="panel__header__action"
|
|
1169
|
-
tabIndex={-1}
|
|
1170
|
-
onClick={addSuite}
|
|
1171
|
-
title="Add Function Suite"
|
|
1172
|
-
>
|
|
1173
|
-
<PlusIcon />
|
|
1174
|
-
</button>
|
|
1175
|
-
</div>
|
|
1176
|
-
</div>
|
|
1177
|
-
<PanelContent>
|
|
1276
|
+
<Panel className="service-test-suite-editor">
|
|
1277
|
+
{functionTestableState.createSuiteModal && (
|
|
1278
|
+
<CreateFunctionTestSuiteModal
|
|
1279
|
+
functionTestableEditorState={functionTestableState}
|
|
1280
|
+
/>
|
|
1281
|
+
)}
|
|
1282
|
+
|
|
1283
|
+
<PanelHeader>
|
|
1284
|
+
{suites.length ? (
|
|
1285
|
+
<PanelHeader className="service-test-suite-editor__header service-test-suite-editor__header--with-tabs">
|
|
1286
|
+
<div className="uml-element-editor__tabs">
|
|
1178
1287
|
{suites.map((suite) => (
|
|
1179
|
-
<
|
|
1288
|
+
<div
|
|
1180
1289
|
key={suite.id}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1290
|
+
onClick={(): void => changeSuite(suite)}
|
|
1291
|
+
className={clsx('service-test-suite-editor__tab', {
|
|
1292
|
+
'service-test-suite-editor__tab--active':
|
|
1293
|
+
selectedSuiteState?.suite === suite,
|
|
1294
|
+
})}
|
|
1295
|
+
>
|
|
1296
|
+
<ContextMenu
|
|
1297
|
+
className="mapping-editor__header__tab__content"
|
|
1298
|
+
content={
|
|
1299
|
+
<SuiteHeaderTabContextMenu
|
|
1300
|
+
functionTestableState={functionTestableState}
|
|
1301
|
+
suite={suite}
|
|
1302
|
+
/>
|
|
1303
|
+
}
|
|
1304
|
+
>
|
|
1305
|
+
{suite.id}
|
|
1306
|
+
</ContextMenu>
|
|
1307
|
+
</div>
|
|
1184
1308
|
))}
|
|
1185
|
-
{!suites.length && (
|
|
1186
|
-
<BlankPanelPlaceholder
|
|
1187
|
-
text="Add Test Suite"
|
|
1188
|
-
onClick={addSuite}
|
|
1189
|
-
clickActionType="add"
|
|
1190
|
-
tooltipText="Click to add test suite"
|
|
1191
|
-
/>
|
|
1192
|
-
)}
|
|
1193
|
-
{!suites.length && (
|
|
1194
|
-
<BlankPanelPlaceholder
|
|
1195
|
-
disabled={functionEditorState.isReadOnly}
|
|
1196
|
-
onClick={addSuite}
|
|
1197
|
-
text="Add a Test Suite"
|
|
1198
|
-
clickActionType="add"
|
|
1199
|
-
tooltipText="Click to add a new function test suite"
|
|
1200
|
-
/>
|
|
1201
|
-
)}
|
|
1202
|
-
</PanelContent>
|
|
1203
|
-
</ResizablePanel>
|
|
1204
|
-
<ResizablePanelSplitter>
|
|
1205
|
-
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
1206
|
-
</ResizablePanelSplitter>
|
|
1207
|
-
<ResizablePanel minSize={56}>
|
|
1208
|
-
<div className="function-test-suite-editor">
|
|
1209
|
-
<div className="function-test-suite-editor__content">
|
|
1210
|
-
{renderSuiteState()}
|
|
1211
|
-
</div>
|
|
1212
1309
|
</div>
|
|
1213
|
-
</
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1310
|
+
</PanelHeader>
|
|
1311
|
+
) : (
|
|
1312
|
+
<div></div>
|
|
1313
|
+
)}
|
|
1314
|
+
<PanelHeaderActions>
|
|
1315
|
+
<PanelHeaderActionItem onClick={runSuites} title="Run All Suites">
|
|
1316
|
+
<RunAllIcon />
|
|
1317
|
+
</PanelHeaderActionItem>
|
|
1318
|
+
<PanelHeaderActionItem onClick={addSuite} title="Add Test Suite">
|
|
1319
|
+
<PlusIcon />
|
|
1320
|
+
</PanelHeaderActionItem>
|
|
1321
|
+
</PanelHeaderActions>
|
|
1322
|
+
</PanelHeader>
|
|
1323
|
+
<Panel className="service-test-suite-editor">
|
|
1324
|
+
{selectedSuiteState && (
|
|
1325
|
+
<FunctionTestSuiteEditor
|
|
1326
|
+
functionTestSuiteState={selectedSuiteState}
|
|
1327
|
+
/>
|
|
1328
|
+
)}
|
|
1329
|
+
{!suites.length && (
|
|
1330
|
+
<BlankPanelPlaceholder
|
|
1331
|
+
text="Add Test Suite"
|
|
1332
|
+
onClick={addSuite}
|
|
1333
|
+
clickActionType="add"
|
|
1334
|
+
tooltipText="Click to add test suite"
|
|
1218
1335
|
/>
|
|
1219
1336
|
)}
|
|
1220
1337
|
{functionTestableState.testableComponentToRename && (
|
|
@@ -1231,8 +1348,8 @@ export const FunctionTestableEditor = observer(
|
|
|
1231
1348
|
}
|
|
1232
1349
|
/>
|
|
1233
1350
|
)}
|
|
1234
|
-
</
|
|
1235
|
-
</
|
|
1351
|
+
</Panel>
|
|
1352
|
+
</Panel>
|
|
1236
1353
|
);
|
|
1237
1354
|
},
|
|
1238
1355
|
);
|