Update ODLUX
[ccsdk/features.git] / sdnr / wt / odlux / apps / configurationApp / src / actions / deviceActions.ts
1 import { Action } from '../../../../framework/src/flux/action';
2 import { Dispatch } from '../../../../framework/src/flux/store';
3 import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
4 import { PushAction, ReplaceAction } from '../../../../framework/src/actions/navigationActions';
5 import { AddErrorInfoAction } from '../../../../framework/src/actions/errorActions';
6
7 import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescriptionHandler';
8
9 import { restService } from '../services/restServices';
10 import { YangParser } from '../yang/yangParser';
11 import { Module } from '../models/yang';
12 import {
13   ViewSpecification,
14   ViewElement,
15   isViewElementReference,
16   isViewElementList,
17   ViewElementString,
18 } from '../models/uiModels';
19
20 import {
21   checkResponseCode,
22   splitVPath,
23   filterViewElements,
24   flattenViewElements,
25   getReferencedDataList,
26   resolveViewDescription,
27 } from '../utilities/viewEngineHelper';
28
29 export class EnableValueSelector extends Action {
30   constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
31     super();
32   }
33 }
34
35 export class SetCollectingSelectionData extends Action {
36   constructor(public busy: boolean) {
37     super();
38   }
39 }
40
41 export class SetSelectedValue extends Action {
42   constructor(public value: any) {
43     super();
44   }
45 }
46
47 export class UpdateDeviceDescription extends Action {
48   constructor( public nodeId: string, public modules: { [name:string]: Module }, public views: ViewSpecification[]) {
49     super();
50   }
51 }
52
53 export class UpdateViewDescription extends Action {
54   constructor(public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay }) {
55     super();
56   }
57 }
58
59 export class UpdateOutputData extends Action {
60   constructor(public outputData: any) {
61     super();
62   }
63 }
64
65 export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState ) => {
66
67   dispatch(new UpdateDeviceDescription('', {}, []));
68   dispatch(new SetCollectingSelectionData(true));
69   
70   const { availableCapabilities, unavailableCapabilities, importOnlyModules } = await restService.getCapabilitiesByMountId(nodeId);
71   
72   if (!availableCapabilities || availableCapabilities.length <= 0) {
73     dispatch(new SetCollectingSelectionData(false));
74     dispatch(new UpdateDeviceDescription(nodeId, {}, []));
75     dispatch(new UpdateViewDescription('', [], {
76       displayMode: DisplayModeType.displayAsMessage,
77       renderMessage: `NetworkElement : "${nodeId}" has no capabilities.`,
78     }));
79     throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
80   }
81
82   const parser = new YangParser(
83     nodeId,
84     availableCapabilities.reduce((acc, cur) => {
85       acc[cur.capability] = cur.version;
86       return acc;
87     }, {} as { [key: string]: string }),
88     unavailableCapabilities || undefined,
89     importOnlyModules || undefined,
90   );
91
92   for (let i = 0; i < availableCapabilities.length; ++i) {
93     const capRaw = availableCapabilities[i];
94     try {
95       await parser.addCapability(capRaw.capability, capRaw.version);
96     } catch (err) {
97       console.error(`Error in ${capRaw.capability} ${capRaw.version}`, err);
98     }
99   }
100
101   parser.postProcess();
102
103   dispatch(new SetCollectingSelectionData(false));
104
105   if (process.env.NODE_ENV === 'development' ) {
106     console.log(parser, parser.modules, parser.views);
107   }
108
109   return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
110 };
111
112 export const postProcessDisplaySpecificationActionCreator = (vPath: string, viewData: any, displaySpecification: DisplaySpecification) => async (dispatch: Dispatch, _getState: () => IApplicationStoreState) => {
113   
114   if (displaySpecification.displayMode === DisplayModeType.displayAsObject) {
115     displaySpecification = {
116       ...displaySpecification,
117       viewSpecification: await filterViewElements(vPath, viewData, displaySpecification.viewSpecification),
118     };  
119   }
120
121   dispatch(new UpdateViewDescription(vPath, viewData, displaySpecification));
122 };
123
124 export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
125   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
126   const { configuration: { deviceDescription: { nodeId, modules, views } } } = getState();
127   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
128
129   let inputViewSpecification: ViewSpecification | undefined = undefined;
130   let outputViewSpecification: ViewSpecification | undefined = undefined;
131
132   let viewSpecification: ViewSpecification = views[0];
133   let viewElement: ViewElement;
134
135   let dataMember: string;
136   let extractList: boolean = false;
137
138   let currentNS: string | null = null;
139   let defaultNS: string | null = null;
140
141   dispatch(new SetCollectingSelectionData(true));
142   try {
143     for (let ind = 0; ind < pathParts.length; ++ind) {
144       const [property, key] = pathParts[ind];
145       const namespaceInd = property && property.indexOf(':') || -1;
146       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
147
148       if (ind === 0) { defaultNS = namespace; }
149
150       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
151       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
152
153       if (viewElement.isList && !key) {
154         if (pathParts.length - 1 > ind) {
155           dispatch(new SetCollectingSelectionData(false));
156           throw new Error('No key for list [' + property + ']');
157         } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
158
159           // empty key is used for new element
160           if (viewElement && 'viewId' in viewElement) viewSpecification = views[+viewElement.viewId];
161           const data = Object.keys(viewSpecification.elements).reduce<{ [name: string]: any }>((acc, cur) => {
162             const elm = viewSpecification.elements[cur];
163             if (elm.default) {
164               acc[elm.id] = elm.default || '';
165             }
166             return acc;
167           }, {});
168
169           // create display specification
170           const ds: DisplaySpecification = {
171             displayMode: DisplayModeType.displayAsObject,
172             viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
173             keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
174           };
175
176           // update display specification
177           return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds));
178         }
179         if (viewElement && isViewElementList(viewElement) && viewSpecification.parentView === '0') {
180           // check if there is a reference as key
181           const listSpecification = views[+viewElement.viewId];
182           const keyElement = viewElement.key && listSpecification.elements[viewElement.key];
183           if (keyElement && isViewElementReference(keyElement)) {
184             const refList = await getReferencedDataList(keyElement.referencePath, dataPath, modules, views);
185             if (!refList) {
186               throw new Error(`Could not find refList for [${keyElement.referencePath}].`);
187             }
188             if (!refList.key) {
189               throw new Error(`Key property not found for [${keyElement.referencePath}].`);
190             }
191             dispatch(new EnableValueSelector(refList.view, refList.data, refList.key, (refKey) => {
192               window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`)));
193             }));
194           } else {
195             // Found a list at root level of a module w/o a reference key.
196             dataPath += `?&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`; 
197             const restResult = (await restService.getConfigData(dataPath));
198             if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ) {
199               // spoof the not existing view here
200               const refData = restResult.data[viewElement.id];
201               if (!Array.isArray(refData) || !refData.length) {
202                 throw new Error('Found a list at root level of a module containing no keys.');
203               }
204               if (refData.length > 1) {
205                 const refView : ViewSpecification  = {
206                   id: '-1',
207                   canEdit: false,
208                   config: false,
209                   language: 'en-US',
210                   elements: {
211                     [viewElement.key!] : { 
212                       uiType: 'string',
213                       config: false,
214                       id: viewElement.key,
215                       label: viewElement.key,
216                       isList: true,
217                     } as ViewElementString,
218                   },
219                 };
220                 dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
221                   window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`))); 
222                 }));
223               } else {
224                 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refData[0]?.id.replace(/\//ig, '%2F')}]`))); 
225               }
226             } else {
227               throw new Error('Found a list at root level of a module and could not determine the keys.');
228             }
229             dispatch(new SetCollectingSelectionData(false));
230           }
231           return;
232         }
233         extractList = true;
234       } else {
235         // normal case & replaces unicode %2C if present
236         dataPath += `/${property}${key ? `=${key.replace(/\%2C/g, ',').replace(/\//ig, '%2F')}` : ''}`;
237
238         // in case of the root element the required namespace will be added later,
239         // while extracting the data
240         dataMember = namespace === defaultNS
241           ? viewElement.label
242           : `${namespace}:${viewElement.label}`;
243         extractList = false;
244       }
245
246       if (viewElement && 'viewId' in viewElement) {
247         viewSpecification = views[+viewElement.viewId];
248       } else if (viewElement.uiType === 'rpc') {
249         viewSpecification = views[+(viewElement.inputViewId || 0)];
250
251         // create new instance & flaten
252         inputViewSpecification = viewElement.inputViewId != null && {
253           ...views[+(viewElement.inputViewId || 0)],
254           elements: flattenViewElements(defaultNS, '', views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label),
255         } || undefined;
256         outputViewSpecification = viewElement.outputViewId != null && {
257           ...views[+(viewElement.outputViewId || 0)],
258           elements: flattenViewElements(defaultNS, '', views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label),
259         } || undefined;
260
261       }
262     }
263
264     let data: any = {};
265     // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc
266     if (viewSpecification && !(viewSpecification.id === '0' || viewElement!.uiType === 'rpc')) {
267       const restResult = (await restService.getConfigData(dataPath));
268       if (!restResult.data) {
269         // special case: if this is a list without any response
270         if (extractList && restResult.status === 404) {
271           if (!isViewElementList(viewElement!)) {
272             throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
273           }
274           // create display specification
275           const ds: DisplaySpecification = {
276             displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
277             viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
278             keyProperty: viewElement.key,
279           };
280
281           // update display specification
282           return dispatch(postProcessDisplaySpecificationActionCreator(vPath, [], ds));
283         }
284         throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
285       } else if (checkResponseCode(restResult)) {
286         const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
287         throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
288       } else {
289         // https://tools.ietf.org/html/rfc7951#section-4 the root element may contain a namespace or not !  
290         data = restResult.data[`${defaultNS}:${dataMember!}`];
291         if (data === undefined) {
292           data = restResult.data[dataMember!]; // extract dataMember w/o namespace
293         }
294       }
295
296       // extract the first element list[key]
297       data = data instanceof Array
298         ? data[0]
299         : data;
300
301       // extract the list -> key: list
302       data = extractList
303         ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
304         : data;
305
306     } else if (viewElement! && viewElement!.uiType === 'rpc') {
307       // set data to defaults
308       data = {};
309       if (inputViewSpecification) {
310         Object.keys(inputViewSpecification.elements).forEach(key => {
311           const elm = inputViewSpecification && inputViewSpecification.elements[key];
312           if (elm && elm.default != undefined) {
313             data[elm.id] = elm.default;
314           }
315         });
316       }
317     }
318     
319     // create display specification
320     const ds: DisplaySpecification = viewElement! && viewElement!.uiType === 'rpc'
321       ? {
322         dataPath,
323         displayMode: DisplayModeType.displayAsRPC,
324         inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
325         outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
326       }
327       : {
328         dataPath,
329         displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
330         viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
331         keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
332         
333         // eslint-disable-next-line max-len
334         apidocPath:  isViewElementList(viewElement!) &&  `/apidoc/explorer/index.html?urls.primaryName=$$$standard$$$#/mounted%20${nodeId}%20${viewElement!.module || 'MODULE_NOT_DEFINED'}/$$$action$$$_${dataPath.replace(/^\//, '').replace(/[\/=\-\:]/g, '_')}_${viewElement! != null ? `${viewElement.id.replace(/[\/=\-\:]/g, '_')}_` : '' }` || undefined,
335       };
336
337     // update display specification
338     return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds));
339     // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
340     // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
341   } catch (error) {
342     history.back();
343     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not process ${dataPath}` }));
344     dispatch(new SetCollectingSelectionData(false));
345   } finally {
346     return;
347   }
348 };
349
350 export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
351   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
352   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
353   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
354   let viewSpecification: ViewSpecification = views[0];
355   let viewElement: ViewElement;
356   let dataMember: string;
357   let embedList: boolean = false;
358   let isNew: string | false = false;
359
360   let currentNS: string | null = null;
361   let defaultNS: string | null = null;
362
363   dispatch(new SetCollectingSelectionData(true));
364   try {
365     for (let ind = 0; ind < pathParts.length; ++ind) {
366       let [property, key] = pathParts[ind];
367       const namespaceInd = property && property.indexOf(':') || -1;
368       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
369
370       if (ind === 0) { defaultNS = namespace; }
371       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
372       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
373
374       if (isViewElementList(viewElement) && !key) {
375         embedList = true;
376         if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
377           throw new Error('Found a list at root level of a module w/o a refenrece key.');
378         }
379         if (pathParts.length - 1 > ind) {
380           dispatch(new SetCollectingSelectionData(false));
381           throw new Error('No key for list [' + property + ']');
382         } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
383           // handle new element with any number of arguments
384           let keyList = viewElement.key?.split(' ');
385           let dataPathParam = keyList?.map(id => data[id]).join(',');
386           key = viewElement.key && String(dataPathParam) || '';
387           isNew = key;
388           if (!key) {
389             dispatch(new SetCollectingSelectionData(false));
390             throw new Error('No value for key [' + viewElement.key + '] in list [' + property + ']');
391           }
392         }
393       }
394
395       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
396       dataMember = viewElement.label;
397       embedList = false;
398
399       if (viewElement && 'viewId' in viewElement) {
400         viewSpecification = views[+viewElement.viewId];
401       }
402     }
403
404     // remove read-only elements
405     const removeReadOnlyElements = (pViewSpecification: ViewSpecification, isList: boolean, pData: any) => {
406       if (isList) {
407         return pData.map((elm : any) => removeReadOnlyElements(pViewSpecification, false, elm));
408       } else {
409         return Object.keys(pData).reduce<{ [key: string]: any }>((acc, cur)=>{
410           const [nsOrName, name] = cur.split(':', 1);
411           const element = pViewSpecification.elements[cur] || pViewSpecification.elements[nsOrName] || pViewSpecification.elements[name];
412           if (!element && process.env.NODE_ENV === 'development' ) {
413             throw new Error('removeReadOnlyElements: Could not determine elment for data.');
414           }
415           if (element && element.config) {
416             if (element.uiType === 'object') {
417               const view = views[+element.viewId];
418               if (!view) {
419                 throw new Error('removeReadOnlyElements: Internal Error could not determine viewId: ' + element.viewId);
420               }
421               acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, pData[cur]);
422             } else {
423               acc[cur] = pData[cur];
424             }
425           }
426           return acc;
427         }, {});
428       }
429     };
430     data = removeReadOnlyElements(viewSpecification, embedList, data);
431
432
433     // embed the list -> key: list
434     data = embedList
435       ? { [viewElement!.label]: data }
436       : data;
437
438     // embed the first element list[key]
439     data = isNew
440       ? [data]
441       : data;
442
443     // do not extract root member (0)
444     if (viewSpecification && viewSpecification.id !== '0') {
445       const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS
446       if (checkResponseCode(updateResult)) {
447         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
448         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
449       }
450     }
451
452     if (isNew) {
453       return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)); // navigate to new element
454     }
455
456     // create display specification
457     const ds: DisplaySpecification = {
458       displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
459       viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
460       keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
461     };
462
463     // update display specification
464     return dispatch(new UpdateViewDescription(vPath, data, ds));
465   } catch (error) {
466     history.back();
467     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
468
469   } finally {
470     dispatch(new SetCollectingSelectionData(false));
471     return;
472   }
473 };
474
475 export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
476   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
477   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
478   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
479   let viewSpecification: ViewSpecification = views[0];
480   let viewElement: ViewElement;
481
482   let currentNS: string | null = null;
483   
484   dispatch(new SetCollectingSelectionData(true));
485   try {
486     for (let ind = 0; ind < pathParts.length; ++ind) {
487       let [property, key] = pathParts[ind];
488       const namespaceInd = property && property.indexOf(':') || -1;
489       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
490
491       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
492       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
493
494       if (isViewElementList(viewElement) && !key) {
495         if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
496           throw new Error('Found a list at root level of a module w/o a reference key.');
497         }
498         if (pathParts.length - 1 > ind) {
499           dispatch(new SetCollectingSelectionData(false));
500           throw new Error('No key for list [' + property + ']');
501         } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
502           // remove the whole table
503         }
504       }
505
506       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
507
508       if (viewElement && 'viewId' in viewElement) {
509         viewSpecification = views[+viewElement.viewId];
510       } else if (viewElement.uiType === 'rpc') {
511         viewSpecification = views[+(viewElement.inputViewId || 0)];
512       }
513     }
514
515     const updateResult = await restService.removeConfigElement(dataPath);
516     if (checkResponseCode(updateResult)) {
517       const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
518       throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
519     }
520   } catch (error) {
521     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not remove ${dataPath}` }));
522   } finally {
523     dispatch(new SetCollectingSelectionData(false));
524   }
525 };
526
527 export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
528   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
529   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
530   let dataPath = `/rests/operations/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
531   let viewSpecification: ViewSpecification = views[0];
532   let viewElement: ViewElement;
533   let dataMember: string;
534   let embedList: boolean = false;
535   let isNew: string | false = false;
536
537   let currentNS: string | null = null;
538   let defaultNS: string | null = null;
539
540   dispatch(new SetCollectingSelectionData(true));
541   try {
542     for (let ind = 0; ind < pathParts.length; ++ind) {
543       let [property, key] = pathParts[ind];
544       const namespaceInd = property && property.indexOf(':') || -1;
545       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
546
547       if (ind === 0) { defaultNS = namespace; }
548       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
549       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
550
551       if (isViewElementList(viewElement) && !key) {
552         embedList = true;
553       //   if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
554       //     throw new Error("Found a list at root level of a module w/o a reference key.");
555       //   }
556       //   if (pathParts.length - 1 > ind) {
557       //     dispatch(new SetCollectingSelectionData(false));
558       //     throw new Error("No key for list [" + property + "]");
559       //   } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
560       //     // handle new element
561       //     key = viewElement.key && String(data[viewElement.key]) || "";
562       //     isNew = key;
563       //     if (!key) {
564       //       dispatch(new SetCollectingSelectionData(false));
565       //       throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
566       //     }
567       //   }
568       }
569
570       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
571       dataMember = viewElement.label;
572       embedList = false;
573
574       if (viewElement && 'viewId' in viewElement) {
575         viewSpecification = views[+viewElement.viewId];
576       } else if (viewElement.uiType === 'rpc') {
577         viewSpecification = views[+(viewElement.inputViewId || 0)];
578       }
579     }
580
581     // re-inflate formerly flatten rpc data
582     data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => {
583       const innerPathParts = cur.split('.');
584       let pos = 0;
585       const updatePath = (obj: any, key: string) => {
586         obj[key] = (pos >= innerPathParts.length)
587           ? data[cur]
588           : updatePath(obj[key] || {}, innerPathParts[pos++]);
589         return obj;
590       };
591       updatePath(acc, innerPathParts[pos++]);
592       return acc;
593     }, {}) || null;
594
595     // embed the list -> key: list
596     data = embedList
597       ? { [viewElement!.label]: data }
598       : data;
599
600     // embed the first element list[key]
601     data = isNew
602       ? [data]
603       : data;
604
605     // do not post root member (0)
606     if ((viewSpecification && viewSpecification.id !== '0') || (dataMember! && !data)) {
607       const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
608       if (checkResponseCode(updateResult)) {
609         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
610         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
611       }
612       dispatch(new UpdateOutputData(updateResult.data));
613     } else {
614       throw new Error('There is NO RPC specified.');
615     }
616
617
618     // // update display specification
619     // return
620   } catch (error) {
621     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
622
623   } finally {
624     dispatch(new SetCollectingSelectionData(false));
625   }
626 };