52137135aee6ae6c6cb1b2f73acd0eb99e545203
[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 += `?content=config&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               const refView : ViewSpecification  = {
202                 id: '-1',
203                 canEdit: false,
204                 config: false,
205                 language: 'en-US',
206                 elements: {
207                   [viewElement.key!] : { 
208                     uiType: 'string',
209                     config: false,
210                     id: viewElement.key,
211                     label: viewElement.key,
212                     isList: true,
213                   } as ViewElementString,
214                 },
215               };
216               dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
217                 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, '%2F')}]`))); 
218               }));
219             } else {
220               throw new Error('Found a list at root level of a module and could not determine the keys.');
221             }
222             dispatch(new SetCollectingSelectionData(false));
223           }
224           return;
225         }
226         extractList = true;
227       } else {
228         // normal case & replaces unicode %2C if present
229         dataPath += `/${property}${key ? `=${key.replace(/\%2C/g, ',').replace(/\//ig, '%2F')}` : ''}`;
230
231         // in case of the root element the required namespace will be added later,
232         // while extracting the data
233         dataMember = namespace === defaultNS
234           ? viewElement.label
235           : `${namespace}:${viewElement.label}`;
236         extractList = false;
237       }
238
239       if (viewElement && 'viewId' in viewElement) {
240         viewSpecification = views[+viewElement.viewId];
241       } else if (viewElement.uiType === 'rpc') {
242         viewSpecification = views[+(viewElement.inputViewId || 0)];
243
244         // create new instance & flaten
245         inputViewSpecification = viewElement.inputViewId != null && {
246           ...views[+(viewElement.inputViewId || 0)],
247           elements: flattenViewElements(defaultNS, '', views[+(viewElement.inputViewId || 0)].elements, views, viewElement.label),
248         } || undefined;
249         outputViewSpecification = viewElement.outputViewId != null && {
250           ...views[+(viewElement.outputViewId || 0)],
251           elements: flattenViewElements(defaultNS, '', views[+(viewElement.outputViewId || 0)].elements, views, viewElement.label),
252         } || undefined;
253
254       }
255     }
256
257     let data: any = {};
258     // do not get any data from netconf if there is no view specified || this is the root element [0] || this is an rpc
259     if (viewSpecification && !(viewSpecification.id === '0' || viewElement!.uiType === 'rpc')) {
260       const restResult = (await restService.getConfigData(dataPath));
261       if (!restResult.data) {
262         // special case: if this is a list without any response
263         if (extractList && restResult.status === 404) {
264           if (!isViewElementList(viewElement!)) {
265             throw new Error(`vPath: [${vPath}]. ViewElement has no key.`);
266           }
267           // create display specification
268           const ds: DisplaySpecification = {
269             displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
270             viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
271             keyProperty: viewElement.key,
272           };
273
274           // update display specification
275           return dispatch(postProcessDisplaySpecificationActionCreator(vPath, [], ds));
276         }
277         throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
278       } else if (checkResponseCode(restResult)) {
279         const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]['error-message'] || '';
280         throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
281       } else {
282         // https://tools.ietf.org/html/rfc7951#section-4 the root element may contain a namespace or not !  
283         data = restResult.data[`${defaultNS}:${dataMember!}`];
284         if (data === undefined) {
285           data = restResult.data[dataMember!]; // extract dataMember w/o namespace
286         }
287       }
288
289       // extract the first element list[key]
290       data = data instanceof Array
291         ? data[0]
292         : data;
293
294       // extract the list -> key: list
295       data = extractList
296         ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
297         : data;
298
299     } else if (viewElement! && viewElement!.uiType === 'rpc') {
300       // set data to defaults
301       data = {};
302       if (inputViewSpecification) {
303         Object.keys(inputViewSpecification.elements).forEach(key => {
304           const elm = inputViewSpecification && inputViewSpecification.elements[key];
305           if (elm && elm.default != undefined) {
306             data[elm.id] = elm.default;
307           }
308         });
309       }
310     }
311     
312     // create display specification
313     const ds: DisplaySpecification = viewElement! && viewElement!.uiType === 'rpc'
314       ? {
315         dataPath,
316         displayMode: DisplayModeType.displayAsRPC,
317         inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
318         outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
319       }
320       : {
321         dataPath,
322         displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
323         viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
324         keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
325         
326         // eslint-disable-next-line max-len
327         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,
328       };
329
330     // update display specification
331     return dispatch(postProcessDisplaySpecificationActionCreator(vPath, data, ds));
332     // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]
333     // https://server.com/#/configuration/Sim12600/core-model:network-element/ltp[LTP-MWPS-TTP-01]/lp
334   } catch (error) {
335     history.back();
336     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not process ${dataPath}` }));
337     dispatch(new SetCollectingSelectionData(false));
338   } finally {
339     return;
340   }
341 };
342
343 export const updateDataActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
344   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
345   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
346   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
347   let viewSpecification: ViewSpecification = views[0];
348   let viewElement: ViewElement;
349   let dataMember: string;
350   let embedList: boolean = false;
351   let isNew: string | false = false;
352
353   let currentNS: string | null = null;
354   let defaultNS: string | null = null;
355
356   dispatch(new SetCollectingSelectionData(true));
357   try {
358     for (let ind = 0; ind < pathParts.length; ++ind) {
359       let [property, key] = pathParts[ind];
360       const namespaceInd = property && property.indexOf(':') || -1;
361       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
362
363       if (ind === 0) { defaultNS = namespace; }
364       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
365       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
366
367       if (isViewElementList(viewElement) && !key) {
368         embedList = true;
369         if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
370           throw new Error('Found a list at root level of a module w/o a refenrece key.');
371         }
372         if (pathParts.length - 1 > ind) {
373           dispatch(new SetCollectingSelectionData(false));
374           throw new Error('No key for list [' + property + ']');
375         } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
376           // handle new element with any number of arguments
377           let keyList = viewElement.key?.split(' ');
378           let dataPathParam = keyList?.map(id => data[id]).join(',');
379           key = viewElement.key && String(dataPathParam) || '';
380           isNew = key;
381           if (!key) {
382             dispatch(new SetCollectingSelectionData(false));
383             throw new Error('No value for key [' + viewElement.key + '] in list [' + property + ']');
384           }
385         }
386       }
387
388       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
389       dataMember = viewElement.label;
390       embedList = false;
391
392       if (viewElement && 'viewId' in viewElement) {
393         viewSpecification = views[+viewElement.viewId];
394       }
395     }
396
397     // remove read-only elements
398     const removeReadOnlyElements = (pViewSpecification: ViewSpecification, isList: boolean, pData: any) => {
399       if (isList) {
400         return pData.map((elm : any) => removeReadOnlyElements(pViewSpecification, false, elm));
401       } else {
402         return Object.keys(pData).reduce<{ [key: string]: any }>((acc, cur)=>{
403           const [nsOrName, name] = cur.split(':', 1);
404           const element = pViewSpecification.elements[cur] || pViewSpecification.elements[nsOrName] || pViewSpecification.elements[name];
405           if (!element && process.env.NODE_ENV === 'development' ) {
406             throw new Error('removeReadOnlyElements: Could not determine elment for data.');
407           }
408           if (element && element.config) {
409             if (element.uiType === 'object') {
410               const view = views[+element.viewId];
411               if (!view) {
412                 throw new Error('removeReadOnlyElements: Internal Error could not determine viewId: ' + element.viewId);
413               }
414               acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, pData[cur]);
415             } else {
416               acc[cur] = pData[cur];
417             }
418           }
419           return acc;
420         }, {});
421       }
422     };
423     data = removeReadOnlyElements(viewSpecification, embedList, data);
424
425
426     // embed the list -> key: list
427     data = embedList
428       ? { [viewElement!.label]: data }
429       : data;
430
431     // embed the first element list[key]
432     data = isNew
433       ? [data]
434       : data;
435
436     // do not extract root member (0)
437     if (viewSpecification && viewSpecification.id !== '0') {
438       const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS
439       if (checkResponseCode(updateResult)) {
440         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
441         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
442       }
443     }
444
445     if (isNew) {
446       return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)); // navigate to new element
447     }
448
449     // create display specification
450     const ds: DisplaySpecification = {
451       displayMode: embedList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
452       viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
453       keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
454     };
455
456     // update display specification
457     return dispatch(new UpdateViewDescription(vPath, data, ds));
458   } catch (error) {
459     history.back();
460     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
461
462   } finally {
463     dispatch(new SetCollectingSelectionData(false));
464     return;
465   }
466 };
467
468 export const removeElementActionAsyncCreator = (vPath: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
469   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
470   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
471   let dataPath = `/rests/data/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
472   let viewSpecification: ViewSpecification = views[0];
473   let viewElement: ViewElement;
474
475   let currentNS: string | null = null;
476   
477   dispatch(new SetCollectingSelectionData(true));
478   try {
479     for (let ind = 0; ind < pathParts.length; ++ind) {
480       let [property, key] = pathParts[ind];
481       const namespaceInd = property && property.indexOf(':') || -1;
482       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
483
484       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
485       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
486
487       if (isViewElementList(viewElement) && !key) {
488         if (viewElement && viewElement.isList && viewSpecification.parentView === '0') {
489           throw new Error('Found a list at root level of a module w/o a reference key.');
490         }
491         if (pathParts.length - 1 > ind) {
492           dispatch(new SetCollectingSelectionData(false));
493           throw new Error('No key for list [' + property + ']');
494         } else if (vPath.endsWith('[]') && pathParts.length - 1 === ind) {
495           // remove the whole table
496         }
497       }
498
499       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
500
501       if (viewElement && 'viewId' in viewElement) {
502         viewSpecification = views[+viewElement.viewId];
503       } else if (viewElement.uiType === 'rpc') {
504         viewSpecification = views[+(viewElement.inputViewId || 0)];
505       }
506     }
507
508     const updateResult = await restService.removeConfigElement(dataPath);
509     if (checkResponseCode(updateResult)) {
510       const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
511       throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
512     }
513   } catch (error) {
514     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not remove ${dataPath}` }));
515   } finally {
516     dispatch(new SetCollectingSelectionData(false));
517   }
518 };
519
520 export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async (dispatch: Dispatch, getState: () => IApplicationStoreState) => {
521   const pathParts = splitVPath(vPath, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
522   const { configuration: { deviceDescription: { nodeId, views } } } = getState();
523   let dataPath = `/rests/operations/network-topology:network-topology/topology=topology-netconf/node=${nodeId}/yang-ext:mount`;
524   let viewSpecification: ViewSpecification = views[0];
525   let viewElement: ViewElement;
526   let dataMember: string;
527   let embedList: boolean = false;
528   let isNew: string | false = false;
529
530   let currentNS: string | null = null;
531   let defaultNS: string | null = null;
532
533   dispatch(new SetCollectingSelectionData(true));
534   try {
535     for (let ind = 0; ind < pathParts.length; ++ind) {
536       let [property, key] = pathParts[ind];
537       const namespaceInd = property && property.indexOf(':') || -1;
538       const namespace: string | null = namespaceInd > -1 ? (currentNS = property.slice(0, namespaceInd)) : currentNS;
539
540       if (ind === 0) { defaultNS = namespace; }
541       viewElement = viewSpecification.elements[property] || viewSpecification.elements[`${namespace}:${property}`];
542       if (!viewElement) throw Error('Property [' + property + '] does not exist.');
543
544       if (isViewElementList(viewElement) && !key) {
545         embedList = true;
546       //   if (viewElement && viewElement.isList && viewSpecification.parentView === "0") {
547       //     throw new Error("Found a list at root level of a module w/o a reference key.");
548       //   }
549       //   if (pathParts.length - 1 > ind) {
550       //     dispatch(new SetCollectingSelectionData(false));
551       //     throw new Error("No key for list [" + property + "]");
552       //   } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
553       //     // handle new element
554       //     key = viewElement.key && String(data[viewElement.key]) || "";
555       //     isNew = key;
556       //     if (!key) {
557       //       dispatch(new SetCollectingSelectionData(false));
558       //       throw new Error("No value for key [" + viewElement.key + "] in list [" + property + "]");
559       //     }
560       //   }
561       }
562
563       dataPath += `/${property}${key ? `=${key.replace(/\//ig, '%2F')}` : ''}`;
564       dataMember = viewElement.label;
565       embedList = false;
566
567       if (viewElement && 'viewId' in viewElement) {
568         viewSpecification = views[+viewElement.viewId];
569       } else if (viewElement.uiType === 'rpc') {
570         viewSpecification = views[+(viewElement.inputViewId || 0)];
571       }
572     }
573
574     // re-inflate formerly flatten rpc data
575     data = data && Object.keys(data).reduce < { [name: string ]: any }>((acc, cur) => {
576       const innerPathParts = cur.split('.');
577       let pos = 0;
578       const updatePath = (obj: any, key: string) => {
579         obj[key] = (pos >= innerPathParts.length)
580           ? data[cur]
581           : updatePath(obj[key] || {}, innerPathParts[pos++]);
582         return obj;
583       };
584       updatePath(acc, innerPathParts[pos++]);
585       return acc;
586     }, {}) || null;
587
588     // embed the list -> key: list
589     data = embedList
590       ? { [viewElement!.label]: data }
591       : data;
592
593     // embed the first element list[key]
594     data = isNew
595       ? [data]
596       : data;
597
598     // do not post root member (0)
599     if ((viewSpecification && viewSpecification.id !== '0') || (dataMember! && !data)) {
600       const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
601       if (checkResponseCode(updateResult)) {
602         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]['error-message'] || '';
603         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
604       }
605       dispatch(new UpdateOutputData(updateResult.data));
606     } else {
607       throw new Error('There is NO RPC specified.');
608     }
609
610
611     // // update display specification
612     // return
613   } catch (error) {
614     dispatch(new AddErrorInfoAction({ title: 'Problem', message: error.message || `Could not change ${dataPath}` }));
615
616   } finally {
617     dispatch(new SetCollectingSelectionData(false));
618   }
619 };