@finos/legend-application-studio
Version:
Legend Studio application core
647 lines (646 loc) • 97.2 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, 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);
};