UNPKG

@finos/legend-application-studio

Version:
664 lines 31 kB
/** * 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 { observable, action, flow, makeObservable, flowResult } from 'mobx'; import { assertErrorThrown, LogEvent, stringifyLosslessJSON, UnsupportedOperationError, filterByType, guaranteeNonNullable, StopWatch, } from '@finos/legend-shared'; import { decorateRuntimeWithNewMapping, RuntimeEditorState, } from '../../../editor-state/element-editor-state/RuntimeEditorState.js'; import { PureSingleExecution, PureMultiExecution, KeyedExecutionParameter, GRAPH_MANAGER_EVENT, RawLambda, EngineRuntime, RuntimePointer, PackageableElementExplicitReference, buildSourceInformationSourceId, QueryProjectCoordinates, buildLambdaVariableExpressions, observe_ValueSpecification, VariableExpression, stub_PackageableRuntime, stub_Mapping, reportGraphAnalytics, QuerySearchSpecification, } from '@finos/legend-graph'; import { parseGACoordinates } from '@finos/legend-storage'; import { runtime_addMapping } from '../../../../graph-modifier/DSL_Mapping_GraphModifierHelper.js'; import { keyedExecutionParameter_setKey, pureExecution_setFunction, pureMultiExecution_addExecutionParameter, pureMultiExecution_deleteExecutionParameter, pureMultiExecution_setExecutionKey, pureSingleExecution_setMapping, pureSingleExecution_setRuntime, service_setExecution, } from '../../../../graph-modifier/DSL_Service_GraphModifierHelper.js'; import { buildExecutionParameterValues, getExecutionQueryFromRawLambda, LambdaEditorState, LambdaParametersState, LambdaParameterState, PARAMETER_SUBMIT_ACTION, QueryBuilderTelemetryHelper, QueryLoaderState, QUERY_BUILDER_EVENT, ExecutionPlanState, QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT, getRawLambdaForLetFuncs, LineageState, } from '@finos/legend-query-builder'; import { DEFAULT_TAB_SIZE } from '@finos/legend-application'; import { openDataCube } from '../../../data-cube/LegendStudioDataCubeHelper.js'; export class ServiceExecutionParametersState extends LambdaParametersState { executionState; constructor(executionState) { super(); makeObservable(this, { parameterValuesEditorState: observable, parameterStates: observable, addParameter: action, removeParameter: action, openModal: action, build: action, setParameters: action, }); this.executionState = executionState; } openModal(query) { this.parameterStates = this.build(query); this.parameterValuesEditorState.open(() => flowResult(this.executionState.runQuery()).catch(this.executionState.editorStore.applicationStore.alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN); } openDataCubeModal(query, element, editorStore) { this.parameterStates = this.build(query); this.parameterValuesEditorState.open(() => this.executionState .openingDataCube(element, editorStore) .catch(this.executionState.editorStore.applicationStore .alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN); } build(query) { const parameters = buildLambdaVariableExpressions(query, this.executionState.editorStore.graphManagerState) .map((p) => observe_ValueSpecification(p, this.executionState.editorStore.changeDetectionState.observerContext)) .filter(filterByType(VariableExpression)); const states = parameters.map((p) => { const parmeterState = new LambdaParameterState(p, this.executionState.editorStore.changeDetectionState.observerContext, this.executionState.editorStore.graphManagerState.graph); parmeterState.mockParameterValue(); return parmeterState; }); return states; } } export class ServiceExecutionState { editorStore; serviceEditorState; execution; constructor(editorStore, serviceEditorState, execution) { makeObservable(this, { execution: observable, }); this.editorStore = editorStore; this.execution = execution; this.serviceEditorState = serviceEditorState; } } export class UnsupportedServiceExecutionState extends ServiceExecutionState { get serviceExecutionParameters() { return undefined; } } const decorateSearchSpecificationForServiceQueryImporter = (val, editorStore) => { const currentProjectCoordinates = new QueryProjectCoordinates(); currentProjectCoordinates.groupId = editorStore.projectConfigurationEditorState.currentProjectConfiguration.groupId; currentProjectCoordinates.artifactId = editorStore.projectConfigurationEditorState.currentProjectConfiguration.artifactId; val.projectCoordinates = [ // either get queries for the current project currentProjectCoordinates, // or any of its dependencies ...Array.from(editorStore.graphManagerState.graph.dependencyManager.projectDependencyModelsIndex.keys()).map((dependencyKey) => { const { groupId, artifactId } = parseGACoordinates(dependencyKey); const coordinates = new QueryProjectCoordinates(); coordinates.groupId = groupId; coordinates.artifactId = artifactId; return coordinates; }), ]; return val; }; export class ServicePureExecutionQueryState extends LambdaEditorState { editorStore; queryLoaderState; execution; isInitializingLambda = false; constructor(editorStore, execution) { super('', ''); makeObservable(this, { execution: observable, isInitializingLambda: observable, setIsInitializingLambda: action, setLambda: action, updateLamba: flow, importQuery: flow, }); this.editorStore = editorStore; this.execution = execution; this.queryLoaderState = new QueryLoaderState(editorStore.applicationStore, editorStore.graphManagerState.graphManager, { loadQuery: (query) => { flowResult(this.importQuery(query.id)).catch(this.editorStore.applicationStore.alertUnhandledError); }, decorateSearchSpecification: (val) => decorateSearchSpecificationForServiceQueryImporter(val, this.editorStore), fetchDefaultQueries: async () => { const searchSpecification = new QuerySearchSpecification(); searchSpecification.limit = QUERY_LOADER_TYPEAHEAD_SEARCH_LIMIT; return this.editorStore.graphManagerState.graphManager.searchQueries(decorateSearchSpecificationForServiceQueryImporter(searchSpecification, this.editorStore)); }, isReadOnly: true, }); } get lambdaId() { return buildSourceInformationSourceId([ this.execution._OWNER.path, 'execution', ]); } get query() { return this.execution.func; } setIsInitializingLambda(val) { this.isInitializingLambda = val; } setLambda(val) { pureExecution_setFunction(this.execution, val); } *importQuery(selectedQueryID) { try { const content = (yield this.editorStore.graphManagerState.graphManager.prettyLambdaContent((yield this.editorStore.graphManagerState.graphManager.getQueryInfo(selectedQueryID)).content)); const lambda = (yield this.editorStore.graphManagerState.graphManager.pureCodeToLambda(content)); yield flowResult(this.updateLamba(lambda)); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.notificationService.notifyError(error); } finally { this.queryLoaderState.setQueryLoaderDialogOpen(false); } } *updateLamba(val) { this.setLambda(val); yield flowResult(this.convertLambdaObjectToGrammarString({ pretty: true })); } *convertLambdaObjectToGrammarString(options) { if (this.execution.func.body) { try { const lambdas = new Map(); lambdas.set(this.lambdaId, new RawLambda(this.execution.func.parameters, this.execution.func.body)); const isolatedLambdas = (yield this.editorStore.graphManagerState.graphManager.lambdasToPureCode(lambdas, options?.pretty)); const grammarText = isolatedLambdas.get(this.lambdaId); this.setLambdaString(grammarText !== undefined ? this.extractLambdaString(grammarText) : ''); this.clearErrors(); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error); } } else { this.clearErrors(); this.setLambdaString(''); } } // NOTE: since we don't allow edition in text mode, we don't need to implement this *convertLambdaGrammarStringToObject() { throw new UnsupportedOperationError(); } } export class ServiceExecutionContextState { executionState; constructor(executionState) { this.executionState = executionState; } } export class SingleExecutionContextState extends ServiceExecutionContextState { constructor(executionState) { super(executionState); makeObservable(this, { setMapping: action, setRuntime: action, }); this.executionState = executionState; } setMapping(value) { pureSingleExecution_setMapping(this.executionState.execution, value, this.executionState.editorStore.changeDetectionState.observerContext); } setRuntime(value) { pureSingleExecution_setRuntime(this.executionState.execution, value, this.executionState.editorStore.changeDetectionState.observerContext); } get executionContext() { return { mapping: guaranteeNonNullable(this.executionState.execution.mapping), runtime: guaranteeNonNullable(this.executionState.execution.runtime), }; } } export class KeyedExecutionContextState extends ServiceExecutionContextState { keyedExecutionParameter; constructor(keyedExecutionParameter, executionState) { super(executionState); makeObservable(this, { setMapping: action, setRuntime: action, }); this.keyedExecutionParameter = keyedExecutionParameter; } setMapping(value) { pureSingleExecution_setMapping(this.keyedExecutionParameter, value, this.executionState.editorStore.changeDetectionState.observerContext); } setRuntime(value) { pureSingleExecution_setRuntime(this.keyedExecutionParameter, value, this.executionState.editorStore.changeDetectionState.observerContext); } get executionContext() { return this.keyedExecutionParameter; } } export class ServicePureExecutionState extends ServiceExecutionState { queryState; selectedExecutionContextState; runtimeEditorState; isOpeningQueryEditor = false; showChangeExecModal = false; isRunningQuery = false; isGeneratingPlan = false; isGeneratingLineage = false; executionResultText; // NOTE: stored as lossless JSON string executionPlanState; parametersState; queryRunPromise = undefined; lineageState; constructor(editorStore, serviceEditorState, execution) { super(editorStore, serviceEditorState, execution); makeObservable(this, { cancelQuery: flow, generateLineage: flow, isGeneratingLineage: observable, }); this.execution = execution; this.queryState = new ServicePureExecutionQueryState(this.editorStore, execution); this.executionPlanState = new ExecutionPlanState(this.editorStore.applicationStore, this.editorStore.graphManagerState); this.parametersState = new ServiceExecutionParametersState(this); this.lineageState = new LineageState(this.editorStore.applicationStore); } isChangeExecutionDisabled() { return false; } setIsRunningQuery(val) { this.isRunningQuery = val; } setShowChangeExecModal(val) { this.showChangeExecModal = val; } setOpeningQueryEditor(val) { this.isOpeningQueryEditor = val; } setExecutionResultText = (executionResult) => { this.executionResultText = executionResult; }; setQueryState = (queryState) => { this.queryState = queryState; }; setQueryRunPromise = (promise) => { this.queryRunPromise = promise; }; async handleOpeningDataCube(element, editorStore) { const query = this.queryState.query; const parameters = (query.parameters ?? []); if (parameters.length) { this.parametersState.openDataCubeModal(query, element, editorStore); } else { await openDataCube(element, editorStore); } } async openingDataCube(element, editorStore) { const params = buildExecutionParameterValues(this.parametersState.parameterStates, this.editorStore.graphManagerState); const letFuncsRawLambda = getRawLambdaForLetFuncs(this.parametersState.parameterStates, this.editorStore.graphManagerState); await openDataCube(element, editorStore, params, letFuncsRawLambda); } *generatePlan(debug) { if (this.isGeneratingPlan) { return; } try { const query = this.queryState.query; this.isGeneratingPlan = true; let rawPlan; const stopWatch = new StopWatch(); const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph); if (debug) { QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugLaunched(this.editorStore.applicationStore.telemetryService); const debugResult = (yield this.editorStore.graphManagerState.graphManager.debugExecutionPlanGeneration(query, this.selectedExecutionContextState?.executionContext.mapping.value, this.selectedExecutionContextState?.executionContext.runtime, this.editorStore.graphManagerState.graph, undefined, report)); rawPlan = debugResult.plan; this.executionPlanState.setDebugText(debugResult.debug); } else { QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationLaunched(this.editorStore.applicationStore.telemetryService); rawPlan = (yield this.editorStore.graphManagerState.graphManager.generateExecutionPlan(query, this.selectedExecutionContextState?.executionContext.mapping.value, this.selectedExecutionContextState?.executionContext.runtime, this.editorStore.graphManagerState.graph, undefined, report)); } stopWatch.record(); try { this.executionPlanState.setRawPlan(rawPlan); const plan = this.editorStore.graphManagerState.graphManager.buildExecutionPlan(rawPlan, this.editorStore.graphManagerState.graph); this.executionPlanState.initialize(plan); } catch { // do nothing } stopWatch.record(QUERY_BUILDER_EVENT.BUILD_EXECUTION_PLAN__SUCCESS); // report report.timings = this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch, report.timings); if (debug) { QueryBuilderTelemetryHelper.logEvent_ExecutionPlanDebugSucceeded(this.editorStore.applicationStore.telemetryService, report); } else { QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationSucceeded(this.editorStore.applicationStore.telemetryService, report); } } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error); this.editorStore.applicationStore.notificationService.notifyError(error); } finally { this.isGeneratingPlan = false; } } *handleRunQuery() { if (this.isRunningQuery) { return; } const query = this.queryState.query; const parameters = (query.parameters ?? []); if (parameters.length) { this.parametersState.openModal(query); } else { this.runQuery(); } } *runQuery() { if (this.isRunningQuery) { return; } QueryBuilderTelemetryHelper.logEvent_QueryRunLaunched(this.editorStore.applicationStore.telemetryService); let promise; try { this.isRunningQuery = true; const stopWatch = new StopWatch(); const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph); promise = this.editorStore.graphManagerState.graphManager.runQuery(getExecutionQueryFromRawLambda(this.queryState.query, this.parametersState.parameterStates, this.editorStore.graphManagerState), this.selectedExecutionContextState?.executionContext.mapping.value, this.selectedExecutionContextState?.executionContext.runtime, this.editorStore.graphManagerState.graph, { useLosslessParse: true, parameterValues: buildExecutionParameterValues(this.parametersState.parameterStates, this.editorStore.graphManagerState), }, report); this.setQueryRunPromise(promise); const result = (yield promise); if (this.queryRunPromise === promise) { this.setExecutionResultText(stringifyLosslessJSON(result.executionResult, undefined, DEFAULT_TAB_SIZE)); this.parametersState.setParameters([]); // report report.timings = this.editorStore.applicationStore.timeService.finalizeTimingsRecord(stopWatch, report.timings); QueryBuilderTelemetryHelper.logEvent_QueryRunSucceeded(this.editorStore.applicationStore.telemetryService, report); } } catch (error) { // When user cancels the query by calling the cancelQuery api, it will throw an execution failure error. // For now, we don't want to notify users about this failure. Therefore we check to ensure the promise is still the same one. // When cancelled the query, we set the queryRunPromise as undefined. if (this.queryRunPromise === promise) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error); this.editorStore.applicationStore.notificationService.notifyError(error); } } finally { this.isRunningQuery = false; } } *cancelQuery() { this.setIsRunningQuery(false); this.setQueryRunPromise(undefined); try { yield this.editorStore.graphManagerState.graphManager.cancelUserExecutions(true); } catch (error) { // Don't notify users about success or failure this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error); } } *generateLineage() { if (this.isGeneratingLineage) { return; } try { this.isGeneratingLineage = true; const query = this.queryState.query; const mapping = this.selectedExecutionContextState?.executionContext.mapping.value; const lineageRawData = (yield this.editorStore.graphManagerState.graphManager.generateLineage(query, mapping, undefined, this.editorStore.graphManagerState.graph, undefined)); const lineageData = this.editorStore.graphManagerState.graphManager.buildLineage(lineageRawData); this.lineageState.setLineageData(lineageData); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.LINEAGE_GENERATION_FAILURE), error); this.editorStore.applicationStore.notificationService.notifyError(error); } finally { this.isGeneratingLineage = false; } } get serviceExecutionParameters() { if (!this.selectedExecutionContextState || this.isRunningQuery) { return undefined; } const query = this.queryState.query; return { query, mapping: this.selectedExecutionContextState.executionContext.mapping.value, runtime: this.selectedExecutionContextState.executionContext.runtime, }; } closeRuntimeEditor() { this.runtimeEditorState = undefined; } openRuntimeEditor() { if (this.selectedExecutionContextState && !(this.selectedExecutionContextState.executionContext.runtime instanceof RuntimePointer)) { this.runtimeEditorState = new RuntimeEditorState(this.editorStore, this.selectedExecutionContextState.executionContext.runtime, true); } } useCustomRuntime() { const customRuntime = new EngineRuntime(); guaranteeNonNullable(this.selectedExecutionContextState); const executionState = this .selectedExecutionContextState; runtime_addMapping(customRuntime, PackageableElementExplicitReference.create(executionState.executionContext.mapping.value)); decorateRuntimeWithNewMapping(customRuntime, executionState.executionContext.mapping.value, this.editorStore); executionState.setRuntime(customRuntime); } autoSelectRuntimeOnMappingChange(mapping) { if (this.selectedExecutionContextState) { const runtimes = this.editorStore.graphManagerState.graph.ownRuntimes.filter((runtime) => runtime.runtimeValue.mappings.map((m) => m.value).includes(mapping)); if (runtimes.length) { this.selectedExecutionContextState.setRuntime(runtimes[0].runtimeValue); } else { this.useCustomRuntime(); } } } updateExecutionQuery() { pureExecution_setFunction(this.execution, this.queryState.query); } } export class InlineServicePureExecutionState extends ServicePureExecutionState { constructor(editorStore, serviceEditorState, execution) { super(editorStore, serviceEditorState, execution); makeObservable(this, { queryState: observable, isRunningQuery: observable, isGeneratingPlan: observable, isOpeningQueryEditor: observable, executionResultText: observable, executionPlanState: observable, showChangeExecModal: observable, setExecutionResultText: action, setQueryState: action, updateExecutionQuery: action, setOpeningQueryEditor: action, generatePlan: flow, handleRunQuery: flow, runQuery: flow, }); this.selectedExecutionContextState = this.getInitiallySelectedExecutionContextState(); } changeExecution() { throw new Error('Method not implemented.'); } getInitiallySelectedExecutionContextState() { return undefined; } } export class SingleServicePureExecutionState extends ServicePureExecutionState { multiExecutionKey = 'key'; constructor(editorStore, serviceEditorState, execution) { super(editorStore, serviceEditorState, execution); makeObservable(this, { queryState: observable, getInitiallySelectedExecutionContextState: observable, selectedExecutionContextState: observable, runtimeEditorState: observable, isRunningQuery: observable, isGeneratingPlan: observable, isOpeningQueryEditor: observable, executionResultText: observable, executionPlanState: observable, showChangeExecModal: observable, multiExecutionKey: observable, setExecutionResultText: action, closeRuntimeEditor: action, openRuntimeEditor: action, useCustomRuntime: action, setQueryState: action, autoSelectRuntimeOnMappingChange: action, updateExecutionQuery: action, setOpeningQueryEditor: action, changeExecution: action, setMultiExecutionKey: action, setShowChangeExecModal: action, setIsRunningQuery: action, generatePlan: flow, handleRunQuery: flow, runQuery: flow, }); this.selectedExecutionContextState = this.getInitiallySelectedExecutionContextState(); } isChangeExecutionDisabled() { return this.multiExecutionKey === ''; } getInitiallySelectedExecutionContextState() { return new SingleExecutionContextState(this); } setMultiExecutionKey(val) { this.multiExecutionKey = val; } changeExecution() { if (this.execution.mapping && this.execution.runtime) { const _execution = new PureMultiExecution(this.multiExecutionKey, this.execution.func, this.serviceEditorState.service); const _parameter = new KeyedExecutionParameter(`execContext_1`, this.execution.mapping, this.execution.runtime); _execution.executionParameters = [_parameter]; service_setExecution(this.serviceEditorState.service, _execution, this.editorStore.changeDetectionState.observerContext); this.serviceEditorState.resetExecutionState(); } } } export class MultiServicePureExecutionState extends ServicePureExecutionState { newKeyParameterModal = false; renameKey; singleExecutionKey; constructor(editorStore, serviceEditorState, execution) { super(editorStore, serviceEditorState, execution); makeObservable(this, { queryState: observable, selectedExecutionContextState: observable, runtimeEditorState: observable, isRunningQuery: observable, isGeneratingPlan: observable, isOpeningQueryEditor: observable, executionResultText: observable, executionPlanState: observable, newKeyParameterModal: observable, renameKey: observable, singleExecutionKey: observable, showChangeExecModal: observable, setExecutionResultText: action, closeRuntimeEditor: action, openRuntimeEditor: action, useCustomRuntime: action, setQueryState: action, autoSelectRuntimeOnMappingChange: action, updateExecutionQuery: action, setOpeningQueryEditor: action, deleteKeyExecutionParameter: action, setNewKeyParameterModal: action, changeKeyedExecutionParameter: action, setRenameKey: action, addExecutionParameter: action, setExecutionKey: action, changeKeyValue: action, setSingleExecutionKey: action, setShowChangeExecModal: action, setIsRunningQuery: action, changeExecution: action, generatePlan: flow, handleRunQuery: flow, runQuery: flow, }); this.execution = execution; this.selectedExecutionContextState = this.getInitiallySelectedExecutionContextState(); this.queryState = new ServicePureExecutionQueryState(this.editorStore, execution); this.executionPlanState = new ExecutionPlanState(this.editorStore.applicationStore, this.editorStore.graphManagerState); } get keyedExecutionParameters() { return this.execution.executionParameters ?? []; } setSingleExecutionKey(val) { this.singleExecutionKey = val; } changeExecution() { const mappingExecution = this.singleExecutionKey; // stub const _mapping = mappingExecution?.mapping.value ?? stub_Mapping(); const mapping = PackageableElementExplicitReference.create(_mapping); const runtime = mappingExecution?.runtime ?? stub_PackageableRuntime(); const _execution = new PureSingleExecution(this.execution.func, this.serviceEditorState.service, mapping, runtime); service_setExecution(this.serviceEditorState.service, _execution, this.editorStore.changeDetectionState.observerContext); this.serviceEditorState.resetExecutionState(); } setRenameKey(key) { this.renameKey = key; } setNewKeyParameterModal(val) { this.newKeyParameterModal = val; } setExecutionKey(val) { pureMultiExecution_setExecutionKey(this.execution, val); } getInitiallySelectedExecutionContextState() { const parameter = this.keyedExecutionParameters[0]; return parameter ? new KeyedExecutionContextState(parameter, this) : undefined; } changeKeyedExecutionParameter(value) { this.selectedExecutionContextState = new KeyedExecutionContextState(value, this); } deleteKeyExecutionParameter(value) { pureMultiExecution_deleteExecutionParameter(this.keyedExecutionParameters, value); if (value === this.selectedExecutionContextState?.executionContext) { this.selectedExecutionContextState = this.getInitiallySelectedExecutionContextState(); } } addExecutionParameter(value) { const _mapping = this.editorStore.graphManagerState.usableMappings[0] ?? stub_Mapping(); const _key = new KeyedExecutionParameter(value, PackageableElementExplicitReference.create(_mapping), stub_PackageableRuntime()); pureMultiExecution_addExecutionParameter(this.keyedExecutionParameters, _key, this.editorStore.changeDetectionState.observerContext); this.selectedExecutionContextState = new KeyedExecutionContextState(_key, this); } changeKeyValue(key, value) { keyedExecutionParameter_setKey(key, value); } } //# sourceMappingURL=ServiceExecutionState.js.map