You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/02/13 09:49:39 UTC

[incubator-devlake] branch main updated: refactor(config-ui): adjust the connection-form (#4402)

This is an automated email from the ASF dual-hosted git repository.

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new ea1d30e47 refactor(config-ui): adjust the connection-form (#4402)
ea1d30e47 is described below

commit ea1d30e47ea656ae6f31c79a4a17413ff15851d3
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon Feb 13 17:49:35 2023 +0800

    refactor(config-ui): adjust the connection-form (#4402)
---
 .../components/connection-form/fields/endpoint.tsx | 33 ++++++++--
 .../components/connection-form/fields/index.tsx    | 47 ++++++++++----
 .../components/connection-form/fields/name.tsx     | 19 ++++--
 .../components/connection-form/fields/password.tsx | 13 ++--
 .../components/connection-form/fields/proxy.tsx    | 13 ++--
 .../connection-form/fields/rate-limit.tsx          | 23 +++++--
 .../components/connection-form/fields/token.tsx    | 13 ++--
 .../components/connection-form/fields/username.tsx | 13 ++--
 .../plugins/components/connection-form/index.tsx   | 17 +++---
 .../components/connection-form/operate/save.tsx    | 12 ++--
 .../components/connection-form/operate/test.tsx    | 14 +++--
 config-ui/src/plugins/register/github/config.tsx   | 20 +++++-
 .../register/github/connection-fields/graphql.tsx  | 21 ++++---
 .../register/github/connection-fields/token.tsx    | 37 +++++++----
 config-ui/src/plugins/register/jira/config.tsx     | 35 ++++++++++-
 .../register/jira/connection-fields/auth.tsx       | 71 ++++++++++++----------
 16 files changed, 280 insertions(+), 121 deletions(-)

diff --git a/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
index 68f509da0..fb3326de4 100644
--- a/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
@@ -33,7 +33,7 @@
  *
  */
 
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
 import { FormGroup, RadioGroup, Radio, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
@@ -45,28 +45,49 @@ interface Props {
   disabled?: boolean;
   name: string;
   multipleVersions?: Record<VersionType, string>;
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  error: string;
+  setValue: (value: string) => void;
+  setError: (error: string) => void;
 }
 
-export const ConnectionEndpoint = ({ subLabel, disabled = false, name, multipleVersions, value, onChange }: Props) => {
+export const ConnectionEndpoint = ({
+  subLabel,
+  disabled = false,
+  name,
+  multipleVersions,
+  initialValue,
+  value,
+  error,
+  setValue,
+  setError,
+}: Props) => {
   const [version, setVersion] = useState<VersionType>('cloud');
 
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
+  useEffect(() => {
+    setError(value ? '' : 'endpoint is required');
+  }, [value]);
+
   const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
     const version = (e.target as HTMLInputElement).value as VersionType;
     if (version === 'cloud') {
-      onChange(multipleVersions?.cloud ?? '');
+      setValue(multipleVersions?.cloud ?? '');
     }
 
     if (version === 'server') {
-      onChange('');
+      setValue('');
     }
 
     setVersion(version);
   };
 
   const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   if (multipleVersions) {
diff --git a/config-ui/src/plugins/components/connection-form/fields/index.tsx b/config-ui/src/plugins/components/connection-form/fields/index.tsx
index a1cbc4b5d..0cad52dba 100644
--- a/config-ui/src/plugins/components/connection-form/fields/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/index.tsx
@@ -29,17 +29,27 @@ import { ConnectionRateLimit } from './rate-limit';
 interface Props {
   name: string;
   fields: any[];
+  initialValues: any;
   values: any;
+  errors: any;
   setValues: (values: any) => void;
-  error: any;
-  setError: (error: any) => void;
+  setErrors: (errors: any) => void;
 }
 
-export const Form = ({ name, fields, values, setValues, error, setError }: Props) => {
+export const Form = ({ name, fields, initialValues, values, errors, setValues, setErrors }: Props) => {
+  const onValues = (values: any) => setValues((prev: any) => ({ ...prev, ...values }));
+  const onErrors = (values: any) => setErrors((prev: any) => ({ ...prev, ...values }));
+
   const generateForm = () => {
     return fields.map((field) => {
       if (typeof field === 'function') {
-        return field({ values, setValues, error, setError });
+        return field({
+          initialValues,
+          values,
+          setValues: onValues,
+          errors,
+          setErrors: onErrors,
+        });
       }
 
       const key = typeof field === 'string' ? field : field.key;
@@ -47,7 +57,14 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
       switch (key) {
         case 'name':
           return (
-            <ConnectionName key={key} value={values.name ?? ''} onChange={(name) => setValues({ ...values, name })} />
+            <ConnectionName
+              key={key}
+              initialValue={initialValues.name ?? ''}
+              value={values.name ?? ''}
+              error={errors.name ?? ''}
+              setValue={(value) => onValues({ name: value })}
+              setError={(value) => onErrors({ name: value })}
+            />
           );
         case 'endpoint':
           return (
@@ -55,16 +72,20 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
               {...field}
               key={key}
               name={name}
+              initialValue={initialValues.endpoint ?? ''}
               value={values.endpoint ?? ''}
-              onChange={(endpoint) => setValues({ ...values, endpoint })}
+              error={errors.endpoint ?? ''}
+              setValue={(value) => onValues({ endpoint: value })}
+              setError={(value) => onErrors({ endpoint: value })}
             />
           );
         case 'username':
           return (
             <ConnectionUsername
               key={key}
+              initialValue={initialValues.username ?? ''}
               value={values.username ?? ''}
-              onChange={(username) => setValues({ ...values, username })}
+              setValue={(value) => onValues({ username: value })}
             />
           );
         case 'password':
@@ -72,8 +93,9 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
             <ConnectionPassword
               {...field}
               key={key}
+              initialValue={initialValues.password ?? ''}
               value={values.password ?? ''}
-              onChange={(password) => setValues({ ...values, password })}
+              setValue={(value) => onValues({ password: value })}
             />
           );
         case 'token':
@@ -81,8 +103,9 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
             <ConnectionToken
               {...field}
               key={key}
+              initialValue={initialValues.token ?? ''}
               value={values.token ?? ''}
-              onChange={(token) => setValues({ ...values, token })}
+              setValue={(value) => onValues({ token: value })}
             />
           );
         case 'proxy':
@@ -90,8 +113,9 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
             <ConnectionProxy
               key={key}
               name={name}
+              initialValue={initialValues.proxy ?? ''}
               value={values.proxy ?? ''}
-              onChange={(proxy) => setValues({ ...values, proxy })}
+              setValue={(value) => onValues({ proxy: value })}
             />
           );
         case 'rateLimitPerHour':
@@ -99,8 +123,9 @@ export const Form = ({ name, fields, values, setValues, error, setError }: Props
             <ConnectionRateLimit
               {...field}
               key={key}
+              initialValue={initialValues.rateLimitPerHour ?? 0}
               value={values.rateLimitPerHour}
-              onChange={(rateLimitPerHour) => setValues({ ...values, rateLimitPerHour })}
+              setValue={(value) => onValues({ rateLimitPerHour: value })}
             />
           );
         default:
diff --git a/config-ui/src/plugins/components/connection-form/fields/name.tsx b/config-ui/src/plugins/components/connection-form/fields/name.tsx
index f207a2601..d8d970cfc 100644
--- a/config-ui/src/plugins/components/connection-form/fields/name.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/name.tsx
@@ -16,19 +16,30 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
 
 interface Props {
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  error: string;
+  setValue: (value: string) => void;
+  setError: (error: string) => void;
 }
 
-export const ConnectionName = ({ value, onChange }: Props) => {
+export const ConnectionName = ({ initialValue, value, setValue, setError }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
+  useEffect(() => {
+    setError(value ? '' : 'name is required');
+  }, [value]);
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/fields/password.tsx b/config-ui/src/plugins/components/connection-form/fields/password.tsx
index 5af8dfe11..35d9eea3c 100644
--- a/config-ui/src/plugins/components/connection-form/fields/password.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/password.tsx
@@ -33,20 +33,25 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
 
 interface Props {
   label?: string;
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  setValue: (value: string) => void;
 }
 
-export const ConnectionPassword = ({ label, value, onChange }: Props) => {
+export const ConnectionPassword = ({ label, initialValue, value, setValue }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/fields/proxy.tsx b/config-ui/src/plugins/components/connection-form/fields/proxy.tsx
index e400491f3..868de21b5 100644
--- a/config-ui/src/plugins/components/connection-form/fields/proxy.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/proxy.tsx
@@ -33,20 +33,25 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
 
 interface Props {
   name: string;
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  setValue: (value: string) => void;
 }
 
-export const ConnectionProxy = ({ name, value, onChange }: Props) => {
+export const ConnectionProxy = ({ name, initialValue, value, setValue }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx b/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx
index 1e1b105b2..2606f02cd 100644
--- a/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx
@@ -45,13 +45,26 @@ interface Props {
   learnMore: string;
   externalInfo: string;
   defaultValue: number;
+  initialValue: number;
   value: number;
-  onChange: (value: number) => void;
+  setValue: (value: number) => void;
 }
 
-export const ConnectionRateLimit = ({ subLabel, learnMore, externalInfo, defaultValue, value, onChange }: Props) => {
+export const ConnectionRateLimit = ({
+  subLabel,
+  learnMore,
+  externalInfo,
+  defaultValue,
+  initialValue,
+  value,
+  setValue,
+}: Props) => {
   const [checked, setChecked] = useState(true);
 
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
   useEffect(() => {
     setChecked(value ? true : false);
   }, []);
@@ -59,15 +72,15 @@ export const ConnectionRateLimit = ({ subLabel, learnMore, externalInfo, default
   const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
     const checked = (e.target as HTMLInputElement).checked;
     if (checked) {
-      onChange(defaultValue);
+      setValue(defaultValue);
     } else {
-      onChange(0);
+      setValue(0);
     }
     setChecked(checked);
   };
 
   const handleChangeValue = (value: number) => {
-    onChange(value);
+    setValue(value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/fields/token.tsx b/config-ui/src/plugins/components/connection-form/fields/token.tsx
index d3a1c808d..c9a0f4370 100644
--- a/config-ui/src/plugins/components/connection-form/fields/token.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/token.tsx
@@ -33,7 +33,7 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
@@ -41,13 +41,18 @@ import * as S from './styled';
 interface Props {
   label?: string;
   subLabel?: string;
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  setValue: (value: string) => void;
 }
 
-export const ConnectionToken = ({ label, subLabel, value, onChange }: Props) => {
+export const ConnectionToken = ({ label, subLabel, initialValue, value, setValue }: Props) => {
+  useEffect(() => {
+    setValue('');
+  }, [initialValue]);
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/fields/username.tsx b/config-ui/src/plugins/components/connection-form/fields/username.tsx
index 717b51f11..23ae9d21c 100644
--- a/config-ui/src/plugins/components/connection-form/fields/username.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/username.tsx
@@ -33,19 +33,24 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, InputGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
 
 interface Props {
+  initialValue: string;
   value: string;
-  onChange: (value: string) => void;
+  setValue: (value: string) => void;
 }
 
-export const ConnectionUsername = ({ value, onChange }: Props) => {
+export const ConnectionUsername = ({ initialValue, value, setValue }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
   const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.value);
+    setValue(e.target.value);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/index.tsx b/config-ui/src/plugins/components/connection-form/index.tsx
index acedfeafd..5a4a28c01 100644
--- a/config-ui/src/plugins/components/connection-form/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/index.tsx
@@ -35,8 +35,8 @@ interface Props {
 }
 
 export const ConnectionForm = ({ plugin, connectionId }: Props) => {
-  const [form, setForm] = useState<Record<string, any>>({});
-  const [error, setError] = useState<Record<string, any>>({});
+  const [values, setValues] = useState<Record<string, any>>({});
+  const [errors, setErrors] = useState<Record<string, any>>({});
 
   const {
     name,
@@ -65,14 +65,15 @@ export const ConnectionForm = ({ plugin, connectionId }: Props) => {
         <Form
           name={name}
           fields={fields}
-          values={{ ...form, ...initialValues, ...data }}
-          setValues={setForm}
-          error={error}
-          setError={setError}
+          initialValues={{ ...initialValues, ...data }}
+          values={values}
+          errors={errors}
+          setValues={setValues}
+          setErrors={setErrors}
         />
         <ButtonGroup className="btns">
-          <Test plugin={plugin} form={form} error={error} />
-          <Save plugin={plugin} connectionId={connectionId} form={form} error={error} />
+          <Test plugin={plugin} values={values} errors={errors} />
+          <Save plugin={plugin} connectionId={connectionId} values={values} errors={errors} />
         </ButtonGroup>
       </S.Form>
     </S.Wrapper>
diff --git a/config-ui/src/plugins/components/connection-form/operate/save.tsx b/config-ui/src/plugins/components/connection-form/operate/save.tsx
index 8a3a45afa..890d7e8ce 100644
--- a/config-ui/src/plugins/components/connection-form/operate/save.tsx
+++ b/config-ui/src/plugins/components/connection-form/operate/save.tsx
@@ -27,11 +27,11 @@ import * as API from '../api';
 interface Props {
   plugin: string;
   connectionId?: ID;
-  form: any;
-  error: any;
+  values: any;
+  errors: any;
 }
 
-export const Save = ({ plugin, connectionId, form, error }: Props) => {
+export const Save = ({ plugin, connectionId, values, errors }: Props) => {
   const history = useHistory();
 
   const { operating, onSubmit } = useOperator(
@@ -43,11 +43,11 @@ export const Save = ({ plugin, connectionId, form, error }: Props) => {
   );
 
   const disabled = useMemo(() => {
-    return Object.values(error).some((value) => value);
-  }, [error]);
+    return Object.values(errors).some((value) => value);
+  }, [errors]);
 
   const handleSubmit = () => {
-    onSubmit(form);
+    onSubmit(values);
   };
 
   return (
diff --git a/config-ui/src/plugins/components/connection-form/operate/test.tsx b/config-ui/src/plugins/components/connection-form/operate/test.tsx
index 976fcb3fd..3e49831e0 100644
--- a/config-ui/src/plugins/components/connection-form/operate/test.tsx
+++ b/config-ui/src/plugins/components/connection-form/operate/test.tsx
@@ -26,19 +26,21 @@ import * as API from '../api';
 
 interface Props {
   plugin: string;
-  form: any;
-  error: any;
+  values: any;
+  errors: any;
 }
 
-export const Test = ({ plugin, form, error }: Props) => {
+export const Test = ({ plugin, values, errors }: Props) => {
   const { operating, onSubmit } = useOperator((payload) => API.testConnection(plugin, payload));
 
   const disabled = useMemo(() => {
-    return Object.values(error).some((value) => value);
-  }, [error]);
+    return Object.values(errors).some((value) => value);
+  }, [errors]);
 
   const handleSubmit = () => {
-    onSubmit(pick(form, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy', 'authMethod']));
+    onSubmit(
+      pick(values, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy', 'authMethod']),
+    );
   };
 
   return <Button loading={operating} disabled={disabled} outlined text="Test Connection" onClick={handleSubmit} />;
diff --git a/config-ui/src/plugins/register/github/config.tsx b/config-ui/src/plugins/register/github/config.tsx
index d3298d205..13c10fa3c 100644
--- a/config-ui/src/plugins/register/github/config.tsx
+++ b/config-ui/src/plugins/register/github/config.tsx
@@ -45,9 +45,25 @@ export const GitHubConfig: PluginConfigType = {
           server: '',
         },
       },
-      (props: any) => <Token key="token" {...props} />,
+      ({ initialValues, values, errors, setValues, setErrors }: any) => (
+        <Token
+          key="token"
+          initialValue={initialValues.token ?? ''}
+          value={values.token ?? ''}
+          error={errors.token ?? ''}
+          setValue={(value) => setValues({ token: value })}
+          setError={(value) => setErrors({ token: value })}
+        />
+      ),
       'proxy',
-      (props: any) => <Graphql key="graphql" {...props} />,
+      ({ initialValues, values, setValues }: any) => (
+        <Graphql
+          key="graphql"
+          initialValue={initialValues.enableGraphql ?? false}
+          value={values.enableGraphql ?? false}
+          setValue={(value) => setValues({ enableGraphql: value })}
+        />
+      ),
       {
         key: 'rateLimitPerHour',
         subLabel:
diff --git a/config-ui/src/plugins/register/github/connection-fields/graphql.tsx b/config-ui/src/plugins/register/github/connection-fields/graphql.tsx
index 24ebecba5..26fc16be8 100644
--- a/config-ui/src/plugins/register/github/connection-fields/graphql.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/graphql.tsx
@@ -16,23 +16,24 @@
  *
  */
 
-import React from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, Switch } from '@blueprintjs/core';
 
 import * as S from './styled';
 
 interface Props {
-  values: any;
-  setValues: any;
+  initialValue: boolean;
+  value: boolean;
+  setValue: (value: boolean) => void;
 }
 
-export const Graphql = ({ values, setValues }: Props) => {
+export const Graphql = ({ initialValue, value, setValue }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
+
   const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
-    const enableGraphql = (e.target as HTMLInputElement).checked;
-    setValues({
-      ...values,
-      enableGraphql,
-    });
+    setValue((e.target as HTMLInputElement).checked);
   };
 
   return (
@@ -44,7 +45,7 @@ export const Graphql = ({ values, setValues }: Props) => {
         </S.LabelDescription>
       }
     >
-      <Switch checked={values.enableGraphql ?? false} onChange={handleChange} />
+      <Switch checked={value} onChange={handleChange} />
     </FormGroup>
   );
 };
diff --git a/config-ui/src/plugins/register/github/connection-fields/token.tsx b/config-ui/src/plugins/register/github/connection-fields/token.tsx
index 4b5b8bfb5..27e7fd0a0 100644
--- a/config-ui/src/plugins/register/github/connection-fields/token.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/token.tsx
@@ -33,19 +33,30 @@ type TokenItem = {
 };
 
 interface Props {
-  values: any;
-  setValues: any;
-  error: any;
-  setError: any;
+  endpoint?: string;
+  proxy?: string;
+  initialValue: string;
+  value: string;
+  error: string;
+  setValue: (value: string) => void;
+  setError: (error: string) => void;
 }
 
-export const Token = ({ values, setValues, error, setError }: Props) => {
+export const Token = ({ endpoint, proxy, initialValue, value, error, setValue, setError }: Props) => {
   const [tokens, setTokens] = useState<TokenItem[]>([{ value: '', status: 'idle' }]);
 
   const testToken = async (token: string): Promise<TokenItem> => {
+    if (!endpoint) {
+      return {
+        value: token,
+        status: 'invalid',
+      };
+    }
+
     try {
       const res = await API.testConnection({
-        ...pick(values, ['endpoint', 'proxy']),
+        endpoint,
+        proxy,
         token,
       });
       return {
@@ -67,15 +78,15 @@ export const Token = ({ values, setValues, error, setError }: Props) => {
   };
 
   useEffect(() => {
-    if (values.token) {
-      checkTokens(values.token);
-    }
-  }, []);
+    checkTokens(initialValue);
+  }, [initialValue]);
+
+  useEffect(() => {
+    setError(value ? '' : 'token is required');
+  }, [value]);
 
   useEffect(() => {
-    const token = tokens.map((it) => it.value).join(',');
-    setValues({ ...values, token });
-    setError({ ...error, token: tokens.every((it) => it.value && it.status === 'valid') ? '' : 'error' });
+    setValue(tokens.map((it) => it.value).join(','));
   }, [tokens]);
 
   const handleCreateToken = () => setTokens([...tokens, { value: '', status: 'idle' }]);
diff --git a/config-ui/src/plugins/register/jira/config.tsx b/config-ui/src/plugins/register/jira/config.tsx
index 0eb528b48..b62651a9e 100644
--- a/config-ui/src/plugins/register/jira/config.tsx
+++ b/config-ui/src/plugins/register/jira/config.tsx
@@ -16,7 +16,7 @@
  *
  */
 
-import React from 'react';
+import React, { useMemo } from 'react';
 
 import type { PluginConfigType } from '@/plugins';
 import { PluginType } from '@/plugins';
@@ -39,7 +39,38 @@ export const JIRAConfig: PluginConfigType = {
         subLabel:
           'Provide the Jira instance API endpoint. For Jira Cloud, e.g. https://your-company.atlassian.net/rest/',
       },
-      (props: any) => <Auth {...props} />,
+      ({ initialValues, values, errors, setValues, setErrors }: any) => {
+        const initialValue = useMemo(
+          () => ({
+            authMethod: initialValues.authMethod ?? 'BasicAuth',
+            username: initialValues.username ?? '',
+            password: initialValues.password ?? '',
+            token: initialValues.token ?? '',
+          }),
+          [],
+        );
+
+        const value = useMemo(
+          () => ({
+            authMethod: values.authMethod ?? 'BasicAuth',
+            username: values.username ?? '',
+            password: values.password ?? '',
+            token: values.token ?? '',
+          }),
+          [values],
+        );
+
+        return (
+          <Auth
+            key="auth"
+            initialValue={initialValue}
+            value={value}
+            error={errors.auth}
+            setValue={(value) => setValues(value)}
+            setError={(value) => setErrors({ auth: value })}
+          />
+        );
+      },
       'proxy',
       {
         key: 'rateLimitPerHour',
diff --git a/config-ui/src/plugins/register/jira/connection-fields/auth.tsx b/config-ui/src/plugins/register/jira/connection-fields/auth.tsx
index 71d179ea7..3e3287e91 100644
--- a/config-ui/src/plugins/register/jira/connection-fields/auth.tsx
+++ b/config-ui/src/plugins/register/jira/connection-fields/auth.tsx
@@ -16,7 +16,7 @@
  *
  */
 
-import React, { useState } from 'react';
+import React, { useEffect } from 'react';
 import { FormGroup, RadioGroup, Radio, InputGroup } from '@blueprintjs/core';
 
 import { ExternalLink } from '@/components';
@@ -25,64 +25,71 @@ import * as S from './styled';
 
 type Method = 'BasicAuth' | 'AccessToken';
 
+type Value = {
+  authMethod: Method;
+  username: string;
+  password: string;
+  token: string;
+};
+
 interface Props {
-  values: any;
-  setValues: (value: any) => void;
+  initialValue: Value;
+  value: Value;
+  error: string;
+  setValue: (value: Value) => void;
+  setError: (value: string) => void;
 }
 
-export const Auth = ({ values, setValues }: Props) => {
-  const [method, setMethod] = useState<Method>('BasicAuth');
+export const Auth = ({ initialValue, value, error, setValue, setError }: Props) => {
+  useEffect(() => {
+    setValue(initialValue);
+  }, [initialValue]);
 
-  const handleChangeMethod = (e: React.FormEvent<HTMLInputElement>) => {
-    const m = (e.target as HTMLInputElement).value as Method;
+  useEffect(() => {
+    const required =
+      (value.authMethod === 'BasicAuth' && value.username && value.password) ||
+      (value.authMethod === 'AccessToken' && value.token);
+    setError(required ? '' : 'auth is required');
+  }, [value]);
 
-    setMethod(m);
-    setValues({
-      ...values,
-      authMethod: m,
-      username: m === 'BasicAuth' ? values.username : undefined,
-      password: m === 'BasicAuth' ? values.password : undefined,
-      token: m === 'AccessToken' ? values.token : undefined,
+  const handleChangeMethod = (e: React.FormEvent<HTMLInputElement>) => {
+    setValue({
+      ...value,
+      authMethod: (e.target as HTMLInputElement).value as Method,
     });
   };
 
   const handleChangeUsername = (e: React.ChangeEvent<HTMLInputElement>) => {
-    setValues({
-      ...values,
-      authMethod: 'BasicAuth',
+    setValue({
+      ...value,
       username: e.target.value,
     });
   };
 
   const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
-    setValues({
-      ...values,
-      authMethod: 'BasicAuth',
+    setValue({
+      ...value,
       password: e.target.value,
     });
   };
 
   const handleChangeToken = (e: React.ChangeEvent<HTMLInputElement>) => {
-    setValues({
-      ...values,
+    setValue({
+      ...value,
       token: e.target.value,
     });
   };
 
   return (
     <FormGroup label={<S.Label>Authentication Method</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
-      <RadioGroup inline selectedValue={method} onChange={handleChangeMethod}>
+      <RadioGroup inline selectedValue={value.authMethod} onChange={handleChangeMethod}>
         <Radio value="BasicAuth">Basic Authentication</Radio>
         <Radio value="AccessToken">Using Personal Access Token</Radio>
       </RadioGroup>
-      {method === 'BasicAuth' && (
+      {value.authMethod === 'BasicAuth' && (
         <>
           <FormGroup label={<S.Label>Username/e-mail</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
-            <InputGroup
-              placeholder="Your Username/e-mail"
-              value={values.username || ''}
-              onChange={handleChangeUsername}
-            />
+            <InputGroup placeholder="Your Username/e-mail" value={value.username} onChange={handleChangeUsername} />
           </FormGroup>
           <FormGroup
             label={<S.Label>Password</S.Label>}
@@ -100,13 +107,13 @@ export const Auth = ({ values, setValues }: Props) => {
             <InputGroup
               type="password"
               placeholder="Your Token/Password"
-              value={values.password || ''}
+              value={value.password}
               onChange={handleChangePassword}
             />
           </FormGroup>
         </>
       )}
-      {method === 'AccessToken' && (
+      {value.authMethod === 'AccessToken' && (
         <FormGroup
           label={<S.Label>Personal Access Token</S.Label>}
           labelInfo={<S.LabelInfo>*</S.LabelInfo>}
@@ -118,7 +125,7 @@ export const Auth = ({ values, setValues }: Props) => {
             </S.LabelDescription>
           }
         >
-          <InputGroup type="password" placeholder="Your PAT" value={values.token || ''} onChange={handleChangeToken} />
+          <InputGroup type="password" placeholder="Your PAT" value={value.token} onChange={handleChangeToken} />
         </FormGroup>
       )}
     </FormGroup>