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

(incubator-devlake) branch main updated: refactor(config-ui): use new component block to replace form-item (#6614)

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

klesh 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 29806aeff refactor(config-ui): use new component block to replace form-item (#6614)
29806aeff is described below

commit 29806aeffb69825742c54e55bdae6ccaa9c5db6c
Author: 青湛 <0x...@gmail.com>
AuthorDate: Wed Dec 13 14:15:06 2023 +1300

    refactor(config-ui): use new component block to replace form-item (#6614)
---
 .../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' && (