SDNR UI don't process list which has more than one key
[ccsdk/features.git] / sdnr / wt / odlux / apps / configurationApp / src / actions / deviceActions.ts
index 1db66c0..3758378 100644 (file)
@@ -8,8 +8,8 @@ import { DisplayModeType, DisplaySpecification } from '../handlers/viewDescripti
 import { restService } from "../services/restServices";
 import { YangParser } from "../yang/yangParser";
 import { Module } from "../models/yang";
-import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase } from "../models/uiModels";
-import { element } from 'prop-types';
+import { ViewSpecification, ViewElement, isViewElementReference, isViewElementList, isViewElementObjectOrList, isViewElementRpc, isViewElementChoise, ViewElementChoiseCase, ViewElementString } from "../models/uiModels";
+
 
 export class EnableValueSelector extends Action {
   constructor(public listSpecification: ViewSpecification, public listData: any[], public keyProperty: string, public onValueSelected : (value: any) => void ) {
@@ -36,7 +36,7 @@ export class UpdateDeviceDescription extends Action {
 }
 
 export class UpdatViewDescription extends Action {
-  constructor (public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay } ) {
+  constructor (public vPath: string, public viewData: any, public displaySpecification: DisplaySpecification = { displayMode: DisplayModeType.doNotDisplay }) {
     super();
   }
 }
@@ -47,30 +47,57 @@ export class UpdatOutputData extends Action {
   }
 }
 
-export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => {
+type HttpResult = {
+  status: number;
+  message?: string | undefined;
+  data: {
+      [key: string]: any;
+  } | null | undefined;
+};
+
+const checkResponseCode = (restResult: HttpResult) =>{
+  
+  //403 gets handled by the framework from now on
 
-  const availableCapabilities = await restService.getCapabilitiesByMoutId(nodeId);
+  return restResult.status !== 403 && ( restResult.status < 200 || restResult.status > 299);
+
+}
+
+export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => {
 
+  dispatch(new UpdateDeviceDescription("", {}, []));
+  dispatch(new SetCollectingSelectionData(true));
+  
+  const { availableCapabilities, unavailableCapabilities, importOnlyModules } = await restService.getCapabilitiesByMountId(nodeId);
+  
   if (!availableCapabilities || availableCapabilities.length <= 0) {
+    dispatch(new SetCollectingSelectionData(false));
+    dispatch(new UpdateDeviceDescription(nodeId, {}, []));
+    dispatch(new UpdatViewDescription("", [], {
+      displayMode: DisplayModeType.displayAsMessage,
+      renderMessage: `NetworkElement : "${nodeId}" has no capabilities.`
+    }));
     throw new Error(`NetworkElement : [${nodeId}] has no capabilities.`);
   }
 
-  const parser = new YangParser();
+  const parser = new YangParser(unavailableCapabilities || undefined, importOnlyModules || undefined, nodeId);
 
-  const capParser = /^\(.*\?revision=(\d{4}-\d{2}-\d{2})\)(\S+)$/i;
   for (let i = 0; i < availableCapabilities.length; ++i){
     const capRaw = availableCapabilities[i];
-    const capMatch = capRaw && capParser.exec(capRaw.capability);
     try {
-      capMatch && await parser.addCapability(capMatch[2], capMatch[1]);
+      await parser.addCapability(capRaw.capability, capRaw.version);
     } catch (err) {
-      console.error(err);
+      console.error(`Error in ${capRaw.capability} ${capRaw.version}`, err);
     }
   }
 
   parser.postProcess();
 
-  console.log(parser.modules, parser.views)
+  dispatch(new SetCollectingSelectionData(false));
+
+  if (process.env.NODE_ENV === "development" ) {
+      console.log(parser, parser.modules, parser.views);
+  }
 
   return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
 }
@@ -117,7 +144,7 @@ const getReferencedDataList = async (refPath: string, dataPath: string, modules:
         for (let j = 0; j < dataUrls.length; ++j) {
           const dataUrl = dataUrls[j];
           const restResult = (await restService.getConfigData(dataUrl));
-          if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
+          if (restResult.data == null || checkResponseCode(restResult)) {
             const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
             throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
           }
@@ -149,7 +176,7 @@ const getReferencedDataList = async (refPath: string, dataPath: string, modules:
       for (let j = 0; j < dataUrls.length; ++j) {
         const dataUrl = dataUrls[j];
         const restResult = (await restService.getConfigData(dataUrl));
-        if (restResult.data == null || restResult.status < 200 || restResult.status > 299) {
+        if (restResult.data == null || checkResponseCode(restResult)) {
           const message = restResult.data && restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
           throw new Error(`Server Error. Status: [${restResult.status}]\n${message || restResult.message || ''}`);
         }
@@ -179,13 +206,25 @@ const resolveViewDescription = (defaultNS: string | null, vPath: string, view: V
   // check if-feature | when | and resolve all references.
   view = { ...view };
   view.elements = Object.keys(view.elements).reduce<{ [name: string]: ViewElement }>((acc, cur) => {
-    const elm = view.elements[cur];
+    const resolveHistory : ViewElement[] = [];  
+    let elm = view.elements[cur];
     const key = defaultNS && cur.replace(new RegExp(`^${defaultNS}:`, "i"),"") || cur;
-    if (isViewElementReference(elm)) {
-      acc[key] = { ...(elm.ref(vPath) || elm), id: key };
-    } else {
-      acc[key] = { ...elm, id: key };
-    }
+    while (isViewElementReference(elm)) {
+      const result = (elm.ref(vPath));  
+      if (result) {
+        const [referencedElement, referencedPath] = result;
+        if (resolveHistory.some(hist => hist === referencedElement)) {
+            console.error(`Circle reference found at: ${vPath}`, resolveHistory);
+            break;
+        }
+        elm = referencedElement;
+        vPath = referencedPath;
+        resolveHistory.push(elm);
+      }
+    } 
+    
+    acc[key] = { ...elm, id: key };
+    
     return acc;
   }, {});
   return view;
@@ -302,18 +341,45 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
               window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`)));
             }));
           } else {
+            // Found a list at root level of a module w/o a refenrece key.
+            dataPath += `?content=config&fields=${encodeURIComponent(viewElement.id)}(${encodeURIComponent(viewElement.key || '')})`; 
+            const restResult = (await restService.getConfigData(dataPath));
+            if (restResult && restResult.status === 200 && restResult.data && restResult.data[viewElement.id] ){
+                // spoof the not existing view here
+                const refData = restResult.data[viewElement.id];
+                const refView : ViewSpecification  = {
+                    id: "-1",
+                    canEdit: false,
+                    config: false,
+                    language: "en-US",
+                    elements: {
+                        [viewElement.key!] : { 
+                           uiType: "string",
+                           config: false,
+                           id: viewElement.key,
+                           label: viewElement.key,
+                           isList: true,
+                        } as ViewElementString
+                    }
+                };
+                dispatch(new EnableValueSelector(refView, refData, viewElement.key!, (refKey) => {
+                 window.setTimeout(() => dispatch(new PushAction(`${vPath}[${refKey.replace(/\//ig, "%2F")}]`))); 
+                }));
+            } else {
+              throw new Error("Found a list at root level of a module and could not determine the keys.");
+            }
             dispatch(new SetCollectingSelectionData(false));
-            throw new Error("Found a list at root level of a module w/o a refenrece key.");
           }
           return;
         }
         extractList = true;
       } else {
-        // normal case 
-        dataPath += `/${property}${key ? `=${key.replace(/\//ig, "%2F")}` : ""}`;
-
+        // normal case
         // in case of the root element the required namespace will be added later,
         // while extracting the data
+
+        dataPath += `/${property}${key ? `=${key.replace(/\%2C/g, ",").replace(/\//ig, "%2F")}` : ""}`;
+
         dataMember = namespace === defaultNS
           ? viewElement.label
           : `${namespace}:${viewElement.label}`;
@@ -359,7 +425,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
           return dispatch(new UpdatViewDescription(vPath, [], ds));
         }
         throw new Error(`Did not get response from Server. Status: [${restResult.status}]`);
-      } else if (restResult.status < 200 || restResult.status > 299) {
+      } else if (checkResponseCode(restResult)) {
         const message = restResult.data.errors && restResult.data.errors.error && restResult.data.errors.error[0] && restResult.data.errors.error[0]["error-message"] || "";
         throw new Error(`Server Error. Status: [${restResult.status}]\n${message}`);
       } else {
@@ -377,7 +443,7 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
 
       // extract the list -> key: list
       data = extractList
-        ? data[viewElement!.label] || [] // if the list is empty, it does not exist
+        ? data[viewElement!.id] || data[viewElement!.label] || [] // if the list is empty, it does not exist
         : data;
 
     } else if (viewElement! && viewElement!.uiType === "rpc") {
@@ -390,18 +456,21 @@ export const updateViewActionAsyncCreator = (vPath: string) => async (dispatch:
         }
       });
     }
-
+    
     // create display specification
     const ds: DisplaySpecification = viewElement! && viewElement!.uiType === "rpc"
       ? {
+        dataPath,
         displayMode: DisplayModeType.displayAsRPC,
         inputViewSpecification: inputViewSpecification && resolveViewDescription(defaultNS, vPath, inputViewSpecification),
         outputViewSpecification: outputViewSpecification && resolveViewDescription(defaultNS, vPath, outputViewSpecification),
       }
       : {
+        dataPath,
         displayMode: extractList ? DisplayModeType.displayAsList : DisplayModeType.displayAsObject,
         viewSpecification: resolveViewDescription(defaultNS, vPath, viewSpecification),
         keyProperty: isViewElementList(viewElement!) && viewElement.key || undefined,
+        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,
       };
 
     // update display specification
@@ -450,8 +519,10 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
           dispatch(new SetCollectingSelectionData(false));
           throw new Error("No key for list [" + property + "]");
         } else if (vPath.endsWith("[]") && pathParts.length - 1 === ind) {
-          // handle new element
-          key = viewElement.key && String(data[viewElement.key]) || "";
+          // handle new element with any number of arguments
+          let keyList = viewElement.key?.split(" ");
+          let dataPathParam = keyList?.map(id => data[id]).join(",");
+          key = viewElement.key && String(dataPathParam) || "";
           isNew = key;
           if (!key) {
             dispatch(new SetCollectingSelectionData(false));
@@ -469,6 +540,35 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
       }
     }
 
+    // remove read-only elements
+    const removeReadOnlyElements = (viewSpecification: ViewSpecification, isList: boolean, data: any) => {
+      if (isList) {
+        return data.map((elm : any) => removeReadOnlyElements(viewSpecification, false, elm));
+      } else {
+        return Object.keys(data).reduce<{[key: string]: any}>((acc, cur)=>{
+          const [nsOrName, name] = cur.split(':',1);
+          const element = viewSpecification.elements[cur] || viewSpecification.elements[nsOrName] || viewSpecification.elements[name];
+          if (!element && process.env.NODE_ENV === "development" ) {
+            throw new Error("removeReadOnlyElements: Could not determine elment for data.");
+          }
+          if (element && element.config) {
+            if (element.uiType==="object") {
+              const view = views[+element.viewId];
+              if (!view) {
+                throw new Error("removeReadOnlyElements: Internal Error could not determine viewId: "+element.viewId);
+              }
+              acc[cur] = removeReadOnlyElements(view, element.isList != null && element.isList, data[cur]);
+            } else {
+              acc[cur] = data[cur];
+            }
+          }
+          return acc;
+        }, {});
+      }
+    };
+    data = removeReadOnlyElements(viewSpecification, embedList, data);
+
+
     // embed the list -> key: list
     data = embedList
       ? { [viewElement!.label]: data }
@@ -481,15 +581,15 @@ export const updateDataActionAsyncCreator = (vPath: string, data: any) => async
 
     // do not extract root member (0)
     if (viewSpecification && viewSpecification.id !== "0") {
-      const updateResult = await restService.setConfigData(dataPath, { [`${defaultNS}:${dataMember!}`]: data }); // addDataMember using defaultNS
-      if (updateResult.status < 200 || updateResult.status > 299) {
+      const updateResult = await restService.setConfigData(dataPath, { [`${currentNS}:${dataMember!}`]: data }); // addDataMember using currentNS
+      if (checkResponseCode(updateResult)) {
         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
       }
     }
 
     if (isNew) {
-      return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)) // navigate to new element
+      return dispatch(new ReplaceAction(`/configuration/${nodeId}/${vPath.replace(/\[\]$/i, `[${isNew}]`)}`)); // navigate to new element
     }
 
     // create display specification
@@ -554,7 +654,7 @@ export const removeElementActionAsyncCreator = (vPath: string) => async (dispatc
     }
 
     const updateResult = await restService.removeConfigElement(dataPath);
-    if (updateResult.status < 200 || updateResult.status > 299) {
+    if (checkResponseCode(updateResult)) {
       const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
       throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
     }
@@ -648,7 +748,7 @@ export const executeRpcActionAsyncCreator = (vPath: string, data: any) => async
     // do not post root member (0)
     if ((viewSpecification && viewSpecification.id !== "0") || (dataMember! && !data)) {
       const updateResult = await restService.executeRpc(dataPath, { [`${defaultNS}:input`]: data || {} });
-      if (updateResult.status < 200 || updateResult.status > 299) {
+      if (checkResponseCode(updateResult)) {
         const message = updateResult.data && updateResult.data.errors && updateResult.data.errors.error && updateResult.data.errors.error[0] && updateResult.data.errors.error[0]["error-message"] || "";
         throw new Error(`Server Error. Status: [${updateResult.status}]\n${message || updateResult.message || ''}`);
       }