UNPKG

@finos/legend-application-studio

Version:
299 lines 26.6 kB
import { jsx as _jsx, jsxs as _jsxs } 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 { useEffect, useCallback, useState, forwardRef } from 'react'; import { observer } from 'mobx-react-lite'; import { ServiceEditorState } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceEditorState.js'; import { SingleServicePureExecutionState, ServicePureExecutionState, MultiServicePureExecutionState, InlineServicePureExecutionState, } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceExecutionState.js'; import { EmbeddedRuntimeEditor } from '../RuntimeEditor.js'; import { useDrop } from 'react-dnd'; import { CORE_DND_TYPE, } from '../../../../stores/editor/utils/DnDUtils.js'; import { UnsupportedEditorPanel } from '../UnsupportedElementEditor.js'; import { clsx, ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, ResizablePanelSplitterLine, PURE_MappingIcon, PURE_RuntimeIcon, ErrorIcon, CogIcon, LongArrowRightIcon, ExclamationTriangleIcon, CustomSelectorInput, ContextMenu, KeyIcon, MenuContent, MenuContentItem, Dialog, InputWithInlineValidation, BlankPanelPlaceholder, PlusIcon, ArrowsJoinIcon, ArrowsSplitIcon, PanelDropZone, Panel, PanelContent, ModalTitle, PanelHeaderActionItem, PanelHeaderActions, PanelHeader, } from '@finos/legend-art'; import { ServiceExecutionQueryEditor } from './ServiceExecutionQueryEditor.js'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { Mapping, RuntimePointer, PackageableRuntime, PackageableElementExplicitReference, validate_PureExecutionMapping, } from '@finos/legend-graph'; import { guaranteeNonNullable } from '@finos/legend-shared'; import { buildElementOption, } from '@finos/legend-lego/graph-editor'; import { CUSTOM_LABEL } from '../../../../stores/editor/NewElementState.js'; const PureExecutionContextConfigurationEditor = observer((props) => { const { executionContextState, pureExecutionState } = props; const executionContext = executionContextState.executionContext; const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const serviceState = editorStore.tabManagerState.getCurrentEditorState(ServiceEditorState); const isReadOnly = serviceState.isReadOnly; // mapping // TODO: this is not generic error handling, as there could be other problems // with mapping, we need to genericize this const isMappingEmpty = validate_PureExecutionMapping(executionContext.mapping.value); const mapping = executionContext.mapping.value; const mappingOptions = editorStore.graphManagerState.usableMappings.map(buildElementOption); const noMappingLabel = (_jsxs("div", { className: "service-execution-editor__configuration__mapping-option--empty", title: isMappingEmpty?.messages.join('\n') ?? '', children: [_jsx("div", { className: "service-execution-editor__configuration__mapping-option--empty__label", children: "(none)" }), _jsx(ErrorIcon, {})] })); const selectedMappingOption = { value: mapping, label: isMappingEmpty ? noMappingLabel : mapping.path, }; const onMappingSelectionChange = (val) => { if (val.value !== mapping) { executionContextState.setMapping(val.value); pureExecutionState.autoSelectRuntimeOnMappingChange(val.value); } }; const visitMapping = () => editorStore.graphEditorMode.openElement(mapping); // runtime const runtime = executionContext.runtime; const isRuntimePointer = runtime instanceof RuntimePointer; const customRuntimeLabel = (_jsxs("div", { className: "service-execution-editor__configuration__runtime-option--custom", children: [_jsx(CogIcon, {}), _jsx("div", { className: "service-execution-editor__configuration__runtime-option--custom__label", children: CUSTOM_LABEL })] })); // only show custom runtime option when a runtime pointer is currently selected let runtimeOptions = !isRuntimePointer ? [] : [{ label: customRuntimeLabel }]; // NOTE: for now, only include runtime associated with the mapping // TODO?: Should we bring the runtime compatibility check from query to here? const runtimes = editorStore.graphManagerState.graph.runtimes.filter((rt) => rt.runtimeValue.mappings.map((m) => m.value).includes(mapping)); runtimeOptions = runtimeOptions.concat(runtimes.map((rt) => ({ label: rt.path, value: new RuntimePointer(PackageableElementExplicitReference.create(rt)), }))); const runtimePointerWarning = runtime instanceof RuntimePointer && !runtimes.includes(runtime.packageableRuntime.value) // if the runtime does not belong to the chosen mapping ? `runtime is not associated with specified mapping '${mapping.path}'` : undefined; const selectedRuntimeOption = { value: runtime, label: runtime instanceof RuntimePointer ? (_jsxs("div", { className: "service-execution-editor__configuration__runtime-option__pointer", title: undefined, children: [_jsx("div", { className: clsx('service-execution-editor__configuration__runtime-option__pointer__label', { 'service-execution-editor__configuration__runtime-option__pointer__label--with-warning': Boolean(runtimePointerWarning), }), children: runtime.packageableRuntime.value.path }), runtimePointerWarning && (_jsx("div", { className: "service-execution-editor__configuration__runtime-option__pointer__warning", title: runtimePointerWarning, children: _jsx(ExclamationTriangleIcon, {}) }))] })) : (customRuntimeLabel), }; const onRuntimeSelectionChange = (val) => { if (val.value === undefined) { pureExecutionState.useCustomRuntime(); } else if (val.value !== runtime) { executionContextState.setRuntime(val.value); } }; const visitRuntime = () => { if (runtime instanceof RuntimePointer) { editorStore.graphEditorMode.openElement(runtime.packageableRuntime.value); } }; const openRuntimeEditor = () => pureExecutionState.openRuntimeEditor(); // DnD const handleMappingOrRuntimeDrop = useCallback((item) => { const element = item.data.packageableElement; if (!isReadOnly) { if (element instanceof Mapping) { executionContextState.setMapping(element); pureExecutionState.autoSelectRuntimeOnMappingChange(element); } else if (element instanceof PackageableRuntime && element.runtimeValue.mappings.map((m) => m.value).includes(mapping)) { executionContextState.setRuntime(new RuntimePointer(PackageableElementExplicitReference.create(element))); } } }, [isReadOnly, mapping, executionContextState, pureExecutionState]); const [{ isMappingOrRuntimeDragOver }, dropConnector] = useDrop(() => ({ accept: [ CORE_DND_TYPE.PROJECT_EXPLORER_MAPPING, CORE_DND_TYPE.PROJECT_EXPLORER_RUNTIME, ], drop: (item) => handleMappingOrRuntimeDrop(item), collect: (monitor) => ({ isMappingOrRuntimeDragOver: monitor.isOver({ shallow: true }), }), }), [handleMappingOrRuntimeDrop]); // close runtime editor as we leave service editor useEffect(() => () => pureExecutionState.closeRuntimeEditor(), [executionContextState, pureExecutionState]); return (_jsx(PanelContent, { children: _jsx(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isMappingOrRuntimeDragOver && !isReadOnly, children: _jsxs("div", { className: "service-execution-editor__configuration__items", children: [_jsxs("div", { className: "service-execution-editor__configuration__item", 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: onMappingSelectionChange, value: selectedMappingOption, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, hasError: Boolean(isMappingEmpty) }), _jsx("button", { className: "btn--dark btn--sm service-execution-editor__configuration__item__btn", onClick: visitMapping, tabIndex: -1, title: "See mapping", children: _jsx(LongArrowRightIcon, {}) })] }), _jsxs("div", { className: "service-execution-editor__configuration__item", children: [_jsx("div", { className: "btn--sm service-execution-editor__configuration__item__label", children: _jsx(PURE_RuntimeIcon, {}) }), _jsx(CustomSelectorInput, { className: "panel__content__form__section__dropdown service-execution-editor__configuration__item__dropdown", disabled: isReadOnly, options: runtimeOptions, onChange: onRuntimeSelectionChange, value: selectedRuntimeOption, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled }), !isRuntimePointer && (_jsx("button", { className: "btn--sm btn--dark service-execution-editor__configuration__item__btn", disabled: Boolean(isReadOnly || isMappingEmpty), onClick: openRuntimeEditor, tabIndex: -1, title: isReadOnly ? 'See runtime' : 'Configure custom runtime', children: _jsx(CogIcon, {}) })), isRuntimePointer && (_jsx("button", { className: "btn--sm btn--dark service-execution-editor__configuration__item__btn", onClick: visitRuntime, tabIndex: -1, title: "See runtime", children: _jsx(LongArrowRightIcon, {}) })), _jsx(EmbeddedRuntimeEditor, { runtimeEditorState: pureExecutionState.runtimeEditorState, isReadOnly: serviceState.isReadOnly, onClose: () => pureExecutionState.closeRuntimeEditor() })] })] }) }) })); }); export const ChangeExecutionModal = observer((props) => { const { executionState, isReadOnly } = props; const applicationStore = executionState.editorStore.applicationStore; const closeModal = () => executionState.setShowChangeExecModal(false); const isChangingToMulti = executionState instanceof SingleServicePureExecutionState; const renderChangeExecution = () => { if (executionState instanceof SingleServicePureExecutionState) { const keyValue = executionState.multiExecutionKey; const validationMessage = keyValue === '' ? `Key value can't be empty` : undefined; const onChange = (event) => { executionState.setMultiExecutionKey(event.target.value); }; return (_jsx(InputWithInlineValidation, { className: "service-execution-editor__input input-group__input", spellCheck: false, value: keyValue, onChange: onChange, placeholder: "Multi Execution Key Name", error: validationMessage })); } else if (executionState instanceof MultiServicePureExecutionState) { const currentOption = executionState.singleExecutionKey ? { value: executionState.singleExecutionKey, label: executionState.singleExecutionKey.key, } : undefined; const multiOptions = executionState.keyedExecutionParameters.map((keyExecutionParameter) => ({ value: keyExecutionParameter, label: keyExecutionParameter.key, })); const _onChange = (val) => { if (val === null) { executionState.setSingleExecutionKey(undefined); } else if (val.value !== currentOption?.value) { executionState.setSingleExecutionKey(val.value); } }; return (_jsx(CustomSelectorInput, { className: "panel__content__form__section__dropdown", options: multiOptions, onChange: _onChange, value: currentOption, escapeClearsValue: true, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, disabled: isReadOnly })); } return null; }; return (_jsx(Dialog, { open: executionState.showChangeExecModal, onClose: closeModal, classes: { container: 'search-modal__container' }, slotProps: { paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs("form", { onSubmit: (event) => { event.preventDefault(); executionState.changeExecution(); closeModal(); }, className: "modal modal--dark search-modal", children: [_jsx("div", { className: "modal__title", children: `Change to ${isChangingToMulti ? 'multi' : 'single'} execution` }), _jsx("div", { className: "service-execution-editor__change__modal", children: renderChangeExecution() }), _jsx("div", { className: "search-modal__actions", children: _jsx("button", { className: "btn btn--dark", disabled: isReadOnly || executionState.isChangeExecutionDisabled(), children: "Change" }) })] }) })); }); const PureSingleExecutionEditor = observer((props) => { const { singleExecutionState } = props; // close runtime editor as we leave service editor useEffect(() => () => singleExecutionState.closeRuntimeEditor(), [singleExecutionState]); return (_jsx("div", { className: "service-execution-editor__execution", children: _jsx(Panel, { children: _jsx("div", { className: "panel__content service-editor__content", children: _jsx(PureExecutionContextConfigurationEditor, { pureExecutionState: singleExecutionState, executionContextState: singleExecutionState.selectedExecutionContextState }) }) }) })); }); const KeyExecutionContextMenu = observer(forwardRef(function TestContainerContextMenu(props, ref) { const { multiExecutionState, keyExecutionParameter } = props; const rename = () => { multiExecutionState.setRenameKey(keyExecutionParameter); }; const remove = () => { multiExecutionState.deleteKeyExecutionParameter(keyExecutionParameter); }; const add = () => { multiExecutionState.setNewKeyParameterModal(true); }; return (_jsxs(MenuContent, { ref: ref, children: [_jsx(MenuContentItem, { onClick: rename, children: "Rename" }), _jsx(MenuContentItem, { onClick: remove, children: "Delete" }), _jsx(MenuContentItem, { onClick: add, children: "Create a new key" })] })); })); const KeyExecutionItem = observer((props) => { const { multiExecutionState, keyExecutionParameter, isReadOnly } = props; const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] = useState(false); const isActive = multiExecutionState.selectedExecutionContextState?.executionContext === keyExecutionParameter; const openKeyedExecution = () => multiExecutionState.changeKeyedExecutionParameter(keyExecutionParameter); const onContextMenuOpen = () => setIsSelectedFromContextMenu(true); const onContextMenuClose = () => setIsSelectedFromContextMenu(false); return (_jsx(ContextMenu, { className: clsx('service-multi-execution-editor__item', { 'service-multi-execution-editor__item--selected-from-context-menu': !isActive && isSelectedFromContextMenu, }, { 'service-multi-execution-editor__item--active': isActive }), disabled: isReadOnly, content: _jsx(KeyExecutionContextMenu, { multiExecutionState: multiExecutionState, keyExecutionParameter: keyExecutionParameter, isReadOnly: isReadOnly }), menuProps: { elevation: 7 }, onOpen: onContextMenuOpen, onClose: onContextMenuClose, children: _jsx("button", { className: clsx('service-multi-execution-editor__item__label'), onClick: openKeyedExecution, tabIndex: -1, children: keyExecutionParameter.key }) })); }); export const NewExecutionParameterModal = observer((props) => { const [keyValue, setKeyValue] = useState(''); const { executionState, isReadOnly } = props; const validationMessage = keyValue === '' ? `Execution context key can't be empty` : executionState.execution.executionParameters?.find((e) => e.key === keyValue) ? 'Execution context key already exists' : undefined; const closeModal = () => executionState.setNewKeyParameterModal(false); const onChange = (event) => { setKeyValue(event.target.value); }; return (_jsx(Dialog, { open: executionState.newKeyParameterModal, onClose: closeModal, classes: { container: 'search-modal__container' }, slotProps: { paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs("form", { onSubmit: (event) => { event.preventDefault(); executionState.addExecutionParameter(keyValue); setKeyValue(''); closeModal(); }, className: "modal modal--dark search-modal", children: [_jsx(ModalTitle, { title: "New Execution Context" }), _jsx("div", { className: "service-execution-editor__change__modal", children: _jsx(InputWithInlineValidation, { className: "service-execution-editor__input input-group__input", spellCheck: false, value: keyValue, onChange: onChange, placeholder: "Key execution name", error: validationMessage }) }), _jsx("div", { className: "search-modal__actions", children: _jsx("button", { className: "btn btn--dark", disabled: isReadOnly || Boolean(validationMessage), children: "Add" }) })] }) })); }); const RenameModal = observer((props) => { const { val, isReadOnly, showModal, closeModal, setValue } = props; const [inputValue, setInputValue] = useState(val); const changeValue = (event) => { setInputValue(event.target.value); }; return (_jsx(Dialog, { open: showModal, onClose: closeModal, classes: { container: 'search-modal__container' }, slotProps: { paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs("form", { onSubmit: (event) => { event.preventDefault(); setValue(inputValue); closeModal(); }, className: "modal modal--dark search-modal", children: [_jsx(ModalTitle, { title: "Rename" }), _jsx("input", { className: "panel__content__form__section__input", spellCheck: false, disabled: isReadOnly, value: inputValue, onChange: changeValue }), _jsx("div", { className: "search-modal__actions", children: _jsx("button", { className: "btn btn--dark", disabled: isReadOnly, children: "Rename" }) })] }) })); }); const MultiPureExecutionEditor = observer((props) => { const { multiExecutionState } = props; const multiExecution = multiExecutionState.execution; const key = multiExecution.executionKey; const addExecutionKey = () => { multiExecutionState.setNewKeyParameterModal(true); }; const editReviewTitle = (event) => { multiExecutionState.setExecutionKey(event.target.value); }; return (_jsx("div", { className: "service-execution-editor__execution", children: _jsxs(ResizablePanelGroup, { orientation: "vertical", children: [_jsxs(ResizablePanel, { size: 300, minSize: 200, children: [_jsx("div", { className: "service-multi-execution-editor__header", children: _jsxs("div", { className: "service-multi-execution-editor__header__content", children: [_jsx("div", { className: "btn--sm service-multi-execution-editor__header__content__label", children: _jsx(KeyIcon, {}) }), _jsx("input", { className: "service-multi-execution-editor__header__content__input input--dark", spellCheck: false, value: key, onChange: editReviewTitle, placeholder: "Execution context key" })] }) }), _jsxs("div", { className: "service-multi-execution-editor__panel", children: [_jsxs(PanelHeader, { children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__content", children: "Execution Contexts" }) }), _jsx(PanelHeaderActions, { children: _jsx(PanelHeaderActionItem, { disabled: multiExecutionState.serviceEditorState.isReadOnly, onClick: addExecutionKey, title: "Add an execution context", children: _jsx(PlusIcon, {}) }) })] }), multiExecutionState.keyedExecutionParameters.map((executionParameter) => (_jsx(KeyExecutionItem, { multiExecutionState: multiExecutionState, keyExecutionParameter: executionParameter, isReadOnly: multiExecutionState.serviceEditorState.isReadOnly }, executionParameter.key))), !multiExecutionState.keyedExecutionParameters.length && (_jsx(BlankPanelPlaceholder, { text: "Add an execution context", onClick: addExecutionKey, clickActionType: "add", tooltipText: "Click to add an execution context" }))] }), multiExecutionState.newKeyParameterModal && (_jsx(NewExecutionParameterModal, { executionState: multiExecutionState, isReadOnly: multiExecutionState.serviceEditorState.isReadOnly })), multiExecutionState.renameKey && (_jsx(RenameModal, { val: multiExecutionState.renameKey.key, isReadOnly: multiExecutionState.serviceEditorState.isReadOnly, showModal: true, closeModal: () => multiExecutionState.setRenameKey(undefined), setValue: (val) => multiExecutionState.changeKeyValue(guaranteeNonNullable(multiExecutionState.renameKey), val) }))] }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { minSize: 56, children: multiExecutionState.selectedExecutionContextState ? (_jsx(PureExecutionContextConfigurationEditor, { pureExecutionState: multiExecutionState, executionContextState: multiExecutionState.selectedExecutionContextState })) : (_jsx(BlankPanelPlaceholder, { text: "Add an execution context", onClick: addExecutionKey, clickActionType: "add", tooltipText: "Click to add an execution context" })) })] }) })); }); const ServicePureExecutionEditor = observer((props) => { const { servicePureExecutionState, isReadOnly } = props; const serviceState = servicePureExecutionState.serviceEditorState; const isMultiExecution = servicePureExecutionState instanceof MultiServicePureExecutionState; const showChangeExecutionModal = () => { if (servicePureExecutionState instanceof MultiServicePureExecutionState) { servicePureExecutionState.setSingleExecutionKey(servicePureExecutionState.keyedExecutionParameters[0]); } servicePureExecutionState.setShowChangeExecModal(true); }; const renderExecutionEditor = () => { if (servicePureExecutionState instanceof SingleServicePureExecutionState) { return (_jsx(PureSingleExecutionEditor, { singleExecutionState: servicePureExecutionState })); } else if (servicePureExecutionState instanceof MultiServicePureExecutionState) { return (_jsx(MultiPureExecutionEditor, { multiExecutionState: servicePureExecutionState })); } return (_jsx(UnsupportedEditorPanel, { text: "Can't display this execution in form-mode", isReadOnly: serviceState.isReadOnly })); }; if (servicePureExecutionState instanceof InlineServicePureExecutionState) { return (_jsx("div", { className: "service-execution-editor", children: _jsx(ServiceExecutionQueryEditor, { executionState: servicePureExecutionState, isReadOnly: isReadOnly }) })); } else { return (_jsx("div", { className: "service-execution-editor", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { size: 500, minSize: 28, children: _jsx(ServiceExecutionQueryEditor, { executionState: servicePureExecutionState, isReadOnly: isReadOnly }) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-200)" }) }), _jsx(ResizablePanel, { minSize: 56, children: _jsx("div", { className: "service-execution-editor__content", children: _jsxs(Panel, { children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label service-editor__execution__label--test", children: "context" }) }), _jsx("div", { className: "service-multi-execution-editor__actions", children: (servicePureExecutionState instanceof SingleServicePureExecutionState || servicePureExecutionState instanceof MultiServicePureExecutionState) && (_jsx("button", { className: "service-multi-execution-editor__action", tabIndex: -1, onClick: showChangeExecutionModal, title: `Switch to ${servicePureExecutionState instanceof SingleServicePureExecutionState ? 'multi execution' : 'single execution'}`, children: isMultiExecution ? (_jsx(ArrowsJoinIcon, {})) : (_jsx(ArrowsSplitIcon, {})) })) })] }), _jsxs("div", { className: "panel__content service-execution-editor__configuration__content", children: [renderExecutionEditor(), servicePureExecutionState.showChangeExecModal && (_jsx(ChangeExecutionModal, { executionState: servicePureExecutionState, isReadOnly: servicePureExecutionState.serviceEditorState .isReadOnly }))] })] }) }) })] }) })); } }); export const ServiceExecutionEditor = observer(() => { const editorStore = useEditorStore(); const serviceState = editorStore.tabManagerState.getCurrentEditorState(ServiceEditorState); const executionState = serviceState.executionState; const isReadOnly = serviceState.isReadOnly; if (executionState instanceof ServicePureExecutionState) { return (_jsx(ServicePureExecutionEditor, { servicePureExecutionState: executionState, isReadOnly: isReadOnly })); } return (_jsx(UnsupportedEditorPanel, { text: "Can't display this service execution in form-mode", isReadOnly: serviceState.isReadOnly })); }); //# sourceMappingURL=ServiceExecutionEditor.js.map