UNPKG

@finos/legend-application-studio

Version:
440 lines 21.7 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 { computed, observable, action, makeObservable, flow, flowResult, override, } from 'mobx'; import { assertErrorThrown, LogEvent, guaranteeType, assertType, StopWatch, filterByType, assertTrue, ActionState, } from '@finos/legend-shared'; import { ElementEditorState } from './ElementEditorState.js'; import { GRAPH_MANAGER_EVENT, LAMBDA_PIPE, ParserError, ConcreteFunctionDefinition, RawLambda, buildSourceInformationSourceId, isStubbed_PackageableElement, reportGraphAnalytics, buildLambdaVariableExpressions, VariableExpression, observe_ValueSpecification, generateFunctionPrettyName, RawVariableExpression, CodeCompletionResult, } from '@finos/legend-graph'; import { ExecutionPlanState, LineageState, LambdaEditorState, LambdaParameterState, LambdaParametersState, PARAMETER_SUBMIT_ACTION, QUERY_BUILDER_EVENT, QueryBuilderTelemetryHelper, buildExecutionParameterValues, getExecutionQueryFromRawLambda, getRawLambdaForLetFuncs, } from '@finos/legend-query-builder'; import { FunctionActivatorState } from './FunctionActivatorState.js'; import { FunctionTestableState } from './function-activator/testable/FunctionTestableState.js'; import { openDataCube } from '../../data-cube/LegendStudioDataCubeHelper.js'; export var FUNCTION_EDITOR_TAB; (function (FUNCTION_EDITOR_TAB) { FUNCTION_EDITOR_TAB["DEFINITION"] = "DEFINITION"; FUNCTION_EDITOR_TAB["TAGGED_VALUES"] = "TAGGED_VALUES"; FUNCTION_EDITOR_TAB["STEREOTYPES"] = "STEREOTYPES"; FUNCTION_EDITOR_TAB["TEST_SUITES"] = "TEST_SUITES"; FUNCTION_EDITOR_TAB["LAMBDAS"] = "LAMBDAS"; })(FUNCTION_EDITOR_TAB || (FUNCTION_EDITOR_TAB = {})); export class FunctionDefinitionEditorState extends LambdaEditorState { editorStore; functionElement; convertingGrammarToProtocolAction = ActionState.create(); isConvertingFunctionBodyToString = false; constructor(functionElement, editorStore) { super('', LAMBDA_PIPE, { typeAheadEnabled: editorStore.applicationStore.config.options.typeAheadEnabled, }); makeObservable(this, { functionElement: observable, isConvertingFunctionBodyToString: observable, convertingGrammarToProtocolAction: observable, }); this.functionElement = functionElement; this.editorStore = editorStore; } get lambdaId() { return buildSourceInformationSourceId([this.functionElement.path]); } *convertLambdaGrammarStringToObject() { if (this.lambdaString) { try { this.convertingGrammarToProtocolAction.inProgress(); const lambda = (yield this.editorStore.graphManagerState.graphManager.pureCodeToLambda(this.fullLambdaString, this.lambdaId)); this.setParserError(undefined); this.functionElement.expressionSequence = lambda.body; } catch (error) { assertErrorThrown(error); if (error instanceof ParserError) { this.setParserError(error); } this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error); } finally { this.convertingGrammarToProtocolAction.complete(); } } else { this.clearErrors(); this.functionElement.expressionSequence = []; } } *convertLambdaObjectToGrammarString(options) { if (!isStubbed_PackageableElement(this.functionElement)) { this.isConvertingFunctionBodyToString = true; try { const lambdas = new Map(); const functionLamba = new RawLambda([], this.functionElement.expressionSequence); lambdas.set(this.lambdaId, functionLamba); const isolatedLambdas = (yield this.editorStore.graphManagerState.graphManager.lambdasToPureCode(lambdas, options?.pretty)); const grammarText = isolatedLambdas.get(this.lambdaId); if (grammarText) { let grammarString = this.extractLambdaString(grammarText); if (this.functionElement.expressionSequence.length > 1 && grammarString.endsWith('}')) { // The lambda object to string converter wraps the lambda inside a '{}' in the case where there are more than one expressions inside the function // causing a parsing error. To handle this we extract only whats inside the '{}' and add ';' to avoid error. grammarString = grammarString.slice(0, -1); grammarString = `${grammarString.endsWith('\n') ? grammarString.slice(0, -1) : grammarString};`; } this.setLambdaString(grammarString); } else { this.setLambdaString(''); } // `firstLoad` flag is used in the first rendering of the function editor (in a `useEffect`) // This flag helps block editing while the JSON is converting to text and to avoid reseting parser/compiler error in reveal error if (!options?.firstLoad) { this.clearErrors({ preserveCompilationError: options?.preserveCompilationError, }); } this.isConvertingFunctionBodyToString = false; } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error); this.isConvertingFunctionBodyToString = false; } } else { this.clearErrors(); this.setLambdaString(''); } } async getCodeComplete(input) { try { return (await this.editorStore.graphManagerState.graphManager.getCodeComplete(input, this.editorStore.graphManagerState.graph, undefined, { ignoreElements: [this.functionElement.path], })); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.PARSING_FAILURE), error); return new CodeCompletionResult(); } } } export class FunctionParametersState extends LambdaParametersState { functionEditorState; constructor(functionEditorState) { super(); makeObservable(this, { parameterValuesEditorState: observable, parameterStates: observable, addParameter: action, removeParameter: action, openModal: action, build: action, setParameters: action, }); this.functionEditorState = functionEditorState; } openModal(lambda) { this.parameterStates = this.build(lambda); this.parameterValuesEditorState.open(() => flowResult(this.functionEditorState.runFunc()).catch(this.functionEditorState.editorStore.applicationStore .alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN); } openDataCubeModal(query, element, editorStore) { this.parameterStates = this.build(query); this.parameterValuesEditorState.open(() => this.functionEditorState .openingDataCube(element, editorStore) .catch(this.functionEditorState.editorStore.applicationStore .alertUnhandledError), PARAMETER_SUBMIT_ACTION.RUN); } build(lambda) { const parameters = buildLambdaVariableExpressions(lambda, this.functionEditorState.editorStore.graphManagerState) .map((parameter) => observe_ValueSpecification(parameter, this.functionEditorState.editorStore.changeDetectionState .observerContext)) .filter(filterByType(VariableExpression)); const states = parameters.map((variable) => { const parmeterState = new LambdaParameterState(variable, this.functionEditorState.editorStore.changeDetectionState.observerContext, this.functionEditorState.editorStore.graphManagerState.graph); parmeterState.mockParameterValue(); return parmeterState; }); return states; } } export class FunctionEditorState extends ElementEditorState { functionDefinitionEditorState; activatorPromoteState; functionTestableEditorState; selectedTab; isRunningFunc = false; isGeneratingPlan = false; isGeneratingLineage = false; executionResult; // NOTE: stored as lossless JSON string executionPlanState; parametersState; lineageState; funcRunPromise = undefined; constructor(editorStore, element) { super(editorStore, element); makeObservable(this, { selectedTab: observable, isRunningFunc: observable, isGeneratingPlan: observable, isGeneratingLineage: observable, executionResult: observable, executionPlanState: observable, label: override, functionElement: computed, setSelectedTab: action, reprocess: action, setExecutionResult: action, setIsRunningFunc: action, runFunc: flow, generatePlan: flow, handleRunFunc: flow, cancelFuncRun: flow, updateFunctionWithQuery: flow, generateLineage: flow, }); assertType(element, ConcreteFunctionDefinition, 'Element inside function editor state must be a function'); this.selectedTab = FUNCTION_EDITOR_TAB.DEFINITION; this.functionDefinitionEditorState = new FunctionDefinitionEditorState(element, this.editorStore); this.activatorPromoteState = new FunctionActivatorState(this); this.executionPlanState = new ExecutionPlanState(this.editorStore.applicationStore, this.editorStore.graphManagerState); this.parametersState = new FunctionParametersState(this); this.functionTestableEditorState = new FunctionTestableState(this); this.lineageState = new LineageState(this.editorStore.applicationStore); } get label() { return generateFunctionPrettyName(this.functionElement, { fullPath: true, spacing: false, }); } get functionElement() { return guaranteeType(this.element, ConcreteFunctionDefinition, 'Element inside function editor state must be a function'); } get activators() { const allActivators = this.editorStore.graphManagerState.graph.functionActivators; return allActivators.filter((activator) => activator.function.value === this.element); } setSelectedTab(tab) { this.selectedTab = tab; } revealCompilationError(compilationError) { let revealed = false; try { if (compilationError.sourceInformation) { this.setSelectedTab(FUNCTION_EDITOR_TAB.DEFINITION); this.functionDefinitionEditorState.setCompilationError(compilationError); revealed = true; } } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.logService.warn(LogEvent.create(GRAPH_MANAGER_EVENT.COMPILATION_FAILURE), `Can't locate error`, error); } return revealed; } clearCompilationError() { this.functionDefinitionEditorState.setCompilationError(undefined); } *updateFunctionWithQuery(val) { const lambdaParam = val.parameters ? val.parameters : []; const parameters = lambdaParam .map((param) => this.editorStore.graphManagerState.graphManager.buildRawValueSpecification(param, this.editorStore.graphManagerState.graph)) .map((rawValueSpec) => guaranteeType(rawValueSpec, RawVariableExpression)); assertTrue(Array.isArray(val.body), `Query body expected to be a list of expressions`); this.functionElement.expressionSequence = val.body; this.functionElement.parameters = parameters; yield flowResult(this.functionDefinitionEditorState.convertLambdaObjectToGrammarString({ pretty: true, firstLoad: true, })); } reprocess(newElement, editorStore) { const functionEditorState = new FunctionEditorState(editorStore, newElement); functionEditorState.selectedTab = this.selectedTab; return functionEditorState; } setIsRunningFunc(val) { this.isRunningFunc = val; } setExecutionResult = (executionResult) => { this.executionResult = executionResult; }; setFuncRunPromise = (promise) => { this.funcRunPromise = promise; }; get bodyExpressionSequence() { return new RawLambda(this.functionElement.parameters.map((parameter) => this.editorStore.graphManagerState.graphManager.serializeRawValueSpecification(parameter)), this.functionElement.expressionSequence); } *generatePlan(debug) { if (this.isGeneratingPlan) { return; } try { const expressionSequence = this.bodyExpressionSequence; 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(expressionSequence, undefined, undefined, 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(expressionSequence, undefined, undefined, 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; } } async handleOpeningDataCube(element, editorStore) { const query = this.bodyExpressionSequence; 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); } *handleRunFunc() { if (this.isRunningFunc) { return; } const expressionSequence = this.bodyExpressionSequence; const parameters = (expressionSequence.parameters ?? []); if (parameters.length) { this.parametersState.openModal(expressionSequence); } else { this.runFunc(); } } *runFunc() { if (this.isRunningFunc) { return; } QueryBuilderTelemetryHelper.logEvent_QueryRunLaunched(this.editorStore.applicationStore.telemetryService); // assure that you are using the latest protocol try { yield flowResult(this.functionDefinitionEditorState.convertLambdaGrammarStringToObject()).catch(this.editorStore.applicationStore.alertUnhandledError); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.notificationService.notifyError(error); return; } let promise; try { this.isRunningFunc = true; const stopWatch = new StopWatch(); const report = reportGraphAnalytics(this.editorStore.graphManagerState.graph); promise = this.editorStore.graphManagerState.graphManager.runQuery(getExecutionQueryFromRawLambda(this.bodyExpressionSequence, this.parametersState.parameterStates, this.editorStore.graphManagerState), undefined, undefined, this.editorStore.graphManagerState.graph, { useLosslessParse: false, parameterValues: buildExecutionParameterValues(this.parametersState.parameterStates, this.editorStore.graphManagerState), }, report); this.setFuncRunPromise(promise); const result = (yield promise); if (this.funcRunPromise === promise) { this.setExecutionResult(result.executionResult); 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.funcRunPromise === promise) { assertErrorThrown(error); this.editorStore.applicationStore.logService.error(LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error); this.editorStore.applicationStore.notificationService.notifyError(error); } } finally { this.isRunningFunc = false; } } *cancelFuncRun() { this.setIsRunningFunc(false); this.setFuncRunPromise(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 expressionSequence = this.bodyExpressionSequence; const lineageRawData = (yield this.editorStore.graphManagerState.graphManager.generateLineage(expressionSequence, undefined, 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; } } } //# sourceMappingURL=FunctionEditorState.js.map