@finos/legend-data-cube
Version:
351 lines • 14.2 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
/**
* 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 { action, computed, makeObservable, observable, override } from 'mobx';
import { DataCubeColumnCreator } from '../../../components/view/extend/DataCubeColumnEditor.js';
import { editor as monacoEditorAPI, Uri } from 'monaco-editor';
import { ActionState, assertErrorThrown, guaranteeNonNullable, hasWhiteSpace, uuid, } from '@finos/legend-shared';
import { buildExecutableQuery } from '../../core/DataCubeQueryBuilder.js';
import { DataCubeColumnDataType, DataCubeColumnKind, DEFAULT_LAMBDA_VARIABLE_NAME, getDataType, isPrimitiveType, } from '../../core/DataCubeQueryEngine.js';
import { CODE_EDITOR_LANGUAGE, setErrorMarkers, } from '@finos/legend-code-editor';
import { EngineError, V1_Lambda, } from '@finos/legend-graph';
import { _lambda } from '../../core/DataCubeQueryBuilderUtils.js';
import { _findCol } from '../../core/model/DataCubeColumn.js';
import { DataCubeCodeEditorState } from '../../../components/view/extend/DataCubeCodeEditorState.js';
export class DataCubeColumnBaseEditorState extends DataCubeCodeEditorState {
uuid = uuid();
_manager;
view;
// NOTE: use UUID in the column name to prevent collision
// when parsing/compiling the expression
_name = `col_${this.uuid.replaceAll('-', '_')}`;
display;
validationState = ActionState.create();
name;
expectedType;
isGroupLevel;
columnKind;
codeSuffix;
model;
queryLambda;
constructor(manager, name, expectedType, isGroupLevel, columnKind) {
super(manager.view.engine);
makeObservable(this, {
name: observable,
setName: action,
isNameValid: computed,
expectedType: observable,
setExpectedType: action,
isTypeValid: computed,
isGroupLevel: observable,
columnKind: observable,
setColumnKind: action,
editor: observable.ref,
setEditor: action,
codeError: observable.ref,
showError: action,
clearError: action,
returnType: observable,
setReturnType: action,
});
this._manager = manager;
this.view = manager.view;
this.model = manager.view.source;
this.engine = this.view.engine;
this.display = this.newDisplay(this);
this.name = name;
this.expectedType = expectedType;
this.isGroupLevel = isGroupLevel;
this.columnKind = columnKind;
this.codePrefix = `->extend(~${this._name}:`;
this.codeSuffix = `)`;
this.editorModelUri = Uri.file(`/${this.uuid}.pure`);
this.editorModel = monacoEditorAPI.createModel('', CODE_EDITOR_LANGUAGE.PURE, this.editorModelUri);
this.queryLambda = this.buildExtendBaseQuery;
}
alertHandler = (error) => {
this.view.dataCube.alertService.alertUnhandledError(error);
};
async initialize() {
this.code = await this.getInitialCode();
this.editorModel.setValue(this.code);
}
setName(value) {
this.name = value;
}
get isNameValid() {
return !this._manager.allColumnNames.includes(this.name);
}
setExpectedType(value) {
this.expectedType = value;
}
get isTypeValid() {
return (this.returnType !== undefined &&
this.expectedType === getDataType(this.returnType));
}
setColumnKind(isGroupLevel, columnKind) {
this.isGroupLevel = isGroupLevel;
this.columnKind = columnKind;
}
showError(error) {
this.codeError = error;
if (error.sourceInformation) {
setErrorMarkers(this.editorModel, [
{
message: error.message,
startLineNumber: error.sourceInformation.startLine,
startColumn: error.sourceInformation.startColumn,
endLineNumber: error.sourceInformation.endLine,
endColumn: error.sourceInformation.endColumn,
},
], this.uuid);
}
}
buildExtendBaseQuery = () => {
const currentSnapshot = guaranteeNonNullable(this._manager.getLatestSnapshot());
const snapshot = currentSnapshot.clone();
if (!this.isGroupLevel) {
snapshot.data.leafExtendedColumns = [];
snapshot.data.selectColumns = [];
snapshot.data.filter = undefined;
snapshot.data.groupBy = undefined;
snapshot.data.pivot = undefined;
}
snapshot.data.groupExtendedColumns = [];
snapshot.data.sortColumns = [];
snapshot.data.limit = undefined;
return _lambda([], [
buildExecutableQuery(snapshot, this.view.source, this.view.engine, {
skipExecutionContext: true,
}),
]);
};
async getReturnType() {
this.validationState.inProgress();
// properly reset the error state before revalidating
this.clearError();
this.setReturnType(undefined);
try {
const returnRelationType = await this.view.engine.getQueryCodeRelationReturnType(this.codePrefix + this.code + this.codeSuffix, this.buildExtendBaseQuery(), this.view.source);
let returnType = _findCol(returnRelationType.columns, this._name)?.type;
returnType =
returnType && isPrimitiveType(returnType) ? returnType : undefined;
this.setReturnType(returnType);
return returnType;
}
catch (error) {
assertErrorThrown(error);
if (error instanceof EngineError) {
this.validationState.fail();
// correct the source information since we added prefix to the code
// and reveal error in the editor
if (error.sourceInformation) {
error.sourceInformation.startColumn -=
error.sourceInformation.startLine === 1
? this.codePrefix.length
: 0;
error.sourceInformation.endColumn -=
error.sourceInformation.endLine === 1 ? this.codePrefix.length : 0;
const fullRange = this.editorModel.getFullModelRange();
if (error.sourceInformation.startLine < 1 ||
(error.sourceInformation.startLine === 1 &&
error.sourceInformation.startColumn < 1) ||
error.sourceInformation.endLine > fullRange.endLineNumber ||
(error.sourceInformation.endLine === fullRange.endLineNumber &&
error.sourceInformation.endColumn > fullRange.endColumn)) {
error.sourceInformation.startColumn = fullRange.startColumn;
error.sourceInformation.startLine = fullRange.startLineNumber;
error.sourceInformation.endColumn = fullRange.endColumn;
error.sourceInformation.endLine = fullRange.endLineNumber;
}
}
this.showError(error);
return undefined;
}
this.view.dataCube.alertService.alertError(error, {
message: `Expression Validation Failure: ${error.message}`,
});
}
finally {
this.validationState.complete();
}
return undefined;
}
close() {
// dispose the editor and its model to avoid memory leak
this.editorModel.dispose();
this.editor?.dispose();
this.display.close();
}
}
export class DataCubeNewColumnState extends DataCubeColumnBaseEditorState {
initialCode;
constructor(manager, referenceColumn) {
super(manager, `col_${manager.allColumnNames.length + 1}`, referenceColumn
? getDataType(referenceColumn.type)
: DataCubeColumnDataType.NUMBER, false, referenceColumn ? referenceColumn.kind : DataCubeColumnKind.MEASURE);
this.initialCode = referenceColumn
? hasWhiteSpace(referenceColumn.name)
? `${DEFAULT_LAMBDA_VARIABLE_NAME}|$${DEFAULT_LAMBDA_VARIABLE_NAME}.'${referenceColumn.name}'`
: `${DEFAULT_LAMBDA_VARIABLE_NAME}|$${DEFAULT_LAMBDA_VARIABLE_NAME}.${referenceColumn.name}`
: `${DEFAULT_LAMBDA_VARIABLE_NAME}|`;
}
async getInitialCode() {
return this.initialCode;
}
newDisplay(state) {
return this.view.dataCube.layoutService.newDisplay('Add New Column', () => _jsx(DataCubeColumnCreator, { state: this }), {
x: 50,
y: 50,
width: 500,
height: 300,
minWidth: 300,
minHeight: 200,
center: false,
});
}
async applyChanges() {
if (!this.validationState.hasCompleted ||
!this.isNameValid ||
!this.isTypeValid) {
return;
}
this.finalizationState.inProgress();
let query;
let returnType;
try {
[query, returnType] = await Promise.all([
this.view.engine.parseValueSpecification(this.code, false),
this.getReturnType(), // recompile to get the return type
]);
}
catch (error) {
assertErrorThrown(error);
this.view.dataCube.alertService.alertError(error, {
message: `Expression Validation Failure: ${error.message}`,
});
return;
}
finally {
this.finalizationState.complete();
}
if (!(query instanceof V1_Lambda)) {
this.view.dataCube.alertService.alertError(new Error(), {
message: `Expression Validation Failure: Expression must be a lambda.`,
});
return;
}
if (!returnType) {
this.view.dataCube.alertService.alertError(new Error(), {
message: `Expression Validation Failure: Can't compute expression return type.`,
});
return;
}
this._manager.addNewColumn({
name: this.name,
type: returnType,
mapFn: this.view.engine.serializeValueSpecification(query),
}, this.isGroupLevel, this.columnKind, this);
this.close();
}
}
export class DataCubeExistingColumnEditorState extends DataCubeColumnBaseEditorState {
initialData;
constructor(manager, column, kind, isGroupLevel) {
super(manager, column.name, getDataType(column.type), isGroupLevel, kind);
makeObservable(this, {
initialData: observable.ref,
isNameValid: override,
});
this.initialData = {
name: column.name,
type: column.type,
kind,
isGroupLevel: isGroupLevel,
mapFn: column.mapFn,
};
}
get isNameValid() {
return !this._manager.allColumnNames
.filter((colName) => colName !== this.initialData.name)
.includes(this.name);
}
async getInitialCode() {
return this.view.engine.getValueSpecificationCode(this.view.engine.deserializeValueSpecification(this.initialData.mapFn), true);
}
newDisplay(state) {
return this.view.dataCube.layoutService.newDisplay('Edit Column', () => _jsx(DataCubeColumnCreator, { state: this }), {
x: 50,
y: 50,
width: 500,
height: 300,
minWidth: 300,
minHeight: 200,
center: false,
});
}
async reset() {
this.setName(this.initialData.name);
this.setExpectedType(getDataType(this.initialData.type));
this.setColumnKind(this.initialData.isGroupLevel, this.initialData.kind);
await this.initialize();
await this.getReturnType();
}
async applyChanges() {
if (!this.validationState.hasCompleted ||
!this.isNameValid ||
!this.isTypeValid) {
return;
}
this.finalizationState.inProgress();
let query;
let returnType;
try {
[query, returnType] = await Promise.all([
this.view.engine.parseValueSpecification(this.code, false),
this.getReturnType(), // recompile to get the return type
]);
}
catch (error) {
assertErrorThrown(error);
this.view.dataCube.alertService.alertError(error, {
message: `Expression Validation Failure: ${error.message}`,
});
return;
}
finally {
this.finalizationState.complete();
}
if (!(query instanceof V1_Lambda)) {
this.view.dataCube.alertService.alertError(new Error(), {
message: `Expression Validation Failure: Expression must be a lambda.`,
});
return;
}
if (!returnType) {
this.view.dataCube.alertService.alertError(new Error(), {
message: `Expression Validation Failure: Can't compute expression return type.`,
});
return;
}
await this._manager.updateColumn(this.initialData.name, {
name: this.name,
type: returnType,
mapFn: this.view.engine.serializeValueSpecification(query),
}, this.isGroupLevel, this.columnKind);
}
}
//# sourceMappingURL=DataCubeColumnEditorState.js.map