UNPKG

@finos/legend-application-studio

Version:
247 lines 10 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 { 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