UNPKG

@finos/legend-application-studio

Version:
584 lines 43.1 kB
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