From 32bfc62760a12fdbdd0c8810ce6fdc216c3914f2 Mon Sep 17 00:00:00 2001
From: Sebastian Listl <s.listl@adito.de>
Date: Wed, 18 Aug 2021 13:02:30 +0000
Subject: [PATCH] 1084919 recordcontainer cache for attributes

---
 .../ab_attribute_id/displayValueProcess.js    |  2 +-
 .../recordcontainers/jdito/onInsert.js        |  4 +-
 .../contentTitleProcess.js                    |  3 +-
 .../ab_attribute_id/displayValueProcess.js    |  7 +-
 entity/Attribute_entity/Attribute_entity.aod  | 37 ++++------
 .../Attribute_entity/contentTitleProcess.js   |  2 +-
 .../attribute_type/displayValueProcess.js     |  2 +-
 .../displaysimplename_param/valueProcess.js   |  3 -
 .../displaysimplename_param/valueProcess.js   |  3 -
 .../full_attribute_name/valueProcess.js       | 11 ---
 .../displaysimplename_param/valueProcess.js   |  3 -
 .../displaysimplename_param/valueProcess.js   |  3 -
 .../recordcontainers/jdito/cacheKeyProcess.js | 15 ++++
 .../recordcontainers/jdito/contentProcess.js  | 74 ++++++++-----------
 .../department/displayValueProcess.js         |  2 +-
 .../AttributeFilter_view.aod                  |  4 +
 .../AttributeLookup_view.aod                  |  2 +-
 .../AttributePreview_view.aod                 |  4 +
 process/Attribute_lib/process.js              | 21 +++---
 process/Employee_lib/process.js               |  2 +-
 process/Util_lib/process.js                   | 26 +++++++
 process/Utils_test/process.js                 | 46 ++++++++++++
 22 files changed, 162 insertions(+), 114 deletions(-)
 delete mode 100644 entity/Attribute_entity/entityfields/attributegroup/children/displaysimplename_param/valueProcess.js
 delete mode 100644 entity/Attribute_entity/entityfields/childattributes/children/displaysimplename_param/valueProcess.js
 delete mode 100644 entity/Attribute_entity/entityfields/full_attribute_name/valueProcess.js
 delete mode 100644 entity/Attribute_entity/entityfields/specificattribute/children/displaysimplename_param/valueProcess.js
 delete mode 100644 entity/Attribute_entity/entityfields/specificfilterattributes/children/displaysimplename_param/valueProcess.js
 create mode 100644 entity/Attribute_entity/recordcontainers/jdito/cacheKeyProcess.js

diff --git a/entity/AddAttributesToSelectionMulti_entity/entityfields/ab_attribute_id/displayValueProcess.js b/entity/AddAttributesToSelectionMulti_entity/entityfields/ab_attribute_id/displayValueProcess.js
index 5799483f23..4873965fd0 100644
--- a/entity/AddAttributesToSelectionMulti_entity/entityfields/ab_attribute_id/displayValueProcess.js
+++ b/entity/AddAttributesToSelectionMulti_entity/entityfields/ab_attribute_id/displayValueProcess.js
@@ -2,4 +2,4 @@ import("system.vars");
 import("system.result");
 import("Attribute_lib");
 
-result.string(AttributeUtil.getFullAttributeName(vars.get("$field.AB_ATTRIBUTE_ID"), false));
+result.string(AttributeUtil.getFullAttributeName(vars.get("$field.AB_ATTRIBUTE_ID")));
diff --git a/entity/AddAttributesToSelection_entity/recordcontainers/jdito/onInsert.js b/entity/AddAttributesToSelection_entity/recordcontainers/jdito/onInsert.js
index 08cbcfb7e2..bf85d3f658 100644
--- a/entity/AddAttributesToSelection_entity/recordcontainers/jdito/onInsert.js
+++ b/entity/AddAttributesToSelection_entity/recordcontainers/jdito/onInsert.js
@@ -148,14 +148,14 @@ attributes.forEach(function(attribute)
             case "maxAttr":
                 failedInsertArray.push([
                     translate.text("the max amount of this attribute has been reached"),
-                    AttributeUtil.getFullAttributeName(attributeId, false, true),
+                    AttributeUtil.getFullAttributeName(attributeId),
                     maxAttrsFailedCount
                     ]);
                 break;
             case "sameAttr":
                 failedInsertArray.push([
                     translate.text("this attribute with the same value exists already"),
-                    AttributeUtil.getFullAttributeName(attributeId, false, true),
+                    AttributeUtil.getFullAttributeName(attributeId),
                     sameAttrCount
                     ]);
                 break;
diff --git a/entity/AttributeRelation_entity/contentTitleProcess.js b/entity/AttributeRelation_entity/contentTitleProcess.js
index 07a93ad61a..4873965fd0 100644
--- a/entity/AttributeRelation_entity/contentTitleProcess.js
+++ b/entity/AttributeRelation_entity/contentTitleProcess.js
@@ -2,5 +2,4 @@ import("system.vars");
 import("system.result");
 import("Attribute_lib");
 
-result.string(AttributeUtil.getFullAttributeName(
-        vars.get("$field.AB_ATTRIBUTE_ID"), false, true ));
+result.string(AttributeUtil.getFullAttributeName(vars.get("$field.AB_ATTRIBUTE_ID")));
diff --git a/entity/AttributeRelation_entity/entityfields/ab_attribute_id/displayValueProcess.js b/entity/AttributeRelation_entity/entityfields/ab_attribute_id/displayValueProcess.js
index 94afb2968a..6889b840e8 100644
--- a/entity/AttributeRelation_entity/entityfields/ab_attribute_id/displayValueProcess.js
+++ b/entity/AttributeRelation_entity/entityfields/ab_attribute_id/displayValueProcess.js
@@ -1,7 +1,8 @@
+import("Util_lib");
 import("system.vars");
 import("system.result");
 import("Attribute_lib");
 
-result.string(AttributeUtil.getFullAttributeName(
-        vars.get("$field.AB_ATTRIBUTE_ID"),
-        vars.exists("$param.DisplaySimpleName_param") ? vars.get("$param.DisplaySimpleName_param") : false ));
+var displaySimpleName = Utils.toBoolean(vars.get("$param.DisplaySimpleName_param"));
+var attributeId = vars.get("$field.AB_ATTRIBUTE_ID");
+result.string(displaySimpleName ? AttributeUtil.getSimpleAttributeName(attributeId) : AttributeUtil.getFullAttributeName(attributeId));
diff --git a/entity/Attribute_entity/Attribute_entity.aod b/entity/Attribute_entity/Attribute_entity.aod
index 1f9f14da10..501f16b29d 100644
--- a/entity/Attribute_entity/Attribute_entity.aod
+++ b/entity/Attribute_entity/Attribute_entity.aod
@@ -140,11 +140,6 @@
           <name>FilteredAttributeIds_param</name>
           <expose v="true" />
         </entityParameter>
-        <entityParameter>
-          <name>DisplaySimpleName_param</name>
-          <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/specificattribute/children/displaysimplename_param/valueProcess.js</valueProcess>
-          <expose v="false" />
-        </entityParameter>
         <entityParameter>
           <name>ChildId_param</name>
           <expose v="false" />
@@ -163,18 +158,12 @@
       <name>FULL_ATTRIBUTE_NAME</name>
       <title>Title</title>
       <state>READONLY</state>
-      <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/full_attribute_name/valueProcess.js</valueProcess>
     </entityField>
     <entityParameter>
       <name>FilteredAttributeIds_param</name>
       <expose v="true" />
       <description>PARAMETER</description>
     </entityParameter>
-    <entityParameter>
-      <name>DisplaySimpleName_param</name>
-      <expose v="true" />
-      <description>PARAMETER</description>
-    </entityParameter>
     <entityField>
       <name>USAGELIST</name>
       <title>Usage</title>
@@ -204,6 +193,10 @@
           <name>ObjectType_param</name>
           <expose v="false" />
         </entityParameter>
+        <entityParameter>
+          <name>ParentType_param</name>
+          <expose v="false" />
+        </entityParameter>
       </children>
     </entityProvider>
     <entityConsumer>
@@ -223,10 +216,6 @@
           <name>ChildType_param</name>
           <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/attributegroup/children/childtype_param/valueProcess.js</valueProcess>
         </entityParameter>
-        <entityParameter>
-          <name>DisplaySimpleName_param</name>
-          <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/attributegroup/children/displaysimplename_param/valueProcess.js</valueProcess>
-        </entityParameter>
       </children>
     </entityConsumer>
     <entityField>
@@ -408,10 +397,6 @@
         <fieldName>AttributeChildren</fieldName>
       </dependency>
       <children>
-        <entityParameter>
-          <name>DisplaySimpleName_param</name>
-          <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/childattributes/children/displaysimplename_param/valueProcess.js</valueProcess>
-        </entityParameter>
         <entityParameter>
           <name>ParentId_param</name>
           <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/childattributes/children/parentid_param/valueProcess.js</valueProcess>
@@ -437,10 +422,6 @@
     <entityProvider>
       <name>SpecificFilterAttributes</name>
       <children>
-        <entityParameter>
-          <name>DisplaySimpleName_param</name>
-          <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/specificfilterattributes/children/displaysimplename_param/valueProcess.js</valueProcess>
-        </entityParameter>
         <entityParameter>
           <name>ChildType_param</name>
           <expose v="false" />
@@ -454,6 +435,11 @@
       <name>VALIDATIONPARAMDEFINITION</name>
       <valueProcess>%aditoprj%/entity/Attribute_entity/entityfields/validationparamdefinition/valueProcess.js</valueProcess>
     </entityField>
+    <entityField>
+      <name>TRANSLATED_NAME</name>
+      <title>Title</title>
+      <state>READONLY</state>
+    </entityField>
   </entityFields>
   <recordContainers>
     <jDitoRecordContainer>
@@ -466,6 +452,8 @@
       <onInsert>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onInsert.js</onInsert>
       <onUpdate>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onUpdate.js</onUpdate>
       <onDelete>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/onDelete.js</onDelete>
+      <cacheType>SESSION</cacheType>
+      <cacheKeyProcess>%aditoprj%/entity/Attribute_entity/recordcontainers/jdito/cacheKeyProcess.js</cacheKeyProcess>
       <recordFieldMappings>
         <jDitoRecordFieldMapping>
           <name>UID.value</name>
@@ -475,6 +463,9 @@
           <isFilterable v="true" />
           <isLookupFilter v="true" />
         </jDitoRecordFieldMapping>
+        <jDitoRecordFieldMapping>
+          <name>TRANSLATED_NAME.value</name>
+        </jDitoRecordFieldMapping>
         <jDitoRecordFieldMapping>
           <name>FULL_ATTRIBUTE_NAME.value</name>
         </jDitoRecordFieldMapping>
diff --git a/entity/Attribute_entity/contentTitleProcess.js b/entity/Attribute_entity/contentTitleProcess.js
index b2f10f7ed6..b68dec76f6 100644
--- a/entity/Attribute_entity/contentTitleProcess.js
+++ b/entity/Attribute_entity/contentTitleProcess.js
@@ -1,4 +1,4 @@
 import("system.vars");
 import("system.result");
 
-result.string(vars.get("$field.FULL_ATTRIBUTE_NAME"));
\ No newline at end of file
+result.string(vars.get("$field.TRANSLATED_NAME"));
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/attribute_type/displayValueProcess.js b/entity/Attribute_entity/entityfields/attribute_type/displayValueProcess.js
index 841ccf95e8..ba7ad39f70 100644
--- a/entity/Attribute_entity/entityfields/attribute_type/displayValueProcess.js
+++ b/entity/Attribute_entity/entityfields/attribute_type/displayValueProcess.js
@@ -6,7 +6,7 @@ import("Attribute_lib");
 import("Keyword_lib");
 import("KeywordRegistry_basic");
 
-if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW && vars.get("$field.ATTRIBUTE_PARENT_ID"))
+if (vars.get("$sys.recordstate") == neon.OPERATINGSTATE_NEW)
 {
     var type = vars.get("$field.ATTRIBUTE_TYPE");
     result.string(KeywordUtils.getViewValue($KeywordRegistry.attributeType(), type));
diff --git a/entity/Attribute_entity/entityfields/attributegroup/children/displaysimplename_param/valueProcess.js b/entity/Attribute_entity/entityfields/attributegroup/children/displaysimplename_param/valueProcess.js
deleted file mode 100644
index 40effa0178..0000000000
--- a/entity/Attribute_entity/entityfields/attributegroup/children/displaysimplename_param/valueProcess.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import("system.result");
-
-result.string(true);
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/childattributes/children/displaysimplename_param/valueProcess.js b/entity/Attribute_entity/entityfields/childattributes/children/displaysimplename_param/valueProcess.js
deleted file mode 100644
index 40effa0178..0000000000
--- a/entity/Attribute_entity/entityfields/childattributes/children/displaysimplename_param/valueProcess.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import("system.result");
-
-result.string(true);
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/full_attribute_name/valueProcess.js b/entity/Attribute_entity/entityfields/full_attribute_name/valueProcess.js
deleted file mode 100644
index 8778b5d409..0000000000
--- a/entity/Attribute_entity/entityfields/full_attribute_name/valueProcess.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import("system.vars");
-import("system.result");
-import("Attribute_lib");
-
-var name = vars.get("$this.value");
-if (!(vars.exists("$param.DisplaySimpleName_param") && vars.get("$param.DisplaySimpleName_param")))
-{
-    var parentName = AttributeUtil.getFullAttributeName(vars.get("$field.ATTRIBUTE_PARENT_ID"));
-    name = (parentName ? parentName + " / " : "") + name;
-}
-result.string(name);
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/specificattribute/children/displaysimplename_param/valueProcess.js b/entity/Attribute_entity/entityfields/specificattribute/children/displaysimplename_param/valueProcess.js
deleted file mode 100644
index 40effa0178..0000000000
--- a/entity/Attribute_entity/entityfields/specificattribute/children/displaysimplename_param/valueProcess.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import("system.result");
-
-result.string(true);
\ No newline at end of file
diff --git a/entity/Attribute_entity/entityfields/specificfilterattributes/children/displaysimplename_param/valueProcess.js b/entity/Attribute_entity/entityfields/specificfilterattributes/children/displaysimplename_param/valueProcess.js
deleted file mode 100644
index e0f3ec47e2..0000000000
--- a/entity/Attribute_entity/entityfields/specificfilterattributes/children/displaysimplename_param/valueProcess.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import("system.result");
-
-result.string(false);
\ No newline at end of file
diff --git a/entity/Attribute_entity/recordcontainers/jdito/cacheKeyProcess.js b/entity/Attribute_entity/recordcontainers/jdito/cacheKeyProcess.js
new file mode 100644
index 0000000000..7b77916c25
--- /dev/null
+++ b/entity/Attribute_entity/recordcontainers/jdito/cacheKeyProcess.js
@@ -0,0 +1,15 @@
+import("system.result");
+import("CachedRecordContainer_lib");
+
+var key = CachedRecordContainerUtils.getCommonKey(
+    "$param.AttributeCount_param", 
+    "$param.ChildId_param", 
+    "$param.ChildType_param", 
+    "$param.FilteredAttributeIds_param",
+    "$param.GetOnlyFirstLevelChildren_param",
+    "$param.IncludeParentRecord_param",
+    "$param.ObjectType_param",
+    "$param.ParentId_param",
+    "$param.ParentType_param"
+);
+result.string(key);
\ No newline at end of file
diff --git a/entity/Attribute_entity/recordcontainers/jdito/contentProcess.js b/entity/Attribute_entity/recordcontainers/jdito/contentProcess.js
index ba69144b21..741ab92a4d 100644
--- a/entity/Attribute_entity/recordcontainers/jdito/contentProcess.js
+++ b/entity/Attribute_entity/recordcontainers/jdito/contentProcess.js
@@ -16,23 +16,19 @@ var childType = vars.get("$param.ChildType_param");
 var objectType = vars.get("$param.ObjectType_param");
 var filteredIds = Utils.parseJSON(vars.get("$param.FilteredAttributeIds_param"));
 var attributeCountObj = Utils.parseJSON(vars.get("$param.AttributeCount_param"));
-var displaySimpleName = Utils.toBoolean(vars.get("$param.DisplaySimpleName_param"));
-
 var themeObjectRowId = vars.get("$param.ThemeObjectRowId_param");
 
 var parentId = vars.get("$param.ParentId_param");
 var includeParentRecord = Utils.toBoolean(vars.get("$param.IncludeParentRecord_param"));
 var onlyFirstLevelChildren = Utils.toBoolean(vars.get("$param.GetOnlyFirstLevelChildren_param"));
 
-var fetchUsages = false;
+var recordFieldHints = new Set(Array.from(vars.get("$local.fieldhints").recordFields));
 var translateName = false;
 
 var condition = newWhere();
-
 if (vars.exists("$local.idvalues") && vars.get("$local.idvalues"))
 {
     condition.andIfSet("AB_ATTRIBUTE.AB_ATTRIBUTEID", vars.get("$local.idvalues"), SqlBuilder.IN()); 
-    fetchUsages = true;
 }
 else if (childId) //if a childId is given, it is the lookup for selecting the superordinate attribute
 {
@@ -44,6 +40,7 @@ else if (childId) //if a childId is given, it is the lookup for selecting the su
 else if (objectType)  //if there's an objectType, it comes from the AttributeRelation entity (lookup for the attribute selection)
 {
     translateName = true;
+    recordFieldHints["delete"]("ATTRIBUTE_PARENT_ID.displayValue");
 
     var attributeIds = AttributeUtil.getPossibleAttributes(objectType, true, filteredIds, attributeCountObj);
 
@@ -65,10 +62,6 @@ else if (parentId)
             condition.or("AB_ATTRIBUTE.AB_ATTRIBUTEID", parentId);
     }
 }
-else
-{
-    fetchUsages = true;
-}
 
 var filterCondition = new FilterSqlTranslator(vars.get("$local.filter"), "AB_ATTRIBUTE")    
     .addSpecialFieldConditionFn("USAGE_FILTER", function (pValue, pOperator)
@@ -90,10 +83,6 @@ var filterCondition = new FilterSqlTranslator(vars.get("$local.filter"), "AB_ATT
     .getSqlCondition();
 condition.andIfSet(filterCondition);
 
-var usageLoader = new AttributeUsageLoader();
-if (fetchUsages)
-    usageLoader.fetchUsages(condition);
-
 var attributes = newSelect([
         "AB_ATTRIBUTEID", 
         "ATTRIBUTE_PARENT_ID", 
@@ -111,9 +100,8 @@ var attributes = newSelect([
     .orderBy(["ATTRIBUTE_PARENT_ID", "SORTING", "ATTRIBUTE_NAME"])
     .table();
 
-var nameResolver = new AttributeNameResolver();
-if (attributes.length !== 0)
-    nameResolver.fetchNames(translateName);
+var nameResolver = new AttributeNameResolver(translateName);
+var usageLoader = new AttributeUsageLoader();
 
 var attributesById = new Map();
 attributes.forEach(function ([attributeId, parentId, simpleName, isActive, 
@@ -122,9 +110,10 @@ attributes.forEach(function ([attributeId, parentId, simpleName, isActive,
     attributesById.set(attributeId, [
         attributeId,
         simpleName,
-        displaySimpleName ? nameResolver.getSimpleName(attributeId) : nameResolver.getFullName(attributeId),
+        translate.text(simpleName),
+        recordFieldHints.has("FULL_ATTRIBUTE_NAME.value") ? nameResolver.getFullName(attributeId) : simpleName,
         parentId,
-        nameResolver.getFullName(parentId),
+        recordFieldHints.has("ATTRIBUTE_PARENT_ID.displayValue") ? nameResolver.getFullName(parentId) : "",
         type,
         typeName,
         isActive,
@@ -132,7 +121,7 @@ attributes.forEach(function ([attributeId, parentId, simpleName, isActive,
         dropDownDefinition,
         dropDownFilter,
         validationParameters,
-        type != AttributeTypes.COMBOVALUE() ? usageLoader.getUsageSummary(attributeId) : "",
+        recordFieldHints.has("USAGELIST.value") ? usageLoader.getUsageSummary(attributeId) : "",
         "usageFilter",
         false
     ]);
@@ -157,37 +146,36 @@ do {
 result.object(resultTable);
 
 
-function AttributeNameResolver ()
+function AttributeNameResolver (pTranslateName)
 {
-    this.cache = new Map();
-    this.nameData = new Map();
-    this.fetchNames = function (pTranslateNames)
+    this._translate = pTranslateName;
+    this.getNameData = Utils.memoize(function ()
     {
+        var nameData = new Map();
         var allNames = newSelect(["AB_ATTRIBUTEID", "ATTRIBUTE_PARENT_ID", "ATTRIBUTE_NAME"])
             .from("AB_ATTRIBUTE")
             .table();
         allNames.forEach(function ([attributeId, parentId, attributeName])
         {
-            this.nameData.set(attributeId, {
+            nameData.set(attributeId, {
                 parentId: parentId,
-                name: pTranslateNames ? translate.text(attributeName) : attributeName
+                name: this._translate ? translate.text(attributeName) : attributeName
             });
         }, this);
-    }
+        return nameData;
+    });
     this.getSimpleName = function (pAttributeId)
     {
-        if (this.nameData.has(pAttributeId))
-            return this.nameData.get(pAttributeId).name;
+        if (this.getNameData().has(pAttributeId))
+            return this.getNameData().get(pAttributeId).name;
         return "";
     }
-    this.getFullName = function (pAttributeId)
+    this.getFullName = Utils.memoize(function (pAttributeId)
     {
-        if (!pAttributeId || !this.nameData.has(pAttributeId)) //if the id is not in this.nameData, it does not exist
+        if (!pAttributeId || !this.getNameData().has(pAttributeId)) //if the id is not in this.nameData, it does not exist
             return "";
-        if (this.cache.has(pAttributeId))
-            return this.cache.get(pAttributeId);
         
-        var attributeData = this.nameData.get(pAttributeId);
+        var attributeData = this.getNameData().get(pAttributeId);
         var fullName = attributeData.name;
         if (attributeData.parentId)
         {
@@ -195,16 +183,15 @@ function AttributeNameResolver ()
             if (parentName)
                 fullName = parentName + " / " + fullName;
         }
-        this.cache.set(pAttributeId, fullName);
         return fullName;
-    }
+    });
 }
 
 function AttributeUsageLoader ()
 {
-    this.usageData = new Map();
-    this.fetchUsages = function (pCondition)
+    this.getAllUsages = Utils.memoize(function (pCondition)
     {
+        var usageData = new Map()
         var usages = newSelect(["AB_ATTRIBUTE_ID", "OBJECT_TYPE"])
             .from("AB_ATTRIBUTEUSAGE")
             .join("AB_ATTRIBUTE", "AB_ATTRIBUTEUSAGE.AB_ATTRIBUTE_ID = AB_ATTRIBUTE.AB_ATTRIBUTEID")
@@ -212,15 +199,16 @@ function AttributeUsageLoader ()
             .table();
         usages.forEach(function ([attributeId, objectType])
         {
-            if (this.usageData.has(attributeId))
-                this.usageData.get(attributeId).push(objectType);
+            if (usageData.has(attributeId))
+                usageData.get(attributeId).push(objectType);
             else
-                this.usageData.set(attributeId, [objectType]);
-        }, this);
-    }
+                usageData.set(attributeId, [objectType]);
+        });
+        return usageData;
+    });
     this.getUsages = function (pAttributeId)
     {
-        return this.usageData.get(pAttributeId) || [];
+        return this.getAllUsages().get(pAttributeId) || [];
     }
     this.getUsageSummary = function (pAttributeId)
     {
diff --git a/entity/Employee_entity/entityfields/department/displayValueProcess.js b/entity/Employee_entity/entityfields/department/displayValueProcess.js
index 9b37b41042..cd5a82a65f 100644
--- a/entity/Employee_entity/entityfields/department/displayValueProcess.js
+++ b/entity/Employee_entity/entityfields/department/displayValueProcess.js
@@ -2,4 +2,4 @@ import("system.result");
 import("system.vars");
 import("Attribute_lib");
 
-result.string(AttributeUtil.getFullAttributeName(vars.get("$field.DEPARTMENT"), undefined, undefined, 1));
\ No newline at end of file
+result.string(AttributeUtil.getFullAttributeName(vars.get("$field.DEPARTMENT"), undefined, 1));
\ No newline at end of file
diff --git a/neonView/AttributeFilter_view/AttributeFilter_view.aod b/neonView/AttributeFilter_view/AttributeFilter_view.aod
index 41d0b35cc6..957a1cef63 100644
--- a/neonView/AttributeFilter_view/AttributeFilter_view.aod
+++ b/neonView/AttributeFilter_view/AttributeFilter_view.aod
@@ -27,6 +27,10 @@
           <name>a776a053-6712-44f9-943f-da26d1ce11ee</name>
           <entityField>ATTRIBUTE_NAME</entityField>
         </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>69217a8e-be3c-4f6c-bc58-2c69bed00b91</name>
+          <entityField>TRANSLATED_NAME</entityField>
+        </neonTreeTableColumn>
         <neonTreeTableColumn>
           <name>b975154d-8bac-4182-9030-e3b3d744c642</name>
           <entityField>ATTRIBUTE_TYPE</entityField>
diff --git a/neonView/AttributeLookup_view/AttributeLookup_view.aod b/neonView/AttributeLookup_view/AttributeLookup_view.aod
index b09289feb8..86c6ee079e 100644
--- a/neonView/AttributeLookup_view/AttributeLookup_view.aod
+++ b/neonView/AttributeLookup_view/AttributeLookup_view.aod
@@ -16,7 +16,7 @@
       <columns>
         <neonTreeTableColumn>
           <name>720c8546-f8ce-4f4f-8bf6-35bbf15a3936</name>
-          <entityField>FULL_ATTRIBUTE_NAME</entityField>
+          <entityField>TRANSLATED_NAME</entityField>
         </neonTreeTableColumn>
       </columns>
     </treeTableViewTemplate>
diff --git a/neonView/AttributePreview_view/AttributePreview_view.aod b/neonView/AttributePreview_view/AttributePreview_view.aod
index f67e3446e9..6f8ebe0220 100644
--- a/neonView/AttributePreview_view/AttributePreview_view.aod
+++ b/neonView/AttributePreview_view/AttributePreview_view.aod
@@ -34,6 +34,10 @@
           <name>70953962-9472-484b-ac95-567d2249f42e</name>
           <entityField>ATTRIBUTE_ACTIVE</entityField>
         </entityFieldLink>
+        <entityFieldLink>
+          <name>e4bd0980-b402-4e62-8b87-70061261c1a7</name>
+          <entityField>TRANSLATED_NAME</entityField>
+        </entityFieldLink>
       </fields>
     </genericViewTemplate>
     <neonViewReference>
diff --git a/process/Attribute_lib/process.js b/process/Attribute_lib/process.js
index b24194eb65..ebdf501b1f 100644
--- a/process/Attribute_lib/process.js
+++ b/process/Attribute_lib/process.js
@@ -260,9 +260,6 @@ AttributeUtil.getPossibleListValues = function (pAttributeId, pAttributeType, pI
  * 
  * @param {String} pAttributeId                         <p>
  *                                                      The id of the attribute.<br>
- * @param {Boolean} pSimpleName=false (optional)        <p>
- *                                                      Use only the name of the attribute <br>
- *                                                      and not the names of the parents.<br>
  * @param {Boolean} pTranslate=true (optional)          <p>
  *                                                      If the name should be translated.<br>
  * @param {Number} pStartLayer=0 (optional)             <p>
@@ -276,17 +273,13 @@ AttributeUtil.getPossibleListValues = function (pAttributeId, pAttributeType, pI
  * @return {String}                                     <p>
  *                                                      The name of the attribute.<br>
  */
-AttributeUtil.getFullAttributeName = function (pAttributeId, pSimpleName, pTranslate, pStartLayer) 
+AttributeUtil.getFullAttributeName = function (pAttributeId, pTranslate, pStartLayer) 
 {
-    if (pSimpleName === undefined)
-        pSimpleName = false;
     if (pTranslate === undefined)
         pTranslate = true;
     
     if (!pAttributeId)
         return "";
-    if (pSimpleName)
-        return AttributeUtil.getSimpleAttributeName(pAttributeId, pTranslate);
     var attributeNames = [];
     var attribute;
     do {
@@ -328,13 +321,17 @@ AttributeUtil.getFullAttributeName = function (pAttributeId, pSimpleName, pTrans
  * 
  * @param {String} pAttributeId                 <p>
  *                                              The id of the attribute.<br>
- * @param {boolean} pTranslate                  <p>
+ * @param {boolean} [pTranslate=true]           <p>
  *                                              If the name should be translated.<br>
  * @return {String}                             <p>
  *                                              The name of the attribute.<br>
  */
 AttributeUtil.getSimpleAttributeName = function (pAttributeId, pTranslate) 
 {
+    if (pTranslate == undefined)
+    {
+        pTranslate = true;
+    }
     var attributeName = newSelect("ATTRIBUTE_NAME")
                             .from("AB_ATTRIBUTE")
                             .whereIfSet("AB_ATTRIBUTE.AB_ATTRIBUTEID", pAttributeId)
@@ -355,7 +352,7 @@ AttributeUtil.getSimpleAttributeName = function (pAttributeId, pTranslate)
 AttributeUtil.getAllChildren = function (pAttributeIds)
 {
     var childIds = [];
-    if (typeof(pAttributeIds) == "string")
+    if (Utils.isString(pAttributeIds))
         pAttributeIds = [pAttributeIds];
         
     while (pAttributeIds.length > 0)
@@ -382,7 +379,7 @@ AttributeUtil.getAllChildren = function (pAttributeIds)
 AttributeUtil.getAllParents = function (pAttributeIds)
 {
     var parentIds = [];
-    if (typeof(pAttributeIds) == "string")
+    if (Utils.isString(pAttributeIds))
         pAttributeIds = [pAttributeIds];
         
     while (pAttributeIds.length > 0)
@@ -397,7 +394,7 @@ AttributeUtil.getAllParents = function (pAttributeIds)
     }
     
     // remove empty array elements
-    parentIds = parentIds.filter(function (id) { return id != null && id != '' });
+    parentIds = parentIds.filter(Utils.isNotNullOrEmptyString);
     
     return parentIds;
 }
diff --git a/process/Employee_lib/process.js b/process/Employee_lib/process.js
index 2cc4dad6f5..68230ad249 100644
--- a/process/Employee_lib/process.js
+++ b/process/Employee_lib/process.js
@@ -171,7 +171,7 @@ EmployeeUtils.getUsersDepartment = function(pContactId, pDepartmentAsDisplayable
     
     if (pDepartmentAsDisplayable)
     {
-       department = AttributeUtil.getSimpleAttributeName(department, true)
+       department = AttributeUtil.getSimpleAttributeName(department)
     }
     
     return department;
diff --git a/process/Util_lib/process.js b/process/Util_lib/process.js
index e3b1a49826..1e1d73087c 100644
--- a/process/Util_lib/process.js
+++ b/process/Util_lib/process.js
@@ -417,6 +417,32 @@ Utils.objectValues = function (pObject)
     });
 }
 
+/**
+ * Creates a cached function. If that function is called multiple times with the same parameters, 
+ * the cached result is returned instead of calculating the result every time anew.
+ * 
+ * @param {Function} pFunction  Function to be memoized
+ * @param {Function} [pHashFn]  Custom function to generate a unique hash value from the function parameters as a cache key. Required if the
+ *                              function has more than one parameter.
+ *                              
+ * @return {Function} memoized function
+ */
+Utils.memoize = function (pFunction, pHashFn) 
+{
+  var memoized = function (pKey) 
+  {
+    var cache = memoized.cache;
+    var address = pHashFn ? pHashFn.apply(this, arguments) : pKey;
+    if (!cache.has(address)) 
+    {
+        cache.set(address, pFunction.apply(this, arguments));
+    }
+    return cache.get(address);
+  };
+  memoized.cache = new Map();
+  return memoized;
+}
+
 
 /**
  * builds/extends a filter-Object, can be used for e. g. entity.filter(), neon.setFilter, etc...
diff --git a/process/Utils_test/process.js b/process/Utils_test/process.js
index 3a094ad4dc..0559bbe716 100644
--- a/process/Utils_test/process.js
+++ b/process/Utils_test/process.js
@@ -519,6 +519,51 @@ var toBoolean = new TestSuite("Utils.toBoolean", [
     )
 ]);
 
+var memoize = new TestSuite("Utils.memoize", [
+    new Test("should return the same result for consecutive calls with same parameter",
+        function (pTester, pDataProvider)
+        {
+            var memoizedFn = Utils.memoize(function (pValue)
+            {
+                return pValue;
+            });
+            var actualValue = memoizedFn(pDataProvider[0]);
+            var expectValue = pDataProvider[1];
+            
+            pTester.expectThat(actualValue).equals(expectValue).assert();
+        },
+        function dataProvider() 
+        {
+            return [
+                [1, 1],
+                [1, 1],
+                [1, 1],
+                [2, 2],
+                [2, 2],
+                [3, 3]
+            ];
+        }
+    ),
+    new Test("should not invoke function multiple times for the same parameters",
+        function (pTester)
+        {
+            var executionCount = 0;
+            var memoizedFn = Utils.memoize(function (pValue)
+            {
+                executionCount++;
+                return pValue;
+            });
+            memoizedFn();
+            memoizedFn();
+            memoizedFn(1);
+            memoizedFn(1);
+            memoizedFn(1);
+            
+            pTester.expectThat(executionCount).equals(2).assert();
+        }
+    )
+]);
+
 var tester = new Tester("Test Util_lib");
 tester.initCoverage(Utils);
 tester.test(isEmpty);
@@ -536,6 +581,7 @@ tester.test(isObject);
 tester.test(isMap);
 tester.test(isBoolean);
 tester.test(toBoolean);
+tester.test(memoize);
 
 tester.summary();
     
-- 
GitLab