/**
* 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
Validate
Render Artifact
}
menuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: {
vertical: 'top',
horizontal: 'right',
},
}}
>
Deploy to Sandbox
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) => (
))}
Documentation
{`Provide a brief description of the service's functionalities and usage`}
)}
{selectedTab === ACTIVATOR_EDITOR_TAB.TAGGED_VALUES && (
{activatorElement.taggedValues.map((taggedValue) => (
))}
)}
{selectedTab === ACTIVATOR_EDITOR_TAB.STEREOTYPES && (
{activatorElement.stereotypes.map((stereotype) => (
))}
)}
{selectedTab === ACTIVATOR_EDITOR_TAB.ACTIONS && (
)}
editorState.setArtifact(value)}
darkMode={
!applicationStore.layoutService
.TEMPORARY__isLightColorThemeEnabled
}
/>
);
});