3 * Copyright 2016 RIFT.IO Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 * Created by onvelocity on 1/18/16.
21 * This class generates the form fields used to edit the CONFD JSON model.
25 import _
from 'lodash'
26 import utils
from '../libraries/utils'
27 import React
from 'react'
28 import ClassNames
from 'classnames'
29 import changeCase
from 'change-case'
30 import toggle
from '../libraries/ToggleElementHandler'
31 import Button
from './Button'
32 import Property
from '../libraries/model/DescriptorModelMetaProperty'
33 import ComposerAppActions
from '../actions/ComposerAppActions'
34 import CatalogItemsActions
from '../actions/CatalogItemsActions'
35 import DESCRIPTOR_MODEL_FIELDS
from '../libraries/model/DescriptorModelFields'
36 import DescriptorModelFactory
from '../libraries/model/DescriptorModelFactory'
37 import DescriptorModelMetaFactory
from '../libraries/model/DescriptorModelMetaFactory'
38 import SelectionManager
from '../libraries/SelectionManager'
39 import DeletionManager
from '../libraries/DeletionManager'
40 import DescriptorModelIconFactory
from '../libraries/model/IconFactory'
41 import getEventPath
from '../libraries/getEventPath'
42 import CatalogDataStore
from '../stores/CatalogDataStore'
44 import imgAdd
from '../../../node_modules/open-iconic/svg/plus.svg'
45 import imgRemove
from '../../../node_modules/open-iconic/svg/trash.svg'
47 import '../styles/EditDescriptorModelProperties.scss'
48 import '../styles/EditConfigParameterMap.scss';
51 function getDescriptorMetaBasicForType(type
) {
52 const basicPropertiesFilter
= d
=> _
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
53 return DescriptorModelMetaFactory
.getModelMetaForType(type
, basicPropertiesFilter
) || {properties
: []};
56 function getDescriptorMetaAdvancedForType(type
) {
57 const advPropertiesFilter
= d
=> !_
.includes(DESCRIPTOR_MODEL_FIELDS
[type
], d
.name
);
58 return DescriptorModelMetaFactory
.getModelMetaForType(type
, advPropertiesFilter
) || {properties
: []};
61 function getTitle(model
= {}) {
62 if (typeof model
['short-name'] === 'string' && model
['short-name']) {
63 return model
['short-name'];
65 if (typeof model
.name
=== 'string' && model
.name
) {
68 if (model
.uiState
&& typeof model
.uiState
.displayName
=== 'string' && model
.uiState
.displayName
) {
69 return model
.uiState
.displayName
71 if (typeof model
.id
=== 'string') {
75 function startEditing() {
76 DeletionManager
.removeEventListeners();
79 function endEditing() {
80 DeletionManager
.addEventListeners();
83 function onClickSelectItem(property
, path
, value
, event
) {
84 event
.preventDefault();
85 const root
= this.getRoot();
86 if (SelectionManager
.select(value
)) {
87 CatalogItemsActions
.catalogItemMetaDataChanged(root
.model
);
91 function onFocusPropertyFormInputElement(property
, path
, value
, event
) {
93 event
.preventDefault();
96 function removeIsFocusedClass(event
) {
97 event
.target
.removeEventListener('blur', removeIsFocusedClass
);
98 Array
.from(document
.querySelectorAll('.-is-focused')).forEach(d
=> d
.classList
.remove('-is-focused'));
101 removeIsFocusedClass(event
);
103 const propertyWrapper
= getEventPath(event
).reduce((parent
, element
) => {
107 if (!element
.classList
) {
110 if (element
.classList
.contains('property')) {
115 if (propertyWrapper
) {
116 propertyWrapper
.classList
.add('-is-focused');
117 event
.target
.addEventListener('blur', removeIsFocusedClass
);
122 function buildAddPropertyAction(container
, property
, path
) {
123 function onClickAddProperty(property
, path
, event
) {
124 event
.preventDefault();
125 //SelectionManager.resume();
126 const create
= Property
.getContainerCreateMethod(property
, this);
129 create(model
, path
, property
);
131 const name
= path
.join('.');
132 const value
= Property
.createModelInstance(property
);
133 utils
.assignPathValue(this.model
, name
, value
);
135 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
138 <Button className
="inline-hint" onClick
={onClickAddProperty
.bind(container
, property
, path
)} label
="Add" src
={imgAdd
} />
142 function buildRemovePropertyAction(container
, property
, path
) {
143 function onClickRemoveProperty(property
, path
, event
) {
144 event
.preventDefault();
145 const name
= path
.join('.');
146 const removeMethod
= Property
.getContainerMethod(property
, this, 'remove');
148 removeMethod(utils
.resolvePath(this.model
, name
));
150 utils
.removePathValue(this.model
, name
);
152 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
155 <Button className
="remove-property-action inline-hint" title
="Remove" onClick
={onClickRemoveProperty
.bind(container
, property
, path
)} label
="Remove" src
={imgRemove
}/>
159 function onFormFieldValueChanged(event
) {
160 if (DescriptorModelFactory
.isContainer(this)) {
161 event
.preventDefault();
162 const name
= event
.target
.name
;
163 const value
= JSON
.parse(event
.target
.value
);
164 utils
.assignPathValue(this.model
, 'config-parameter-source.config-parameter-source-ref', value
.value
);
165 utils
.assignPathValue(this.model
, 'config-parameter-source.member-vnf-index-ref', value
.index
);
166 CatalogItemsActions
.catalogItemDescriptorChanged(this.getRoot());
170 function buildField(container
, property
, path
, value
, fieldKey
, vnfdIndex
) {
171 let cds
= CatalogDataStore
;
172 let catalogs
= cds
.getTransientCatalogs();
174 const name
= path
.join('.');
175 const isEditable
= true;
176 const isGuid
= Property
.isGuid(property
);
177 const onChange
= onFormFieldValueChanged
.bind(container
);
178 const isEnumeration
= Property
.isEnumeration(property
);
179 const isLeafRef
= Property
.isLeafRef(property
);
180 const onFocus
= onFocusPropertyFormInputElement
.bind(container
, property
, path
, value
);
181 const placeholder
= changeCase
.title(property
.name
);
182 const className
= ClassNames(property
.name
+ '-input', {'-is-guid': isGuid
});
183 const fieldValue
= value
? (value
.constructor.name
!= "Object") ? value
: '' : undefined;
184 // if (isEnumeration) {
185 // const enumeration = Property.getEnumeration(property, value);
186 // const options = enumeration.map((d, i) => {
187 // // note yangforge generates values for enums but the system does not use them
188 // // so we categorically ignore them
189 // // https://trello.com/c/uzEwVx6W/230-bug-enum-should-not-use-index-only-name
190 // //return <option key={fieldKey + ':' + i} value={d.value}>{d.name}</option>;
191 // return <option key={fieldKey.toString() + ':' + i} value={d.name}>{d.name}</option>;
193 // const isValueSet = enumeration.filter(d => d.isSelected).length > 0;
194 // if (!isValueSet || property.cardinality === '0..1') {
195 // const noValueDisplayText = changeCase.title(property.name);
196 // options.unshift(<option key={'(value-not-in-enum)' + fieldKey.toString()} value="" placeholder={placeholder}>{noValueDisplayText}</option>);
198 // return <select key={fieldKey.toString()} id={fieldKey.toString()} className={ClassNames({'-value-not-set': !isValueSet})} name={name} value={value} title={name} onChange={onChange} onFocus={onFocus} onBlur={endEditing} onMouseDown={startEditing} onMouseOver={startEditing} readOnly={!isEditable}>{options}</select>;
201 if (isLeafRef
&& (path
.indexOf("config-parameter-source-ref") > -1)) {
202 let fullFieldKey
= _
.isArray(fieldKey
) ? fieldKey
.join(':') : fieldKey
;
203 let containerRef
= container
;
206 let leafRefPathValues
= [];
207 while (containerRef
.parent
) {
208 fullFieldKey
= containerRef
.parent
.key
+ ':' + fullFieldKey
;
209 containerRef
= containerRef
.parent
;
211 let parentProperty
= container
.parent
.constituentVnfd
;
212 parentProperty
.map((v
, i
) => {
213 let somevalues
= Property
.getConfigParamRef(property
, path
, value
, fullFieldKey
, catalogs
, container
, v
.vnfdId
);
214 options
= somevalues
&& options
.concat(somevalues
.map((d
, i
) => {
215 return <option key
={v
.vnfdId
+ ':' + fieldKey
.toString() + ':' + i
} value
={JSON
.stringify({value
: d
.value
, index
: v
.vnfdIndex
})}>{`${v['short-name']} (${v.vnfdIndex}) / ${d.value}`}</option
>;
217 leafRefPathValues
= leafRefPathValues
.concat(somevalues
);
221 const isValueSet
= leafRefPathValues
.filter(d
=> d
.isSelected
).length
> 0;
223 const noValueDisplayText
= changeCase
.title(property
.name
);
224 options
.unshift(<option key
={'(value-not-in-leafref)' + fieldKey
.toString()} value
="" placeholder
={placeholder
}>{noValueDisplayText
}</option
>);
226 return <select key
={fieldKey
.toString()} id
={fieldKey
.toString()} className
={ClassNames({'-value-not-set': !isValueSet
})} name
={name
} value
={JSON
.stringify({value
:value
, index
: container
.model
['config-parameter-source']['member-vnf-index-ref']})} title
={name
} onChange
={onChange
} onFocus
={onFocus
} onBlur
={endEditing
} onMouseDown
={startEditing
} onMouseOver
={startEditing
} readOnly
={!isEditable
}>{options
}</select
>;
230 function buildElement(container
, property
, valuePath
, value
, vnfdIndex
) {
231 return property
.properties
.map((property
, index
) => {
233 const childPath
= valuePath
.slice();
234 if (typeof value
=== 'object') {
235 childValue
= value
[property
.name
];
237 if(property
.type
!= 'choice'){
238 childPath
.push(property
.name
);
240 return build(container
, property
, childPath
, childValue
, {}, vnfdIndex
);
246 function buildSimpleListItem(container
, property
, path
, value
, key
, index
) {
247 // todo need to abstract this better
248 const title
= getTitle(value
);
249 var req
= require
.context("../", true, /\.svg
/);
252 <a href
="#select-list-item" key
={Date
.now()} className
={property
.name
+ '-list-item simple-list-item '} onClick
={onClickSelectItem
.bind(container
, property
, path
, value
)}>
253 <img src
={req('./' + DescriptorModelIconFactory
.getUrlForType(property
.name
))} width
="20px" />
256 {buildRemovePropertyAction(container
, property
, path
)}
261 function buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) {
262 const className
= ClassNames(property
.name
+ '-remove actions');
264 <div key
={fieldKey
.concat(index
).join(':')} className
={className
}>
266 <span className
={property
.type
+ '-name name'}>{changeCase
.title(property
.name
)}</span
>
267 <span className
="info">{index
+ 1}</span
>
268 {buildRemovePropertyAction(container
, property
, valuePath
)}
274 function buildLeafListItem(container
, property
, valuePath
, value
, key
, index
, vnfdIndex
) {
275 // look at the type to determine how to parse the value
278 {buildRemoveListItem(container
, property
, valuePath
, key
, index
)}
279 {buildField(container
, property
, valuePath
, value
, key
, vnfdIndex
)}
285 function build(container
, property
, path
, value
, props
= {}, vnfdIndex
) {
287 const isLeaf
= Property
.isLeaf(property
);
288 const isArray
= Property
.isArray(property
);
289 const isObject
= Property
.isObject(property
);
290 const isLeafList
= Property
.isLeafList(property
);
291 const fieldKey
= [container
.id
].concat(path
);
292 const isRequired
= Property
.isRequired(property
);
293 const title
= changeCase
.titleCase(property
.name
);
294 const columnCount
= property
.properties
.length
|| 1;
295 const isColumnar
= isArray
&& (Math
.round(props
.width
/ columnCount
) > 155);
296 const classNames
= {'-is-required': isRequired
, '-is-columnar': isColumnar
};
298 if (!property
.properties
&& isObject
) {
299 const uiState
= DescriptorModelMetaFactory
.getModelMetaForType(property
.name
) || {};
300 property
.properties
= uiState
.properties
;
303 const hasProperties
= _
.isArray(property
.properties
) && property
.properties
.length
;
304 const isMissingDescriptorMeta
= !hasProperties
&& !Property
.isLeaf(property
);
306 // ensure value is not undefined for non-leaf property types
308 if (typeof value
!== 'object') {
309 value
= isArray
? [] : {};
312 const valueAsArray
= _
.isArray(value
) ? value
: isLeafList
&& typeof value
=== 'undefined' ? [] : [value
];
314 const isMetaField
= property
.name
=== 'meta';
315 const isCVNFD
= property
.name
=== 'constituent-vnfd';
316 const isSimpleListView
= Property
.isSimpleList(property
);
318 valueAsArray
.forEach((value
, index
) => {
321 const key
= fieldKey
.slice();
322 const valuePath
= path
.slice();
325 valuePath
.push(index
);
330 if (typeof value
=== 'object') {
331 value
= JSON
.stringify(value
, undefined, 12);
332 } else if (typeof value
!== 'string') {
337 if (isMissingDescriptorMeta
) {
338 field
= <span key
={key
.concat('warning').join(':')} className
="warning">No Descriptor Meta
for {property
.name
}</span
>;
339 } else if (property
.type
=== 'choice') {
340 field
= buildChoice(container
, property
, valuePath
, value
, key
.join(':'), props
);
341 } else if (isSimpleListView
) {
342 field
= buildSimpleListItem(container
, property
, valuePath
, value
, key
, index
, props
);
343 } else if (isLeafList
) {
344 field
= buildLeafListItem(container
, property
, valuePath
, value
, key
, index
, vnfdIndex
);
345 } else if (hasProperties
) {
346 field
= buildElement(container
, property
, valuePath
, value
, vnfdIndex
)
348 field
= buildField(container
, property
, valuePath
, value
, key
.join(':'), vnfdIndex
);
351 function onClickLeaf(property
, path
, value
, event
) {
352 if (event
.isDefaultPrevented()) {
355 event
.preventDefault();
356 event
.stopPropagation();
357 this.getRoot().uiState
.focusedPropertyPath
= path
.join('.');
358 console
.log('property selected', path
.join('.'));
359 ComposerAppActions
.propertySelected([path
.join('.')]);
362 const clickHandler
= isLeaf
? onClickLeaf
: () => {};
363 const isContainerList
= isArray
&& !(isSimpleListView
|| isLeafList
);
364 if(valuePath
.indexOf("member-vnf-index-ref") == -1) {
366 <div key
={fieldKey
.concat(['property-content', index
]).join(':')}
367 className
={ClassNames('property-content', {'simple-list': isSimpleListView
})}
368 onClick
={clickHandler
.bind(container
, property
, valuePath
, value
)}>
369 {isContainerList
? buildRemoveListItem(container
, property
, valuePath
, fieldKey
, index
) : null}
376 classNames
['-is-leaf'] = isLeaf
;
377 classNames
['-is-array'] = isArray
;
378 classNames
['cols-' + columnCount
] = isColumnar
;
380 if (property
.type
=== 'choice') {
381 value
= utils
.resolvePath(container
.model
, ['uiState.choice'].concat(path
, 'selected').join('.'));
383 property
.properties
.map(function(p
) {
384 let pname
= p
.properties
[0].name
;
385 if(container
.model
.hasOwnProperty(pname
)) {
386 value
= container
.model
[pname
];
392 let displayValue
= typeof value
=== 'object' ? '' : value
;
393 const displayValueInfo
= isArray
? valueAsArray
.filter(d
=> typeof d
!== 'undefined').length
+ ' items' : '';
395 const onFocus
= isLeaf
? event
=> event
.target
.classList
.add('-is-focused') : false;
397 //Remove first entry, which is member-vnf-index because I'm tired of trying to figure out how it's being added and feel pressed for time. Come back here if there's ever time and fix correctly.
398 if (fieldKey
.indexOf('member-vnf-index-ref') == -1) {
400 <div key
={fieldKey
.join(':')} onFocus
={onFocus
}>
406 export default function EditDescriptorModelProperties(props
, type
) {
408 const container
= props
.container
;
410 if (!(DescriptorModelFactory
.isContainer(container
))) {
416 const containerType
= (_
.isEmpty(type
) ? false : type
)|| container
.uiState
['qualified-type'] || container
.uiState
.type
;
417 const basicProperties
= getDescriptorMetaBasicForType(containerType
).properties
;
420 function buildAdvancedGroup() {
421 const properties
= getDescriptorMetaAdvancedForType(containerType
).properties
;
422 if (properties
.length
=== 0) {
425 const hasBasicFields
= basicProperties
.length
> 0;
426 const closeGroup
= basicProperties
.length
> 0;
429 properties
.map((property
,i
) => {
430 const path
= [property
.name
];
431 const value
= container
.model
[property
.name
];
435 if(path
== 'config-parameter-request') {
436 let cds
= CatalogDataStore
;
437 let catalogs
= cds
.getTransientCatalogs();
438 let vnfdIndexRef
= container
.model
[path
]['member-vnf-index-ref'];
439 let vnfdIdRef
= container
.parent
.model
['constituent-vnfd'].filter(function(v
){return v
['member-vnf-index'] == vnfdIndexRef
})[0]['vnfd-id-ref'];
440 let vnfd
= catalogs
[1].descriptors
.filter((v
) => v
.id
== vnfdIdRef
)[0];
441 let primitiveName
= vnfd
['config-parameter']['config-parameter-request'].filter((p
) => p
.name
== value
['config-parameter-request-ref'] )[0].parameter
[0]['config-primitive-name-ref'];
443 <div className
="config-parameter-request" key
={path
+ '-' + i
}>
444 {`${vnfd['short-name']} (${vnfdIndexRef}) / ${primitiveName} / ${value['config-parameter-request-ref']}`}
447 } else if(path
== 'config-parameter-source') {
449 return <div className
="config-parameter-source"> {build(container
, property
, path
, value
, _
.assign({toggle
: true, width
: props
.width
}, props
), value
['member-vnf-index-ref'])} </div
>
456 function buildMoreLess(d
, i
) {
458 <span key
={'bread-crumb-part-' + i
}>
459 <a href
="#select-item" onClick
={onClickSelectItem
.bind(d
, null, null, d
)}>{d
.title
}</a
>
466 if (container
.parent
) {
467 path
.push(container
.parent
);
469 path
.push(container
);
472 <div className
="EditDescriptorModelProperties -is-tree-view config-parameter config-parameter-group">
473 {buildAdvancedGroup()}
479 // export buildElement;
480 // export buildChoice;