UNPKG

@finos/legend-application-studio

Version:
401 lines 25.1 kB
import { jsx as _jsx, Fragment as _Fragment, 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 React, { useRef } from 'react'; import { LEGEND_STUDIO_TEST_ID } from '../../../__lib__/LegendStudioTesting.js'; import { observer } from 'mobx-react-lite'; import { NewPackageableRuntimeDriver, NewPackageableConnectionDriver, NewPureModelConnectionDriver, NewFileGenerationDriver, resolvePackageAndElementName, NewDataElementDriver, NewServiceDriver, CONNECTION_TYPE, NewLakehouseDataProductDriver, NewRuntimeType, } from '../../../stores/editor/NewElementState.js'; import { Dialog, compareLabelFn, CustomSelectorInput, PanelBooleanField, } from '@finos/legend-art'; import { guaranteeNonNullable, prettyCONSTName, toTitleCase, } from '@finos/legend-shared'; import { useEditorStore } from '../EditorStoreProvider.js'; import { ELEMENT_PATH_DELIMITER, } from '@finos/legend-graph'; import { flowResult } from 'mobx'; import { useApplicationStore } from '@finos/legend-application'; import { buildElementOption, getPackageableElementOptionFormatter, } from '@finos/legend-lego/graph-editor'; import { PACKAGEABLE_ELEMENT_TYPE } from '../../../stores/editor/utils/ModelClassifierUtils.js'; import { EmbeddedDataType } from '../../../stores/editor/editor-state/ExternalFormatState.js'; export const getElementTypeLabel = (editorStore, type) => { switch (type) { case PACKAGEABLE_ELEMENT_TYPE.PACKAGE: case PACKAGEABLE_ELEMENT_TYPE.CLASS: case PACKAGEABLE_ELEMENT_TYPE.ENUMERATION: case PACKAGEABLE_ELEMENT_TYPE.ASSOCIATION: case PACKAGEABLE_ELEMENT_TYPE.MEASURE: case PACKAGEABLE_ELEMENT_TYPE.PROFILE: case PACKAGEABLE_ELEMENT_TYPE.FUNCTION: case PACKAGEABLE_ELEMENT_TYPE.MAPPING: case PACKAGEABLE_ELEMENT_TYPE.CONNECTION: case PACKAGEABLE_ELEMENT_TYPE.RUNTIME: case PACKAGEABLE_ELEMENT_TYPE.SERVICE: return type.toLowerCase(); case PACKAGEABLE_ELEMENT_TYPE.FLAT_DATA_STORE: return 'flat-data store'; case PACKAGEABLE_ELEMENT_TYPE.DATABASE: return 'relational database'; case PACKAGEABLE_ELEMENT_TYPE.SERVICE_STORE: return 'service store'; case PACKAGEABLE_ELEMENT_TYPE.FILE_GENERATION: return 'file generation'; case PACKAGEABLE_ELEMENT_TYPE.GENERATION_SPECIFICATION: return 'generation specification'; case PACKAGEABLE_ELEMENT_TYPE.DATA: return 'data'; case PACKAGEABLE_ELEMENT_TYPE.TEMPORARY__LOCAL_CONNECTION: return 'local connection'; case PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT: return 'Lakehouse Data Product'; default: { if (type) { const extraElementTypeLabelGetters = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraElementTypeLabelGetters?.() ?? []); for (const typeLabelGetter of extraElementTypeLabelGetters) { const label = typeLabelGetter(type); if (label) { return label; } } return type.toLowerCase(); } return undefined; } } }; const buildElementTypeOption = (type) => ({ label: type, value: type, }); const NewDataElementDriverEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const newDataELementDriver = editorStore.newElementState.getNewElementDriver(NewDataElementDriver); const selectedOption = newDataELementDriver.embeddedDataOption ? { label: prettyCONSTName(newDataELementDriver.embeddedDataOption.label), value: newDataELementDriver.embeddedDataOption.value, } : undefined; const extraOptionTypes = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraEmbeddedDataTypeOptions?.() ?? []); let options = Object.values(EmbeddedDataType) .filter((type) => type !== EmbeddedDataType.DATA_ELEMENT) .map((typeOption) => ({ label: prettyCONSTName(typeOption), value: typeOption, })); options = options.concat(extraOptionTypes); const onTypeSelectionChange = (val) => { if (!val) { newDataELementDriver.setEmbeddedDataOption(undefined); } else { newDataELementDriver.setEmbeddedDataOption(val); } }; return (_jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: options, onChange: onTypeSelectionChange, value: selectedOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) })); }); const NewRuntimeDriverEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const newRuntimeDriver = editorStore.newElementState.getNewElementDriver(NewPackageableRuntimeDriver); const type = newRuntimeDriver.type; const typeOptions = Object.values(NewRuntimeType).map((typeOption) => ({ label: prettyCONSTName(typeOption), value: typeOption, })); const typeOption = { value: type, label: prettyCONSTName(type), }; const onTypeChange = (val) => { newRuntimeDriver.setType(val.value); }; // mapping const isStandard = type === NewRuntimeType.LEGACY; const mapping = newRuntimeDriver.mapping; const mappingOptions = editorStore.graphManagerState.usableMappings.map(buildElementOption); const selectedMappingOption = mapping ? { label: mapping.path, value: mapping } : null; const onMappingSelectionChange = (val) => { if (val.value !== mapping) { newRuntimeDriver.setMapping(val.value); } }; return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Runtime Type" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: typeOptions, onChange: onTypeChange, value: typeOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) }), isStandard && (mapping ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Mapping" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: mappingOptions, onChange: onMappingSelectionChange, value: selectedMappingOption, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled }) })] })) : (_jsx("div", { children: "no mapping found" })))] })); }); const NewPureModelConnectionDriverEditor = observer((props) => { const { newConnectionValueDriver } = props; const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; // class const _class = newConnectionValueDriver.class; const classOptions = editorStore.graphManagerState.usableClasses .map(buildElementOption) .toSorted(compareLabelFn); const selectedClassOption = _class ? { label: _class.path, value: _class } : null; const onClassSelectionChange = (val) => { if (val) { newConnectionValueDriver.setClass(val.value); } }; if (!_class) { // TODO: show warning return _jsx("div", { children: "no class found" }); } return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Source Class" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "sub-panel__content__form__section__dropdown", options: classOptions, onChange: onClassSelectionChange, value: selectedClassOption, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, formatOptionLabel: getPackageableElementOptionFormatter({ darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled, }) }) })] })); }); const NewConnectionValueDriverEditor = observer(() => { const editorStore = useEditorStore(); const newConnectionDriver = editorStore.newElementState.getNewElementDriver(NewPackageableConnectionDriver); const newConnectionValueDriver = newConnectionDriver.newConnectionValueDriver; if (newConnectionValueDriver instanceof NewPureModelConnectionDriver) { return (_jsx(NewPureModelConnectionDriverEditor, { newConnectionValueDriver: newConnectionValueDriver })); } return null; }); const NewConnectionDriverEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const newConnectionDriver = editorStore.newElementState.getNewElementDriver(NewPackageableConnectionDriver); // type const currentConnectionType = newConnectionDriver.geDriverConnectionType(); const currentConnectionTypeOption = { label: prettyCONSTName(currentConnectionType), value: currentConnectionType, }; const extraOptionTypes = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraConnectionTypeOptions?.() ?? []); const connectionOptions = Object.values(CONNECTION_TYPE) .map((typeOption) => ({ label: prettyCONSTName(typeOption), value: typeOption.toString(), })) .concat(extraOptionTypes); const onConnectionChange = (val) => { if (val?.value && currentConnectionTypeOption.value !== val.value) { newConnectionDriver.changeConnectionState(val.value); } }; // store const store = newConnectionDriver.store; let storeOptions = [ { label: 'ModelStore', value: undefined }, ]; // TODO: we should think more about this and filter the store by the connection type // or think about a way to completely revamp this workflow, maybe to let people select // the store first and then the type of the connection // See storeOptions = storeOptions.concat(editorStore.graphManagerState.usableStores .map(buildElementOption) .toSorted(compareLabelFn)); const selectedStoreOption = { label: store.path, value: store, }; const onStoreSelectionChange = (val) => { if (val.value) { newConnectionDriver.setStore(val.value); } }; return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Connection Type" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: connectionOptions, onChange: onConnectionChange, value: currentConnectionTypeOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) }), _jsx("div", { className: "panel__content__form__section__header__label", children: "Source Store" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: storeOptions, onChange: onStoreSelectionChange, value: selectedStoreOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) }), _jsx(NewConnectionValueDriverEditor, {})] })); }); const NewServiceDriverEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const runtimeSelectorPlaceholder = 'Choose a compatible runtime...'; const newServiceDriver = editorStore.newElementState.getNewElementDriver(NewServiceDriver); // runtime const onRuntimeChange = (val) => { if (val) { newServiceDriver.setRuntimeOption(val); } }; // mapping const mappingOptions = editorStore.graphManagerState.usableMappings.map(buildElementOption); const onMappingChange = (val) => { if (!val) { newServiceDriver.setMappingOption(undefined); } else { newServiceDriver.setMappingOption(val); } //reset runtime newServiceDriver.setRuntimeOption(guaranteeNonNullable(newServiceDriver.runtimeOptions[0])); }; return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Mapping" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: mappingOptions, onChange: onMappingChange, value: newServiceDriver.mappingOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) }), _jsx("div", { className: "panel__content__form__section__header__label", children: "Runtime" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "explorer__new-element-modal__driver__dropdown", options: newServiceDriver.runtimeOptions, onChange: onRuntimeChange, value: newServiceDriver.runtimeOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled, placeholder: runtimeSelectorPlaceholder }) })] })); }); const NewLakehouseDataProductEditor = observer(() => { const editorStore = useEditorStore(); const newProductDriver = editorStore.newElementState.getNewElementDriver(NewLakehouseDataProductDriver); const handleTitleChange = (event) => newProductDriver.setTitle(event.target.value); const setIsModeled = (val) => { newProductDriver.setModeled(val); }; return (_jsxs(_Fragment, { children: [_jsx("div", { className: "panel__content__form__section__header__label", children: "Title" }), _jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx("input", { className: "input--dark explorer__new-element-modal__name-input", spellCheck: false, value: newProductDriver.title, onChange: handleTitleChange, placeholder: `Choose a title for this Data Product to display in Marketplace` }) }), _jsx("div", { className: "explorer__new-element-modal__driver--bottom-padding", children: _jsx(PanelBooleanField, { isReadOnly: false, value: newProductDriver.modeled, name: "Modeled", prompt: "Expose data product access points through curated data models", update: (value) => setIsModeled(Boolean(value)) }) })] })); }); const NewFileGenerationDriverEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = editorStore.applicationStore; const newConnectionDriver = editorStore.newElementState.getNewElementDriver(NewFileGenerationDriver); const options = editorStore.graphState.graphGenerationState.globalFileGenerationState .fileGenerationConfigurationOptions; const onTypeSelectionChange = (val) => { if (!val) { newConnectionDriver.setTypeOption(undefined); } else { newConnectionDriver.setTypeOption(val); } }; return (_jsx("div", { className: "explorer__new-element-modal__driver", children: _jsx(CustomSelectorInput, { className: "sub-panel__content__form__section__dropdown explorer__new-element-modal__driver__dropdown", options: options, onChange: onTypeSelectionChange, value: newConnectionDriver.typeOption, darkMode: !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled }) })); }); const renderNewElementDriver = (type, editorStore) => { switch (type) { case PACKAGEABLE_ELEMENT_TYPE.RUNTIME: return _jsx(NewRuntimeDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE.CONNECTION: return _jsx(NewConnectionDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE.FILE_GENERATION: return _jsx(NewFileGenerationDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE.DATA: return _jsx(NewDataElementDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE.SERVICE: return _jsx(NewServiceDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE.SERVICE: return _jsx(NewServiceDriverEditor, {}); case PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT: return _jsx(NewLakehouseDataProductEditor, {}); default: { const extraNewElementDriverEditorCreators = editorStore.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraNewElementDriverEditorRenderers?.() ?? []); for (const creator of extraNewElementDriverEditorCreators) { const editor = creator(type); if (editor) { return editor; } } return null; } } }; export const CreateNewLocalConnectionModal = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const newElementState = editorStore.newElementState; const selectedPackage = newElementState.selectedPackage; // Name const name = newElementState.name; const handleNameChange = (event) => newElementState.setName(event.target.value); const elementNameInputRef = useRef(null); // Type const typeOptions = [PACKAGEABLE_ELEMENT_TYPE.PACKAGE] .concat(editorStore.getSupportedElementTypes()) .filter( // NOTE: we can only create package in root (type) => selectedPackage !== editorStore.graphManagerState.graph.root || type === PACKAGEABLE_ELEMENT_TYPE.PACKAGE) .map(buildElementTypeOption); const selectedTypeOption = buildElementTypeOption(newElementState.type); const handleTypeChange = (val) => newElementState.setElementType(val.value); // Submit button const closeModal = () => newElementState.closeModal(); const [packagePath, elementName] = resolvePackageAndElementName(selectedPackage, selectedPackage === editorStore.graphManagerState.graph.root, name); const resolvedPackage = editorStore.graphManagerState.graph.getNullablePackage(packagePath); const needsToOverride = Boolean(resolvedPackage?.children.find((child) => child.name === elementName)); const isDisabled = !name || needsToOverride || !newElementState.isValid; const save = applicationStore.guardUnhandledError(() => flowResult(newElementState.save())); const handleEnter = () => { newElementState.setName(''); elementNameInputRef.current?.focus(); }; if (!newElementState.showModal) { return null; } return (_jsx(Dialog, { open: newElementState.showModal, onClose: closeModal, classes: { container: 'search-modal__container' }, slotProps: { transition: { onEnter: handleEnter, }, paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs("form", { "data-testid": LEGEND_STUDIO_TEST_ID.NEW_ELEMENT_MODAL, onSubmit: (event) => { event.preventDefault(); save(); }, className: "modal modal--dark search-modal", children: [_jsx("div", { className: "modal__title", children: `Create a New ${getElementTypeLabel(editorStore, newElementState.type) ?? 'element'}` }), _jsxs("div", { children: [newElementState.showType && (_jsx(CustomSelectorInput, { options: typeOptions, disabled: typeOptions.length === 1, onChange: handleTypeChange, value: selectedTypeOption, isClearable: false, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled })), _jsx("input", { className: "input--dark explorer__new-element-modal__name-input", ref: elementNameInputRef, spellCheck: false, value: name, onChange: handleNameChange, placeholder: `Enter a name, use ${ELEMENT_PATH_DELIMITER} to create new package(s) for the ${getElementTypeLabel(editorStore, newElementState.type) ?? 'element'}` }), renderNewElementDriver(newElementState.type, editorStore)] }), _jsxs("div", { className: "search-modal__actions", children: [_jsx("button", { type: "button", className: "btn btn--dark", onClick: closeModal, children: "Cancel" }), _jsx("button", { className: "btn btn--dark", disabled: isDisabled, children: "Create" })] })] }) })); }); export const CreateNewElementModal = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const newElementState = editorStore.newElementState; const selectedPackage = newElementState.selectedPackage; const elementLabel = getElementTypeLabel(editorStore, newElementState.type); // Name const name = newElementState.name; const handleNameChange = (event) => newElementState.setName(event.target.value); const elementNameInputRef = useRef(null); // Type const typeOptions = [PACKAGEABLE_ELEMENT_TYPE.PACKAGE] .concat(editorStore.getSupportedElementTypes()) .filter( // NOTE: we can only create package in root (type) => selectedPackage !== editorStore.graphManagerState.graph.root || type === PACKAGEABLE_ELEMENT_TYPE.PACKAGE) .map(buildElementTypeOption); const selectedTypeOption = buildElementTypeOption(newElementState.type); const handleTypeChange = (val) => newElementState.setElementType(val.value); // Submit button const closeModal = () => newElementState.closeModal(); const [packagePath, elementName] = resolvePackageAndElementName(selectedPackage, selectedPackage === editorStore.graphManagerState.graph.root, name); const resolvedPackage = editorStore.graphManagerState.graph.getNullablePackage(packagePath); const needsToOverride = Boolean(resolvedPackage?.children.find((child) => child.name === elementName)); const isDisabled = !name || needsToOverride || !newElementState.isValid; const save = applicationStore.guardUnhandledError(() => flowResult(newElementState.save())); const handleEnter = () => { newElementState.setName(''); elementNameInputRef.current?.focus(); }; if (!newElementState.showModal) { return null; } return (_jsx(Dialog, { open: newElementState.showModal, onClose: closeModal, classes: { container: 'search-modal__container' }, slotProps: { transition: { onEnter: handleEnter, }, paper: { classes: { root: 'search-modal__inner-container' }, }, }, children: _jsxs("form", { "data-testid": LEGEND_STUDIO_TEST_ID.NEW_ELEMENT_MODAL, onSubmit: (event) => { event.preventDefault(); save(); }, className: "modal modal--dark search-modal", children: [_jsx("div", { className: "modal__title", children: `Create a new ${elementLabel ? toTitleCase(elementLabel) : 'element'}` }), _jsxs("div", { children: [newElementState.showType && (_jsx(CustomSelectorInput, { options: typeOptions, disabled: typeOptions.length === 1, onChange: handleTypeChange, value: selectedTypeOption, isClearable: false, darkMode: !applicationStore.layoutService .TEMPORARY__isLightColorThemeEnabled })), _jsx("input", { className: "input--dark explorer__new-element-modal__name-input", ref: elementNameInputRef, spellCheck: false, value: name, onChange: handleNameChange, placeholder: `Enter a name, use ${ELEMENT_PATH_DELIMITER} to create new package(s) for the ${getElementTypeLabel(editorStore, newElementState.type) ?? 'element'}` }), renderNewElementDriver(newElementState.type, editorStore)] }), _jsxs("div", { className: "search-modal__actions", children: [_jsx("button", { type: "button", className: "btn btn--dark", onClick: closeModal, children: "Cancel" }), _jsx("button", { className: "btn btn--dark", disabled: isDisabled, children: "Create" })] })] }) })); }); //# sourceMappingURL=CreateNewElementModal.js.map