@finos/legend-application-studio
Version:
Legend Studio application core
440 lines • 21.7 kB
JavaScript
/**
* 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