/** * 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, { useEffect } from 'react'; import { observer } from 'mobx-react-lite'; import { ServicePureExecutionState } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceExecutionState.js'; import { Dialog, PanelLoadingIndicator, PlayIcon, ControlledDropdownMenu, MenuContent, CaretDownIcon, MenuContentItem, PauseCircleIcon, PencilIcon, Modal, ModalBody, ModalFooter, ModalFooterButton, ModalHeader, } from '@finos/legend-art'; import { assertErrorThrown, guaranteeNonNullable, returnUndefOnError, } from '@finos/legend-shared'; import { flowResult } from 'mobx'; import { useEditorStore } from '../../EditorStoreProvider.js'; import { type Service, KeyedExecutionParameter, MultiExecutionParameters, PureExecution, } from '@finos/legend-graph'; import { type QueryBuilderState, ServiceQueryBuilderState, LambdaParameterValuesEditor, QueryBuilderTextEditorMode, QueryLoaderDialog, ExecutionPlanViewer, QueryBuilderAdvancedWorkflowState, QueryBuilderActionConfig, LineageViewer, } from '@finos/legend-query-builder'; import { ProjectViewerEditorMode } from '../../../../stores/project-view/ProjectViewerEditorMode.js'; import { useLegendStudioApplicationStore } from '../../../LegendStudioFrameworkProvider.js'; import { SNAPSHOT_ALIAS, SNAPSHOT_VERSION_ALIAS, } from '@finos/legend-server-depot'; import type { ProjectGAVCoordinates } from '@finos/legend-storage'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { CodeEditor } from '@finos/legend-lego/code-editor'; import { EXTERNAL_APPLICATION_NAVIGATION__generateServiceQueryCreatorUrl } from '../../../../__lib__/LegendStudioNavigation.js'; import type { EditorStore } from '../../../../stores/editor/EditorStore.js'; import { pureExecution_setFunction } from '../../../../stores/graph-modifier/DSL_Service_GraphModifierHelper.js'; import { ServiceEditorState } from '../../../../stores/editor/editor-state/element-editor-state/service/ServiceEditorState.js'; const ServiceExecutionResultViewer = observer( (props: { executionState: ServicePureExecutionState }) => { const { executionState } = props; const applicationStore = executionState.editorStore.applicationStore; // execution const executionResultText = executionState.executionResultText; const closeExecutionResultViewer = (): void => executionState.setExecutionResultText(undefined); return ( ); }, ); export const ServiceExecutionQueryEditor = observer( (props: { executionState: ServicePureExecutionState; isReadOnly: boolean; }) => { const { executionState, isReadOnly } = props; const applicationStore = useLegendStudioApplicationStore(); const editorStore = useEditorStore(); const queryState = executionState.queryState; const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState; const service = executionState.serviceEditorState.service; // actions const editWithQueryBuilder = (openInTextMode = false): (() => void) => applicationStore.guardUnhandledError(async () => { const selectedExecutionState = executionState.selectedExecutionContextState; await flowResult( embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({ setupQueryBuilderState: async (): Promise => { const sourceInfo = { service: service.path, ...editorStore.editorMode.getSourceInfo(), }; const queryBuilderState = new ServiceQueryBuilderState( embeddedQueryBuilderState.editorStore.applicationStore, embeddedQueryBuilderState.editorStore.graphManagerState, QueryBuilderAdvancedWorkflowState.INSTANCE, QueryBuilderActionConfig.INSTANCE, service, undefined, selectedExecutionState?.executionContext instanceof KeyedExecutionParameter ? selectedExecutionState.executionContext.key : undefined, undefined, undefined, embeddedQueryBuilderState.editorStore.applicationStore.config.options.queryBuilderConfig, sourceInfo, ); queryBuilderState.initializeWithQuery( executionState.execution.func, ); if (openInTextMode) { queryBuilderState.textEditorState.openModal( QueryBuilderTextEditorMode.TEXT, ); } return queryBuilderState; }, actionConfigs: [ { key: 'save-query-btn', renderer: ( queryBuilderState: QueryBuilderState, ): React.ReactNode => { const save = applicationStore.guardUnhandledError( async () => { try { const rawLambda = queryBuilderState.buildQuery(); await flowResult( executionState.queryState.updateLamba(rawLambda), ); applicationStore.notificationService.notifySuccess( `Service query is updated`, ); embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration( undefined, ); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError( `Can't save query: ${error.message}`, ); } }, ); return ( Save Query ); }, }, ], disableCompile: true, }), ); executionState.setOpeningQueryEditor(false); }); const importQuery = (): void => queryState.queryLoaderState.setQueryLoaderDialogOpen(true); const runQuery = applicationStore.guardUnhandledError(() => flowResult(executionState.handleRunQuery()), ); const executionIsRunning = executionState.isRunningQuery || executionState.isGeneratingPlan; const cancelQuery = applicationStore.guardUnhandledError(() => flowResult(executionState.cancelQuery()), ); const generatePlan = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(false)), ); const generateLineage = applicationStore.guardUnhandledError(() => flowResult(executionState.generateLineage()), ); const debugPlanGeneration = applicationStore.guardUnhandledError(() => flowResult(executionState.generatePlan(true)), ); const openServiceCubeViewer = editorStore.applicationStore.guardUnhandledError(async () => { await executionState.handleOpeningDataCube(service, editorStore); }); const openQueryInLegendQuery = (): void => { if (!applicationStore.config.queryApplicationUrl) { return; } let projectGAV: ProjectGAVCoordinates; if (editorStore.editorMode instanceof ProjectViewerEditorMode) { const viewerEditorMode = editorStore.editorMode; // for Achive mode if (viewerEditorMode.viewerStore.projectGAVCoordinates) { projectGAV = viewerEditorMode.viewerStore.projectGAVCoordinates; } else { // for other viewer modes, if no version we use project `HEAD` projectGAV = { groupId: editorStore.projectConfigurationEditorState .currentProjectConfiguration.groupId, artifactId: editorStore.projectConfigurationEditorState .currentProjectConfiguration.artifactId, versionId: viewerEditorMode.viewerStore.version?.id.id ?? SNAPSHOT_VERSION_ALIAS, }; } } else { const currentWorkSpaceId = editorStore.sdlcState.currentWorkspace?.workspaceId; projectGAV = { groupId: editorStore.projectConfigurationEditorState .currentProjectConfiguration.groupId, artifactId: editorStore.projectConfigurationEditorState .currentProjectConfiguration.artifactId, versionId: editorStore.sdlcState.projectPublishedVersions.includes( `${currentWorkSpaceId}-${SNAPSHOT_ALIAS}`, ) ? `${currentWorkSpaceId}-${SNAPSHOT_ALIAS}` : SNAPSHOT_VERSION_ALIAS, }; } applicationStore.navigationService.navigator.visitAddress( EXTERNAL_APPLICATION_NAVIGATION__generateServiceQueryCreatorUrl( applicationStore.config.queryApplicationUrl, projectGAV.groupId, projectGAV.artifactId, projectGAV.versionId, service.path, ), ); }; // convert to string useEffect(() => { flowResult( queryState.convertLambdaObjectToGrammarString({ pretty: true }), ).catch(applicationStore.alertUnhandledError); }, [applicationStore, queryState]); return ( query Edit Query Text Mode } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, }} > {executionState.isRunningQuery ? ( Stop ) : ( <> Run Query Generate Plan View Lineage Debug } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, }} > > )} Data Cube Import Query Create an Ad-hoc Query } menuProps={{ anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, }} > Advanced {queryState.queryLoaderState.isQueryLoaderDialogOpen && ( )} {executionState.parametersState.parameterValuesEditorState .showModal && ( )} ); }, ); export const queryService = async ( service: Service, editorStore: EditorStore, ): Promise => { const embeddedQueryBuilderState = editorStore.embeddedQueryBuilderState; const applicationStore = editorStore.applicationStore; const execution = service.execution instanceof PureExecution ? service.execution : undefined; const selectedExec = execution instanceof MultiExecutionParameters ? execution.singleExecutionParameters[0]?.key : undefined; const sourceInfo = { service: service.path, ...editorStore.editorMode.getSourceInfo(), }; await flowResult( embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration({ setupQueryBuilderState: async (): Promise => { const queryBuilderState = new ServiceQueryBuilderState( embeddedQueryBuilderState.editorStore.applicationStore, embeddedQueryBuilderState.editorStore.graphManagerState, QueryBuilderAdvancedWorkflowState.INSTANCE, QueryBuilderActionConfig.INSTANCE, service, undefined, selectedExec, undefined, undefined, embeddedQueryBuilderState.editorStore.applicationStore.config.options.queryBuilderConfig, sourceInfo, ); if (execution) { queryBuilderState.initializeWithQuery(execution.func); } return queryBuilderState; }, actionConfigs: [ { key: 'save-query-btn', renderer: (queryBuilderState: QueryBuilderState): React.ReactNode => { const save = applicationStore.guardUnhandledError(async () => { try { const rawLambda = queryBuilderState.buildQuery(); const serviceState = returnUndefOnError(() => editorStore.tabManagerState.getCurrentEditorState( ServiceEditorState, ), ); if ( serviceState?.service === service && serviceState.executionState instanceof ServicePureExecutionState ) { await flowResult( serviceState.executionState.queryState.updateLamba( rawLambda, ), ); } else { pureExecution_setFunction( guaranteeNonNullable( execution, 'Service execution expected to be a pure execution', ), rawLambda, ); } applicationStore.notificationService.notifySuccess( `Service query is updated`, ); embeddedQueryBuilderState.setEmbeddedQueryBuilderConfiguration( undefined, ); } catch (error) { assertErrorThrown(error); applicationStore.notificationService.notifyError( `Can't save query: ${error.message}`, ); } }); return ( Save Query ); }, }, ], }), ); };