UNPKG

@finos/legend-studio

Version:
266 lines 23.3 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 { Fragment, useState, useRef, useCallback } from 'react'; import { flowResult } from 'mobx'; import { Dialog, ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, createFilter, CustomSelectorInput, BlankPanelPlaceholder, PanelLoadingIndicator, TimesIcon, PlayIcon, FlaskIcon, ResizablePanelSplitterLine, compareLabelFn, DropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, RefreshIcon, RobotIcon, } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { getMappingElementSource, getMappingElementTarget, getMappingElementLabel, } from '../../../../stores/editor-state/element-editor-state/mapping/MappingEditorState.js'; import { useDrop } from 'react-dnd'; import { NewServiceModal } from '../service-editor/NewServiceModal.js'; import { CORE_DND_TYPE, } from '../../../../stores/shared/DnDUtil.js'; import { guaranteeType, uniq } from '@finos/legend-shared'; import { MappingExecutionEmptyInputDataState, MappingExecutionObjectInputDataState, MappingExecutionFlatDataInputDataState, MappingExecutionRelationalInputDataState, } from '../../../../stores/editor-state/element-editor-state/mapping/MappingExecutionState.js'; import { EDITOR_LANGUAGE, ActionAlertActionType, ActionAlertType, useApplicationStore, ExecutionPlanViewer, } from '@finos/legend-application'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { Class, SetImplementation, OperationSetImplementation, getAllClassMappings, RelationalInputType, stub_RawLambda, isStubbed_RawLambda, } from '@finos/legend-graph'; import { StudioTextInputEditor } from '../../../shared/StudioTextInputEditor.js'; import { objectInputData_setData } from '../../../../stores/graphModifier/DSLMapping_GraphModifierHelper.js'; import { flatData_setData } from '../../../../stores/graphModifier/StoreFlatData_GraphModifierHelper.js'; import { relationalInputData_setData, relationalInputData_setInputType, } from '../../../../stores/graphModifier/StoreRelational_GraphModifierHelper.js'; export const ClassMappingSelectorModal = observer((props) => { const { mappingEditorState, changeClassMapping, hideClassMappingSelectorModal, classMappingFilterFn, } = props; const editorStore = useEditorStore(); // Class mapping selector const classMappingSelectorRef = useRef(null); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option) => getMappingElementLabel(option.value, editorStore).value, }); const classMappingOptions = uniq(getAllClassMappings(mappingEditorState.mapping) .filter((classMapping) => !classMappingFilterFn || classMappingFilterFn(classMapping)) .map((classMapping) => ({ label: getMappingElementLabel(classMapping, editorStore).value, value: classMapping, })) .sort(compareLabelFn)); const handleEnterClassMappingSelectorModal = () => classMappingSelectorRef.current?.focus(); const changeClassMappingOption = (val) => changeClassMapping(val.value); return (_jsx(Dialog, { open: true, onClose: hideClassMappingSelectorModal, TransitionProps: { onEnter: handleEnterClassMappingSelectorModal, }, classes: { container: 'search-modal__container' }, PaperProps: { classes: { root: 'search-modal__inner-container' } }, children: _jsxs("div", { className: "modal search-modal", children: [_jsx("div", { className: "modal__title", children: "Choose a class mapping" }), _jsx(CustomSelectorInput, { ref: classMappingSelectorRef, options: classMappingOptions, onChange: changeClassMappingOption, value: null, placeholder: 'Choose a class mapping...', filterOption: filterOption, isClearable: true })] }) })); }); export const getRelationalInputTestDataEditorLanguage = (type) => { switch (type) { case RelationalInputType.SQL: return EDITOR_LANGUAGE.SQL; default: return EDITOR_LANGUAGE.TEXT; } }; const MappingExecutionQueryEditor = observer((props) => { const { executionState } = props; const queryState = executionState.queryState; const mappingEditorState = executionState.mappingEditorState; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const extraQueryEditorActions = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraMappingExecutionQueryEditorActionConfigurations?.() ?? []) .map((config) => (_jsx(Fragment, { children: config.renderer(executionState) }, config.key))); // Class mapping selector const [openClassMappingSelectorModal, setOpenClassMappingSelectorModal] = useState(false); const showClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(true); const hideClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(false); const changeClassMapping = useCallback((setImplementation) => { // do all the necessary updates executionState.setExecutionResultText(undefined); flowResult(queryState.updateLamba(setImplementation ? editorStore.graphManagerState.graphManager.HACKY__createGetAllLambda(guaranteeType(getMappingElementTarget(setImplementation), Class)) : stub_RawLambda())).catch(applicationStore.alertUnhandledError); hideClassMappingSelectorModal(); // Attempt to generate data for input data panel as we pick the class mapping: // - If the source panel is empty right now, automatically try to generate input data: // - We generate based on the class mapping, if it's concrete // - If the class mapping is operation, output a warning message // - If the source panel is non-empty (show modal), show an option to keep current input data if (setImplementation) { if (executionState.inputDataState instanceof MappingExecutionEmptyInputDataState) { if (setImplementation instanceof OperationSetImplementation) { applicationStore.notifyWarning(`Can't auto-generate input data for operation class mapping. Please pick a concrete class mapping instead`); } else { executionState.setInputDataStateBasedOnSource(getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins()), true); } } else { editorStore.setActionAlertInfo({ message: 'Mapping execution input data is already set', prompt: 'Do you want to regenerate the input data?', type: ActionAlertType.CAUTION, onEnter: () => editorStore.setBlockGlobalHotkeys(true), onClose: () => editorStore.setBlockGlobalHotkeys(false), actions: [ { label: 'Regenerate', type: ActionAlertActionType.PROCEED_WITH_CAUTION, handler: () => executionState.setInputDataStateBasedOnSource(getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins()), true), }, { label: 'Keep my input data', type: ActionAlertActionType.PROCEED, default: true, }, ], }); } } // TODO: open query builder }, [applicationStore, editorStore, executionState, queryState]); // Drag and Drop const handleDrop = useCallback((item) => { changeClassMapping(guaranteeType(item.data, SetImplementation)); }, [changeClassMapping]); const [{ isDragOver, canDrop }, dropRef] = useDrop(() => ({ accept: CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING, drop: (item) => handleDrop(item), collect: (monitor) => ({ isDragOver: monitor.isOver({ shallow: true }), canDrop: monitor.canDrop(), }), }), [handleDrop]); const clearQuery = applicationStore.guardUnhandledError(() => flowResult(executionState.queryState.updateLamba(stub_RawLambda()))); return (_jsxs("div", { className: "panel mapping-execution-builder__query-panel", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "query" }) }), _jsxs("div", { className: "panel__header__actions", children: [extraQueryEditorActions, _jsx("button", { className: "panel__header__action", tabIndex: -1, onClick: clearQuery, title: 'Clear query', children: _jsx(TimesIcon, {}) })] })] }), !isStubbed_RawLambda(queryState.query) && (_jsx("div", { className: "panel__content", children: _jsx("div", { className: "mapping-execution-builder__query-panel__query", children: _jsx(StudioTextInputEditor, { inputValue: queryState.lambdaString, isReadOnly: true, language: EDITOR_LANGUAGE.PURE, showMiniMap: false }) }) })), isStubbed_RawLambda(queryState.query) && (_jsx("div", { ref: dropRef, className: "panel__content", children: _jsx(BlankPanelPlaceholder, { placeholderText: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to start building the query", dndProps: { isDragOver: isDragOver, canDrop: canDrop, } }) })), openClassMappingSelectorModal && (_jsx(ClassMappingSelectorModal, { mappingEditorState: mappingEditorState, hideClassMappingSelectorModal: hideClassMappingSelectorModal, changeClassMapping: changeClassMapping }))] })); }); export const MappingExecutionObjectInputDataBuilder = observer((props) => { const { inputDataState } = props; // TODO?: handle XML/type // Input data const updateInput = (val) => objectInputData_setData(inputDataState.inputData, val); return (_jsx("div", { className: "panel__content mapping-execution-builder__input-data-panel__content", children: _jsx(StudioTextInputEditor, { language: EDITOR_LANGUAGE.JSON, inputValue: inputDataState.inputData.data, updateInput: updateInput }) })); }); export const MappingExecutionFlatDataInputDataBuilder = observer((props) => { const { inputDataState } = props; // Input data const updateInput = (val) => flatData_setData(inputDataState.inputData, val); return (_jsx("div", { className: "panel__content mapping-execution-builder__input-data-panel__content", children: _jsx(StudioTextInputEditor, { language: EDITOR_LANGUAGE.TEXT, inputValue: inputDataState.inputData.data, updateInput: updateInput }) })); }); /** * Right now, we always default this to use Local H2 connection. */ export const MappingExecutionRelationalInputDataBuilder = observer((props) => { const { inputDataState } = props; // Input data const updateInput = (val) => relationalInputData_setData(inputDataState.inputData, val); return (_jsx("div", { className: "panel__content mapping-execution-builder__input-data-panel__content", children: _jsx(StudioTextInputEditor, { language: getRelationalInputTestDataEditorLanguage(inputDataState.inputData.inputType), inputValue: inputDataState.inputData.data, updateInput: updateInput }) })); }); export const MappingExecutionEmptyInputDataBuilder = observer((props) => { const { changeClassMapping, showClassMappingSelectorModal } = props; // Drag and Drop const handleDrop = useCallback((item) => { changeClassMapping(guaranteeType(item.data, SetImplementation)); }, [changeClassMapping]); const [{ isDragOver, canDrop }, dropRef] = useDrop(() => ({ accept: CORE_DND_TYPE.MAPPING_EXPLORER_CLASS_MAPPING, drop: (item) => handleDrop(item), collect: (monitor) => ({ isDragOver: monitor.isOver({ shallow: true }), canDrop: monitor.canDrop(), }), }), [handleDrop]); return (_jsx("div", { ref: dropRef, className: "panel__content", children: _jsx(BlankPanelPlaceholder, { placeholderText: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to generate input data", dndProps: { isDragOver: isDragOver, canDrop: canDrop, } }) })); }); const RelationalMappingExecutionInputDataTypeSelector = observer((props) => { const { inputDataState } = props; const changeInputType = (val) => () => { relationalInputData_setInputType(inputDataState.inputData, val); }; return (_jsx(DropdownMenu, { className: "mapping-execution-builder__input-data-panel__type-selector", content: _jsx(MenuContent, { children: Object.keys(RelationalInputType).map((mode) => (_jsx(MenuContentItem, { className: "mapping-execution-builder__input-data-panel__type-selector__option", onClick: changeInputType(mode), children: mode }, mode))) }), children: _jsxs("div", { className: "mapping-execution-builder__input-data-panel__type-selector__value", title: "Choose input data type...", children: [_jsx("div", { className: "mapping-execution-builder__input-data-panel__type-selector__value__label", children: inputDataState.inputData.inputType }), _jsx(CaretDownIcon, {})] }) })); }); export const MappingExecutionInputDataBuilder = observer((props) => { const { executionState } = props; const editorStore = useEditorStore(); const mappingEditorState = executionState.mappingEditorState; const inputDataState = executionState.inputDataState; // Class mapping selector const [openClassMappingSelectorModal, setOpenClassMappingSelectorModal] = useState(false); const showClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(true); const hideClassMappingSelectorModal = () => setOpenClassMappingSelectorModal(false); const changeClassMapping = useCallback((setImplementation) => { executionState.setInputDataStateBasedOnSource(setImplementation ? getMappingElementSource(setImplementation, editorStore.pluginManager.getApplicationPlugins()) : undefined, true); executionState.setExecutionResultText(undefined); hideClassMappingSelectorModal(); }, [executionState, editorStore]); const classMappingFilterFn = (setImp) => !(setImp instanceof OperationSetImplementation); // Input data builder let inputDataBuilder; if (inputDataState instanceof MappingExecutionEmptyInputDataState) { inputDataBuilder = (_jsx(MappingExecutionEmptyInputDataBuilder, { inputDataState: inputDataState, showClassMappingSelectorModal: showClassMappingSelectorModal, changeClassMapping: changeClassMapping })); } else if (inputDataState instanceof MappingExecutionObjectInputDataState) { inputDataBuilder = (_jsx(MappingExecutionObjectInputDataBuilder, { inputDataState: inputDataState })); } else if (inputDataState instanceof MappingExecutionFlatDataInputDataState) { inputDataBuilder = (_jsx(MappingExecutionFlatDataInputDataBuilder, { inputDataState: inputDataState })); } else if (inputDataState instanceof MappingExecutionRelationalInputDataState) { inputDataBuilder = (_jsx(MappingExecutionRelationalInputDataBuilder, { inputDataState: inputDataState })); } else { inputDataBuilder = null; } // input type builder let inputTypeSelector; if (inputDataState instanceof MappingExecutionRelationalInputDataState) { inputTypeSelector = (_jsx(RelationalMappingExecutionInputDataTypeSelector, { inputDataState: inputDataState })); } else { inputTypeSelector = null; } const clearInputData = () => executionState.setInputDataState(new MappingExecutionEmptyInputDataState(mappingEditorState.editorStore, mappingEditorState.mapping, undefined)); return (_jsxs("div", { className: "panel mapping-execution-builder__input-data-panel", children: [_jsxs("div", { className: "panel__header", children: [_jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "input data" }) }), _jsxs("div", { className: "panel__header__actions", children: [inputTypeSelector, _jsx("button", { className: "panel__header__action", tabIndex: -1, onClick: showClassMappingSelectorModal, title: 'Regenerate...', children: _jsx(RefreshIcon, { className: "mapping-execution-builder__icon--refresh" }) }), _jsx("button", { className: "panel__header__action", tabIndex: -1, onClick: clearInputData, title: 'Clear input data', children: _jsx(TimesIcon, {}) })] })] }), inputDataBuilder, openClassMappingSelectorModal && (_jsx(ClassMappingSelectorModal, { mappingEditorState: mappingEditorState, hideClassMappingSelectorModal: hideClassMappingSelectorModal, changeClassMapping: changeClassMapping, classMappingFilterFn: classMappingFilterFn }))] })); }); export const MappingExecutionBuilder = observer((props) => { const { executionState } = props; const mappingEditorState = executionState.mappingEditorState; const applicationStore = useApplicationStore(); const { queryState, inputDataState } = executionState; // execute const generatePlan = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(false))); const debugPlanGeneration = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(true))); const execute = applicationStore.guardUnhandledError(() => flowResult(executionState.executeMapping())); const executionResultText = executionState.executionResultText; // actions const promote = applicationStore.guardUnhandledError(() => flowResult(executionState.promoteToTest())); const promoteToService = () => executionState.setShowServicePathModal(true); return (_jsxs("div", { className: "mapping-execution-builder", children: [_jsx(PanelLoadingIndicator, { isLoading: executionState.isExecuting || executionState.isGeneratingPlan }), _jsxs("div", { className: "mapping-execution-builder__header", children: [_jsx("div", {}), _jsxs("div", { className: "mapping-execution-builder__header__actions", children: [!mappingEditorState.isReadOnly && (_jsx("button", { className: "mapping-execution-builder__header__action", disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isExecuting || !executionState.executionResultText, onClick: promoteToService, tabIndex: -1, title: "Promote to Service...", children: _jsx(RobotIcon, {}) })), !mappingEditorState.isReadOnly && (_jsx("button", { className: "mapping-execution-builder__header__action", disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isExecuting || !executionState.executionResultText, onClick: promote, tabIndex: -1, title: "Promote to Test", children: _jsx(FlaskIcon, {}) })), _jsxs("button", { className: "mapping-execution-builder__execute-btn", onClick: execute, disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isGeneratingPlan || executionState.isExecuting, tabIndex: -1, children: [_jsxs("div", { className: "mapping-execution-builder__execute-btn__label", children: [_jsx(PlayIcon, { className: "mapping-execution-builder__execute-btn__label__icon" }), _jsx("div", { className: "mapping-execution-builder__execute-btn__label__title", children: "Execute" })] }), _jsx(DropdownMenu, { className: "mapping-execution-builder__execute-btn__dropdown-btn", disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isGeneratingPlan || executionState.isExecuting, content: _jsxs(MenuContent, { children: [_jsx(MenuContentItem, { className: "mapping-execution-builder__execute-btn__option", onClick: generatePlan, children: "Generate Plan" }), _jsx(MenuContentItem, { className: "mapping-execution-builder__execute-btn__option", onClick: debugPlanGeneration, children: "Debug" })] }), menuProps: { anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, }, children: _jsx(CaretDownIcon, {}) })] })] })] }), _jsx("div", { className: "mapping-execution-builder__content", children: _jsxs(ResizablePanelGroup, { orientation: "horizontal", children: [_jsx(ResizablePanel, { size: 250, minSize: 28, children: _jsx(MappingExecutionQueryEditor, { executionState: executionState }, executionState.queryState.uuid) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-50)" }) }), _jsx(ResizablePanel, { size: 250, minSize: 28, children: _jsx(MappingExecutionInputDataBuilder, { executionState: executionState }, executionState.inputDataState.uuid) }), _jsx(ResizablePanelSplitter, { children: _jsx(ResizablePanelSplitterLine, { color: "var(--color-dark-grey-50)" }) }), _jsx(ResizablePanel, { minSize: 28, children: _jsxs("div", { className: "panel mapping-execution-builder__result-panel", children: [_jsx("div", { className: "panel__header", children: _jsx("div", { className: "panel__header__title", children: _jsx("div", { className: "panel__header__title__label", children: "result" }) }) }), _jsx("div", { className: "panel__content mapping-execution-builder__result-panel__content", children: _jsx(StudioTextInputEditor, { inputValue: executionResultText ?? '', isReadOnly: true, language: EDITOR_LANGUAGE.JSON }) })] }) })] }) }), _jsx(ExecutionPlanViewer, { executionPlanState: executionState.executionPlanState }), _jsx(NewServiceModal, { mapping: mappingEditorState.mapping, close: () => executionState.setShowServicePathModal(false), showModal: executionState.showServicePathModal, promoteToService: (packagePath, name) => flowResult(executionState.promoteToService(packagePath, name)), isReadOnly: mappingEditorState.isReadOnly })] })); }); //# sourceMappingURL=MappingExecutionBuilder.js.map