UNPKG

@finos/legend-application-studio

Version:
299 lines 25.9 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useState, useRef, useCallback } from 'react'; import { flowResult } from 'mobx'; import { Dialog, ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, createFilter, CustomSelectorInput, BlankPanelPlaceholder, PanelLoadingIndicator, TimesIcon, PlayIcon, FlaskIcon, ResizablePanelSplitterLine, compareLabelFn, ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, RefreshIcon, RobotIcon, PanelDropZone, PencilIcon, PanelContent, Modal, ModalTitle, clsx, PanelHeaderActionItem, PanelHeader, PanelHeaderActions, Panel, PauseCircleIcon, } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { getMappingElementSource, getMappingElementTarget, getMappingElementLabel, } from '../../../../stores/editor/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/editor/utils/DnDUtils.js'; import { assertErrorThrown, guaranteeType, uniq } from '@finos/legend-shared'; import { MappingExecutionEmptyInputDataState, MappingExecutionObjectInputDataState, MappingExecutionFlatDataInputDataState, MappingExecutionRelationalInputDataState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingExecutionState.js'; import { ActionAlertActionType, ActionAlertType, useApplicationStore, } from '@finos/legend-application'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { Class, SetImplementation, OperationSetImplementation, getAllClassMappings, RelationalInputType, stub_RawLambda, isStubbed_RawLambda, } from '@finos/legend-graph'; import { objectInputData_setData } from '../../../../stores/graph-modifier/DSL_Mapping_GraphModifierHelper.js'; import { flatData_setData } from '../../../../stores/graph-modifier/STO_FlatData_GraphModifierHelper.js'; import { relationalInputData_setData, relationalInputData_setInputType, } from '../../../../stores/graph-modifier/STO_Relational_GraphModifierHelper.js'; import { MappingExecutionQueryBuilderState } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingExecutionQueryBuilderState.js'; import { ExecutionPlanViewer, } from '@finos/legend-query-builder'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { CodeEditor } from '@finos/legend-lego/code-editor'; export const ClassMappingSelectorModal = observer((props) => { const { mappingEditorState, changeClassMapping, hideClassMappingSelectorModal, classMappingFilterFn, } = props; const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; // Class mapping selector const classMappingSelectorRef = useRef(null); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option) => getMappingElementLabel(option.data.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, classes: { container: 'search-modal__container' }, slotProps: { transition: { onEnter: handleEnterClassMappingSelectorModal, }, paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs(Modal, { className: clsx('search-modal', { 'modal--dark': true, }), children: [_jsx(ModalTitle, { title: "Choose a class mapping" }), _jsx(CustomSelectorInput, { inputRef: classMappingSelectorRef, options: classMappingOptions, onChange: changeClassMappingOption, value: null, placeholder: "Choose a class mapping...", filterOption: filterOption, isClearable: true, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled })] }) })); }); export const getRelationalInputTestDataEditorLanguage = (type) => { switch (type) { case RelationalInputType.SQL: return CODE_EDITOR_LANGUAGE.SQL; default: return CODE_EDITOR_LANGUAGE.TEXT; } }; const MappingExecutionQueryEditor = observer((props) => { const { executionState } = props; const queryState = executionState.queryState; const mappingEditorState = executionState.mappingEditorState; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); // actions const editWithQueryBuilder = applicationStore.guardUnhandledError(async () => { const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState; await flowResult(embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({ setupQueryBuilderState: async () => { const queryBuilderState = new MappingExecutionQueryBuilderState(embeddedQueryBuilderState.editorStore.applicationStore, embeddedQueryBuilderState.editorStore.graphManagerState, executionState.mappingEditorState.mapping, editorStore.applicationStore.config.options.queryBuilderConfig, editorStore.editorMode.getSourceInfo()); queryBuilderState.initializeWithQuery(executionState.queryState.query); return queryBuilderState; }, actionConfigs: [ { key: 'save-query-btn', renderer: (queryBuilderState) => { const save = applicationStore.guardUnhandledError(async () => { try { const rawLambda = queryBuilderState.buildQuery(); await flowResult(executionState.queryState.updateLamba(rawLambda)); applicationStore.notificationService.notifySuccess(`Mapping execution query is updated`); embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration(undefined); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError(`Can't save query: ${error.message}`); } }); return (_jsx("button", { className: "query-builder__dialog__header__custom-action", tabIndex: -1, onClick: save, children: "Save Query" })); }, }, ], disableCompile: isStubbed_RawLambda(executionState.queryState.query), })); }); // 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.createGetAllRawLambda(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.notificationService.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 { applicationStore.alertService.setActionAlertInfo({ message: 'Mapping execution input data is already set', prompt: 'Do you want to regenerate the input data?', type: ActionAlertType.CAUTION, 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 }, dropConnector] = 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(Panel, { className: "mapping-execution-builder__query-panel", children: [_jsx(PanelHeader, { title: "query", children: _jsxs(PanelHeaderActions, { children: [_jsx(PanelHeaderActionItem, { onClick: editWithQueryBuilder, title: "Edit query...", children: _jsx(PencilIcon, {}) }), _jsx(PanelHeaderActionItem, { onClick: clearQuery, title: "Clear query", children: _jsx(TimesIcon, {}) })] }) }), !isStubbed_RawLambda(queryState.query) && (_jsx(PanelContent, { children: _jsx("div", { className: "mapping-execution-builder__query-panel__query", children: _jsx(CodeEditor, { inputValue: queryState.lambdaString, isReadOnly: true, language: CODE_EDITOR_LANGUAGE.PURE, hideMinimap: true }) }) })), isStubbed_RawLambda(queryState.query) && (_jsx(PanelContent, { children: _jsx(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isDragOver, children: _jsx(BlankPanelPlaceholder, { text: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to start building the query", isDropZoneActive: 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(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { language: CODE_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(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { language: CODE_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(PanelContent, { className: "mapping-execution-builder__input-data-panel__content", children: _jsx(CodeEditor, { 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 }, dropConnector] = 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(PanelContent, { children: _jsx(PanelDropZone, { dropTargetConnector: dropConnector, isDragOver: isDragOver, children: _jsx(BlankPanelPlaceholder, { text: "Choose a class mapping", onClick: showClassMappingSelectorModal, clickActionType: "add", tooltipText: "Drop a class mapping, or click to choose one to generate input data", isDropZoneActive: canDrop }) }) })); }); const RelationalMappingExecutionInputDataTypeSelector = observer((props) => { const { inputDataState } = props; const changeInputType = (val) => () => { relationalInputData_setInputType(inputDataState.inputData, val); }; return (_jsx(ControlledDropdownMenu, { className: "mapping-execution-builder__input-data-panel__type-selector", title: "Choose input data type...", 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", 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 cancelExecution = applicationStore.guardUnhandledError(() => flowResult(executionState.cancelExecution())); 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, {}) })), _jsx("div", { className: "mapping-execution-builder__action-btn btn__dropdown-combo btn__dropdown-combo--primary", children: executionState.isExecuting ? (_jsx("button", { className: "btn__dropdown-combo__canceler", onClick: cancelExecution, tabIndex: -1, children: _jsxs("div", { className: "btn--dark btn--caution btn__dropdown-combo__canceler__label", children: [_jsx(PauseCircleIcon, { className: "btn__dropdown-combo__canceler__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__canceler__label__title", children: "Stop" })] }) })) : (_jsxs(_Fragment, { children: [_jsxs("button", { className: "btn__dropdown-combo__label", onClick: execute, disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isGeneratingPlan || executionState.isExecuting, tabIndex: -1, children: [_jsx(PlayIcon, { className: "btn__dropdown-combo__label__icon" }), _jsx("div", { className: "btn__dropdown-combo__label__title", children: "Run Query" })] }), _jsx(ControlledDropdownMenu, { className: "btn__dropdown-combo__dropdown-btn", disabled: isStubbed_RawLambda(queryState.query) || !inputDataState.isValid || executionState.isGeneratingPlan || executionState.isExecuting, content: _jsxs(MenuContent, { children: [_jsx(MenuContentItem, { className: "btn__dropdown-combo__option", onClick: generatePlan, children: "Generate Plan" }), _jsx(MenuContentItem, { className: "btn__dropdown-combo__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(Panel, { className: "mapping-execution-builder__result-panel", children: [_jsx(PanelHeader, { title: "result" }), _jsx(PanelContent, { className: "mapping-execution-builder__result-panel__content", children: _jsx(CodeEditor, { inputValue: executionResultText ?? '', isReadOnly: true, language: CODE_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