/** * 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 { ResizablePanelGroup, ResizablePanel, ResizablePanelSplitter, PlusIcon, ContextMenu, MenuContent, MenuContentItem, PanelListSelectorItem, ControlledDropdownMenu, ResizablePanelSplitterLine, PanelContent, Panel, Badge, PanelListSelectorItemLabel, PanelFormBooleanField, PanelHeaderActions, PanelForm, PURE_FunctionIcon, LongArrowRightIcon, PanelLoadingIndicator, PanelFormValidatedTextField, TimesIcon, clsx, PanelHeader, CaretDownIcon, } from '@finos/legend-art'; import { type PostDeploymentAction, Profile, StereotypeExplicitReference, type StereotypeReference, type TaggedValue, generateFunctionPrettyName, stub_Profile, stub_Stereotype, stub_Tag, stub_TaggedValue, validate_ServicePattern, } from '@finos/legend-graph'; import { observer } from 'mobx-react-lite'; import { useApplicationStore } from '@finos/legend-application'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { HostedServiceFunctionActivatorEditorState, ACTIVATOR_EDITOR_TAB, } from '../../../../stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.js'; import { hostedService_setAutoActivateUpdates, hostedService_setDocumentation, hostedService_setPattern, hostedService_removePatternParameter, hostedService_setStoreModel, hostedServices_deleteAction, } from '../../../../stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.js'; import { useCallback, useEffect, useRef, useState } from 'react'; import { prettyCONSTName } from '@finos/legend-shared'; import { flowResult } from 'mobx'; import { ActivatorOwnershipForm } from './ActivatorFormComponents.js'; import { annotatedElement_addStereotype, annotatedElement_addTaggedValue, annotatedElement_deleteStereotype, annotatedElement_deleteTaggedValue, } from '../../../../stores/graph-modifier/DomainGraphModifierHelper.js'; import { CORE_DND_TYPE, type ElementDragSource, type UMLEditorElementDropTarget, } from '../../../../stores/editor/utils/DnDUtils.js'; import { useDrop } from 'react-dnd'; import { TaggedValueDragPreviewLayer, TaggedValueEditor, } from '../uml-editor/TaggedValueEditor.js'; import { StereotypeDragPreviewLayer, StereotypeSelector, } from '../uml-editor/StereotypeSelector.js'; import type { DSL_Service_LegendStudioApplicationPlugin_Extension } from '../../../../stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.js'; import { UnsupportedEditorPanel } from '../UnsupportedElementEditor.js'; import type { LegendStudioApplicationPlugin } from '../../../../stores/LegendStudioApplicationPlugin.js'; import { ActivatorArtifactViewer } from './ActivatorArtifactViewer.js'; const renderEditorPostAction = ( postDeploymentAction: PostDeploymentAction, isReadOnly: boolean, plugins: LegendStudioApplicationPlugin[], activatorState: HostedServiceFunctionActivatorEditorState, ): React.ReactNode => { const extraPostDeploymentActionEditorRenderers = plugins.flatMap( (plugin) => ( plugin as DSL_Service_LegendStudioApplicationPlugin_Extension ).getExtraActionEditorRenderers?.() ?? [], ); for (const editorRenderer of extraPostDeploymentActionEditorRenderers) { if (postDeploymentAction.properties) { const editor = editorRenderer( postDeploymentAction.properties, activatorState, isReadOnly, postDeploymentAction, ); if (editor) { return editor; } } } return ( ); }; const HostedServicesActionEditor = observer( (props: { activatorState: HostedServiceFunctionActivatorEditorState; isReadOnly: boolean; }) => { const { activatorState, isReadOnly } = props; const editorStore = useEditorStore(); const plugins = editorStore.pluginManager.getApplicationPlugins(); const editorState = editorStore.tabManagerState.getCurrentEditorState( HostedServiceFunctionActivatorEditorState, ); const deleteAction = (action: PostDeploymentAction, index: number): (() => void) => (): void => { hostedServices_deleteAction(editorState.activator, index); }; const postDeploymentActionOptions = plugins .flatMap( (plugin) => ( plugin as DSL_Service_LegendStudioApplicationPlugin_Extension ).getExtraPostDeploymentTypes?.() ?? [], ) .map((type) => ({ value: type, label: type, })); const addAction = (actionType: string): (() => void) => (): void => { const extraActions = plugins.flatMap( (plugin) => ( plugin as DSL_Service_LegendStudioApplicationPlugin_Extension ).getExtraActionCreators?.() ?? [], ); for (const creator of extraActions) { creator( actionType, activatorState.activator, editorStore.changeDetectionState.observerContext, ); } }; const selectAction = (postDeploymentAction: PostDeploymentAction): void => { editorState.setSelectedAction(postDeploymentAction); }; const getPostDeploymentActionLabel = ( postDeploymentAction: PostDeploymentAction, ): string => { const extraPostDeploymentActionClassifier = plugins.flatMap( (plugin) => ( plugin as DSL_Service_LegendStudioApplicationPlugin_Extension ).getExtraPostDeploymentActionClassifierGetters?.() ?? [], ); for (const classify of extraPostDeploymentActionClassifier) { if (postDeploymentAction.properties) { const label = classify(postDeploymentAction.properties); if (label) { return label; } } } return 'unknown type'; }; return (
( New {postDeploymentActionType.label} Action ), )} menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right', }, transformOrigin: { vertical: 'top', horizontal: 'right', }, elevation: 7, }} > {editorState.activator.actions.map((actionItem, idx) => ( Delete } menuProps={{ elevation: 7 }} > selectAction(actionItem)} isSelected={ actionItem === editorState.selectedAction } > ))} {editorState.selectedAction && renderEditorPostAction( editorState.selectedAction, isReadOnly, plugins, editorState, )}
); }, ); export const HostedServiceFunctionActivatorEditor = observer(() => { const editorStore = useEditorStore(); const applicationStore = useApplicationStore(); const editorState = editorStore.tabManagerState.getCurrentEditorState( HostedServiceFunctionActivatorEditorState, ); const activatorElement = editorState.element; const isReadOnly = editorState.isReadOnly; const selectedTab = editorState.selectedTab; const activator = editorState.activator; let addButtonTitle = ''; switch (selectedTab) { case ACTIVATOR_EDITOR_TAB.TAGGED_VALUES: addButtonTitle = 'Add tagged value'; break; case ACTIVATOR_EDITOR_TAB.STEREOTYPES: addButtonTitle = 'Add stereotype'; break; default: break; } // Tagged Values const add = (): void => { if (!isReadOnly) { if (selectedTab === ACTIVATOR_EDITOR_TAB.TAGGED_VALUES) { annotatedElement_addTaggedValue( activatorElement, stub_TaggedValue(stub_Tag(stub_Profile())), ); } else if (selectedTab === ACTIVATOR_EDITOR_TAB.STEREOTYPES) { annotatedElement_addStereotype( activatorElement, StereotypeExplicitReference.create(stub_Stereotype(stub_Profile())), ); } } }; const handleDropTaggedValue = useCallback( (item: UMLEditorElementDropTarget): void => { if (!isReadOnly && item.data.packageableElement instanceof Profile) { annotatedElement_addTaggedValue( activatorElement, stub_TaggedValue(stub_Tag(item.data.packageableElement)), ); } }, [activatorElement, 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( activatorElement, StereotypeExplicitReference.create( stub_Stereotype(item.data.packageableElement), ), ); } }, [activatorElement, 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(activatorElement, val); const _deleteTaggedValue = (val: TaggedValue): (() => void) => (): void => annotatedElement_deleteTaggedValue(activatorElement, val); const changeTab = (tab: ACTIVATOR_EDITOR_TAB): (() => void) => (): void => editorState.setSelectedTab(tab); const visitFunction = (): void => editorState.editorStore.graphEditorMode.openElement( activator.function.value, ); const validate = (): void => { flowResult(editorState.validate()).catch( applicationStore.alertUnhandledError, ); }; const renderArtifact = (): void => { flowResult(editorState.renderArtifact()).catch( applicationStore.alertUnhandledError, ); }; const deploy = (): void => { flowResult(editorState.deployToSandbox()).catch( applicationStore.alertUnhandledError, ); }; const changeDocumentation: React.ChangeEventHandler = ( event, ) => { if (!isReadOnly) { hostedService_setDocumentation(activator, event.target.value); } }; const toggleUseStoreModel = (): void => { hostedService_setStoreModel(activator, !activator.storeModel); }; const toggleAutoActivateUpdates = (): void => { hostedService_setAutoActivateUpdates( activator, !activator.autoActivateUpdates, ); }; const getValidationMessage = (inputPattern: string): string | undefined => { const patternValidationResult = validate_ServicePattern(inputPattern); return patternValidationResult ? patternValidationResult.messages[0] : undefined; }; //Pattern const patternRef = useRef(null); const [pattern, setPattern] = useState(activator.pattern); const updatePattern = (newPattern: string): void => { if (!isReadOnly) { hostedService_setPattern(activator, newPattern); } }; const removePatternParameter = (val: string): (() => void) => (): void => { hostedService_removePatternParameter(activator, val); setPattern(activator.pattern); }; useEffect(() => { patternRef.current?.focus(); }, [editorState]); return (
{Object.values(ACTIVATOR_EDITOR_TAB).map((tab) => (
{prettyCONSTName(tab)}
))}
{selectedTab !== ACTIVATOR_EDITOR_TAB.DEFINITION && ( )}
{selectedTab === ACTIVATOR_EDITOR_TAB.DEFINITION && (
Rest Service Activator
Render Artifact } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right', }, transformOrigin: { vertical: 'top', horizontal: 'right', }, }} >
Specifies the URL pattern of the service (e.g. /myService/ {`{param}`} ) } update={(value: string | undefined): void => { updatePattern(value ?? ''); }} validate={getValidationMessage} value={pattern} />
Parameters
URL parameters (each must be surrounded by curly braces) will be passed as arguments for the execution query. Note that if the service is configured to use multi-execution, one of the URL parameters must be chosen as the execution key.
{!activator.patternParameters.length && (
No parameter
)} {Boolean(activator.patternParameters.length) && activator.patternParameters.map((parameter) => (
{parameter}
))}
Function
Documentation
{`Provide a brief description of the service's functionalities and usage`}