UNPKG

@finos/legend-application-studio

Version:
647 lines (646 loc) 97.2 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, DATA_PRODUCT_TYPE, DataProductEditorState, generateUrlToDeployOnOpen, LakehouseAccessPointState, ModelAccessPointGroupState, } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js'; import { BugIcon, BuildingIcon, CaretDownIcon, Checkbox, CloseEyeIcon, clsx, compressImage, ControlledDropdownMenu, CustomSelectorInput, Dialog, DragPreviewLayer, ErrorWarnIcon, EyeIcon, GroupWorkIcon, HomeIcon, IconSelectorGrid, IconSelectorIcons, InfoCircleIcon, ListEditor, LockIcon, MenuContent, MenuContentItem, Modal, ModalBody, ModalFooter, ModalFooterButton, ModalHeader, ModalTitle, PanelContent, PanelDnDEntry, PanelEntryDragHandle, PanelFormTextField, PanelHeader, PanelHeaderActionItem, PanelHeaderActions, PencilEditIcon, PlusIcon, QuestionCircleIcon, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, RocketIcon, Switch, TimesIcon, Tooltip, UploadIcon, useDragPreviewLayer, WarningIcon, LongArrowRightIcon, PURE_MappingIcon, GitBranchIcon, ListIcon, PanelLoadingIndicator, GearSuggestIcon, } from '@finos/legend-art'; import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from 'react'; import { filterByType, guaranteeType, UserSearchService, } from '@finos/legend-shared'; import { InlineLambdaEditor, LineageViewer } from '@finos/legend-query-builder'; import { action, autorun, 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 { DataProductEmbeddedImageIcon, DataProductLibraryIcon, Email, LakehouseTargetEnv, StereotypeExplicitReference, V1_DataProduct, V1_DataProductIconLibraryId, V1_PureGraphManager, V1_RemoteEngine, validate_PureExecutionMapping, InternalDataProductType, ExternalDataProductType, DataProductLink, observer_DataProductLink, DataProduct_Region, DataProduct_DeliveryFrequency, } from '@finos/legend-graph'; import { accessPoint_setClassification, accessPoint_setReproducible, accessPointGroup_setDescription, accessPointGroup_setName, dataProduct_setDescription, dataProduct_setIcon, dataProduct_setSupportInfoIfAbsent, dataProduct_setTitle, supportInfo_addEmail, supportInfo_deleteEmail, supportInfo_setDocumentationUrl, supportInfo_setFaqUrl, supportInfo_setLinkLabel, supportInfo_setSupportUrl, supportInfo_setWebsite, dataProduct_setType, expertise_setDescription, expertise_addId, expertise_deleteId, supportInfo_deleteExpertise, externalType_setLinkURL, externalType_setLinkLabel, accessPointGroup_setTitle, accessPoint_setDescription, accessPoint_setTitle, dataProduct_setOperationalMetadataIfAbsent, operationalMetadata_deleteCoverageRegion, operationalMetadata_addCoverageRegion, dataProductDiagram_setTitle, dataProductDiagram_setDescription, operationalMetadata_setUpdateFrequency, } 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'; import { buildElementOption, } from '@finos/legend-lego/graph-editor'; import { DataProductViewerState, ProductViewer, } from '@finos/legend-extension-dsl-data-product'; import { RelationElementEditor } from '../data-editor/RelationElementsDataEditor.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), style: { whiteSpace: 'pre-line' }, children: text })); }; const hoverIcon = () => { return (_jsx("div", { "data-testid": LEGEND_STUDIO_TEST_ID.HOVER_EDIT_ICON, children: _jsx(PencilEditIcon, {}) })); }; const AccessPointTitle = observer((props) => { const { accessPointState } = props; const accessPoint = accessPointState.accessPoint; const [editingName, setEditingName] = useState(accessPoint.id === newNamePlaceholder); const handleNameEdit = () => setEditingName(true); const handleNameBlur = () => { if (accessPoint.id !== newNamePlaceholder) { setEditingName(false); const relationElement = accessPointState.relationElementState?.relationElement; if (relationElement) { relationElement.paths[0] = accessPoint.id; } } }; const updateAccessPointName = action((event) => { if (event.target.value.match(/^[0-9a-zA-Z_]*$/)) { 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", { className: "access-point-editor__name__label", onClick: handleNameEdit, title: "Click to edit access point name", style: { flex: '1 1 auto' }, children: accessPoint.id })); }); const AccessPointClassification = observer((props) => { const { accessPoint, groupState } = props; const applicationStore = useEditorStore().applicationStore; const CHOOSE_CLASSIFICATION = '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", { style: { borderWidth: 'thin', borderColor: currentClassification.label === CHOOSE_CLASSIFICATION ? 'var(--color-red-300)' : 'transparent', }, 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, {}) }) })] })); }); const AccessPointGenerationViewer = observer((props) => { const { accessPointState, generationOutput } = props; const editorStore = accessPointState.state.state.editorStore; const closeDebug = () => { accessPointState.setArtifactContent(undefined); }; return (_jsx(Dialog, { open: accessPointState.artifactGenerationContent !== undefined, onClose: closeDebug, classes: { root: 'editor-modal__root-container', container: 'editor-modal__container', paper: 'editor-modal__content', }, children: _jsxs(Modal, { className: "editor-modal", darkMode: !editorStore.applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, children: [_jsx(ModalHeader, { title: `${accessPointState.accessPoint.id} Plan Generation` }), _jsx(ModalBody, { children: _jsx("div", { className: "panel__content execution-plan-viewer__panel__content", children: _jsx(CodeEditor, { inputValue: generationOutput, isReadOnly: true, language: CODE_EDITOR_LANGUAGE.JSON, hidePadding: true, hideMinimap: true }) }) }), _jsx(ModalFooter, { children: _jsx(ModalFooterButton, { title: "Close plan generation modal", onClick: closeDebug, text: "Close", type: "secondary" }) })] }) })); }); const SampleValuesEditorModal = observer((props) => { const { accessPointState, isReadOnly } = props; const editorStore = accessPointState.state.state.editorStore; const dataElementPath = accessPointState.relationElementExistsinDataElementReference(); const closeModal = () => { accessPointState.setShowSampleValuesModal(false); }; const handleDeleteSampleValues = () => { editorStore.applicationStore.alertService.setActionAlertInfo({ message: `Are you sure you want to delete sample values for Access Point ${accessPointState.accessPoint.id}?`, type: ActionAlertType.CAUTION, actions: [ { label: 'Confirm', type: ActionAlertActionType.PROCEED_WITH_CAUTION, handler: () => { accessPointState.deleteRelationElement(); closeModal(); }, }, { label: 'Cancel', type: ActionAlertActionType.PROCEED, default: true, }, ], }); }; const handleNavigateToDataElement = () => { if (dataElementPath) { const dataElement = editorStore.graphManagerState.graph.getNullableElement(dataElementPath); if (dataElement) { editorStore.graphEditorMode.openElement(dataElement); closeModal(); } } }; return (_jsx(Dialog, { open: accessPointState.showSampleValuesModal, onClose: closeModal, classes: { root: 'editor-modal__root-container', container: 'editor-modal__container', paper: 'editor-modal__content', }, children: _jsxs(Modal, { className: "editor-modal", darkMode: !editorStore.applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, children: [_jsx(ModalHeader, { title: `${accessPointState.accessPoint.title ?? accessPointState.accessPoint.id} Sample Values` }), _jsx(ModalBody, { children: _jsx("div", { style: { padding: 0, height: '100%', flex: 1, overflowY: 'auto', overflowX: 'hidden', }, children: dataElementPath !== undefined ? (_jsxs("div", { style: { padding: '1.5rem', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '1rem', }, children: [_jsx("span", { style: { color: 'var(--color-light-grey-200)', whiteSpace: 'nowrap', fontSize: '1.3rem', fontWeight: 600, }, children: "Sample Values already in Data Element" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.5rem 1rem', background: 'var(--color-dark-grey-100)', border: '1px solid var(--color-dark-grey-300)', borderRadius: '0.2rem', }, children: [_jsx("span", { style: { fontSize: '1.3rem', fontWeight: 600, color: 'var(--color-light-grey-200)', }, children: dataElementPath }), _jsx("button", { className: "btn--sm btn--dark", onClick: handleNavigateToDataElement, tabIndex: -1, title: "Navigate to data element", style: { padding: '0.3rem 0.5rem', display: 'flex', alignItems: 'center', }, children: _jsx(LongArrowRightIcon, {}) })] })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { style: { color: 'var(--color-orange-200)', fontWeight: 'bold', fontSize: '14px', padding: '0.5rem', marginBottom: '1rem', textAlign: 'center', backgroundColor: 'var(--color-orange-50)', border: '2px solid var(--color-orange-200)', borderRadius: '4px', }, children: "DO NOT ADD SENSITIVE DATA" }), accessPointState.relationElementState && (_jsx(Tooltip, { title: accessPointState.getRelationElementMismatchMessage() ?? '', arrow: true, placement: "top", disableHoverListener: !accessPointState.hasRelationElementMismatch, children: _jsx("div", { style: { border: accessPointState.hasRelationElementMismatch ? '2px solid var(--color-red-300)' : 'none', borderRadius: '4px', padding: accessPointState.hasRelationElementMismatch ? '0.5rem' : '0', }, children: _jsx(RelationElementEditor, { relationElementState: accessPointState.relationElementState, isReadOnly: isReadOnly }) }) }))] })) }) }), _jsxs(ModalFooter, { children: [dataElementPath === undefined && (_jsx(ModalFooterButton, { title: "Delete sample values", onClick: handleDeleteSampleValues, text: "Delete", type: "secondary", disabled: isReadOnly })), _jsx(ModalFooterButton, { title: "Close sample values editor", onClick: closeModal, text: "Close", type: "secondary" })] })] }) })); }); export const LakehouseDataProductAccessPointEditor = 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 [isHoveringDesc, setIsHoveringDesc] = useState(false); const [editingTitle, setEditingTitle] = useState(false); const [isHoveringTitle, setIsHoveringTitle] = useState(false); const ref = useRef(null); const handleEditorBlur = useCallback(() => { flowResult(lambdaEditorState.updateLambdaRelationColumns()).catch(editorStore.applicationStore.alertUnhandledError); }, [lambdaEditorState, editorStore.applicationStore]); const handleDescriptionEdit = () => setEditingDescription(true); const handleDescriptionBlur = () => { setEditingDescription(false); setIsHoveringDesc(false); }; const handleMouseOverDesc = () => { setIsHoveringDesc(true); }; const handleMouseOutDesc = () => { setIsHoveringDesc(false); }; const handleTitleEdit = () => setEditingTitle(true); const handleTitleBlur = () => { setEditingTitle(false); setIsHoveringTitle(false); }; const handleMouseOverTitle = () => { setIsHoveringTitle(true); }; const handleMouseOutTitle = () => { setIsHoveringTitle(false); }; const updateAccessPointTargetEnvironment = action((targetEnvironment) => { accessPoint.targetEnvironment = targetEnvironment; }); const debugPlanGeneration = editorStore.applicationStore.guardUnhandledError(() => flowResult(accessPointState.generateArtifact())); 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); const generateLineage = editorStore.applicationStore.guardUnhandledError(() => flowResult(accessPointState.generateLineage())); 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, { accessPointState: accessPointState }), _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: _jsx("div", { style: { maxWidth: '400px', whiteSpace: 'normal', wordWrap: 'break-word', }, children: "Marking as \"reproducible\" means consumers can consistently retrieve the exact historical data as it existed at any specific Lakehouse batch." }), arrow: true, placement: 'top', children: _jsx("div", { children: "Reproducible" }) })] }), accessPointState.relationElementExistsinDataElementReference() !== undefined ? (_jsxs("button", { className: "access-point-editor__sample-values-btn", onClick: () => accessPointState.setShowSampleValuesModal(true), disabled: props.isReadOnly, title: "Edit sample values", style: { border: '1px solid var(--color-blue-200)', borderRadius: '4px', padding: '0.5rem 0.75rem', background: 'var(--color-blue-200)', cursor: props.isReadOnly ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'white', fontSize: '1.2rem', whiteSpace: 'nowrap', }, children: [_jsx(PencilEditIcon, {}), _jsx("span", { children: "Sample Values" })] })) : accessPointState.relationElementState !== undefined ? (_jsxs("button", { className: "access-point-editor__sample-values-btn", onClick: () => accessPointState.setShowSampleValuesModal(true), disabled: props.isReadOnly, title: "Edit sample values", style: { border: accessPointState.hasRelationElementMismatch ? '2px solid var(--color-red-300)' : '1px solid var(--color-blue-200)', borderRadius: '4px', padding: '0.5rem 0.75rem', background: 'var(--color-blue-200)', cursor: props.isReadOnly ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'white', fontSize: '1.2rem', whiteSpace: 'nowrap', }, children: [_jsx(PencilEditIcon, {}), _jsx("span", { children: "Sample Values" })] })) : (_jsxs("button", { className: "access-point-editor__sample-values-btn", onClick: () => { accessPointState.createAndaddRelationElement(); }, disabled: props.isReadOnly, title: "Add sample values", style: { border: '1px solid var(--color-blue-200)', borderRadius: '4px', padding: '0.5rem 0.75rem', background: 'var(--color-blue-200)', cursor: props.isReadOnly ? 'not-allowed' : 'pointer', display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'white', fontSize: '1.2rem', whiteSpace: 'nowrap', }, children: [_jsx(PlusIcon, {}), _jsx("span", { children: "Sample Values" })] })), 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, {}) })] }), _jsx(ControlledDropdownMenu, { className: "access-point-editor__dropdown", content: _jsxs(MenuContent, { children: [_jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: debugPlanGeneration, children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center', }, children: [_jsx(BugIcon, {}), " ", _jsx("span", { children: "AP Plan Generation" })] }) }), _jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: generateLineage, children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', alignItems: 'center', }, children: [_jsx("span", { style: { display: 'flex', alignItems: 'center', transform: 'rotate(90deg)', }, children: _jsx(GitBranchIcon, {}) }), _jsx("span", { children: "Lineage Viewer" })] }) })] }), menuProps: { anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, }, children: _jsx("div", { className: "access-point-editor__generic-entry__remove-btn__debug", tabIndex: -1, title: "Access Point Tools", style: { background: 'var(--color-blue-200)', borderRadius: '4px', marginRight: '1.5rem', padding: '0.25rem 0.5rem', display: 'flex', alignItems: 'center', color: 'white', }, children: _jsx(ListIcon, {}) }) })] })] }), editingTitle ? (_jsx("textarea", { className: "access-point-editor__name", spellCheck: false, value: accessPoint.title, onChange: (event) => accessPoint_setTitle(accessPoint, event.target.value), placeholder: 'Access Point Title', onBlur: handleTitleBlur, style: { borderColor: 'transparent', margin: '0.5rem', } })) : (_jsxs("div", { onClick: handleTitleEdit, title: "Click to edit access point title", className: "access-point-editor__description-container", children: [accessPoint.title ? (_jsx(HoverTextArea, { text: accessPoint.title, handleMouseOver: handleMouseOverTitle, handleMouseOut: handleMouseOutTitle, className: "access-point-editor__title" })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOverTitle, onMouseOut: handleMouseOutTitle, style: { fontSize: '15px' }, children: [_jsx(ErrorWarnIcon, {}), "Provide a title for this Access Point"] })), isHoveringTitle && hoverIcon()] })), editingDescription ? (_jsx("textarea", { className: "panel__content__form__section__input", spellCheck: false, value: accessPoint.description ?? '', onChange: (event) => accessPoint_setDescription(accessPoint, event.target.value), placeholder: "Access Point description", onBlur: handleDescriptionBlur, style: { resize: 'vertical', padding: '0.25rem', marginLeft: '0.5rem', marginTop: '0.5rem', height: 'auto', } })) : (_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: handleMouseOverDesc, handleMouseOut: handleMouseOutDesc })) : (_jsxs("div", { className: "access-point-editor__group-container__description--warning", onMouseOver: handleMouseOverDesc, onMouseOut: handleMouseOutDesc, children: [_jsx(ErrorWarnIcon, {}), AP_EMPTY_DESC_WARNING] })), isHoveringDesc && hoverIcon()] })), _jsx("div", { className: "access-point-editor__content", children: _jsx("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), onEditorBlur: handleEditorBlur }) }) }) }) }), _jsx(LineageViewer, { lineageState: accessPointState.lineageState })] }), _jsx("button", { className: "access-point-editor__generic-entry__remove-btn", onClick: () => { handleRemoveAccessPoint(); }, tabIndex: -1, title: "Remove", children: _jsx(TimesIcon, {}) }), accessPointState.artifactGenerationContent && (_jsx(AccessPointGenerationViewer, { accessPointState: accessPointState, generationOutput: accessPointState.artifactGenerationContent })), accessPointState.showSampleValuesModal && (_jsx(SampleValuesEditorModal, { accessPointState: accessPointState, isReadOnly: props.isReadOnly }))] }) })); }); 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."] })); }); export const CompatibleDiagramsEditor = observer((props) => { const { groupState } = props; const group = groupState.value; const handleDiagramTitleChange = (diagram, value) => { dataProductDiagram_setTitle(diagram, value ?? ''); }; const handleDiagramDescriptionChange = (diagram, value) => { dataProductDiagram_setDescription(diagram, value); }; const handleAddDiagram = (option) => { groupState.addDiagram(option); }; const handleRemoveDiagram = (diagram) => { groupState.handleRemoveDiagram(diagram); }; // ListEditor component renderers const DiagramComponent = observer((diagramComponentProps) => { const { item } = diagramComponentProps; return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "panel__content__form__section__list__item__content", children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Diagram" }), _jsx("div", { className: "panel__content__form__section__list__item__content__title", children: item.diagram.name })] }), _jsxs("div", { className: "panel__content__form__section__list__item__form", children: [_jsx(PanelFormTextField, { name: "Title", value: item.title, update: (value) => handleDiagramTitleChange(item, value), placeholder: "Enter title", className: "dataSpace-editor__general__diagrams__title" }), _jsx(PanelFormTextField, { name: "Description", value: item.description ?? '', update: (value) => handleDiagramDescriptionChange(item, value), placeholder: "Enter description", className: "dataSpace-editor__general__diagrams__description" })] })] })); }); const hasMappingSet = group.mapping.value.path !== ''; const noDiagramsInProject = groupState.getCompatibleDiagramOptions().length === 0 && group.diagrams.length === 0; const [isGenerating, setIsGenerating] = useState(false); const [isAddingDiagram, setIsAddingDiagram] = useState(false); const handleGenerateFromMapping = () => { setIsGenerating(true); flowResult(groupState.generateDiagramFromMapping()) .catch(groupState.state.editorStore.applicationStore.alertUnhandledError) .finally(() => setIsGenerating(false)); }; const NewDiagramComponent = observer((newDiagramProps) => { const { onFinishEditing } = newDiagramProps; useLayoutEffect(() => { setIsAddingDiagram(true); return () => setIsAddingDiagram(false); }, []); return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__list__new-item__input", children: _jsx(CustomSelectorInput, { options: groupState.getCompatibleDiagramOptions(), onChange: (event) => { onFinishEditing(); handleAddDiagram(event); }, placeholder: "Select a diagram to add...", darkMode: !groupState.state.editorStore.applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled }) }), noDiagramsInProject && hasMappingSet && !groupState.state.isReadOnly && (_jsx("button", { className: "panel__content__form__section__list__new-item__cancel-btn btn btn--dark", disabled: isGenerating, onClick: () => { onFinishEditing(); handleGenerateFromMapping(); }, title: "Auto-generate a diagram from the mapping", tabIndex: -1, children: isGenerating ? 'Generating...' : 'Generate from Mapping' }))] })); }); return (_jsxs("div", { className: "data-product-editor__diagrams-section", children: [_jsx(ListEditor, { title: "Diagrams", prompt: "Add diagrams to include in this Data Product. Diagrams are required to showcase and explain your curated data models", items: group.diagrams, keySelector: (element) => element.diagram.name, ItemComponent: DiagramComponent, NewItemComponent: NewDiagramComponent, handleRemoveItem: handleRemoveDiagram, isReadOnly: groupState.state.isReadOnly, emptyMessage: "\u26A0 No Diagrams specified. Add at least one diagram to this Data Product.", emptyClassName: "data-product-editor__empty-diagram" }), noDiagramsInProject && !isAddingDiagram && hasMappingSet && !groupState.state.isReadOnly && (_jsx("div", { className: "data-product-editor__generate-diagram-btn", children: _jsx("button", { className: "panel__content__form__section__list__new-item__add-btn btn btn--dark", disabled: isGenerating, onClick: handleGenerateFromMapping, title: "Generate a Diagram from the Mapping", tabIndex: -1, children: isGenerating ? 'Generating...' : 'Generate from Mapping' }) }))] })); }); export const FeaturedElementsEditor = observer((props) => { const { groupState, isReadOnly } = props; const group = groupState.value; // Event handlers const handleAddElement = (option) => { if (typeof option.value === 'object') { groupState.addFeaturedElement(option.value); } }; const handleRemoveElement = (element) => { groupState.removeFeaturedElement(element); }; const handleElementExcludeChange = (element, event) => { groupState.excludeFeaturedElement(element, event.target.checked); }; // ListEditor component renderers const ElementComponent = observer((elementComponentProps) => { const { item } = elementComponentProps; return (_jsxs("div", { className: "data-product-editor__element-item", children: [_jsx("div", { className: "panel__content__form__section__list__item__content__label", children: item.element.value.path }), _jsx("div", { className: "panel__content__form__section__list__item__content__actions", children: _jsxs("div", { className: "panel__content__form__section__list__item__content__actions-exclude", children: [_jsx(Checkbox, { disabled: isReadOnly, checked: item.exclude ?? false, onChange: (event) => handleElementExcludeChange(item, event), size: "small", className: "panel__content__form__section__list__item__content__actions-exclude__btn" }), _jsx("span", { className: "panel__content__form__section__list__item__content__actions__label", children: "Exclude" })] }) })] })); }); const NewElementComponent = observer((newElementProps) => { const { onFinishEditing } = newElementProps; return (_jsx("div", { className: "panel__content__form__section__list__new-item__input", children: _jsx(CustomSelectorInput, { options: groupState.getFeaturedElementOptions(), onChange: (event) => { onFinishEditing(); handleAddElement(event); }, placeholder: "Select an element to add...", darkMode: !groupState.state.editorStore.applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled }) })); }); return (_jsx(ListEditor, { title: "Featured Elements", prompt: "Add classes and associations to display under Models Documentation. Use the exclude checkbox to exclude certain elements from this Data Product entirely.", items: group.featuredElements, keySelector: (element) => element.element.value.path, ItemComponent: ElementComponent, NewItemComponent: NewElementComponent, handleRemoveItem: handleRemoveElement, isReadOnly: isReadOnly, emptyMessage: "No elements specified" })); }); const ModelAccessPointGroupEditor = observer((props) => { const { groupState, isReadOnly } = props; const editorStore = useEditorStore(); // mapping const mapping = groupState.value.mapping; const isMappingEmpty = validate_PureExecutionMapping(groupState.value.mapping.value); const mappingOptions = groupState.state.editorStore.graphManagerState.usableMappings.map(buildElementOption); const selectedMappingOption = { value: mapping.value, label: mapping.value.path === '' ? '(none)' : mapping.value.path, }; const onMappingChange = (val) => { if (val.value !== mapping.value) { groupState.setMapping(val.value); } }; const visitElement = (element) => { editorStore.graphEditorMode.openElement(element); }; return (_jsxs("div", { className: "data-product-editor__model-apg-editor", children: [_jsx("div", { className: "data-product-editor__model-apg-editor__label", children: "Mapping" }), _jsxs("div", { className: "service-execution-editor__configuration__item", style: { padding: '0 1rem' }, children: [_jsx("div", { className: "btn--sm service-execution-editor__configuration__item__label", children: _jsx(PURE_MappingIcon, {}) }), _jsx(CustomSelectorInput, { className: "panel__content__form__section__dropdown service-execution-editor__configuration__item__dropdown", disabled: isReadOnly, options: mappingOptions, onChange: onMappingChange, value: selectedMappingOption, darkMode: !groupState.state.editorStore.applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, hasError: Boolean(isMappingEmpty) }), _jsx("button", { className: "btn--dark btn--sm service-execution-editor__configuration__item__btn", onClick: () => { visitElement(groupState.value.mapping.value); }, tabIndex: -1, title: "See mapping", disabled: Boolean(isMappingEmpty), children: _jsx(LongArrowRightIcon, {}) })] }), _jsx(FeaturedElementsEditor, { groupState: groupState, isReadOnly: isReadOnly }), _jsx(CompatibleDiagramsEditor, { groupState: groupState })] })); }); 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 [editingTitle, setEditingTitle] = useState(false); const [isHoveringTitle, setIsHoveringTitle] = 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.match(/^[0-9a-zA-Z_]+$/)) { accessPointGroup_setName(groupState.value, val); } }; const handleTitleEdit = () => setEditingTitle(true); const handleTitleBlur = () => { setEditingTitle(false); setIsHoveringTitle(false); }; const handleMouseOverTitle = () => { setIsHoveringTitle(true); }; const handleMouseOutTitle = () => { setIsHoveringTitle(false); }; const updateGroupTitle = (val) => { accessPointGroup_setTitle(groupState.value, val); }; const handleRemoveAccessPointGroup = () => { editorStore.applicationStore.alertService.setActionAlertInfo({ message: groupState instanceof ModelAccessPointGroupState ? `Are you sure you want to delete Model Access Point Group "${groupState.value.id}"?` : `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); };