@finos/legend-application-studio 28.19.13 → 28.19.15
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/__lib__/LegendStudioTesting.d.ts +2 -1
- package/lib/__lib__/LegendStudioTesting.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioTesting.js +1 -0
- package/lib/__lib__/LegendStudioTesting.js.map +1 -1
- package/lib/application/LegendStudioApplicationConfig.d.ts +13 -0
- package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendStudioApplicationConfig.js +22 -0
- package/lib/application/LegendStudioApplicationConfig.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.d.ts +8 -2
- package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js +379 -253
- package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js.map +1 -1
- package/lib/components/editor/side-bar/CreateNewElementModal.d.ts.map +1 -1
- package/lib/components/editor/side-bar/CreateNewElementModal.js +1 -2
- package/lib/components/editor/side-bar/CreateNewElementModal.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/NewElementState.d.ts.map +1 -1
- package/lib/stores/editor/NewElementState.js +6 -3
- package/lib/stores/editor/NewElementState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +20 -9
- 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 +72 -24
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +4 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +10 -1
- package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
- package/package.json +6 -6
- package/src/__lib__/LegendStudioTesting.ts +1 -0
- package/src/application/LegendStudioApplicationConfig.ts +33 -0
- package/src/components/editor/editor-group/dataProduct/DataPoductEditor.tsx +971 -745
- package/src/components/editor/side-bar/CreateNewElementModal.tsx +0 -15
- package/src/stores/editor/NewElementState.ts +8 -1
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +122 -26
- package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +28 -2
@@ -18,6 +18,8 @@ import { observer } from 'mobx-react-lite';
|
|
18
18
|
import { useEditorStore } from '../../EditorStoreProvider.js';
|
19
19
|
import {
|
20
20
|
type AccessPointGroupState,
|
21
|
+
type AccessPointState,
|
22
|
+
DATA_PRODUCT_TAB,
|
21
23
|
DataProductEditorState,
|
22
24
|
generateUrlToDeployOnOpen,
|
23
25
|
LakehouseAccessPointState,
|
@@ -29,10 +31,6 @@ import {
|
|
29
31
|
PanelHeader,
|
30
32
|
PanelHeaderActions,
|
31
33
|
Dialog,
|
32
|
-
PanelDivider,
|
33
|
-
InputWithInlineValidation,
|
34
|
-
useResizeDetector,
|
35
|
-
AccessPointIcon,
|
36
34
|
TimesIcon,
|
37
35
|
PlusIcon,
|
38
36
|
PanelHeaderActionItem,
|
@@ -52,12 +50,26 @@ import {
|
|
52
50
|
CaretDownIcon,
|
53
51
|
WarningIcon,
|
54
52
|
PanelFormSection,
|
53
|
+
useDragPreviewLayer,
|
54
|
+
DragPreviewLayer,
|
55
|
+
PanelDnDEntry,
|
56
|
+
PanelEntryDragHandle,
|
57
|
+
HomeIcon,
|
58
|
+
QuestionCircleIcon,
|
59
|
+
ErrorWarnIcon,
|
60
|
+
GroupWorkIcon,
|
61
|
+
CustomSelectorInput,
|
62
|
+
Switch,
|
63
|
+
BuildingIcon,
|
64
|
+
Tooltip,
|
65
|
+
InfoCircleIcon,
|
55
66
|
} from '@finos/legend-art';
|
56
67
|
import React, {
|
57
68
|
useRef,
|
58
69
|
useState,
|
59
70
|
useEffect,
|
60
71
|
type ChangeEventHandler,
|
72
|
+
useCallback,
|
61
73
|
} from 'react';
|
62
74
|
import { filterByType } from '@finos/legend-shared';
|
63
75
|
import { InlineLambdaEditor } from '@finos/legend-query-builder';
|
@@ -65,7 +77,13 @@ import { action, flowResult } from 'mobx';
|
|
65
77
|
import { useAuth } from 'react-oidc-context';
|
66
78
|
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
|
67
79
|
import { CodeEditor } from '@finos/legend-lego/code-editor';
|
68
|
-
import {
|
80
|
+
import {
|
81
|
+
LakehouseTargetEnv,
|
82
|
+
Email,
|
83
|
+
type DataProduct,
|
84
|
+
type LakehouseAccessPoint,
|
85
|
+
StereotypeExplicitReference,
|
86
|
+
} from '@finos/legend-graph';
|
69
87
|
import {
|
70
88
|
accessPointGroup_setDescription,
|
71
89
|
accessPointGroup_setName,
|
@@ -78,6 +96,7 @@ import {
|
|
78
96
|
supportInfo_setSupportUrl,
|
79
97
|
supportInfo_addEmail,
|
80
98
|
supportInfo_deleteEmail,
|
99
|
+
accessPoint_setClassification,
|
81
100
|
} from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
|
82
101
|
import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
|
83
102
|
import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
|
@@ -86,6 +105,11 @@ import {
|
|
86
105
|
ActionAlertType,
|
87
106
|
useApplicationNavigationContext,
|
88
107
|
} from '@finos/legend-application';
|
108
|
+
import { useDrag, useDrop } from 'react-dnd';
|
109
|
+
import {
|
110
|
+
annotatedElement_addStereotype,
|
111
|
+
annotatedElement_deleteStereotype,
|
112
|
+
} from '../../../../stores/graph-modifier/DomainGraphModifierHelper.js';
|
89
113
|
|
90
114
|
export enum AP_GROUP_MODAL_ERRORS {
|
91
115
|
GROUP_NAME_EMPTY = 'Group Name is empty',
|
@@ -97,332 +121,20 @@ export enum AP_GROUP_MODAL_ERRORS {
|
|
97
121
|
}
|
98
122
|
|
99
123
|
export const AP_EMPTY_DESC_WARNING =
|
100
|
-
'
|
124
|
+
'Click here to describe the data this access point produces';
|
101
125
|
|
102
|
-
const
|
103
|
-
|
104
|
-
const { dataProductEditorState: dataProductEditorState } = props;
|
105
|
-
const accessPointInputRef = useRef<HTMLInputElement>(null);
|
106
|
-
const [id, setId] = useState<string | undefined>(undefined);
|
107
|
-
const handleIdChange: React.ChangeEventHandler<HTMLInputElement> = (
|
108
|
-
event,
|
109
|
-
) => setId(event.target.value);
|
110
|
-
const [description, setDescription] = useState<string | undefined>(
|
111
|
-
undefined,
|
112
|
-
);
|
113
|
-
const handleDescriptionChange: React.ChangeEventHandler<
|
114
|
-
HTMLInputElement
|
115
|
-
> = (event) => setDescription(event.target.value);
|
116
|
-
const handleClose = () => {
|
117
|
-
dataProductEditorState.setAccessPointModal(false);
|
118
|
-
};
|
119
|
-
const handleSubmit = () => {
|
120
|
-
if (id) {
|
121
|
-
const accessPointGroup =
|
122
|
-
dataProductEditorState.editingGroupState ?? 'default';
|
123
|
-
dataProductEditorState.addAccessPoint(
|
124
|
-
id,
|
125
|
-
description,
|
126
|
-
accessPointGroup,
|
127
|
-
);
|
128
|
-
handleClose();
|
129
|
-
}
|
130
|
-
};
|
131
|
-
const handleEnter = (): void => {
|
132
|
-
accessPointInputRef.current?.focus();
|
133
|
-
};
|
134
|
-
const disableCreateButton =
|
135
|
-
id === '' ||
|
136
|
-
id === undefined ||
|
137
|
-
description === '' ||
|
138
|
-
description === undefined ||
|
139
|
-
dataProductEditorState.accessPoints.map((e) => e.id).includes(id);
|
140
|
-
const nameErrors =
|
141
|
-
id === ''
|
142
|
-
? AP_GROUP_MODAL_ERRORS.AP_NAME_EMPTY
|
143
|
-
: dataProductEditorState.accessPoints
|
144
|
-
.map((e) => e.id)
|
145
|
-
.includes(id ?? '')
|
146
|
-
? AP_GROUP_MODAL_ERRORS.AP_NAME_EXISTS
|
147
|
-
: undefined;
|
148
|
-
|
149
|
-
const descriptionErrors =
|
150
|
-
description === ''
|
151
|
-
? AP_GROUP_MODAL_ERRORS.AP_DESCRIPTION_EMPTY
|
152
|
-
: undefined;
|
153
|
-
return (
|
154
|
-
<Dialog
|
155
|
-
open={true}
|
156
|
-
onClose={handleClose}
|
157
|
-
TransitionProps={{
|
158
|
-
onEnter: handleEnter,
|
159
|
-
}}
|
160
|
-
classes={{
|
161
|
-
container: 'search-modal__container',
|
162
|
-
}}
|
163
|
-
PaperProps={{
|
164
|
-
classes: {
|
165
|
-
root: 'search-modal__inner-container',
|
166
|
-
},
|
167
|
-
}}
|
168
|
-
>
|
169
|
-
<form
|
170
|
-
onSubmit={(event) => {
|
171
|
-
event.preventDefault();
|
172
|
-
handleSubmit();
|
173
|
-
}}
|
174
|
-
className={clsx('modal search-modal', {
|
175
|
-
'modal--dark': true,
|
176
|
-
})}
|
177
|
-
style={{
|
178
|
-
display: 'flex',
|
179
|
-
flexDirection: 'column',
|
180
|
-
gap: '1rem',
|
181
|
-
}}
|
182
|
-
>
|
183
|
-
<div className="modal__title">New Access Point</div>
|
184
|
-
<div>
|
185
|
-
<div className="panel__content__form__section__header__label">
|
186
|
-
Name
|
187
|
-
</div>
|
188
|
-
<InputWithInlineValidation
|
189
|
-
className={clsx('input new-access-point-modal__id-input', {
|
190
|
-
'input--dark': true,
|
191
|
-
})}
|
192
|
-
ref={accessPointInputRef}
|
193
|
-
spellCheck={false}
|
194
|
-
value={id ?? ''}
|
195
|
-
onChange={handleIdChange}
|
196
|
-
placeholder="Access Point Name"
|
197
|
-
error={nameErrors}
|
198
|
-
/>
|
199
|
-
</div>
|
200
|
-
<div>
|
201
|
-
<div className="panel__content__form__section__header__label">
|
202
|
-
Description
|
203
|
-
</div>
|
204
|
-
<InputWithInlineValidation
|
205
|
-
className={clsx('input new-access-point-modal__id-input', {
|
206
|
-
'input--dark': true,
|
207
|
-
})}
|
208
|
-
spellCheck={false}
|
209
|
-
value={description ?? ''}
|
210
|
-
onChange={handleDescriptionChange}
|
211
|
-
placeholder="Access Point Description"
|
212
|
-
error={descriptionErrors}
|
213
|
-
/>
|
214
|
-
</div>
|
215
|
-
<div></div>
|
216
|
-
<PanelDivider />
|
217
|
-
<div className="search-modal__actions">
|
218
|
-
<button
|
219
|
-
className={clsx('btn btn--primary', {
|
220
|
-
'btn--dark': true,
|
221
|
-
})}
|
222
|
-
disabled={disableCreateButton}
|
223
|
-
>
|
224
|
-
Create
|
225
|
-
</button>
|
226
|
-
</div>
|
227
|
-
</form>
|
228
|
-
</Dialog>
|
229
|
-
);
|
230
|
-
},
|
231
|
-
);
|
126
|
+
const AP_DND_TYPE = 'ACCESS_POINT';
|
127
|
+
const AP_GROUP_DND_TYPE = 'ACCESS_POINT_GROUP';
|
232
128
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
const accessPointGroupInputRef = useRef<HTMLInputElement>(null);
|
237
|
-
const [groupName, setGroupName] = useState<string | undefined>(undefined);
|
238
|
-
const handleGroupNameChange: React.ChangeEventHandler<HTMLInputElement> = (
|
239
|
-
event,
|
240
|
-
) => setGroupName(event.target.value);
|
241
|
-
const [groupDescription, setGroupDescription] = useState<
|
242
|
-
string | undefined
|
243
|
-
>(undefined);
|
244
|
-
const handleGroupDescriptionChange: React.ChangeEventHandler<
|
245
|
-
HTMLInputElement
|
246
|
-
> = (event) => setGroupDescription(event.target.value);
|
247
|
-
const [apName, setApName] = useState<string | undefined>(undefined);
|
248
|
-
const handleApNameChange: React.ChangeEventHandler<HTMLInputElement> = (
|
249
|
-
event,
|
250
|
-
) => setApName(event.target.value);
|
251
|
-
const [apDescription, setApDescription] = useState<string | undefined>(
|
252
|
-
undefined,
|
253
|
-
);
|
254
|
-
const handleApDescriptionChange: React.ChangeEventHandler<
|
255
|
-
HTMLInputElement
|
256
|
-
> = (event) => setApDescription(event.target.value);
|
257
|
-
const handleClose = () => {
|
258
|
-
dataProductEditorState.setAccessPointGroupModal(false);
|
259
|
-
};
|
260
|
-
const handleEnter = (): void => {
|
261
|
-
accessPointGroupInputRef.current?.focus();
|
262
|
-
};
|
129
|
+
export type AccessPointDragSource = {
|
130
|
+
accessPointState: AccessPointState;
|
131
|
+
};
|
263
132
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
: dataProductEditorState.accessPointGroupStates
|
268
|
-
.map((e) => e.value.id)
|
269
|
-
.includes(groupName ?? '')
|
270
|
-
? AP_GROUP_MODAL_ERRORS.GROUP_NAME_EXISTS
|
271
|
-
: undefined;
|
272
|
-
const groupDescriptionErrors =
|
273
|
-
groupDescription === ''
|
274
|
-
? AP_GROUP_MODAL_ERRORS.GROUP_DESCRIPTION_EMPTY
|
275
|
-
: undefined;
|
276
|
-
const apNameErrors =
|
277
|
-
apName === ''
|
278
|
-
? AP_GROUP_MODAL_ERRORS.AP_NAME_EMPTY
|
279
|
-
: dataProductEditorState.accessPoints
|
280
|
-
.map((e) => e.id)
|
281
|
-
.includes(apName ?? '')
|
282
|
-
? AP_GROUP_MODAL_ERRORS.AP_NAME_EXISTS
|
283
|
-
: undefined;
|
284
|
-
const apDescriptionErrors =
|
285
|
-
apDescription === ''
|
286
|
-
? AP_GROUP_MODAL_ERRORS.AP_DESCRIPTION_EMPTY
|
287
|
-
: undefined;
|
288
|
-
|
289
|
-
const disableCreateButton =
|
290
|
-
!groupName ||
|
291
|
-
!groupDescription ||
|
292
|
-
!apName ||
|
293
|
-
!apDescription ||
|
294
|
-
Boolean(
|
295
|
-
groupNameErrors ??
|
296
|
-
groupDescriptionErrors ??
|
297
|
-
apNameErrors ??
|
298
|
-
apDescriptionErrors,
|
299
|
-
);
|
300
|
-
const handleSubmit = () => {
|
301
|
-
if (!disableCreateButton && apName) {
|
302
|
-
const createdGroup = dataProductEditorState.createGroupAndAdd(
|
303
|
-
groupName,
|
304
|
-
groupDescription,
|
305
|
-
);
|
306
|
-
dataProductEditorState.addAccessPoint(
|
307
|
-
apName,
|
308
|
-
apDescription,
|
309
|
-
createdGroup,
|
310
|
-
);
|
311
|
-
handleClose();
|
312
|
-
}
|
313
|
-
};
|
133
|
+
export type APGDragSource = {
|
134
|
+
groupState: AccessPointGroupState;
|
135
|
+
};
|
314
136
|
|
315
|
-
|
316
|
-
<Dialog
|
317
|
-
open={true}
|
318
|
-
onClose={handleClose}
|
319
|
-
TransitionProps={{
|
320
|
-
onEnter: handleEnter,
|
321
|
-
}}
|
322
|
-
classes={{
|
323
|
-
container: 'search-modal__container',
|
324
|
-
}}
|
325
|
-
PaperProps={{
|
326
|
-
classes: {
|
327
|
-
root: 'search-modal__inner-container',
|
328
|
-
},
|
329
|
-
}}
|
330
|
-
>
|
331
|
-
<form
|
332
|
-
onSubmit={(event) => {
|
333
|
-
event.preventDefault();
|
334
|
-
handleSubmit();
|
335
|
-
}}
|
336
|
-
className={clsx('modal search-modal', {
|
337
|
-
'modal--dark': true,
|
338
|
-
})}
|
339
|
-
style={{
|
340
|
-
display: 'flex',
|
341
|
-
flexDirection: 'column',
|
342
|
-
gap: '1rem',
|
343
|
-
}}
|
344
|
-
>
|
345
|
-
<div className="modal__title">New Access Point Group</div>
|
346
|
-
<div>
|
347
|
-
<div className="panel__content__form__section__header__label">
|
348
|
-
Group Name
|
349
|
-
</div>
|
350
|
-
<InputWithInlineValidation
|
351
|
-
className={clsx('input new-access-point-modal__id-input', {
|
352
|
-
'input--dark': true,
|
353
|
-
})}
|
354
|
-
ref={accessPointGroupInputRef}
|
355
|
-
spellCheck={false}
|
356
|
-
value={groupName ?? ''}
|
357
|
-
onChange={handleGroupNameChange}
|
358
|
-
placeholder="Access Point Group Name"
|
359
|
-
error={groupNameErrors}
|
360
|
-
/>
|
361
|
-
</div>
|
362
|
-
<div>
|
363
|
-
<div className="panel__content__form__section__header__label">
|
364
|
-
Group Description
|
365
|
-
</div>
|
366
|
-
<InputWithInlineValidation
|
367
|
-
className={clsx('input new-access-point-modal__id-input', {
|
368
|
-
'input--dark': true,
|
369
|
-
})}
|
370
|
-
spellCheck={false}
|
371
|
-
value={groupDescription ?? ''}
|
372
|
-
onChange={handleGroupDescriptionChange}
|
373
|
-
placeholder="Access Point Group Description"
|
374
|
-
error={groupDescriptionErrors}
|
375
|
-
/>
|
376
|
-
</div>
|
377
|
-
<div>
|
378
|
-
<div className="panel__content__form__section__header__label">
|
379
|
-
Access Point
|
380
|
-
</div>
|
381
|
-
<div className="new-access-point-group-modal">
|
382
|
-
<div className="panel__content__form__section__header__label">
|
383
|
-
Name
|
384
|
-
</div>
|
385
|
-
<InputWithInlineValidation
|
386
|
-
className={clsx('input new-access-point-modal__id-input', {
|
387
|
-
'input--dark': true,
|
388
|
-
})}
|
389
|
-
spellCheck={false}
|
390
|
-
value={apName ?? ''}
|
391
|
-
onChange={handleApNameChange}
|
392
|
-
placeholder="Access Point Name"
|
393
|
-
error={apNameErrors}
|
394
|
-
/>
|
395
|
-
<div className="panel__content__form__section__header__label">
|
396
|
-
Description
|
397
|
-
</div>
|
398
|
-
<InputWithInlineValidation
|
399
|
-
className={clsx('input new-access-point-modal__id-input', {
|
400
|
-
'input--dark': true,
|
401
|
-
})}
|
402
|
-
spellCheck={false}
|
403
|
-
value={apDescription ?? ''}
|
404
|
-
onChange={handleApDescriptionChange}
|
405
|
-
placeholder="Access Point Description"
|
406
|
-
error={apDescriptionErrors}
|
407
|
-
/>
|
408
|
-
</div>
|
409
|
-
</div>
|
410
|
-
<PanelDivider />
|
411
|
-
<div className="search-modal__actions">
|
412
|
-
<button
|
413
|
-
className={clsx('btn btn--primary', {
|
414
|
-
'btn--dark': true,
|
415
|
-
})}
|
416
|
-
disabled={disableCreateButton}
|
417
|
-
>
|
418
|
-
Create
|
419
|
-
</button>
|
420
|
-
</div>
|
421
|
-
</form>
|
422
|
-
</Dialog>
|
423
|
-
);
|
424
|
-
},
|
425
|
-
);
|
137
|
+
const newNamePlaceholder = '';
|
426
138
|
|
427
139
|
interface HoverTextAreaProps {
|
428
140
|
text: string;
|
@@ -456,204 +168,396 @@ const hoverIcon = () => {
|
|
456
168
|
);
|
457
169
|
};
|
458
170
|
|
171
|
+
const AccessPointTitle = observer(
|
172
|
+
(props: { accessPoint: LakehouseAccessPoint }) => {
|
173
|
+
const { accessPoint } = props;
|
174
|
+
const [editingName, setEditingName] = useState(
|
175
|
+
accessPoint.id === newNamePlaceholder,
|
176
|
+
);
|
177
|
+
const handleNameEdit = () => setEditingName(true);
|
178
|
+
const handleNameBlur = () => {
|
179
|
+
if (accessPoint.id !== newNamePlaceholder) {
|
180
|
+
setEditingName(false);
|
181
|
+
}
|
182
|
+
};
|
183
|
+
const updateAccessPointName: React.ChangeEventHandler<HTMLTextAreaElement> =
|
184
|
+
action((event) => {
|
185
|
+
if (!event.target.value.includes(' ')) {
|
186
|
+
accessPoint.id = event.target.value;
|
187
|
+
}
|
188
|
+
});
|
189
|
+
|
190
|
+
return editingName ? (
|
191
|
+
<textarea
|
192
|
+
className="access-point-editor__name"
|
193
|
+
spellCheck={false}
|
194
|
+
value={accessPoint.id}
|
195
|
+
onChange={updateAccessPointName}
|
196
|
+
placeholder={'Access Point Name'}
|
197
|
+
onBlur={handleNameBlur}
|
198
|
+
style={{
|
199
|
+
borderColor:
|
200
|
+
accessPoint.id === newNamePlaceholder
|
201
|
+
? 'var(--color-red-300)'
|
202
|
+
: 'transparent',
|
203
|
+
}}
|
204
|
+
/>
|
205
|
+
) : (
|
206
|
+
<div onClick={handleNameEdit} title="Click to edit access point title">
|
207
|
+
<div className="access-point-editor__name__label">{accessPoint.id}</div>
|
208
|
+
</div>
|
209
|
+
);
|
210
|
+
},
|
211
|
+
);
|
212
|
+
|
213
|
+
const AccessPointClassification = observer(
|
214
|
+
(props: {
|
215
|
+
accessPoint: LakehouseAccessPoint;
|
216
|
+
groupState: AccessPointGroupState;
|
217
|
+
}) => {
|
218
|
+
const { accessPoint, groupState } = props;
|
219
|
+
const applicationStore = useEditorStore().applicationStore;
|
220
|
+
const CHOOSE_CLASSIFICATION = 'Choose Classification';
|
221
|
+
const updateAccessPointClassificationTextbox: React.ChangeEventHandler<HTMLTextAreaElement> =
|
222
|
+
action((event) => {
|
223
|
+
accessPoint.classification = event.target.value;
|
224
|
+
});
|
225
|
+
|
226
|
+
const conditionalClassifications = (): string[] => {
|
227
|
+
if (groupState.containsPublicStereotype) {
|
228
|
+
return (
|
229
|
+
applicationStore.config.options.dataProductConfig
|
230
|
+
?.publicClassifications ?? []
|
231
|
+
);
|
232
|
+
} else {
|
233
|
+
return (
|
234
|
+
applicationStore.config.options.dataProductConfig?.classifications ??
|
235
|
+
[]
|
236
|
+
);
|
237
|
+
}
|
238
|
+
};
|
239
|
+
|
240
|
+
const classificationOptions = [CHOOSE_CLASSIFICATION]
|
241
|
+
.concat(conditionalClassifications())
|
242
|
+
.map((classfication) => ({
|
243
|
+
label: classfication,
|
244
|
+
value: classfication,
|
245
|
+
}));
|
246
|
+
|
247
|
+
const updateAccessPointClassificationFromDropdown = action(
|
248
|
+
(val: { label: string; value: string } | null): void => {
|
249
|
+
accessPoint_setClassification(
|
250
|
+
accessPoint,
|
251
|
+
val?.value === CHOOSE_CLASSIFICATION ? undefined : val?.value,
|
252
|
+
);
|
253
|
+
},
|
254
|
+
);
|
255
|
+
|
256
|
+
const currentClassification =
|
257
|
+
accessPoint.classification !== undefined
|
258
|
+
? {
|
259
|
+
label: accessPoint.classification,
|
260
|
+
value: accessPoint.classification,
|
261
|
+
}
|
262
|
+
: {
|
263
|
+
label: CHOOSE_CLASSIFICATION,
|
264
|
+
value: CHOOSE_CLASSIFICATION,
|
265
|
+
};
|
266
|
+
|
267
|
+
const classificationDocumentationLink = (): void => {
|
268
|
+
const docLink =
|
269
|
+
applicationStore.config.options.dataProductConfig?.classificationDoc;
|
270
|
+
if (docLink) {
|
271
|
+
applicationStore.navigationService.navigator.visitAddress(docLink);
|
272
|
+
}
|
273
|
+
};
|
274
|
+
return (
|
275
|
+
<div className="access-point-editor__classification">
|
276
|
+
{classificationOptions.length > 1 ? (
|
277
|
+
<div>
|
278
|
+
<CustomSelectorInput
|
279
|
+
className="explorer__new-element-modal__driver__dropdown"
|
280
|
+
options={classificationOptions}
|
281
|
+
onChange={updateAccessPointClassificationFromDropdown}
|
282
|
+
value={currentClassification}
|
283
|
+
darkMode={
|
284
|
+
!applicationStore.layoutService
|
285
|
+
.TEMPORARY__isLightColorThemeEnabled
|
286
|
+
}
|
287
|
+
/>
|
288
|
+
</div>
|
289
|
+
) : (
|
290
|
+
<textarea
|
291
|
+
className="panel__content__form__section__input"
|
292
|
+
spellCheck={false}
|
293
|
+
value={accessPoint.classification ?? ''}
|
294
|
+
onChange={updateAccessPointClassificationTextbox}
|
295
|
+
placeholder="Add classification"
|
296
|
+
style={{
|
297
|
+
overflow: 'hidden',
|
298
|
+
width: '125px',
|
299
|
+
resize: 'none',
|
300
|
+
padding: '0.25rem',
|
301
|
+
}}
|
302
|
+
/>
|
303
|
+
)}
|
304
|
+
<Tooltip
|
305
|
+
title="Learn more about data classification scheme here."
|
306
|
+
arrow={true}
|
307
|
+
placement={'top'}
|
308
|
+
>
|
309
|
+
<button onClick={classificationDocumentationLink}>
|
310
|
+
<InfoCircleIcon />
|
311
|
+
</button>
|
312
|
+
</Tooltip>
|
313
|
+
</div>
|
314
|
+
);
|
315
|
+
},
|
316
|
+
);
|
317
|
+
|
459
318
|
export const LakehouseDataProductAcccessPointEditor = observer(
|
460
319
|
(props: {
|
461
320
|
accessPointState: LakehouseAccessPointState;
|
462
321
|
isReadOnly: boolean;
|
463
322
|
}) => {
|
464
323
|
const { accessPointState } = props;
|
324
|
+
const editorStore = useEditorStore();
|
465
325
|
const accessPoint = accessPointState.accessPoint;
|
466
|
-
const
|
326
|
+
const groupState = accessPointState.state;
|
467
327
|
const lambdaEditorState = accessPointState.lambdaState;
|
468
|
-
const propertyHasParserError =
|
328
|
+
const propertyHasParserError = groupState.accessPointStates
|
469
329
|
.filter(filterByType(LakehouseAccessPointState))
|
470
330
|
.find((pm) => pm.lambdaState.parserError);
|
471
331
|
const [editingDescription, setEditingDescription] = useState(false);
|
472
332
|
const [isHovering, setIsHovering] = useState(false);
|
333
|
+
const ref = useRef<HTMLDivElement>(null);
|
473
334
|
|
474
|
-
const
|
475
|
-
const
|
335
|
+
const handleDescriptionEdit = () => setEditingDescription(true);
|
336
|
+
const handleDescriptionBlur = () => {
|
476
337
|
setEditingDescription(false);
|
477
338
|
setIsHovering(false);
|
478
339
|
};
|
479
|
-
|
480
340
|
const handleMouseOver: React.MouseEventHandler<HTMLDivElement> = () => {
|
481
341
|
setIsHovering(true);
|
482
342
|
};
|
483
343
|
const handleMouseOut: React.MouseEventHandler<HTMLDivElement> = () => {
|
484
344
|
setIsHovering(false);
|
485
345
|
};
|
486
|
-
|
487
346
|
const updateAccessPointDescription: React.ChangeEventHandler<HTMLTextAreaElement> =
|
488
347
|
action((event) => {
|
489
348
|
accessPoint.description = event.target.value;
|
490
349
|
});
|
491
|
-
|
492
350
|
const updateAccessPointTargetEnvironment = action(
|
493
351
|
(targetEnvironment: LakehouseTargetEnv) => {
|
494
352
|
accessPoint.targetEnvironment = targetEnvironment;
|
495
353
|
},
|
496
354
|
);
|
497
355
|
|
356
|
+
const handleRemoveAccessPoint = (): void => {
|
357
|
+
editorStore.applicationStore.alertService.setActionAlertInfo({
|
358
|
+
message: `Are you sure you want to delete Access Point ${accessPoint.id}?`,
|
359
|
+
type: ActionAlertType.CAUTION,
|
360
|
+
actions: [
|
361
|
+
{
|
362
|
+
label: 'Confirm',
|
363
|
+
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
|
364
|
+
handler: (): void => {
|
365
|
+
groupState.deleteAccessPoint(accessPointState);
|
366
|
+
},
|
367
|
+
},
|
368
|
+
{
|
369
|
+
label: 'Cancel',
|
370
|
+
type: ActionAlertActionType.PROCEED,
|
371
|
+
default: true,
|
372
|
+
},
|
373
|
+
],
|
374
|
+
});
|
375
|
+
};
|
376
|
+
|
377
|
+
//Drag and drop - reorder access points/move between groups
|
378
|
+
const handleHover = useCallback(
|
379
|
+
(item: AccessPointDragSource): void => {
|
380
|
+
const draggingProperty = item.accessPointState;
|
381
|
+
const hoveredProperty = accessPointState;
|
382
|
+
groupState.swapAccessPoints(draggingProperty, hoveredProperty);
|
383
|
+
},
|
384
|
+
[accessPointState, groupState],
|
385
|
+
);
|
386
|
+
|
387
|
+
const [{ isBeingDraggedAP }, dropConnector] = useDrop<
|
388
|
+
AccessPointDragSource,
|
389
|
+
void,
|
390
|
+
{ isBeingDraggedAP: AccessPointState | undefined }
|
391
|
+
>(
|
392
|
+
() => ({
|
393
|
+
accept: [AP_DND_TYPE],
|
394
|
+
hover: (item) => handleHover(item),
|
395
|
+
collect: (monitor) => ({
|
396
|
+
isBeingDraggedAP: monitor.getItem<AccessPointDragSource | null>()
|
397
|
+
?.accessPointState,
|
398
|
+
}),
|
399
|
+
}),
|
400
|
+
[handleHover],
|
401
|
+
);
|
402
|
+
const isBeingDragged = accessPoint === isBeingDraggedAP?.accessPoint;
|
403
|
+
|
404
|
+
const [, dragConnector, dragPreviewConnector] =
|
405
|
+
useDrag<AccessPointDragSource>(
|
406
|
+
() => ({
|
407
|
+
type: AP_DND_TYPE,
|
408
|
+
item: () => ({
|
409
|
+
accessPointState: accessPointState,
|
410
|
+
}),
|
411
|
+
collect: (monitor) => ({
|
412
|
+
isDragging: monitor.isDragging(),
|
413
|
+
}),
|
414
|
+
}),
|
415
|
+
[accessPointState],
|
416
|
+
);
|
417
|
+
dragConnector(ref);
|
418
|
+
dropConnector(ref);
|
419
|
+
useDragPreviewLayer(dragPreviewConnector);
|
420
|
+
|
498
421
|
return (
|
499
|
-
<
|
500
|
-
|
501
|
-
|
502
|
-
}
|
422
|
+
<PanelDnDEntry
|
423
|
+
ref={ref}
|
424
|
+
placeholder={<div className="dnd__placeholder--light"></div>}
|
425
|
+
showPlaceholder={isBeingDragged}
|
503
426
|
>
|
504
|
-
<div
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
<HoverTextArea
|
532
|
-
text={accessPoint.description}
|
533
|
-
handleMouseOver={handleMouseOver}
|
534
|
-
handleMouseOut={handleMouseOut}
|
427
|
+
<div
|
428
|
+
className={clsx('access-point-editor', {
|
429
|
+
backdrop__element: propertyHasParserError,
|
430
|
+
})}
|
431
|
+
>
|
432
|
+
<PanelEntryDragHandle
|
433
|
+
dragSourceConnector={ref}
|
434
|
+
isDragging={isBeingDragged}
|
435
|
+
title={'Drag this Access Point to another group'}
|
436
|
+
className="access-point-editor__dnd-handle"
|
437
|
+
/>
|
438
|
+
<div style={{ flex: 1 }}>
|
439
|
+
<div className="access-point-editor__metadata">
|
440
|
+
<AccessPointTitle accessPoint={accessPoint} />
|
441
|
+
{editingDescription ? (
|
442
|
+
<textarea
|
443
|
+
className="panel__content__form__section__input"
|
444
|
+
spellCheck={false}
|
445
|
+
value={accessPoint.description ?? ''}
|
446
|
+
onChange={updateAccessPointDescription}
|
447
|
+
placeholder="Access Point description"
|
448
|
+
onBlur={handleDescriptionBlur}
|
449
|
+
style={{
|
450
|
+
overflow: 'hidden',
|
451
|
+
resize: 'none',
|
452
|
+
padding: '0.25rem',
|
453
|
+
}}
|
535
454
|
/>
|
536
455
|
) : (
|
537
456
|
<div
|
538
|
-
|
539
|
-
|
540
|
-
|
457
|
+
onClick={handleDescriptionEdit}
|
458
|
+
title="Click to edit access point description"
|
459
|
+
className="access-point-editor__description-container"
|
541
460
|
>
|
542
|
-
|
543
|
-
|
461
|
+
{accessPoint.description ? (
|
462
|
+
<HoverTextArea
|
463
|
+
text={accessPoint.description}
|
464
|
+
handleMouseOver={handleMouseOver}
|
465
|
+
handleMouseOut={handleMouseOut}
|
466
|
+
/>
|
467
|
+
) : (
|
468
|
+
<div
|
469
|
+
className="access-point-editor__group-container__description--warning"
|
470
|
+
onMouseOver={handleMouseOver}
|
471
|
+
onMouseOut={handleMouseOut}
|
472
|
+
>
|
473
|
+
<WarningIcon />
|
474
|
+
{AP_EMPTY_DESC_WARNING}
|
475
|
+
</div>
|
476
|
+
)}
|
477
|
+
{isHovering && hoverIcon()}
|
544
478
|
</div>
|
545
479
|
)}
|
480
|
+
<div className="access-point-editor__info">
|
481
|
+
{editorStore.applicationStore.config.options
|
482
|
+
.dataProductConfig && (
|
483
|
+
<AccessPointClassification
|
484
|
+
accessPoint={accessPoint}
|
485
|
+
groupState={groupState}
|
486
|
+
/>
|
487
|
+
)}
|
546
488
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
489
|
+
<div
|
490
|
+
className={clsx('access-point-editor__type')}
|
491
|
+
title={'Change target environment'}
|
492
|
+
>
|
493
|
+
<div className="access-point-editor__type__label">
|
494
|
+
{accessPoint.targetEnvironment}
|
495
|
+
</div>
|
496
|
+
<ControlledDropdownMenu
|
497
|
+
className="access-point-editor__dropdown"
|
498
|
+
content={
|
499
|
+
<MenuContent>
|
500
|
+
{Object.values(LakehouseTargetEnv).map(
|
501
|
+
(environment) => (
|
502
|
+
<MenuContentItem
|
503
|
+
key={environment}
|
504
|
+
className="btn__dropdown-combo__option"
|
505
|
+
onClick={() =>
|
506
|
+
updateAccessPointTargetEnvironment(environment)
|
507
|
+
}
|
508
|
+
>
|
509
|
+
{environment}
|
510
|
+
</MenuContentItem>
|
511
|
+
),
|
512
|
+
)}
|
513
|
+
</MenuContent>
|
514
|
+
}
|
515
|
+
menuProps={{
|
516
|
+
anchorOrigin: {
|
517
|
+
vertical: 'bottom',
|
518
|
+
horizontal: 'right',
|
519
|
+
},
|
520
|
+
transformOrigin: {
|
521
|
+
vertical: 'top',
|
522
|
+
horizontal: 'right',
|
523
|
+
},
|
524
|
+
}}
|
525
|
+
>
|
526
|
+
<CaretDownIcon />
|
527
|
+
</ControlledDropdownMenu>
|
528
|
+
</div>
|
557
529
|
</div>
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
{
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
{environment}
|
579
|
-
</MenuContentItem>
|
580
|
-
))}
|
581
|
-
</MenuContent>
|
582
|
-
}
|
583
|
-
menuProps={{
|
584
|
-
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
|
585
|
-
transformOrigin: { vertical: 'top', horizontal: 'right' },
|
530
|
+
</div>
|
531
|
+
<div className="access-point-editor__content">
|
532
|
+
<div className="access-point-editor__generic-entry">
|
533
|
+
<div className="access-point-editor__entry__container">
|
534
|
+
<div className="access-point-editor__entry">
|
535
|
+
<InlineLambdaEditor
|
536
|
+
className={'access-point-editor__lambda-editor'}
|
537
|
+
disabled={
|
538
|
+
lambdaEditorState.val.state.state
|
539
|
+
.isConvertingTransformLambdaObjects
|
540
|
+
}
|
541
|
+
lambdaEditorState={lambdaEditorState}
|
542
|
+
forceBackdrop={Boolean(lambdaEditorState.parserError)}
|
543
|
+
/>
|
544
|
+
</div>
|
545
|
+
</div>
|
546
|
+
<button
|
547
|
+
className="access-point-editor__generic-entry__remove-btn"
|
548
|
+
onClick={() => {
|
549
|
+
handleRemoveAccessPoint();
|
586
550
|
}}
|
551
|
+
tabIndex={-1}
|
552
|
+
title="Remove"
|
587
553
|
>
|
588
|
-
<
|
589
|
-
</
|
554
|
+
<TimesIcon />
|
555
|
+
</button>
|
590
556
|
</div>
|
591
557
|
</div>
|
592
558
|
</div>
|
593
559
|
</div>
|
594
|
-
|
595
|
-
<div className="access-point-editor__generic-entry">
|
596
|
-
<div className="access-point-editor__entry__container">
|
597
|
-
<div className="access-point-editor__entry">
|
598
|
-
<InlineLambdaEditor
|
599
|
-
className={'access-point-editor__lambda-editor'}
|
600
|
-
disabled={
|
601
|
-
lambdaEditorState.val.state.state
|
602
|
-
.isConvertingTransformLambdaObjects
|
603
|
-
}
|
604
|
-
lambdaEditorState={lambdaEditorState}
|
605
|
-
forceBackdrop={Boolean(lambdaEditorState.parserError)}
|
606
|
-
/>
|
607
|
-
</div>
|
608
|
-
</div>
|
609
|
-
<button
|
610
|
-
className="access-point-editor__generic-entry__remove-btn"
|
611
|
-
onClick={() => {
|
612
|
-
productEditorState.deleteAccessPoint(accessPointState);
|
613
|
-
}}
|
614
|
-
tabIndex={-1}
|
615
|
-
title="Remove"
|
616
|
-
>
|
617
|
-
<TimesIcon />
|
618
|
-
</button>
|
619
|
-
</div>
|
620
|
-
</div>
|
621
|
-
</div>
|
622
|
-
);
|
623
|
-
},
|
624
|
-
);
|
625
|
-
|
626
|
-
const DataProductEditorSplashScreen = observer(
|
627
|
-
(props: { dataProductEditorState: DataProductEditorState }) => {
|
628
|
-
const { dataProductEditorState } = props;
|
629
|
-
const logoWidth = 280;
|
630
|
-
const logoHeight = 270;
|
631
|
-
const [showLogo, setShowLogo] = useState(false);
|
632
|
-
const { ref, height, width } = useResizeDetector<HTMLDivElement>();
|
633
|
-
|
634
|
-
useEffect(() => {
|
635
|
-
setShowLogo((width ?? 0) > logoWidth && (height ?? 0) > logoHeight);
|
636
|
-
}, [height, width]);
|
637
|
-
|
638
|
-
return (
|
639
|
-
<div ref={ref} className="data-product-editor__splash-screen">
|
640
|
-
<div
|
641
|
-
onClick={() => dataProductEditorState.setAccessPointGroupModal(true)}
|
642
|
-
className="data-product-editor__splash-screen__label"
|
643
|
-
>
|
644
|
-
Add Access Point Group
|
645
|
-
</div>
|
646
|
-
<div className="data-product-editor__splash-screen__spacing"></div>
|
647
|
-
<div
|
648
|
-
onClick={() => dataProductEditorState.setAccessPointGroupModal(true)}
|
649
|
-
title="Add new Access Point Group"
|
650
|
-
className={clsx('data-product-editor__splash-screen__logo', {
|
651
|
-
'data-product-editor__splash-screen__logo--hidden': !showLogo,
|
652
|
-
})}
|
653
|
-
>
|
654
|
-
<AccessPointIcon />
|
655
|
-
</div>
|
656
|
-
</div>
|
560
|
+
</PanelDnDEntry>
|
657
561
|
);
|
658
562
|
},
|
659
563
|
);
|
@@ -708,14 +612,54 @@ const DataProductDeploymentResponseModal = observer(
|
|
708
612
|
},
|
709
613
|
);
|
710
614
|
|
711
|
-
const
|
615
|
+
const AccessPointGroupPublicToggle = observer(
|
616
|
+
(props: { groupState: AccessPointGroupState }) => {
|
617
|
+
const { groupState } = props;
|
618
|
+
|
619
|
+
const handleSwitchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
620
|
+
const isChecked = event.target.checked;
|
621
|
+
if (isChecked && groupState.publicStereotype) {
|
622
|
+
annotatedElement_addStereotype(
|
623
|
+
groupState.value,
|
624
|
+
StereotypeExplicitReference.create(groupState.publicStereotype),
|
625
|
+
);
|
626
|
+
} else if (groupState.containsPublicStereotype) {
|
627
|
+
annotatedElement_deleteStereotype(
|
628
|
+
groupState.value,
|
629
|
+
groupState.containsPublicStereotype,
|
630
|
+
);
|
631
|
+
}
|
632
|
+
};
|
633
|
+
return (
|
634
|
+
<div className="access-point-editor__toggle">
|
635
|
+
<Switch
|
636
|
+
checked={Boolean(groupState.containsPublicStereotype)}
|
637
|
+
onChange={handleSwitchChange}
|
638
|
+
sx={{
|
639
|
+
'& .MuiSwitch-track': {
|
640
|
+
backgroundColor: groupState.containsPublicStereotype
|
641
|
+
? 'default'
|
642
|
+
: 'var(--color-light-grey-400)',
|
643
|
+
},
|
644
|
+
}}
|
645
|
+
/>
|
646
|
+
<BuildingIcon />
|
647
|
+
Enterprise Data. Anyone at the firm can access this without approvals.
|
648
|
+
</div>
|
649
|
+
);
|
650
|
+
},
|
651
|
+
);
|
652
|
+
|
653
|
+
const AccessPointGroupEditor = observer(
|
712
654
|
(props: { groupState: AccessPointGroupState; isReadOnly: boolean }) => {
|
713
655
|
const { groupState, isReadOnly } = props;
|
714
656
|
const editorStore = useEditorStore();
|
715
657
|
const productEditorState = groupState.state;
|
716
658
|
const [editingDescription, setEditingDescription] = useState(false);
|
717
659
|
const [isHoveringDescription, setIsHoveringDescription] = useState(false);
|
718
|
-
const [editingName, setEditingName] = useState(
|
660
|
+
const [editingName, setEditingName] = useState(
|
661
|
+
groupState.value.id === newNamePlaceholder,
|
662
|
+
);
|
719
663
|
const [isHoveringName, setIsHoveringName] = useState(false);
|
720
664
|
|
721
665
|
const handleDescriptionEdit = () => setEditingDescription(true);
|
@@ -739,8 +683,10 @@ const AccessPointGroupSection = observer(
|
|
739
683
|
|
740
684
|
const handleNameEdit = () => setEditingName(true);
|
741
685
|
const handleNameBlur = () => {
|
742
|
-
|
743
|
-
|
686
|
+
if (groupState.value.id !== newNamePlaceholder) {
|
687
|
+
setEditingName(false);
|
688
|
+
setIsHoveringName(false);
|
689
|
+
}
|
744
690
|
};
|
745
691
|
const handleMouseOverName: React.MouseEventHandler<HTMLDivElement> = () => {
|
746
692
|
setIsHoveringName(true);
|
@@ -749,38 +695,45 @@ const AccessPointGroupSection = observer(
|
|
749
695
|
setIsHoveringName(false);
|
750
696
|
};
|
751
697
|
const updateGroupName = (val: string): void => {
|
752
|
-
if (val) {
|
698
|
+
if (val && !val.includes(' ')) {
|
753
699
|
accessPointGroup_setName(groupState.value, val);
|
754
700
|
}
|
755
701
|
};
|
756
702
|
|
757
703
|
const handleRemoveAccessPointGroup = (): void => {
|
758
704
|
editorStore.applicationStore.alertService.setActionAlertInfo({
|
759
|
-
message: `
|
705
|
+
message: `Are you sure you want to delete Access Point Group ${groupState.value.id} and all of its Access Points?`,
|
760
706
|
type: ActionAlertType.CAUTION,
|
761
707
|
actions: [
|
762
708
|
{
|
763
|
-
label: '
|
764
|
-
type: ActionAlertActionType.PROCEED,
|
765
|
-
default: true,
|
766
|
-
},
|
767
|
-
{
|
768
|
-
label: 'Proceed',
|
709
|
+
label: 'Confirm',
|
769
710
|
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
|
770
711
|
handler: (): void => {
|
771
712
|
productEditorState.deleteAccessPointGroup(groupState);
|
772
713
|
},
|
773
714
|
},
|
715
|
+
{
|
716
|
+
label: 'Cancel',
|
717
|
+
type: ActionAlertActionType.PROCEED,
|
718
|
+
default: true,
|
719
|
+
},
|
774
720
|
],
|
775
721
|
});
|
776
722
|
};
|
777
723
|
|
778
|
-
const
|
779
|
-
productEditorState.
|
780
|
-
|
724
|
+
const handleAddAccessPoint = () => {
|
725
|
+
productEditorState.addAccessPoint(
|
726
|
+
newNamePlaceholder,
|
727
|
+
undefined,
|
728
|
+
productEditorState.selectedGroupState ?? groupState,
|
729
|
+
);
|
781
730
|
};
|
731
|
+
|
782
732
|
return (
|
783
|
-
<div
|
733
|
+
<div
|
734
|
+
className="access-point-editor__group-container"
|
735
|
+
data-testid={LEGEND_STUDIO_TEST_ID.ACCESS_POINT_GROUP_EDITOR}
|
736
|
+
>
|
784
737
|
<div className="access-point-editor__group-container__title-editor">
|
785
738
|
{editingName ? (
|
786
739
|
<textarea
|
@@ -794,6 +747,11 @@ const AccessPointGroupSection = observer(
|
|
794
747
|
overflow: 'hidden',
|
795
748
|
resize: 'none',
|
796
749
|
padding: '0.25rem',
|
750
|
+
borderColor:
|
751
|
+
groupState.value.id === newNamePlaceholder
|
752
|
+
? 'var(--color-red-300)'
|
753
|
+
: 'transparent',
|
754
|
+
borderWidth: 'thin',
|
797
755
|
}}
|
798
756
|
/>
|
799
757
|
) : (
|
@@ -815,7 +773,6 @@ const AccessPointGroupSection = observer(
|
|
815
773
|
<button
|
816
774
|
className="access-point-editor__generic-entry__remove-btn--group"
|
817
775
|
onClick={() => {
|
818
|
-
// productEditorState.deleteAccessPointGroup(groupState);
|
819
776
|
handleRemoveAccessPointGroup();
|
820
777
|
}}
|
821
778
|
tabIndex={-1}
|
@@ -859,21 +816,23 @@ const AccessPointGroupSection = observer(
|
|
859
816
|
onMouseOut={handleMouseOutDescription}
|
860
817
|
>
|
861
818
|
<WarningIcon />
|
862
|
-
|
863
|
-
|
864
|
-
group level.
|
819
|
+
Users request access at the access point group level. Click
|
820
|
+
here to add a meaningful description to guide users.
|
865
821
|
</div>
|
866
822
|
)}
|
867
823
|
{isHoveringDescription && hoverIcon()}
|
868
824
|
</div>
|
869
825
|
)}
|
870
826
|
</div>
|
827
|
+
{editorStore.applicationStore.config.options.dataProductConfig && (
|
828
|
+
<AccessPointGroupPublicToggle groupState={groupState} />
|
829
|
+
)}
|
871
830
|
<PanelHeader className="panel__header--access-point">
|
872
831
|
<div className="panel__header__title">Access Points</div>
|
873
832
|
<PanelHeaderActions>
|
874
833
|
<PanelHeaderActionItem
|
875
834
|
className="panel__header__action"
|
876
|
-
onClick={
|
835
|
+
onClick={handleAddAccessPoint}
|
877
836
|
disabled={isReadOnly}
|
878
837
|
title="Create new access point"
|
879
838
|
>
|
@@ -881,38 +840,513 @@ const AccessPointGroupSection = observer(
|
|
881
840
|
</PanelHeaderActionItem>
|
882
841
|
</PanelHeaderActions>
|
883
842
|
</PanelHeader>
|
884
|
-
{groupState.accessPointStates
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
843
|
+
{groupState.accessPointStates.length === 0 && (
|
844
|
+
<div className="access-point-editor__group-container__description--warning">
|
845
|
+
<WarningIcon />
|
846
|
+
This group needs at least one access point defined.
|
847
|
+
</div>
|
848
|
+
)}
|
849
|
+
<div style={{ gap: '1rem', display: 'flex', flexDirection: 'column' }}>
|
850
|
+
{groupState.accessPointStates
|
851
|
+
.filter(filterByType(LakehouseAccessPointState))
|
852
|
+
.map((apState) => (
|
853
|
+
<LakehouseDataProductAcccessPointEditor
|
854
|
+
key={apState.uuid}
|
855
|
+
isReadOnly={isReadOnly}
|
856
|
+
accessPointState={apState}
|
857
|
+
/>
|
858
|
+
))}
|
859
|
+
</div>
|
860
|
+
</div>
|
861
|
+
);
|
862
|
+
},
|
863
|
+
);
|
864
|
+
|
865
|
+
const GroupTabRenderer = observer(
|
866
|
+
(props: {
|
867
|
+
group: AccessPointGroupState;
|
868
|
+
dataProductEditorState: DataProductEditorState;
|
869
|
+
}) => {
|
870
|
+
const { group, dataProductEditorState } = props;
|
871
|
+
const changeGroup = (newGroup: AccessPointGroupState): void => {
|
872
|
+
dataProductEditorState.setSelectedGroupState(newGroup);
|
873
|
+
};
|
874
|
+
const selectedGroupState = dataProductEditorState.selectedGroupState;
|
875
|
+
const ref = useRef<HTMLDivElement>(null);
|
876
|
+
|
877
|
+
const groupError = (): boolean => {
|
878
|
+
return (
|
879
|
+
group.accessPointStates.length === 0 ||
|
880
|
+
group.value.id === newNamePlaceholder ||
|
881
|
+
Boolean(
|
882
|
+
group.accessPointStates.find(
|
883
|
+
(apState) => apState.accessPoint.id === newNamePlaceholder,
|
884
|
+
),
|
885
|
+
)
|
886
|
+
);
|
887
|
+
};
|
888
|
+
|
889
|
+
//Drag and Drop - reorder groups and accept access points from other groups
|
890
|
+
const handleHover = useCallback(
|
891
|
+
(item: APGDragSource): void => {
|
892
|
+
const draggingProperty = item.groupState;
|
893
|
+
const hoveredProperty = group;
|
894
|
+
dataProductEditorState.swapAccessPointGroups(
|
895
|
+
draggingProperty,
|
896
|
+
hoveredProperty,
|
897
|
+
);
|
898
|
+
},
|
899
|
+
[group, dataProductEditorState],
|
900
|
+
);
|
901
|
+
|
902
|
+
const [{ isOver }, dropConnector] = useDrop<
|
903
|
+
APGDragSource | AccessPointDragSource,
|
904
|
+
void,
|
905
|
+
{
|
906
|
+
isOver: boolean;
|
907
|
+
}
|
908
|
+
>(
|
909
|
+
() => ({
|
910
|
+
accept: [AP_GROUP_DND_TYPE, AP_DND_TYPE],
|
911
|
+
hover: (item, monitor) => {
|
912
|
+
const itemType = monitor.getItemType();
|
913
|
+
if (itemType === AP_GROUP_DND_TYPE) {
|
914
|
+
const groupItem = item as APGDragSource;
|
915
|
+
handleHover(groupItem);
|
916
|
+
}
|
917
|
+
},
|
918
|
+
drop: (item, monitor) => {
|
919
|
+
const itemType = monitor.getItemType();
|
920
|
+
if (itemType === AP_DND_TYPE) {
|
921
|
+
const accessPointItem = item as AccessPointDragSource;
|
922
|
+
group.addAccessPoint(accessPointItem.accessPointState);
|
923
|
+
accessPointItem.accessPointState.state.deleteAccessPoint(
|
924
|
+
accessPointItem.accessPointState,
|
925
|
+
);
|
926
|
+
accessPointItem.accessPointState.changeGroupState(group);
|
927
|
+
}
|
928
|
+
},
|
929
|
+
collect: (monitor) => ({
|
930
|
+
isBeingDraggedAPG:
|
931
|
+
monitor.getItemType() === AP_GROUP_DND_TYPE
|
932
|
+
? monitor.getItem<APGDragSource | null>()?.groupState
|
933
|
+
: undefined,
|
934
|
+
isBeingDraggedAP:
|
935
|
+
monitor.getItemType() === AP_DND_TYPE
|
936
|
+
? monitor.getItem<AccessPointDragSource | null>()
|
937
|
+
?.accessPointState
|
938
|
+
: undefined,
|
939
|
+
isOver: monitor.isOver(),
|
940
|
+
}),
|
941
|
+
}),
|
942
|
+
[handleHover],
|
943
|
+
);
|
944
|
+
|
945
|
+
const [, dragConnector, dragPreviewConnector] = useDrag<APGDragSource>(
|
946
|
+
() => ({
|
947
|
+
type: AP_GROUP_DND_TYPE,
|
948
|
+
item: () => ({
|
949
|
+
groupState: group,
|
950
|
+
}),
|
951
|
+
collect: (monitor) => ({
|
952
|
+
isDragging: monitor.isDragging(),
|
953
|
+
}),
|
954
|
+
}),
|
955
|
+
[group],
|
956
|
+
);
|
957
|
+
dragConnector(ref);
|
958
|
+
dropConnector(ref);
|
959
|
+
dragPreviewConnector(ref);
|
960
|
+
|
961
|
+
return (
|
962
|
+
<div
|
963
|
+
ref={ref}
|
964
|
+
key={group.uuid}
|
965
|
+
onClick={(): void => changeGroup(group)}
|
966
|
+
className={clsx('service-editor__tab', {
|
967
|
+
'service-editor__tab--active': group === selectedGroupState,
|
968
|
+
})}
|
969
|
+
style={{
|
970
|
+
backgroundColor: isOver
|
971
|
+
? 'var(--color-dark-grey-100)'
|
972
|
+
: 'var(--color-dark-grey-50)',
|
973
|
+
}}
|
974
|
+
>
|
975
|
+
{group.value.id}
|
976
|
+
|
977
|
+
{groupError() && (
|
978
|
+
<ErrorWarnIcon
|
979
|
+
title="Resolve Access Point Group error(s)"
|
980
|
+
style={{ color: 'var(--color-red-300)' }}
|
981
|
+
/>
|
982
|
+
)}
|
983
|
+
</div>
|
984
|
+
);
|
985
|
+
},
|
986
|
+
);
|
987
|
+
|
988
|
+
const AccessPointGroupTab = observer(
|
989
|
+
(props: {
|
990
|
+
dataProductEditorState: DataProductEditorState;
|
991
|
+
isReadOnly: boolean;
|
992
|
+
}) => {
|
993
|
+
const { dataProductEditorState, isReadOnly } = props;
|
994
|
+
const groupStates = dataProductEditorState.accessPointGroupStates;
|
995
|
+
const selectedGroupState = dataProductEditorState.selectedGroupState;
|
996
|
+
const handleAddAccessPointGroup = () => {
|
997
|
+
const newGroup =
|
998
|
+
dataProductEditorState.createGroupAndAdd(newNamePlaceholder);
|
999
|
+
dataProductEditorState.setSelectedGroupState(newGroup);
|
1000
|
+
};
|
1001
|
+
|
1002
|
+
const AccessPointDragPreviewLayer: React.FC = () => (
|
1003
|
+
<DragPreviewLayer
|
1004
|
+
labelGetter={(item: AccessPointDragSource): string => {
|
1005
|
+
return item.accessPointState.accessPoint.id;
|
1006
|
+
}}
|
1007
|
+
types={[AP_DND_TYPE]}
|
1008
|
+
/>
|
1009
|
+
);
|
1010
|
+
|
1011
|
+
const disableAddGroup = Boolean(
|
1012
|
+
dataProductEditorState.accessPointGroupStates.find(
|
1013
|
+
(group) => group.value.id === newNamePlaceholder,
|
1014
|
+
),
|
1015
|
+
);
|
1016
|
+
|
1017
|
+
return (
|
1018
|
+
<div className="panel" style={{ overflow: 'visible' }}>
|
1019
|
+
<AccessPointDragPreviewLayer />
|
1020
|
+
<div
|
1021
|
+
className="panel__content__form__section__header__label"
|
1022
|
+
style={{ paddingLeft: '1rem' }}
|
1023
|
+
>
|
1024
|
+
Access Point Groups
|
1025
|
+
</div>
|
1026
|
+
<PanelHeader>
|
1027
|
+
<div className="uml-element-editor__tabs">
|
1028
|
+
{groupStates.map((group) => {
|
1029
|
+
return (
|
1030
|
+
<GroupTabRenderer
|
1031
|
+
key={group.uuid}
|
1032
|
+
group={group}
|
1033
|
+
dataProductEditorState={dataProductEditorState}
|
1034
|
+
/>
|
1035
|
+
);
|
1036
|
+
})}
|
1037
|
+
<PanelHeaderActionItem
|
1038
|
+
className="panel__header__action"
|
1039
|
+
onClick={handleAddAccessPointGroup}
|
1040
|
+
disabled={isReadOnly || disableAddGroup}
|
1041
|
+
title={
|
1042
|
+
disableAddGroup
|
1043
|
+
? 'Provide all group names'
|
1044
|
+
: 'Create new access point group'
|
1045
|
+
}
|
1046
|
+
>
|
1047
|
+
<PlusIcon />
|
1048
|
+
</PanelHeaderActionItem>
|
1049
|
+
</div>
|
1050
|
+
|
1051
|
+
<PanelHeaderActions></PanelHeaderActions>
|
1052
|
+
</PanelHeader>
|
1053
|
+
<PanelContent>
|
1054
|
+
{selectedGroupState && (
|
1055
|
+
<AccessPointGroupEditor
|
1056
|
+
key={selectedGroupState.uuid}
|
1057
|
+
groupState={selectedGroupState}
|
889
1058
|
isReadOnly={isReadOnly}
|
890
|
-
accessPointState={apState}
|
891
1059
|
/>
|
1060
|
+
)}
|
1061
|
+
</PanelContent>
|
1062
|
+
{dataProductEditorState.deployResponse && (
|
1063
|
+
<DataProductDeploymentResponseModal state={dataProductEditorState} />
|
1064
|
+
)}
|
1065
|
+
</div>
|
1066
|
+
);
|
1067
|
+
},
|
1068
|
+
);
|
1069
|
+
|
1070
|
+
const DataProductSidebar = observer(
|
1071
|
+
(props: { dataProductEditorState: DataProductEditorState }) => {
|
1072
|
+
const { dataProductEditorState } = props;
|
1073
|
+
const sidebarTabs = [
|
1074
|
+
{
|
1075
|
+
label: DATA_PRODUCT_TAB.HOME,
|
1076
|
+
icon: <HomeIcon />,
|
1077
|
+
},
|
1078
|
+
{
|
1079
|
+
label: DATA_PRODUCT_TAB.APG,
|
1080
|
+
title: 'Access Point Groups',
|
1081
|
+
icon: <GroupWorkIcon />,
|
1082
|
+
},
|
1083
|
+
{
|
1084
|
+
label: DATA_PRODUCT_TAB.SUPPORT,
|
1085
|
+
icon: <QuestionCircleIcon />,
|
1086
|
+
},
|
1087
|
+
];
|
1088
|
+
return (
|
1089
|
+
<div
|
1090
|
+
className="data-space__viewer__activity-bar"
|
1091
|
+
style={{ position: 'static', maxHeight: '100%' }}
|
1092
|
+
>
|
1093
|
+
<div className="data-space__viewer__activity-bar__items">
|
1094
|
+
{sidebarTabs.map((activity) => (
|
1095
|
+
<button
|
1096
|
+
key={activity.label}
|
1097
|
+
className={clsx('data-space__viewer__activity-bar__item', {
|
1098
|
+
'data-space__viewer__activity-bar__item--active':
|
1099
|
+
dataProductEditorState.selectedTab === activity.label,
|
1100
|
+
})}
|
1101
|
+
onClick={() =>
|
1102
|
+
dataProductEditorState.setSelectedTab(activity.label)
|
1103
|
+
}
|
1104
|
+
tabIndex={-1}
|
1105
|
+
title={activity.title ?? activity.label}
|
1106
|
+
style={{
|
1107
|
+
flexDirection: 'column',
|
1108
|
+
fontSize: '12px',
|
1109
|
+
margin: '1rem 0rem',
|
1110
|
+
}}
|
1111
|
+
>
|
1112
|
+
{activity.icon}
|
1113
|
+
{activity.label}
|
1114
|
+
</button>
|
892
1115
|
))}
|
893
|
-
|
894
|
-
|
895
|
-
|
1116
|
+
</div>
|
1117
|
+
</div>
|
1118
|
+
);
|
1119
|
+
},
|
1120
|
+
);
|
1121
|
+
|
1122
|
+
const HomeTab = observer(
|
1123
|
+
(props: { product: DataProduct; isReadOnly: boolean }) => {
|
1124
|
+
const { product, isReadOnly } = props;
|
1125
|
+
const updateDataProductTitle = (val: string | undefined): void => {
|
1126
|
+
dataProduct_setTitle(product, val ?? '');
|
1127
|
+
};
|
1128
|
+
const updateDataProductDescription: ChangeEventHandler<
|
1129
|
+
HTMLTextAreaElement
|
1130
|
+
> = (event) => {
|
1131
|
+
dataProduct_setDescription(product, event.target.value);
|
1132
|
+
};
|
1133
|
+
|
1134
|
+
return (
|
1135
|
+
<div style={{ flexDirection: 'column', display: 'flex' }}>
|
1136
|
+
<PanelFormTextField
|
1137
|
+
name="Title"
|
1138
|
+
value={product.title}
|
1139
|
+
prompt="Provide a descriptive name for the Data Product to appear in Marketplace."
|
1140
|
+
update={updateDataProductTitle}
|
1141
|
+
placeholder="Enter title"
|
1142
|
+
/>
|
1143
|
+
<div style={{ margin: '1rem' }}>
|
1144
|
+
<div className="panel__content__form__section__header__label">
|
1145
|
+
Description
|
1146
|
+
</div>
|
1147
|
+
<div
|
1148
|
+
className="panel__content__form__section__header__prompt"
|
1149
|
+
style={{
|
1150
|
+
color:
|
1151
|
+
product.description === '' || product.description === undefined
|
1152
|
+
? 'var(--color-red-300)'
|
1153
|
+
: 'var(--color-light-grey-400)',
|
1154
|
+
}}
|
1155
|
+
>
|
1156
|
+
Clearly describe the purpose, content, and intended use of the Data
|
1157
|
+
Product.
|
1158
|
+
</div>
|
1159
|
+
<textarea
|
1160
|
+
className="panel__content__form__section__textarea"
|
1161
|
+
spellCheck={false}
|
1162
|
+
disabled={isReadOnly}
|
1163
|
+
value={product.description}
|
1164
|
+
onChange={updateDataProductDescription}
|
1165
|
+
style={{
|
1166
|
+
padding: '0.5rem',
|
1167
|
+
width: '45rem',
|
1168
|
+
maxWidth: '45rem !important',
|
1169
|
+
borderColor:
|
1170
|
+
product.description === '' || product.description === undefined
|
1171
|
+
? 'var(--color-red-300)'
|
1172
|
+
: 'transparent',
|
1173
|
+
}}
|
896
1174
|
/>
|
897
|
-
|
1175
|
+
</div>
|
898
1176
|
</div>
|
899
1177
|
);
|
900
1178
|
},
|
901
1179
|
);
|
902
1180
|
|
1181
|
+
const SupportTab = observer(
|
1182
|
+
(props: { product: DataProduct; isReadOnly: boolean }) => {
|
1183
|
+
const { product, isReadOnly } = props;
|
1184
|
+
const updateSupportInfoDocumentationUrl = (
|
1185
|
+
val: string | undefined,
|
1186
|
+
): void => {
|
1187
|
+
dataProduct_setSupportInfoIfAbsent(product);
|
1188
|
+
if (product.supportInfo) {
|
1189
|
+
supportInfo_setDocumentationUrl(product.supportInfo, val ?? '');
|
1190
|
+
}
|
1191
|
+
};
|
1192
|
+
|
1193
|
+
const updateSupportInfoWebsite = (val: string | undefined): void => {
|
1194
|
+
dataProduct_setSupportInfoIfAbsent(product);
|
1195
|
+
if (product.supportInfo) {
|
1196
|
+
supportInfo_setWebsite(product.supportInfo, val ?? '');
|
1197
|
+
}
|
1198
|
+
};
|
1199
|
+
|
1200
|
+
const updateSupportInfoFaqUrl = (val: string | undefined): void => {
|
1201
|
+
dataProduct_setSupportInfoIfAbsent(product);
|
1202
|
+
if (product.supportInfo) {
|
1203
|
+
supportInfo_setFaqUrl(product.supportInfo, val ?? '');
|
1204
|
+
}
|
1205
|
+
};
|
1206
|
+
|
1207
|
+
const updateSupportInfoSupportUrl = (val: string | undefined): void => {
|
1208
|
+
dataProduct_setSupportInfoIfAbsent(product);
|
1209
|
+
if (product.supportInfo) {
|
1210
|
+
supportInfo_setSupportUrl(product.supportInfo, val ?? '');
|
1211
|
+
}
|
1212
|
+
};
|
1213
|
+
|
1214
|
+
const handleSupportInfoEmailAdd = (
|
1215
|
+
address: string,
|
1216
|
+
title: string,
|
1217
|
+
): void => {
|
1218
|
+
dataProduct_setSupportInfoIfAbsent(product);
|
1219
|
+
if (product.supportInfo) {
|
1220
|
+
supportInfo_addEmail(product.supportInfo, new Email(address, title));
|
1221
|
+
}
|
1222
|
+
};
|
1223
|
+
|
1224
|
+
const handleSupportInfoEmailRemove = (email: Email): void => {
|
1225
|
+
if (product.supportInfo) {
|
1226
|
+
supportInfo_deleteEmail(product.supportInfo, email);
|
1227
|
+
}
|
1228
|
+
};
|
1229
|
+
|
1230
|
+
const SupportEmailComponent = observer(
|
1231
|
+
(supportEmailProps: { item: Email }): React.ReactElement => {
|
1232
|
+
const { item } = supportEmailProps;
|
1233
|
+
|
1234
|
+
return (
|
1235
|
+
<div className="panel__content__form__section__list__item__rows">
|
1236
|
+
<div className="row">
|
1237
|
+
<label className="label">Address</label>
|
1238
|
+
<div className="textbox">{item.address}</div>
|
1239
|
+
</div>
|
1240
|
+
<div className="row">
|
1241
|
+
<label className="label">Title</label>
|
1242
|
+
<div className="textbox">{item.title}</div>
|
1243
|
+
</div>
|
1244
|
+
</div>
|
1245
|
+
);
|
1246
|
+
},
|
1247
|
+
);
|
1248
|
+
|
1249
|
+
const NewSupportEmailComponent = observer(
|
1250
|
+
(newSupportEmailProps: { onFinishEditing: () => void }) => {
|
1251
|
+
const { onFinishEditing } = newSupportEmailProps;
|
1252
|
+
const [address, setAddress] = useState('');
|
1253
|
+
const [title, setTitle] = useState('');
|
1254
|
+
|
1255
|
+
return (
|
1256
|
+
<div className="data-product-editor__support-info__new-email">
|
1257
|
+
<div className="panel__content__form__section__list__new-item__input">
|
1258
|
+
<input
|
1259
|
+
className="input input-group__input panel__content__form__section__input input--dark"
|
1260
|
+
type="email"
|
1261
|
+
placeholder="Enter email"
|
1262
|
+
value={address}
|
1263
|
+
onChange={(event) => {
|
1264
|
+
setAddress(event.target.value);
|
1265
|
+
}}
|
1266
|
+
/>
|
1267
|
+
</div>
|
1268
|
+
<div className="panel__content__form__section__list__new-item__input">
|
1269
|
+
<input
|
1270
|
+
className="input input-group__input panel__content__form__section__input input--dark"
|
1271
|
+
type="title"
|
1272
|
+
placeholder="Enter title"
|
1273
|
+
value={title}
|
1274
|
+
onChange={(event) => {
|
1275
|
+
setTitle(event.target.value);
|
1276
|
+
}}
|
1277
|
+
/>
|
1278
|
+
</div>
|
1279
|
+
<button
|
1280
|
+
className="panel__content__form__section__list__new-item__add-btn btn btn--dark"
|
1281
|
+
onClick={() => {
|
1282
|
+
handleSupportInfoEmailAdd(address, title);
|
1283
|
+
setAddress('');
|
1284
|
+
setTitle('');
|
1285
|
+
onFinishEditing();
|
1286
|
+
}}
|
1287
|
+
>
|
1288
|
+
Save
|
1289
|
+
</button>
|
1290
|
+
</div>
|
1291
|
+
);
|
1292
|
+
},
|
1293
|
+
);
|
1294
|
+
|
1295
|
+
return (
|
1296
|
+
<PanelFormSection>
|
1297
|
+
<div className="panel__content__form__section__header__label">
|
1298
|
+
Support Information
|
1299
|
+
</div>
|
1300
|
+
<div className="panel__content__form__section__header__prompt">
|
1301
|
+
Configure support information for this Lakehouse Data Product.
|
1302
|
+
</div>
|
1303
|
+
<PanelFormTextField
|
1304
|
+
name="Documentation URL"
|
1305
|
+
value={product.supportInfo?.documentationUrl ?? ''}
|
1306
|
+
update={updateSupportInfoDocumentationUrl}
|
1307
|
+
placeholder="Enter Documentation URL"
|
1308
|
+
/>
|
1309
|
+
<PanelFormTextField
|
1310
|
+
name="Website"
|
1311
|
+
value={product.supportInfo?.website}
|
1312
|
+
update={updateSupportInfoWebsite}
|
1313
|
+
placeholder="Enter Website"
|
1314
|
+
/>
|
1315
|
+
<PanelFormTextField
|
1316
|
+
name="FAQ URL"
|
1317
|
+
value={product.supportInfo?.faqUrl}
|
1318
|
+
update={updateSupportInfoFaqUrl}
|
1319
|
+
placeholder="Enter FAQ URL"
|
1320
|
+
/>
|
1321
|
+
<PanelFormTextField
|
1322
|
+
name="Support URL"
|
1323
|
+
value={product.supportInfo?.supportUrl}
|
1324
|
+
update={updateSupportInfoSupportUrl}
|
1325
|
+
placeholder="Enter Support URL"
|
1326
|
+
/>
|
1327
|
+
<ListEditor
|
1328
|
+
title="Emails"
|
1329
|
+
items={product.supportInfo?.emails}
|
1330
|
+
keySelector={(email: Email) => email.address + email.title}
|
1331
|
+
ItemComponent={SupportEmailComponent}
|
1332
|
+
NewItemComponent={NewSupportEmailComponent}
|
1333
|
+
handleRemoveItem={handleSupportInfoEmailRemove}
|
1334
|
+
isReadOnly={isReadOnly}
|
1335
|
+
emptyMessage="No emails specified"
|
1336
|
+
/>
|
1337
|
+
</PanelFormSection>
|
1338
|
+
);
|
1339
|
+
},
|
1340
|
+
);
|
1341
|
+
|
903
1342
|
export const DataProductEditor = observer(() => {
|
904
1343
|
const editorStore = useEditorStore();
|
905
1344
|
const dataProductEditorState =
|
906
1345
|
editorStore.tabManagerState.getCurrentEditorState(DataProductEditorState);
|
907
1346
|
const product = dataProductEditorState.product;
|
908
|
-
const accessPointStates = dataProductEditorState.accessPointGroupStates
|
909
|
-
.map((e) => e.accessPointStates)
|
910
|
-
.flat();
|
911
1347
|
const isReadOnly = dataProductEditorState.isReadOnly;
|
912
|
-
const openNewModal = () => {
|
913
|
-
dataProductEditorState.setAccessPointGroupModal(true);
|
914
|
-
};
|
915
1348
|
const auth = useAuth();
|
1349
|
+
|
916
1350
|
const deployDataProduct = (): void => {
|
917
1351
|
// Trigger OAuth flow if not authenticated
|
918
1352
|
if (!auth.isAuthenticated) {
|
@@ -937,42 +1371,25 @@ export const DataProductEditor = observer(() => {
|
|
937
1371
|
}
|
938
1372
|
};
|
939
1373
|
|
940
|
-
const
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
const updateSupportInfoWebsite = (val: string | undefined): void => {
|
957
|
-
dataProduct_setSupportInfoIfAbsent(product);
|
958
|
-
if (product.supportInfo) {
|
959
|
-
supportInfo_setWebsite(product.supportInfo, val ?? '');
|
960
|
-
}
|
961
|
-
};
|
962
|
-
|
963
|
-
const updateSupportInfoFaqUrl = (val: string | undefined): void => {
|
964
|
-
dataProduct_setSupportInfoIfAbsent(product);
|
965
|
-
if (product.supportInfo) {
|
966
|
-
supportInfo_setFaqUrl(product.supportInfo, val ?? '');
|
1374
|
+
const selectedActivity = dataProductEditorState.selectedTab;
|
1375
|
+
const renderActivivtyBarTab = (): React.ReactNode => {
|
1376
|
+
switch (selectedActivity) {
|
1377
|
+
case DATA_PRODUCT_TAB.HOME:
|
1378
|
+
return <HomeTab product={product} isReadOnly={isReadOnly} />;
|
1379
|
+
case DATA_PRODUCT_TAB.SUPPORT:
|
1380
|
+
return <SupportTab product={product} isReadOnly={isReadOnly} />;
|
1381
|
+
case DATA_PRODUCT_TAB.APG:
|
1382
|
+
return (
|
1383
|
+
<AccessPointGroupTab
|
1384
|
+
dataProductEditorState={dataProductEditorState}
|
1385
|
+
isReadOnly={isReadOnly}
|
1386
|
+
/>
|
1387
|
+
);
|
1388
|
+
default:
|
1389
|
+
return null;
|
967
1390
|
}
|
968
1391
|
};
|
969
1392
|
|
970
|
-
const updateSupportInfoSupportUrl = (val: string | undefined): void => {
|
971
|
-
dataProduct_setSupportInfoIfAbsent(product);
|
972
|
-
if (product.supportInfo) {
|
973
|
-
supportInfo_setSupportUrl(product.supportInfo, val ?? '');
|
974
|
-
}
|
975
|
-
};
|
976
1393
|
useApplicationNavigationContext(
|
977
1394
|
LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.DATA_PRODUCT_EDITOR,
|
978
1395
|
);
|
@@ -995,84 +1412,6 @@ export const DataProductEditor = observer(() => {
|
|
995
1412
|
dataProductEditorState,
|
996
1413
|
]);
|
997
1414
|
|
998
|
-
const handleSupportInfoEmailAdd = (address: string, title: string): void => {
|
999
|
-
dataProduct_setSupportInfoIfAbsent(product);
|
1000
|
-
if (product.supportInfo) {
|
1001
|
-
supportInfo_addEmail(product.supportInfo, new Email(address, title));
|
1002
|
-
}
|
1003
|
-
};
|
1004
|
-
|
1005
|
-
const handleSupportInfoEmailRemove = (email: Email): void => {
|
1006
|
-
if (product.supportInfo) {
|
1007
|
-
supportInfo_deleteEmail(product.supportInfo, email);
|
1008
|
-
}
|
1009
|
-
};
|
1010
|
-
|
1011
|
-
const SupportEmailComponent = observer(
|
1012
|
-
(props: { item: Email }): React.ReactElement => {
|
1013
|
-
const { item } = props;
|
1014
|
-
|
1015
|
-
return (
|
1016
|
-
<div className="panel__content__form__section__list__item__rows">
|
1017
|
-
<div className="row">
|
1018
|
-
<label className="label">Address</label>
|
1019
|
-
<div className="textbox">{item.address}</div>
|
1020
|
-
</div>
|
1021
|
-
<div className="row">
|
1022
|
-
<label className="label">Title</label>
|
1023
|
-
<div className="textbox">{item.title}</div>
|
1024
|
-
</div>
|
1025
|
-
</div>
|
1026
|
-
);
|
1027
|
-
},
|
1028
|
-
);
|
1029
|
-
|
1030
|
-
const NewSupportEmailComponent = observer(
|
1031
|
-
(props: { onFinishEditing: () => void }) => {
|
1032
|
-
const { onFinishEditing } = props;
|
1033
|
-
const [address, setAddress] = useState('');
|
1034
|
-
const [title, setTitle] = useState('');
|
1035
|
-
|
1036
|
-
return (
|
1037
|
-
<div className="data-product-editor__support-info__new-email">
|
1038
|
-
<div className="panel__content__form__section__list__new-item__input">
|
1039
|
-
<input
|
1040
|
-
className="input input-group__input panel__content__form__section__input input--dark"
|
1041
|
-
type="email"
|
1042
|
-
placeholder="Enter email"
|
1043
|
-
value={address}
|
1044
|
-
onChange={(event) => {
|
1045
|
-
setAddress(event.target.value);
|
1046
|
-
}}
|
1047
|
-
/>
|
1048
|
-
</div>
|
1049
|
-
<div className="panel__content__form__section__list__new-item__input">
|
1050
|
-
<input
|
1051
|
-
className="input input-group__input panel__content__form__section__input input--dark"
|
1052
|
-
type="title"
|
1053
|
-
placeholder="Enter title"
|
1054
|
-
value={title}
|
1055
|
-
onChange={(event) => {
|
1056
|
-
setTitle(event.target.value);
|
1057
|
-
}}
|
1058
|
-
/>
|
1059
|
-
</div>
|
1060
|
-
<button
|
1061
|
-
className="panel__content__form__section__list__new-item__add-btn btn btn--dark"
|
1062
|
-
onClick={() => {
|
1063
|
-
handleSupportInfoEmailAdd(address, title);
|
1064
|
-
setAddress('');
|
1065
|
-
setTitle('');
|
1066
|
-
onFinishEditing();
|
1067
|
-
}}
|
1068
|
-
>
|
1069
|
-
Save
|
1070
|
-
</button>
|
1071
|
-
</div>
|
1072
|
-
);
|
1073
|
-
},
|
1074
|
-
);
|
1075
|
-
|
1076
1415
|
return (
|
1077
1416
|
<div className="data-product-editor">
|
1078
1417
|
<div className="panel">
|
@@ -1100,127 +1439,14 @@ export const DataProductEditor = observer(() => {
|
|
1100
1439
|
</div>
|
1101
1440
|
</PanelHeaderActions>
|
1102
1441
|
</div>
|
1103
|
-
<div className="panel" style={{ padding: '1rem', flex: 0 }}>
|
1104
|
-
<PanelFormTextField
|
1105
|
-
name="Title"
|
1106
|
-
value={product.title}
|
1107
|
-
prompt="Provide a title for this Lakehouse Data Product."
|
1108
|
-
update={updateDataProductTitle}
|
1109
|
-
placeholder="Enter title"
|
1110
|
-
/>
|
1111
|
-
<div style={{ margin: '1rem' }}>
|
1112
|
-
<div className="panel__content__form__section__header__label">
|
1113
|
-
Description
|
1114
|
-
</div>
|
1115
|
-
<div className="panel__content__form__section__header__prompt">
|
1116
|
-
Provide a description for this Lakehouse Data Product.
|
1117
|
-
</div>
|
1118
|
-
<textarea
|
1119
|
-
className="panel__content__form__section__textarea"
|
1120
|
-
spellCheck={false}
|
1121
|
-
disabled={isReadOnly}
|
1122
|
-
value={product.description}
|
1123
|
-
onChange={updateDataProductDescription}
|
1124
|
-
style={{
|
1125
|
-
padding: '0.5rem',
|
1126
|
-
width: '45rem',
|
1127
|
-
maxWidth: '45rem !important',
|
1128
|
-
}}
|
1129
|
-
/>
|
1130
|
-
</div>
|
1131
1442
|
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
<PanelFormTextField
|
1140
|
-
name="Documentation URL"
|
1141
|
-
value={product.supportInfo?.documentationUrl ?? ''}
|
1142
|
-
update={updateSupportInfoDocumentationUrl}
|
1143
|
-
placeholder="Enter Documentation URL"
|
1144
|
-
/>
|
1145
|
-
<PanelFormTextField
|
1146
|
-
name="Website"
|
1147
|
-
value={product.supportInfo?.website}
|
1148
|
-
update={updateSupportInfoWebsite}
|
1149
|
-
placeholder="Enter Website"
|
1150
|
-
/>
|
1151
|
-
<PanelFormTextField
|
1152
|
-
name="FAQ URL"
|
1153
|
-
value={product.supportInfo?.faqUrl}
|
1154
|
-
update={updateSupportInfoFaqUrl}
|
1155
|
-
placeholder="Enter FAQ URL"
|
1156
|
-
/>
|
1157
|
-
<PanelFormTextField
|
1158
|
-
name="Support URL"
|
1159
|
-
value={product.supportInfo?.supportUrl}
|
1160
|
-
update={updateSupportInfoSupportUrl}
|
1161
|
-
placeholder="Enter Support URL"
|
1162
|
-
/>
|
1163
|
-
<ListEditor
|
1164
|
-
title="Emails"
|
1165
|
-
items={product.supportInfo?.emails}
|
1166
|
-
keySelector={(email: Email) => email.address + email.title}
|
1167
|
-
ItemComponent={SupportEmailComponent}
|
1168
|
-
NewItemComponent={NewSupportEmailComponent}
|
1169
|
-
handleRemoveItem={handleSupportInfoEmailRemove}
|
1170
|
-
isReadOnly={isReadOnly}
|
1171
|
-
emptyMessage="No emails specified"
|
1172
|
-
/>
|
1173
|
-
</PanelFormSection>
|
1174
|
-
</div>
|
1175
|
-
<div className="panel" style={{ overflow: 'auto' }}>
|
1176
|
-
<PanelHeader>
|
1177
|
-
<div className="panel__header__title">
|
1178
|
-
<div className="panel__header__title__label">
|
1179
|
-
access point groups
|
1180
|
-
</div>
|
1181
|
-
</div>
|
1182
|
-
<PanelHeaderActions>
|
1183
|
-
<PanelHeaderActionItem
|
1184
|
-
className="panel__header__action"
|
1185
|
-
onClick={openNewModal}
|
1186
|
-
disabled={isReadOnly}
|
1187
|
-
title="Create new access point group"
|
1188
|
-
>
|
1189
|
-
<PlusIcon />
|
1190
|
-
</PanelHeaderActionItem>
|
1191
|
-
</PanelHeaderActions>
|
1192
|
-
</PanelHeader>
|
1193
|
-
<PanelContent>
|
1194
|
-
<div
|
1195
|
-
style={{ overflow: 'auto', margin: '1rem', marginLeft: '1.5rem' }}
|
1196
|
-
>
|
1197
|
-
{dataProductEditorState.accessPointGroupStates.map(
|
1198
|
-
(groupState) =>
|
1199
|
-
groupState.accessPointStates.length > 0 && (
|
1200
|
-
<AccessPointGroupSection
|
1201
|
-
key={groupState.uuid}
|
1202
|
-
groupState={groupState}
|
1203
|
-
isReadOnly={isReadOnly}
|
1204
|
-
/>
|
1205
|
-
),
|
1206
|
-
)}
|
1207
|
-
</div>
|
1208
|
-
{!accessPointStates.length && (
|
1209
|
-
<DataProductEditorSplashScreen
|
1210
|
-
dataProductEditorState={dataProductEditorState}
|
1211
|
-
/>
|
1212
|
-
)}
|
1213
|
-
</PanelContent>
|
1214
|
-
{dataProductEditorState.accessPointGroupModal && (
|
1215
|
-
<NewAccessPointGroupModal
|
1216
|
-
dataProductEditorState={dataProductEditorState}
|
1217
|
-
/>
|
1218
|
-
)}
|
1219
|
-
{dataProductEditorState.deployResponse && (
|
1220
|
-
<DataProductDeploymentResponseModal
|
1221
|
-
state={dataProductEditorState}
|
1222
|
-
/>
|
1223
|
-
)}
|
1443
|
+
<div
|
1444
|
+
className="panel"
|
1445
|
+
style={{ padding: '1rem', flexDirection: 'row' }}
|
1446
|
+
>
|
1447
|
+
<DataProductSidebar dataProductEditorState={dataProductEditorState} />
|
1448
|
+
|
1449
|
+
{renderActivivtyBarTab()}
|
1224
1450
|
</div>
|
1225
1451
|
</div>
|
1226
1452
|
</div>
|