diff options
Diffstat (limited to 'catalog-ui/src/app/models/properties-inputs')
4 files changed, 269 insertions, 18 deletions
diff --git a/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts b/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts index 8ed5c734c0..33e83ce00a 100644 --- a/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts +++ b/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts @@ -18,14 +18,18 @@ * ============LICENSE_END========================================================= */ +import * as _ from "lodash"; import { SchemaPropertyGroupModel, SchemaProperty } from '../aschema-property'; -import { DerivedPropertyType, PropertyBEModel } from '../../models'; +import { DerivedPropertyType, PropertyBEModel, PropertyFEModel } from '../../models'; import { PROPERTY_TYPES } from 'app/utils'; import { UUID } from "angular2-uuid"; export class DerivedFEProperty extends PropertyBEModel { - valueObj: any; + valueObj: any; + valueObjIsValid: boolean; + valueObjOrig: any; + valueObjIsChanged: boolean; parentName: string; propertiesName: string; //"network_assignments#ipv4_subnet#use_ipv4 = parentPath + name derivedDataType: DerivedPropertyType; @@ -36,6 +40,7 @@ export class DerivedFEProperty extends PropertyBEModel { isChildOfListOrMap: boolean; canBeDeclared: boolean; mapKey: string; + mapKeyError: string; constructor(property: PropertyBEModel, parentName?: string, createChildOfListOrMap?: boolean, key?:string, value?:any) { if (!createChildOfListOrMap) { //creating a standard derived prop @@ -54,17 +59,44 @@ export class DerivedFEProperty extends PropertyBEModel { if (property.type == PROPERTY_TYPES.LIST) { this.mapKey = property.schema.property.type.split('.').pop(); + this.mapKeyError = null; this.type = property.schema.property.type; } else { //map - this.mapKey = key || ""; + if (key) { + this.mapKey = key; + this.mapKeyError = null; + } else { + this.mapKey = ''; + this.mapKeyError = 'Key cannot be empty.'; + } this.type = property.type; } this.valueObj = (this.type == PROPERTY_TYPES.JSON && typeof value == 'object') ? JSON.stringify(value) : value; this.schema = new SchemaPropertyGroupModel(new SchemaProperty(property.schema.property)); + this.updateValueObjOrig(); } + this.valueObjIsValid = true; this.derivedDataType = this.getDerivedPropertyType(); } - + + public getActualMapKey() { + return (this.mapKeyError) ? this.name : this.mapKey; + } + + public updateValueObj(valueObj:any, isValid:boolean) { + this.valueObj = PropertyFEModel.cleanValueObj(valueObj); + this.valueObjIsValid = isValid; + this.valueObjIsChanged = this.hasValueObjChanged(); + } + + public updateValueObjOrig() { + this.valueObjOrig = _.cloneDeep(this.valueObj); + this.valueObjIsChanged = false; + } + + public hasValueObjChanged() { + return !_.isEqual(this.valueObj, this.valueObjOrig); + } } export class DerivedFEPropertyMap { [parentPath: string]: Array<DerivedFEProperty>; diff --git a/catalog-ui/src/app/models/properties-inputs/input-be-model.ts b/catalog-ui/src/app/models/properties-inputs/input-be-model.ts index f6548a352c..942d775c6e 100644 --- a/catalog-ui/src/app/models/properties-inputs/input-be-model.ts +++ b/catalog-ui/src/app/models/properties-inputs/input-be-model.ts @@ -41,9 +41,6 @@ export class InputBEModel extends PropertyBEModel { this.inputPath = input.inputPath; } - public toJSON = (): any => { - }; - } export interface ComponentInstanceModel extends InputBEModel { diff --git a/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts b/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts index 630374aac3..c349f41ea2 100644 --- a/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts +++ b/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts @@ -18,15 +18,22 @@ * ============LICENSE_END========================================================= */ +import * as _ from "lodash"; import { SchemaPropertyGroupModel, SchemaProperty } from "../aschema-property"; -import { PropertyBEModel } from "../../models"; +import {PropertyFEModel} from "../../models"; import {PROPERTY_DATA} from "../../utils/constants"; import {InputBEModel} from "./input-be-model"; +import {DerivedPropertyType} from "./property-be-model"; export class InputFEModel extends InputBEModel { isSimpleType: boolean; relatedPropertyValue: any; relatedPropertyName: string; + defaultValueObj:any; + defaultValueObjIsValid:boolean; + defaultValueObjOrig:any; + defaultValueObjIsChanged:boolean; + derivedDataType: DerivedPropertyType; constructor(input?: InputBEModel) { super(input); @@ -37,7 +44,37 @@ export class InputFEModel extends InputBEModel { this.relatedPropertyValue = relatedProperty.value; this.relatedPropertyName = relatedProperty.name; } + this.derivedDataType = this.getDerivedPropertyType(); + this.resetDefaultValueObjValidation(); + this.updateDefaultValueObjOrig(); } } + public updateDefaultValueObj(defaultValueObj:any, isValid:boolean) { + this.defaultValueObj = PropertyFEModel.cleanValueObj(defaultValueObj); + this.defaultValueObjIsValid = isValid; + this.defaultValueObjIsChanged = this.hasDefaultValueChanged(); + } + + public updateDefaultValueObjOrig() { + this.defaultValueObjOrig = _.cloneDeep(this.defaultValueObj); + this.defaultValueObjIsChanged = false; + } + + public getJSONDefaultValue(): string { + return PropertyFEModel.stringifyValueObj(this.defaultValueObj, this.schema.property.type, this.derivedDataType); + } + + public getDefaultValueObj(): any { + return PropertyFEModel.parseValueObj(this.defaultValue, this.type, this.derivedDataType); + } + + public resetDefaultValueObjValidation() { + this.defaultValueObjIsValid = true; + } + + hasDefaultValueChanged(): boolean { + return !_.isEqual(this.defaultValueObj, this.defaultValueObjOrig); + } + }
\ No newline at end of file diff --git a/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts b/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts index 6faa6ada84..a0c087bdc2 100644 --- a/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts +++ b/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts @@ -18,6 +18,7 @@ * ============LICENSE_END========================================================= */ +import * as _ from "lodash"; import {SchemaPropertyGroupModel, SchemaProperty} from '../aschema-property'; import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils'; import { FilterPropertiesAssignmentData, PropertyBEModel, DerivedPropertyType, DerivedFEPropertyMap, DerivedFEProperty } from 'app/models'; @@ -34,6 +35,10 @@ export class PropertyFEModel extends PropertyBEModel { propertiesName: string; uniqueId: string; valueObj: any; //this is the only value we relate to in the html templates + valueObjValidation: any; + valueObjIsValid: boolean; + valueObjOrig: any; //this is valueObj representation as saved in server + valueObjIsChanged: boolean; derivedDataType: DerivedPropertyType; constructor(property: PropertyBEModel){ @@ -44,19 +49,50 @@ export class PropertyFEModel extends PropertyBEModel { this.derivedDataType = this.getDerivedPropertyType(); this.flattenedChildren = []; this.propertiesName = this.name; + this.valueObj = null; + this.updateValueObjOrig(); + this.resetValueObjValidation(); } - public getJSONValue = (): string => { - //If type is JSON, need to try parsing it before we stringify it so that it appears property in TOSCA - change per Bracha due to AMDOCS - //TODO: handle this.derivedDataType == DerivedPropertyType.MAP - if (this.derivedDataType == DerivedPropertyType.LIST && this.schema.property.type == PROPERTY_TYPES.JSON) { - try { - return JSON.stringify(this.valueObj.map(item => (typeof item == 'string')? JSON.parse(item) : item)); - } catch (e){} + public updateValueObj(valueObj:any, isValid:boolean) { + this.valueObj = PropertyFEModel.cleanValueObj(valueObj); + this.valueObjValidation = this.valueObjIsValid = isValid; + this.valueObjIsChanged = this.hasValueObjChanged(); + } + + public updateValueObjOrig() { + this.valueObjOrig = _.cloneDeep(this.valueObj); + this.valueObjIsChanged = false; + } + + public calculateValueObjIsValid(valueObjValidation?: any) { + valueObjValidation = (valueObjValidation !== undefined) ? valueObjValidation : this.valueObjValidation; + if (valueObjValidation instanceof Array) { + return valueObjValidation.every((v) => this.calculateValueObjIsValid(v)); + } else if (valueObjValidation instanceof Object) { + return Object.keys(valueObjValidation).every((k) => this.calculateValueObjIsValid(valueObjValidation[k])); } + return Boolean(valueObjValidation); + } - return (this.derivedDataType == DerivedPropertyType.SIMPLE) ? this.valueObj : JSON.stringify(this.valueObj); + public resetValueObjValidation() { + if (this.derivedDataType === DerivedPropertyType.SIMPLE) { + this.valueObjValidation = null; + } else if (this.derivedDataType === DerivedPropertyType.LIST) { + this.valueObjValidation = []; + } else { + this.valueObjValidation = {}; + } + this.valueObjIsValid = true; + } + + public getJSONValue = (): string => { + return PropertyFEModel.stringifyValueObj(this.valueObj, this.schema.property.type, this.derivedDataType); + } + + public getValueObj = (): any => { + return PropertyFEModel.parseValueObj(this.value, this.type, this.derivedDataType, this.defaultValue); } public setNonDeclared = (childPath?: string): void => { @@ -106,12 +142,94 @@ export class PropertyFEModel extends PropertyBEModel { public childPropUpdated = (childProp: DerivedFEProperty): void => { let parentNames = this.getParentNamesArray(childProp.propertiesName, []); if (parentNames.length) { - _.set(this.valueObj, parentNames.join('.'), childProp.valueObj); + const childPropName = parentNames.join('.'); + // unset value only if is null and valid, and not in a list + if (childProp.valueObj === null && childProp.valueObjIsValid) { + const parentChildProp = this.flattenedChildren.find((ch) => ch.propertiesName === childProp.parentName) || this; + if (parentChildProp.derivedDataType !== DerivedPropertyType.LIST) { + _.unset(this.valueObj, childPropName); + this.valueObj = PropertyFEModel.cleanValueObj(this.valueObj); + } else { + _.set(this.valueObj, childPropName, null); + } + } else { + _.set(this.valueObj, childPropName, childProp.valueObj); + } + if (childProp.valueObjIsChanged) { + _.set(this.valueObjValidation, childPropName, childProp.valueObjIsValid); + this.valueObjIsValid = childProp.valueObjIsValid && this.calculateValueObjIsValid(); + this.valueObjIsChanged = true; + } else { + _.unset(this.valueObjValidation, childPropName); + this.valueObjIsValid = this.calculateValueObjIsValid(); + this.valueObjIsChanged = this.hasValueObjChanged(); + } + } + }; + + childPropMapKeyUpdated = (childProp: DerivedFEProperty, newMapKey: string, forceValidate: boolean = false) => { + if (!childProp.isChildOfListOrMap || childProp.derivedDataType !== DerivedPropertyType.MAP) { + return; + } + + const childParentNames = this.getParentNamesArray(childProp.parentName); + const oldActualMapKey = childProp.getActualMapKey(); + + childProp.mapKey = newMapKey; + if (childProp.mapKey === null) { // null -> remove map key + childProp.mapKeyError = null; + } else if (!childProp.mapKey) { + childProp.mapKeyError = 'Key cannot be empty.'; + } else if (this.flattenedChildren + .filter((fch) => fch !== childProp && fch.parentName === childProp.parentName) // filter sibling child props + .map((fch) => fch.mapKey) + .indexOf(childProp.mapKey) !== -1) { + childProp.mapKeyError = 'This key already exists.'; + } else { + childProp.mapKeyError = null; + } + const newActualMapKey = childProp.getActualMapKey(); + const newMapKeyIsValid = !childProp.mapKeyError; + + // if mapKey was changed, then replace the old key with the new one + if (newActualMapKey !== oldActualMapKey) { + const oldChildPropNames = childParentNames.concat([oldActualMapKey]); + const newChildPropNames = (newActualMapKey) ? childParentNames.concat([newActualMapKey]) : null; + + // add map key to valueObj and valueObjValidation + if (newChildPropNames) { + const newChildVal = _.get(this.valueObj, oldChildPropNames); + if (newChildVal !== undefined) { + _.set(this.valueObj, newChildPropNames, newChildVal); + _.set(this.valueObjValidation, newChildPropNames, _.get(this.valueObjValidation, oldChildPropNames, childProp.valueObjIsValid)); + } + } + + // remove map key from valueObj and valueObjValidation + _.unset(this.valueObj, oldChildPropNames); + _.unset(this.valueObjValidation, oldChildPropNames); + + // force validate after map key change + forceValidate = true; + } + + if (forceValidate) { + // add custom entry for map key validation: + const childMapKeyNames = childParentNames.concat(`%%KEY:${childProp.name}%%`); + if (newActualMapKey) { + _.set(this.valueObjValidation, childMapKeyNames, newMapKeyIsValid); + } else { + _.unset(this.valueObjValidation, childMapKeyNames); + } + + this.valueObjIsValid = newMapKeyIsValid && this.calculateValueObjIsValid(); + this.valueObjIsChanged = this.hasValueObjChanged(); } }; /* Returns array of individual parents for given prop path, with list/map UUIDs replaced with index/mapkey */ public getParentNamesArray = (parentPropName: string, parentNames?: Array<string>): Array<string> => { + parentNames = parentNames || []; if (parentPropName.indexOf("#") == -1) { return parentNames; } //finished recursing parents. return let parentProp: DerivedFEProperty = this.flattenedChildren.find(prop => prop.propertiesName === parentPropName); @@ -119,7 +237,7 @@ export class PropertyFEModel extends PropertyBEModel { if (parentProp.isChildOfListOrMap) { if (parentProp.derivedDataType == DerivedPropertyType.MAP) { - nameToInsert = parentProp.mapKey; + nameToInsert = parentProp.getActualMapKey(); } else { //LIST let siblingProps = this.flattenedChildren.filter(prop => prop.parentName == parentProp.parentName).map(prop => prop.propertiesName); nameToInsert = siblingProps.indexOf(parentProp.propertiesName).toString(); @@ -130,5 +248,72 @@ export class PropertyFEModel extends PropertyBEModel { return this.getParentNamesArray(parentProp.parentName, parentNames); //continue recursing } + public hasValueObjChanged() { + return !_.isEqual(this.valueObj, this.valueObjOrig); + } + static stringifyValueObj(valueObj: any, propertyType: PROPERTY_TYPES, propertyDerivedType: DerivedPropertyType): string { + // if valueObj is null, return null + if (valueObj === null || valueObj === undefined) { + return null; + } + + //If type is JSON, need to try parsing it before we stringify it so that it appears property in TOSCA - change per Bracha due to AMDOCS + //TODO: handle this.derivedDataType == DerivedPropertyType.MAP + if (propertyDerivedType == DerivedPropertyType.LIST && propertyType == PROPERTY_TYPES.JSON) { + try { + return JSON.stringify(valueObj.map(item => (typeof item == 'string') ? JSON.parse(item) : item)); + } catch (e){} + } + + // if type is anything but string, then stringify valueObj + if ((typeof valueObj) !== 'string') { + return JSON.stringify(valueObj); + } + + // return string value as is + return valueObj; + } + + static parseValueObj(value: string, propertyType: PROPERTY_TYPES, propertyDerivedType: DerivedPropertyType, defaultValue?: string): any { + let valueObj; + if (propertyDerivedType === DerivedPropertyType.SIMPLE) { + valueObj = value || defaultValue || null; // use null for empty value object + if (valueObj && + propertyType !== PROPERTY_TYPES.STRING && + propertyType !== PROPERTY_TYPES.JSON && + PROPERTY_DATA.SCALAR_TYPES.indexOf(<string>propertyType) == -1) { + valueObj = JSON.parse(value); // the value object contains the real value ans not the value as string + } + } else if (propertyDerivedType == DerivedPropertyType.LIST) { + valueObj = _.merge([], JSON.parse(defaultValue || '[]'), JSON.parse(value || '[]')); // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined. + } else { + valueObj = _.merge({}, JSON.parse(defaultValue || '{}'), JSON.parse(value || '{}')); // value object should be merged value and default value. Value takes higher precedence. Set value object to empty obj if undefined. + } + return valueObj; + }; + + static cleanValueObj(valueObj: any, unsetEmpty?: boolean): any { + // By default - unsetEmpty undefined - will make valueObj cleaned (no null or empty objects, but array will keep null or empty objects). + if (valueObj === undefined || valueObj === null || valueObj === '') { + return null; + } + if (valueObj instanceof Array) { + const cleanArr = valueObj.map((v) => PropertyFEModel.cleanValueObj(v)).filter((v) => v !== null); + valueObj.splice(0, valueObj.length, ...cleanArr) + } else if (valueObj instanceof Object) { + Object.keys(valueObj).forEach((k) => { + // clean each item in the valueObj (by default, unset empty objects) + valueObj[k] = PropertyFEModel.cleanValueObj(valueObj[k], unsetEmpty !== undefined ? unsetEmpty : true); + if (valueObj[k] === null) { + delete valueObj[k]; + } + }); + // if unsetEmpty flag is true and valueObj is empty + if (unsetEmpty && !Object.keys(valueObj).length) { + return null; + } + } + return valueObj; + } } |