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/05/29 02:07:19 UTC

[incubator-devlake] branch main updated: feat(config-ui): use new design for bp and project detail page (#5297)

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 376f0e873 feat(config-ui): use new design for bp and project detail page (#5297)
376f0e873 is described below

commit 376f0e8733f2eca4fe4395d5611300edf8e2dd42
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon May 29 10:07:14 2023 +0800

    feat(config-ui): use new design for bp and project detail page (#5297)
    
    * feat(config-ui): add new component form-item
    
    * refactor(config-ui): remove blueprint-connection-add page
    
    * feat(config-ui): add new component buttons
    
    * refactor(config-ui): use data-scope-select to replace data-scope and data-scope-form
    
    * refactor(config-ui): use new design for bp detail page
    
    * refactor(config-ui): use new design for project detail page
---
 config-ui/src/App.tsx                              |   8 -
 .../styled.ts => components/buttons/index.tsx}     |  22 ++-
 .../step-3.tsx => components/form-item/index.tsx}  |  32 ++--
 .../components => components/form-item}/styled.ts  |  16 +-
 config-ui/src/components/index.ts                  |   2 +
 config-ui/src/global.d.ts                          |   6 +-
 .../src/pages/blueprint/connection-add/api.ts      |  24 ---
 .../blueprint/connection-add/components/index.ts   |  21 ---
 .../blueprint/connection-add/components/step-1.tsx |  69 -------
 .../blueprint/connection-add/components/step-2.tsx |  44 -----
 .../src/pages/blueprint/connection-add/context.tsx | 137 --------------
 .../src/pages/blueprint/connection-add/index.tsx   |  62 ------
 .../pages/blueprint/connection-detail/index.tsx    |   9 +-
 config-ui/src/pages/blueprint/detail/api.ts        |   4 +-
 .../blueprint/detail/blueprint-detail-page.tsx     |  15 +-
 .../pages/blueprint/detail/blueprint-detail.tsx    |  83 +++++---
 .../components/add-connection-dialog/index.tsx     |  71 +++++++
 .../advanced-editor/example/customize.ts           |   0
 .../components/advanced-editor/example/dbt.ts      |   0
 .../components/advanced-editor/example/feishu.ts   |   0
 .../components/advanced-editor/example/general.ts  |   0
 .../advanced-editor/example/gitextractor.ts        |   0
 .../components/advanced-editor/example/github.ts   |   0
 .../components/advanced-editor/example/gitlab.ts   |   0
 .../components/advanced-editor/example/index.ts    |   0
 .../components/advanced-editor/example/jenkins.ts  |   0
 .../components/advanced-editor/example/jira.ts     |   0
 .../components/advanced-editor/example/refdiff.ts  |   0
 .../advanced-editor/example/starrocks.ts           |   0
 .../components/advanced-editor/example/tapd.ts     |   0
 .../components/advanced-editor/example/zentao.ts   |   0
 .../components/advanced-editor/index.tsx           |   0
 .../detail/components/connection-list/index.tsx    |  75 --------
 .../detail/components/connection-list/styled.ts    |  55 ------
 .../src/pages/blueprint/detail/components/index.ts |   3 +-
 .../{ => detail}/components/sync-policy/index.tsx  |   0
 .../components/sync-policy/start-from-selector.tsx |   0
 .../{ => detail}/components/sync-policy/styled.ts  |   0
 .../components/update-policy-dialog/index.tsx      |   7 +-
 .../pages/blueprint/detail/panel/configuration.tsx | 190 ++++++++++++-------
 .../src/pages/blueprint/detail/panel/status.tsx    |  28 ++-
 config-ui/src/pages/blueprint/detail/styled.ts     |  88 ++++-----
 config-ui/src/pages/blueprint/detail/use-detail.ts |  90 ---------
 config-ui/src/pages/blueprint/index.ts             |   1 -
 config-ui/src/pages/blueprint/types.ts             |   7 +-
 config-ui/src/pages/project/detail/api.ts          |  16 +-
 config-ui/src/pages/project/detail/index.tsx       |  39 ++--
 .../src/pages/project/detail/panel/blueprint.tsx   |  50 -----
 config-ui/src/pages/project/detail/panel/index.ts  |  21 ---
 .../src/pages/project/detail/panel/settings.tsx    |  67 -------
 .../src/pages/project/detail/settings-panel.tsx    |  95 ++++++++++
 config-ui/src/pages/project/detail/styled.ts       |  14 --
 config-ui/src/pages/project/detail/use-project.ts  | 162 ----------------
 .../incoming-webhooks.tsx => webhooks-panel.tsx}   |  91 +++++++--
 config-ui/src/pages/project/home/index.tsx         |   4 +-
 config-ui/src/pages/project/index.ts               |   2 +
 config-ui/src/pages/project/{detail => }/types.ts  |   6 +-
 config-ui/src/pages/project/utils.ts               |   2 +
 .../src/plugins/components/data-scope-form/api.ts  |  34 ----
 .../plugins/components/data-scope-form/index.tsx   | 210 ---------------------
 .../components/data-scope-select/api.ts}           |   6 +-
 .../plugins/components/data-scope-select/index.tsx |  96 ++++++++++
 .../styled.ts                                      |  14 +-
 .../src/plugins/components/data-scope/index.tsx    | 173 -----------------
 .../src/plugins/components/data-scope/styled.ts    |  63 -------
 config-ui/src/plugins/components/index.ts          |   3 +-
 .../plugins/components/transformation/index.tsx    |   6 +-
 67 files changed, 679 insertions(+), 1664 deletions(-)

diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index 3998e95c1..cc4b18aeb 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -28,7 +28,6 @@ import {
   ProjectDetailPage,
   BlueprintHomePage,
   BlueprintDetailPage,
-  BlueprintConnectioAddPage,
   BlueprintConnectionDetailPage,
 } from '@/pages';
 import { history } from '@/utils';
@@ -69,15 +68,8 @@ function App() {
                 <Route exact path="/connections/:plugin/:id" component={() => <ConnectionDetailPage />} />
                 <Route exact path="/projects" component={() => <ProjectHomePage />} />
                 <Route exact path="/projects/:pname" component={() => <ProjectDetailPage />} />
-                <Route
-                  exact
-                  path="/projects/:pname/:bid/connection-add"
-                  component={() => <BlueprintConnectioAddPage />}
-                />
-                <Route exact path="/projects/:pname/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
                 <Route exact path="/blueprints" component={() => <BlueprintHomePage />} />
                 <Route exact path="/blueprints/:id" component={() => <BlueprintDetailPage />} />
-                <Route exact path="/blueprints/:bid/connection-add" component={() => <BlueprintConnectioAddPage />} />
                 <Route exact path="/blueprints/:bid/:unique" component={() => <BlueprintConnectionDetailPage />} />
               </Switch>
             </BaseLayout>
diff --git a/config-ui/src/pages/blueprint/connection-add/components/styled.ts b/config-ui/src/components/buttons/index.tsx
similarity index 76%
copy from config-ui/src/pages/blueprint/connection-add/components/styled.ts
copy to config-ui/src/components/buttons/index.tsx
index 229370bff..8734aa563 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/styled.ts
+++ b/config-ui/src/components/buttons/index.tsx
@@ -18,13 +18,21 @@
 
 import styled from 'styled-components';
 
-export const Wrapper = styled.div`
-  margin-top: 36px;
-`;
-
-export const Action = styled.div`
+const Wrapper = styled.div`
   display: flex;
   align-items: center;
-  justify-content: space-between;
-  margin-top: 36px;
+  justify-content: end;
+  margin-top: 24px;
+
+  .bp4-button + .bp4-button {
+    margin-left: 8px;
+  }
 `;
+
+interface Props {
+  children: React.ReactNode;
+}
+
+export const Buttons = ({ children }: Props) => {
+  return <Wrapper>{children}</Wrapper>;
+};
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx b/config-ui/src/components/form-item/index.tsx
similarity index 64%
rename from config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
rename to config-ui/src/components/form-item/index.tsx
index d0c9aee5d..0ee800c6d 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/step-3.tsx
+++ b/config-ui/src/components/form-item/index.tsx
@@ -16,27 +16,25 @@
  *
  */
 
-import { Transformation } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
+import { FormGroup } from '@blueprintjs/core';
 
 import * as S from './styled';
 
-export const Step3 = () => {
-  const { connection, onPrev, saving, onSave } = useConnectionAdd();
-
-  if (!connection) {
-    return null;
-  }
+interface Props {
+  label?: React.ReactNode;
+  subLabel?: React.ReactNode;
+  required?: boolean;
+  children: React.ReactNode;
+}
 
+export const FormItem = ({ label, subLabel, required, children }: Props) => {
   return (
-    <S.Wrapper>
-      <Transformation
-        connections={[connection]}
-        submitBtnProps={{ text: 'Save', loading: saving }}
-        onCancel={onPrev}
-        onNext={onSave}
-      />
-    </S.Wrapper>
+    <FormGroup
+      label={<S.Label>{label}</S.Label>}
+      subLabel={<S.subLabel>{subLabel}</S.subLabel>}
+      labelInfo={required ? <S.LabelInfo>*</S.LabelInfo> : null}
+    >
+      {children}
+    </FormGroup>
   );
 };
diff --git a/config-ui/src/pages/blueprint/connection-add/components/styled.ts b/config-ui/src/components/form-item/styled.ts
similarity index 82%
rename from config-ui/src/pages/blueprint/connection-add/components/styled.ts
rename to config-ui/src/components/form-item/styled.ts
index 229370bff..291e5b3e9 100644
--- a/config-ui/src/pages/blueprint/connection-add/components/styled.ts
+++ b/config-ui/src/components/form-item/styled.ts
@@ -18,13 +18,15 @@
 
 import styled from 'styled-components';
 
-export const Wrapper = styled.div`
-  margin-top: 36px;
+export const Label = styled.label`
+  font-size: 16px;
+  font-weight: 600;
 `;
 
-export const Action = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 36px;
+export const LabelInfo = styled.i`
+  color: #ff8b8b;
+`;
+
+export const subLabel = styled.p`
+  margin: 0;
 `;
diff --git a/config-ui/src/components/index.ts b/config-ui/src/components/index.ts
index c5a8ae8e4..b7582018b 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -18,10 +18,12 @@
 
 export * from './action';
 export * from './alert';
+export * from './buttons';
 export * from './card';
 export * from './dialog';
 export * from './divider';
 export { default as ErrorBoundary } from './error-boundary';
+export * from './form-item';
 export * from './inspector';
 export * from './loading';
 export * from './logo';
diff --git a/config-ui/src/global.d.ts b/config-ui/src/global.d.ts
index fbfabde2f..a1c271815 100644
--- a/config-ui/src/global.d.ts
+++ b/config-ui/src/global.d.ts
@@ -24,11 +24,7 @@ type MixConnection = {
   connectionId: ID;
   name: string;
   icon: string;
-  scope: Array<{
-    id: string;
-    entities: string[];
-  }>;
-  origin: Array<any>;
+  scope: Array<any>;
   transformationType?: 'none' | 'for-connection' | 'for-scope';
 };
 
diff --git a/config-ui/src/pages/blueprint/connection-add/api.ts b/config-ui/src/pages/blueprint/connection-add/api.ts
deleted file mode 100644
index 0abf6526e..000000000
--- a/config-ui/src/pages/blueprint/connection-add/api.ts
+++ /dev/null
@@ -1,24 +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.
- *
- */
-
-import { request } from '@/utils';
-
-export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
-
-export const updateBlueprint = (id: ID, payload: any) =>
-  request(`/blueprints/${id}`, { method: 'patch', data: payload });
diff --git a/config-ui/src/pages/blueprint/connection-add/components/index.ts b/config-ui/src/pages/blueprint/connection-add/components/index.ts
deleted file mode 100644
index 6ffb0bdf8..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/index.ts
+++ /dev/null
@@ -1,21 +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 './step-1';
-export * from './step-2';
-export * from './step-3';
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx b/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx
deleted file mode 100644
index 3a1bb2263..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/step-1.tsx
+++ /dev/null
@@ -1,69 +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.
- *
- */
-
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Table } from '@/components';
-import { useConnections } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
-
-import * as S from './styled';
-
-export const Step1 = () => {
-  const { filter, connection, onChangeConnection, onCancel, onNext } = useConnectionAdd();
-  const { connections, onGet } = useConnections({ filter, filterBeta: true, filterPlugin: ['webhook'] });
-
-  return (
-    <S.Wrapper>
-      <Table
-        columns={[{ title: 'Data Connection', dataIndex: 'name', key: 'name' }]}
-        dataSource={connections}
-        rowSelection={{
-          rowKey: 'unique',
-          type: 'radio',
-          selectedRowKeys: connection?.unique ? [connection?.unique] : [],
-          onChange: (selectedRowKeys) => {
-            const unique = selectedRowKeys[0];
-            const connection = onGet(unique);
-            const config = getPluginConfig(connection.plugin);
-            onChangeConnection({
-              unique: connection.unique,
-              plugin: connection.plugin,
-              connectionId: connection.id,
-              name: connection.name,
-              icon: connection.icon,
-              scope: [],
-              origin: [],
-              transformationType: config.transformationType,
-            });
-          },
-        }}
-      />
-      <S.Action>
-        <Button outlined intent={Intent.PRIMARY} onClick={onCancel}>
-          Cancel
-        </Button>
-        <Button intent={Intent.PRIMARY} disabled={!connection} onClick={onNext}>
-          Next Step
-        </Button>
-      </S.Action>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx b/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
deleted file mode 100644
index 6675e4483..000000000
--- a/config-ui/src/pages/blueprint/connection-add/components/step-2.tsx
+++ /dev/null
@@ -1,44 +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.
- *
- */
-
-import { DataScope } from '@/plugins';
-
-import { useConnectionAdd } from '../context';
-
-import * as S from './styled';
-
-export const Step2 = () => {
-  const { connection, onChangeConnection, onPrev, onNext } = useConnectionAdd();
-
-  if (!connection) {
-    return null;
-  }
-
-  return (
-    <S.Wrapper>
-      <DataScope
-        connections={[connection]}
-        cancelBtnProps={{ text: 'Previous Step' }}
-        submitBtnProps={{ text: 'Next Step' }}
-        onCancel={onPrev}
-        onSubmit={(connections) => onChangeConnection(connections[0])}
-        onNext={onNext}
-      />
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/connection-add/context.tsx b/config-ui/src/pages/blueprint/connection-add/context.tsx
deleted file mode 100644
index 9c4ee3c45..000000000
--- a/config-ui/src/pages/blueprint/connection-add/context.tsx
+++ /dev/null
@@ -1,137 +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.
- *
- */
-
-import React, { useState, useContext } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { PageLoading } from '@/components';
-import { useRefreshData } from '@/hooks';
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-type ContextType = {
-  name: string;
-  step: number;
-  filter: string[];
-  connection?: MixConnection;
-  onChangeConnection: (connection: MixConnection) => void;
-
-  onPrev: () => void;
-  onNext: () => void;
-  saving: boolean;
-  onSave: () => void;
-  onCancel: () => void;
-};
-
-export const Context = React.createContext<ContextType>({
-  name: '',
-  step: 1,
-  filter: [],
-
-  onChangeConnection: () => {},
-  onPrev: () => {},
-  onNext: () => {},
-  saving: false,
-  onSave: () => {},
-  onCancel: () => {},
-});
-
-interface Props {
-  pname?: string;
-  id: string;
-  children: React.ReactNode;
-}
-
-export const ContextProvider = ({ pname, id, children }: Props) => {
-  const [step, setStep] = useState(1);
-  const [connection, setConnection] = useState<MixConnection>();
-  const [saving, setSaving] = useState(false);
-
-  const history = useHistory();
-
-  const { ready, data } = useRefreshData(() => API.getBlueprint(id), [id]);
-
-  const handlePrev = () => {
-    setStep(step - 1);
-  };
-
-  const handleNext = () => {
-    setStep(step + 1);
-  };
-
-  const handleSave = async () => {
-    if (!connection) return null;
-    const { plugin, connectionId, scope } = connection;
-    const payload = {
-      ...data,
-      settings: {
-        ...data.settings,
-        connections: [
-          ...data.settings.connections,
-          {
-            plugin,
-            connectionId,
-            scopes: scope.map((sc) => ({
-              id: `${sc.id}`,
-              entities: sc.entities,
-            })),
-          },
-        ],
-      },
-    };
-
-    const [success] = await operator(() => API.updateBlueprint(data.id, payload), {
-      setOperating: setSaving,
-    });
-
-    if (success) {
-      history.push(pname ? `/projects/${pname}` : `/blueprints/${id}`);
-      return;
-    }
-  };
-
-  const handleCancel = () => {
-    history.push(`/blueprints/${id}`);
-  };
-
-  if (!ready || !data) {
-    return <PageLoading />;
-  }
-
-  return (
-    <Context.Provider
-      value={{
-        name: data.name,
-        step,
-        filter: data.settings.connections.map((cs: any) => `${cs.plugin}-${cs.connectionId}`),
-        connection,
-        onChangeConnection: setConnection,
-        onPrev: handlePrev,
-        onNext: handleNext,
-        saving,
-        onSave: handleSave,
-        onCancel: handleCancel,
-      }}
-    >
-      {children}
-    </Context.Provider>
-  );
-};
-
-export const useConnectionAdd = () => useContext(Context);
diff --git a/config-ui/src/pages/blueprint/connection-add/index.tsx b/config-ui/src/pages/blueprint/connection-add/index.tsx
deleted file mode 100644
index 1ea739381..000000000
--- a/config-ui/src/pages/blueprint/connection-add/index.tsx
+++ /dev/null
@@ -1,62 +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.
- *
- */
-
-import { useParams } from 'react-router-dom';
-
-import { PageHeader, Workflow } from '@/components';
-
-import { Step1, Step2, Step3 } from './components';
-import { ContextProvider, Context } from './context';
-
-export const BlueprintConnectioAddPage = () => {
-  const { pname, bid } = useParams<{ pname?: string; bid: string }>();
-
-  return (
-    <ContextProvider pname={pname} id={bid}>
-      <Context.Consumer>
-        {({ name, step }) => (
-          <PageHeader
-            breadcrumbs={[
-              ...(pname
-                ? [
-                    {
-                      name: 'Projects',
-                      path: '/projects',
-                    },
-                    {
-                      name: pname,
-                      path: `/projects/${pname}`,
-                    },
-                  ]
-                : [{ name: name, path: `/blueprints/${bid}` }]),
-              { name: 'Add a New Connection', path: '' },
-            ]}
-          >
-            <Workflow
-              steps={['Select a Data Connection', 'Set Data Scope', 'Add Transformation (Optional)']}
-              activeStep={step}
-            />
-            {step === 1 && <Step1 />}
-            {step === 2 && <Step2 />}
-            {step === 3 && <Step3 />}
-          </PageHeader>
-        )}
-      </Context.Consumer>
-    </ContextProvider>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx b/config-ui/src/pages/blueprint/connection-detail/index.tsx
index 90e50deab..01929b4d6 100644
--- a/config-ui/src/pages/blueprint/connection-detail/index.tsx
+++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx
@@ -24,7 +24,7 @@ import { Popover2 } from '@blueprintjs/popover2';
 import { Dialog, PageHeader, PageLoading } from '@/components';
 import { EntitiesLabel } from '@/config';
 import { useRefreshData } from '@/hooks';
-import { DataScope, getPluginConfig, Transformation } from '@/plugins';
+import { DataScopeSelect, getPluginConfig, Transformation } from '@/plugins';
 
 import * as API from './api';
 import * as S from './styled';
@@ -169,12 +169,7 @@ export const BlueprintConnectionDetailPage = () => {
         style={{ width: 820 }}
         onCancel={handleHideDataScope}
       >
-        <DataScope
-          connections={[connection]}
-          onCancel={handleHideDataScope}
-          onSubmit={handleChangeDataScope}
-          onNext={handleHideDataScope}
-        />
+        <DataScopeSelect plugin={connection.plugin} connectionId={connection.connectionId} />
       </Dialog>
     </PageHeader>
   );
diff --git a/config-ui/src/pages/blueprint/detail/api.ts b/config-ui/src/pages/blueprint/detail/api.ts
index be4fb40e7..2fc8424eb 100644
--- a/config-ui/src/pages/blueprint/detail/api.ts
+++ b/config-ui/src/pages/blueprint/detail/api.ts
@@ -18,7 +18,9 @@
 
 import { request } from '@/utils';
 
-export const getBlueprint = (id: ID) => request(`/blueprints/${id}`);
+import type { BlueprintType } from '../types';
+
+export const getBlueprint = (id: ID): Promise<BlueprintType> => request(`/blueprints/${id}`);
 
 export const getBlueprintPipelines = (id: ID) => request(`/blueprints/${id}/pipelines`);
 
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
index 52044acef..997fa3e66 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail-page.tsx
@@ -18,30 +18,21 @@
 
 import { useParams } from 'react-router-dom';
 
-import { PageHeader, PageLoading } from '@/components';
+import { PageHeader } from '@/components';
 
-import { FromEnum } from '../types';
-
-import { useDetail } from './use-detail';
 import { BlueprintDetail } from './blueprint-detail';
 
 export const BlueprintDetailPage = () => {
   const { id } = useParams<{ id: string }>();
 
-  const { loading, blueprint } = useDetail({ id });
-
-  if (loading || !blueprint) {
-    return <PageLoading />;
-  }
-
   return (
     <PageHeader
       breadcrumbs={[
         { name: 'Blueprints', path: '/blueprints' },
-        { name: blueprint.name, path: `/blueprints/${blueprint.id}` },
+        // { name: blueprint.name, path: `/blueprints/${id}` },
       ]}
     >
-      <BlueprintDetail from={FromEnum.blueprint} id={blueprint.id} />
+      <BlueprintDetail id={id} />
     </PageHeader>
   );
 };
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
index 75d06b626..dc8faff31 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
@@ -16,59 +16,88 @@
  *
  */
 
-import { useState, useMemo } from 'react';
+import { useState } from 'react';
 import type { TabId } from '@blueprintjs/core';
-import { Tabs, Tab } from '@blueprintjs/core';
+import { Tabs, Tab, Switch } from '@blueprintjs/core';
 
 import { PageLoading } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { operator } from '@/utils';
 
-import { FromEnum } from '../types';
-
-import type { UseDetailProps } from './use-detail';
-import { useDetail } from './use-detail';
 import { Configuration } from './panel/configuration';
 import { Status } from './panel/status';
+import * as API from './api';
 import * as S from './styled';
 
-interface Props extends UseDetailProps {
-  from?: FromEnum;
-  pname?: string;
+interface Props {
+  id: ID;
 }
 
-export const BlueprintDetail = ({ from = FromEnum.project, pname, id }: Props) => {
-  const [activeTab, setActiveTab] = useState<TabId>('status');
+export const BlueprintDetail = ({ id }: Props) => {
+  const [activeTab, setActiveTab] = useState<TabId>('configuration');
+  const [version, setVersion] = useState(1);
+  const [operating, setOperating] = useState(false);
 
-  const paths = useMemo(
-    () =>
-      from === FromEnum.project
-        ? [
-            `/projects/${window.encodeURIComponent(pname ?? '')}/${id}/connection-add`,
-            `/projects/${window.encodeURIComponent(pname ?? '')}/${id}/`,
-          ]
-        : [`/blueprints/${id}/connection-add`, `/blueprints/${id}/`],
-    [from, pname],
+  const { ready, data } = useRefreshData(
+    async () => Promise.all([API.getBlueprint(id), API.getBlueprintPipelines(id)]),
+    [version],
   );
 
-  const { loading, blueprint, pipelineId, operating, onRun, onUpdate } = useDetail({
-    id,
-  });
-
-  if (loading || !blueprint) {
+  if (!ready || !data) {
     return <PageLoading />;
   }
 
+  const [blueprint, pipelines] = data;
+
+  const handleUpdate = async (payload: any, callback?: () => void) => {
+    const [success] = await operator(
+      () =>
+        API.updateBlueprint(id, {
+          ...blueprint,
+          ...payload,
+        }),
+      {
+        setOperating,
+      },
+    );
+
+    if (success) {
+      setVersion((v) => v + 1);
+      callback?.();
+    }
+  };
+
+  const handleRun = async () => {
+    const [success] = await operator(() => API.runBlueprint(id), {
+      setOperating,
+    });
+
+    if (success) {
+      setVersion((v) => v + 1);
+    }
+  };
+
   return (
     <S.Wrapper>
       <Tabs selectedTabId={activeTab} onChange={(at) => setActiveTab(at)}>
         <Tab
           id="status"
           title="Status"
-          panel={<Status blueprint={blueprint} pipelineId={pipelineId} operating={operating} onRun={onRun} />}
+          panel={
+            <Status blueprint={blueprint} pipelineId={pipelines?.[0]?.id} operating={operating} onRun={handleRun} />
+          }
         />
         <Tab
           id="configuration"
           title="Configuration"
-          panel={<Configuration paths={paths} blueprint={blueprint} operating={operating} onUpdate={onUpdate} />}
+          panel={<Configuration blueprint={blueprint} operating={operating} onUpdate={handleUpdate} />}
+        />
+        <Tabs.Expander />
+        <Switch
+          style={{ marginBottom: 0 }}
+          label="Blueprint Enabled"
+          checked={blueprint.enable}
+          onChange={(e) => handleUpdate({ enable: (e.target as HTMLInputElement).checked })}
         />
       </Tabs>
     </S.Wrapper>
diff --git a/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx
new file mode 100644
index 000000000..95982ba94
--- /dev/null
+++ b/config-ui/src/pages/blueprint/detail/components/add-connection-dialog/index.tsx
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ *
+ */
+
+import { useState } from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+
+import { Dialog, FormItem, Selector, Buttons } from '@/components';
+import { useConnections } from '@/hooks';
+import { DataScopeSelect } from '@/plugins';
+import type { ConnectionItemType } from '@/store';
+
+interface Props {
+  onCancel: () => void;
+  onSubmit: (value: any) => void;
+}
+
+export const AddConnectionDialog = ({ onCancel, onSubmit }: Props) => {
+  const [step, setStep] = useState(1);
+  const [selectedConnection, setSelectedConnection] = useState<ConnectionItemType>();
+
+  const { connections } = useConnections();
+
+  const handleSubmit = (scope: any) => console.log(scope);
+
+  return (
+    <Dialog style={{ width: 820 }} isOpen title={`Add a Connection - Step ${step}`} footer={null}>
+      {step === 1 && (
+        <FormItem
+          label="Data Connections"
+          subLabel="Select from existing Data Connections. If you have not created any Data Connections yet, please create and manage Connections first."
+          required
+        >
+          <Selector
+            items={connections}
+            getKey={(it) => it.unique}
+            getName={(it) => it.name}
+            selectedItem={selectedConnection}
+            onChangeItem={(selectedItem) => setSelectedConnection(selectedItem)}
+          />
+          <Buttons>
+            <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+            <Button disabled={!selectedConnection} intent={Intent.PRIMARY} text="Next" onClick={() => setStep(2)} />
+          </Buttons>
+        </FormItem>
+      )}
+      {step === 2 && selectedConnection && (
+        <DataScopeSelect
+          plugin={selectedConnection.plugin}
+          connectionId={selectedConnection.id}
+          onCancel={onCancel}
+          onSubmit={handleSubmit}
+        />
+      )}
+    </Dialog>
+  );
+};
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/customize.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/customize.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/customize.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/customize.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/dbt.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/dbt.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/dbt.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/dbt.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/feishu.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/feishu.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/feishu.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/feishu.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/general.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/general.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/general.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/general.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/gitextractor.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitextractor.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/gitextractor.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitextractor.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/github.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/github.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/github.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/github.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/gitlab.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitlab.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/gitlab.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/gitlab.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/index.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/index.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/index.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/index.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/jenkins.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jenkins.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/jenkins.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jenkins.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/jira.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jira.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/jira.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/jira.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/refdiff.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/refdiff.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/refdiff.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/refdiff.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/starrocks.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/starrocks.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/starrocks.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/starrocks.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/tapd.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/tapd.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/tapd.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/tapd.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/example/zentao.ts b/config-ui/src/pages/blueprint/detail/components/advanced-editor/example/zentao.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/example/zentao.ts
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/example/zentao.ts
diff --git a/config-ui/src/pages/blueprint/components/advanced-editor/index.tsx b/config-ui/src/pages/blueprint/detail/components/advanced-editor/index.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/advanced-editor/index.tsx
rename to config-ui/src/pages/blueprint/detail/components/advanced-editor/index.tsx
diff --git a/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx b/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx
deleted file mode 100644
index 91acde78a..000000000
--- a/config-ui/src/pages/blueprint/detail/components/connection-list/index.tsx
+++ /dev/null
@@ -1,75 +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.
- *
- */
-
-import { useMemo } from 'react';
-import { Link } from 'react-router-dom';
-
-import { useConnections } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
-
-import type { BlueprintType } from '../../../types';
-
-import * as S from './styled';
-
-interface Props {
-  path: string;
-  blueprint: BlueprintType;
-}
-
-export const ConnectionList = ({ path, blueprint }: Props) => {
-  const { onGet } = useConnections();
-
-  const list = useMemo(
-    () =>
-      blueprint.settings?.connections
-        .filter((cs) => cs.plugin !== 'webhook')
-        .map((cs: any) => {
-          const unique = `${cs.plugin}-${cs.connectionId}`;
-          const plugin = getPluginConfig(cs.plugin);
-          const connection = onGet(unique);
-
-          return {
-            unique,
-            icon: plugin.icon,
-            name: connection.name,
-            scope: cs.scopes,
-          };
-        })
-        .filter(Boolean),
-    [blueprint],
-  );
-
-  return (
-    <S.List>
-      {list.map((cs) => (
-        <S.Item key={cs.unique}>
-          <div className="title">
-            <img src={cs.icon} alt="" />
-            <span>{cs.name}</span>
-          </div>
-          <div className="count">
-            <span>{cs.scope.length} data scope</span>
-          </div>
-          <div className="link">
-            <Link to={`${path}${cs.unique}`}>View Detail</Link>
-          </div>
-        </S.Item>
-      ))}
-    </S.List>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts b/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
deleted file mode 100644
index da2ce28c2..000000000
--- a/config-ui/src/pages/blueprint/detail/components/connection-list/styled.ts
+++ /dev/null
@@ -1,55 +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.
- *
- */
-
-import styled from 'styled-components';
-
-export const List = styled.ul`
-  display: flex;
-  align-items: center;
-`;
-
-export const Item = styled.li`
-  margin-right: 24px;
-  padding: 12px 16px;
-  width: 280px;
-  background: #ffffff;
-  box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
-  border-radius: 4px;
-
-  &:last-child {
-    margin-right: 0;
-  }
-
-  .title {
-    display: flex;
-    align-items: center;
-
-    img {
-      width: 24px;
-      height: 24px;
-    }
-
-    span {
-      margin-left: 8px;
-    }
-  }
-
-  .count {
-    margin: 24px 0;
-  }
-`;
diff --git a/config-ui/src/pages/blueprint/detail/components/index.ts b/config-ui/src/pages/blueprint/detail/components/index.ts
index f892b941f..3d9b2044e 100644
--- a/config-ui/src/pages/blueprint/detail/components/index.ts
+++ b/config-ui/src/pages/blueprint/detail/components/index.ts
@@ -16,6 +16,7 @@
  *
  */
 
+export * from './add-connection-dialog';
+export * from './advanced-editor';
 export * from './update-name-dialog';
 export * from './update-policy-dialog';
-export * from './connection-list';
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/index.tsx b/config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/index.tsx
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/index.tsx
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/start-from-selector.tsx b/config-ui/src/pages/blueprint/detail/components/sync-policy/start-from-selector.tsx
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/start-from-selector.tsx
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/start-from-selector.tsx
diff --git a/config-ui/src/pages/blueprint/components/sync-policy/styled.ts b/config-ui/src/pages/blueprint/detail/components/sync-policy/styled.ts
similarity index 100%
rename from config-ui/src/pages/blueprint/components/sync-policy/styled.ts
rename to config-ui/src/pages/blueprint/detail/components/sync-policy/styled.ts
diff --git a/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx b/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
index ce3becbb0..c70118c9e 100644
--- a/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
+++ b/config-ui/src/pages/blueprint/detail/components/update-policy-dialog/index.tsx
@@ -16,13 +16,14 @@
  *
  */
 
-import React, { useState, useEffect } from 'react';
+import { useState, useEffect } from 'react';
 
 import { Dialog } from '@/components';
 
 import type { BlueprintType } from '../../../types';
 import { ModeEnum } from '../../../types';
-import { SyncPolicy } from '../../../components';
+
+import { SyncPolicy } from '../sync-policy';
 
 interface Props {
   blueprint: BlueprintType;
@@ -32,7 +33,7 @@ interface Props {
   timeAfter: string | null;
   operating: boolean;
   onCancel: () => void;
-  onSubmit: (params: any) => Promise<void>;
+  onSubmit: (params: any) => void;
 }
 
 export const UpdatePolicyDialog = ({ blueprint, operating, onCancel, onSubmit, ...props }: Props) => {
diff --git a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
index 8038ae7fb..32c1b78c7 100644
--- a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
@@ -17,106 +17,167 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-import { Icon, Button, Switch, Colors, Intent } from '@blueprintjs/core';
-import dayjs from 'dayjs';
+import { Link } from 'react-router-dom';
+import { Button, Intent } from '@blueprintjs/core';
 
-import { getCron } from '@/config';
+import { IconButton, Table, NoData } from '@/components';
+import { useConnections } from '@/hooks';
+import { getPluginConfig } from '@/plugins';
 
 import type { BlueprintType } from '../../types';
 import { ModeEnum } from '../../types';
 import { validRawPlan } from '../../utils';
-import { AdvancedEditor } from '../../components';
 
-import { UpdateNameDialog, UpdatePolicyDialog, ConnectionList } from '../components';
+import { AdvancedEditor, UpdateNameDialog, UpdatePolicyDialog, AddConnectionDialog } from '../components';
 import * as S from '../styled';
 
-type Type = 'name' | 'frequency' | 'scope' | 'transformation';
-
 interface Props {
-  paths: string[];
   blueprint: BlueprintType;
   operating: boolean;
-  onUpdate: (bp: any) => void;
+  onUpdate: (payload: any, callback?: () => void) => void;
 }
 
-export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props) => {
-  const [type, setType] = useState<Type>();
+export const Configuration = ({ blueprint, operating, onUpdate }: Props) => {
+  const [type, setType] = useState<'name' | 'policy' | 'add-connection'>();
   const [rawPlan, setRawPlan] = useState('');
 
-  const history = useHistory();
-
   useEffect(() => {
     setRawPlan(JSON.stringify(blueprint.plan, null, '  '));
   }, [blueprint]);
 
-  const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]);
+  const { onGet } = useConnections();
+
+  const connections = useMemo(
+    () =>
+      blueprint.settings?.connections
+        .filter((cs) => cs.plugin !== 'webhook')
+        .map((cs: any) => {
+          const unique = `${cs.plugin}-${cs.connectionId}`;
+          const plugin = getPluginConfig(cs.plugin);
+          const connection = onGet(unique);
+
+          return {
+            unique,
+            icon: plugin.icon,
+            name: connection.name,
+            scope: cs.scopes,
+          };
+        })
+        .filter(Boolean),
+    [blueprint],
+  );
 
   const handleCancel = () => {
     setType(undefined);
   };
 
-  const handleUpdateName = async (name: string) => {
-    await onUpdate({ name });
-    handleCancel();
+  const handleShowNameDialog = () => {
+    setType('name');
   };
 
-  const handleUpdatePolicy = async (policy: any) => {
-    await onUpdate(policy);
-    handleCancel();
+  const handleShowPolicyDialog = () => {
+    setType('policy');
   };
 
-  const handleToggleEnabled = (checked: boolean) => onUpdate({ enable: checked });
-
-  const handleUpdatePlan = () =>
-    onUpdate({
-      plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, '  '),
-    });
+  const handleShowAddConnectionDialog = () => {
+    setType('add-connection');
+  };
 
   return (
     <S.ConfigurationPanel>
-      <div className="top">
-        <ul>
-          <li>
-            <h3>Name</h3>
-            <div className="detail">
-              <span>{blueprint.name}</span>
-              <Icon icon="annotation" color={Colors.BLUE2} onClick={() => setType('name')} />
-            </div>
-          </li>
-          <li>
-            <h3>Sync Policy</h3>
-            <div className="detail">
-              <span>
-                {cron.label} {cron.value !== 'manual' ? dayjs(cron.nextTime).format('HH:mm A') : null}
-              </span>
-              <Icon icon="annotation" color={Colors.BLUE2} onClick={() => setType('frequency')} />
-            </div>
-          </li>
-        </ul>
-        <Switch
-          label="Blueprint Enabled"
-          checked={blueprint.enable}
-          onChange={(e) => handleToggleEnabled((e.target as HTMLInputElement).checked)}
+      <div className="block">
+        <h3>Blueprint Name</h3>
+        <div>
+          <span>{blueprint.name}</span>
+          <IconButton icon="annotation" tooltip="Edit" onClick={handleShowNameDialog} />
+        </div>
+      </div>
+      <div className="block">
+        <h3>
+          <span>Sync Policy</span>
+          <IconButton icon="annotation" tooltip="Edit" onClick={handleShowPolicyDialog} />
+        </h3>
+        <Table
+          columns={[
+            {
+              title: 'Data Time Range',
+              dataIndex: 'timeRange',
+              key: 'timeRange',
+            },
+            {
+              title: 'Sync Frequency',
+              dataIndex: 'frequency',
+              key: 'frequency',
+            },
+            {
+              title: 'Skip Failed Tasks',
+              dataIndex: 'skipFailed',
+              key: 'skipFailed',
+            },
+          ]}
+          dataSource={[
+            {
+              timeRange: blueprint.settings.timeAfter,
+              frequency: blueprint.cronConfig,
+              skipFailed: blueprint.skipOnFail,
+            },
+          ]}
         />
       </div>
       {blueprint.mode === ModeEnum.normal && (
-        <div className="bottom">
-          <h3>
-            <span>Connections</span>
-            <Button small intent={Intent.PRIMARY} onClick={() => history.push(paths[0])}>
-              Add a Connection
-            </Button>
-          </h3>
-          <ConnectionList path={paths[1]} blueprint={blueprint} />
+        <div className="block">
+          <h3>Data Connections</h3>
+          {!connections.length ? (
+            <NoData
+              text={
+                <>
+                  If you have not created data connections yet, please <Link to="/connections">create connections</Link>{' '}
+                  first and then add them to the project.
+                </>
+              }
+              action={
+                <Button
+                  intent={Intent.PRIMARY}
+                  icon="add"
+                  text="Add a Connection"
+                  onClick={handleShowAddConnectionDialog}
+                />
+              }
+            />
+          ) : (
+            <S.ConnectionList>
+              {connections.map((cs) => (
+                <S.ConnectionItem key={cs.unique}>
+                  <div className="title">
+                    <img src={cs.icon} alt="" />
+                    <span>{cs.name}</span>
+                  </div>
+                  <div className="count">
+                    <span>{cs.scope.length} data scope</span>
+                  </div>
+                  <div className="link">
+                    <Link to={`${cs.unique}`}>View Detail</Link>
+                  </div>
+                </S.ConnectionItem>
+              ))}
+            </S.ConnectionList>
+          )}
         </div>
       )}
       {blueprint.mode === ModeEnum.advanced && (
-        <div className="bottom">
+        <div className="block">
           <h3>JSON Configuration</h3>
           <AdvancedEditor value={rawPlan} onChange={setRawPlan} />
           <div className="btns">
-            <Button intent={Intent.PRIMARY} text="Save" onClick={handleUpdatePlan} />
+            <Button
+              intent={Intent.PRIMARY}
+              text="Save"
+              onClick={() =>
+                onUpdate({
+                  plan: !validRawPlan(rawPlan) ? JSON.parse(rawPlan) : JSON.stringify([[]], null, '  '),
+                })
+              }
+            />
           </div>
         </div>
       )}
@@ -125,10 +186,10 @@ export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props)
           name={blueprint.name}
           operating={operating}
           onCancel={handleCancel}
-          onSubmit={handleUpdateName}
+          onSubmit={(name) => onUpdate({ name }, handleCancel)}
         />
       )}
-      {type === 'frequency' && (
+      {type === 'policy' && (
         <UpdatePolicyDialog
           blueprint={blueprint}
           isManual={blueprint.isManual}
@@ -137,9 +198,12 @@ export const Configuration = ({ paths, blueprint, operating, onUpdate }: Props)
           timeAfter={blueprint.settings?.timeAfter}
           operating={operating}
           onCancel={handleCancel}
-          onSubmit={handleUpdatePolicy}
+          onSubmit={(payload) => onUpdate(payload, handleCancel)}
         />
       )}
+      {type === 'add-connection' && (
+        <AddConnectionDialog onCancel={handleCancel} onSubmit={(value) => console.log(value)} />
+      )}
     </S.ConfigurationPanel>
   );
 };
diff --git a/config-ui/src/pages/blueprint/detail/panel/status.tsx b/config-ui/src/pages/blueprint/detail/panel/status.tsx
index cb01d53ea..264fa28b3 100644
--- a/config-ui/src/pages/blueprint/detail/panel/status.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/status.tsx
@@ -16,11 +16,12 @@
  *
  */
 
-import React, { useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
+import { useMemo } from 'react';
+import { Button, Intent, Position } from '@blueprintjs/core';
+import { Tooltip2 } from '@blueprintjs/popover2';
 
-import { getCron } from '@/config';
 import { Card } from '@/components';
+import { getCron } from '@/config';
 import { PipelineContextProvider, PipelineInfo, PipelineTasks, PipelineHistorical } from '@/pages';
 import { formatTime } from '@/utils';
 
@@ -38,22 +39,29 @@ interface Props {
 export const Status = ({ blueprint, pipelineId, operating, onRun }: Props) => {
   const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]);
 
-  const handleRunNow = () => onRun();
-
   return (
     <S.StatusPanel>
       <div className="info">
         <span>{cron.value === 'manual' ? 'Manual' : `Next Run: ${formatTime(cron.nextTime, 'YYYY-MM-DD HH:mm')}`}</span>
-        <span>
+        <Tooltip2
+          position={Position.TOP}
+          content="It is recommended to re-transform your data in this project if you have updated the transformation of the data scope in this project."
+        >
           <Button
             disabled={!blueprint.enable}
             loading={operating}
-            small
             intent={Intent.PRIMARY}
-            text="Run Now"
-            onClick={handleRunNow}
+            text="Re-transform Data"
+            onClick={onRun}
           />
-        </span>
+        </Tooltip2>
+        <Button
+          disabled={!blueprint.enable}
+          loading={operating}
+          intent={Intent.PRIMARY}
+          text="Collect All Data"
+          onClick={onRun}
+        />
       </div>
       <PipelineContextProvider>
         <div className="block">
diff --git a/config-ui/src/pages/blueprint/detail/styled.ts b/config-ui/src/pages/blueprint/detail/styled.ts
index b865d3c8d..31af92f98 100644
--- a/config-ui/src/pages/blueprint/detail/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/styled.ts
@@ -23,74 +23,54 @@ export const Wrapper = styled.div`
 `;
 
 export const ConfigurationPanel = styled.div`
+  .block + .block {
+    margin-top: 36px;
+  }
+
   h3 {
     margin-bottom: 16px;
   }
 
-  .top {
-    display: flex;
-    align-items: flex-start;
-    justify-content: space-between;
-
-    ul {
-      display: flex;
-      align-items: flex-start;
-
-      li + li {
-        margin-left: 48px;
-      }
-    }
-
-    .detail {
-      .bp4-icon {
-        margin-left: 4px;
-        cursor: pointer;
-      }
-    }
-  }
-
-  .bottom {
-    margin-top: 24px;
-
-    h3 {
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-    }
-
-    .btns {
-      margin-top: 16px;
-      text-align: right;
-    }
+  .btns {
+    margin-top: 16px;
+    text-align: right;
   }
 `;
 
-export const ConnectionColumn = styled.div`
+export const ConnectionList = styled.ul`
   display: flex;
   align-items: center;
-
-  img {
-    margin-right: 4px;
-    width: 20px;
-  }
 `;
 
-export const ActionColumn = styled.div`
-  display: inline-flex;
-  flex-direction: column;
-  align-items: flex-start;
+export const ConnectionItem = styled.li`
+  margin-right: 24px;
+  padding: 12px 16px;
+  width: 280px;
+  background: #ffffff;
+  box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
+  border-radius: 4px;
 
-  .item + .item {
-    margin-top: 8px;
+  &:last-child {
+    margin-right: 0;
   }
 
-  .item {
-    cursor: pointer;
+  .title {
+    display: flex;
+    align-items: center;
+
+    img {
+      width: 24px;
+      height: 24px;
+    }
 
-    .bp4-icon {
-      margin-right: 4px;
+    span {
+      margin-left: 8px;
     }
   }
+
+  .count {
+    margin: 24px 0;
+  }
 `;
 
 export const StatusPanel = styled.div`
@@ -103,13 +83,9 @@ export const StatusPanel = styled.div`
     justify-content: flex-end;
     align-items: center;
 
-    & > span {
+    & > * {
       margin-left: 16px;
     }
-
-    .bp4-switch {
-      margin-bottom: 0;
-    }
   }
 
   .block + .block {
diff --git a/config-ui/src/pages/blueprint/detail/use-detail.ts b/config-ui/src/pages/blueprint/detail/use-detail.ts
deleted file mode 100644
index 994989f5e..000000000
--- a/config-ui/src/pages/blueprint/detail/use-detail.ts
+++ /dev/null
@@ -1,90 +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.
- *
- */
-
-import { useState, useEffect, useMemo } from 'react';
-
-import { operator } from '@/utils';
-
-import type { BlueprintType } from '@/pages';
-import * as API from './api';
-
-export interface UseDetailProps {
-  id: ID;
-}
-
-export const useDetail = ({ id }: UseDetailProps) => {
-  const [loading, setLoading] = useState(false);
-  const [operating, setOperating] = useState(false);
-  const [blueprint, setBlueprint] = useState<BlueprintType>();
-  const [pipelineId, setPipelineId] = useState<ID>();
-
-  const getBlueprint = async () => {
-    setLoading(true);
-    try {
-      const [bpRes, plRes] = await Promise.all([API.getBlueprint(id), API.getBlueprintPipelines(id)]);
-
-      setBlueprint(bpRes);
-      setPipelineId(plRes.pipelines?.[0]?.id);
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  useEffect(() => {
-    getBlueprint();
-  }, []);
-
-  const handleRun = async () => {
-    const [success] = await operator(() => API.runBlueprint(id), {
-      setOperating,
-    });
-
-    if (success) {
-      getBlueprint();
-    }
-  };
-
-  const handleUpdate = async (payload: any) => {
-    const [success] = await operator(
-      () =>
-        API.updateBlueprint(id, {
-          ...blueprint,
-          ...payload,
-        }),
-      {
-        setOperating,
-      },
-    );
-
-    if (success) {
-      getBlueprint();
-    }
-  };
-
-  return useMemo(
-    () => ({
-      loading,
-      operating,
-      blueprint,
-      pipelineId,
-      onRun: handleRun,
-      onUpdate: handleUpdate,
-    }),
-    [loading, operating, blueprint, pipelineId],
-  );
-};
diff --git a/config-ui/src/pages/blueprint/index.ts b/config-ui/src/pages/blueprint/index.ts
index 68aec9419..3633cb637 100644
--- a/config-ui/src/pages/blueprint/index.ts
+++ b/config-ui/src/pages/blueprint/index.ts
@@ -19,5 +19,4 @@
 export * from './types';
 export * from './home';
 export * from './detail';
-export * from './connection-add';
 export * from './connection-detail';
diff --git a/config-ui/src/pages/blueprint/types.ts b/config-ui/src/pages/blueprint/types.ts
index 362848031..eec9164d6 100644
--- a/config-ui/src/pages/blueprint/types.ts
+++ b/config-ui/src/pages/blueprint/types.ts
@@ -21,11 +21,6 @@ export enum ModeEnum {
   normal = 'NORMAL',
 }
 
-export enum FromEnum {
-  project = 'project',
-  blueprint = 'blueprint',
-}
-
 export type BlueprintType = {
   id: ID;
   enable: boolean;
@@ -41,7 +36,7 @@ export type BlueprintType = {
     connections: Array<{
       plugin: string;
       connectionId: ID;
-      scopes: Array<{
+      scopes?: Array<{
         id: string;
         entities: string[];
       }>;
diff --git a/config-ui/src/pages/project/detail/api.ts b/config-ui/src/pages/project/detail/api.ts
index 106498b85..2c7c65581 100644
--- a/config-ui/src/pages/project/detail/api.ts
+++ b/config-ui/src/pages/project/detail/api.ts
@@ -18,23 +18,15 @@
 
 import { request } from '@/utils';
 
-export const getProject = (name: string) => request(`/projects/${name}`);
+import type { ProjectType, BlueprintType } from '@/pages';
 
-type UpdateProjectPayload = {
-  name: string;
-  description: string;
-  metrics: Array<{
-    pluginName: string;
-    pluginOption: string;
-    enable: boolean;
-  }>;
-};
+export const getProject = (name: string): Promise<ProjectType> => request(`/projects/${name}`);
 
-export const updateProject = (name: string, payload: UpdateProjectPayload) =>
+export const updateProject = (name: string, payload: Omit<ProjectType, 'blueprint'>) =>
   request(`/projects/${name}`, {
     method: 'patch',
     data: payload,
   });
 
-export const updateBlueprint = (id: ID, payload: any) =>
+export const updateBlueprint = (id: ID, payload: BlueprintType) =>
   request(`/blueprints/${id}`, { method: 'patch', data: payload });
diff --git a/config-ui/src/pages/project/detail/index.tsx b/config-ui/src/pages/project/detail/index.tsx
index 065352a08..4db19c51c 100644
--- a/config-ui/src/pages/project/detail/index.tsx
+++ b/config-ui/src/pages/project/detail/index.tsx
@@ -21,13 +21,17 @@ import { useParams, useLocation, useHistory } from 'react-router-dom';
 import { Tabs, Tab } from '@blueprintjs/core';
 
 import { PageHeader, PageLoading } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { BlueprintDetail } from '@/pages';
 
-import { useProject } from './use-project';
-import { BlueprintPanel, IncomingWebhooksPanel, SettingsPanel } from './panel';
+import { WebhooksPanel } from './webhooks-panel';
+import { SettingsPanel } from './settings-panel';
+import * as API from './api';
 import * as S from './styled';
 
 export const ProjectDetailPage = () => {
-  const [tabId, setTabId] = useState('bp');
+  const [tabId, setTabId] = useState('blueprint');
+  const [version, setVersion] = useState(1);
 
   const { pname } = useParams<{ pname: string }>();
   const { search } = useLocation();
@@ -37,10 +41,10 @@ export const ProjectDetailPage = () => {
   const urlTabId = query.get('tabId');
 
   useEffect(() => {
-    setTabId(urlTabId ?? 'bp');
+    setTabId(urlTabId ?? 'blueprint');
   }, [urlTabId]);
 
-  const { loading, project, saving, onUpdate, onSelectWebhook, onCreateWebhook, onDeleteWebhook } = useProject(pname);
+  const { ready, data } = useRefreshData(() => Promise.all([API.getProject(pname)]), [version]);
 
   const handleChangeTabId = (tabId: string) => {
     query.delete('tabId');
@@ -48,10 +52,16 @@ export const ProjectDetailPage = () => {
     history.push({ search: query.toString() });
   };
 
-  if (loading || !project) {
+  const handleRefresh = () => {
+    setVersion((v) => v + 1);
+  };
+
+  if (!ready || !data) {
     return <PageLoading />;
   }
 
+  const [project] = data;
+
   return (
     <PageHeader
       breadcrumbs={[
@@ -61,22 +71,13 @@ export const ProjectDetailPage = () => {
     >
       <S.Wrapper>
         <Tabs selectedTabId={tabId} onChange={handleChangeTabId}>
-          <Tab id="bp" title="Blueprint" panel={<BlueprintPanel project={project} />} />
+          <Tab id="blueprint" title="Blueprint" panel={<BlueprintDetail id={project.blueprint.id} />} />
           <Tab
-            id="iw"
+            id="webhook"
             title="Incoming Webhooks"
-            disabled={!project.blueprint}
-            panel={
-              <IncomingWebhooksPanel
-                project={project}
-                saving={saving}
-                onSelectWebhook={onSelectWebhook}
-                onCreateWebhook={onCreateWebhook}
-                onDeleteWebhook={onDeleteWebhook}
-              />
-            }
+            panel={<WebhooksPanel project={project} onRefresh={handleRefresh} />}
           />
-          <Tab id="st" title="Settings" panel={<SettingsPanel project={project} onUpdate={onUpdate} />} />
+          <Tab id="settings" title="Settings" panel={<SettingsPanel project={project} />} />
         </Tabs>
       </S.Wrapper>
     </PageHeader>
diff --git a/config-ui/src/pages/project/detail/panel/blueprint.tsx b/config-ui/src/pages/project/detail/panel/blueprint.tsx
deleted file mode 100644
index d818446ed..000000000
--- a/config-ui/src/pages/project/detail/panel/blueprint.tsx
+++ /dev/null
@@ -1,50 +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.
- *
- */
-
-import React from 'react';
-import { useHistory } from 'react-router-dom';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { NoData } from '@/components';
-import { BlueprintDetail } from '@/pages';
-
-import type { ProjectType } from '../types';
-
-interface Props {
-  project: ProjectType;
-}
-
-export const BlueprintPanel = ({ project }: Props) => {
-  const history = useHistory();
-
-  const handleGoCreateBlueprint = () =>
-    history.push(`/projects/${window.encodeURIComponent(project.name)}/create-blueprint`);
-
-  if (!project.blueprint) {
-    return (
-      <NoData
-        text="Create a blueprint to collect data from data sources."
-        action={
-          <Button intent={Intent.PRIMARY} icon="plus" text="Create a Blueprint" onClick={handleGoCreateBlueprint} />
-        }
-      />
-    );
-  }
-
-  return <BlueprintDetail pname={project.name} id={project.blueprint.id} />;
-};
diff --git a/config-ui/src/pages/project/detail/panel/index.ts b/config-ui/src/pages/project/detail/panel/index.ts
deleted file mode 100644
index 0642ffc62..000000000
--- a/config-ui/src/pages/project/detail/panel/index.ts
+++ /dev/null
@@ -1,21 +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 './blueprint';
-export * from './incoming-webhooks';
-export * from './settings';
diff --git a/config-ui/src/pages/project/detail/panel/settings.tsx b/config-ui/src/pages/project/detail/panel/settings.tsx
deleted file mode 100644
index 807e504fb..000000000
--- a/config-ui/src/pages/project/detail/panel/settings.tsx
+++ /dev/null
@@ -1,67 +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.
- *
- */
-
-import React, { useEffect, useState } from 'react';
-import { InputGroup, Checkbox, ButtonGroup, Button, Intent } from '@blueprintjs/core';
-
-import { Card } from '@/components';
-
-import type { ProjectType } from '../types';
-import * as S from '../styled';
-
-interface Props {
-  project: ProjectType;
-  onUpdate: (name: string, enableDora: boolean) => void;
-}
-
-export const SettingsPanel = ({ project, onUpdate }: Props) => {
-  const [name, setName] = useState('');
-  const [enableDora, setEnableDora] = useState(false);
-
-  useEffect(() => {
-    if (project) {
-      setName(project.name);
-      setEnableDora(project.enableDora);
-    }
-  }, [project]);
-
-  const handleSave = () => onUpdate(name, enableDora);
-
-  return (
-    <Card>
-      <S.Settings>
-        <div className="block">
-          <h3>Project Name *</h3>
-          <p>Edit your project name with letters, numbers, -, _ or /</p>
-          <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
-        </div>
-        <div className="block">
-          <Checkbox
-            label="Enable DORA Metrics"
-            checked={enableDora}
-            onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
-          />
-          <p>DORA metrics are four widely-adopted metrics for measuring software delivery performance.</p>
-        </div>
-        <ButtonGroup>
-          <Button text="Save" disabled={!name} intent={Intent.PRIMARY} onClick={handleSave} />
-        </ButtonGroup>
-      </S.Settings>
-    </Card>
-  );
-};
diff --git a/config-ui/src/pages/project/detail/settings-panel.tsx b/config-ui/src/pages/project/detail/settings-panel.tsx
new file mode 100644
index 000000000..a271b05f7
--- /dev/null
+++ b/config-ui/src/pages/project/detail/settings-panel.tsx
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ *
+ */
+
+import { useEffect, useState } from 'react';
+import { useHistory } from 'react-router-dom';
+import { InputGroup, Checkbox, Button, Intent } from '@blueprintjs/core';
+
+import { Card, FormItem, Buttons, toast } from '@/components';
+import { operator } from '@/utils';
+
+import type { ProjectType } from '../types';
+import { validName } from '../utils';
+
+import * as API from './api';
+
+interface Props {
+  project: ProjectType;
+}
+
+export const SettingsPanel = ({ project }: Props) => {
+  const [name, setName] = useState('');
+  const [enableDora, setEnableDora] = useState(false);
+  const [operating, setOperating] = useState(false);
+
+  const history = useHistory();
+
+  useEffect(() => {
+    const doraMetrics = project.metrics.find((ms: any) => ms.pluginName === 'dora');
+
+    setName(project.name);
+    setEnableDora(doraMetrics?.enable ?? false);
+  }, [project]);
+
+  const handleUpdate = async () => {
+    if (!validName(name)) {
+      toast.error('Please enter alphanumeric or underscore');
+      return;
+    }
+
+    const [success] = await operator(
+      () =>
+        API.updateProject(project.name, {
+          name,
+          description: '',
+          metrics: [
+            {
+              pluginName: 'dora',
+              pluginOption: '',
+              enable: enableDora,
+            },
+          ],
+        }),
+      {
+        setOperating,
+      },
+    );
+
+    if (success) {
+      history.push(`/projects/${name}`);
+    }
+  };
+
+  return (
+    <Card>
+      <FormItem label="Project Name" subLabel="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.">
+        <Checkbox
+          label="Enable DORA Metrics"
+          checked={enableDora}
+          onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
+        />
+      </FormItem>
+      <Buttons>
+        <Button text="Save" loading={operating} disabled={!name} intent={Intent.PRIMARY} onClick={handleUpdate} />
+      </Buttons>
+    </Card>
+  );
+};
diff --git a/config-ui/src/pages/project/detail/styled.ts b/config-ui/src/pages/project/detail/styled.ts
index 0620323ab..248651df2 100644
--- a/config-ui/src/pages/project/detail/styled.ts
+++ b/config-ui/src/pages/project/detail/styled.ts
@@ -24,17 +24,3 @@ export const Wrapper = styled.div`
     justify-content: center;
   }
 `;
-
-export const Settings = styled.div`
-  .block + .block {
-    margin-top: 24px;
-  }
-
-  .bp4-control {
-    margin-bottom: 0;
-  }
-
-  .bp4-input-group {
-    width: 386px;
-  }
-`;
diff --git a/config-ui/src/pages/project/detail/use-project.ts b/config-ui/src/pages/project/detail/use-project.ts
deleted file mode 100644
index 227b1ec23..000000000
--- a/config-ui/src/pages/project/detail/use-project.ts
+++ /dev/null
@@ -1,162 +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.
- *
- */
-
-import { useState, useEffect, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { toast } from '@/components';
-import type { WebhookItemType } from '@/plugins/register/webook';
-import { operator } from '@/utils';
-
-import type { ProjectType } from './types';
-import * as API from './api';
-
-export const useProject = (name: string) => {
-  const [loading, setLoading] = useState(false);
-  const [project, setProject] = useState<ProjectType>();
-  const [saving, setSaving] = useState(false);
-
-  const history = useHistory();
-
-  const getProject = async () => {
-    setLoading(true);
-    try {
-      const res = await API.getProject(name);
-      const doraMetrics = res.metrics.find((ms: any) => ms.pluginName === 'dora');
-
-      setProject({
-        name: res.name,
-        description: res.description,
-        blueprint: res.blueprint,
-        enableDora: doraMetrics.enable,
-      });
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  useEffect(() => {
-    getProject();
-  }, []);
-
-  const handleUpdate = async (newName: string, enableDora: boolean) => {
-    if (!/^(\w|-|\/)+$/.test(newName)) {
-      toast.error('Please enter alphanumeric or underscore');
-      return;
-    }
-
-    const payload = {
-      name: newName,
-      description: '',
-      metrics: [
-        {
-          pluginName: 'dora',
-          pluginOption: '',
-          enable: enableDora,
-        },
-      ],
-    };
-
-    const [success] = await operator(() => API.updateProject(name, payload));
-
-    if (success) {
-      history.push(`/projects/${newName}`);
-    }
-  };
-
-  const handleSelectWebhook = async (items: WebhookItemType[]) => {
-    const payload = {
-      ...project?.blueprint,
-      settings: {
-        version: '2.0.0',
-        connections: [
-          ...project?.blueprint.settings.connections,
-          ...items.map((it) => ({
-            plugin: 'webhook',
-            connectionId: it.id,
-          })),
-        ],
-      },
-    };
-
-    const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
-      setOperating: setSaving,
-    });
-
-    if (success) {
-      getProject();
-    }
-  };
-
-  const handleCreateWebhook = async (id: ID) => {
-    const payload = {
-      ...project?.blueprint,
-      settings: {
-        version: '2.0.0',
-        connections: [
-          ...project?.blueprint.settings.connections,
-          {
-            plugin: 'webhook',
-            connectionId: id,
-          },
-        ],
-      },
-    };
-
-    const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
-      setOperating: setSaving,
-    });
-
-    if (success) {
-      getProject();
-    }
-  };
-
-  const handleDeleteWebhook = async (id: ID) => {
-    const payload = {
-      ...project?.blueprint,
-      settings: {
-        version: '2.0.0',
-        connections: project?.blueprint.settings.connections.filter(
-          (cs: any) => !(cs.plugin === 'webhook' && cs.connectionId === id),
-        ),
-      },
-    };
-
-    const [success] = await operator(() => API.updateBlueprint(project?.blueprint.id, payload), {
-      setOperating: setSaving,
-    });
-
-    if (success) {
-      getProject();
-    }
-  };
-
-  return useMemo(
-    () => ({
-      loading,
-      project,
-      saving,
-      onUpdate: handleUpdate,
-      onSelectWebhook: handleSelectWebhook,
-      onCreateWebhook: handleCreateWebhook,
-      onDeleteWebhook: handleDeleteWebhook,
-    }),
-    [loading, project, saving, name],
-  );
-};
diff --git a/config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx b/config-ui/src/pages/project/detail/webhooks-panel.tsx
similarity index 60%
rename from config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx
rename to config-ui/src/pages/project/detail/webhooks-panel.tsx
index 27fde2ede..81413fbd9 100644
--- a/config-ui/src/pages/project/detail/panel/incoming-webhooks.tsx
+++ b/config-ui/src/pages/project/detail/webhooks-panel.tsx
@@ -22,25 +22,20 @@ import { Button, Intent } from '@blueprintjs/core';
 import { Alert, NoData } from '@/components';
 import type { WebhookItemType } from '@/plugins/register/webook';
 import { WebhookCreateDialog, WebhookSelectorDialog, WebHookConnection } from '@/plugins/register/webook';
+import { operator } from '@/utils';
 
 import type { ProjectType } from '../types';
 
+import * as API from './api';
+
 interface Props {
   project: ProjectType;
-  saving: boolean;
-  onSelectWebhook: (items: WebhookItemType[]) => void;
-  onCreateWebhook: (id: ID) => any;
-  onDeleteWebhook: (id: ID) => any;
+  onRefresh: () => void;
 }
 
-export const IncomingWebhooksPanel = ({
-  project,
-  saving,
-  onSelectWebhook,
-  onCreateWebhook,
-  onDeleteWebhook,
-}: Props) => {
+export const WebhooksPanel = ({ project, onRefresh }: Props) => {
   const [type, setType] = useState<'selectExist' | 'create'>();
+  const [operating, setOperating] = useState(false);
 
   const webhookIds = useMemo(
     () =>
@@ -56,6 +51,74 @@ export const IncomingWebhooksPanel = ({
     setType(undefined);
   };
 
+  const handleCreate = async (id: ID) => {
+    const payload = {
+      ...project.blueprint,
+      settings: {
+        ...project.blueprint.settings,
+        connections: [
+          ...project.blueprint.settings.connections,
+          {
+            plugin: 'webhook',
+            connectionId: id,
+          },
+        ],
+      },
+    };
+
+    const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+      setOperating,
+    });
+
+    if (success) {
+      onRefresh();
+    }
+  };
+
+  const handleSelect = async (items: WebhookItemType[]) => {
+    const payload = {
+      ...project.blueprint,
+      settings: {
+        ...project.blueprint.settings,
+        connections: [
+          ...project.blueprint.settings.connections,
+          ...items.map((it) => ({
+            plugin: 'webhook',
+            connectionId: it.id,
+          })),
+        ],
+      },
+    };
+
+    const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+      setOperating,
+    });
+
+    if (success) {
+      onRefresh();
+    }
+  };
+
+  const handleDelete = async (id: ID) => {
+    const payload = {
+      ...project.blueprint,
+      settings: {
+        ...project.blueprint.settings,
+        connections: project.blueprint.settings.connections.filter(
+          (cs: any) => !(cs.plugin === 'webhook' && cs.connectionId === id),
+        ),
+      },
+    };
+
+    const [success] = await operator(() => API.updateBlueprint(project.blueprint.id, payload), {
+      setOperating,
+    });
+
+    if (success) {
+      onRefresh();
+    }
+  };
+
   return (
     <>
       <Alert style={{ marginBottom: 24, color: '#3C5088' }}>
@@ -86,13 +149,13 @@ export const IncomingWebhooksPanel = ({
               </>
             }
           />
-          {type === 'create' && <WebhookCreateDialog isOpen onCancel={handleCancel} onSubmitAfter={onCreateWebhook} />}
+          {type === 'create' && <WebhookCreateDialog isOpen onCancel={handleCancel} onSubmitAfter={handleCreate} />}
           {type === 'selectExist' && (
-            <WebhookSelectorDialog isOpen saving={saving} onCancel={handleCancel} onSubmit={onSelectWebhook} />
+            <WebhookSelectorDialog isOpen saving={operating} onCancel={handleCancel} onSubmit={handleSelect} />
           )}
         </>
       ) : (
-        <WebHookConnection filterIds={webhookIds} onCreateAfter={onCreateWebhook} onDeleteAfter={onDeleteWebhook} />
+        <WebHookConnection filterIds={webhookIds} onCreateAfter={handleCreate} onDeleteAfter={handleDelete} />
       )}
     </>
   );
diff --git a/config-ui/src/pages/project/home/index.tsx b/config-ui/src/pages/project/home/index.tsx
index ffc287fd4..ec8573252 100644
--- a/config-ui/src/pages/project/home/index.tsx
+++ b/config-ui/src/pages/project/home/index.tsx
@@ -26,7 +26,7 @@ import { cronPresets } from '@/config';
 import { useRefreshData } from '@/hooks';
 import { formatTime, operator } from '@/utils';
 
-import { encodeName } from '../utils';
+import { validName, encodeName } from '../utils';
 import { ModeEnum } from '../../blueprint';
 
 import * as API from './api';
@@ -54,7 +54,7 @@ export const ProjectHomePage = () => {
   };
 
   const handleCreate = async () => {
-    if (!/^(\w|-|\/)+$/.test(name)) {
+    if (!validName(name)) {
       toast.error('Please enter alphanumeric or underscore');
       return;
     }
diff --git a/config-ui/src/pages/project/index.ts b/config-ui/src/pages/project/index.ts
index d5131a681..40b545722 100644
--- a/config-ui/src/pages/project/index.ts
+++ b/config-ui/src/pages/project/index.ts
@@ -16,5 +16,7 @@
  *
  */
 
+export * from './types';
+export * from './utils';
 export * from './home';
 export * from './detail';
diff --git a/config-ui/src/pages/project/detail/types.ts b/config-ui/src/pages/project/types.ts
similarity index 84%
rename from config-ui/src/pages/project/detail/types.ts
rename to config-ui/src/pages/project/types.ts
index 7568cf9b1..37d4573ce 100644
--- a/config-ui/src/pages/project/detail/types.ts
+++ b/config-ui/src/pages/project/types.ts
@@ -16,9 +16,11 @@
  *
  */
 
+import type { BlueprintType } from '@/pages';
+
 export type ProjectType = {
   name: string;
   description: string;
-  blueprint: any | null;
-  enableDora: boolean;
+  blueprint: BlueprintType;
+  metrics: Array<{ pluginName: string; pluginOption: string; enable: boolean }>;
 };
diff --git a/config-ui/src/pages/project/utils.ts b/config-ui/src/pages/project/utils.ts
index 0dd911bd3..ac605cbb9 100644
--- a/config-ui/src/pages/project/utils.ts
+++ b/config-ui/src/pages/project/utils.ts
@@ -16,6 +16,8 @@
  *
  */
 
+export const validName = (name: string) => /^(\w|-|\/)+$/.test(name);
+
 export const decodeName = (name: string) => window.decodeURIComponent(name);
 
 export const encodeName = (name: string) => window.encodeURIComponent(name);
diff --git a/config-ui/src/plugins/components/data-scope-form/api.ts b/config-ui/src/plugins/components/data-scope-form/api.ts
deleted file mode 100644
index 19abb110d..000000000
--- a/config-ui/src/plugins/components/data-scope-form/api.ts
+++ /dev/null
@@ -1,34 +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.
- *
- */
-
-import { request } from '@/utils';
-
-export const getDataScope = (plugin: string, connectionId: ID, scopeId: string) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`);
-
-export const updateDataScope = (plugin: string, connectionId: ID, payload: any) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/scopes`, {
-    method: 'put',
-    data: payload,
-  });
-
-export const updateDataScopeWithType = (plugin: string, connectionId: ID, type: string, payload: any) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/${type}/scopes`, {
-    method: 'put',
-    data: payload,
-  });
diff --git a/config-ui/src/plugins/components/data-scope-form/index.tsx b/config-ui/src/plugins/components/data-scope-form/index.tsx
deleted file mode 100644
index 2e60b5436..000000000
--- a/config-ui/src/plugins/components/data-scope-form/index.tsx
+++ /dev/null
@@ -1,210 +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.
- *
- */
-
-import { useEffect, useMemo, useState } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Card, MultiSelector } from '@/components';
-import { transformEntities } from '@/config';
-import { getPluginConfig, getPluginId } from '@/plugins';
-
-import { GitHubDataScope } from '@/plugins/register/github';
-import { JiraDataScope } from '@/plugins/register/jira';
-import { GitLabDataScope } from '@/plugins/register/gitlab';
-import { JenkinsDataScope } from '@/plugins/register/jenkins';
-import { BitbucketDataScope } from '@/plugins/register/bitbucket';
-import { AzureDataScope } from '@/plugins/register/azure';
-import { SonarQubeDataScope } from '@/plugins/register/sonarqube';
-import { PagerDutyDataScope } from '@/plugins/register/pagerduty';
-import { ZentaoDataScope } from '@/plugins/register/zentao';
-
-import * as API from './api';
-import * as S from './styled';
-import { TapdDataScope } from '@/plugins/register/tapd';
-
-interface Props {
-  plugin: string;
-  connectionId: ID;
-  initialScope?: any[];
-  initialEntities?: string[];
-  cancelBtnProps?: {
-    text?: string;
-  };
-  submitBtnProps?: {
-    text?: string;
-  };
-  onCancel?: () => void;
-  onSubmit?: (scope: Array<{ id: string; entities: string[] }>, origin: any) => void;
-}
-
-export const DataScopeForm = ({
-  plugin,
-  connectionId,
-  initialScope,
-  initialEntities,
-  onSubmit,
-  onCancel,
-  cancelBtnProps,
-  submitBtnProps,
-}: Props) => {
-  const [operating, setOperating] = useState(false);
-  const [scope, setScope] = useState<any>([]);
-  const [entities, setEntites] = useState<string[]>([]);
-
-  const config = useMemo(() => getPluginConfig(plugin), []);
-
-  const error = useMemo(
-    () => (!scope.length || !entities.length ? 'No Data Scope is Selected' : ''),
-    [scope, entities],
-  );
-
-  useEffect(() => {
-    setScope(initialScope ?? []);
-    setEntites(initialEntities ?? config.entities);
-  }, []);
-
-  const getDataScope = async (scope: any) => {
-    try {
-      const res = await API.getDataScope(plugin, connectionId, scope[getPluginId(plugin)]);
-      return {
-        ...scope,
-        transformationRuleId: res.transformationRuleId,
-      };
-    } catch {
-      return scope;
-    }
-  };
-
-  const handleSubmit = async () => {
-    setOperating(true);
-    try {
-      const data = await Promise.all(scope.map((sc: any) => getDataScope(sc)));
-      const res =
-        plugin === 'zentao'
-          ? [
-              ...(await API.updateDataScopeWithType(plugin, connectionId, 'product', {
-                data: data.filter((s) => s.type !== 'project'),
-              })),
-              ...(await API.updateDataScopeWithType(plugin, connectionId, 'project', {
-                data: data.filter((s) => s.type === 'project'),
-              })),
-            ]
-          : await API.updateDataScope(plugin, connectionId, {
-              data,
-            });
-
-      onSubmit?.(
-        res.map((it: any) => ({
-          id: plugin === 'zentao' ? `${it.type}/${it.id}` : it[getPluginId(plugin)],
-          entities,
-        })),
-        res,
-      );
-    } finally {
-      setOperating(false);
-    }
-  };
-
-  return (
-    <S.Wrapper>
-      <Card>
-        <div className="block">
-          {plugin === 'github' && (
-            <GitHubDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'jira' && (
-            <JiraDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'gitlab' && (
-            <GitLabDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'jenkins' && (
-            <JenkinsDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'bitbucket' && (
-            <BitbucketDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'azuredevops' && (
-            <AzureDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'sonarqube' && (
-            <SonarQubeDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'pagerduty' && (
-            <PagerDutyDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'tapd' && (
-            <TapdDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-
-          {plugin === 'zentao' && (
-            <ZentaoDataScope connectionId={connectionId} selectedItems={scope} onChangeItems={setScope} />
-          )}
-        </div>
-
-        <div className="block">
-          <h3>Data Entities</h3>
-          <p>
-            <span>Select the data entities you wish to collect for the projects.</span>{' '}
-            <a
-              href="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema/#data-models"
-              target="_blank"
-              rel="noreferrer"
-            >
-              Learn about data entities
-            </a>
-          </p>
-          <MultiSelector
-            items={transformEntities(config.entities)}
-            getKey={(item) => item.value}
-            getName={(item) => item.label}
-            selectedItems={transformEntities(entities)}
-            onChangeItems={(items) => setEntites(items.map((it) => it.value))}
-          />
-        </div>
-      </Card>
-
-      <div className="action">
-        <Button
-          outlined
-          intent={Intent.PRIMARY}
-          text="Cancel"
-          {...cancelBtnProps}
-          disabled={operating}
-          onClick={onCancel}
-        />
-        <Button
-          intent={Intent.PRIMARY}
-          text="Save"
-          {...submitBtnProps}
-          loading={operating}
-          disabled={!!error}
-          onClick={handleSubmit}
-        />
-      </div>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/blueprint/components/index.ts b/config-ui/src/plugins/components/data-scope-select/api.ts
similarity index 82%
rename from config-ui/src/pages/blueprint/components/index.ts
rename to config-ui/src/plugins/components/data-scope-select/api.ts
index 109693dd6..58fe70730 100644
--- a/config-ui/src/pages/blueprint/components/index.ts
+++ b/config-ui/src/plugins/components/data-scope-select/api.ts
@@ -16,5 +16,7 @@
  *
  */
 
-export * from './advanced-editor';
-export * from './sync-policy';
+import { request } from '@/utils';
+
+export const getDataScope = (plugin: string, connectionId: ID) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scopes`);
diff --git a/config-ui/src/plugins/components/data-scope-select/index.tsx b/config-ui/src/plugins/components/data-scope-select/index.tsx
new file mode 100644
index 000000000..93cf8b353
--- /dev/null
+++ b/config-ui/src/plugins/components/data-scope-select/index.tsx
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ *
+ */
+
+import { useState, useEffect } from 'react';
+import { Button, InputGroup, Intent } from '@blueprintjs/core';
+
+import { FormItem, ExternalLink, Table, Buttons } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { getPluginId } from '@/plugins';
+
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+  plugin: string;
+  connectionId: ID;
+  initialScope?: any[];
+  onCancel?: () => void;
+  onSubmit?: (scope: any) => void;
+}
+
+export const DataScopeSelect = ({ plugin, connectionId, initialScope, onSubmit, onCancel }: Props) => {
+  const [scopeIds, setScopeIds] = useState<ID[]>([]);
+
+  const { ready, data } = useRefreshData(() => API.getDataScope(plugin, connectionId));
+
+  useEffect(() => {
+    setScopeIds((initialScope ?? data ?? []).map((sc: any) => sc[getPluginId(plugin)]) ?? []);
+  }, [data]);
+
+  const handleSubmit = () => {
+    const scope = data.filter((it: any) => scopeIds.includes(it[getPluginId(plugin)]));
+    onSubmit?.(scope);
+  };
+
+  return (
+    <FormItem
+      label="Select Data Scope"
+      subLabel={
+        <>
+          {' '}
+          Select the data scope in this Connection that you wish to associate with this Project. If you wish to add more
+          Data Scope to this Connection, please{' '}
+          <ExternalLink link={`/connections/${plugin}/${connectionId}`}>go to the Connection page</ExternalLink>.
+        </>
+      }
+      required
+    >
+      <S.Wrapper>
+        <div className="action">
+          <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data Scope" />
+        </div>
+        <div className="search">
+          <InputGroup placeholder="Search for Data Scopes" />
+        </div>
+        <Table
+          noShadow
+          loading={!ready}
+          columns={[
+            {
+              title: 'Data Scope',
+              dataIndex: 'name',
+              key: 'name',
+            },
+          ]}
+          dataSource={data}
+          rowSelection={{
+            rowKey: getPluginId(plugin),
+            type: 'checkbox',
+            selectedRowKeys: scopeIds as string[],
+            onChange: (selectedRowKeys) => setScopeIds(selectedRowKeys),
+          }}
+        />
+        <Buttons>
+          <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+          <Button intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} />
+        </Buttons>
+      </S.Wrapper>
+    </FormItem>
+  );
+};
diff --git a/config-ui/src/plugins/components/data-scope-form/styled.ts b/config-ui/src/plugins/components/data-scope-select/styled.ts
similarity index 88%
rename from config-ui/src/plugins/components/data-scope-form/styled.ts
rename to config-ui/src/plugins/components/data-scope-select/styled.ts
index e094ccea8..719705ec3 100644
--- a/config-ui/src/plugins/components/data-scope-form/styled.ts
+++ b/config-ui/src/plugins/components/data-scope-select/styled.ts
@@ -19,14 +19,16 @@
 import styled from 'styled-components';
 
 export const Wrapper = styled.div`
-  .block {
-    margin-bottom: 16px;
+  .action {
+    margin-top: 24px;
   }
 
-  .action {
+  .search {
+    margin-top: 24px;
+    margin-bottom: 24px;
+  }
+
+  .btns {
     display: flex;
-    align-items: center;
-    justify-content: space-between;
-    margin-top: 36px;
   }
 `;
diff --git a/config-ui/src/plugins/components/data-scope/index.tsx b/config-ui/src/plugins/components/data-scope/index.tsx
deleted file mode 100644
index 02bca7c65..000000000
--- a/config-ui/src/plugins/components/data-scope/index.tsx
+++ /dev/null
@@ -1,173 +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.
- *
- */
-
-import { useState, useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Table, Dialog } from '@/components';
-import { DataScopeForm } from '@/plugins';
-
-import * as S from './styled';
-
-interface Props {
-  connections: MixConnection[];
-  cancelBtnProps?: {
-    text?: string;
-  };
-  submitBtnProps?: {
-    text?: string;
-  };
-  onCancel?: () => void;
-  onSubmit?: (connections: MixConnection[]) => void;
-  onNext?: () => void;
-}
-
-export const DataScope = ({ connections, cancelBtnProps, submitBtnProps, onCancel, onSubmit, onNext }: Props) => {
-  const [connection, setConnection] = useState<MixConnection>();
-
-  const error = useMemo(() => (!connections.every((cs) => cs.scope.length) ? true : false), [connections]);
-
-  const handleCancel = () => setConnection(undefined);
-
-  const handleSubmit = (connection: MixConnection, scope: MixConnection['scope'], origin: MixConnection['origin']) => {
-    onSubmit?.(
-      connections.map((cs) => {
-        if (cs.unique === connection.unique) {
-          return {
-            ...cs,
-            scope,
-            origin,
-          };
-        }
-        return cs;
-      }),
-    );
-    handleCancel();
-  };
-
-  if (connections.length === 1) {
-    const [{ plugin, connectionId, scope, origin, ...props }] = connections;
-    return (
-      <DataScopeForm
-        plugin={plugin}
-        connectionId={connectionId}
-        initialScope={origin}
-        initialEntities={scope[0]?.entities}
-        cancelBtnProps={cancelBtnProps}
-        submitBtnProps={submitBtnProps}
-        onCancel={onCancel}
-        onSubmit={(scope: MixConnection['scope'], origin: MixConnection['origin']) => {
-          onSubmit?.([
-            {
-              ...props,
-              plugin,
-              connectionId,
-              scope,
-              origin,
-            },
-          ]);
-          onNext?.();
-        }}
-      />
-    );
-  }
-
-  return (
-    <S.Wrapper>
-      <Table
-        columns={[
-          {
-            title: 'Data Connections',
-            dataIndex: ['icon', 'name'],
-            key: 'connection',
-            render: ({ icon, name }) => (
-              <S.ConnectionColumn>
-                <img src={icon} alt="" />
-                <span>{name}</span>
-              </S.ConnectionColumn>
-            ),
-          },
-          {
-            title: 'Data Scope',
-            dataIndex: 'origin',
-            key: 'scope',
-            render: (scope: MixConnection['origin']) =>
-              !scope.length ? (
-                <span>No Data Scope Selected</span>
-              ) : (
-                <S.ScopeColumn>
-                  {scope.map((sc, i) => (
-                    <S.ScopeItem key={i}>
-                      <span>{sc.name}</span>
-                    </S.ScopeItem>
-                  ))}
-                </S.ScopeColumn>
-              ),
-          },
-          {
-            title: '',
-            dataIndex: 'id',
-            key: 'action',
-            align: 'center',
-            render: (_, connection) => (
-              <Button
-                small
-                minimal
-                intent={Intent.PRIMARY}
-                icon="cog"
-                text="Set Data Scope"
-                onClick={() => setConnection(connection)}
-              />
-            ),
-          },
-        ]}
-        dataSource={connections}
-      />
-      <S.Btns>
-        <Button outlined intent={Intent.PRIMARY} text="Cancel" {...cancelBtnProps} onClick={onCancel} />
-        <Button intent={Intent.PRIMARY} text="Save" {...submitBtnProps} disabled={error} onClick={onNext} />
-      </S.Btns>
-      {connection && (
-        <Dialog
-          isOpen
-          title={
-            <S.DialogTitle>
-              <img src={connection.icon} alt="" />
-              <span>{connection.name}</span>
-              <span>(Set Data Scope)</span>
-            </S.DialogTitle>
-          }
-          footer={null}
-          style={{ width: 820 }}
-          onCancel={handleCancel}
-        >
-          <DataScopeForm
-            plugin={connection.plugin}
-            connectionId={connection.connectionId}
-            initialScope={connection.origin}
-            initialEntities={connection.scope[0]?.entities}
-            onCancel={handleCancel}
-            onSubmit={(scope: MixConnection['scope'], origin: MixConnection['origin']) =>
-              handleSubmit(connection, scope, origin)
-            }
-          />
-        </Dialog>
-      )}
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/plugins/components/data-scope/styled.ts b/config-ui/src/plugins/components/data-scope/styled.ts
deleted file mode 100644
index 8ba790992..000000000
--- a/config-ui/src/plugins/components/data-scope/styled.ts
+++ /dev/null
@@ -1,63 +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.
- *
- */
-
-import styled from 'styled-components';
-
-export const Wrapper = styled.div``;
-
-export const ConnectionColumn = styled.div`
-  display: flex;
-  align-items: center;
-
-  img {
-    margin-right: 4px;
-    width: 20px;
-  }
-`;
-
-export const ScopeColumn = styled.ul``;
-
-export const ScopeItem = styled.li`
-  margin-bottom: 4px;
-
-  &:last-child {
-    margin-bottom: 0;
-  }
-
-  .bp4-button {
-    margin-left: 6px;
-  }
-`;
-
-export const Btns = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 36px;
-`;
-
-export const DialogTitle = styled.div`
-  display: flex;
-  align-items: center;
-
-  img {
-    margin-right: 4px;
-    width: 24px;
-    height: 24px;
-  }
-`;
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index 9fa4e8fdd..6a81da1a4 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -19,11 +19,10 @@
 export * from './connection-form';
 export * from './connection-list';
 export * from './connection-status';
-export * from './data-scope';
-export * from './data-scope-form';
 export * from './data-scope-form-2';
 export * from './data-scope-miller-columns';
 export * from './data-scope-search';
+export * from './data-scope-select';
 export * from './transformation';
 export * from './transformation-form';
 export * from './transformation-select';
diff --git a/config-ui/src/plugins/components/transformation/index.tsx b/config-ui/src/plugins/components/transformation/index.tsx
index 758892369..407cc275d 100644
--- a/config-ui/src/plugins/components/transformation/index.tsx
+++ b/config-ui/src/plugins/components/transformation/index.tsx
@@ -76,14 +76,14 @@ export const Transformation = ({
           return cs;
         }
 
-        const origin = cs.origin.map((sc) => {
+        const scope = cs.scope.map((sc) => {
           if (!scopeIds.includes(sc[getPluginId(cs.plugin)])) {
             return sc;
           }
           return scopes.find((it) => it[getPluginId(cs.plugin)] === sc[getPluginId(cs.plugin)]);
         });
 
-        return { ...cs, origin };
+        return { ...cs, scope };
       }),
     );
     setSelected({
@@ -145,7 +145,7 @@ export const Transformation = ({
                   ),
               },
             ]}
-            dataSource={cs.origin}
+            dataSource={cs.scope}
             rowSelection={
               cs.transformationType === 'for-connection'
                 ? {