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