/** * 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, { useState, useEffect, useCallback, useRef } from 'react'; import { observer } from 'mobx-react-lite'; import { FunctionEditorState, FUNCTION_EDITOR_TAB, } from '../../../../stores/editor/editor-state/element-editor-state/FunctionEditorState.js'; import { CORE_DND_TYPE, type UMLEditorElementDropTarget, type ElementDragSource, } from '../../../../stores/editor/utils/DnDUtils.js'; import { assertErrorThrown, assertTrue, prettyCONSTName, returnUndefOnError, UnsupportedOperationError, } from '@finos/legend-shared'; import { useDrag, useDrop } from 'react-dnd'; import { clsx, CustomSelectorInput, createFilter, LockIcon, PlusIcon, TimesIcon, ArrowCircleRightIcon, PanelEntryDragHandle, DragPreviewLayer, useDragPreviewLayer, Panel, PanelContent, PanelDnDEntry, Dialog, ModalBody, ModalFooter, CaretDownIcon, ControlledDropdownMenu, BlankPanelContent, MenuContent, MenuContentItem, Modal, PauseCircleIcon, PlayIcon, PanelLoadingIndicator, PencilIcon, RocketIcon, ModalFooterButton, BaseCard, Snowflake_BrandIcon, InputWithInlineValidation, LongArrowRightIcon, CheckSquareIcon, SquareIcon, SinglestoreIcon, } from '@finos/legend-art'; import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js'; import { StereotypeDragPreviewLayer, StereotypeSelector, } from '../uml-editor/StereotypeSelector.js'; import { TaggedValueDragPreviewLayer, TaggedValueEditor, } from '../uml-editor/TaggedValueEditor.js'; import { flowResult } from 'mobx'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { type ConcreteFunctionDefinition, type StereotypeReference, type TaggedValue, type RawVariableExpression, Profile, MULTIPLICITY_INFINITE, Unit, Type, Enumeration, Class, PrimitiveType, StereotypeExplicitReference, stub_Tag, stub_Profile, stub_TaggedValue, stub_Stereotype, stub_RawVariableExpression, getFunctionNameWithPath, getFunctionSignature, RawExecutionResult, extractExecutionResultValues, RawLambda, DatabaseType, RelationalDatabaseConnection, type FunctionActivator, GenericType, requireTypeArugments, GenericTypeExplicitReference, CORE_PURE_PATH, TDSExecutionResult, } from '@finos/legend-graph'; import { type ApplicationStore, type LegendApplicationPlugin, type LegendApplicationConfig, type LegendApplicationPluginManager, useApplicationNavigationContext, useApplicationStore, DEFAULT_TAB_SIZE, } from '@finos/legend-application'; import { type PackageableElementOption, buildElementOption, } from '@finos/legend-lego/graph-editor'; import { getElementIcon } from '../../../ElementIconUtils.js'; import { function_setReturnGenericType, function_setReturnMultiplicity, function_addParameter, function_deleteParameter, annotatedElement_addTaggedValue, annotatedElement_addStereotype, annotatedElement_deleteStereotype, annotatedElement_deleteTaggedValue, function_swapParameters, } from '../../../../stores/graph-modifier/DomainGraphModifierHelper.js'; import { rawVariableExpression_setMultiplicity, rawVariableExpression_setName, rawVariableExpression_setType, } from '../../../../stores/graph-modifier/RawValueSpecificationGraphModifierHelper.js'; import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../../../__lib__/LegendStudioApplicationNavigationContext.js'; import { type QueryBuilderState, ExecutionPlanViewer, LineageViewer, FunctionQueryBuilderState, getTDSColumnCustomizations, LambdaEditor, LambdaParameterValuesEditor, QUERY_BUILDER_TEST_ID, QueryBuilderAdvancedWorkflowState, getRowDataFromExecutionResult, } from '@finos/legend-query-builder'; import type { EditorStore } from '../../../../stores/editor/EditorStore.js'; import { graph_renameElement } from '../../../../stores/graph-modifier/GraphModifierHelper.js'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { CodeEditor } from '@finos/legend-lego/code-editor'; import { PanelGroupItemExperimentalBadge } from '../../panel-group/PanelGroup.js'; import type { FunctionActivatorState } from '../../../../stores/editor/editor-state/element-editor-state/FunctionActivatorState.js'; import { FunctionTestableEditor } from './testable/FunctionTestableEditor.js'; import { DocumentationLink } from '@finos/legend-lego/application'; import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../__lib__/LegendStudioDocumentation.js'; import { DataGrid, type DataGridColumnDefinition, } from '@finos/legend-lego/data-grid'; enum FUNCTION_PARAMETER_TYPE { CLASS = 'CLASS', ENUMERATION = 'ENUMERATION', PRIMITIVE = 'PRIMITIVE', } export enum FUNCTION_ACTIVATE_TYPE { SNOWFLAKE_NATIVE_APP = 'Snowflake UDTF', SNOWFLAKE_M2M_UDF = 'Snowflake M2M UDF', HOSTED_SERVICE = 'REST Service', MEM_SQL_FUNCTION = 'Mem SQL Function', SERVICE_JAR = 'Service JAR', REFINER = 'Refiner', BIG_QUERY_NATIVE_APP = 'BigQuery Native App', } const getFunctionParameterType = (type: Type): FUNCTION_PARAMETER_TYPE => { if (type instanceof PrimitiveType) { return FUNCTION_PARAMETER_TYPE.PRIMITIVE; } else if (type instanceof Enumeration) { return FUNCTION_PARAMETER_TYPE.ENUMERATION; } else if (type instanceof Class) { return FUNCTION_PARAMETER_TYPE.CLASS; } throw new UnsupportedOperationError( `Can't classify function parameter`, type, ); }; type FunctionParameterDragSource = { parameter: RawVariableExpression; }; const FUNCTION_PARAMETER_DND_TYPE = 'FUNCTION_PARAMETER'; /** * NOTE: every time we update the function signature (parameters, return value), we need to adjust the function path, * therefore, we need to update the graph's function index. */ const updateFunctionName = ( editorStore: EditorStore, applicationStore: ApplicationStore< LegendApplicationConfig, LegendApplicationPluginManager >, func: ConcreteFunctionDefinition, ): void => { try { graph_renameElement( editorStore.graphManagerState.graph, func, `${getFunctionNameWithPath(func)}${getFunctionSignature(func)}`, editorStore.changeDetectionState.observerContext, ); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError(error); } }; const ParameterBasicEditor = observer( (props: { parameter: RawVariableExpression; _func: ConcreteFunctionDefinition; deleteParameter: () => void; isReadOnly: boolean; }) => { const ref = useRef(null); const handleRef = useRef(null); const { parameter, _func, deleteParameter, isReadOnly } = props; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); // Name const changeValue: React.ChangeEventHandler = (event) => rawVariableExpression_setName(parameter, event.target.value); // Type const [isEditingType, setIsEditingType] = useState(false); const typeOptions = editorStore.graphManagerState.usableClassPropertyTypes.map( buildElementOption, ); const paramType = parameter.type.value; const typeName = getFunctionParameterType(paramType); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option: { data: PackageableElementOption }): string => option.data.value.path, }); const selectedType = { value: paramType, label: paramType.name }; const changeType = (val: PackageableElementOption): void => { if (val.value !== parameter.type.value) { rawVariableExpression_setType(parameter, val.value); if (requireTypeArugments(val.value)) { parameter.typeArguments = [ GenericTypeExplicitReference.create( new GenericType( editorStore.graphManagerState.graph.getType(CORE_PURE_PATH.ANY), ), ), ]; } updateFunctionName(editorStore, applicationStore, _func); } setIsEditingType(false); }; const openElement = (): void => { if (!(paramType instanceof PrimitiveType)) { editorStore.graphEditorMode.openElement( paramType instanceof Unit ? paramType.measure : paramType, ); } }; // Multiplicity const [lowerBound, setLowerBound] = useState( parameter.multiplicity.lowerBound, ); const [upperBound, setUpperBound] = useState( parameter.multiplicity.upperBound ?? MULTIPLICITY_INFINITE, ); const updateMultiplicity = ( lower: number | string, upper: number | string, ): void => { const lBound = typeof lower === 'number' ? lower : parseInt(lower, 10); const uBound = upper === MULTIPLICITY_INFINITE ? undefined : typeof upper === 'number' ? upper : parseInt(upper, 10); if (!isNaN(lBound) && (uBound === undefined || !isNaN(uBound))) { rawVariableExpression_setMultiplicity( parameter, editorStore.graphManagerState.graph.getMultiplicity(lBound, uBound), ); updateFunctionName(editorStore, applicationStore, _func); } }; const changeLowerBound: React.ChangeEventHandler = ( event, ) => { setLowerBound(event.target.value); updateMultiplicity(event.target.value, upperBound); }; const changeUpperBound: React.ChangeEventHandler = ( event, ) => { setUpperBound(event.target.value); updateMultiplicity(lowerBound, event.target.value); }; // Drag and Drop const handleHover = useCallback( (item: FunctionParameterDragSource): void => { const draggingParameter = item.parameter; const hoveredParameter = parameter; function_swapParameters(_func, draggingParameter, hoveredParameter); }, [_func, parameter], ); const [{ isBeingDraggedParameter }, dropConnector] = useDrop< FunctionParameterDragSource, void, { isBeingDraggedParameter: RawVariableExpression | undefined } >( () => ({ accept: [FUNCTION_PARAMETER_DND_TYPE], hover: (item) => handleHover(item), collect: ( monitor, ): { isBeingDraggedParameter: RawVariableExpression | undefined; } => ({ isBeingDraggedParameter: monitor.getItem()?.parameter, }), }), [handleHover], ); const isBeingDragged = parameter === isBeingDraggedParameter; const [, dragConnector, dragPreviewConnector] = useDrag( () => ({ type: FUNCTION_PARAMETER_DND_TYPE, item: () => ({ parameter: parameter, }), }), [parameter], ); dragConnector(handleRef); dropConnector(ref); useDragPreviewLayer(dragPreviewConnector); return ( } className="property-basic-editor__container" showPlaceholder={isBeingDragged} >
{isReadOnly && (
)} {!isReadOnly && isEditingType && ( )} {!isReadOnly && !isEditingType && (
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (
{getElementIcon(paramType, editorStore)}
)}
{paramType.name}
setIsEditingType(true)} > Click to edit
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && ( )}
)} {isReadOnly && (
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (
{getElementIcon(paramType, editorStore)}
)}
{paramType.name}
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && ( )}
)}
..
{!isReadOnly && ( )}
); }, ); const ReturnTypeEditor = observer( (props: { functionElement: ConcreteFunctionDefinition; isReadOnly: boolean; }) => { const { functionElement, isReadOnly } = props; const { returnType, returnMultiplicity } = functionElement; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); // Type const [isEditingType, setIsEditingType] = useState(false); const typeOptions = editorStore.graphManagerState.usableClassPropertyTypes.map( buildElementOption, ); const typeName = getFunctionParameterType(returnType.value.rawType); const filterOption = createFilter({ ignoreCase: true, ignoreAccents: false, stringify: (option: { data: PackageableElementOption }): string => option.data.value.path, }); const selectedType = { value: returnType.value.rawType, label: returnType.value.rawType.name, }; const changeType = (val: PackageableElementOption): void => { const value = val.value; const genericType = new GenericType(value); if (requireTypeArugments(value)) { genericType.typeArguments = [ GenericTypeExplicitReference.create( new GenericType( editorStore.graphManagerState.graph.getType(CORE_PURE_PATH.ANY), ), ), ]; } function_setReturnGenericType(functionElement, genericType); setIsEditingType(false); updateFunctionName(editorStore, applicationStore, functionElement); }; const openElement = (): void => { if (!(returnType.value.rawType instanceof PrimitiveType)) { editorStore.graphEditorMode.openElement( returnType.value.rawType instanceof Unit ? returnType.value.rawType.measure : returnType.value.rawType, ); } }; // Multiplicity const [lowerBound, setLowerBound] = useState( returnMultiplicity.lowerBound, ); const [upperBound, setUpperBound] = useState( returnMultiplicity.upperBound ?? MULTIPLICITY_INFINITE, ); const updateMultiplicity = ( lower: number | string, upper: number | string, ): void => { const lBound = typeof lower === 'number' ? lower : parseInt(lower, 10); const uBound = upper === MULTIPLICITY_INFINITE ? undefined : typeof upper === 'number' ? upper : parseInt(upper, 10); if (!isNaN(lBound) && (uBound === undefined || !isNaN(uBound))) { function_setReturnMultiplicity( functionElement, editorStore.graphManagerState.graph.getMultiplicity(lBound, uBound), ); updateFunctionName(editorStore, applicationStore, functionElement); } }; const changeLowerBound: React.ChangeEventHandler = ( event, ) => { setLowerBound(event.target.value); updateMultiplicity(event.target.value, upperBound); }; const changeUpperBound: React.ChangeEventHandler = ( event, ) => { setUpperBound(event.target.value); updateMultiplicity(lowerBound, event.target.value); }; return (
{!isReadOnly && isEditingType && ( )} {!isReadOnly && !isEditingType && (
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (
{getElementIcon(returnType.value.rawType, editorStore)}
)}
{returnType.value.rawType.name}
setIsEditingType(true)} > Click to edit
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && ( )}
)} {isReadOnly && (
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && (
{getElementIcon(returnType.value.rawType, editorStore)}
)}
{returnType.value.rawType.name}
{typeName !== FUNCTION_PARAMETER_TYPE.PRIMITIVE && ( )}
)}
..
); }, ); const FunctionPromoteEditor = observer( (props: { functionElement: ConcreteFunctionDefinition; activatorPromoteState: FunctionActivatorState; }) => { const { functionElement, activatorPromoteState } = props; const applicationStore = useApplicationStore(); const elementAlreadyExistsMessage = activatorPromoteState.functionEditorState.editorStore.graphManagerState.graph.allElements .map((s) => s.path) .includes(activatorPromoteState.activatorPath) ? 'Element with same path already exists' : undefined; let validationMessage = ''; const closeModal = (): void => { activatorPromoteState.closeFunctionActivateModal(); activatorPromoteState.setAcitvateType(undefined); }; const promoteFunction = (): void => { flowResult(activatorPromoteState.activate(functionElement)) .then(() => { activatorPromoteState.closeFunctionActivateModal(); }) .catch(applicationStore.alertUnhandledError); }; const onTargetPathChange: React.ChangeEventHandler = ( event, ) => { activatorPromoteState.updateActivatorPath(event.target.value); }; const validateFunctionActivator = (type: string): boolean => { switch (type) { case FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_NATIVE_APP: { const availableConnections = activatorPromoteState.functionEditorState.editorStore.graphManagerState.usableConnections.filter( (connection) => connection.connectionValue instanceof RelationalDatabaseConnection && connection.connectionValue.type === DatabaseType.Snowflake, ); if (availableConnections.length > 0) { return true; } else { validationMessage = 'There is no available connection of type Snowflake'; } return false; } case FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_M2M_UDF: { const availableConnections = activatorPromoteState.functionEditorState.editorStore.graphManagerState.usableConnections.filter( (connection) => connection.connectionValue instanceof RelationalDatabaseConnection && connection.connectionValue.type === DatabaseType.Snowflake, ); if (availableConnections.length > 0) { return true; } else { validationMessage = 'There is no available connection of type Snowflake'; } return false; } case FUNCTION_ACTIVATE_TYPE.MEM_SQL_FUNCTION: { const availableConnections = activatorPromoteState.functionEditorState.editorStore.graphManagerState.usableConnections.filter( (connection) => connection.connectionValue instanceof RelationalDatabaseConnection && connection.connectionValue.type === DatabaseType.MemSQL, ); if (availableConnections.length > 0) { return true; } else { validationMessage = 'There is no available connection of type MemSQL'; } return false; } default: return true; } }; const renderFunctionPromoteTypes = (type: string): React.ReactNode => { switch (type) { case FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_NATIVE_APP: return ( } cardName={type} cardContent="Deploy the function as a UDTF(user-defined table function) in snowflake" isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_NATIVE_APP } onClick={() => { activatorPromoteState.setAcitvateType(type); }} /> ); case FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_M2M_UDF: return ( } cardName={type} cardContent="Deploy the function as a UDF(user-defined function) in snowflake" isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.SNOWFLAKE_M2M_UDF } onClick={() => { activatorPromoteState.setAcitvateType(type); }} /> ); case FUNCTION_ACTIVATE_TYPE.HOSTED_SERVICE: return ( } cardName={type} cardContent="Create a HostedService that will be deployed to a server environment and executed with a pattern" isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.HOSTED_SERVICE } onClick={() => { activatorPromoteState.setAcitvateType(type); }} /> ); case FUNCTION_ACTIVATE_TYPE.MEM_SQL_FUNCTION: return ( } cardName={type} cardContent="Deploy the function as a UDF (user-defined function) in Mem SQL" isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.MEM_SQL_FUNCTION } onClick={() => { activatorPromoteState.setAcitvateType(type); }} /> ); case FUNCTION_ACTIVATE_TYPE.SERVICE_JAR: return ( Coming Soon} cardName={type} cardContent="Deploy the function in the definition of a Store persistence" disabled={true} isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.SERVICE_JAR } /> ); case FUNCTION_ACTIVATE_TYPE.REFINER: return ( Coming Soon} cardName={type} cardContent="Use the service in a refiner context" disabled={true} isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.REFINER } /> ); case FUNCTION_ACTIVATE_TYPE.BIG_QUERY_NATIVE_APP: return ( Coming Soon} cardName={type} cardContent="Deploy the function as a UDTF(user-defined table function) in BigQuery" disabled={true} isActive={ activatorPromoteState.activateType === FUNCTION_ACTIVATE_TYPE.BIG_QUERY_NATIVE_APP } /> ); default: return <>; } }; return (
Select any one of the following activator types to continue
{Object.values(FUNCTION_ACTIVATE_TYPE).map((type) => renderFunctionPromoteTypes(type), )}
Target Path
Cancel Activate
); }, ); const FunctionDefinitionEditor = observer( (props: { functionEditorState: FunctionEditorState; isReadOnly: boolean; }) => { const { functionEditorState, isReadOnly } = props; const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const lambdaEditorState = functionEditorState.functionDefinitionEditorState; const functionElement = functionEditorState.functionElement; const execResult = functionEditorState.executionResult; // Parameters const addParameter = (): void => { function_addParameter( functionElement, stub_RawVariableExpression(PrimitiveType.STRING), ); updateFunctionName(editorStore, applicationStore, functionElement); }; const deleteParameter = (val: RawVariableExpression): (() => void) => (): void => { function_deleteParameter(functionElement, val); updateFunctionName(editorStore, applicationStore, functionElement); }; const handleDropParameter = useCallback( (item: UMLEditorElementDropTarget): void => { if (!isReadOnly && item.data.packageableElement instanceof Type) { function_addParameter( functionElement, stub_RawVariableExpression(item.data.packageableElement), ); updateFunctionName(editorStore, applicationStore, functionElement); } }, [applicationStore, editorStore, functionElement, isReadOnly], ); const [{ isParameterDragOver }, dropConnector] = useDrop< ElementDragSource, void, { isParameterDragOver: boolean } >( () => ({ accept: [ CORE_DND_TYPE.PROJECT_EXPLORER_CLASS, CORE_DND_TYPE.PROJECT_EXPLORER_ENUMERATION, ], drop: (item) => handleDropParameter(item), collect: (monitor) => ({ isParameterDragOver: monitor.isOver({ shallow: true }), }), }), [handleDropParameter], ); const ref = useRef(null); dropConnector(ref); const renderFuncResult = (): React.ReactNode => { if (execResult instanceof RawExecutionResult) { const val = execResult.value === null ? 'null' : execResult.value.toString(); return ( ); } else if (execResult instanceof TDSExecutionResult) { const colDefs = execResult.result.columns.map( (colName) => ({ minWidth: 50, sortable: true, resizable: true, field: colName, flex: 1, headerName: colName, ...getTDSColumnCustomizations(execResult, colName), }) as DataGridColumnDefinition, ); const darkMode = !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled; return (
`${data.data.rowNumber}`, rowSelection: { mode: 'multiRow', checkboxes: false, headerCheckbox: false, }, }} // NOTE: when column definition changed, we need to force refresh the cell to make sure the cell renderer is updated // See https://stackoverflow.com/questions/56341073/how-to-refresh-an-ag-grid-when-a-change-occurs-inside-a-custom-cell-renderer-com onRowDataUpdated={(params) => { params.api.refreshCells({ force: true }); }} suppressFieldDotNotation={true} suppressContextMenu={false} columnDefs={colDefs} />
); } else if (execResult !== undefined) { const json = returnUndefOnError(() => JSON.stringify( extractExecutionResultValues(execResult), null, DEFAULT_TAB_SIZE, ), ) ?? JSON.stringify(execResult); return ( ); } return Function Did Not Run; }; return ( <>
PARAMETERS
item.parameter.name === '' ? '(unknown)' : item.parameter.name } types={[FUNCTION_PARAMETER_DND_TYPE]} />
{functionElement.parameters.map((param) => ( ))} {functionElement.parameters.length === 0 && (
No parameters
)}
LAMBDA
(BETA) TypeAhead
RESULT
{renderFuncResult()}
); }, ); export const FunctionEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const functionEditorState = editorStore.tabManagerState.getCurrentEditorState(FunctionEditorState); const isReadOnly = functionEditorState.isReadOnly; const functionElement = functionEditorState.functionElement; const selectedTab = functionEditorState.selectedTab; let addButtonTitle = ''; switch (selectedTab) { case FUNCTION_EDITOR_TAB.TAGGED_VALUES: addButtonTitle = 'Add stereotype'; break; case FUNCTION_EDITOR_TAB.STEREOTYPES: addButtonTitle = 'Add tagged value'; break; default: break; } // Tagged Values const add = (): void => { if (!isReadOnly) { if (selectedTab === FUNCTION_EDITOR_TAB.TAGGED_VALUES) { annotatedElement_addTaggedValue( functionElement, stub_TaggedValue(stub_Tag(stub_Profile())), ); } else if (selectedTab === FUNCTION_EDITOR_TAB.STEREOTYPES) { annotatedElement_addStereotype( functionElement, StereotypeExplicitReference.create(stub_Stereotype(stub_Profile())), ); } } }; const handleDropTaggedValue = useCallback( (item: UMLEditorElementDropTarget): void => { if (!isReadOnly && item.data.packageableElement instanceof Profile) { annotatedElement_addTaggedValue( functionElement, stub_TaggedValue(stub_Tag(item.data.packageableElement)), ); } }, [functionElement, isReadOnly], ); const [{ isTaggedValueDragOver }, taggedValueDropConnector] = useDrop< ElementDragSource, void, { isTaggedValueDragOver: boolean } >( () => ({ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE], drop: (item) => handleDropTaggedValue(item), collect: (monitor) => ({ isTaggedValueDragOver: monitor.isOver({ shallow: true }), }), }), [handleDropTaggedValue], ); const taggedValueRef = useRef(null); taggedValueDropConnector(taggedValueRef); // Stereotype const handleDropStereotype = useCallback( (item: UMLEditorElementDropTarget): void => { if (!isReadOnly && item.data.packageableElement instanceof Profile) { annotatedElement_addStereotype( functionElement, StereotypeExplicitReference.create( stub_Stereotype(item.data.packageableElement), ), ); } }, [functionElement, isReadOnly], ); const [{ isStereotypeDragOver }, stereotypeDropConnector] = useDrop< ElementDragSource, void, { isStereotypeDragOver: boolean } >( () => ({ accept: [CORE_DND_TYPE.PROJECT_EXPLORER_PROFILE], drop: (item) => handleDropStereotype(item), collect: (monitor) => ({ isStereotypeDragOver: monitor.isOver({ shallow: true }), }), }), [handleDropStereotype], ); const stereotypeRef = useRef(null); stereotypeDropConnector(stereotypeRef); const _deleteStereotype = (val: StereotypeReference): (() => void) => (): void => annotatedElement_deleteStereotype(functionElement, val); const _deleteTaggedValue = (val: TaggedValue): (() => void) => (): void => annotatedElement_deleteTaggedValue(functionElement, val); const changeTab = (tab: FUNCTION_EDITOR_TAB): (() => void) => (): void => functionEditorState.setSelectedTab(tab); const runFunc = applicationStore.guardUnhandledError(() => flowResult(functionEditorState.handleRunFunc()), ); const executionIsRunning = functionEditorState.isRunningFunc || functionEditorState.isGeneratingPlan; const cancelQuery = applicationStore.guardUnhandledError(() => flowResult(functionEditorState.cancelFuncRun()), ); const generatePlan = applicationStore.guardUnhandledError(() => flowResult(functionEditorState.generatePlan(false)), ); const generateLineage = applicationStore.guardUnhandledError(() => flowResult(functionEditorState.generateLineage()), ); const debugPlanGeneration = applicationStore.guardUnhandledError(() => flowResult(functionEditorState.generatePlan(true)), ); const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState; useEffect(() => { flowResult( functionEditorState.functionDefinitionEditorState.convertLambdaObjectToGrammarString( { pretty: true, firstLoad: true, }, ), ).catch(applicationStore.alertUnhandledError); }, [applicationStore, functionEditorState]); useApplicationNavigationContext( LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY.FUNCTION_EDITOR, ); const editWithQueryBuilder = (): (() => void) => applicationStore.guardUnhandledError(async () => { try { const functionQueryBuilderState = new FunctionQueryBuilderState( embeddedQueryBuilderState.editorStore.applicationStore, embeddedQueryBuilderState.editorStore.graphManagerState, QueryBuilderAdvancedWorkflowState.INSTANCE, functionEditorState.functionElement, editorStore.applicationStore.config.options.queryBuilderConfig, ); functionQueryBuilderState.initializeWithQuery( new RawLambda( functionEditorState.functionElement.parameters.map((_param) => functionEditorState.editorStore.graphManagerState.graphManager.serializeRawValueSpecification( _param, ), ), functionEditorState.functionElement.expressionSequence, ), ); assertTrue( Boolean( functionQueryBuilderState.isQuerySupported && functionQueryBuilderState.executionContextState.mapping && functionQueryBuilderState.executionContextState.runtimeValue, ), `Only functions returning TDS/graph fetch using the from() function can be edited via query builder`, ); await flowResult( embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({ setupQueryBuilderState: async (): Promise => functionQueryBuilderState, actionConfigs: [ { key: 'save-query-btn', renderer: ( queryBuilderState: QueryBuilderState, ): React.ReactNode => { const save = applicationStore.guardUnhandledError( async () => { try { const rawLambda = queryBuilderState.buildQuery(); await flowResult( functionEditorState.updateFunctionWithQuery( rawLambda, ), ); applicationStore.notificationService.notifySuccess( `Function query is updated`, ); embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration( undefined, ); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError( `Can't save query: ${error.message}`, ); } }, ); return ( ); }, }, ], }), ); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError( `Unable to edit via query builder: ${error.message}`, ); } }); const openFunctionCubeViewer = editorStore.applicationStore.guardUnhandledError(async () => { await functionEditorState.handleOpeningDataCube( functionEditorState.element, editorStore, ); }); const visitActivator = (activator: FunctionActivator): void => functionEditorState.editorStore.graphEditorMode.openElement(activator); const openFunctionActivateModal = (): void => { functionEditorState.activatorPromoteState.showFunctionActivateModal(); }; return (
{isReadOnly && (
)}
function
{functionElement.functionName}
{Object.values(FUNCTION_EDITOR_TAB).map((tab) => (
{prettyCONSTName(tab)} {tab === FUNCTION_EDITOR_TAB.TEST_SUITES && ( )}
))}
{selectedTab === FUNCTION_EDITOR_TAB.DEFINITION && ( <>
{functionEditorState.isRunningFunc ? ( ) : ( <> Generate Plan Debug View Lineage } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right', }, transformOrigin: { vertical: 'top', horizontal: 'right', }, }} > )}
)}
{selectedTab === FUNCTION_EDITOR_TAB.DEFINITION && ( )} {selectedTab === FUNCTION_EDITOR_TAB.TAGGED_VALUES && (
{functionElement.taggedValues.map((taggedValue) => ( ))}
)} {selectedTab === FUNCTION_EDITOR_TAB.STEREOTYPES && (
{functionElement.stereotypes.map((stereotype) => ( ))}
)} {selectedTab === FUNCTION_EDITOR_TAB.TEST_SUITES && ( )} {selectedTab === FUNCTION_EDITOR_TAB.LAMBDAS && (
{functionEditorState.activators.map((activator) => (
{getElementIcon(activator, editorStore)}
))}
)} {functionEditorState.parametersState.parameterValuesEditorState .showModal && ( )}
{functionEditorState.activatorPromoteState.isActivatingFunction && ( )}
); });