Merge pull request #1444 from merico-dev/jc-1386-configure-connection-ux-updates

`fix` `v0.9.2` data integrations - configure connection form ux updates
diff --git a/config-ui/src/components/validation/InputValidationError.jsx b/config-ui/src/components/validation/InputValidationError.jsx
new file mode 100644
index 0000000..3eb8f57
--- /dev/null
+++ b/config-ui/src/components/validation/InputValidationError.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import {
+  Colors,
+  Icon,
+  Popover,
+  PopoverInteractionKind,
+  Intent,
+  Position
+} from '@blueprintjs/core'
+
+const InputValidationError = (props) => {
+  const { error, position = Position.TOP } = props
+  return error
+    ? (
+      <div className='inline-input-error' style={{ outline: 'none', cursor: 'pointer', margin: '5px 5px 3px 5px' }}>
+        <Popover
+          position={position}
+          usePortal={true}
+          openOnTargetFocus={true}
+          intent={Intent.WARNING}
+          interactionKind={PopoverInteractionKind.HOVER_TARGET_ONLY}
+        >
+          <Icon icon='warning-sign' size={12} color={Colors.RED5} style={{ outline: 'none' }} />
+          <div style={{ outline: 'none', padding: '5px', borderTop: `2px solid ${Colors.RED5}` }}>{error}</div>
+        </Popover>
+      </div>
+      )
+    : null
+}
+
+export default InputValidationError
diff --git a/config-ui/src/data/Providers.js b/config-ui/src/data/Providers.js
index 75acee0..5747fdc 100644
--- a/config-ui/src/data/Providers.js
+++ b/config-ui/src/data/Providers.js
@@ -91,44 +91,44 @@
 
 const ProviderFormPlaceholders = {
   null: {
-    name: 'Enter Instance Name',
-    endpoint: 'Enter Endpoint URL eg. https://null-api.localhost',
-    proxy: 'Enter Proxy URL eg. http://proxy.localhost:8080',
-    token: 'Enter Auth Token eg. 3f5cda2a23ff410792e0',
+    name: 'eg. Enter Instance Name',
+    endpoint: 'eg. https://null-api.localhost',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. 3f5cda2a23ff410792e0',
     username: 'Enter Username / E-mail',
     password: 'Enter Password'
   },
   gitlab: {
-    name: 'Enter Instance Name',
-    endpoint: 'Enter Endpoint URL eg. https://gitlab.com/api/v4',
-    proxy: 'Enter Proxy URL eg. http://proxy.localhost:8080',
-    token: 'Enter Auth Token eg. ff9d1ad0e5c04f1f98fa',
+    name: 'eg. GitLab',
+    endpoint: 'eg. https://gitlab.com/api/v4',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. ff9d1ad0e5c04f1f98fa',
     username: 'Enter Username / E-mail',
     password: 'Enter Password'
   },
   jenkins: {
-    name: 'Enter Instance Name',
-    endpoint: 'Enter Endpoint URL eg. https://api.jenkins.io',
-    proxy: 'Enter Proxy URL eg. http://proxy.localhost:8080',
-    token: 'Enter Auth Token eg. 6b057ffe68464c93a057',
-    username: 'Enter Username / E-mail',
-    password: 'Enter Password'
+    name: 'eg. Jenkins',
+    endpoint: 'URL eg. https://api.jenkins.io',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. 6b057ffe68464c93a057',
+    username: 'eg. admin',
+    password: 'eg. ************'
   },
   jira: {
-    name: 'Enter Instance Name',
-    endpoint: 'Enter Endpoint URL eg. https://your-domain.atlassian.net/rest/',
-    proxy: 'Enter Proxy URL eg. http://proxy.localhost:8080',
-    token: 'Enter Auth Token eg. 8c06a7cc50b746bfab30',
-    username: 'Enter Username / E-mail',
-    password: 'Enter Password'
+    name: 'eg. JIRA',
+    endpoint: 'eg. https://your-domain.atlassian.net/rest/',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. 8c06a7cc50b746bfab30',
+    username: 'eg. admin',
+    password: 'eg. ************'
   },
   github: {
-    name: 'Enter Instance Name',
-    endpoint: 'Enter Endpoint URL eg. https://api.github.com',
-    proxy: 'Enter Proxy URL eg. http://proxy.localhost:8080',
-    token: 'Enter Auth Token(s) eg. 4c5cbdb62c165e2b3d18, 40008ebccff9837bb8d2',
-    username: 'Enter Username / E-mail',
-    password: 'Enter Password'
+    name: 'eg. GitHub',
+    endpoint: 'eg. https://api.github.com',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. 4c5cbdb62c165e2b3d18, 40008ebccff9837bb8d2',
+    username: 'eg. admin',
+    password: 'eg. ************'
   }
 }
 
diff --git a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
index 5a67de2..71665e4 100644
--- a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
+++ b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
@@ -10,6 +10,8 @@
   Intent,
   Card,
   Elevation,
+  Popover,
+  Colors,
 } from '@blueprintjs/core'
 import Nav from '@/components/Nav'
 import Sidebar from '@/components/Sidebar'
@@ -218,7 +220,8 @@
                     </div>
                     {activeConnection && (
                       <>
-                        <h2 style={{ margin: 0 }}>{activeConnection.name}</h2>
+                        {activeProvider.id === Providers.JIRA &&
+                        (<h2 style={{ margin: 0 }}>#{activeConnection.ID} {activeConnection.name}</h2>)}
                         <p className='page-description'>Manage settings and options for this connection.</p>
                       </>
                     )}
@@ -246,6 +249,7 @@
                         <div className='editConnection' style={{ display: 'flex' }}>
                           <ConnectionForm
                             isValid={isValidForm}
+                            validationErrors={validationErrors}
                             activeProvider={activeProvider}
                             name={name}
                             endpointUrl={endpointUrl}
@@ -285,9 +289,9 @@
                           </p>
                         </>
                         )}
-                    {validationErrors.length > 0 && (
+                    {/* {validationErrors.length > 0 && (
                       <FormValidationErrors errors={validationErrors} />
-                    )}
+                    )} */}
                   </Card>
                   <div style={{ marginTop: '30px' }}>
                     {renderProviderSettings(providerId, activeProvider)}
diff --git a/config-ui/src/pages/configure/connections/ConnectionForm.jsx b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
index f20f6ab..836df2d 100644
--- a/config-ui/src/pages/configure/connections/ConnectionForm.jsx
+++ b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
@@ -7,11 +7,15 @@
   Tag,
   Elevation,
   Popover,
+  // PopoverInteractionKind,
   Position,
-  Intent
+  Intent,
+  PopoverInteractionKind
 } from '@blueprintjs/core'
 import { Providers } from '@/data/Providers'
 import GenerateTokenForm from '@/pages/configure/connections/GenerateTokenForm'
+import FormValidationErrors from '@/components/messages/FormValidationErrors'
+import InputValidationError from '@/components/validation/InputValidationError'
 
 import '@/styles/integration.scss'
 import '@/styles/connections.scss'
@@ -20,6 +24,7 @@
   const {
     isLocked = false,
     isValid = true,
+    validationErrors = [],
     activeProvider,
     name,
     endpointUrl,
@@ -83,6 +88,14 @@
     setShowTokenCreator(isOpen)
   }
 
+  const fieldHasError = (fieldId) => {
+    return validationErrors.some(e => e.includes(fieldId))
+  }
+
+  const getFieldError = (fieldId) => {
+    return validationErrors.find(e => e.includes(fieldId))
+  }
+
   useEffect(() => {
     if (!allowedAuthTypes.includes(authType)) {
       console.log('INVALID AUTH TYPE!')
@@ -158,10 +171,10 @@
             label=''
             inline={true}
             labelFor='connection-name'
-            className='formGroup'
+            className='formGroup-inline'
             contentClassName='formGroupContent'
           >
-            <Label style={{ display: 'inline' }}>
+            <Label style={{ display: 'inline', marginRight: 0 }}>
               {labels
                 ? labels.name
                 : (
@@ -176,9 +189,15 @@
               placeholder={placeholders ? placeholders.name : 'Enter Instance Name'}
               value={name}
               onChange={(e) => onNameChange(e.target.value)}
-              className='input connection-name-input'
+              className={`input connection-name-input ${fieldHasError('Connection Source') ? 'invalid-field' : ''}`}
               leftIcon={[Providers.GITHUB, Providers.GITLAB, Providers.JENKINS].includes(activeProvider.id) ? 'lock' : null}
-              fill
+              inline={true}
+              rightElement={(
+                <InputValidationError
+                  error={getFieldError('Connection Source')}
+                />
+              )}
+              // fill
             />
           </FormGroup>
         </div>
@@ -206,8 +225,13 @@
               placeholder={placeholders ? placeholders.endpoint : 'Enter Endpoint URL'}
               value={endpointUrl}
               onChange={(e) => onEndpointChange(e.target.value)}
-              className='input'
+              className={`input endpoint-url-input ${fieldHasError('Endpoint') ? 'invalid-field' : ''}`}
               fill
+              rightElement={(
+                <InputValidationError
+                  error={getFieldError('Endpoint')}
+                />
+              )}
             />
             {/* <a href='#' style={{ margin: '5px 0 5px 5px' }}><Icon icon='info-sign' size='16' /></a> */}
           </FormGroup>
@@ -237,9 +261,14 @@
                 placeholder={placeholders ? placeholders.token : 'Enter Auth Token eg. EJrLG8DNeXADQcGOaaaX4B47'}
                 value={token}
                 onChange={(e) => onTokenChange(e.target.value)}
-                className='input'
+                className={`input auth-input ${fieldHasError('Auth') ? 'invalid-field' : ''}`}
                 fill
                 required
+                rightElement={(
+                  <InputValidationError
+                    error={getFieldError('Auth')}
+                  />
+                )}
               />
               {
                 activeProvider.id === Providers.JIRA &&
@@ -305,8 +334,13 @@
                   placeholder='Enter Username'
                   defaultValue={username}
                   onChange={(e) => onUsernameChange(e.target.value)}
-                  className='input'
-                  style={{ maxWidth: '300px' }}
+                  className={`input username-input ${fieldHasError('Username') ? 'invalid-field' : ''}`}
+                  // style={{ maxWidth: '300px' }}
+                  rightElement={(
+                    <InputValidationError
+                      error={getFieldError('Username')}
+                    />
+                  )}
                 />
               </FormGroup>
             </div>
@@ -334,8 +368,13 @@
                   placeholder='Enter Password'
                   defaultValue={password}
                   onChange={(e) => onPasswordChange(e.target.value)}
-                  className='input'
-                  style={{ maxWidth: '300px' }}
+                  className={`input password-input ${fieldHasError('Password') ? 'invalid-field' : ''}`}
+                  // style={{ maxWidth: '300px' }}
+                  rightElement={(
+                    <InputValidationError
+                      error={getFieldError('Password')}
+                    />
+                  )}
                 />
               </FormGroup>
             </div>
@@ -363,7 +402,12 @@
                 defaultValue={proxy}
                 onChange={(e) => onProxyChange(e.target.value)}
                 disabled={isTesting || isSaving || isLocked}
-                className='input'
+                className={`input input-proxy ${fieldHasError('Proxy') ? 'invalid-field' : ''}`}
+                rightElement={(
+                  <InputValidationError
+                    error={getFieldError('Proxy')}
+                  />
+                )}
               />
             </FormGroup>
           </div>
@@ -383,6 +427,14 @@
             />
           </div>
           <div style={{ display: 'flex' }}>
+            <div style={{ justifyContent: 'center', padding: '8px' }}>
+              {validationErrors.length > 0 && (
+                <Popover interactionKind={PopoverInteractionKind.HOVER_TARGET_ONLY}>
+                  <Icon icon='warning-sign' size={16} color={Colors.RED5} style={{ outline: 'none' }} />
+                  <div style={{ padding: '5px' }}><FormValidationErrors errors={validationErrors} /></div>
+                </Popover>
+              )}
+            </div>
             <Button className='btn-cancel' icon='remove' text='Cancel' onClick={onCancel} disabled={isSaving || isTesting} />
             <Button
               className='btn-save'
diff --git a/config-ui/src/styles/integration.scss b/config-ui/src/styles/integration.scss
index 6441a9e..62c56a6 100644
--- a/config-ui/src/styles/integration.scss
+++ b/config-ui/src/styles/integration.scss
@@ -114,4 +114,29 @@
   background-position: center center;
   background-repeat: no-repeat;
   background-size: 12px 12px;
+}
+
+.configureConnection {
+  min-width: 690px;
+  max-width: 860px;
+  margin-right: auto;
+
+  .bp3-input-group {
+    &.connection-name-input, &.username-input, &.password-input {
+      min-width: 320px;
+      width: auto;
+      max-width: 460px;
+    }
+  }
+}
+
+.bp3-input-group {
+  &.invalid-field {
+    + label { font-weight: bold }
+    > input {
+      box-shadow: rgb(232, 28, 28) 0px 0px 0px 1px, rgba(232, 71, 28, 0.3) 0px 0px 0px 3px, rgba(16, 22, 26, 0.2) 0px 1px 1px 0px inset;
+      box-sizing: border-box;
+      background-color: #ffeeee;
+    }
+  }
 }
\ No newline at end of file