@finos/legend-extension-dsl-data-space-studio 0.1.340 → 0.1.342

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 (73) hide show
  1. package/lib/components/DSL_DataProduct_ElementDriver.d.ts.map +1 -1
  2. package/lib/components/DSL_DataProduct_ElementDriver.js +1 -10
  3. package/lib/components/DSL_DataProduct_ElementDriver.js.map +1 -1
  4. package/lib/components/DataSpaceEditor.js +9 -4
  5. package/lib/components/DataSpaceEditor.js.map +1 -1
  6. package/lib/components/DataSpaceExecutableQueryAction.d.ts +20 -0
  7. package/lib/components/DataSpaceExecutableQueryAction.d.ts.map +1 -0
  8. package/lib/components/DataSpaceExecutableQueryAction.js +85 -0
  9. package/lib/components/DataSpaceExecutableQueryAction.js.map +1 -0
  10. package/lib/components/DataSpaceExecutionContextEditor.d.ts +1 -12
  11. package/lib/components/DataSpaceExecutionContextEditor.d.ts.map +1 -1
  12. package/lib/components/DataSpaceExecutionContextEditor.js +206 -134
  13. package/lib/components/DataSpaceExecutionContextEditor.js.map +1 -1
  14. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.d.ts.map +1 -1
  15. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.js +130 -26
  16. package/lib/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.js.map +1 -1
  17. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.d.ts.map +1 -1
  18. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.js +16 -75
  19. package/lib/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.js.map +1 -1
  20. package/lib/components/DataSpaceGeneralEditor/{DataSpaceDefaultExecutionContextSection.d.ts → DataSpaceHomeTab.d.ts} +3 -3
  21. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.d.ts.map +1 -0
  22. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.js +100 -0
  23. package/lib/components/DataSpaceGeneralEditor/DataSpaceHomeTab.js.map +1 -0
  24. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.d.ts +23 -0
  25. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.d.ts.map +1 -0
  26. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.js +31 -0
  27. package/lib/components/DataSpaceGeneralEditor/DataSpaceSidebar.js.map +1 -0
  28. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.d.ts +28 -0
  29. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.d.ts.map +1 -0
  30. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.js +45 -0
  31. package/lib/components/DataSpaceGeneralEditor/DataSpaceValidation.js.map +1 -0
  32. package/lib/components/DataSpaceQueryAction.d.ts.map +1 -1
  33. package/lib/components/DataSpaceQueryAction.js +11 -8
  34. package/lib/components/DataSpaceQueryAction.js.map +1 -1
  35. package/lib/index.css +2 -2
  36. package/lib/index.css.map +1 -1
  37. package/lib/package.json +1 -1
  38. package/lib/stores/DataSpaceEditorState.d.ts +9 -0
  39. package/lib/stores/DataSpaceEditorState.d.ts.map +1 -1
  40. package/lib/stores/DataSpaceEditorState.js +15 -0
  41. package/lib/stores/DataSpaceEditorState.js.map +1 -1
  42. package/lib/stores/DataSpaceExecutableTemplateState.d.ts +48 -0
  43. package/lib/stores/DataSpaceExecutableTemplateState.d.ts.map +1 -0
  44. package/lib/stores/DataSpaceExecutableTemplateState.js +105 -0
  45. package/lib/stores/DataSpaceExecutableTemplateState.js.map +1 -0
  46. package/lib/stores/DataSpaceExecutionContextState.d.ts +4 -9
  47. package/lib/stores/DataSpaceExecutionContextState.d.ts.map +1 -1
  48. package/lib/stores/DataSpaceExecutionContextState.js +13 -39
  49. package/lib/stores/DataSpaceExecutionContextState.js.map +1 -1
  50. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.d.ts +12 -6
  51. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.d.ts.map +1 -1
  52. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.js +45 -3
  53. package/lib/stores/studio/DSL_DataSpace_GraphModifierHelper.js.map +1 -1
  54. package/package.json +9 -9
  55. package/src/components/DSL_DataProduct_ElementDriver.tsx +0 -14
  56. package/src/components/DataSpaceEditor.tsx +10 -10
  57. package/src/components/DataSpaceExecutableQueryAction.tsx +159 -0
  58. package/src/components/DataSpaceExecutionContextEditor.tsx +699 -558
  59. package/src/components/DataSpaceGeneralEditor/DataSpaceExecutablesSection.tsx +517 -60
  60. package/src/components/DataSpaceGeneralEditor/DataSpaceGeneralEditor.tsx +22 -198
  61. package/src/components/DataSpaceGeneralEditor/DataSpaceHomeTab.tsx +235 -0
  62. package/src/components/DataSpaceGeneralEditor/DataSpaceSidebar.tsx +60 -0
  63. package/src/components/DataSpaceGeneralEditor/DataSpaceValidation.tsx +83 -0
  64. package/src/components/DataSpaceQueryAction.tsx +19 -12
  65. package/src/stores/DataSpaceEditorState.ts +18 -0
  66. package/src/stores/DataSpaceExecutableTemplateState.ts +152 -0
  67. package/src/stores/DataSpaceExecutionContextState.ts +17 -70
  68. package/src/stores/studio/DSL_DataSpace_GraphModifierHelper.ts +79 -6
  69. package/tsconfig.json +6 -2
  70. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.d.ts.map +0 -1
  71. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.js +0 -138
  72. package/lib/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.js.map +0 -1
  73. package/src/components/DataSpaceGeneralEditor/DataSpaceDefaultExecutionContextSection.tsx +0 -393
@@ -15,686 +15,827 @@
15
15
  */
16
16
 
17
17
  import {
18
- type ElementDragSource,
19
- type UMLEditorElementDropTarget,
20
18
  CORE_DND_TYPE,
19
+ type ElementDragSource,
21
20
  useEditorStore,
21
+ type UMLEditorElementDropTarget,
22
22
  } from '@finos/legend-application-studio';
23
23
  import { observer } from 'mobx-react-lite';
24
- import { useDrop } from 'react-dnd';
25
24
  import { DataSpaceEditorState } from '../stores/DataSpaceEditorState.js';
26
25
  import {
27
26
  BlankPanelPlaceholder,
28
27
  clsx,
29
- ContextMenu,
30
28
  CustomSelectorInput,
31
29
  Dialog,
32
- ErrorIcon,
33
- ExclamationTriangleIcon,
34
- InputWithInlineValidation,
35
30
  LongArrowRightIcon,
36
- MenuContent,
37
- MenuContentItem,
38
31
  ModalTitle,
39
32
  PanelContent,
33
+ PanelDivider,
40
34
  PanelDropZone,
35
+ PanelFormSection,
36
+ PanelFormTextField,
37
+ PanelFormValidatedTextField,
41
38
  PanelHeader,
42
- PanelHeaderActionItem,
43
- PanelHeaderActions,
44
39
  PlusIcon,
45
40
  PURE_MappingIcon,
46
41
  PURE_RuntimeIcon,
47
- ResizablePanel,
48
- ResizablePanelGroup,
49
- ResizablePanelSplitter,
50
- ResizablePanelSplitterLine,
42
+ TimesIcon,
43
+ WarningIcon,
51
44
  } from '@finos/legend-art';
52
45
  import {
46
+ DataProduct,
47
+ LakehouseRuntime,
53
48
  Mapping,
49
+ ModelAccessPointGroup,
54
50
  PackageableElementExplicitReference,
55
51
  PackageableRuntime,
56
- validate_PureExecutionMapping,
57
52
  } from '@finos/legend-graph';
58
53
  import {
54
+ buildElementOption,
55
+ type PackageableElementOption,
56
+ } from '@finos/legend-lego/graph-editor';
57
+ import {
58
+ DataSpaceExecutionContext,
59
+ DataSpaceMappingProvider,
60
+ type DataSpaceExecutionContext as DataSpaceExecutionContextType,
61
+ } from '@finos/legend-extension-dsl-data-space/graph';
62
+ import { useCallback, useState } from 'react';
63
+ import { useDrop } from 'react-dnd';
64
+ import type { DataSpaceExecutionContextState } from '../stores/DataSpaceExecutionContextState.js';
65
+ import {
66
+ dataSpace_setDefaultExecutionContext,
59
67
  dataSpace_setExecutionContextDefaultRuntime,
68
+ dataSpace_setExecutionContextDescription,
60
69
  dataSpace_setExecutionContextMapping,
70
+ dataSpace_setExecutionContextMappingProvider,
71
+ dataSpace_setExecutionContextName,
61
72
  dataSpace_setExecutionContextTitle,
62
- dataSpace_setExecutionContextDescription,
73
+ dataSpace_setMappingProviderKeys,
63
74
  } from '../stores/studio/DSL_DataSpace_GraphModifierHelper.js';
64
- import { guaranteeNonNullable } from '@finos/legend-shared';
65
- import { forwardRef, useCallback, useState } from 'react';
66
- import type { DataSpaceExecutionContextState } from '../stores/DataSpaceExecutionContextState.js';
67
- import type { DataSpaceExecutionContext } from '@finos/legend-extension-dsl-data-space/graph';
68
75
  import {
69
- buildElementOption,
70
- type PackageableElementOption,
71
- } from '@finos/legend-lego/graph-editor';
72
- import type { PropsValue } from 'react-select';
76
+ collectExecutionContextValidationIssues,
77
+ hasExecutionContextValidationError,
78
+ hasNoMappingSource,
79
+ InlineIssue,
80
+ } from './DataSpaceGeneralEditor/DataSpaceValidation.js';
81
+
82
+ type MappingSourceKind = 'mapping' | 'mappingProvider';
83
+
84
+ const getDataProductsWithModelAccessPointGroups = (
85
+ editorStore: ReturnType<typeof useEditorStore>,
86
+ ): DataProduct[] =>
87
+ editorStore.graphManagerState.graph.dataProducts.filter((dataProduct) =>
88
+ dataProduct.accessPointGroups.some(
89
+ (group) => group instanceof ModelAccessPointGroup,
90
+ ),
91
+ );
92
+
93
+ const getModelAccessPointGroupIds = (dataProduct: DataProduct): string[] =>
94
+ dataProduct.accessPointGroups
95
+ .filter((group) => group instanceof ModelAccessPointGroup)
96
+ .map((group) => group.id);
97
+
98
+ const getMappingSourceKind = (
99
+ executionContext: DataSpaceExecutionContextType,
100
+ ): MappingSourceKind | undefined => {
101
+ if (executionContext.mappingProvider) {
102
+ return 'mappingProvider';
103
+ }
104
+ if (executionContext.mapping) {
105
+ return 'mapping';
106
+ }
107
+ return undefined;
108
+ };
73
109
 
74
- const DataSpaceExecutionContextConfigurationEditor = observer(
110
+ export const NewExecutionContextModal = observer(
75
111
  (props: {
76
112
  executionContextState: DataSpaceExecutionContextState;
77
- executionContext: DataSpaceExecutionContext;
113
+ isReadOnly: boolean;
78
114
  }) => {
79
- const { executionContextState, executionContext } = props;
80
- const isReadOnly = executionContextState.dataSpaceEditorState.isReadOnly;
115
+ const { executionContextState, isReadOnly } = props;
116
+ const existingNames = executionContextState.executionContexts.map(
117
+ (context) => context.name,
118
+ );
119
+ const [name, setName] = useState('');
120
+ const [isNameValid, setIsNameValid] = useState(false);
121
+
122
+ const validateName = (val: string): string | undefined => {
123
+ if (!val) {
124
+ return `Execution context name can't be empty`;
125
+ }
126
+ if (existingNames.includes(val)) {
127
+ return `Execution context '${val}' already exists`;
128
+ }
129
+ return undefined;
130
+ };
131
+
132
+ const closeModal = (): void =>
133
+ executionContextState.setNewExecutionContextModal(false);
134
+
135
+ const create = (): void => {
136
+ if (isReadOnly || !isNameValid) {
137
+ return;
138
+ }
139
+ const context = new DataSpaceExecutionContext();
140
+ context.name = name;
141
+ executionContextState.addExecutionContext(context);
142
+ closeModal();
143
+ };
144
+
145
+ return (
146
+ <Dialog
147
+ open={executionContextState.newExecutionContextModal}
148
+ onClose={closeModal}
149
+ classes={{ container: 'search-modal__container' }}
150
+ slotProps={{
151
+ paper: { classes: { root: 'search-modal__inner-container' } },
152
+ }}
153
+ >
154
+ <form
155
+ onSubmit={(event): void => {
156
+ event.preventDefault();
157
+ create();
158
+ }}
159
+ className="modal search-modal modal--dark"
160
+ >
161
+ <ModalTitle title="New Execution Context" />
162
+ <PanelFormValidatedTextField
163
+ name="Name"
164
+ value={name}
165
+ update={(value): void => setName(value ?? '')}
166
+ validate={validateName}
167
+ onValidate={(issue): void => setIsNameValid(!issue)}
168
+ isReadOnly={isReadOnly}
169
+ placeholder="Enter a unique name"
170
+ fullWidth={true}
171
+ />
172
+ <PanelDivider />
173
+ <PanelFormSection>
174
+ <div className="search-modal__actions">
175
+ <button
176
+ type="button"
177
+ className="btn btn--dark"
178
+ onClick={closeModal}
179
+ >
180
+ Cancel
181
+ </button>
182
+ <button
183
+ type="submit"
184
+ className="btn btn--dark"
185
+ disabled={isReadOnly || !isNameValid}
186
+ >
187
+ Create
188
+ </button>
189
+ </div>
190
+ </PanelFormSection>
191
+ </form>
192
+ </Dialog>
193
+ );
194
+ },
195
+ );
196
+
197
+ const MappingSourceEditor = observer(
198
+ (props: {
199
+ executionContextState: DataSpaceExecutionContextState;
200
+ executionContext: DataSpaceExecutionContextType;
201
+ isReadOnly: boolean;
202
+ sourceKind: MappingSourceKind;
203
+ setSourceKind: (val: MappingSourceKind) => void;
204
+ }) => {
205
+ const {
206
+ executionContextState,
207
+ executionContext,
208
+ isReadOnly,
209
+ sourceKind,
210
+ setSourceKind,
211
+ } = props;
81
212
  const editorStore = executionContextState.editorStore;
82
213
  const applicationStore = editorStore.applicationStore;
214
+ const darkMode =
215
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
83
216
 
84
- // Mapping
85
- // TODO: this is not generic error handling, as there could be other problems
86
- // with mapping, we need to genericize this
87
- const isMappingEmpty = validate_PureExecutionMapping(
88
- executionContext.mapping.value,
89
- );
90
- const mapping = executionContext.mapping.value;
217
+ const mapping = executionContext.mapping?.value;
91
218
  const mappingOptions =
92
219
  editorStore.graphManagerState.usableMappings.map(buildElementOption);
93
- const noMappingLabel = (
94
- <div
95
- className="service-execution-editor__configuration__mapping-option--empty"
96
- title={isMappingEmpty?.messages.join('\n') ?? ''}
97
- >
98
- <div className="service-execution-editor__configuration__mapping-option--empty__label">
99
- (none)
100
- </div>
101
- <ErrorIcon />
102
- </div>
103
- );
104
- const selectedMappingOption = {
105
- value: mapping,
106
- label: isMappingEmpty ? noMappingLabel : mapping.path,
107
- } as PackageableElementOption<Mapping>;
108
- const onMappingSelectionChange = (
109
- val: PackageableElementOption<Mapping>,
220
+ const onMappingChange = (
221
+ option: PackageableElementOption<Mapping> | null,
222
+ ): void =>
223
+ dataSpace_setExecutionContextMapping(
224
+ executionContext,
225
+ option
226
+ ? PackageableElementExplicitReference.create(option.value)
227
+ : undefined,
228
+ );
229
+
230
+ const mappingProvider = executionContext.mappingProvider;
231
+ const providerElement = mappingProvider?.element.value;
232
+ const providerDataProduct =
233
+ providerElement instanceof DataProduct ? providerElement : undefined;
234
+ const dataProductOptions =
235
+ getDataProductsWithModelAccessPointGroups(editorStore).map(
236
+ buildElementOption,
237
+ );
238
+ const onDataProductChange = (
239
+ option: PackageableElementOption<DataProduct> | null,
110
240
  ): void => {
111
- if (val.value !== mapping) {
112
- dataSpace_setExecutionContextMapping(
241
+ if (!option) {
242
+ dataSpace_setExecutionContextMappingProvider(
113
243
  executionContext,
114
- PackageableElementExplicitReference.create(val.value),
244
+ undefined,
115
245
  );
116
- executionContextState.autoSelectRuntimeOnMappingChange(val.value);
246
+ return;
117
247
  }
248
+ const provider = new DataSpaceMappingProvider();
249
+ provider.element = PackageableElementExplicitReference.create(
250
+ option.value,
251
+ );
252
+ const groupIds = getModelAccessPointGroupIds(option.value);
253
+ provider.keys = groupIds.length === 1 && groupIds[0] ? [groupIds[0]] : [];
254
+ dataSpace_setExecutionContextMappingProvider(executionContext, provider);
118
255
  };
119
- const visitMapping = (): void =>
120
- editorStore.graphEditorMode.openElement(mapping);
121
-
122
- // Runtime
123
- const defaultRuntime = executionContext.defaultRuntime;
124
- // NOTE: for now, only include runtime associated with the mapping
125
- // TODO?: Should we bring the runtime compatibility check from query to here?
126
- const runtimes = editorStore.graphManagerState.graph.runtimes.filter((rt) =>
127
- rt.runtimeValue.mappings.map((m) => m.value).includes(mapping),
128
- );
129
- const runtimeOptions = runtimes.map((rt) => ({
130
- label: rt.path,
131
- value: PackageableElementExplicitReference.create(rt),
256
+
257
+ const accessPointGroupIds = providerDataProduct
258
+ ? getModelAccessPointGroupIds(providerDataProduct)
259
+ : [];
260
+ const accessPointGroupOptions = accessPointGroupIds.map((id) => ({
261
+ label: id,
262
+ value: id,
132
263
  }));
133
- const runtimePointerWarning = !runtimes.includes(defaultRuntime.value) // if the runtime does not belong to the chosen mapping
134
- ? `runtime is not associated with specified mapping '${mapping.path}'`
135
- : undefined;
136
- const selectedRuntimeOption = {
137
- value: defaultRuntime,
138
- label: (
139
- <div
140
- className="service-execution-editor__configuration__runtime-option__pointer"
141
- title={undefined}
142
- >
143
- <div
144
- className={clsx(
145
- 'service-execution-editor__configuration__runtime-option__pointer__label',
146
- {
147
- 'service-execution-editor__configuration__runtime-option__pointer__label--with-warning':
148
- Boolean(runtimePointerWarning),
149
- },
150
- )}
151
- >
152
- {defaultRuntime.value.path}
153
- </div>
154
- {runtimePointerWarning && (
155
- <div
156
- className="service-execution-editor__configuration__runtime-option__pointer__warning"
157
- title={runtimePointerWarning}
158
- >
159
- <ExclamationTriangleIcon />
160
- </div>
161
- )}
162
- </div>
163
- ),
164
- } as unknown as PropsValue<{
165
- label: string;
166
- value: PackageableElementExplicitReference<PackageableRuntime>;
167
- }>;
168
- const onRuntimeSelectionChange = (val: {
169
- label: string | React.ReactNode;
170
- value:
171
- | PackageableElementExplicitReference<PackageableRuntime>
172
- | undefined;
173
- }): void => {
174
- if (
175
- val.value?.value !== defaultRuntime.value &&
176
- val.value !== undefined
177
- ) {
178
- dataSpace_setExecutionContextDefaultRuntime(
179
- executionContext,
180
- val.value,
264
+ const accessPointGroupId = mappingProvider?.keys[0];
265
+ const onAccessPointGroupChange = (
266
+ option: { value: string } | null,
267
+ ): void => {
268
+ if (mappingProvider) {
269
+ dataSpace_setMappingProviderKeys(
270
+ mappingProvider,
271
+ option ? [option.value] : [],
181
272
  );
182
273
  }
183
274
  };
184
- const visitRuntime = (): void => {
185
- editorStore.graphEditorMode.openElement(defaultRuntime.value);
186
- };
187
275
 
188
- // DnD
189
- const handleMappingOrRuntimeDrop = useCallback(
276
+ const handleMappingDrop = useCallback(
190
277
  (item: UMLEditorElementDropTarget): void => {
191
278
  const element = item.data.packageableElement;
192
- if (!isReadOnly) {
193
- if (element instanceof Mapping) {
194
- dataSpace_setExecutionContextMapping(
195
- executionContext,
196
- PackageableElementExplicitReference.create(element),
197
- );
198
- executionContextState.autoSelectRuntimeOnMappingChange(element);
199
- } else if (
200
- element instanceof PackageableRuntime &&
201
- element.runtimeValue.mappings.map((m) => m.value).includes(mapping)
202
- ) {
203
- dataSpace_setExecutionContextDefaultRuntime(
204
- executionContext,
205
- PackageableElementExplicitReference.create(element),
206
- );
207
- }
279
+ if (!isReadOnly && element instanceof Mapping) {
280
+ dataSpace_setExecutionContextMapping(
281
+ executionContext,
282
+ PackageableElementExplicitReference.create(element),
283
+ );
284
+ setSourceKind('mapping');
208
285
  }
209
286
  },
210
- [isReadOnly, mapping, executionContextState, executionContext],
287
+ [isReadOnly, executionContext, setSourceKind],
211
288
  );
212
- const [{ isMappingOrRuntimeDragOver }, dropConnector] = useDrop<
289
+ const [{ isMappingDragOver }, mappingDropConnector] = useDrop<
213
290
  ElementDragSource,
214
291
  void,
215
- { isMappingOrRuntimeDragOver: boolean }
292
+ { isMappingDragOver: boolean }
216
293
  >(
217
294
  () => ({
218
- accept: [
219
- CORE_DND_TYPE.PROJECT_EXPLORER_MAPPING,
220
- CORE_DND_TYPE.PROJECT_EXPLORER_RUNTIME,
221
- ],
222
- drop: (item) => handleMappingOrRuntimeDrop(item),
295
+ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_MAPPING],
296
+ drop: (item) => handleMappingDrop(item),
223
297
  collect: (monitor) => ({
224
- isMappingOrRuntimeDragOver: monitor.isOver({ shallow: true }),
298
+ isMappingDragOver: monitor.isOver({ shallow: true }),
225
299
  }),
226
300
  }),
227
- [handleMappingOrRuntimeDrop],
301
+ [handleMappingDrop],
228
302
  );
229
303
 
230
- return (
231
- <PanelContent>
232
- <PanelDropZone
233
- dropTargetConnector={dropConnector}
234
- isDragOver={isMappingOrRuntimeDragOver && !isReadOnly}
235
- >
236
- <div className="service-execution-editor__configuration__items">
237
- <div className="service-execution-editor__configuration__item">
238
- <div className="btn--sm service-execution-editor__configuration__item__label">
239
- <PURE_MappingIcon />
240
- </div>
241
- <CustomSelectorInput
242
- className="panel__content__form__section__dropdown service-execution-editor__configuration__item__dropdown"
243
- disabled={isReadOnly}
244
- options={mappingOptions}
245
- onChange={onMappingSelectionChange}
246
- value={selectedMappingOption}
247
- darkMode={
248
- !applicationStore.layoutService
249
- .TEMPORARY__isLightColorThemeEnabled
250
- }
251
- hasError={Boolean(isMappingEmpty)}
252
- />
253
- <button
254
- className="btn--dark btn--sm service-execution-editor__configuration__item__btn"
255
- onClick={visitMapping}
256
- tabIndex={-1}
257
- title="See mapping"
258
- disabled={Boolean(isMappingEmpty)}
259
- >
260
- <LongArrowRightIcon />
261
- </button>
262
- </div>
263
- <div className="service-execution-editor__configuration__item">
264
- <div className="btn--sm service-execution-editor__configuration__item__label">
265
- <PURE_RuntimeIcon />
266
- </div>
267
- <CustomSelectorInput
268
- className="panel__content__form__section__dropdown service-execution-editor__configuration__item__dropdown"
269
- disabled={isReadOnly}
270
- options={runtimeOptions}
271
- onChange={onRuntimeSelectionChange}
272
- value={selectedRuntimeOption}
273
- darkMode={
274
- !applicationStore.layoutService
275
- .TEMPORARY__isLightColorThemeEnabled
276
- }
277
- />
278
- <button
279
- className="btn--sm btn--dark service-execution-editor__configuration__item__btn"
280
- onClick={visitRuntime}
281
- tabIndex={-1}
282
- title="See runtime"
283
- >
284
- <LongArrowRightIcon />
285
- </button>
286
- </div>
304
+ const providerElementIssue =
305
+ providerElement && !providerDataProduct
306
+ ? `'${providerElement.path}' is not a data product.`
307
+ : undefined;
308
+ const providerKeyIssue =
309
+ providerDataProduct &&
310
+ accessPointGroupId &&
311
+ !accessPointGroupIds.includes(accessPointGroupId)
312
+ ? `Access point group '${accessPointGroupId}' is not defined on '${providerDataProduct.path}'.`
313
+ : undefined;
287
314
 
288
- {/* Title input */}
289
- <div className="execution-context-editor__form-section">
290
- <div className="execution-context-editor__form-section__label">
291
- Title
292
- </div>
293
- <div className="execution-context-editor__form-section__content">
294
- <input
295
- className="execution-context-editor__form-section__content__input panel__content__form__section__input"
296
- spellCheck={false}
315
+ return (
316
+ <div className="dataSpace-editor__configuration__section">
317
+ <div className="dataSpace-editor__configuration__section__header">
318
+ <div className="dataSpace-editor__configuration__section__title">
319
+ Mapping Source
320
+ </div>
321
+ <div className="dataSpace-editor__configuration__section__hint">
322
+ Where this context gets its model from. Choose a mapping directly,
323
+ or indirectly via a mapping provider.
324
+ </div>
325
+ {hasNoMappingSource(executionContext) && (
326
+ <InlineIssue
327
+ issue={{
328
+ severity: 'error',
329
+ message: 'Specify either a mapping or a mapping provider.',
330
+ }}
331
+ />
332
+ )}
333
+ </div>
334
+ <div className="dataSpace-editor__tab-toggle">
335
+ <button
336
+ type="button"
337
+ className={clsx('dataSpace-editor__tab-toggle__tab', {
338
+ 'dataSpace-editor__tab-toggle__tab--active':
339
+ sourceKind === 'mapping',
340
+ })}
341
+ disabled={isReadOnly}
342
+ onClick={(): void => setSourceKind('mapping')}
343
+ >
344
+ Mapping
345
+ </button>
346
+ <button
347
+ type="button"
348
+ className={clsx('dataSpace-editor__tab-toggle__tab', {
349
+ 'dataSpace-editor__tab-toggle__tab--active':
350
+ sourceKind === 'mappingProvider',
351
+ })}
352
+ disabled={isReadOnly}
353
+ onClick={(): void => setSourceKind('mappingProvider')}
354
+ >
355
+ Mapping Provider
356
+ </button>
357
+ </div>
358
+ <div className="dataSpace-editor__configuration__section__body">
359
+ {sourceKind === 'mapping' && (
360
+ <PanelDropZone
361
+ dropTargetConnector={mappingDropConnector}
362
+ isDragOver={isMappingDragOver && !isReadOnly}
363
+ >
364
+ <div className="dataSpace-editor__configuration__row">
365
+ <div className="dataSpace-editor__configuration__row__icon">
366
+ <PURE_MappingIcon />
367
+ </div>
368
+ <CustomSelectorInput
369
+ className="dataSpace-editor__configuration__row__select"
297
370
  disabled={isReadOnly}
298
- value={executionContext.title ?? ''}
299
- onChange={(event): void =>
300
- dataSpace_setExecutionContextTitle(
301
- executionContext,
302
- event.target.value,
303
- )
304
- }
371
+ options={mappingOptions}
372
+ onChange={onMappingChange}
373
+ value={mapping ? buildElementOption(mapping) : null}
374
+ placeholder="Select a mapping..."
375
+ isClearable={true}
376
+ darkMode={darkMode}
377
+ hasError={!mapping}
305
378
  />
379
+ <button
380
+ className="btn--dark btn--sm dataSpace-editor__configuration__row__btn"
381
+ onClick={(): void => {
382
+ if (mapping) {
383
+ editorStore.graphEditorMode.openElement(mapping);
384
+ }
385
+ }}
386
+ disabled={!mapping}
387
+ title="See mapping"
388
+ tabIndex={-1}
389
+ >
390
+ <LongArrowRightIcon />
391
+ </button>
306
392
  </div>
307
- </div>
308
-
309
- {/* Description input */}
310
- <div className="execution-context-editor__form-section">
311
- <div className="execution-context-editor__form-section__label">
312
- Description
313
- </div>
314
- <div className="execution-context-editor__form-section__content">
315
- <textarea
316
- className="execution-context-editor__form-section__content__textarea panel__content__form__section__input"
317
- spellCheck={false}
393
+ </PanelDropZone>
394
+ )}
395
+ {sourceKind === 'mappingProvider' && (
396
+ <div className="dataSpace-editor__mapping-provider">
397
+ <div className="dataSpace-editor__configuration__row">
398
+ <CustomSelectorInput
399
+ className="dataSpace-editor__configuration__row__select"
318
400
  disabled={isReadOnly}
319
- value={executionContext.description ?? ''}
320
- onChange={(event): void =>
321
- dataSpace_setExecutionContextDescription(
322
- executionContext,
323
- event.target.value,
324
- )
401
+ options={dataProductOptions}
402
+ onChange={onDataProductChange}
403
+ value={
404
+ providerDataProduct
405
+ ? buildElementOption(providerDataProduct)
406
+ : null
325
407
  }
326
- rows={4}
408
+ placeholder="Select a mapping provider..."
409
+ isClearable={true}
410
+ darkMode={darkMode}
411
+ hasError={!providerDataProduct}
327
412
  />
413
+ <button
414
+ className="btn--dark btn--sm dataSpace-editor__configuration__row__btn"
415
+ onClick={(): void => {
416
+ if (providerDataProduct) {
417
+ editorStore.graphEditorMode.openElement(
418
+ providerDataProduct,
419
+ );
420
+ }
421
+ }}
422
+ disabled={!providerDataProduct}
423
+ title="See data product"
424
+ tabIndex={-1}
425
+ >
426
+ <LongArrowRightIcon />
427
+ </button>
328
428
  </div>
429
+ {providerDataProduct && (
430
+ <div className="dataSpace-editor__configuration__field">
431
+ <div className="dataSpace-editor__configuration__field__label">
432
+ Model Access Point Group
433
+ </div>
434
+ <CustomSelectorInput
435
+ disabled={isReadOnly}
436
+ options={accessPointGroupOptions}
437
+ onChange={onAccessPointGroupChange}
438
+ value={
439
+ accessPointGroupId
440
+ ? {
441
+ label: accessPointGroupId,
442
+ value: accessPointGroupId,
443
+ }
444
+ : null
445
+ }
446
+ placeholder="Select a model access point group..."
447
+ darkMode={darkMode}
448
+ hasError={!accessPointGroupId}
449
+ />
450
+ </div>
451
+ )}
452
+ {providerElementIssue && (
453
+ <InlineIssue
454
+ issue={{ severity: 'error', message: providerElementIssue }}
455
+ />
456
+ )}
457
+ {providerKeyIssue && (
458
+ <InlineIssue
459
+ issue={{ severity: 'error', message: providerKeyIssue }}
460
+ />
461
+ )}
329
462
  </div>
330
-
331
- {/* Test Data input removed as it's not used */}
332
- </div>
333
- </PanelDropZone>
334
- </PanelContent>
463
+ )}
464
+ </div>
465
+ </div>
335
466
  );
336
467
  },
337
468
  );
338
469
 
339
- export const NewExecutionContextModal = observer(
470
+ const DefaultRuntimeEditor = observer(
340
471
  (props: {
341
- executionState: DataSpaceExecutionContextState;
472
+ executionContextState: DataSpaceExecutionContextState;
473
+ executionContext: DataSpaceExecutionContextType;
342
474
  isReadOnly: boolean;
343
475
  }) => {
344
- const { executionState, isReadOnly } = props;
345
- const [name, setName] = useState('');
346
- const validationMessage =
347
- name === ''
348
- ? `Execution context name can't be empty`
349
- : executionState.executionContexts.find((e) => e.name === name)
350
- ? 'Execution context name already exists'
351
- : undefined;
476
+ const { executionContextState, executionContext, isReadOnly } = props;
477
+ const editorStore = executionContextState.editorStore;
478
+ const applicationStore = editorStore.applicationStore;
479
+ const darkMode =
480
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
352
481
 
353
- const closeModal = (): void =>
354
- executionState.setNewExecutionContextModal(false);
355
- const onChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
356
- setName(event.target.value);
357
- };
358
- return (
359
- <Dialog
360
- open={executionState.newExecutionContextModal}
361
- onClose={closeModal}
362
- classes={{ container: 'search-modal__container' }}
363
- slotProps={{
364
- paper: {
365
- classes: { root: 'search-modal__inner-container' },
366
- },
367
- }}
368
- >
369
- <form
370
- onSubmit={(event) => {
371
- event.preventDefault();
372
- executionState.addExecutionContext(name);
373
- setName('');
374
- closeModal();
375
- }}
376
- className="modal modal--dark search-modal"
377
- >
378
- <ModalTitle title="New Execution Context" />
379
- <div className="service-execution-editor__change__modal">
380
- <InputWithInlineValidation
381
- className="service-execution-editor__input input-group__input"
382
- spellCheck={false}
383
- value={name}
384
- onChange={onChange}
385
- placeholder="Key execution name"
386
- error={validationMessage}
387
- />
388
- </div>
389
- <div className="search-modal__actions">
390
- <button
391
- className="btn btn--dark"
392
- disabled={isReadOnly || Boolean(validationMessage)}
393
- >
394
- Add
395
- </button>
396
- </div>
397
- </form>
398
- </Dialog>
482
+ const mapping = executionContext.mapping?.value;
483
+ const defaultRuntime = executionContext.defaultRuntime?.value;
484
+ const compatibleRuntimes =
485
+ editorStore.graphManagerState.graph.runtimes.filter(
486
+ (runtime) =>
487
+ !mapping ||
488
+ runtime.runtimeValue.mappings.map((m) => m.value).includes(mapping),
489
+ );
490
+ const runtimeOptions = (
491
+ compatibleRuntimes.length
492
+ ? compatibleRuntimes
493
+ : editorStore.graphManagerState.graph.runtimes
494
+ ).map(buildElementOption);
495
+
496
+ const runtimeWarning =
497
+ defaultRuntime &&
498
+ mapping &&
499
+ !(defaultRuntime.runtimeValue instanceof LakehouseRuntime) &&
500
+ !compatibleRuntimes.includes(defaultRuntime)
501
+ ? `Runtime is not associated with mapping '${mapping.path}'.`
502
+ : undefined;
503
+
504
+ const onRuntimeChange = (
505
+ option: PackageableElementOption<PackageableRuntime> | null,
506
+ ): void =>
507
+ dataSpace_setExecutionContextDefaultRuntime(
508
+ executionContext,
509
+ option
510
+ ? PackageableElementExplicitReference.create(option.value)
511
+ : undefined,
512
+ );
513
+
514
+ const handleRuntimeDrop = useCallback(
515
+ (item: UMLEditorElementDropTarget): void => {
516
+ const element = item.data.packageableElement;
517
+ if (!isReadOnly && element instanceof PackageableRuntime) {
518
+ dataSpace_setExecutionContextDefaultRuntime(
519
+ executionContext,
520
+ PackageableElementExplicitReference.create(element),
521
+ );
522
+ }
523
+ },
524
+ [isReadOnly, executionContext],
525
+ );
526
+ const [{ isRuntimeDragOver }, runtimeDropConnector] = useDrop<
527
+ ElementDragSource,
528
+ void,
529
+ { isRuntimeDragOver: boolean }
530
+ >(
531
+ () => ({
532
+ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_RUNTIME],
533
+ drop: (item) => handleRuntimeDrop(item),
534
+ collect: (monitor) => ({
535
+ isRuntimeDragOver: monitor.isOver({ shallow: true }),
536
+ }),
537
+ }),
538
+ [handleRuntimeDrop],
399
539
  );
400
- },
401
- );
402
540
 
403
- export const RenameModal = observer(
404
- (props: {
405
- val: string;
406
- isReadOnly: boolean;
407
- setValue: (val: string) => void;
408
- showModal: boolean;
409
- closeModal: () => void;
410
- executionContext: DataSpaceExecutionContext;
411
- }) => {
412
- const {
413
- val,
414
- isReadOnly,
415
- showModal,
416
- closeModal,
417
- setValue,
418
- executionContext,
419
- } = props;
420
- const [inputValue, setInputValue] = useState(val);
421
- const changeValue: React.ChangeEventHandler<HTMLInputElement> = (event) => {
422
- setInputValue(event.target.value);
423
- };
424
541
  return (
425
- <Dialog
426
- open={showModal}
427
- onClose={closeModal}
428
- classes={{ container: 'search-modal__container' }}
429
- slotProps={{
430
- paper: {
431
- classes: { root: 'search-modal__inner-container' },
432
- },
433
- }}
434
- >
435
- <form
436
- onSubmit={(event) => {
437
- event.preventDefault();
438
- setValue(inputValue);
439
- closeModal();
440
- }}
441
- className="modal modal--dark search-modal"
442
- >
443
- <ModalTitle title="Edit Execution Context" />
444
- <div className="execution-context-editor__form-section">
445
- <div className="execution-context-editor__form-section__label">
446
- Key
447
- </div>
448
- <div className="execution-context-editor__form-section__content">
449
- <input
450
- className="execution-context-editor__form-section__content__input panel__content__form__section__input"
451
- spellCheck={false}
452
- disabled={isReadOnly}
453
- value={inputValue}
454
- onChange={changeValue}
455
- />
456
- </div>
457
- </div>
458
-
459
- {/* Title input */}
460
- <div className="execution-context-editor__form-section">
461
- <div className="execution-context-editor__form-section__label">
462
- Title
463
- </div>
464
- <div className="execution-context-editor__form-section__content">
465
- <input
466
- className="execution-context-editor__form-section__content__input panel__content__form__section__input"
467
- spellCheck={false}
468
- disabled={isReadOnly}
469
- value={executionContext.title ?? ''}
470
- onChange={(event): void =>
471
- dataSpace_setExecutionContextTitle(
472
- executionContext,
473
- event.target.value,
474
- )
475
- }
476
- />
477
- </div>
542
+ <div className="dataSpace-editor__configuration__section">
543
+ <div className="dataSpace-editor__configuration__section__header">
544
+ <div className="dataSpace-editor__configuration__section__title">
545
+ Default Runtime
478
546
  </div>
479
-
480
- {/* Description input */}
481
- <div className="execution-context-editor__form-section">
482
- <div className="execution-context-editor__form-section__label">
483
- Description
484
- </div>
485
- <div className="execution-context-editor__form-section__content">
486
- <textarea
487
- className="execution-context-editor__form-section__content__textarea panel__content__form__section__input"
488
- spellCheck={false}
547
+ </div>
548
+ <div className="dataSpace-editor__configuration__section__body">
549
+ <PanelDropZone
550
+ dropTargetConnector={runtimeDropConnector}
551
+ isDragOver={isRuntimeDragOver && !isReadOnly}
552
+ >
553
+ <div className="dataSpace-editor__configuration__row">
554
+ <div className="dataSpace-editor__configuration__row__icon">
555
+ <PURE_RuntimeIcon />
556
+ </div>
557
+ <CustomSelectorInput
558
+ className="dataSpace-editor__configuration__row__select"
489
559
  disabled={isReadOnly}
490
- value={executionContext.description ?? ''}
491
- onChange={(event): void =>
492
- dataSpace_setExecutionContextDescription(
493
- executionContext,
494
- event.target.value,
495
- )
560
+ options={runtimeOptions}
561
+ onChange={onRuntimeChange}
562
+ value={
563
+ defaultRuntime ? buildElementOption(defaultRuntime) : null
496
564
  }
497
- rows={4}
565
+ placeholder="Select a default runtime (optional)..."
566
+ isClearable={true}
567
+ darkMode={darkMode}
498
568
  />
569
+ <button
570
+ className="btn--dark btn--sm dataSpace-editor__configuration__row__btn"
571
+ onClick={(): void => {
572
+ if (defaultRuntime) {
573
+ editorStore.graphEditorMode.openElement(defaultRuntime);
574
+ }
575
+ }}
576
+ disabled={!defaultRuntime}
577
+ title="See runtime"
578
+ tabIndex={-1}
579
+ >
580
+ <LongArrowRightIcon />
581
+ </button>
499
582
  </div>
500
- </div>
501
-
502
- <div className="search-modal__actions">
503
- <button className="btn btn--dark" disabled={isReadOnly}>
504
- Done
505
- </button>
506
- </div>
507
- </form>
508
- </Dialog>
583
+ </PanelDropZone>
584
+ {runtimeWarning && (
585
+ <InlineIssue
586
+ issue={{ severity: 'warning', message: runtimeWarning }}
587
+ />
588
+ )}
589
+ </div>
590
+ </div>
509
591
  );
510
592
  },
511
593
  );
512
594
 
513
- const ExecutionContextMenu = observer(
514
- forwardRef<
515
- HTMLDivElement,
516
- {
517
- dataSpaceExecutionContextState: DataSpaceExecutionContextState;
518
- dataSpaceExecutionContext: DataSpaceExecutionContext;
519
- isReadOnly: boolean;
595
+ const ExecutionContextConfigurationEditor = observer(
596
+ (props: {
597
+ executionContextState: DataSpaceExecutionContextState;
598
+ executionContext: DataSpaceExecutionContextType;
599
+ isReadOnly: boolean;
600
+ }) => {
601
+ const { executionContextState, executionContext, isReadOnly } = props;
602
+ const dataSpace = executionContextState.dataSpace;
603
+
604
+ const [sourceKindOverride, setSourceKindOverride] = useState<
605
+ MappingSourceKind | undefined
606
+ >(undefined);
607
+ const [trackedContext, setTrackedContext] = useState(executionContext);
608
+ if (trackedContext !== executionContext) {
609
+ setTrackedContext(executionContext);
610
+ setSourceKindOverride(undefined);
520
611
  }
521
- >(function TestContainerContextMenu(props, ref) {
522
- const { dataSpaceExecutionContextState, dataSpaceExecutionContext } = props;
523
- const rename = (): void => {
524
- dataSpaceExecutionContextState.setExecutionContextToRename(
525
- dataSpaceExecutionContext,
526
- );
612
+ const sourceKind =
613
+ getMappingSourceKind(executionContext) ?? sourceKindOverride ?? 'mapping';
614
+
615
+ const otherNames = executionContextState.executionContexts
616
+ .filter((context) => context !== executionContext)
617
+ .map((context) => context.name);
618
+ const validateName = (val: string): string | undefined => {
619
+ if (!val) {
620
+ return `Execution context name can't be empty`;
621
+ }
622
+ if (otherNames.includes(val)) {
623
+ return `Execution context '${val}' already exists`;
624
+ }
625
+ return undefined;
527
626
  };
528
- const remove = (): void => {
529
- dataSpaceExecutionContextState.removeExecutionContext(
530
- dataSpaceExecutionContext,
627
+
628
+ const isDefault = dataSpace.defaultExecutionContext === executionContext;
629
+ const toggleDefault = (): void =>
630
+ dataSpace_setDefaultExecutionContext(
631
+ dataSpace,
632
+ isDefault ? undefined : executionContext,
531
633
  );
532
- };
533
- const add = (): void => {
534
- dataSpaceExecutionContextState.setNewExecutionContextModal(true);
535
- };
634
+
635
+ const issues = collectExecutionContextValidationIssues(executionContext);
636
+ const nameError = validateName(executionContext.name);
637
+
536
638
  return (
537
- <MenuContent ref={ref}>
538
- <MenuContentItem onClick={rename}>Rename</MenuContentItem>
539
- <MenuContentItem onClick={remove}>Delete</MenuContentItem>
540
- <MenuContentItem onClick={add}>Create a new key</MenuContentItem>
541
- </MenuContent>
639
+ <div className="dataSpace-editor__configuration">
640
+ <PanelFormTextField
641
+ name="Name"
642
+ value={executionContext.name}
643
+ update={(value): void =>
644
+ dataSpace_setExecutionContextName(executionContext, value ?? '')
645
+ }
646
+ isReadOnly={isReadOnly}
647
+ placeholder="Enter a unique name"
648
+ fullWidth={true}
649
+ hasError={Boolean(nameError)}
650
+ errorMessage={nameError}
651
+ errorClassName="dataSpace-editor__field--error"
652
+ />
653
+ <PanelFormTextField
654
+ name="Title"
655
+ value={executionContext.title ?? ''}
656
+ update={(value): void =>
657
+ dataSpace_setExecutionContextTitle(executionContext, value)
658
+ }
659
+ isReadOnly={isReadOnly}
660
+ placeholder="Enter title"
661
+ prompt="Provide a title for this execution context"
662
+ fullWidth={true}
663
+ />
664
+ <PanelFormSection>
665
+ <div className="panel__content__form__section__header__label">
666
+ Description
667
+ </div>
668
+ <div className="panel__content__form__section__header__prompt">
669
+ Provide a description for this execution context.
670
+ </div>
671
+ <textarea
672
+ className="panel__content__form__section__textarea"
673
+ spellCheck={false}
674
+ disabled={isReadOnly}
675
+ value={executionContext.description ?? ''}
676
+ onChange={(event): void =>
677
+ dataSpace_setExecutionContextDescription(
678
+ executionContext,
679
+ event.target.value || undefined,
680
+ )
681
+ }
682
+ rows={3}
683
+ placeholder="Enter description"
684
+ />
685
+ </PanelFormSection>
686
+ <MappingSourceEditor
687
+ executionContextState={executionContextState}
688
+ executionContext={executionContext}
689
+ isReadOnly={isReadOnly}
690
+ sourceKind={sourceKind}
691
+ setSourceKind={setSourceKindOverride}
692
+ />
693
+ <DefaultRuntimeEditor
694
+ executionContextState={executionContextState}
695
+ executionContext={executionContext}
696
+ isReadOnly={isReadOnly}
697
+ />
698
+ <PanelFormSection>
699
+ <button
700
+ className="btn btn--dark"
701
+ onClick={toggleDefault}
702
+ disabled={isReadOnly}
703
+ tabIndex={-1}
704
+ >
705
+ {isDefault
706
+ ? 'Unset as default execution context'
707
+ : 'Set as default execution context'}
708
+ </button>
709
+ </PanelFormSection>
710
+ {issues.length > 0 && (
711
+ <div className="dataSpace-editor__configuration__issues">
712
+ {issues.map((issue) => (
713
+ <InlineIssue key={issue.message} issue={issue} />
714
+ ))}
715
+ </div>
716
+ )}
717
+ </div>
542
718
  );
543
- }),
719
+ },
544
720
  );
545
721
 
546
- const ExecutionContextItem = observer(
722
+ const ExecutionContextTab = observer(
547
723
  (props: {
548
- dataSpaceExecutionContextState: DataSpaceExecutionContextState;
549
- dataSpaceExecutionContext: DataSpaceExecutionContext;
724
+ executionContextState: DataSpaceExecutionContextState;
725
+ executionContext: DataSpaceExecutionContextType;
550
726
  isReadOnly: boolean;
551
727
  }) => {
552
- const {
553
- dataSpaceExecutionContextState,
554
- dataSpaceExecutionContext,
555
- isReadOnly,
556
- } = props;
557
- const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] =
558
- useState(false);
728
+ const { executionContextState, executionContext, isReadOnly } = props;
729
+ const dataSpace = executionContextState.dataSpace;
559
730
  const isActive =
560
- dataSpaceExecutionContextState.selectedExecutionContext ===
561
- dataSpaceExecutionContext;
731
+ executionContextState.selectedExecutionContext === executionContext;
732
+ const isDefault = dataSpace.defaultExecutionContext === executionContext;
733
+ const hasError = hasExecutionContextValidationError(executionContext);
734
+
735
+ const select = (): void =>
736
+ executionContextState.setSelectedExecutionContext(executionContext);
737
+ const remove = (event: React.MouseEvent): void => {
738
+ event.stopPropagation();
739
+ executionContextState.removeExecutionContext(executionContext);
740
+ };
562
741
 
563
- const openKeyedExecution = (): void =>
564
- dataSpaceExecutionContextState.setSelectedExecutionContext(
565
- dataSpaceExecutionContext,
566
- );
567
- const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
568
- const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false);
569
742
  return (
570
- <ContextMenu
571
- className={clsx(
572
- 'service-multi-execution-editor__item',
573
- {
574
- 'service-multi-execution-editor__item--selected-from-context-menu':
575
- !isActive && isSelectedFromContextMenu,
576
- },
577
- { 'service-multi-execution-editor__item--active': isActive },
743
+ <div
744
+ onClick={select}
745
+ className={clsx('service-editor__tab', 'dataSpace-editor__tab', {
746
+ 'service-editor__tab--active': isActive,
747
+ })}
748
+ role="tab"
749
+ >
750
+ <span className="dataSpace-editor__tab__label">
751
+ {executionContext.name}
752
+ </span>
753
+ {isDefault && (
754
+ <span
755
+ className="dataSpace-editor__tab__badge"
756
+ title="Default execution context"
757
+ >
758
+ default
759
+ </span>
578
760
  )}
579
- disabled={isReadOnly}
580
- content={
581
- <ExecutionContextMenu
582
- dataSpaceExecutionContextState={dataSpaceExecutionContextState}
583
- dataSpaceExecutionContext={dataSpaceExecutionContext}
584
- isReadOnly={isReadOnly}
761
+ {hasError && (
762
+ <WarningIcon
763
+ className="dataSpace-editor__tab__error-icon"
764
+ title="This execution context is incomplete"
585
765
  />
586
- }
587
- menuProps={{ elevation: 7 }}
588
- onOpen={onContextMenuOpen}
589
- onClose={onContextMenuClose}
590
- >
591
- <button
592
- className={clsx('service-multi-execution-editor__item__label')}
593
- onClick={openKeyedExecution}
594
- tabIndex={-1}
595
- >
596
- {dataSpaceExecutionContext.name}
597
- </button>
598
- </ContextMenu>
766
+ )}
767
+ {!isReadOnly && (
768
+ <button
769
+ className="dataSpace-editor__tab__close-btn"
770
+ onClick={remove}
771
+ tabIndex={-1}
772
+ title="Delete execution context"
773
+ >
774
+ <TimesIcon />
775
+ </button>
776
+ )}
777
+ </div>
599
778
  );
600
779
  },
601
780
  );
602
781
 
603
782
  export const DataSpaceExecutionContextEditor = observer(() => {
604
783
  const editorStore = useEditorStore();
605
-
606
784
  const dataSpaceState =
607
785
  editorStore.tabManagerState.getCurrentEditorState(DataSpaceEditorState);
608
786
  const executionContextState = dataSpaceState.executionContextState;
787
+ const isReadOnly = dataSpaceState.isReadOnly;
788
+ const executionContexts = executionContextState.executionContexts;
609
789
 
610
- const addExecutionKey = (): void => {
790
+ const addExecutionContext = (): void =>
611
791
  executionContextState.setNewExecutionContextModal(true);
612
- };
613
792
 
614
793
  return (
615
- <div className="service-execution-editor__execution">
616
- <ResizablePanelGroup orientation="vertical">
617
- <ResizablePanel size={300} minSize={200}>
618
- <div className="service-multi-execution-editor__panel">
619
- <PanelHeader>
620
- <div className="panel__header__title">
621
- <div className="panel__header__title__content">
622
- Execution Contexts
623
- </div>
624
- </div>
625
- <PanelHeaderActions>
626
- <PanelHeaderActionItem
627
- disabled={dataSpaceState.isReadOnly}
628
- onClick={addExecutionKey}
629
- title="Add an execution context"
630
- >
631
- <PlusIcon />
632
- </PanelHeaderActionItem>
633
- </PanelHeaderActions>
634
- </PanelHeader>
635
-
636
- {executionContextState.executionContexts.map((executionContext) => (
637
- <ExecutionContextItem
638
- key={executionContext.name}
639
- dataSpaceExecutionContextState={executionContextState}
640
- dataSpaceExecutionContext={executionContext}
641
- isReadOnly={dataSpaceState.isReadOnly}
642
- />
643
- ))}
644
- {!executionContextState.executionContexts.length && (
645
- <BlankPanelPlaceholder
646
- text="Add an execution context"
647
- onClick={addExecutionKey}
648
- clickActionType="add"
649
- tooltipText="Click to add an execution context"
650
- />
651
- )}
652
- </div>
653
- {executionContextState.newExecutionContextModal && (
654
- <NewExecutionContextModal
655
- executionState={executionContextState}
656
- isReadOnly={dataSpaceState.isReadOnly}
657
- />
658
- )}
659
- {executionContextState.executionContextToRename && (
660
- <RenameModal
661
- val={executionContextState.executionContextToRename.name}
662
- isReadOnly={dataSpaceState.isReadOnly}
663
- showModal={true}
664
- closeModal={(): void =>
665
- executionContextState.setExecutionContextToRename(undefined)
666
- }
667
- setValue={(val: string): void =>
668
- executionContextState.renameExecutionContext(
669
- guaranteeNonNullable(
670
- executionContextState.executionContextToRename,
671
- ),
672
- val,
673
- )
674
- }
675
- executionContext={executionContextState.executionContextToRename}
676
- />
677
- )}
678
- </ResizablePanel>
679
- <ResizablePanelSplitter>
680
- <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
681
- </ResizablePanelSplitter>
682
- <ResizablePanel minSize={56}>
683
- {executionContextState.selectedExecutionContext ? (
684
- <DataSpaceExecutionContextConfigurationEditor
794
+ <div className="panel dataSpace-editor__tab-panel">
795
+ <PanelHeader>
796
+ <div className="uml-element-editor__tabs">
797
+ {executionContexts.map((executionContext) => (
798
+ <ExecutionContextTab
799
+ key={executionContext.name}
685
800
  executionContextState={executionContextState}
686
- executionContext={executionContextState.selectedExecutionContext}
687
- />
688
- ) : (
689
- <BlankPanelPlaceholder
690
- text="Add an execution context"
691
- onClick={addExecutionKey}
692
- clickActionType="add"
693
- tooltipText="Click to add an execution context"
801
+ executionContext={executionContext}
802
+ isReadOnly={isReadOnly}
694
803
  />
695
- )}
696
- </ResizablePanel>
697
- </ResizablePanelGroup>
804
+ ))}
805
+ <button
806
+ className="panel__header__action"
807
+ disabled={isReadOnly}
808
+ onClick={addExecutionContext}
809
+ title="Add an execution context"
810
+ tabIndex={-1}
811
+ >
812
+ <PlusIcon />
813
+ </button>
814
+ </div>
815
+ </PanelHeader>
816
+ <PanelContent>
817
+ {executionContextState.selectedExecutionContext ? (
818
+ <ExecutionContextConfigurationEditor
819
+ executionContextState={executionContextState}
820
+ executionContext={executionContextState.selectedExecutionContext}
821
+ isReadOnly={isReadOnly}
822
+ />
823
+ ) : (
824
+ <BlankPanelPlaceholder
825
+ text="Add an execution context"
826
+ onClick={addExecutionContext}
827
+ clickActionType="add"
828
+ tooltipText="Click to add an execution context"
829
+ disabled={isReadOnly}
830
+ />
831
+ )}
832
+ </PanelContent>
833
+ {executionContextState.newExecutionContextModal && (
834
+ <NewExecutionContextModal
835
+ executionContextState={executionContextState}
836
+ isReadOnly={isReadOnly}
837
+ />
838
+ )}
698
839
  </div>
699
840
  );
700
841
  });