@finos/legend-application-studio
Version:
Legend Studio application core
247 lines • 10 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 { action, makeObservable, observable } from 'mobx';
import { getAllClassProperties, PrimitiveType, PRIMITIVE_TYPE, PROPERTY_ACCESSOR, classHasCycle, } from '@finos/legend-graph';
import { isString, uuid, prettyCONSTName, } from '@finos/legend-shared';
export class ProtocolValueFieldNode {
id;
label;
builderState;
property;
propertyPath;
isOpen = true;
childrenIds = [];
childrenNodes = [];
constructor(id, label, builderState, property, propertyPath) {
makeObservable(this, {
isOpen: observable,
setIsOpen: action,
});
this.id = id;
this.label = label;
this.builderState = builderState;
this.property = property;
this.propertyPath = propertyPath;
}
setIsOpen(val) {
this.isOpen = val;
}
}
export class StringFieldNode extends ProtocolValueFieldNode {
value;
constructor(id, label, builderState, property, propertyPath, initialValue) {
super(id, label, builderState, property, propertyPath);
makeObservable(this, {
value: observable,
setValue: action,
});
this.value = initialValue;
}
setValue(val) {
this.value = val;
this.builderState.onValueChange?.(this.builderState.getValue());
}
getValue() {
return this.value;
}
}
export class OptionalStringFieldNode extends ProtocolValueFieldNode {
value;
constructor(id, label, builderState, property, propertyPath, initialValue) {
super(id, label, builderState, property, propertyPath);
makeObservable(this, {
value: observable,
setValue: action,
});
this.value = initialValue;
}
setValue(val) {
this.value = val;
this.builderState.onValueChange?.(this.builderState.getValue());
}
getValue() {
return this.value;
}
}
export class UnsupportedFieldNode extends ProtocolValueFieldNode {
value; // we will not support editing unknown value
constructor(id, label, builderState, property, propertyPath, initialValue) {
super(id, label, builderState, property, propertyPath);
this.value = initialValue;
}
getValue() {
return this.value;
}
}
export class ProtocolValueBuilderState {
type;
isCyclic;
graph;
excludedPaths;
initialValue;
onValueChange;
decorateValue;
treeData;
constructor(type, options) {
makeObservable(this, { treeData: observable.ref });
this.type = type;
this.graph = options.graph;
this.onValueChange = options.onValueChange;
this.decorateValue = options.decorateValue;
this.excludedPaths = new Map();
options.excludedPaths.forEach((path) => {
const idx = path.indexOf(PROPERTY_ACCESSOR);
if (idx === -1) {
return;
}
const classPath = path.substring(0, idx);
const propertyName = path.substring(idx + 1);
if (!this.excludedPaths.has(classPath)) {
this.excludedPaths.set(classPath, [propertyName]);
}
else {
this.excludedPaths.get(classPath)?.push(propertyName);
}
});
this.isCyclic = classHasCycle(type, {
traverseNonRequiredProperties: true,
excludedPaths: this.excludedPaths,
});
this.initialValue =
options.initialValue ??
(this.isCyclic ? {} : this.generateInitialValue(type));
this.treeData = this.isCyclic ? undefined : this.buildTreeData(type);
}
buildTreeData(type) {
const rootIds = [];
const nodes = new Map();
getAllClassProperties(type).forEach((property) => {
// filter properties
const propertyOwnerPath = property._OWNER.path;
const excludedProperties = this.excludedPaths.get(propertyOwnerPath) ?? [];
if (excludedProperties.includes(property.name)) {
return;
}
const propertyType = property.genericType.value.rawType;
const multiplicity = property.multiplicity;
const childNodeValue = this.initialValue[property.name];
const childNodeId = uuid();
let childNode;
if (!(propertyType instanceof PrimitiveType)) {
childNode = new UnsupportedFieldNode(uuid(), '', this, property, property.name, childNodeValue);
}
else {
switch (propertyType.name) {
case PRIMITIVE_TYPE.STRING: {
if (multiplicity.lowerBound === 0) {
childNode = new OptionalStringFieldNode(childNodeId, '', this, property, property.name, childNodeValue === undefined
? undefined
: isString(childNodeValue)
? childNodeValue
: '');
}
else if (multiplicity.upperBound === 1) {
childNode = new StringFieldNode(childNodeId, '', this, property, property.name, isString(childNodeValue) ? childNodeValue : '');
}
else {
childNode = new UnsupportedFieldNode(childNodeId, '', this, property, property.name, childNodeValue);
}
break;
}
default: {
childNode = new UnsupportedFieldNode(childNodeId, '', this, property, property.name, childNodeValue);
break;
}
}
}
nodes.set(childNodeId, childNode);
});
nodes.forEach((node) => {
rootIds.push(node.id);
});
return { rootIds, nodes };
}
generateInitialValue(type) {
const value = {};
const excludedProperties = this.excludedPaths.get(type.path) ?? [];
getAllClassProperties(type)
.filter((property) => !excludedProperties.includes(property.name))
.forEach((property) => {
// NOTE: for now, we only handle very basic properties: i.e. primitive property (except Date) with multiplicity [0..1] or [1]
// we have not handled list of primitives, enumeration type, list of enumeration values, complex type
//
// Note that subtype is something we need to think very carefully about: displaying subtype in the form is not necessarily hard
// as we can draw a selector dropdown for the type, but in terms of actually generate the right protocol value, it depends on the
// use case and that could add complexity, for example, if we use this to generate Pure protocol, we would need the information
// for _type flag for subtype
const propertyType = property.genericType.value.rawType;
const multiplicity = property.multiplicity;
if (!(propertyType instanceof PrimitiveType) ||
(multiplicity.upperBound && multiplicity.upperBound > 1)) {
return;
}
if (multiplicity.lowerBound === 0) {
value[property.name] = undefined;
}
else {
switch (propertyType.name) {
case PRIMITIVE_TYPE.BOOLEAN: {
value[property.name] = false;
break;
}
case PRIMITIVE_TYPE.FLOAT:
case PRIMITIVE_TYPE.DECIMAL: {
value[property.name] = 0.0;
break;
}
case PRIMITIVE_TYPE.NUMBER:
case PRIMITIVE_TYPE.INTEGER: {
value[property.name] = 0;
break;
}
case PRIMITIVE_TYPE.STRING: {
// NOTE: this just provides some default value, and they are not necessarily sensible
// perhaps, we can create a mechanism for this class where we can inject value to fields
// but that might violate the generic usage principle of this builder as this builder's
// user should be relatively agnostic to the extension they are picking
value[property.name] = prettyCONSTName(property.name);
break;
}
case PRIMITIVE_TYPE.DATE:
case PRIMITIVE_TYPE.STRICTDATE:
case PRIMITIVE_TYPE.DATETIME:
default:
return; // unsupported
}
}
});
return value;
}
getValue() {
if (!this.treeData) {
return {};
}
const value = {};
this.treeData.rootIds.forEach((rootId) => {
const node = this.treeData?.nodes.get(rootId);
if (node) {
value[node.property.name] = node.getValue();
}
});
return this.decorateValue ? this.decorateValue(value) : value;
}
}
//# sourceMappingURL=ProtocolValueBuilderState.js.map