@finos/legend-application-studio
Version:
Legend Studio application core
584 lines • 43.1 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { observer } from 'mobx-react-lite';
import { useEditorStore } from '../../EditorStoreProvider.js';
import { DATA_PRODUCT_TAB, DataProductEditorState, generateUrlToDeployOnOpen, LakehouseAccessPointState, } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
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, ResizablePanelGroup, ResizablePanel, MarkdownTextViewer, ResizablePanelSplitter, EyeIcon, CloseEyeIcon, Checkbox, } from '@finos/legend-art';
import React, { useRef, useState, useEffect, useCallback, } from 'react';
import { filterByType } from '@finos/legend-shared';
import { InlineLambdaEditor } from '@finos/legend-query-builder';
import { action, flowResult } from 'mobx';
import { useAuth } from 'react-oidc-context';
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
import { CodeEditor } from '@finos/legend-lego/code-editor';
import { LakehouseTargetEnv, Email, StereotypeExplicitReference, } from '@finos/legend-graph';
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, accessPoint_setReproducible, } from '../../../../stores/graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js';
import { ActionAlertActionType, ActionAlertType, useApplicationNavigationContext, } from '@finos/legend-application';
import { useDrag, useDrop } from 'react-dnd';
import { annotatedElement_addStereotype, annotatedElement_deleteStereotype, } from '../../../../stores/graph-modifier/DomainGraphModifierHelper.js';
export var AP_GROUP_MODAL_ERRORS;
(function (AP_GROUP_MODAL_ERRORS) {
AP_GROUP_MODAL_ERRORS["GROUP_NAME_EMPTY"] = "Group Name is empty";
AP_GROUP_MODAL_ERRORS["GROUP_NAME_EXISTS"] = "Group Name already exists";
AP_GROUP_MODAL_ERRORS["GROUP_DESCRIPTION_EMPTY"] = "Group Description is empty";
AP_GROUP_MODAL_ERRORS["AP_NAME_EMPTY"] = "Access Point Name is empty";
AP_GROUP_MODAL_ERRORS["AP_NAME_EXISTS"] = "Access Point Name already exists";
AP_GROUP_MODAL_ERRORS["AP_DESCRIPTION_EMPTY"] = "Access Point Description is empty";
})(AP_GROUP_MODAL_ERRORS || (AP_GROUP_MODAL_ERRORS = {}));
export const AP_EMPTY_DESC_WARNING = 'Click here to describe the data this access point produces';
const AP_DND_TYPE = 'ACCESS_POINT';
const AP_GROUP_DND_TYPE = 'ACCESS_POINT_GROUP';
const newNamePlaceholder = '';
const HoverTextArea = ({ text: text, handleMouseOver, handleMouseOut, className, }) => {
return (_jsx("div", { onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, className: clsx(className), children: text }));
};
const hoverIcon = () => {
return (_jsx("div", { "data-testid": LEGEND_STUDIO_TEST_ID.HOVER_EDIT_ICON, children: _jsx(PencilEditIcon, {}) }));
};
const AccessPointTitle = observer((props) => {
const { accessPoint } = props;
const [editingName, setEditingName] = useState(accessPoint.id === newNamePlaceholder);
const handleNameEdit = () => setEditingName(true);
const handleNameBlur = () => {
if (accessPoint.id !== newNamePlaceholder) {
setEditingName(false);
}
};
const updateAccessPointName = action((event) => {
if (!event.target.value.includes(' ')) {
accessPoint.id = event.target.value;
}
});
return editingName ? (_jsx("textarea", { className: "access-point-editor__name", spellCheck: false, value: accessPoint.id, onChange: updateAccessPointName, placeholder: 'Access Point Name', onBlur: handleNameBlur, style: {
borderColor: accessPoint.id === newNamePlaceholder
? 'var(--color-red-300)'
: 'transparent',
} })) : (_jsx("div", { onClick: handleNameEdit, title: "Click to edit access point title", children: _jsx("div", { className: "access-point-editor__name__label", children: accessPoint.id }) }));
});
const AccessPointClassification = observer((props) => {
const { accessPoint, groupState } = props;
const applicationStore = useEditorStore().applicationStore;
const CHOOSE_CLASSIFICATION = 'Choose Classification';
const updateAccessPointClassificationTextbox = action((event) => {
accessPoint.classification = event.target.value;
});
const conditionalClassifications = () => {
if (groupState.containsPublicStereotype) {
return (applicationStore.config.options.dataProductConfig
?.publicClassifications ?? []);
}
else {
return (applicationStore.config.options.dataProductConfig?.classifications ??
[]);
}
};
const classificationOptions = [CHOOSE_CLASSIFICATION]
.concat(conditionalClassifications())
.map((classfication) => ({
label: classfication,
value: classfication,
}));
const updateAccessPointClassificationFromDropdown = action((val) => {
accessPoint_setClassification(accessPoint, val?.value === CHOOSE_CLASSIFICATION ? undefined : val?.value);
});
const currentClassification = accessPoint.classification !== undefined
? {
label: accessPoint.classification,
value: accessPoint.classification,
}
: {
label: CHOOSE_CLASSIFICATION,
value: CHOOSE_CLASSIFICATION,
};
const classificationDocumentationLink = () => {
const docLink = applicationStore.config.options.dataProductConfig?.classificationDoc;
if (docLink) {
applicationStore.navigationService.navigator.visitAddress(docLink);
}
};
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
.TEMPORARY__isLightColorThemeEnabled }) })) : (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.classification ?? '', onChange: updateAccessPointClassificationTextbox, placeholder: "Add classification", style: {
overflow: 'hidden',
width: '125px',
resize: 'none',
padding: '0.25rem',
} })), _jsx(Tooltip, { title: "Learn more about data classification scheme here.", arrow: true, placement: 'top', children: _jsx("button", { onClick: classificationDocumentationLink, children: _jsx(InfoCircleIcon, {}) }) })] }));
});
export const LakehouseDataProductAcccessPointEditor = observer((props) => {
const { accessPointState } = props;
const editorStore = useEditorStore();
const accessPoint = accessPointState.accessPoint;
const groupState = accessPointState.state;
const lambdaEditorState = accessPointState.lambdaState;
const propertyHasParserError = groupState.accessPointStates
.filter(filterByType(LakehouseAccessPointState))
.find((pm) => pm.lambdaState.parserError);
const [editingDescription, setEditingDescription] = useState(false);
const [isHovering, setIsHovering] = useState(false);
const ref = useRef(null);
const handleDescriptionEdit = () => setEditingDescription(true);
const handleDescriptionBlur = () => {
setEditingDescription(false);
setIsHovering(false);
};
const handleMouseOver = () => {
setIsHovering(true);
};
const handleMouseOut = () => {
setIsHovering(false);
};
const updateAccessPointDescription = action((event) => {
accessPoint.description = event.target.value;
});
const updateAccessPointTargetEnvironment = action((targetEnvironment) => {
accessPoint.targetEnvironment = targetEnvironment;
});
const handleRemoveAccessPoint = () => {
editorStore.applicationStore.alertService.setActionAlertInfo({
message: `Are you sure you want to delete Access Point ${accessPoint.id}?`,
type: ActionAlertType.CAUTION,
actions: [
{
label: 'Confirm',
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
handler: () => {
groupState.deleteAccessPoint(accessPointState);
},
},
{
label: 'Cancel',
type: ActionAlertActionType.PROCEED,
default: true,
},
],
});
};
//Drag and drop - reorder access points/move between groups
const handleHover = useCallback((item) => {
const draggingProperty = item.accessPointState;
const hoveredProperty = accessPointState;
groupState.swapAccessPoints(draggingProperty, hoveredProperty);
}, [accessPointState, groupState]);
const [{ isBeingDraggedAP }, dropConnector] = useDrop(() => ({
accept: [AP_DND_TYPE],
hover: (item) => handleHover(item),
collect: (monitor) => ({
isBeingDraggedAP: monitor.getItem()
?.accessPointState,
}),
}), [handleHover]);
const isBeingDragged = accessPoint === isBeingDraggedAP?.accessPoint;
const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
type: AP_DND_TYPE,
item: () => ({
accessPointState: accessPointState,
}),
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}), [accessPointState]);
dragConnector(ref);
dropConnector(ref);
useDragPreviewLayer(dragPreviewConnector);
return (_jsx(PanelDnDEntry, { ref: ref, placeholder: _jsx("div", { className: "dnd__placeholder--light" }), showPlaceholder: isBeingDragged, children: _jsxs("div", { className: clsx('access-point-editor', {
backdrop__element: propertyHasParserError,
}), 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 }), _jsxs("div", { className: "access-point-editor__info", children: [_jsxs("div", { className: "access-point-editor__reproducible", children: [_jsx(Checkbox, { disabled: groupState.state.isReadOnly, checked: accessPoint.reproducible ?? false, onChange: () => accessPoint_setReproducible(accessPoint, !accessPoint.reproducible), size: "small", style: { padding: 0, margin: 0 } }), _jsx(Tooltip, { title: "This access point is reproducible based on a specific Lakehouse batch in time", arrow: true, placement: 'top', children: _jsx("div", { children: "Reproducible" }) })] }), editorStore.applicationStore.config.options
.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: {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
}, children: _jsx(CaretDownIcon, {}) })] })] })] }), editingDescription ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.description ?? '', onChange: updateAccessPointDescription, placeholder: "Access Point description", onBlur: handleDescriptionBlur, style: {
overflow: 'hidden',
resize: 'none',
padding: '0.25rem',
marginLeft: '0.5rem',
} })) : (_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()] })), _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
.isConvertingTransformLambdaObjects, lambdaEditorState: lambdaEditorState, forceBackdrop: Boolean(lambdaEditorState.parserError) }) }) }), _jsx("button", { className: "access-point-editor__generic-entry__remove-btn", onClick: () => {
handleRemoveAccessPoint();
}, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) })] }) })] })] }) }));
});
const DataProductDeploymentResponseModal = observer((props) => {
const { state } = props;
const applicationStore = state.editorStore.applicationStore;
const closeModal = () => state.setDeployResponse(undefined);
return (_jsx(Dialog, { open: Boolean(state.deployResponse), classes: {
root: 'editor-modal__root-container',
container: 'editor-modal__container',
paper: 'editor-modal__content',
}, 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" }) })] }) }));
});
const AccessPointGroupPublicToggle = observer((props) => {
const { groupState } = props;
const handleSwitchChange = (event) => {
const isChecked = event.target.checked;
if (isChecked && groupState.publicStereotype) {
annotatedElement_addStereotype(groupState.value, StereotypeExplicitReference.create(groupState.publicStereotype));
}
else if (groupState.containsPublicStereotype) {
annotatedElement_deleteStereotype(groupState.value, groupState.containsPublicStereotype);
}
};
return (_jsxs("div", { className: "access-point-editor__toggle", children: [_jsx(Switch, { checked: Boolean(groupState.containsPublicStereotype), onChange: handleSwitchChange, sx: {
'& .MuiSwitch-track': {
backgroundColor: groupState.containsPublicStereotype
? 'default'
: 'var(--color-light-grey-400)',
},
} }), _jsx(BuildingIcon, {}), "Enterprise Data. Anyone at the firm can access this without approvals."] }));
});
const AccessPointGroupEditor = observer((props) => {
const { groupState, isReadOnly } = props;
const editorStore = useEditorStore();
const productEditorState = groupState.state;
const [editingDescription, setEditingDescription] = useState(false);
const [isHoveringDescription, setIsHoveringDescription] = useState(false);
const [editingName, setEditingName] = useState(groupState.value.id === newNamePlaceholder);
const [isHoveringName, setIsHoveringName] = useState(false);
const handleDescriptionEdit = () => setEditingDescription(true);
const handleDescriptionBlur = () => {
setEditingDescription(false);
setIsHoveringDescription(false);
};
const handleMouseOverDescription = () => {
setIsHoveringDescription(true);
};
const handleMouseOutDescription = () => {
setIsHoveringDescription(false);
};
const updateGroupDescription = (val) => {
accessPointGroup_setDescription(groupState.value, val);
};
const handleNameEdit = () => setEditingName(true);
const handleNameBlur = () => {
if (groupState.value.id !== newNamePlaceholder) {
setEditingName(false);
setIsHoveringName(false);
}
};
const handleMouseOverName = () => {
setIsHoveringName(true);
};
const handleMouseOutName = () => {
setIsHoveringName(false);
};
const updateGroupName = (val) => {
if (val && !val.includes(' ')) {
accessPointGroup_setName(groupState.value, val);
}
};
const handleRemoveAccessPointGroup = () => {
editorStore.applicationStore.alertService.setActionAlertInfo({
message: `Are you sure you want to delete Access Point Group ${groupState.value.id} and all of its Access Points?`,
type: ActionAlertType.CAUTION,
actions: [
{
label: 'Confirm',
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
handler: () => {
productEditorState.deleteAccessPointGroup(groupState);
},
},
{
label: 'Cancel',
type: ActionAlertActionType.PROCEED,
default: true,
},
],
});
};
const handleAddAccessPoint = () => {
productEditorState.addAccessPoint(newNamePlaceholder, undefined, productEditorState.selectedGroupState ?? groupState);
};
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: {
overflow: 'hidden',
resize: 'none',
padding: '0.25rem',
borderColor: groupState.value.id === newNamePlaceholder
? 'var(--color-red-300)'
: 'transparent',
borderWidth: 'thin',
} })) : (_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: () => {
handleRemoveAccessPointGroup();
}, 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: {
overflow: 'hidden',
resize: 'none',
padding: '0.25rem',
} })) : (_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", style: { color: 'var(--color-red-300)' }, children: [_jsx(WarningIcon, {}), "This group needs at least one access point defined."] })), _jsx("div", { style: { gap: '1rem', display: 'flex', flexDirection: 'column' }, children: groupState.accessPointStates
.filter(filterByType(LakehouseAccessPointState))
.map((apState) => (_jsx(LakehouseDataProductAcccessPointEditor, { isReadOnly: isReadOnly, accessPointState: apState }, apState.uuid))) })] }));
});
const GroupTabRenderer = observer((props) => {
const { group, dataProductEditorState } = props;
const changeGroup = (newGroup) => {
dataProductEditorState.setSelectedGroupState(newGroup);
};
const selectedGroupState = dataProductEditorState.selectedGroupState;
const ref = useRef(null);
const groupError = () => {
return (group.accessPointStates.length === 0 ||
group.value.id === newNamePlaceholder ||
Boolean(group.accessPointStates.find((apState) => apState.accessPoint.id === newNamePlaceholder)));
};
//Drag and Drop - reorder groups and accept access points from other groups
const handleHover = useCallback((item) => {
const draggingProperty = item.groupState;
const hoveredProperty = group;
dataProductEditorState.swapAccessPointGroups(draggingProperty, hoveredProperty);
}, [group, dataProductEditorState]);
const [{ isOver }, dropConnector] = useDrop(() => ({
accept: [AP_GROUP_DND_TYPE, AP_DND_TYPE],
hover: (item, monitor) => {
const itemType = monitor.getItemType();
if (itemType === AP_GROUP_DND_TYPE) {
const groupItem = item;
handleHover(groupItem);
}
},
drop: (item, monitor) => {
const itemType = monitor.getItemType();
if (itemType === AP_DND_TYPE) {
const accessPointItem = item;
group.addAccessPoint(accessPointItem.accessPointState);
accessPointItem.accessPointState.state.deleteAccessPoint(accessPointItem.accessPointState);
accessPointItem.accessPointState.changeGroupState(group);
}
},
collect: (monitor) => ({
isBeingDraggedAPG: monitor.getItemType() === AP_GROUP_DND_TYPE
? monitor.getItem()?.groupState
: undefined,
isBeingDraggedAP: monitor.getItemType() === AP_DND_TYPE
? monitor.getItem()
?.accessPointState
: undefined,
isOver: monitor.isOver(),
}),
}), [handleHover]);
const [, dragConnector, dragPreviewConnector] = useDrag(() => ({
type: AP_GROUP_DND_TYPE,
item: () => ({
groupState: group,
}),
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}), [group]);
dragConnector(ref);
dropConnector(ref);
dragPreviewConnector(ref);
return (_jsxs("div", { ref: ref, onClick: () => changeGroup(group), className: clsx('service-editor__tab', {
'service-editor__tab--active': group === selectedGroupState,
}), style: {
backgroundColor: isOver
? 'var(--color-dark-grey-100)'
: 'var(--color-dark-grey-50)',
}, children: [group.value.id, "\u00A0", groupError() && (_jsx(ErrorWarnIcon, { title: "Resolve Access Point Group error(s)", style: { color: 'var(--color-red-300)' } }))] }, group.uuid));
});
const AccessPointGroupTab = observer((props) => {
const { dataProductEditorState, isReadOnly } = props;
const groupStates = dataProductEditorState.accessPointGroupStates;
const selectedGroupState = dataProductEditorState.selectedGroupState;
const handleAddAccessPointGroup = () => {
const newGroup = dataProductEditorState.createGroupAndAdd(newNamePlaceholder);
dataProductEditorState.setSelectedGroupState(newGroup);
};
const AccessPointDragPreviewLayer = () => (_jsx(DragPreviewLayer, { labelGetter: (item) => {
return item.accessPointState.accessPoint.id;
}, types: [AP_DND_TYPE] }));
const disableAddGroup = Boolean(dataProductEditorState.accessPointGroupStates.find((group) => group.value.id === newNamePlaceholder));
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) => {
return (_jsx(GroupTabRenderer, { group: group, dataProductEditorState: dataProductEditorState }, group.uuid));
}), _jsx(PanelHeaderActionItem, { className: "panel__header__action", onClick: handleAddAccessPointGroup, disabled: isReadOnly || disableAddGroup, title: disableAddGroup
? 'Provide all group names'
: '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 }))] }));
});
const DataProductSidebar = observer((props) => {
const { dataProductEditorState } = props;
const sidebarTabs = [
{
label: DATA_PRODUCT_TAB.HOME,
icon: _jsx(HomeIcon, {}),
},
{
label: DATA_PRODUCT_TAB.APG,
title: 'Access Point Groups',
icon: _jsx(GroupWorkIcon, {}),
},
{
label: DATA_PRODUCT_TAB.SUPPORT,
icon: _jsx(QuestionCircleIcon, {}),
},
];
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', {
'data-space__viewer__activity-bar__item--active': dataProductEditorState.selectedTab === activity.label,
}), onClick: () => dataProductEditorState.setSelectedTab(activity.label), tabIndex: -1, title: activity.title ?? activity.label, style: {
flexDirection: 'column',
fontSize: '12px',
margin: '1rem 0rem',
}, children: [activity.icon, activity.label] }, activity.label))) }) }));
});
const HomeTab = observer((props) => {
const { product, isReadOnly } = props;
const updateDataProductTitle = (val) => {
dataProduct_setTitle(product, val ?? '');
};
const updateDataProductDescription = (event) => {
dataProduct_setDescription(product, event.target.value);
};
const [showPreview, setshowPreview] = useState(false);
return (_jsx("div", { className: "panel__content", children: _jsxs(ResizablePanelGroup, { orientation: "vertical", children: [_jsxs(ResizablePanel, { 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: [_jsxs("div", { className: "panel__content__form__section__header__label", style: { justifyContent: 'space-between', width: '45rem' }, children: ["Description", _jsx("button", { className: "btn__dropdown-combo__label", onClick: () => setshowPreview(!showPreview), title: showPreview ? 'Hide Preview' : 'Preview Description', tabIndex: -1, style: {
width: '12rem',
justifyContent: 'center',
}, children: showPreview ? (_jsxs(_Fragment, { children: [_jsx(CloseEyeIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Hide Preview" })] })) : (_jsxs(_Fragment, { children: [_jsx(EyeIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Preview" })] })) })] }), _jsx("div", { className: "panel__content__form__section__header__prompt", style: {
color: product.description === '' ||
product.description === undefined
? 'var(--color-red-300)'
: 'var(--color-light-grey-400)',
width: '45rem',
}, children: "Clearly describe the purpose, content, and intended use of the Data Product. Markdown is supported." }), _jsx("textarea", { className: "panel__content__form__section__textarea", spellCheck: false, disabled: isReadOnly, value: product.description, onChange: updateDataProductDescription, style: {
padding: '0.5rem',
width: '45rem',
height: '10rem',
borderColor: product.description === '' ||
product.description === undefined
? 'var(--color-red-300)'
: 'transparent',
resize: 'vertical',
maxHeight: '100%',
maxWidth: '100%',
} })] })] }), showPreview && _jsx(ResizablePanelSplitter, {}), showPreview && (_jsx(ResizablePanel, { children: _jsx("div", { className: "text-element-editor__preview", children: _jsx(MarkdownTextViewer, { value: { value: product.description ?? '' }, className: "text-element-editor__preview__markdown" }) }) }))] }) }));
});
const SupportTab = observer((props) => {
const { product, isReadOnly } = props;
const updateSupportInfoDocumentationUrl = (val) => {
dataProduct_setSupportInfoIfAbsent(product);
if (product.supportInfo) {
supportInfo_setDocumentationUrl(product.supportInfo, val ?? '');
}
};
const updateSupportInfoWebsite = (val) => {
dataProduct_setSupportInfoIfAbsent(product);
if (product.supportInfo) {
supportInfo_setWebsite(product.supportInfo, val ?? '');
}
};
const updateSupportInfoFaqUrl = (val) => {
dataProduct_setSupportInfoIfAbsent(product);
if (product.supportInfo) {
supportInfo_setFaqUrl(product.supportInfo, val ?? '');
}
};
const updateSupportInfoSupportUrl = (val) => {
dataProduct_setSupportInfoIfAbsent(product);
if (product.supportInfo) {
supportInfo_setSupportUrl(product.supportInfo, val ?? '');
}
};
const handleSupportInfoEmailAdd = (address, title) => {
dataProduct_setSupportInfoIfAbsent(product);
if (product.supportInfo) {
supportInfo_addEmail(product.supportInfo, new Email(address, title));
}
};
const handleSupportInfoEmailRemove = (email) => {
if (product.supportInfo) {
supportInfo_deleteEmail(product.supportInfo, email);
}
};
const SupportEmailComponent = observer((supportEmailProps) => {
const { item } = supportEmailProps;
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 })] })] }));
});
const NewSupportEmailComponent = observer((newSupportEmailProps) => {
const { onFinishEditing } = newSupportEmailProps;
const [address, setAddress] = useState('');
const [title, setTitle] = useState('');
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) => {
setAddress(event.target.value);
} }) }), _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: "title", placeholder: "Enter title", value: title, onChange: (event) => {
setTitle(event.target.value);
} }) }), _jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", onClick: () => {
handleSupportInfoEmailAdd(address, title);
setAddress('');
setTitle('');
onFinishEditing();
}, children: "Save" })] }));
});
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" })] }));
});
export const DataProductEditor = observer(() => {
const editorStore = useEditorStore();
const dataProductEditorState = editorStore.tabManagerState.getCurrentEditorState(DataProductEditorState);
const product = dataProductEditorState.product;
const isReadOnly = dataProductEditorState.isReadOnly;
const auth = useAuth();
const deployDataProduct = () => {
// Trigger OAuth flow if not authenticated
if (!auth.isAuthenticated) {
// remove this redirect if we move to do oauth at the beginning of opening studio
auth
.signinRedirect({
state: generateUrlToDeployOnOpen(dataProductEditorState),
})
.catch(editorStore.applicationStore.alertUnhandledError);
return;
}
// Use the token for deployment
const token = auth.user?.access_token;
if (token) {
flowResult(dataProductEditorState.deploy(token)).catch(editorStore.applicationStore.alertUnhandledError);
}
else {
editorStore.applicationStore.notificationService.notifyError('Authentication failed. No token available.');
}
};
const selectedActivity = dataProductEditorState.selectedTab;
const renderActivivtyBarTab = () => {
switch (selectedActivity) {
case DATA_PRODUCT_TAB.HOME:
return _jsx(HomeTab, { product: product, isReadOnly: isReadOnly });
case DATA_PRODUCT_TAB.SUPPORT:
return _jsx(SupportTab, { product: product, isReadOnly: isReadOnly });
case DATA_PRODUCT_TAB.APG:
return (_jsx(AccessPointGroupTab, { dataProductEditorState: dataProductEditorState, isReadOnly: isReadOnly }));
default:
return null;
}
};
useApplicationNavigationContext(LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.DATA_PRODUCT_EDITOR);
useEffect(() => {
flowResult(dataProductEditorState.convertAccessPointsFuncObjects()).catch(dataProductEditorState.editorStore.applicationStore.alertUnhandledError);
}, [dataProductEditorState]);
useEffect(() => {
if (dataProductEditorState.deployOnOpen) {
flowResult(dataProductEditorState.deploy(auth.user?.access_token)).catch(editorStore.applicationStore.alertUnhandledError);
}
}, [
auth,
editorStore.applicationStore.alertUnhandledError,
dataProductEditorState,
]);
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()] })] }) }));
});
//# sourceMappingURL=DataPoductEditor.js.map