You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by mi...@apache.org on 2023/12/12 02:41:28 UTC

(incubator-devlake) branch refactor-6307 created (now 5b434062f)

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

mintsweet pushed a change to branch refactor-6307
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


      at 5b434062f refactor(config-ui): use new component block to replace form-item

This branch includes the following new commits:

     new 5b434062f refactor(config-ui): use new component block to replace form-item

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(incubator-devlake) 01/01: refactor(config-ui): use new component block to replace form-item

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5b434062f19604b07fb0adaf7acd76abe9897860
Author: mintsweet <0x...@gmail.com>
AuthorDate: Tue Dec 12 15:34:40 2023 +1300

    refactor(config-ui): use new component block to replace form-item
---
 .../src/components/{form/item => block}/index.tsx  | 43 ++++++++++++++++------
 .../src/components/{form/item => block}/styled.ts  |  0
 config-ui/src/components/form/index.ts             | 19 ----------
 config-ui/src/components/index.ts                  |  2 +-
 .../detail/components/sync-policy/index.tsx        | 26 ++++++-------
 .../src/pages/project/detail/settings-panel.tsx    | 10 ++---
 .../plugins/components/data-scope-select/index.tsx | 10 ++---
 .../plugins/components/scope-config-form/index.tsx | 18 ++++-----
 .../jira/transformation-fields/dev-panel.tsx       | 34 ++++++++---------
 .../register/webhook/components/create-dialog.tsx  | 18 ++++-----
 .../register/webhook/components/edit-dialog.tsx    |  6 +--
 .../webhook/components/selector-dialog.tsx         |  6 +--
 .../register/webhook/components/view-dialog.tsx    | 18 ++++-----
 .../register/zentao/connection-fields/db-url.tsx   |  6 +--
 config-ui/src/routes/api-keys/api-keys.tsx         | 18 ++++-----
 15 files changed, 117 insertions(+), 117 deletions(-)

diff --git a/config-ui/src/components/form/item/index.tsx b/config-ui/src/components/block/index.tsx
similarity index 63%
rename from config-ui/src/components/form/item/index.tsx
rename to config-ui/src/components/block/index.tsx
index c1c5002e8..e08f94abe 100644
--- a/config-ui/src/components/form/item/index.tsx
+++ b/config-ui/src/components/block/index.tsx
@@ -16,27 +16,46 @@
  *
  */
 
-import { FormGroup } from '@blueprintjs/core';
+import styled from 'styled-components';
 
-import * as S from './styled';
+const Wrapper = styled.div`
+  & > h4 {
+    display: flex;
+
+    & > i {
+      margin-left: 4px;
+      color: #ff8b8b;
+    }
+  }
+
+  & > p {
+    margin-top: 0;
+  }
+
+  & + & {
+    margin-top: 16px;
+  }
+`;
 
 interface Props {
   style?: React.CSSProperties;
-  label?: React.ReactNode;
-  subLabel?: React.ReactNode;
+  title?: React.ReactNode;
+  description?: React.ReactNode;
   required?: boolean;
   children: React.ReactNode;
 }
 
-export const FormItem = ({ style, label, subLabel, required, children }: Props) => {
+export const Block = ({ style, title, description, required, children }: Props) => {
   return (
-    <FormGroup
-      style={style}
-      label={label ? <S.Label>{label}</S.Label> : null}
-      subLabel={subLabel ? <S.subLabel>{subLabel}</S.subLabel> : null}
-      labelInfo={required ? <S.LabelInfo>*</S.LabelInfo> : null}
-    >
+    <Wrapper style={style}>
+      {title && (
+        <h4>
+          {title}
+          {required && <i>*</i>}
+        </h4>
+      )}
+      {description && <p>{description}</p>}
       {children}
-    </FormGroup>
+    </Wrapper>
   );
 };
diff --git a/config-ui/src/components/form/item/styled.ts b/config-ui/src/components/block/styled.ts
similarity index 100%
rename from config-ui/src/components/form/item/styled.ts
rename to config-ui/src/components/block/styled.ts
diff --git a/config-ui/src/components/form/index.ts b/config-ui/src/components/form/index.ts
deleted file mode 100644
index 14ed54fe6..000000000
--- a/config-ui/src/components/form/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-export * from './item';
diff --git a/config-ui/src/components/index.ts b/config-ui/src/components/index.ts
index e1122ffb5..f1295f84d 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -17,8 +17,8 @@
  */
 
 export * from './action';
+export * from './block';
 export * from './buttons';
-export * from './form';
 export * from './inspector';
 export * from './loading';
 export * from './logo';
diff --git a/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx b/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
index 2c22754ff..7289aa928 100644
--- a/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
+++ b/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
@@ -22,7 +22,7 @@ import { Tag, Checkbox, FormGroup, InputGroup, Radio, RadioGroup } from '@bluepr
 import { TimePrecision } from '@blueprintjs/datetime';
 import { DateInput2 } from '@blueprintjs/datetime2';
 
-import { FormItem, ExternalLink } from '@/components';
+import { Block, ExternalLink } from '@/components';
 import { getCron, getCronOptions } from '@/config';
 import { formatTime } from '@/utils';
 
@@ -100,9 +100,9 @@ export const SyncPolicy = ({
         Your local time zone is <strong>UTC {timezone}</strong>. All time listed below is shown in your local time.
       </div>
       {showTimeFilter && (
-        <FormItem
-          label="Time Range"
-          subLabel="Select the time range for the data you wish to collect. DevLake will collect the last six months of data by default."
+        <Block
+          title="Time Range"
+          description="Select the time range for the data you wish to collect. DevLake will collect the last six months of data by default."
         >
           <div className="quick-selection">
             {quickTimeOpts.map((opt, i) => (
@@ -131,13 +131,13 @@ export const SyncPolicy = ({
             />
             <strong>to Now</strong>
           </div>
-        </FormItem>
+        </Block>
       )}
       <div className="cron">
-        <FormItem
+        <Block
           style={{ flex: '0 0 450px', marginRight: 20 }}
-          label="Sync Frequency"
-          subLabel="Blueprints will run on creation and recurringly based on the schedule."
+          title="Sync Frequency"
+          description="Blueprints will run on creation and recurringly based on the schedule."
         >
           <RadioGroup selectedValue={selectedValue} onChange={handleChangeFrequency}>
             {cronOpts.map(({ label, subLabel }) => (
@@ -185,8 +185,8 @@ export const SyncPolicy = ({
             <ExternalLink link="https://crontab.cronhub.io/">Learn how to use cron code</ExternalLink> or{' '}
             <ExternalLink link="https://cron-ai.vercel.app/">auto-convert English to cron code</ExternalLink>
           </div>
-        </FormItem>
-        <FormItem label="Next Three Runs:">
+        </Block>
+        <Block title="Next Three Runs:">
           {cron.nextTimes.length ? (
             <ul>
               {cron.nextTimes.map((it, i) => (
@@ -198,9 +198,9 @@ export const SyncPolicy = ({
           ) : (
             'N/A'
           )}
-        </FormItem>
+        </Block>
       </div>
-      <FormItem label="Running Policy">
+      <Block title="Running Policy">
         <Checkbox
           label="Skip failed tasks (Recommended when collecting a large volume of data, eg. 10+ GitHub repos, Jira boards, etc.)"
           checked={skipOnFail}
@@ -212,7 +212,7 @@ export const SyncPolicy = ({
           pipeline will continue to run, and the data collected by successful tasks will not be affected. After the
           pipeline is finished, you can rerun these failed tasks.
         </p>
-      </FormItem>
+      </Block>
     </S.Wrapper>
   );
 };
diff --git a/config-ui/src/pages/project/detail/settings-panel.tsx b/config-ui/src/pages/project/detail/settings-panel.tsx
index 8b9d0ca3e..0b42e7fe6 100644
--- a/config-ui/src/pages/project/detail/settings-panel.tsx
+++ b/config-ui/src/pages/project/detail/settings-panel.tsx
@@ -22,7 +22,7 @@ import { Card, Modal, message } from 'antd';
 import { InputGroup, Checkbox, Button, Icon, Intent } from '@blueprintjs/core';
 
 import API from '@/api';
-import { FormItem, Buttons } from '@/components';
+import { Block, Buttons } from '@/components';
 import { IProject } from '@/types';
 import { operator } from '@/utils';
 
@@ -102,16 +102,16 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
   return (
     <>
       <Card>
-        <FormItem label="Project Name" subLabel="Edit your project name with letters, numbers, -, _ or /" required>
+        <Block title="Project Name" description="Edit your project name with letters, numbers, -, _ or /" required>
           <InputGroup style={{ width: 386 }} value={name} onChange={(e) => setName(e.target.value)} />
-        </FormItem>
-        <FormItem subLabel="DORA metrics are four widely-adopted metrics for measuring software delivery performance.">
+        </Block>
+        <Block description="DORA metrics are four widely-adopted metrics for measuring software delivery performance.">
           <Checkbox
             label="Enable DORA Metrics"
             checked={enableDora}
             onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
           />
-        </FormItem>
+        </Block>
         <Buttons position="bottom">
           <Button text="Save" loading={operating} disabled={!name} intent={Intent.PRIMARY} onClick={handleUpdate} />
         </Buttons>
diff --git a/config-ui/src/plugins/components/data-scope-select/index.tsx b/config-ui/src/plugins/components/data-scope-select/index.tsx
index 457cac229..e5e7777c9 100644
--- a/config-ui/src/plugins/components/data-scope-select/index.tsx
+++ b/config-ui/src/plugins/components/data-scope-select/index.tsx
@@ -23,7 +23,7 @@ import type { McsItem } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import API from '@/api';
-import { Loading, FormItem, ExternalLink, Message, Buttons, MultiSelector } from '@/components';
+import { Loading, Block, ExternalLink, Message, Buttons, MultiSelector } from '@/components';
 import { useRefreshData } from '@/hooks';
 import { getPluginScopeId } from '@/plugins';
 
@@ -92,9 +92,9 @@ export const DataScopeSelect = ({
   const handleSubmit = () => onSubmit?.(selectedIds);
 
   return (
-    <FormItem
-      label="Select Data Scope"
-      subLabel={
+    <Block
+      title="Select Data Scope"
+      description={
         items.length ? (
           <>
             Select the data scope in this Connection that you wish to associate with this Project. If you wish to add
@@ -168,6 +168,6 @@ export const DataScopeSelect = ({
           </ExternalLink>
         </S.Wrapper>
       )}
-    </FormItem>
+    </Block>
   );
 };
diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx
index 0542ff451..2f79bd813 100644
--- a/config-ui/src/plugins/components/scope-config-form/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-form/index.tsx
@@ -22,7 +22,7 @@ import { Form, Card, Alert, Divider } from 'antd';
 import { InputGroup, Button, Intent } from '@blueprintjs/core';
 
 import API from '@/api';
-import { ExternalLink, FormItem, MultiSelector, Message, Buttons } from '@/components';
+import { ExternalLink, Block, MultiSelector, Message, Buttons } from '@/components';
 import { transformEntities, EntitiesLabel } from '@/config';
 import { getPluginConfig } from '@/plugins';
 import { GitHubTransformation } from '@/plugins/register/github';
@@ -128,18 +128,18 @@ export const ScopeConfigForm = ({
       {step === 1 && (
         <>
           <Card>
-            <FormItem
-              label="Scope Config Name"
-              subLabel="Give this Scope Config a unique name so that you can identify it in the future."
+            <Block
+              title="Scope Config Name"
+              description="Give this Scope Config a unique name so that you can identify it in the future."
               required
             >
               <InputGroup placeholder="My Scope Config 1" value={name} onChange={(e) => setName(e.target.value)} />
-            </FormItem>
+            </Block>
           </Card>
           <Card>
-            <FormItem
-              label="Data Entities"
-              subLabel={
+            <Block
+              title="Data Entities"
+              description={
                 <>
                   Select the data entities you wish to collect for the Data Scope.
                   <ExternalLink link="">Learn about data entities</ExternalLink>
@@ -154,7 +154,7 @@ export const ScopeConfigForm = ({
                 selectedItems={entities.map((it) => ({ label: EntitiesLabel[it], value: it }))}
                 onChangeItems={(its) => setEntities(its.map((it) => it.value))}
               />
-            </FormItem>
+            </Block>
             {showWarning && (
               <Message
                 content="Please note: if you edit Data Entities and expect to see the Dashboards updated, you will need to visit
diff --git a/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx b/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx
index 58e9371b8..53f3867f2 100644
--- a/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx
+++ b/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx
@@ -21,7 +21,7 @@ import { Modal, message } from 'antd';
 import { InputGroup, Button, RadioGroup, Radio, Icon, Collapse } from '@blueprintjs/core';
 
 import API from '@/api';
-import { FormItem } from '@/components';
+import { Block } from '@/components';
 import JiraIssueTipsImg from '@/images/jira-issue-tips.png';
 import { operator } from '@/utils';
 
@@ -143,9 +143,9 @@ export const DevPanel = ({ connectionId, transformation, setTransformation, open
       <S.DialogBody>
         {step === 1 && (
           <>
-            <FormItem
-              label="Jira Issue Key"
-              subLabel="Input any issue key that has connected commit(s) in the development panel"
+            <Block
+              title="Jira Issue Key"
+              description="Input any issue key that has connected commit(s) in the development panel"
               required
             >
               <div className="search">
@@ -156,9 +156,9 @@ export const DevPanel = ({ connectionId, transformation, setTransformation, open
                 />
                 <Button loading={searching} disabled={!issueKey} text="See Results" onClick={handleSearch} />
               </div>
-            </FormItem>
+            </Block>
             {applicationTypes.length > 0 && (
-              <FormItem label="Application Type" subLabel="Please choose an application type." required>
+              <Block title="Application Type" description="Please choose an application type." required>
                 <RadioGroup
                   selectedValue={applicationType}
                   onChange={(e) => setApplicationType((e.target as HTMLInputElement).value)}
@@ -167,24 +167,24 @@ export const DevPanel = ({ connectionId, transformation, setTransformation, open
                     <Radio key={at} value={at} label={at} />
                   ))}
                 </RadioGroup>
-              </FormItem>
+              </Block>
             )}
           </>
         )}
         {step === 2 && (
           <>
-            <FormItem label="Jira Issue Key">{issueKey}</FormItem>
-            <FormItem label="Application Type">{applicationType}</FormItem>
-            <FormItem label="Commit Url Preview" subLabel="The latest five commit(s) associated with the issue.">
+            <Block title="Jira Issue Key">{issueKey}</Block>
+            <Block title="Application Type">{applicationType}</Block>
+            <Block title="Commit Url Preview" description="The latest five commit(s) associated with the issue.">
               <ul>
                 {devPanelCommits.map((commit) => (
                   <li key={commit}>{commit}</li>
                 ))}
               </ul>
-            </FormItem>
-            <FormItem
-              label="Commit Pattern"
-              subLabel={
+            </Block>
+            <Block
+              title="Commit Pattern"
+              description={
                 <>
                   <p style={{ display: 'flex', alignItems: 'center' }} onClick={() => setShowTip(!showTip)}>
                     Input pattern(s) to match and parse commits and repo identifiers from above commit URLs. See
@@ -202,12 +202,12 @@ export const DevPanel = ({ connectionId, transformation, setTransformation, open
                 value={pattern}
                 onChange={(e) => setPattern(e.target.value)}
               />
-            </FormItem>
-            <FormItem label="Configuration Results Preview">
+            </Block>
+            <Block title="Configuration Results Preview">
               <code>
                 <pre>{JSON.stringify(preview, null, '  ')}</pre>
               </code>
-            </FormItem>
+            </Block>
           </>
         )}
       </S.DialogBody>
diff --git a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
index c119150aa..8b39923ae 100644
--- a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
@@ -21,7 +21,7 @@ import { Modal } from 'antd';
 import { InputGroup, Icon } from '@blueprintjs/core';
 
 import { useAppDispatch } from '@/app/hook';
-import { FormItem, CopyText, ExternalLink } from '@/components';
+import { Block, CopyText, ExternalLink } from '@/components';
 import { addWebhook } from '@/features';
 import { operator } from '@/utils';
 
@@ -117,13 +117,13 @@ export const CreateDialog = ({ open, onCancel, onSubmitAfter }: Props) => {
     >
       {step === 1 && (
         <S.Wrapper>
-          <FormItem
-            label="Webhook Name"
-            subLabel="Give your Webhook a unique name to help you identify it in the future."
+          <Block
+            title="Webhook Name"
+            description="Give your Webhook a unique name to help you identify it in the future."
             required
           >
             <InputGroup placeholder="Webhook Name" value={name} onChange={(e) => setName(e.target.value)} />
-          </FormItem>
+          </Block>
         </S.Wrapper>
       )}
       {step === 2 && (
@@ -136,7 +136,7 @@ export const CreateDialog = ({ open, onCancel, onSubmitAfter }: Props) => {
             A non-expired API key is automatically generated for the authentication of the webhook. This key will only
             show now. You can revoke it in the webhook page at any time.
           </p>
-          <FormItem label="Incident">
+          <Block title="Incident">
             <h5>Post to register/update an incident</h5>
             <CopyText content={record.postIssuesEndpoint} />
             <p>
@@ -155,8 +155,8 @@ export const CreateDialog = ({ open, onCancel, onSubmitAfter }: Props) => {
               </ExternalLink>
               .
             </p>
-          </FormItem>
-          <FormItem label="Deployments">
+          </Block>
+          <Block title="Deployments">
             <h5>Post to register a deployment</h5>
             <CopyText content={record.postDeploymentsCurl} />
             <p>
@@ -166,7 +166,7 @@ export const CreateDialog = ({ open, onCancel, onSubmitAfter }: Props) => {
               </ExternalLink>
               .
             </p>
-          </FormItem>
+          </Block>
         </S.Wrapper>
       )}
     </Modal>
diff --git a/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx b/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
index b5bb79f67..91d441610 100644
--- a/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
@@ -21,7 +21,7 @@ import { Modal } from 'antd';
 import { InputGroup } from '@blueprintjs/core';
 
 import { useAppDispatch, useAppSelector } from '@/app/hook';
-import { FormItem } from '@/components';
+import { Block } from '@/components';
 import { updateWebhook, selectWebhook } from '@/features';
 import { operator } from '@/utils';
 
@@ -65,9 +65,9 @@ export const EditDialog = ({ initialId, onCancel }: Props) => {
       onCancel={onCancel}
       onOk={handleSubmit}
     >
-      <FormItem label="Name" required>
+      <Block title="Name" required>
         <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
-      </FormItem>
+      </Block>
     </Modal>
   );
 };
diff --git a/config-ui/src/plugins/register/webhook/components/selector-dialog.tsx b/config-ui/src/plugins/register/webhook/components/selector-dialog.tsx
index 28db3a076..ed242f246 100644
--- a/config-ui/src/plugins/register/webhook/components/selector-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/selector-dialog.tsx
@@ -21,7 +21,7 @@ import { Modal } from 'antd';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import { useAppSelector } from '@/app/hook';
-import { FormItem, Loading } from '@/components';
+import { Block, Loading } from '@/components';
 import { selectWebhooks } from '@/features';
 import { IWebhook } from '@/types';
 
@@ -56,7 +56,7 @@ export const SelectorDialog = ({ open, saving, onCancel, onSubmit }: Props) => {
       onOk={handleSubmit}
     >
       <S.Wrapper>
-        <FormItem label="Webhooks" subLabel="Select an existing Webhook to import to the current project.">
+        <Block title="Webhooks" description="Select an existing Webhook to import to the current project.">
           <MillerColumnsSelect
             columnCount={1}
             columnHeight={160}
@@ -71,7 +71,7 @@ export const SelectorDialog = ({ open, saving, onCancel, onSubmit }: Props) => {
             selectedIds={selectedIds}
             onSelectItemIds={setSelectedIds}
           />
-        </FormItem>
+        </Block>
       </S.Wrapper>
     </Modal>
   );
diff --git a/config-ui/src/plugins/register/webhook/components/view-dialog.tsx b/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
index 014499b2c..360868f0b 100644
--- a/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
@@ -21,7 +21,7 @@ import { Modal } from 'antd';
 import { Button, Intent } from '@blueprintjs/core';
 
 import { useAppDispatch, useAppSelector } from '@/app/hook';
-import { FormItem, CopyText, ExternalLink, Message } from '@/components';
+import { Block, CopyText, ExternalLink, Message } from '@/components';
 import { selectWebhook, renewWebhookApiKey } from '@/features';
 import { IWebhook } from '@/types';
 import { operator } from '@/utils';
@@ -92,7 +92,7 @@ export const ViewDialog = ({ initialId, onCancel }: Props) => {
           Copy the following CURL commands to your issue tracking or CI/CD tools to push `Incidents` and `Deployments`
           by making a POST to DevLake. Please replace the {'{'}API_KEY{'}'} in the following URLs.
         </p>
-        <FormItem label="Incident">
+        <Block title="Incident">
           <h5>Post to register/update an incident</h5>
           <CopyText content={URI.postIssuesEndpoint} />
           <p>
@@ -111,8 +111,8 @@ export const ViewDialog = ({ initialId, onCancel }: Props) => {
             </ExternalLink>
             .
           </p>
-        </FormItem>
-        <FormItem label="Deployments">
+        </Block>
+        <Block title="Deployments">
           <h5>Post to register a deployment</h5>
           <CopyText content={URI.postDeploymentsCurl} />
           <p>
@@ -122,10 +122,10 @@ export const ViewDialog = ({ initialId, onCancel }: Props) => {
             </ExternalLink>
             .
           </p>
-        </FormItem>
-        <FormItem
-          label="API Key"
-          subLabel="If you have forgotten your API key, you can revoke the previous key and generate a new one as a replacement."
+        </Block>
+        <Block
+          title="API Key"
+          description="If you have forgotten your API key, you can revoke the previous key and generate a new one as a replacement."
         >
           {!apiKey ? (
             <Button intent={Intent.PRIMARY} text="Revoke and generate a new key" onClick={() => setOpen(true)} />
@@ -140,7 +140,7 @@ export const ViewDialog = ({ initialId, onCancel }: Props) => {
               </S.Tips>
             </>
           )}
-        </FormItem>
+        </Block>
       </S.Wrapper>
       <Modal
         open={open}
diff --git a/config-ui/src/plugins/register/zentao/connection-fields/db-url.tsx b/config-ui/src/plugins/register/zentao/connection-fields/db-url.tsx
index bee065687..c1e4195d4 100644
--- a/config-ui/src/plugins/register/zentao/connection-fields/db-url.tsx
+++ b/config-ui/src/plugins/register/zentao/connection-fields/db-url.tsx
@@ -19,7 +19,7 @@
 import { useEffect } from 'react';
 import { InputGroup } from '@blueprintjs/core';
 
-import { FormItem } from '@/components';
+import { Block } from '@/components';
 
 interface Props {
   initialValue: string;
@@ -37,12 +37,12 @@ export const DBUrl = ({ initialValue, value, setValue }: Props) => {
   };
 
   return (
-    <FormItem label="Database URL" subLabel="Provide the DB URL of Zentao if you want to collect issue changelogs.">
+    <Block title="Database URL" description="Provide the DB URL of Zentao if you want to collect issue changelogs.">
       <InputGroup
         placeholder="e.g. mysql://root:devlake@sshd-proxy:3306/zentao?charset=utf8mb4&parseTime=True"
         value={value}
         onChange={handleChange}
       />
-    </FormItem>
+    </Block>
   );
 };
diff --git a/config-ui/src/routes/api-keys/api-keys.tsx b/config-ui/src/routes/api-keys/api-keys.tsx
index ec540942d..2d7f51fcf 100644
--- a/config-ui/src/routes/api-keys/api-keys.tsx
+++ b/config-ui/src/routes/api-keys/api-keys.tsx
@@ -22,7 +22,7 @@ import { Button, Tag, Intent, InputGroup } from '@blueprintjs/core';
 import dayjs from 'dayjs';
 
 import API from '@/api';
-import { PageHeader, FormItem, Selector, ExternalLink, CopyText, Message } from '@/components';
+import { PageHeader, Block, Selector, ExternalLink, CopyText, Message } from '@/components';
 import { useRefreshData } from '@/hooks';
 import { operator, formatTime } from '@/utils';
 
@@ -165,15 +165,15 @@ export const ApiKeys = () => {
           onCancel={handleCancel}
           onOk={handleSubmit}
         >
-          <FormItem label="API Key Name" subLabel="Give your API key a unique name to identify in the future." required>
+          <Block title="API Key Name" description="Give your API key a unique name to identify in the future." required>
             <InputGroup
               style={{ width: 386 }}
               placeholder="My API Key"
               value={form.name}
               onChange={(e) => setForm({ ...form, name: e.target.value })}
             />
-          </FormItem>
-          <FormItem label="Expiration" subLabel="Set an expiration time for your API key." required>
+          </Block>
+          <Block title="Expiration" description="Set an expiration time for your API key." required>
             <div style={{ width: 386 }}>
               <Selector
                 items={C.timeOptions}
@@ -183,10 +183,10 @@ export const ApiKeys = () => {
                 onChangeItem={(it) => setForm({ ...form, expiredAt: it.value ? it.value : undefined })}
               />
             </div>
-          </FormItem>
-          <FormItem
-            label="Allowed Path"
-            subLabel={
+          </Block>
+          <Block
+            title="Allowed Path"
+            description={
               <p>
                 Enter a Regular Expression that matches the API URL(s) from the{' '}
                 <ExternalLink link="/api/swagger/index.html">DevLake API docs</ExternalLink>. The default Regular
@@ -203,7 +203,7 @@ export const ApiKeys = () => {
                 onChange={(e) => setForm({ ...form, allowedPath: e.target.value })}
               />
             </S.InputContainer>
-          </FormItem>
+          </Block>
         </Modal>
       )}
       {modal === 'show' && (