/** * 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 { observer } from 'mobx-react-lite'; import { PurePropertyMappingEditor } from './PurePropertyMappingEditor.js'; import { getElementIcon } from '../../../ElementIconUtils.js'; import { type MappingElement, MappingEditorState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingEditorState.js'; import type { InstanceSetImplementationState, PropertyMappingState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/MappingElementState.js'; import { PurePropertyMappingState, PureInstanceSetImplementationState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/PureInstanceSetImplementationState.js'; import { clsx, ArrowCircleRightIcon, TimesCircleIcon, TimesIcon, AsteriskIcon, } from '@finos/legend-art'; import { guaranteeType } from '@finos/legend-shared'; import { type FlatDataPropertyMappingState, FlatDataInstanceSetImplementationState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/FlatDataInstanceSetImplementationState.js'; import { FlatDataPropertyMappingEditor } from './FlatDataPropertyMappingEditor.js'; import { RelationalPropertyMappingEditor } from './relational/RelationalPropertyMappingEditor.js'; import type { RelationalPropertyMappingState, RootRelationalInstanceSetImplementationState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/relational/RelationalInstanceSetImplementationState.js'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { type Property, type PropertyMapping, type Type, getRootSetImplementation, Class, SetImplementation, PrimitiveType, PureInstanceSetImplementation, EmbeddedFlatDataPropertyMapping, OperationSetImplementation, FlatDataInstanceSetImplementation, RootRelationalInstanceSetImplementation, MULTIPLICITY_INFINITE, type Multiplicity, RelationFunctionInstanceSetImplementation, } from '@finos/legend-graph'; import { useApplicationStore } from '@finos/legend-application'; import { instanceSetImplementation_deletePropertyMapping, setImpl_nominateRoot, setImplementation_setRoot, } from '../../../../stores/graph-modifier/DSL_Mapping_GraphModifierHelper.js'; import { CLASS_PROPERTY_TYPE, getClassPropertyType, } from '../../../../stores/editor/utils/ModelClassifierUtils.js'; import type { DSL_Mapping_LegendStudioApplicationPlugin_Extension } from '../../../../stores/extensions/DSL_Mapping_LegendStudioApplicationPlugin_Extension.js'; import { Fragment } from 'react'; import { RelationFunctionPropertyMappingEditor } from './RelationFunctionPropertyMappingEditor.js'; import type { RelationFunctionInstanceSetImplementationState, RelationFunctionPropertyMappingState, } from '../../../../stores/editor/editor-state/element-editor-state/mapping/RelationFunctionInstanceSetImplementationState.js'; const MultiplicityBadge: React.FC<{ multiplicity: Multiplicity; }> = (props) => { const { multiplicity } = props; const isRequired = multiplicity.lowerBound && multiplicity.lowerBound > 0; const tooltipText = `${isRequired ? '[required]' : '[optional]'}${ isRequired ? ` minimum: ${multiplicity.lowerBound}` : '' } maximum: ${multiplicity.upperBound ?? MULTIPLICITY_INFINITE}`; return (
{multiplicity.upperBound ? null : }
); }; export const getExpectedReturnType = ( targetSetImplementation: SetImplementation | undefined, ): Type | undefined => { if (targetSetImplementation instanceof PureInstanceSetImplementation) { return targetSetImplementation.srcClass?.value; } else if (targetSetImplementation instanceof OperationSetImplementation) { return targetSetImplementation.class.value; } else { return undefined; } }; const GenericPropertyMappingEditorEntry = observer( (props: { children: React.ReactNode; instanceSetImplementationState: InstanceSetImplementationState; propertyMappingStates: PropertyMappingState[]; propertyMappingState: PropertyMappingState; propertyBasicType: CLASS_PROPERTY_TYPE; }) => { const { children, instanceSetImplementationState, propertyMappingStates, propertyMappingState, propertyBasicType, } = props; const removePropertyMapping = (pm: PropertyMapping): void => { instanceSetImplementation_deletePropertyMapping( instanceSetImplementationState.mappingElement, pm, ); instanceSetImplementationState.decorate(); }; return (
{children} {propertyMappingStates.length > 1 && propertyBasicType !== CLASS_PROPERTY_TYPE.CLASS && ( )}
); }, ); export const PropertyMappingEditor = observer( (props: { property: Property; instanceSetImplementationState: InstanceSetImplementationState; isReadOnly: boolean; }) => { const { instanceSetImplementationState, property, isReadOnly } = props; const instanceSetImplementation = instanceSetImplementationState.mappingElement; const validationErrorMessage = instanceSetImplementation.propertyMappings.filter( (pm) => pm.property.value === property, ).length > 1 && !(property.genericType.value.rawType instanceof Class) ? 'Only one property mapping should exist per property of type other than class (e.g. primitive, measure, enumeration)' : undefined; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const mappingEditorState = editorStore.tabManagerState.getCurrentEditorState(MappingEditorState); const propertyRawType = property.genericType.value.rawType; const propertyBasicType = getClassPropertyType(propertyRawType); const isEmbedded = instanceSetImplementation._isEmbedded; // Parser Error const propertyMappingStates: PropertyMappingState[] = instanceSetImplementationState.propertyMappingStates.filter( (pm) => pm.propertyMapping.property.value === property, ); const propertyHasParserError = Boolean( propertyMappingStates.find((pm) => pm.parserError), ); const setImplementationHasParserError = Boolean( instanceSetImplementationState.propertyMappingStates.find( (pm) => pm.parserError, ), ); /** * TODO: revisit this behavior now that we have more types of property mapping to support * e.g. embedded, target set implementation, etc. * See https://github.com/finos/legend-studio/issues/310 * * @modularize * TODO: modularize this for other types * See https://github.com/finos/legend-studio/issues/65 */ const visitOrCreateMappingElement = (): void => { if (propertyRawType instanceof Class) { if ( instanceSetImplementation instanceof PureInstanceSetImplementation ) { const rootMappingElement = getRootSetImplementation( mappingEditorState.mapping, propertyRawType, ); if (rootMappingElement) { if (!rootMappingElement.root.value) { setImplementation_setRoot(rootMappingElement, true); } const parent = rootMappingElement._PARENT; if (parent !== mappingEditorState.element) { // TODO: think more about this flow. Right now we open the mapping element in the parent mapping editorStore.graphEditorMode.openElement(parent); editorStore.tabManagerState .getCurrentEditorState(MappingEditorState) .openMappingElement(rootMappingElement, false); } { mappingEditorState.openMappingElement(rootMappingElement, true); } } else { if (!isReadOnly) { mappingEditorState.createMappingElement({ target: property.genericType.value.rawType, showTarget: false, openInAdjacentTab: true, postSubmitAction: ( newSetImpl: MappingElement | undefined, ): void => { // Make this set implementation the new root if (newSetImpl instanceof SetImplementation) { setImpl_nominateRoot(newSetImpl); } }, }); } } } else if ( instanceSetImplementationState instanceof FlatDataInstanceSetImplementationState ) { if ( propertyMappingStates.length === 1 && propertyMappingStates[0]?.propertyMapping instanceof EmbeddedFlatDataPropertyMapping ) { mappingEditorState.openMappingElement( propertyMappingStates[0].propertyMapping, true, ); } else if (!propertyMappingStates.length) { const embedded = instanceSetImplementationState.addEmbeddedPropertyMapping( property, ); mappingEditorState.openMappingElement(embedded, true); } } else { applicationStore.notificationService.notifyWarning( `Can't visit mapping element for type '${propertyRawType.name}'`, ); } } }; const renderPropertyMappingEntry = ( propertyMappingState: PropertyMappingState, ): React.ReactNode => { if (instanceSetImplementation instanceof PureInstanceSetImplementation) { return ( ); } else if ( instanceSetImplementation instanceof FlatDataInstanceSetImplementation || instanceSetImplementation instanceof EmbeddedFlatDataPropertyMapping ) { return ( ); } else if ( instanceSetImplementation instanceof RootRelationalInstanceSetImplementation ) { return ( ); } else if ( instanceSetImplementation instanceof RelationFunctionInstanceSetImplementation ) { return ( ); } else { const extraPropertyMappingEditorRenderers = editorStore.pluginManager .getApplicationPlugins() .flatMap( (plugin) => ( plugin as DSL_Mapping_LegendStudioApplicationPlugin_Extension ).getExtraPropertyMappingEditorRenderers?.() ?? [], ); for (const renderer of extraPropertyMappingEditorRenderers) { const renderedPropertyMappingEditor = renderer( instanceSetImplementationState, propertyMappingState, ); if (renderedPropertyMappingEditor) { return ( {renderedPropertyMappingEditor} ); } } return (
Unsupported property mapping
); } }; return (
{property.name}
{validationErrorMessage && (
)}
{propertyBasicType !== CLASS_PROPERTY_TYPE.PRIMITIVE && (
{getElementIcon(propertyRawType, editorStore)}
)}
{propertyRawType.name}
{propertyBasicType === CLASS_PROPERTY_TYPE.CLASS && ( )}
{propertyMappingStates.map((propertyMappingState) => ( {renderPropertyMappingEntry(propertyMappingState)} ))} {propertyBasicType === CLASS_PROPERTY_TYPE.CLASS && !propertyMappingStates.length && ( <> {isEmbedded && (
Click {`to create an embedded class mapping for property '${property.name}'.`}
)} {!isEmbedded && (
No set implementation found. Click {`to create a root class mapping for '${propertyRawType.name}'.`}
)} )}
); }, );