@finos/legend-application-studio
Version:
Legend Studio application core
299 lines • 26.6 kB
JavaScript
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