From 5c271d5aac44a51b3ea41bbf81bef6fb2570a5ea Mon Sep 17 00:00:00 2001
From: pkupczyk <piotr.kupczyk@id.ethz.ch>
Date: Thu, 20 Aug 2020 14:39:37 +0200
Subject: [PATCH] NG_UI : bugfixing after Caterina's tests : SSDM-10060 -
 validate 'code' format on the client side onBlur (instead of on the server
 side onSave); the mandatory fields validation remains unchanged (i.e. on the
 client side onSave)

---
 .../components/common/form/FormValidator.js   | 66 +++++++++++++------
 .../common/page/PageConrollerValidate.js      |  6 +-
 .../common/page/PageControllerLoad.js         |  2 +-
 .../common/page/PageControllerSave.js         |  2 +-
 .../src/js/components/login/Login.jsx         | 10 +--
 .../types/form/TypeFormControllerValidate.js  |  2 +
 .../form/VocabularyFormControllerValidate.js  |  9 +++
 .../types/form/TypeFormComponent.test.js      | 48 +++++++++++++-
 .../form/VocabularyFormComponent.test.js      | 43 +++++++++++-
 9 files changed, 151 insertions(+), 37 deletions(-)

diff --git a/openbis_ng_ui/src/js/components/common/form/FormValidator.js b/openbis_ng_ui/src/js/components/common/form/FormValidator.js
index 5ff9d4733a1..bb5567064ad 100644
--- a/openbis_ng_ui/src/js/components/common/form/FormValidator.js
+++ b/openbis_ng_ui/src/js/components/common/form/FormValidator.js
@@ -1,12 +1,18 @@
+const CODE_PATTERN = /^[A-Za-z0-9_\\-\\.:]+$/
+
 class FormValidator {
-  constructor() {
+  constructor(mode) {
+    this.mode = mode
+    this.objects = new Set()
+    this.fields = new Set()
     this.errors = []
-    this.objects = []
   }
 
   validateNotEmpty(object, name, label) {
-    this._validateField(object, name, () => {
-      const field = object[name]
+    this._validateField(object, name, field => {
+      if (this.mode !== 'full') {
+        return null
+      }
       if (
         field.value === null ||
         field.value === undefined ||
@@ -19,31 +25,53 @@ class FormValidator {
     })
   }
 
-  _validateField(object, name, fn) {
-    const field = object[name]
+  validatePattern(object, name, label, pattern) {
+    this._validateField(object, name, field => {
+      if (
+        field.value === null ||
+        field.value === undefined ||
+        field.value.trim() === ''
+      ) {
+        return null
+      } else {
+        if (pattern.test(field.value)) {
+          return null
+        } else {
+          return label
+        }
+      }
+    })
+  }
 
-    const oldError = field.error
-    const newError = fn()
+  validateCode(object, name, label) {
+    this.validatePattern(
+      object,
+      name,
+      label + ' can only contain A-Z, a-z, 0-9 and _, -, .',
+      CODE_PATTERN
+    )
+  }
 
-    if (!this.objects.includes(object)) {
-      this.objects.push(object)
+  _validateField(object, name, fn) {
+    if (!this.objects.has(object)) {
       object.errors = 0
+      this.objects.add(object)
     }
 
-    if (newError !== oldError) {
+    if (!this.fields.has(object[name])) {
       object[name] = {
-        ...field,
-        error: newError
+        ...object[name],
+        error: null
       }
+      this.fields.add(object[name])
     }
 
-    if (newError) {
+    const error = fn(object[name])
+
+    if (error) {
       object.errors++
-      this.errors.push({
-        object,
-        name,
-        error: newError
-      })
+      object[name].error = error
+      this.errors.push({ object, name, error })
     }
   }
 
diff --git a/openbis_ng_ui/src/js/components/common/page/PageConrollerValidate.js b/openbis_ng_ui/src/js/components/common/page/PageConrollerValidate.js
index bb4b8ef1b31..506cd2c96bc 100644
--- a/openbis_ng_ui/src/js/components/common/page/PageConrollerValidate.js
+++ b/openbis_ng_ui/src/js/components/common/page/PageConrollerValidate.js
@@ -21,11 +21,7 @@ export default class PageControllerValidate {
   async execute(autofocus) {
     const { validate } = this.context.getState()
 
-    if (!validate) {
-      return true
-    }
-
-    const validator = new FormValidator()
+    const validator = new FormValidator(validate)
     const newState = await this.validate(validator)
     const errors = validator.getErrors()
 
diff --git a/openbis_ng_ui/src/js/components/common/page/PageControllerLoad.js b/openbis_ng_ui/src/js/components/common/page/PageControllerLoad.js
index 30f6d5632f9..d5876756d19 100644
--- a/openbis_ng_ui/src/js/components/common/page/PageControllerLoad.js
+++ b/openbis_ng_ui/src/js/components/common/page/PageControllerLoad.js
@@ -17,7 +17,7 @@ export default class PageControllerLoad {
     try {
       await this.context.setState({
         loading: true,
-        validate: false
+        validate: 'basic'
       })
 
       const isNew = this.object.type === this.controller.getNewObjectType()
diff --git a/openbis_ng_ui/src/js/components/common/page/PageControllerSave.js b/openbis_ng_ui/src/js/components/common/page/PageControllerSave.js
index 563c7cceb1b..5b7466a9116 100644
--- a/openbis_ng_ui/src/js/components/common/page/PageControllerSave.js
+++ b/openbis_ng_ui/src/js/components/common/page/PageControllerSave.js
@@ -15,7 +15,7 @@ export default class PageControllerSave {
   async execute() {
     try {
       await this.context.setState({
-        validate: true
+        validate: 'full'
       })
 
       const valid = await this.controller.validate(true)
diff --git a/openbis_ng_ui/src/js/components/login/Login.jsx b/openbis_ng_ui/src/js/components/login/Login.jsx
index ec5811007dc..5f2e55738f4 100644
--- a/openbis_ng_ui/src/js/components/login/Login.jsx
+++ b/openbis_ng_ui/src/js/components/login/Login.jsx
@@ -61,7 +61,7 @@ class WithLogin extends React.Component {
         error: null
       },
       selection: 'user',
-      validate: false
+      validate: 'basic'
     }
     this.references = {
       user: React.createRef(),
@@ -94,13 +94,9 @@ class WithLogin extends React.Component {
   }
 
   validate(autofocus) {
-    if (!this.state.validate) {
-      return true
-    }
-
     const newState = { ...this.state }
 
-    const validator = new FormValidator()
+    const validator = new FormValidator(this.state.validate)
     validator.validateNotEmpty(newState, 'user', 'User')
     validator.validateNotEmpty(newState, 'password', 'Password')
 
@@ -144,7 +140,7 @@ class WithLogin extends React.Component {
 
     this.setState(
       {
-        validate: true
+        validate: 'full'
       },
       () => {
         if (this.validate(true)) {
diff --git a/openbis_ng_ui/src/js/components/types/form/TypeFormControllerValidate.js b/openbis_ng_ui/src/js/components/types/form/TypeFormControllerValidate.js
index ac50135d074..d4adc46aa2b 100644
--- a/openbis_ng_ui/src/js/components/types/form/TypeFormControllerValidate.js
+++ b/openbis_ng_ui/src/js/components/types/form/TypeFormControllerValidate.js
@@ -43,6 +43,7 @@ export default class TypeFormControllerValidate extends PageControllerValidate {
   _validateType(validator, type) {
     const strategy = this._getStrategy()
     validator.validateNotEmpty(type, 'code', 'Code')
+    validator.validateCode(type, 'code', 'Code')
     strategy.validateTypeAttributes(validator, type)
   }
 
@@ -54,6 +55,7 @@ export default class TypeFormControllerValidate extends PageControllerValidate {
 
   _validateProperty(validator, property) {
     validator.validateNotEmpty(property, 'code', 'Code')
+    validator.validateCode(property, 'code', 'Code')
     validator.validateNotEmpty(property, 'label', 'Label')
     validator.validateNotEmpty(property, 'description', 'Description')
     validator.validateNotEmpty(property, 'dataType', 'Data Type')
diff --git a/openbis_ng_ui/src/js/components/types/form/VocabularyFormControllerValidate.js b/openbis_ng_ui/src/js/components/types/form/VocabularyFormControllerValidate.js
index 6c9d55e963d..45127335b92 100644
--- a/openbis_ng_ui/src/js/components/types/form/VocabularyFormControllerValidate.js
+++ b/openbis_ng_ui/src/js/components/types/form/VocabularyFormControllerValidate.js
@@ -1,5 +1,7 @@
 import PageControllerValidate from '@src/js/components/common/page/PageConrollerValidate.js'
 
+const TERM_CODE_PATTERN = /^[A-Za-z0-9_\\-\\.:]+$/
+
 export default class VocabularyFormControllerValidate extends PageControllerValidate {
   validate(validator) {
     const { vocabulary, terms } = this.context.getState()
@@ -45,6 +47,7 @@ export default class VocabularyFormControllerValidate extends PageControllerVali
 
   _validateVocabulary(validator, vocabulary) {
     validator.validateNotEmpty(vocabulary, 'code', 'Code')
+    validator.validateCode(vocabulary, 'code', 'Code')
   }
 
   _validateTerms(validator, terms) {
@@ -55,5 +58,11 @@ export default class VocabularyFormControllerValidate extends PageControllerVali
 
   _validateTerm(validator, term) {
     validator.validateNotEmpty(term, 'code', 'Code')
+    validator.validatePattern(
+      term,
+      'code',
+      'Code can only contain A-Z, a-z, 0-9 and _, -, ., :',
+      TERM_CODE_PATTERN
+    )
   }
 }
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponent.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponent.test.js
index 3b56c0d7bc8..93a75a85c9c 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponent.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponent.test.js
@@ -1093,7 +1093,8 @@ async function testValidateType() {
       type: {
         title: 'Type',
         code: {
-          error: 'Code cannot be empty'
+          error: 'Code cannot be empty',
+          focused: true
         },
         description: {
           error: null
@@ -1110,6 +1111,30 @@ async function testValidateType() {
       message: null
     }
   })
+
+  form.getParameters().getType().getCode().change('I am illegal')
+  await form.update()
+
+  form.getButtons().getSave().click()
+  await form.update()
+
+  form.expectJSON({
+    parameters: {
+      type: {
+        code: {
+          value: 'I am illegal',
+          error: 'Code can only contain A-Z, a-z, 0-9 and _, -, .',
+          focused: true
+        }
+      }
+    },
+    buttons: {
+      message: {
+        text: 'You have unsaved changes.',
+        type: 'warning'
+      }
+    }
+  })
 }
 
 async function testValidateProperty() {
@@ -1131,7 +1156,8 @@ async function testValidateProperty() {
           error: null
         },
         code: {
-          error: 'Code cannot be empty'
+          error: 'Code cannot be empty',
+          focused: true
         },
         dataType: {
           error: null
@@ -1154,6 +1180,24 @@ async function testValidateProperty() {
       }
     }
   })
+
+  form.getParameters().getProperty().getCode().change('I am illegal')
+  await form.update()
+
+  form.getButtons().getSave().click()
+  await form.update()
+
+  form.expectJSON({
+    parameters: {
+      property: {
+        code: {
+          value: 'I am illegal',
+          error: 'Code can only contain A-Z, a-z, 0-9 and _, -, .',
+          focused: true
+        }
+      }
+    }
+  })
 }
 
 async function testValidateTypeAndProperty() {
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponent.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponent.test.js
index b6956ff4429..d615bf1e549 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponent.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponent.test.js
@@ -1082,7 +1082,8 @@ async function testValidateTerm() {
         title: 'Term',
         code: {
           value: null,
-          error: 'Code cannot be empty'
+          error: 'Code cannot be empty',
+          focused: true
         }
       }
     },
@@ -1093,6 +1094,24 @@ async function testValidateTerm() {
       }
     }
   })
+
+  form.getParameters().getTerm().getCode().change('I am illegal')
+  await form.update()
+
+  form.getButtons().getSave().click()
+  await form.update()
+
+  form.expectJSON({
+    parameters: {
+      term: {
+        code: {
+          value: 'I am illegal',
+          error: 'Code can only contain A-Z, a-z, 0-9 and _, -, ., :',
+          focused: true
+        }
+      }
+    }
+  })
 }
 
 async function testValidateVocabulary() {
@@ -1122,7 +1141,9 @@ async function testValidateVocabulary() {
       vocabulary: {
         title: 'Vocabulary',
         code: {
-          error: 'Code cannot be empty'
+          value: null,
+          error: 'Code cannot be empty',
+          focused: true
         }
       }
     },
@@ -1133,6 +1154,24 @@ async function testValidateVocabulary() {
       }
     }
   })
+
+  form.getParameters().getVocabulary().getCode().change('I am illegal')
+  await form.update()
+
+  form.getButtons().getSave().click()
+  await form.update()
+
+  form.expectJSON({
+    parameters: {
+      vocabulary: {
+        code: {
+          value: 'I am illegal',
+          error: 'Code can only contain A-Z, a-z, 0-9 and _, -, .',
+          focused: true
+        }
+      }
+    }
+  })
 }
 
 async function mountNew() {
-- 
GitLab