From 8b18f6ae4409e751b5bc4ece11736fd4d4a73677 Mon Sep 17 00:00:00 2001
From: pkupczyk <piotr.kupczyk@id.ethz.ch>
Date: Fri, 17 Mar 2023 13:07:26 +0100
Subject: [PATCH] SSDM-13133 : Add info to Admin UI : have common methods for
 date columns with range filters + user columns

---
 ui-admin/src/js/common/date.js                | 19 ++++-
 .../src/js/components/common/grid/GridUtil.js | 73 +++++++++++++++++++
 .../database/browser/DatabaseBrowserCommon.js |  1 -
 .../components/tools/common/HistoryGrid.jsx   | 58 ++++-----------
 .../tools/common/PersonalAccessTokensGrid.jsx | 72 ++++++------------
 .../components/tools/common/PluginsGrid.jsx   | 18 +----
 .../components/tools/common/QueriesGrid.jsx   | 18 +----
 .../types/common/EntityTypesGrid.jsx          |  8 +-
 .../types/common/PropertyTypesGrid.jsx        | 18 +----
 .../types/common/VocabularyTypesGrid.jsx      | 24 +-----
 .../vocabularytype/VocabularyTypeForm.jsx     | 18 +----
 .../js/components/users/common/RolesGrid.jsx  | 18 +----
 .../users/common/UserGroupsGrid.jsx           | 24 +-----
 .../js/components/users/common/UsersGrid.jsx  | 27 ++-----
 14 files changed, 156 insertions(+), 240 deletions(-)
 create mode 100644 ui-admin/src/js/components/common/grid/GridUtil.js

diff --git a/ui-admin/src/js/common/date.js b/ui-admin/src/js/common/date.js
index 9f12472b5ff..472345695e2 100644
--- a/ui-admin/src/js/common/date.js
+++ b/ui-admin/src/js/common/date.js
@@ -8,7 +8,7 @@ const MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR
 const MILLIS_PER_YEAR = 365 * MILLIS_PER_DAY
 
 function format(value) {
-  if (value === null) {
+  if (_.isNil(value)) {
     return ''
   }
 
@@ -56,13 +56,24 @@ function duration(millis) {
 
 function inRange(value, from, to) {
   if (from || to) {
-    if (value === null || value === undefined) {
+    if (_.isNil(value)) {
       return false
     }
-    if (from && value.getTime() < from.getTime()) {
+
+    var time = null
+
+    if (_.isNumber(value)) {
+      time = value
+    } else if (_.isDate(value)) {
+      time = value.getTime()
+    } else {
+      return false
+    }
+
+    if (from && time < from.getTime()) {
       return false
     }
-    if (to && value.getTime() > to.getTime()) {
+    if (to && time > to.getTime()) {
       return false
     }
   }
diff --git a/ui-admin/src/js/components/common/grid/GridUtil.js b/ui-admin/src/js/components/common/grid/GridUtil.js
new file mode 100644
index 00000000000..26f4a454b4b
--- /dev/null
+++ b/ui-admin/src/js/components/common/grid/GridUtil.js
@@ -0,0 +1,73 @@
+import _ from 'lodash'
+import React from 'react'
+import UserLink from '@src/js/components/common/link/UserLink.jsx'
+import DateRangeField from '@src/js/components/common/form/DateRangeField.jsx'
+import date from '@src/js/common/date.js'
+import messages from '@src/js/common/messages.js'
+
+function userColumn(params) {
+  return {
+    ...params,
+    getValue: ({ row }) => _.get(row, params.path),
+    renderValue: ({ value }) => {
+      return <UserLink userId={value} />
+    }
+  }
+}
+
+function registratorColumn(params) {
+  return userColumn({
+    ...params,
+    name: 'registrator',
+    label: messages.get(messages.REGISTRATOR),
+    path: params.path
+  })
+}
+
+function dateColumn(params) {
+  return {
+    ...params,
+    getValue: ({ row }) => _.get(row, params.path),
+    renderValue: ({ value }) => date.format(value),
+    renderFilter: ({ value, onChange }) => {
+      return (
+        <DateRangeField value={value} variant='standard' onChange={onChange} />
+      )
+    },
+    matchesValue: ({ value, filter, defaultMatches }) => {
+      if (_.isString(filter)) {
+        return defaultMatches(value, filter)
+      } else {
+        return date.inRange(
+          value,
+          filter.from ? filter.from.dateObject : null,
+          filter.to ? filter.to.dateObject : null
+        )
+      }
+    }
+  }
+}
+
+function registrationDateColumn(params) {
+  return dateColumn({
+    ...params,
+    name: 'registrationDate',
+    label: messages.get(messages.REGISTRATION_DATE)
+  })
+}
+
+function modificationDateColumn(params) {
+  return dateColumn({
+    ...params,
+    name: 'modificationDate',
+    label: messages.get(messages.MODIFICATION_DATE)
+  })
+}
+
+export default {
+  userColumn,
+  registratorColumn,
+  dateColumn,
+  registrationDateColumn,
+  modificationDateColumn
+}
diff --git a/ui-admin/src/js/components/database/browser/DatabaseBrowserCommon.js b/ui-admin/src/js/components/database/browser/DatabaseBrowserCommon.js
index e8e452078ec..c147e29d23a 100644
--- a/ui-admin/src/js/components/database/browser/DatabaseBrowserCommon.js
+++ b/ui-admin/src/js/components/database/browser/DatabaseBrowserCommon.js
@@ -1,4 +1,3 @@
-import BrowserCommon from '@src/js/components/common/browser/BrowserCommon.js'
 import objectType from '@src/js/common/consts/objectType.js'
 import messages from '@src/js/common/messages.js'
 
diff --git a/ui-admin/src/js/components/tools/common/HistoryGrid.jsx b/ui-admin/src/js/components/tools/common/HistoryGrid.jsx
index 05955d09839..a25bd0c7fcd 100644
--- a/ui-admin/src/js/components/tools/common/HistoryGrid.jsx
+++ b/ui-admin/src/js/components/tools/common/HistoryGrid.jsx
@@ -4,9 +4,8 @@ import autoBind from 'auto-bind'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
 import GridFilterOptions from '@src/js/components/common/grid/GridFilterOptions.js'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import SelectField from '@src/js/components/common/form/SelectField.jsx'
-import DateRangeField from '@src/js/components/common/form/DateRangeField.jsx'
 import FormUtil from '@src/js/components/common/form/FormUtil.js'
 import EntityType from '@src/js/components/common/dto/EntityType.js'
 import HistoryGridContentCell from '@src/js/components/tools/common/HistoryGridContentCell.jsx'
@@ -162,28 +161,18 @@ class HistoryGrid extends React.PureComponent {
             sortable: false,
             getValue: ({ row }) => row.entityProject.value
           },
-          {
+          GridUtil.userColumn({
             name: 'entityRegistrator',
             label: messages.get(messages.ENTITY_REGISTRATOR),
-            sortable: false,
-            getValue: ({ row }) => row.entityRegistrator.value
-          },
-          {
+            path: 'entityRegistrator.value',
+            sortable: false
+          }),
+          GridUtil.dateColumn({
             name: 'entityRegistrationDate',
             label: messages.get(messages.ENTITY_REGISTRATION_DATE),
-            sortable: false,
-            getValue: ({ row }) =>
-              date.format(row.entityRegistrationDate.value),
-            renderFilter: ({ value, onChange }) => {
-              return (
-                <DateRangeField
-                  value={value}
-                  variant='standard'
-                  onChange={onChange}
-                />
-              )
-            }
-          },
+            path: 'entityRegistrationDate.value',
+            sortable: false
+          }),
           {
             name: 'reason',
             label: messages.get(messages.REASON),
@@ -206,30 +195,11 @@ class HistoryGrid extends React.PureComponent {
               return <HistoryGridContentCell value={value} />
             }
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.USER),
-            sortable: false,
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.DATE),
-            sortable: true,
-            getValue: ({ row }) => date.format(row.registrationDate.value),
-            renderFilter: ({ value, onChange }) => {
-              return (
-                <DateRangeField
-                  value={value}
-                  variant='standard'
-                  onChange={onChange}
-                />
-              )
-            }
-          }
+          GridUtil.registratorColumn({
+            path: 'registrator.value',
+            sortable: false
+          }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
         ]}
         loadRows={this.load}
         sort='registrationDate'
diff --git a/ui-admin/src/js/components/tools/common/PersonalAccessTokensGrid.jsx b/ui-admin/src/js/components/tools/common/PersonalAccessTokensGrid.jsx
index 6591ac446bf..d81279073e2 100644
--- a/ui-admin/src/js/components/tools/common/PersonalAccessTokensGrid.jsx
+++ b/ui-admin/src/js/components/tools/common/PersonalAccessTokensGrid.jsx
@@ -2,9 +2,8 @@ import _ from 'lodash'
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
-import DateRangeField from '@src/js/components/common/form/DateRangeField.jsx'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import SelectField from '@src/js/components/common/form/SelectField.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
 import Message from '@src/js/components/common/form/Message.jsx'
 import ServerInformation from '@src/js/components/common/dto/ServerInformation.js'
 import AppController from '@src/js/components/AppController.js'
@@ -41,14 +40,11 @@ class PersonalAccessTokensGrid extends React.PureComponent {
             getValue: ({ row }) => row.hash.value,
             nowrap: true
           },
-          {
+          GridUtil.userColumn({
             name: 'owner',
             label: messages.get(messages.OWNER),
-            getValue: ({ row }) => row.owner.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
+            path: 'owner.value'
+          }),
           {
             name: 'sessionName',
             label: messages.get(messages.SESSION_NAME),
@@ -67,8 +63,16 @@ class PersonalAccessTokensGrid extends React.PureComponent {
               }
             }
           },
-          dateColumn('validFromDate', messages.get(messages.VALID_FROM)),
-          dateColumn('validToDate', messages.get(messages.VALID_TO)),
+          GridUtil.dateColumn({
+            name: 'validFromDate',
+            label: messages.get(messages.VALID_FROM),
+            path: 'validFromDate.value.dateObject'
+          }),
+          GridUtil.dateColumn({
+            name: 'validToDate',
+            label: messages.get(messages.VALID_TO),
+            path: 'validToDate.value.dateObject'
+          }),
           {
             name: 'validityPeriod',
             label: messages.get(messages.VALIDITY_PERIOD),
@@ -231,19 +235,15 @@ class PersonalAccessTokensGrid extends React.PureComponent {
             },
             nowrap: true
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          dateColumn(
-            'registrationDate',
-            messages.get(messages.REGISTRATION_DATE)
-          ),
-          dateColumn('accessDate', messages.get(messages.ACCESS_DATE))
+          GridUtil.dateColumn({
+            name: 'accessDate',
+            label: messages.get(messages.ACCESS_DATE),
+            path: 'accessDate.value.dateObject'
+          }),
+          GridUtil.registratorColumn({ path: 'registrator.value' }),
+          GridUtil.registrationDateColumn({
+            path: 'registrationDate.value.dateObject'
+          })
         ]}
         rows={rows}
         sortings={[
@@ -263,30 +263,4 @@ class PersonalAccessTokensGrid extends React.PureComponent {
   }
 }
 
-function dateColumn(name, message) {
-  return {
-    name: name,
-    label: message,
-    getValue: ({ row }) => {
-      return date.format(row[name].value ? row[name].value.dateObject : null)
-    },
-    renderFilter: ({ value, onChange }) => {
-      return (
-        <DateRangeField value={value} variant='standard' onChange={onChange} />
-      )
-    },
-    matchesValue: ({ row, value, filter, defaultMatches }) => {
-      if (_.isString(filter)) {
-        return defaultMatches(value, filter)
-      } else {
-        return date.inRange(
-          row[name].value ? row[name].value.dateObject : null,
-          filter.from ? filter.from.dateObject : null,
-          filter.to ? filter.to.dateObject : null
-        )
-      }
-    }
-  }
-}
-
 export default PersonalAccessTokensGrid
diff --git a/ui-admin/src/js/components/tools/common/PluginsGrid.jsx b/ui-admin/src/js/components/tools/common/PluginsGrid.jsx
index 176de73cf7b..15320be72bc 100644
--- a/ui-admin/src/js/components/tools/common/PluginsGrid.jsx
+++ b/ui-admin/src/js/components/tools/common/PluginsGrid.jsx
@@ -1,10 +1,9 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import PluginLink from '@src/js/components/common/link/PluginLink.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
 import EntityKind from '@src/js/components/common/dto/EntityKind.js'
-import date from '@src/js/common/date.js'
 import openbis from '@src/js/services/openbis.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
@@ -62,19 +61,8 @@ class PluginsGrid extends React.PureComponent {
                 : '(' + messages.get(messages.ALL) + ')'
             }
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate.value)
-          }
+          GridUtil.registratorColumn({ path: 'registrator.value' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
         ]}
         rows={rows}
         exportable={this.getExportable()}
diff --git a/ui-admin/src/js/components/tools/common/QueriesGrid.jsx b/ui-admin/src/js/components/tools/common/QueriesGrid.jsx
index 6aea2cdf426..546cf1252c5 100644
--- a/ui-admin/src/js/components/tools/common/QueriesGrid.jsx
+++ b/ui-admin/src/js/components/tools/common/QueriesGrid.jsx
@@ -1,10 +1,9 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import QueryLink from '@src/js/components/common/link/QueryLink.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
 import QueryType from '@src/js/components/common/dto/QueryType.js'
-import date from '@src/js/common/date.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
 
@@ -56,19 +55,8 @@ class QueriesGrid extends React.PureComponent {
             label: messages.get(messages.PUBLIC),
             getValue: ({ row }) => row.publicFlag.value
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate.value)
-          }
+          GridUtil.registratorColumn({ path: 'registrator.value' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
         ]}
         rows={rows}
         exportable={{
diff --git a/ui-admin/src/js/components/types/common/EntityTypesGrid.jsx b/ui-admin/src/js/components/types/common/EntityTypesGrid.jsx
index fd08a125448..d7a0ebcfa1a 100644
--- a/ui-admin/src/js/components/types/common/EntityTypesGrid.jsx
+++ b/ui-admin/src/js/components/types/common/EntityTypesGrid.jsx
@@ -1,8 +1,8 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import EntityTypeLink from '@src/js/components/common/link/EntityTypeLink.jsx'
 import PluginLink from '@src/js/components/common/link/PluginLink.jsx'
-import date from '@src/js/common/date.js'
 import openbis from '@src/js/services/openbis.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
@@ -124,11 +124,7 @@ class EntityTypesGrid extends React.PureComponent {
       })
     }
 
-    columns.push({
-      name: 'modificationDate',
-      label: messages.get(messages.MODIFICATION_DATE),
-      getValue: ({ row }) => date.format(row.modificationDate)
-    })
+    columns.push(GridUtil.modificationDateColumn({ path: 'modificationDate' }))
 
     return columns
   }
diff --git a/ui-admin/src/js/components/types/common/PropertyTypesGrid.jsx b/ui-admin/src/js/components/types/common/PropertyTypesGrid.jsx
index c5f8ea8c58c..5d6dd88980c 100644
--- a/ui-admin/src/js/components/types/common/PropertyTypesGrid.jsx
+++ b/ui-admin/src/js/components/types/common/PropertyTypesGrid.jsx
@@ -1,12 +1,11 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import EntityTypeLink from '@src/js/components/common/link/EntityTypeLink.jsx'
 import VocabularyTypeLink from '@src/js/components/common/link/VocabularyTypeLink.jsx'
 import PropertyTypesGridUsagesCell from '@src/js/components/types/common/PropertyTypesGridUsagesCell.jsx'
 import PropertyTypesGridXMLCell from '@src/js/components/types/common/PropertyTypesGridXMLCell.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
-import date from '@src/js/common/date.js'
 import openbis from '@src/js/services/openbis.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
@@ -105,19 +104,8 @@ class PropertyTypesGrid extends React.PureComponent {
               return <PropertyTypesGridUsagesCell value={row.usages} />
             }
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate)
-          }
+          GridUtil.registratorColumn({ path: 'registrator' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate' })
         ]}
         rows={rows}
         sort='code'
diff --git a/ui-admin/src/js/components/types/common/VocabularyTypesGrid.jsx b/ui-admin/src/js/components/types/common/VocabularyTypesGrid.jsx
index e66e3202303..f05e5997b4e 100644
--- a/ui-admin/src/js/components/types/common/VocabularyTypesGrid.jsx
+++ b/ui-admin/src/js/components/types/common/VocabularyTypesGrid.jsx
@@ -1,9 +1,8 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import VocabularyTypeLink from '@src/js/components/common/link/VocabularyTypeLink.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
-import date from '@src/js/common/date.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
 
@@ -39,24 +38,9 @@ class VocabularyTypesGrid extends React.PureComponent {
             label: messages.get(messages.URL_TEMPLATE),
             getValue: ({ row }) => row.urlTemplate
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate)
-          },
-          {
-            name: 'modificationDate',
-            label: messages.get(messages.MODIFICATION_DATE),
-            getValue: ({ row }) => date.format(row.modificationDate)
-          }
+          GridUtil.registratorColumn({ path: 'registrator' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate' }),
+          GridUtil.modificationDateColumn({ path: 'modificationDate' })
         ]}
         rows={rows}
         sort='code'
diff --git a/ui-admin/src/js/components/types/form/vocabularytype/VocabularyTypeForm.jsx b/ui-admin/src/js/components/types/form/vocabularytype/VocabularyTypeForm.jsx
index 71e0cff3c44..3c484b75698 100644
--- a/ui-admin/src/js/components/types/form/vocabularytype/VocabularyTypeForm.jsx
+++ b/ui-admin/src/js/components/types/form/vocabularytype/VocabularyTypeForm.jsx
@@ -5,13 +5,12 @@ import PageWithTwoPanels from '@src/js/components/common/page/PageWithTwoPanels.
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
 import GridContainer from '@src/js/components/common/grid/GridContainer.jsx'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import VocabularyTypeFormSelectionType from '@src/js/components/types/form/vocabularytype/VocabularyTypeFormSelectionType.js'
 import VocabularyTypeFormController from '@src/js/components/types/form/vocabularytype/VocabularyTypeFormController.js'
 import VocabularyTypeFormFacade from '@src/js/components/types/form/vocabularytype/VocabularyTypeFormFacade.js'
 import VocabularyTypeFormParameters from '@src/js/components/types/form/vocabularytype/VocabularyTypeFormParameters.jsx'
 import VocabularyTypeFormButtons from '@src/js/components/types/form/vocabularytype/VocabularyTypeFormButtons.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
-import date from '@src/js/common/date.js'
 import ids from '@src/js/common/consts/ids.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
@@ -37,19 +36,8 @@ const columns = [
     label: messages.get(messages.OFFICIAL),
     getValue: ({ row }) => row.official.value
   },
-  {
-    name: 'registrator',
-    label: messages.get(messages.REGISTRATOR),
-    getValue: ({ row }) => row.registrator.value,
-    renderValue: ({ value }) => {
-      return <UserLink userId={value} />
-    }
-  },
-  {
-    name: 'registrationDate',
-    label: messages.get(messages.REGISTRATION_DATE),
-    getValue: ({ row }) => date.format(row.registrationDate.value)
-  }
+  GridUtil.registratorColumn({ path: 'registrator.value' }),
+  GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
 ]
 
 class VocabularyTypeForm extends React.PureComponent {
diff --git a/ui-admin/src/js/components/users/common/RolesGrid.jsx b/ui-admin/src/js/components/users/common/RolesGrid.jsx
index 2bd9398fb6a..f3b06e4b0a6 100644
--- a/ui-admin/src/js/components/users/common/RolesGrid.jsx
+++ b/ui-admin/src/js/components/users/common/RolesGrid.jsx
@@ -4,11 +4,11 @@ import autoBind from 'auto-bind'
 import { withStyles } from '@material-ui/core/styles'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import UserLink from '@src/js/components/common/link/UserLink.jsx'
 import UserGroupLink from '@src/js/components/common/link/UserGroupLink.jsx'
 import openbis from '@src/js/services/openbis.js'
 import ids from '@src/js/common/consts/ids.js'
-import date from '@src/js/common/date.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
 
@@ -240,20 +240,8 @@ class RolesGrid extends React.PureComponent {
           )
         }
       },
-      {
-        name: 'registrator',
-        label: messages.get(messages.REGISTRATOR),
-        getValue: ({ row }) => row.registrator.value,
-        renderValue: ({ value }) => {
-          return <UserLink userId={value} />
-        }
-      },
-      {
-        name: 'registrationDate',
-        label: messages.get(messages.REGISTRATION_DATE),
-        getValue: ({ row }) => date.format(row.registrationDate.value),
-        renderValue: this.renderDefault
-      }
+      GridUtil.registratorColumn({ path: 'registrator.value' }),
+      GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
     ]
   }
 
diff --git a/ui-admin/src/js/components/users/common/UserGroupsGrid.jsx b/ui-admin/src/js/components/users/common/UserGroupsGrid.jsx
index 623b4ed57ec..d6b0a27d418 100644
--- a/ui-admin/src/js/components/users/common/UserGroupsGrid.jsx
+++ b/ui-admin/src/js/components/users/common/UserGroupsGrid.jsx
@@ -1,9 +1,8 @@
 import React from 'react'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import UserGroupLink from '@src/js/components/common/link/UserGroupLink.jsx'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
-import date from '@src/js/common/date.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
 
@@ -39,24 +38,9 @@ export default class GroupsGrid extends React.PureComponent {
             label: messages.get(messages.DESCRIPTION),
             getValue: ({ row }) => row.description.value
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate.value)
-          },
-          {
-            name: 'modificationDate',
-            label: messages.get(messages.MODIFICATION_DATE),
-            getValue: ({ row }) => date.format(row.modificationDate.value)
-          }
+          GridUtil.registratorColumn({ path: 'registrator.value' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate.value' }),
+          GridUtil.modificationDateColumn({ path: 'modificationDate.value' })
         ]}
         rows={rows}
         exportable={{
diff --git a/ui-admin/src/js/components/users/common/UsersGrid.jsx b/ui-admin/src/js/components/users/common/UsersGrid.jsx
index 458a0a920c4..9e6d92306ff 100644
--- a/ui-admin/src/js/components/users/common/UsersGrid.jsx
+++ b/ui-admin/src/js/components/users/common/UsersGrid.jsx
@@ -4,8 +4,7 @@ import autoBind from 'auto-bind'
 import { withStyles } from '@material-ui/core/styles'
 import GridWithOpenbis from '@src/js/components/common/grid/GridWithOpenbis.jsx'
 import GridExportOptions from '@src/js/components/common/grid/GridExportOptions.js'
-import UserLink from '@src/js/components/common/link/UserLink.jsx'
-import date from '@src/js/common/date.js'
+import GridUtil from '@src/js/components/common/grid/GridUtil.js'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
 
@@ -31,14 +30,11 @@ class UsersGrid extends React.PureComponent {
         header={messages.get(messages.USERS)}
         sort='userId'
         columns={[
-          {
+          GridUtil.userColumn({
             name: 'userId',
             label: messages.get(messages.USER_ID),
-            getValue: ({ row }) => row.userId.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
+            path: 'userId.value'
+          }),
           {
             name: 'firstName',
             label: messages.get(messages.FIRST_NAME),
@@ -65,19 +61,8 @@ class UsersGrid extends React.PureComponent {
             label: messages.get(messages.ACTIVE),
             getValue: ({ row }) => row.active.value
           },
-          {
-            name: 'registrator',
-            label: messages.get(messages.REGISTRATOR),
-            getValue: ({ row }) => row.registrator.value,
-            renderValue: ({ value }) => {
-              return <UserLink userId={value} />
-            }
-          },
-          {
-            name: 'registrationDate',
-            label: messages.get(messages.REGISTRATION_DATE),
-            getValue: ({ row }) => date.format(row.registrationDate.value)
-          }
+          GridUtil.registratorColumn({ path: 'registrator.value' }),
+          GridUtil.registrationDateColumn({ path: 'registrationDate.value' })
         ]}
         rows={rows}
         exportable={{
-- 
GitLab