@finos/legend-application-studio 28.19.14 → 28.19.16

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 (37) hide show
  1. package/lib/__lib__/LegendStudioTesting.d.ts +2 -1
  2. package/lib/__lib__/LegendStudioTesting.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioTesting.js +1 -0
  4. package/lib/__lib__/LegendStudioTesting.js.map +1 -1
  5. package/lib/application/LegendStudioApplicationConfig.d.ts +13 -0
  6. package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
  7. package/lib/application/LegendStudioApplicationConfig.js +22 -0
  8. package/lib/application/LegendStudioApplicationConfig.js.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.d.ts +8 -2
  10. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.d.ts.map +1 -1
  11. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js +379 -253
  12. package/lib/components/editor/editor-group/dataProduct/DataPoductEditor.js.map +1 -1
  13. package/lib/components/editor/side-bar/CreateNewElementModal.d.ts.map +1 -1
  14. package/lib/components/editor/side-bar/CreateNewElementModal.js +1 -2
  15. package/lib/components/editor/side-bar/CreateNewElementModal.js.map +1 -1
  16. package/lib/index.css +2 -2
  17. package/lib/index.css.map +1 -1
  18. package/lib/package.json +1 -1
  19. package/lib/stores/editor/NewElementState.d.ts.map +1 -1
  20. package/lib/stores/editor/NewElementState.js +6 -3
  21. package/lib/stores/editor/NewElementState.js.map +1 -1
  22. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +20 -9
  23. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  24. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +72 -24
  25. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  26. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts +4 -1
  27. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.d.ts.map +1 -1
  28. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js +10 -1
  29. package/lib/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js.map +1 -1
  30. package/package.json +9 -9
  31. package/src/__lib__/LegendStudioTesting.ts +1 -0
  32. package/src/application/LegendStudioApplicationConfig.ts +33 -0
  33. package/src/components/editor/editor-group/dataProduct/DataPoductEditor.tsx +971 -745
  34. package/src/components/editor/side-bar/CreateNewElementModal.tsx +0 -15
  35. package/src/stores/editor/NewElementState.ts +8 -1
  36. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +122 -26
  37. package/src/stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.ts +28 -2
@@ -16,20 +16,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  */
17
17
  import { observer } from 'mobx-react-lite';
18
18
  import { useEditorStore } from '../../EditorStoreProvider.js';
19
- import { DataProductEditorState, generateUrlToDeployOnOpen, LakehouseAccessPointState, } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
20
- import { clsx, LockIcon, PanelContent, PanelHeader, PanelHeaderActions, Dialog, PanelDivider, InputWithInlineValidation, useResizeDetector, AccessPointIcon, TimesIcon, PlusIcon, PanelHeaderActionItem, RocketIcon, ListEditor, Modal, ModalHeader, ModalTitle, ModalBody, ModalFooter, ModalFooterButton, PencilEditIcon, PanelFormTextField, ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, WarningIcon, PanelFormSection, } from '@finos/legend-art';
21
- import React, { useRef, useState, useEffect, } from 'react';
19
+ import { DATA_PRODUCT_TAB, DataProductEditorState, generateUrlToDeployOnOpen, LakehouseAccessPointState, } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
20
+ import { clsx, LockIcon, PanelContent, PanelHeader, PanelHeaderActions, Dialog, TimesIcon, PlusIcon, PanelHeaderActionItem, RocketIcon, ListEditor, Modal, ModalHeader, ModalTitle, ModalBody, ModalFooter, ModalFooterButton, PencilEditIcon, PanelFormTextField, ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, WarningIcon, PanelFormSection, useDragPreviewLayer, DragPreviewLayer, PanelDnDEntry, PanelEntryDragHandle, HomeIcon, QuestionCircleIcon, ErrorWarnIcon, GroupWorkIcon, CustomSelectorInput, Switch, BuildingIcon, Tooltip, InfoCircleIcon, } from '@finos/legend-art';
21
+ import React, { useRef, useState, useEffect, useCallback, } from 'react';
22
22
  import { filterByType } from '@finos/legend-shared';
23
23
  import { InlineLambdaEditor } from '@finos/legend-query-builder';
24
24
  import { action, flowResult } from 'mobx';
25
25
  import { useAuth } from 'react-oidc-context';
26
26
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
27
27
  import { CodeEditor } from '@finos/legend-lego/code-editor';
28
- import { LakehouseTargetEnv, Email } from '@finos/legend-graph';
29
- import { accessPointGroup_setDescription, accessPointGroup_setName, dataProduct_setDescription, dataProduct_setSupportInfoIfAbsent, dataProduct_setTitle, supportInfo_setDocumentationUrl, supportInfo_setWebsite, supportInfo_setFaqUrl, supportInfo_setSupportUrl, supportInfo_addEmail, supportInfo_deleteEmail, } from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
28
+ import { LakehouseTargetEnv, Email, StereotypeExplicitReference, } from '@finos/legend-graph';
29
+ import { accessPointGroup_setDescription, accessPointGroup_setName, dataProduct_setDescription, dataProduct_setSupportInfoIfAbsent, dataProduct_setTitle, supportInfo_setDocumentationUrl, supportInfo_setWebsite, supportInfo_setFaqUrl, supportInfo_setSupportUrl, supportInfo_addEmail, supportInfo_deleteEmail, accessPoint_setClassification, } from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
30
30
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
31
31
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
32
32
  import { ActionAlertActionType, ActionAlertType, useApplicationNavigationContext, } from '@finos/legend-application';
33
+ import { useDrag, useDrop } from 'react-dnd';
34
+ import { annotatedElement_addStereotype, annotatedElement_deleteStereotype, } from '../../../../stores/graph-modifier/DomainGraphModifierHelper.js';
33
35
  export var AP_GROUP_MODAL_ERRORS;
34
36
  (function (AP_GROUP_MODAL_ERRORS) {
35
37
  AP_GROUP_MODAL_ERRORS["GROUP_NAME_EMPTY"] = "Group Name is empty";
@@ -39,166 +41,99 @@ export var AP_GROUP_MODAL_ERRORS;
39
41
  AP_GROUP_MODAL_ERRORS["AP_NAME_EXISTS"] = "Access Point Name already exists";
40
42
  AP_GROUP_MODAL_ERRORS["AP_DESCRIPTION_EMPTY"] = "Access Point Description is empty";
41
43
  })(AP_GROUP_MODAL_ERRORS || (AP_GROUP_MODAL_ERRORS = {}));
42
- export const AP_EMPTY_DESC_WARNING = 'Describe the data this access point produces';
43
- const NewAccessPointAccessPoint = observer((props) => {
44
- const { dataProductEditorState: dataProductEditorState } = props;
45
- const accessPointInputRef = useRef(null);
46
- const [id, setId] = useState(undefined);
47
- const handleIdChange = (event) => setId(event.target.value);
48
- const [description, setDescription] = useState(undefined);
49
- const handleDescriptionChange = (event) => setDescription(event.target.value);
50
- const handleClose = () => {
51
- dataProductEditorState.setAccessPointModal(false);
52
- };
53
- const handleSubmit = () => {
54
- if (id) {
55
- const accessPointGroup = dataProductEditorState.editingGroupState ?? 'default';
56
- dataProductEditorState.addAccessPoint(id, description, accessPointGroup);
57
- handleClose();
44
+ export const AP_EMPTY_DESC_WARNING = 'Click here to describe the data this access point produces';
45
+ const AP_DND_TYPE = 'ACCESS_POINT';
46
+ const AP_GROUP_DND_TYPE = 'ACCESS_POINT_GROUP';
47
+ const newNamePlaceholder = '';
48
+ const HoverTextArea = ({ text: text, handleMouseOver, handleMouseOut, className, }) => {
49
+ return (_jsx("div", { onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, className: clsx(className), children: text }));
50
+ };
51
+ const hoverIcon = () => {
52
+ return (_jsx("div", { "data-testid": LEGEND_STUDIO_TEST_ID.HOVER_EDIT_ICON, children: _jsx(PencilEditIcon, {}) }));
53
+ };
54
+ const AccessPointTitle = observer((props) => {
55
+ const { accessPoint } = props;
56
+ const [editingName, setEditingName] = useState(accessPoint.id === newNamePlaceholder);
57
+ const handleNameEdit = () => setEditingName(true);
58
+ const handleNameBlur = () => {
59
+ if (accessPoint.id !== newNamePlaceholder) {
60
+ setEditingName(false);
58
61
  }
59
62
  };
60
- const handleEnter = () => {
61
- accessPointInputRef.current?.focus();
62
- };
63
- const disableCreateButton = id === '' ||
64
- id === undefined ||
65
- description === '' ||
66
- description === undefined ||
67
- dataProductEditorState.accessPoints.map((e) => e.id).includes(id);
68
- const nameErrors = id === ''
69
- ? AP_GROUP_MODAL_ERRORS.AP_NAME_EMPTY
70
- : dataProductEditorState.accessPoints
71
- .map((e) => e.id)
72
- .includes(id ?? '')
73
- ? AP_GROUP_MODAL_ERRORS.AP_NAME_EXISTS
74
- : undefined;
75
- const descriptionErrors = description === ''
76
- ? AP_GROUP_MODAL_ERRORS.AP_DESCRIPTION_EMPTY
77
- : undefined;
78
- return (_jsx(Dialog, { open: true, onClose: handleClose, TransitionProps: {
79
- onEnter: handleEnter,
80
- }, classes: {
81
- container: 'search-modal__container',
82
- }, PaperProps: {
83
- classes: {
84
- root: 'search-modal__inner-container',
85
- },
86
- }, children: _jsxs("form", { onSubmit: (event) => {
87
- event.preventDefault();
88
- handleSubmit();
89
- }, className: clsx('modal search-modal', {
90
- 'modal--dark': true,
91
- }), style: {
92
- display: 'flex',
93
- flexDirection: 'column',
94
- gap: '1rem',
95
- }, children: [_jsx("div", { className: "modal__title", children: "New Access Point" }), _jsxs("div", { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Name" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
96
- 'input--dark': true,
97
- }), ref: accessPointInputRef, spellCheck: false, value: id ?? '', onChange: handleIdChange, placeholder: "Access Point Name", error: nameErrors })] }), _jsxs("div", { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Description" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
98
- 'input--dark': true,
99
- }), spellCheck: false, value: description ?? '', onChange: handleDescriptionChange, placeholder: "Access Point Description", error: descriptionErrors })] }), _jsx("div", {}), _jsx(PanelDivider, {}), _jsx("div", { className: "search-modal__actions", children: _jsx("button", { className: clsx('btn btn--primary', {
100
- 'btn--dark': true,
101
- }), disabled: disableCreateButton, children: "Create" }) })] }) }));
63
+ const updateAccessPointName = action((event) => {
64
+ if (!event.target.value.includes(' ')) {
65
+ accessPoint.id = event.target.value;
66
+ }
67
+ });
68
+ return editingName ? (_jsx("textarea", { className: "access-point-editor__name", spellCheck: false, value: accessPoint.id, onChange: updateAccessPointName, placeholder: 'Access Point Name', onBlur: handleNameBlur, style: {
69
+ borderColor: accessPoint.id === newNamePlaceholder
70
+ ? 'var(--color-red-300)'
71
+ : 'transparent',
72
+ } })) : (_jsx("div", { onClick: handleNameEdit, title: "Click to edit access point title", children: _jsx("div", { className: "access-point-editor__name__label", children: accessPoint.id }) }));
102
73
  });
103
- const NewAccessPointGroupModal = observer((props) => {
104
- const { dataProductEditorState: dataProductEditorState } = props;
105
- const accessPointGroupInputRef = useRef(null);
106
- const [groupName, setGroupName] = useState(undefined);
107
- const handleGroupNameChange = (event) => setGroupName(event.target.value);
108
- const [groupDescription, setGroupDescription] = useState(undefined);
109
- const handleGroupDescriptionChange = (event) => setGroupDescription(event.target.value);
110
- const [apName, setApName] = useState(undefined);
111
- const handleApNameChange = (event) => setApName(event.target.value);
112
- const [apDescription, setApDescription] = useState(undefined);
113
- const handleApDescriptionChange = (event) => setApDescription(event.target.value);
114
- const handleClose = () => {
115
- dataProductEditorState.setAccessPointGroupModal(false);
116
- };
117
- const handleEnter = () => {
118
- accessPointGroupInputRef.current?.focus();
74
+ const AccessPointClassification = observer((props) => {
75
+ const { accessPoint, groupState } = props;
76
+ const applicationStore = useEditorStore().applicationStore;
77
+ const CHOOSE_CLASSIFICATION = 'Choose Classification';
78
+ const updateAccessPointClassificationTextbox = action((event) => {
79
+ accessPoint.classification = event.target.value;
80
+ });
81
+ const conditionalClassifications = () => {
82
+ if (groupState.containsPublicStereotype) {
83
+ return (applicationStore.config.options.dataProductConfig
84
+ ?.publicClassifications ?? []);
85
+ }
86
+ else {
87
+ return (applicationStore.config.options.dataProductConfig?.classifications ??
88
+ []);
89
+ }
119
90
  };
120
- const groupNameErrors = groupName === ''
121
- ? AP_GROUP_MODAL_ERRORS.GROUP_NAME_EMPTY
122
- : dataProductEditorState.accessPointGroupStates
123
- .map((e) => e.value.id)
124
- .includes(groupName ?? '')
125
- ? AP_GROUP_MODAL_ERRORS.GROUP_NAME_EXISTS
126
- : undefined;
127
- const groupDescriptionErrors = groupDescription === ''
128
- ? AP_GROUP_MODAL_ERRORS.GROUP_DESCRIPTION_EMPTY
129
- : undefined;
130
- const apNameErrors = apName === ''
131
- ? AP_GROUP_MODAL_ERRORS.AP_NAME_EMPTY
132
- : dataProductEditorState.accessPoints
133
- .map((e) => e.id)
134
- .includes(apName ?? '')
135
- ? AP_GROUP_MODAL_ERRORS.AP_NAME_EXISTS
136
- : undefined;
137
- const apDescriptionErrors = apDescription === ''
138
- ? AP_GROUP_MODAL_ERRORS.AP_DESCRIPTION_EMPTY
139
- : undefined;
140
- const disableCreateButton = !groupName ||
141
- !groupDescription ||
142
- !apName ||
143
- !apDescription ||
144
- Boolean(groupNameErrors ??
145
- groupDescriptionErrors ??
146
- apNameErrors ??
147
- apDescriptionErrors);
148
- const handleSubmit = () => {
149
- if (!disableCreateButton && apName) {
150
- const createdGroup = dataProductEditorState.createGroupAndAdd(groupName, groupDescription);
151
- dataProductEditorState.addAccessPoint(apName, apDescription, createdGroup);
152
- handleClose();
91
+ const classificationOptions = [CHOOSE_CLASSIFICATION]
92
+ .concat(conditionalClassifications())
93
+ .map((classfication) => ({
94
+ label: classfication,
95
+ value: classfication,
96
+ }));
97
+ const updateAccessPointClassificationFromDropdown = action((val) => {
98
+ accessPoint_setClassification(accessPoint, val?.value === CHOOSE_CLASSIFICATION ? undefined : val?.value);
99
+ });
100
+ const currentClassification = accessPoint.classification !== undefined
101
+ ? {
102
+ label: accessPoint.classification,
103
+ value: accessPoint.classification,
104
+ }
105
+ : {
106
+ label: CHOOSE_CLASSIFICATION,
107
+ value: CHOOSE_CLASSIFICATION,
108
+ };
109
+ const classificationDocumentationLink = () => {
110
+ const docLink = applicationStore.config.options.dataProductConfig?.classificationDoc;
111
+ if (docLink) {
112
+ applicationStore.navigationService.navigator.visitAddress(docLink);
153
113
  }
154
114
  };
155
- return (_jsx(Dialog, { open: true, onClose: handleClose, TransitionProps: {
156
- onEnter: handleEnter,
157
- }, classes: {
158
- container: 'search-modal__container',
159
- }, PaperProps: {
160
- classes: {
161
- root: 'search-modal__inner-container',
162
- },
163
- }, children: _jsxs("form", { onSubmit: (event) => {
164
- event.preventDefault();
165
- handleSubmit();
166
- }, className: clsx('modal search-modal', {
167
- 'modal--dark': true,
168
- }), style: {
169
- display: 'flex',
170
- flexDirection: 'column',
171
- gap: '1rem',
172
- }, children: [_jsx("div", { className: "modal__title", children: "New Access Point Group" }), _jsxs("div", { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Group Name" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
173
- 'input--dark': true,
174
- }), ref: accessPointGroupInputRef, spellCheck: false, value: groupName ?? '', onChange: handleGroupNameChange, placeholder: "Access Point Group Name", error: groupNameErrors })] }), _jsxs("div", { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Group Description" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
175
- 'input--dark': true,
176
- }), spellCheck: false, value: groupDescription ?? '', onChange: handleGroupDescriptionChange, placeholder: "Access Point Group Description", error: groupDescriptionErrors })] }), _jsxs("div", { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Access Point" }), _jsxs("div", { className: "new-access-point-group-modal", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Name" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
177
- 'input--dark': true,
178
- }), spellCheck: false, value: apName ?? '', onChange: handleApNameChange, placeholder: "Access Point Name", error: apNameErrors }), _jsx("div", { className: "panel__content__form__section__header__label", children: "Description" }), _jsx(InputWithInlineValidation, { className: clsx('input new-access-point-modal__id-input', {
179
- 'input--dark': true,
180
- }), spellCheck: false, value: apDescription ?? '', onChange: handleApDescriptionChange, placeholder: "Access Point Description", error: apDescriptionErrors })] })] }), _jsx(PanelDivider, {}), _jsx("div", { className: "search-modal__actions", children: _jsx("button", { className: clsx('btn btn--primary', {
181
- 'btn--dark': true,
182
- }), disabled: disableCreateButton, children: "Create" }) })] }) }));
115
+ return (_jsxs("div", { className: "access-point-editor__classification", children: [classificationOptions.length > 1 ? (_jsx("div", { children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: classificationOptions, onChange: updateAccessPointClassificationFromDropdown, value: currentClassification, darkMode: !applicationStore.layoutService
116
+ .TEMPORARY__isLightColorThemeEnabled }) })) : (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.classification ?? '', onChange: updateAccessPointClassificationTextbox, placeholder: "Add classification", style: {
117
+ overflow: 'hidden',
118
+ width: '125px',
119
+ resize: 'none',
120
+ padding: '0.25rem',
121
+ } })), _jsx(Tooltip, { title: "Learn more about data classification scheme here.", arrow: true, placement: 'top', children: _jsx("button", { onClick: classificationDocumentationLink, children: _jsx(InfoCircleIcon, {}) }) })] }));
183
122
  });
184
- const HoverTextArea = ({ text: text, handleMouseOver, handleMouseOut, className, }) => {
185
- return (_jsx("div", { onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, className: clsx(className), children: text }));
186
- };
187
- const hoverIcon = () => {
188
- return (_jsx("div", { "data-testid": LEGEND_STUDIO_TEST_ID.HOVER_EDIT_ICON, children: _jsx(PencilEditIcon, {}) }));
189
- };
190
123
  export const LakehouseDataProductAcccessPointEditor = observer((props) => {
191
124
  const { accessPointState } = props;
125
+ const editorStore = useEditorStore();
192
126
  const accessPoint = accessPointState.accessPoint;
193
- const productEditorState = accessPointState.state;
127
+ const groupState = accessPointState.state;
194
128
  const lambdaEditorState = accessPointState.lambdaState;
195
- const propertyHasParserError = productEditorState.accessPointStates
129
+ const propertyHasParserError = groupState.accessPointStates
196
130
  .filter(filterByType(LakehouseAccessPointState))
197
131
  .find((pm) => pm.lambdaState.parserError);
198
132
  const [editingDescription, setEditingDescription] = useState(false);
199
133
  const [isHovering, setIsHovering] = useState(false);
200
- const handleEdit = () => setEditingDescription(true);
201
- const handleBlur = () => {
134
+ const ref = useRef(null);
135
+ const handleDescriptionEdit = () => setEditingDescription(true);
136
+ const handleDescriptionBlur = () => {
202
137
  setEditingDescription(false);
203
138
  setIsHovering(false);
204
139
  };
@@ -214,37 +149,73 @@ export const LakehouseDataProductAcccessPointEditor = observer((props) => {
214
149
  const updateAccessPointTargetEnvironment = action((targetEnvironment) => {
215
150
  accessPoint.targetEnvironment = targetEnvironment;
216
151
  });
217
- return (_jsxs("div", { className: clsx('access-point-editor', {
218
- backdrop__element: propertyHasParserError,
219
- }), children: [_jsxs("div", { className: "access-point-editor__metadata", children: [_jsx("div", { className: clsx('access-point-editor__name', {}), children: _jsx("div", { className: "access-point-editor__name__label", children: accessPoint.id }) }), editingDescription ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.description ?? '', onChange: updateAccessPointDescription, placeholder: "Access Point description", onBlur: handleBlur, style: {
220
- overflow: 'hidden',
221
- resize: 'none',
222
- padding: '0.25rem',
223
- } })) : (_jsxs("div", { onClick: handleEdit, title: "Click to edit access point description", className: "access-point-editor__description-container", children: [accessPoint.description ? (_jsx(HoverTextArea, { text: accessPoint.description, handleMouseOver: handleMouseOver, handleMouseOut: handleMouseOut })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, children: [_jsx(WarningIcon, {}), AP_EMPTY_DESC_WARNING] })), isHovering && hoverIcon()] })), _jsx("div", { className: "access-point-editor__info", children: _jsxs("div", { className: clsx('access-point-editor__type'), title: 'Change target environment', children: [_jsx("div", { className: "access-point-editor__type__label", children: accessPoint.targetEnvironment }), _jsx("div", { style: {
224
- background: 'transparent',
225
- height: '100%',
226
- alignItems: 'center',
227
- display: 'flex',
228
- }, children: _jsx(ControlledDropdownMenu, { className: "access-point-editor__dropdown", content: _jsx(MenuContent, { children: Object.values(LakehouseTargetEnv).map((environment) => (_jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: () => updateAccessPointTargetEnvironment(environment), children: environment }, environment))) }), menuProps: {
229
- anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
230
- transformOrigin: { vertical: 'top', horizontal: 'right' },
231
- }, children: _jsx(CaretDownIcon, {}) }) })] }) })] }), _jsx("div", { className: "access-point-editor__content", children: _jsxs("div", { className: "access-point-editor__generic-entry", children: [_jsx("div", { className: "access-point-editor__entry__container", children: _jsx("div", { className: "access-point-editor__entry", children: _jsx(InlineLambdaEditor, { className: 'access-point-editor__lambda-editor', disabled: lambdaEditorState.val.state.state
232
- .isConvertingTransformLambdaObjects, lambdaEditorState: lambdaEditorState, forceBackdrop: Boolean(lambdaEditorState.parserError) }) }) }), _jsx("button", { className: "access-point-editor__generic-entry__remove-btn", onClick: () => {
233
- productEditorState.deleteAccessPoint(accessPointState);
234
- }, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] }) })] }));
235
- });
236
- const DataProductEditorSplashScreen = observer((props) => {
237
- const { dataProductEditorState } = props;
238
- const logoWidth = 280;
239
- const logoHeight = 270;
240
- const [showLogo, setShowLogo] = useState(false);
241
- const { ref, height, width } = useResizeDetector();
242
- useEffect(() => {
243
- setShowLogo((width ?? 0) > logoWidth && (height ?? 0) > logoHeight);
244
- }, [height, width]);
245
- return (_jsxs("div", { ref: ref, className: "data-product-editor__splash-screen", children: [_jsx("div", { onClick: () => dataProductEditorState.setAccessPointGroupModal(true), className: "data-product-editor__splash-screen__label", children: "Add Access Point Group" }), _jsx("div", { className: "data-product-editor__splash-screen__spacing" }), _jsx("div", { onClick: () => dataProductEditorState.setAccessPointGroupModal(true), title: "Add new Access Point Group", className: clsx('data-product-editor__splash-screen__logo', {
246
- 'data-product-editor__splash-screen__logo--hidden': !showLogo,
247
- }), children: _jsx(AccessPointIcon, {}) })] }));
152
+ const handleRemoveAccessPoint = () => {
153
+ editorStore.applicationStore.alertService.setActionAlertInfo({
154
+ message: `Are you sure you want to delete Access Point ${accessPoint.id}?`,
155
+ type: ActionAlertType.CAUTION,
156
+ actions: [
157
+ {
158
+ label: 'Confirm',
159
+ type: ActionAlertActionType.PROCEED_WITH_CAUTION,
160
+ handler: () => {
161
+ groupState.deleteAccessPoint(accessPointState);
162
+ },
163
+ },
164
+ {
165
+ label: 'Cancel',
166
+ type: ActionAlertActionType.PROCEED,
167
+ default: true,
168
+ },
169
+ ],
170
+ });
171
+ };
172
+ //Drag and drop - reorder access points/move between groups
173
+ const handleHover = useCallback((item) => {
174
+ const draggingProperty = item.accessPointState;
175
+ const hoveredProperty = accessPointState;
176
+ groupState.swapAccessPoints(draggingProperty, hoveredProperty);
177
+ }, [accessPointState, groupState]);
178
+ const [{ isBeingDraggedAP }, dropConnector] = useDrop(() => ({
179
+ accept: [AP_DND_TYPE],
180
+ hover: (item) => handleHover(item),
181
+ collect: (monitor) => ({
182
+ isBeingDraggedAP: monitor.getItem()
183
+ ?.accessPointState,
184
+ }),
185
+ }), [handleHover]);
186
+ const isBeingDragged = accessPoint === isBeingDraggedAP?.accessPoint;
187
+ const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
188
+ type: AP_DND_TYPE,
189
+ item: () => ({
190
+ accessPointState: accessPointState,
191
+ }),
192
+ collect: (monitor) => ({
193
+ isDragging: monitor.isDragging(),
194
+ }),
195
+ }), [accessPointState]);
196
+ dragConnector(ref);
197
+ dropConnector(ref);
198
+ useDragPreviewLayer(dragPreviewConnector);
199
+ return (_jsx(PanelDnDEntry, { ref: ref, placeholder: _jsx("div", { className: "dnd__placeholder--light" }), showPlaceholder: isBeingDragged, children: _jsxs("div", { className: clsx('access-point-editor', {
200
+ backdrop__element: propertyHasParserError,
201
+ }), children: [_jsx(PanelEntryDragHandle, { dragSourceConnector: ref, isDragging: isBeingDragged, title: 'Drag this Access Point to another group', className: "access-point-editor__dnd-handle" }), _jsxs("div", { style: { flex: 1 }, children: [_jsxs("div", { className: "access-point-editor__metadata", children: [_jsx(AccessPointTitle, { accessPoint: accessPoint }), editingDescription ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.description ?? '', onChange: updateAccessPointDescription, placeholder: "Access Point description", onBlur: handleDescriptionBlur, style: {
202
+ overflow: 'hidden',
203
+ resize: 'none',
204
+ padding: '0.25rem',
205
+ } })) : (_jsxs("div", { onClick: handleDescriptionEdit, title: "Click to edit access point description", className: "access-point-editor__description-container", children: [accessPoint.description ? (_jsx(HoverTextArea, { text: accessPoint.description, handleMouseOver: handleMouseOver, handleMouseOut: handleMouseOut })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, children: [_jsx(WarningIcon, {}), AP_EMPTY_DESC_WARNING] })), isHovering && hoverIcon()] })), _jsxs("div", { className: "access-point-editor__info", children: [editorStore.applicationStore.config.options
206
+ .dataProductConfig && (_jsx(AccessPointClassification, { accessPoint: accessPoint, groupState: groupState })), _jsxs("div", { className: clsx('access-point-editor__type'), title: 'Change target environment', children: [_jsx("div", { className: "access-point-editor__type__label", children: accessPoint.targetEnvironment }), _jsx(ControlledDropdownMenu, { className: "access-point-editor__dropdown", content: _jsx(MenuContent, { children: Object.values(LakehouseTargetEnv).map((environment) => (_jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: () => updateAccessPointTargetEnvironment(environment), children: environment }, environment))) }), menuProps: {
207
+ anchorOrigin: {
208
+ vertical: 'bottom',
209
+ horizontal: 'right',
210
+ },
211
+ transformOrigin: {
212
+ vertical: 'top',
213
+ horizontal: 'right',
214
+ },
215
+ }, children: _jsx(CaretDownIcon, {}) })] })] })] }), _jsx("div", { className: "access-point-editor__content", children: _jsxs("div", { className: "access-point-editor__generic-entry", children: [_jsx("div", { className: "access-point-editor__entry__container", children: _jsx("div", { className: "access-point-editor__entry", children: _jsx(InlineLambdaEditor, { className: 'access-point-editor__lambda-editor', disabled: lambdaEditorState.val.state.state
216
+ .isConvertingTransformLambdaObjects, lambdaEditorState: lambdaEditorState, forceBackdrop: Boolean(lambdaEditorState.parserError) }) }) }), _jsx("button", { className: "access-point-editor__generic-entry__remove-btn", onClick: () => {
217
+ handleRemoveAccessPoint();
218
+ }, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] }) })] })] }) }));
248
219
  });
249
220
  const DataProductDeploymentResponseModal = observer((props) => {
250
221
  const { state } = props;
@@ -256,13 +227,32 @@ const DataProductDeploymentResponseModal = observer((props) => {
256
227
  paper: 'editor-modal__content',
257
228
  }, onClose: closeModal, children: _jsxs(Modal, { darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled, className: "editor-modal", children: [_jsx(ModalHeader, { children: _jsx(ModalTitle, { title: "Data Product Deployment Response" }) }), _jsx(ModalBody, { children: _jsx(PanelContent, { children: _jsx(CodeEditor, { inputValue: JSON.stringify(state.deployResponse?.content ?? {}, null, 2), isReadOnly: true, language: CODE_EDITOR_LANGUAGE.JSON }) }) }), _jsx(ModalFooter, { children: _jsx(ModalFooterButton, { onClick: closeModal, text: "Close", type: "secondary" }) })] }) }));
258
229
  });
259
- const AccessPointGroupSection = observer((props) => {
230
+ const AccessPointGroupPublicToggle = observer((props) => {
231
+ const { groupState } = props;
232
+ const handleSwitchChange = (event) => {
233
+ const isChecked = event.target.checked;
234
+ if (isChecked && groupState.publicStereotype) {
235
+ annotatedElement_addStereotype(groupState.value, StereotypeExplicitReference.create(groupState.publicStereotype));
236
+ }
237
+ else if (groupState.containsPublicStereotype) {
238
+ annotatedElement_deleteStereotype(groupState.value, groupState.containsPublicStereotype);
239
+ }
240
+ };
241
+ return (_jsxs("div", { className: "access-point-editor__toggle", children: [_jsx(Switch, { checked: Boolean(groupState.containsPublicStereotype), onChange: handleSwitchChange, sx: {
242
+ '& .MuiSwitch-track': {
243
+ backgroundColor: groupState.containsPublicStereotype
244
+ ? 'default'
245
+ : 'var(--color-light-grey-400)',
246
+ },
247
+ } }), _jsx(BuildingIcon, {}), "Enterprise Data. Anyone at the firm can access this without approvals."] }));
248
+ });
249
+ const AccessPointGroupEditor = observer((props) => {
260
250
  const { groupState, isReadOnly } = props;
261
251
  const editorStore = useEditorStore();
262
252
  const productEditorState = groupState.state;
263
253
  const [editingDescription, setEditingDescription] = useState(false);
264
254
  const [isHoveringDescription, setIsHoveringDescription] = useState(false);
265
- const [editingName, setEditingName] = useState(false);
255
+ const [editingName, setEditingName] = useState(groupState.value.id === newNamePlaceholder);
266
256
  const [isHoveringName, setIsHoveringName] = useState(false);
267
257
  const handleDescriptionEdit = () => setEditingDescription(true);
268
258
  const handleDescriptionBlur = () => {
@@ -280,8 +270,10 @@ const AccessPointGroupSection = observer((props) => {
280
270
  };
281
271
  const handleNameEdit = () => setEditingName(true);
282
272
  const handleNameBlur = () => {
283
- setEditingName(false);
284
- setIsHoveringName(false);
273
+ if (groupState.value.id !== newNamePlaceholder) {
274
+ setEditingName(false);
275
+ setIsHoveringName(false);
276
+ }
285
277
  };
286
278
  const handleMouseOverName = () => {
287
279
  setIsHoveringName(true);
@@ -290,87 +282,184 @@ const AccessPointGroupSection = observer((props) => {
290
282
  setIsHoveringName(false);
291
283
  };
292
284
  const updateGroupName = (val) => {
293
- if (val) {
285
+ if (val && !val.includes(' ')) {
294
286
  accessPointGroup_setName(groupState.value, val);
295
287
  }
296
288
  };
297
289
  const handleRemoveAccessPointGroup = () => {
298
290
  editorStore.applicationStore.alertService.setActionAlertInfo({
299
- message: `Deleting access point group ${groupState.value.id} will permanently remove it and all associated access points. Are you sure you want to proceed?`,
291
+ message: `Are you sure you want to delete Access Point Group ${groupState.value.id} and all of its Access Points?`,
300
292
  type: ActionAlertType.CAUTION,
301
293
  actions: [
302
294
  {
303
- label: 'Cancel',
304
- type: ActionAlertActionType.PROCEED,
305
- default: true,
306
- },
307
- {
308
- label: 'Proceed',
295
+ label: 'Confirm',
309
296
  type: ActionAlertActionType.PROCEED_WITH_CAUTION,
310
297
  handler: () => {
311
298
  productEditorState.deleteAccessPointGroup(groupState);
312
299
  },
313
300
  },
301
+ {
302
+ label: 'Cancel',
303
+ type: ActionAlertActionType.PROCEED,
304
+ default: true,
305
+ },
314
306
  ],
315
307
  });
316
308
  };
317
- const openNewModal = () => {
318
- productEditorState.setEditingGroupState(groupState);
319
- productEditorState.setAccessPointModal(true);
309
+ const handleAddAccessPoint = () => {
310
+ productEditorState.addAccessPoint(newNamePlaceholder, undefined, productEditorState.selectedGroupState ?? groupState);
320
311
  };
321
- return (_jsxs("div", { className: "access-point-editor__group-container", children: [_jsxs("div", { className: "access-point-editor__group-container__title-editor", children: [editingName ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: groupState.value.id, onChange: (event) => updateGroupName(event.target.value), placeholder: "Access Point Group Name", onBlur: handleNameBlur, style: {
312
+ return (_jsxs("div", { className: "access-point-editor__group-container", "data-testid": LEGEND_STUDIO_TEST_ID.ACCESS_POINT_GROUP_EDITOR, children: [_jsxs("div", { className: "access-point-editor__group-container__title-editor", children: [editingName ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: groupState.value.id, onChange: (event) => updateGroupName(event.target.value), placeholder: "Access Point Group Name", onBlur: handleNameBlur, style: {
322
313
  overflow: 'hidden',
323
314
  resize: 'none',
324
315
  padding: '0.25rem',
316
+ borderColor: groupState.value.id === newNamePlaceholder
317
+ ? 'var(--color-red-300)'
318
+ : 'transparent',
319
+ borderWidth: 'thin',
325
320
  } })) : (_jsxs("div", { onClick: handleNameEdit, title: "Click to edit group name", className: "access-point-editor__group-container__title", children: [_jsx(HoverTextArea, { text: groupState.value.id, handleMouseOver: handleMouseOverName, handleMouseOut: handleMouseOutName, className: "access-point-editor__group-container__title" }), isHoveringName && hoverIcon()] })), _jsx("button", { className: "access-point-editor__generic-entry__remove-btn--group", onClick: () => {
326
- // productEditorState.deleteAccessPointGroup(groupState);
327
321
  handleRemoveAccessPointGroup();
328
322
  }, tabIndex: -1, title: "Remove Access Point Group", children: _jsx(TimesIcon, {}) })] }), _jsx("div", { className: "access-point-editor__group-container__description-editor", children: editingDescription ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: groupState.value.description ?? '', onChange: (event) => updateGroupDescription(event.target.value), placeholder: "Provide a description for this Access Point Group", onBlur: handleDescriptionBlur, style: {
329
323
  overflow: 'hidden',
330
324
  resize: 'none',
331
325
  padding: '0.25rem',
332
- } })) : (_jsxs("div", { onClick: handleDescriptionEdit, title: "Click to edit group description", className: "access-point-editor__description-container", children: [groupState.value.description ? (_jsx(HoverTextArea, { text: groupState.value.description, handleMouseOver: handleMouseOverDescription, handleMouseOut: handleMouseOutDescription, className: "access-point-editor__group-container__description" })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOverDescription, onMouseOut: handleMouseOutDescription, children: [_jsx(WarningIcon, {}), "Describe this access point group to clarify what users are requesting access to. Entitlements are provisioned at the group level."] })), isHoveringDescription && hoverIcon()] })) }), _jsxs(PanelHeader, { className: "panel__header--access-point", children: [_jsx("div", { className: "panel__header__title", children: "Access Points" }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { className: "panel__header__action", onClick: openNewModal, disabled: isReadOnly, title: "Create new access point", children: _jsx(PlusIcon, {}) }) })] }), groupState.accessPointStates
333
- .filter(filterByType(LakehouseAccessPointState))
334
- .map((apState) => (_jsx(LakehouseDataProductAcccessPointEditor, { isReadOnly: isReadOnly, accessPointState: apState }, apState.accessPoint.id))), productEditorState.accessPointModal && (_jsx(NewAccessPointAccessPoint, { dataProductEditorState: productEditorState }))] }));
326
+ } })) : (_jsxs("div", { onClick: handleDescriptionEdit, title: "Click to edit group description", className: "access-point-editor__description-container", children: [groupState.value.description ? (_jsx(HoverTextArea, { text: groupState.value.description, handleMouseOver: handleMouseOverDescription, handleMouseOut: handleMouseOutDescription, className: "access-point-editor__group-container__description" })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOverDescription, onMouseOut: handleMouseOutDescription, children: [_jsx(WarningIcon, {}), "Users request access at the access point group level. Click here to add a meaningful description to guide users."] })), isHoveringDescription && hoverIcon()] })) }), editorStore.applicationStore.config.options.dataProductConfig && (_jsx(AccessPointGroupPublicToggle, { groupState: groupState })), _jsxs(PanelHeader, { className: "panel__header--access-point", children: [_jsx("div", { className: "panel__header__title", children: "Access Points" }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { className: "panel__header__action", onClick: handleAddAccessPoint, disabled: isReadOnly, title: "Create new access point", children: _jsx(PlusIcon, {}) }) })] }), groupState.accessPointStates.length === 0 && (_jsxs("div", { className: "access-point-editor__group-container__description--warning", children: [_jsx(WarningIcon, {}), "This group needs at least one access point defined."] })), _jsx("div", { style: { gap: '1rem', display: 'flex', flexDirection: 'column' }, children: groupState.accessPointStates
327
+ .filter(filterByType(LakehouseAccessPointState))
328
+ .map((apState) => (_jsx(LakehouseDataProductAcccessPointEditor, { isReadOnly: isReadOnly, accessPointState: apState }, apState.uuid))) })] }));
335
329
  });
336
- export const DataProductEditor = observer(() => {
337
- const editorStore = useEditorStore();
338
- const dataProductEditorState = editorStore.tabManagerState.getCurrentEditorState(DataProductEditorState);
339
- const product = dataProductEditorState.product;
340
- const accessPointStates = dataProductEditorState.accessPointGroupStates
341
- .map((e) => e.accessPointStates)
342
- .flat();
343
- const isReadOnly = dataProductEditorState.isReadOnly;
344
- const openNewModal = () => {
345
- dataProductEditorState.setAccessPointGroupModal(true);
330
+ const GroupTabRenderer = observer((props) => {
331
+ const { group, dataProductEditorState } = props;
332
+ const changeGroup = (newGroup) => {
333
+ dataProductEditorState.setSelectedGroupState(newGroup);
346
334
  };
347
- const auth = useAuth();
348
- const deployDataProduct = () => {
349
- // Trigger OAuth flow if not authenticated
350
- if (!auth.isAuthenticated) {
351
- // remove this redirect if we move to do oauth at the beginning of opening studio
352
- auth
353
- .signinRedirect({
354
- state: generateUrlToDeployOnOpen(dataProductEditorState),
355
- })
356
- .catch(editorStore.applicationStore.alertUnhandledError);
357
- return;
358
- }
359
- // Use the token for deployment
360
- const token = auth.user?.access_token;
361
- if (token) {
362
- flowResult(dataProductEditorState.deploy(token)).catch(editorStore.applicationStore.alertUnhandledError);
363
- }
364
- else {
365
- editorStore.applicationStore.notificationService.notifyError('Authentication failed. No token available.');
366
- }
335
+ const selectedGroupState = dataProductEditorState.selectedGroupState;
336
+ const ref = useRef(null);
337
+ const groupError = () => {
338
+ return (group.accessPointStates.length === 0 ||
339
+ group.value.id === newNamePlaceholder ||
340
+ Boolean(group.accessPointStates.find((apState) => apState.accessPoint.id === newNamePlaceholder)));
367
341
  };
342
+ //Drag and Drop - reorder groups and accept access points from other groups
343
+ const handleHover = useCallback((item) => {
344
+ const draggingProperty = item.groupState;
345
+ const hoveredProperty = group;
346
+ dataProductEditorState.swapAccessPointGroups(draggingProperty, hoveredProperty);
347
+ }, [group, dataProductEditorState]);
348
+ const [{ isOver }, dropConnector] = useDrop(() => ({
349
+ accept: [AP_GROUP_DND_TYPE, AP_DND_TYPE],
350
+ hover: (item, monitor) => {
351
+ const itemType = monitor.getItemType();
352
+ if (itemType === AP_GROUP_DND_TYPE) {
353
+ const groupItem = item;
354
+ handleHover(groupItem);
355
+ }
356
+ },
357
+ drop: (item, monitor) => {
358
+ const itemType = monitor.getItemType();
359
+ if (itemType === AP_DND_TYPE) {
360
+ const accessPointItem = item;
361
+ group.addAccessPoint(accessPointItem.accessPointState);
362
+ accessPointItem.accessPointState.state.deleteAccessPoint(accessPointItem.accessPointState);
363
+ accessPointItem.accessPointState.changeGroupState(group);
364
+ }
365
+ },
366
+ collect: (monitor) => ({
367
+ isBeingDraggedAPG: monitor.getItemType() === AP_GROUP_DND_TYPE
368
+ ? monitor.getItem()?.groupState
369
+ : undefined,
370
+ isBeingDraggedAP: monitor.getItemType() === AP_DND_TYPE
371
+ ? monitor.getItem()
372
+ ?.accessPointState
373
+ : undefined,
374
+ isOver: monitor.isOver(),
375
+ }),
376
+ }), [handleHover]);
377
+ const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
378
+ type: AP_GROUP_DND_TYPE,
379
+ item: () => ({
380
+ groupState: group,
381
+ }),
382
+ collect: (monitor) => ({
383
+ isDragging: monitor.isDragging(),
384
+ }),
385
+ }), [group]);
386
+ dragConnector(ref);
387
+ dropConnector(ref);
388
+ dragPreviewConnector(ref);
389
+ return (_jsxs("div", { ref: ref, onClick: () => changeGroup(group), className: clsx('service-editor__tab', {
390
+ 'service-editor__tab--active': group === selectedGroupState,
391
+ }), style: {
392
+ backgroundColor: isOver
393
+ ? 'var(--color-dark-grey-100)'
394
+ : 'var(--color-dark-grey-50)',
395
+ }, children: [group.value.id, "\u00A0", groupError() && (_jsx(ErrorWarnIcon, { title: "Resolve Access Point Group error(s)", style: { color: 'var(--color-red-300)' } }))] }, group.uuid));
396
+ });
397
+ const AccessPointGroupTab = observer((props) => {
398
+ const { dataProductEditorState, isReadOnly } = props;
399
+ const groupStates = dataProductEditorState.accessPointGroupStates;
400
+ const selectedGroupState = dataProductEditorState.selectedGroupState;
401
+ const handleAddAccessPointGroup = () => {
402
+ const newGroup = dataProductEditorState.createGroupAndAdd(newNamePlaceholder);
403
+ dataProductEditorState.setSelectedGroupState(newGroup);
404
+ };
405
+ const AccessPointDragPreviewLayer = () => (_jsx(DragPreviewLayer, { labelGetter: (item) => {
406
+ return item.accessPointState.accessPoint.id;
407
+ }, types: [AP_DND_TYPE] }));
408
+ const disableAddGroup = Boolean(dataProductEditorState.accessPointGroupStates.find((group) => group.value.id === newNamePlaceholder));
409
+ return (_jsxs("div", { className: "panel", style: { overflow: 'visible' }, children: [_jsx(AccessPointDragPreviewLayer, {}), _jsx("div", { className: "panel__content__form__section__header__label", style: { paddingLeft: '1rem' }, children: "Access Point Groups" }), _jsxs(PanelHeader, { children: [_jsxs("div", { className: "uml-element-editor__tabs", children: [groupStates.map((group) => {
410
+ return (_jsx(GroupTabRenderer, { group: group, dataProductEditorState: dataProductEditorState }, group.uuid));
411
+ }), _jsx(PanelHeaderActionItem, { className: "panel__header__action", onClick: handleAddAccessPointGroup, disabled: isReadOnly || disableAddGroup, title: disableAddGroup
412
+ ? 'Provide all group names'
413
+ : 'Create new access point group', children: _jsx(PlusIcon, {}) })] }), _jsx(PanelHeaderActions, {})] }), _jsx(PanelContent, { children: selectedGroupState && (_jsx(AccessPointGroupEditor, { groupState: selectedGroupState, isReadOnly: isReadOnly }, selectedGroupState.uuid)) }), dataProductEditorState.deployResponse && (_jsx(DataProductDeploymentResponseModal, { state: dataProductEditorState }))] }));
414
+ });
415
+ const DataProductSidebar = observer((props) => {
416
+ const { dataProductEditorState } = props;
417
+ const sidebarTabs = [
418
+ {
419
+ label: DATA_PRODUCT_TAB.HOME,
420
+ icon: _jsx(HomeIcon, {}),
421
+ },
422
+ {
423
+ label: DATA_PRODUCT_TAB.APG,
424
+ title: 'Access Point Groups',
425
+ icon: _jsx(GroupWorkIcon, {}),
426
+ },
427
+ {
428
+ label: DATA_PRODUCT_TAB.SUPPORT,
429
+ icon: _jsx(QuestionCircleIcon, {}),
430
+ },
431
+ ];
432
+ return (_jsx("div", { className: "data-space__viewer__activity-bar", style: { position: 'static', maxHeight: '100%' }, children: _jsx("div", { className: "data-space__viewer__activity-bar__items", children: sidebarTabs.map((activity) => (_jsxs("button", { className: clsx('data-space__viewer__activity-bar__item', {
433
+ 'data-space__viewer__activity-bar__item--active': dataProductEditorState.selectedTab === activity.label,
434
+ }), onClick: () => dataProductEditorState.setSelectedTab(activity.label), tabIndex: -1, title: activity.title ?? activity.label, style: {
435
+ flexDirection: 'column',
436
+ fontSize: '12px',
437
+ margin: '1rem 0rem',
438
+ }, children: [activity.icon, activity.label] }, activity.label))) }) }));
439
+ });
440
+ const HomeTab = observer((props) => {
441
+ const { product, isReadOnly } = props;
368
442
  const updateDataProductTitle = (val) => {
369
443
  dataProduct_setTitle(product, val ?? '');
370
444
  };
371
445
  const updateDataProductDescription = (event) => {
372
446
  dataProduct_setDescription(product, event.target.value);
373
447
  };
448
+ return (_jsxs("div", { style: { flexDirection: 'column', display: 'flex' }, children: [_jsx(PanelFormTextField, { name: "Title", value: product.title, prompt: "Provide a descriptive name for the Data Product to appear in Marketplace.", update: updateDataProductTitle, placeholder: "Enter title" }), _jsxs("div", { style: { margin: '1rem' }, children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Description" }), _jsx("div", { className: "panel__content__form__section__header__prompt", style: {
449
+ color: product.description === '' || product.description === undefined
450
+ ? 'var(--color-red-300)'
451
+ : 'var(--color-light-grey-400)',
452
+ }, children: "Clearly describe the purpose, content, and intended use of the Data Product." }), _jsx("textarea", { className: "panel__content__form__section__textarea", spellCheck: false, disabled: isReadOnly, value: product.description, onChange: updateDataProductDescription, style: {
453
+ padding: '0.5rem',
454
+ width: '45rem',
455
+ maxWidth: '45rem !important',
456
+ borderColor: product.description === '' || product.description === undefined
457
+ ? 'var(--color-red-300)'
458
+ : 'transparent',
459
+ } })] })] }));
460
+ });
461
+ const SupportTab = observer((props) => {
462
+ const { product, isReadOnly } = props;
374
463
  const updateSupportInfoDocumentationUrl = (val) => {
375
464
  dataProduct_setSupportInfoIfAbsent(product);
376
465
  if (product.supportInfo) {
@@ -395,19 +484,6 @@ export const DataProductEditor = observer(() => {
395
484
  supportInfo_setSupportUrl(product.supportInfo, val ?? '');
396
485
  }
397
486
  };
398
- useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.DATA_PRODUCT_EDITOR);
399
- useEffect(() => {
400
- flowResult(dataProductEditorState.convertAccessPointsFuncObjects()).catch(dataProductEditorState.editorStore.applicationStore.alertUnhandledError);
401
- }, [dataProductEditorState]);
402
- useEffect(() => {
403
- if (dataProductEditorState.deployOnOpen) {
404
- flowResult(dataProductEditorState.deploy(auth.user?.access_token)).catch(editorStore.applicationStore.alertUnhandledError);
405
- }
406
- }, [
407
- auth,
408
- editorStore.applicationStore.alertUnhandledError,
409
- dataProductEditorState,
410
- ]);
411
487
  const handleSupportInfoEmailAdd = (address, title) => {
412
488
  dataProduct_setSupportInfoIfAbsent(product);
413
489
  if (product.supportInfo) {
@@ -419,12 +495,12 @@ export const DataProductEditor = observer(() => {
419
495
  supportInfo_deleteEmail(product.supportInfo, email);
420
496
  }
421
497
  };
422
- const SupportEmailComponent = observer((props) => {
423
- const { item } = props;
498
+ const SupportEmailComponent = observer((supportEmailProps) => {
499
+ const { item } = supportEmailProps;
424
500
  return (_jsxs("div", { className: "panel__content__form__section__list__item__rows", children: [_jsxs("div", { className: "row", children: [_jsx("label", { className: "label", children: "Address" }), _jsx("div", { className: "textbox", children: item.address })] }), _jsxs("div", { className: "row", children: [_jsx("label", { className: "label", children: "Title" }), _jsx("div", { className: "textbox", children: item.title })] })] }));
425
501
  });
426
- const NewSupportEmailComponent = observer((props) => {
427
- const { onFinishEditing } = props;
502
+ const NewSupportEmailComponent = observer((newSupportEmailProps) => {
503
+ const { onFinishEditing } = newSupportEmailProps;
428
504
  const [address, setAddress] = useState('');
429
505
  const [title, setTitle] = useState('');
430
506
  return (_jsxs("div", { className: "data-product-editor__support-info__new-email", children: [_jsx("div", { className: "panel__content__form__section__list__new-item__input", children: _jsx("input", { className: "input input-group__input panel__content__form__section__input input--dark", type: "email", placeholder: "Enter email", value: address, onChange: (event) => {
@@ -438,10 +514,60 @@ export const DataProductEditor = observer(() => {
438
514
  onFinishEditing();
439
515
  }, children: "Save" })] }));
440
516
  });
441
- return (_jsx("div", { className: "data-product-editor", children: _jsxs("div", { className: "panel", children: [_jsxs("div", { className: "panel__header", children: [_jsxs("div", { className: "panel__header__title", children: [isReadOnly && (_jsx("div", { className: "uml-element-editor__header__lock", children: _jsx(LockIcon, {}) })), _jsx("div", { className: "panel__header__title__label", children: "data product" })] }), _jsx(PanelHeaderActions, { children: _jsx("div", { className: "btn__dropdown-combo btn__dropdown-combo--primary", children: _jsxs("button", { className: "btn__dropdown-combo__label", onClick: deployDataProduct, title: dataProductEditorState.deployValidationMessage, tabIndex: -1, disabled: !dataProductEditorState.deployValidationMessage, children: [_jsx(RocketIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Deploy" })] }) }) })] }), _jsxs("div", { className: "panel", style: { padding: '1rem', flex: 0 }, children: [_jsx(PanelFormTextField, { name: "Title", value: product.title, prompt: "Provide a title for this Lakehouse Data Product.", update: updateDataProductTitle, placeholder: "Enter title" }), _jsxs("div", { style: { margin: '1rem' }, children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Description" }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: "Provide a description for this Lakehouse Data Product." }), _jsx("textarea", { className: "panel__content__form__section__textarea", spellCheck: false, disabled: isReadOnly, value: product.description, onChange: updateDataProductDescription, style: {
442
- padding: '0.5rem',
443
- width: '45rem',
444
- maxWidth: '45rem !important',
445
- } })] }), _jsxs(PanelFormSection, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Support Information" }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: "Configure support information for this Lakehouse Data Product." }), _jsx(PanelFormTextField, { name: "Documentation URL", value: product.supportInfo?.documentationUrl ?? '', update: updateSupportInfoDocumentationUrl, placeholder: "Enter Documentation URL" }), _jsx(PanelFormTextField, { name: "Website", value: product.supportInfo?.website, update: updateSupportInfoWebsite, placeholder: "Enter Website" }), _jsx(PanelFormTextField, { name: "FAQ URL", value: product.supportInfo?.faqUrl, update: updateSupportInfoFaqUrl, placeholder: "Enter FAQ URL" }), _jsx(PanelFormTextField, { name: "Support URL", value: product.supportInfo?.supportUrl, update: updateSupportInfoSupportUrl, placeholder: "Enter Support URL" }), _jsx(ListEditor, { title: "Emails", items: product.supportInfo?.emails, keySelector: (email) => email.address + email.title, ItemComponent: SupportEmailComponent, NewItemComponent: NewSupportEmailComponent, handleRemoveItem: handleSupportInfoEmailRemove, isReadOnly: isReadOnly, emptyMessage: "No emails specified" })] })] }), _jsxs("div", { className: "panel", style: { overflow: 'auto' }, children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "access point groups" }) }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { className: "panel__header__action", onClick: openNewModal, disabled: isReadOnly, title: "Create new access point group", children: _jsx(PlusIcon, {}) }) })] }), _jsxs(PanelContent, { children: [_jsx("div", { style: { overflow: 'auto', margin: '1rem', marginLeft: '1.5rem' }, children: dataProductEditorState.accessPointGroupStates.map((groupState) => groupState.accessPointStates.length > 0 && (_jsx(AccessPointGroupSection, { groupState: groupState, isReadOnly: isReadOnly }, groupState.uuid))) }), !accessPointStates.length && (_jsx(DataProductEditorSplashScreen, { dataProductEditorState: dataProductEditorState }))] }), dataProductEditorState.accessPointGroupModal && (_jsx(NewAccessPointGroupModal, { dataProductEditorState: dataProductEditorState })), dataProductEditorState.deployResponse && (_jsx(DataProductDeploymentResponseModal, { state: dataProductEditorState }))] })] }) }));
517
+ return (_jsxs(PanelFormSection, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Support Information" }), _jsx("div", { className: "panel__content__form__section__header__prompt", children: "Configure support information for this Lakehouse Data Product." }), _jsx(PanelFormTextField, { name: "Documentation URL", value: product.supportInfo?.documentationUrl ?? '', update: updateSupportInfoDocumentationUrl, placeholder: "Enter Documentation URL" }), _jsx(PanelFormTextField, { name: "Website", value: product.supportInfo?.website, update: updateSupportInfoWebsite, placeholder: "Enter Website" }), _jsx(PanelFormTextField, { name: "FAQ URL", value: product.supportInfo?.faqUrl, update: updateSupportInfoFaqUrl, placeholder: "Enter FAQ URL" }), _jsx(PanelFormTextField, { name: "Support URL", value: product.supportInfo?.supportUrl, update: updateSupportInfoSupportUrl, placeholder: "Enter Support URL" }), _jsx(ListEditor, { title: "Emails", items: product.supportInfo?.emails, keySelector: (email) => email.address + email.title, ItemComponent: SupportEmailComponent, NewItemComponent: NewSupportEmailComponent, handleRemoveItem: handleSupportInfoEmailRemove, isReadOnly: isReadOnly, emptyMessage: "No emails specified" })] }));
518
+ });
519
+ export const DataProductEditor = observer(() => {
520
+ const editorStore = useEditorStore();
521
+ const dataProductEditorState = editorStore.tabManagerState.getCurrentEditorState(DataProductEditorState);
522
+ const product = dataProductEditorState.product;
523
+ const isReadOnly = dataProductEditorState.isReadOnly;
524
+ const auth = useAuth();
525
+ const deployDataProduct = () => {
526
+ // Trigger OAuth flow if not authenticated
527
+ if (!auth.isAuthenticated) {
528
+ // remove this redirect if we move to do oauth at the beginning of opening studio
529
+ auth
530
+ .signinRedirect({
531
+ state: generateUrlToDeployOnOpen(dataProductEditorState),
532
+ })
533
+ .catch(editorStore.applicationStore.alertUnhandledError);
534
+ return;
535
+ }
536
+ // Use the token for deployment
537
+ const token = auth.user?.access_token;
538
+ if (token) {
539
+ flowResult(dataProductEditorState.deploy(token)).catch(editorStore.applicationStore.alertUnhandledError);
540
+ }
541
+ else {
542
+ editorStore.applicationStore.notificationService.notifyError('Authentication failed. No token available.');
543
+ }
544
+ };
545
+ const selectedActivity = dataProductEditorState.selectedTab;
546
+ const renderActivivtyBarTab = () => {
547
+ switch (selectedActivity) {
548
+ case DATA_PRODUCT_TAB.HOME:
549
+ return _jsx(HomeTab, { product: product, isReadOnly: isReadOnly });
550
+ case DATA_PRODUCT_TAB.SUPPORT:
551
+ return _jsx(SupportTab, { product: product, isReadOnly: isReadOnly });
552
+ case DATA_PRODUCT_TAB.APG:
553
+ return (_jsx(AccessPointGroupTab, { dataProductEditorState: dataProductEditorState, isReadOnly: isReadOnly }));
554
+ default:
555
+ return null;
556
+ }
557
+ };
558
+ useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.DATA_PRODUCT_EDITOR);
559
+ useEffect(() => {
560
+ flowResult(dataProductEditorState.convertAccessPointsFuncObjects()).catch(dataProductEditorState.editorStore.applicationStore.alertUnhandledError);
561
+ }, [dataProductEditorState]);
562
+ useEffect(() => {
563
+ if (dataProductEditorState.deployOnOpen) {
564
+ flowResult(dataProductEditorState.deploy(auth.user?.access_token)).catch(editorStore.applicationStore.alertUnhandledError);
565
+ }
566
+ }, [
567
+ auth,
568
+ editorStore.applicationStore.alertUnhandledError,
569
+ dataProductEditorState,
570
+ ]);
571
+ return (_jsx("div", { className: "data-product-editor", children: _jsxs("div", { className: "panel", children: [_jsxs("div", { className: "panel__header", children: [_jsxs("div", { className: "panel__header__title", children: [isReadOnly && (_jsx("div", { className: "uml-element-editor__header__lock", children: _jsx(LockIcon, {}) })), _jsx("div", { className: "panel__header__title__label", children: "data product" })] }), _jsx(PanelHeaderActions, { children: _jsx("div", { className: "btn__dropdown-combo btn__dropdown-combo--primary", children: _jsxs("button", { className: "btn__dropdown-combo__label", onClick: deployDataProduct, title: dataProductEditorState.deployValidationMessage, tabIndex: -1, disabled: !dataProductEditorState.deployValidationMessage, children: [_jsx(RocketIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Deploy" })] }) }) })] }), _jsxs("div", { className: "panel", style: { padding: '1rem', flexDirection: 'row' }, children: [_jsx(DataProductSidebar, { dataProductEditorState: dataProductEditorState }), renderActivivtyBarTab()] })] }) }));
446
572
  });
447
573
  //# sourceMappingURL=DataPoductEditor.js.map