You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by mi...@apache.org on 2024/04/29 05:05:14 UTC

(incubator-devlake) branch feat-7407 created (now e000d7a13)

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

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


      at e000d7a13 feat: simplify scope config configuration

This branch includes the following new commits:

     new e000d7a13 feat: simplify scope config configuration

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



(incubator-devlake) 01/01: feat: simplify scope config configuration

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

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

commit e000d7a133fdab802b772c546ee9583ef8fbae62
Author: mintsweet <0x...@gmail.com>
AuthorDate: Mon Apr 29 13:04:58 2024 +0800

    feat: simplify scope config configuration
---
 config-ui/package.json                             |   2 -
 config-ui/src/api/blueprint/index.ts               |   7 +-
 config-ui/src/app/store.ts                         |   3 +-
 config-ui/src/features/index.ts                    |   1 -
 config-ui/src/features/tips/index.ts               |  19 ---
 config-ui/src/features/tips/slice.ts               |  58 ---------
 config-ui/src/plugins/components/index.ts          |   1 +
 .../plugins/components/scope-config-form/index.tsx |   3 +-
 .../components/scope-config-select/index.tsx       |  28 +----
 .../src/plugins/components/scope-config/index.tsx  | 132 +++++++++++++++++++++
 .../routes/blueprint/connection-detail/index.tsx   | 113 +++++++++++++-----
 config-ui/src/routes/connection/connection.tsx     |  72 +++++------
 config-ui/src/routes/layout/layout.tsx             |  72 +----------
 config-ui/src/routes/layout/styled.ts              |  34 ------
 config-ui/src/routes/layout/tips-transition.css    |  38 ------
 config-ui/yarn.lock                                |  40 +------
 16 files changed, 263 insertions(+), 360 deletions(-)

diff --git a/config-ui/package.json b/config-ui/package.json
index f5481d989..8b4a2b693 100644
--- a/config-ui/package.json
+++ b/config-ui/package.json
@@ -43,7 +43,6 @@
     "react-medium-image-zoom": "^5.1.10",
     "react-redux": "^9.1.0",
     "react-router-dom": "^6.22.2",
-    "react-transition-group": "^4.4.5",
     "redux": "^5.0.1",
     "rehype-raw": "^7.0.0",
     "styled-components": "^6.1.8"
@@ -55,7 +54,6 @@
     "@types/react-copy-to-clipboard": "^5.0.7",
     "@types/react-dom": "^18.2.19",
     "@types/react-router-dom": "^5.3.3",
-    "@types/react-transition-group": "^4.4.10",
     "@types/styled-components": "^5.1.34",
     "@vitejs/plugin-react": "^4.2.1",
     "eslint": "^8.56.0",
diff --git a/config-ui/src/api/blueprint/index.ts b/config-ui/src/api/blueprint/index.ts
index 0f63d0210..c514ed744 100644
--- a/config-ui/src/api/blueprint/index.ts
+++ b/config-ui/src/api/blueprint/index.ts
@@ -37,8 +37,9 @@ export const update = (id: ID, data: IBlueprint) => request(`/blueprints/${id}`,
 export const pipelines = (id: ID) => request(`/blueprints/${id}/pipelines`);
 
 type TriggerQuery = {
-  skipCollectors: boolean;
-  fullSync: boolean;
+  skipCollectors?: boolean;
+  fullSync?: boolean;
 };
 
-export const trigger = (id: ID, data: TriggerQuery) => request(`/blueprints/${id}/trigger`, { method: 'post', data });
+export const trigger = (id: ID, data: TriggerQuery = { skipCollectors: false, fullSync: false }) =>
+  request(`/blueprints/${id}/trigger`, { method: 'post', data });
diff --git a/config-ui/src/app/store.ts b/config-ui/src/app/store.ts
index 5725ddc7a..8d2ce0c34 100644
--- a/config-ui/src/app/store.ts
+++ b/config-ui/src/app/store.ts
@@ -18,12 +18,11 @@
 
 import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
 
-import { connectionsSlice, tipsSlice } from '@/features';
+import { connectionsSlice } from '@/features';
 
 export const store = configureStore({
   reducer: {
     connections: connectionsSlice.reducer,
-    tips: tipsSlice.reducer,
   },
 });
 
diff --git a/config-ui/src/features/index.ts b/config-ui/src/features/index.ts
index a247f9b21..4e6e8a4de 100644
--- a/config-ui/src/features/index.ts
+++ b/config-ui/src/features/index.ts
@@ -17,4 +17,3 @@
  */
 
 export * from './connections';
-export * from './tips';
diff --git a/config-ui/src/features/tips/index.ts b/config-ui/src/features/tips/index.ts
deleted file mode 100644
index 513ab48a7..000000000
--- a/config-ui/src/features/tips/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-export * from './slice';
diff --git a/config-ui/src/features/tips/slice.ts b/config-ui/src/features/tips/slice.ts
deleted file mode 100644
index b817aaa5c..000000000
--- a/config-ui/src/features/tips/slice.ts
+++ /dev/null
@@ -1,58 +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 { createSlice } from '@reduxjs/toolkit';
-
-import { RootState } from '@/app/store';
-
-const initialState: {
-  show: boolean;
-  type?: 'data-scope-changed' | 'scope-config-changed';
-  payload?: {
-    pname?: string;
-    blueprintId: ID;
-  };
-} = {
-  show: false,
-};
-
-export const tipsSlice = createSlice({
-  name: 'tips',
-  initialState,
-  reducers: {
-    showTips: (state, action) => {
-      state.show = true;
-      state.type = action.payload.type;
-      state.payload = action.payload.payload;
-    },
-    hideTips: (state) => {
-      state.show = false;
-      state.type = undefined;
-    },
-  },
-});
-
-export const { showTips, hideTips } = tipsSlice.actions;
-
-export default tipsSlice.reducer;
-
-export const selectTipsShow = (state: RootState) => state.tips.show;
-
-export const selectTipsType = (state: RootState) => state.tips.type;
-
-export const selectTipsPayload = (state: RootState) => state.tips.payload;
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index c374a89bb..337e446fe 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -23,5 +23,6 @@ export * from './connection-status';
 export * from './data-scope-remote';
 export * from './data-scope-select';
 export * from './plugin-name';
+export * from './scope-config';
 export * from './scope-config-form';
 export * from './scope-config-select';
diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx
index 7c8d6f0f5..c6ba08d78 100644
--- a/config-ui/src/plugins/components/scope-config-form/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-form/index.tsx
@@ -104,7 +104,8 @@ export const ScopeConfigForm = ({
           : API.scopeConfig.update(plugin, connectionId, scopeConfigId, { name, entities, ...transformation }),
       {
         setOperating,
-        formatMessage: () => (!scopeConfigId ? 'Create scope config successful.' : 'Update scope config successful'),
+        hideToast: !!scopeConfigId,
+        formatMessage: () => 'Create scope config successful.',
       },
     );
 
diff --git a/config-ui/src/plugins/components/scope-config-select/index.tsx b/config-ui/src/plugins/components/scope-config-select/index.tsx
index e1105c3c7..dfc37a163 100644
--- a/config-ui/src/plugins/components/scope-config-select/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-select/index.tsx
@@ -17,7 +17,7 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import { PlusOutlined, EditOutlined } from '@ant-design/icons';
+import { PlusOutlined } from '@ant-design/icons';
 import { Flex, Table, Button, Modal } from 'antd';
 
 import API from '@/api';
@@ -37,7 +37,6 @@ export const ScopeConfigSelect = ({ plugin, connectionId, scopeConfigId, onCance
   const [version, setVersion] = useState(1);
   const [trId, setTrId] = useState<ID>();
   const [open, setOpen] = useState(false);
-  const [updatedId, setUpdatedId] = useState<ID>();
 
   const { ready, data } = useRefreshData(() => API.scopeConfig.list(plugin, connectionId), [version]);
 
@@ -56,12 +55,6 @@ export const ScopeConfigSelect = ({ plugin, connectionId, scopeConfigId, onCance
 
   const handleHideDialog = () => {
     setOpen(false);
-    setUpdatedId(undefined);
-  };
-
-  const handleUpdate = async (id: ID) => {
-    setUpdatedId(id);
-    handleShowDialog();
   };
 
   const handleSubmit = async (trId: ID) => {
@@ -72,7 +65,7 @@ export const ScopeConfigSelect = ({ plugin, connectionId, scopeConfigId, onCance
 
   return (
     <Flex vertical gap="middle">
-      <Flex>
+      <Flex style={{ marginTop: 20 }}>
         <Button type="primary" icon={<PlusOutlined />} onClick={handleShowDialog}>
           Add New Scope Config
         </Button>
@@ -81,17 +74,7 @@ export const ScopeConfigSelect = ({ plugin, connectionId, scopeConfigId, onCance
         rowKey="id"
         size="small"
         loading={!ready}
-        columns={[
-          { title: 'Name', dataIndex: 'name', key: 'name' },
-          {
-            title: '',
-            dataIndex: 'id',
-            key: 'id',
-            width: 100,
-            render: (id) =>
-              id !== 'None' ? <Button type="link" icon={<EditOutlined />} onClick={() => handleUpdate(id)} /> : null,
-          },
-        ]}
+        columns={[{ title: 'Name', dataIndex: 'name', key: 'name' }]}
         dataSource={dataSource}
         rowSelection={{
           type: 'radio',
@@ -114,15 +97,12 @@ export const ScopeConfigSelect = ({ plugin, connectionId, scopeConfigId, onCance
         width={960}
         centered
         footer={null}
-        title={!updatedId ? 'Add Scope Config' : 'Edit Scope Config'}
+        title="Add Scope Config"
         onCancel={handleHideDialog}
       >
         <ScopeConfigForm
           plugin={plugin}
           connectionId={connectionId}
-          defaultName={`shared-config-<${dataSource.length}>`}
-          showWarning={!!updatedId}
-          scopeConfigId={updatedId}
           onCancel={handleHideDialog}
           onSubmit={handleSubmit}
         />
diff --git a/config-ui/src/plugins/components/scope-config/index.tsx b/config-ui/src/plugins/components/scope-config/index.tsx
new file mode 100644
index 000000000..7dd5b7e05
--- /dev/null
+++ b/config-ui/src/plugins/components/scope-config/index.tsx
@@ -0,0 +1,132 @@
+/*
+ * 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 { LinkOutlined, EditOutlined } from '@ant-design/icons';
+import { Button, Modal } from 'antd';
+import styled from 'styled-components';
+
+import API from '@/api';
+import { operator } from '@/utils';
+
+import { PluginName } from '../plugin-name';
+import { ScopeConfigSelect } from '../scope-config-select';
+import { ScopeConfigForm } from '../scope-config-form';
+
+const Wrapper = styled.div``;
+
+interface Props {
+  plugin: string;
+  connectionId: ID;
+  scopeId: ID;
+  id?: ID;
+  name?: string;
+  onSuccess?: () => void;
+}
+
+export const ScopeConfig = ({ plugin, connectionId, scopeId, id, name, onSuccess }: Props) => {
+  const [type, setType] = useState<'associate' | 'update'>();
+
+  const handleHideDialog = () => setType(undefined);
+
+  const handleAssociate = async (trId: ID) => {
+    const [success] = await operator(
+      () => API.scope.update(plugin, connectionId, scopeId, { scopeConfigId: trId !== 'None' ? +trId : null }),
+      {
+        hideToast: true,
+      },
+    );
+
+    if (success) {
+      handleHideDialog();
+      onSuccess?.();
+    }
+  };
+
+  const handleUpdate = (trId: ID) => {
+    handleHideDialog();
+    onSuccess?.();
+  };
+
+  return (
+    <Wrapper>
+      <span>{id ? name : 'N/A'}</span>
+      <Button
+        size="small"
+        type="link"
+        icon={<LinkOutlined />}
+        onClick={() => {
+          setType('associate');
+        }}
+      />
+      {id && (
+        <Button
+          size="small"
+          type="link"
+          icon={<EditOutlined />}
+          onClick={() => {
+            // TO-DO: check if the scope config is associated with any scope
+            setType('update');
+          }}
+        />
+      )}
+      {type === 'associate' && (
+        <Modal
+          open
+          width={960}
+          centered
+          footer={null}
+          title={<PluginName plugin={plugin} name="Associate Scope Config" />}
+          onCancel={handleHideDialog}
+        >
+          {plugin === 'tapd' ? (
+            <ScopeConfigForm
+              plugin={plugin}
+              connectionId={connectionId}
+              scopeConfigId={id}
+              scopeId={scopeId}
+              onCancel={handleHideDialog}
+              onSubmit={handleAssociate}
+            />
+          ) : (
+            <ScopeConfigSelect
+              plugin={plugin}
+              connectionId={connectionId}
+              scopeConfigId={id}
+              onCancel={handleHideDialog}
+              onSubmit={handleAssociate}
+            />
+          )}
+        </Modal>
+      )}
+      {type === 'update' && (
+        <Modal open width={960} centered footer={null} title="Edit Scope Config" onCancel={handleHideDialog}>
+          <ScopeConfigForm
+            plugin={plugin}
+            connectionId={connectionId}
+            showWarning
+            scopeConfigId={id}
+            scopeId={scopeId}
+            onCancel={handleHideDialog}
+            onSubmit={handleUpdate}
+          />
+        </Modal>
+      )}
+    </Wrapper>
+  );
+};
diff --git a/config-ui/src/routes/blueprint/connection-detail/index.tsx b/config-ui/src/routes/blueprint/connection-detail/index.tsx
index 35687cb29..7d62415f4 100644
--- a/config-ui/src/routes/blueprint/connection-detail/index.tsx
+++ b/config-ui/src/routes/blueprint/connection-detail/index.tsx
@@ -24,9 +24,8 @@ import { Flex, Table, Popconfirm, Modal, Button } from 'antd';
 import API from '@/api';
 import { PageLoading, PageHeader, ExternalLink } from '@/components';
 import { PATHS } from '@/config';
-import { showTips } from '@/features';
-import { useAppDispatch, useRefreshData } from '@/hooks';
-import { DataScopeSelect, getPluginConfig, getPluginScopeId } from '@/plugins';
+import { useRefreshData } from '@/hooks';
+import { ScopeConfig, DataScopeSelect, getPluginScopeId } from '@/plugins';
 import { operator } from '@/utils';
 
 import * as S from './styled';
@@ -34,11 +33,12 @@ import * as S from './styled';
 export const BlueprintConnectionDetailPage = () => {
   const [version, setVersion] = useState(1);
   const [open, setOpen] = useState(false);
+  const [operating, setOperating] = useState(false);
 
   const { pname, bid, unique } = useParams() as { pname?: string; bid?: string; unique: string };
   const navigate = useNavigate();
 
-  const dispatch = useAppDispatch();
+  const [modal, contextHolder] = Modal.useModal();
 
   const getBlueprint = async (pname?: string, bid?: string) => {
     if (pname) {
@@ -51,8 +51,6 @@ export const BlueprintConnectionDetailPage = () => {
 
   const [plugin, connectionId] = unique.split('-');
 
-  const pluginConfig = getPluginConfig(plugin);
-
   const { ready, data } = useRefreshData(async () => {
     const [blueprint, connection] = await Promise.all([
       getBlueprint(pname, bid),
@@ -92,16 +90,16 @@ export const BlueprintConnectionDetailPage = () => {
   const handleShowDataScope = () => setOpen(true);
   const handleHideDataScope = () => setOpen(false);
 
-  const handleShowTips = () =>
-    dispatch(
-      showTips({
-        type: 'data-scope-changed',
-        payload: {
-          pname,
-          blueprintId: blueprint.id,
-        },
-      }),
-    );
+  const handleRun = async (data?: { skipCollectors?: boolean; fullSync?: boolean }) => {
+    const [success] = await operator(() => API.blueprint.trigger(blueprint.id, data), {
+      setOperating,
+      hideToast: true,
+    });
+
+    if (success) {
+      navigate(pname ? PATHS.PROJECT(pname, { tab: 'status' }) : PATHS.BLUEPRINT(blueprint.id, 'status'));
+    }
+  };
 
   const handleRemoveConnection = async () => {
     const [success] = await operator(() =>
@@ -114,8 +112,25 @@ export const BlueprintConnectionDetailPage = () => {
     );
 
     if (success) {
-      handleShowTips();
-      navigate(pname ? PATHS.PROJECT(pname, { tab: 'configuration' }) : PATHS.BLUEPRINT(blueprint.id, 'configuration'));
+      modal.success({
+        closable: true,
+        centered: true,
+        width: 500,
+        title: 'Data Scope Changed',
+        content: 'Re-collect the data to get the project metrics updated?',
+        footer: (
+          <div style={{ marginTop: 20, textAlign: 'center' }}>
+            <Button type="primary" loading={operating} onClick={() => handleRun()}>
+              Recollect Data
+            </Button>
+          </div>
+        ),
+        onCancel: () => {
+          navigate(
+            pname ? PATHS.PROJECT(pname, { tab: 'configuration' }) : PATHS.BLUEPRINT(blueprint.id, 'configuration'),
+          );
+        },
+      });
     }
   };
 
@@ -135,17 +150,52 @@ export const BlueprintConnectionDetailPage = () => {
           }),
         }),
       {
-        formatMessage: () => 'Update data scope successful.',
+        hideToast: true,
       },
     );
 
     if (success) {
-      handleShowTips();
       handleHideDataScope();
-      setVersion((v) => v + 1);
+      modal.success({
+        closable: true,
+        centered: true,
+        width: 500,
+        title: 'Data Scope Changed',
+        content: 'Re-collect the data to get the project metrics updated?',
+        footer: (
+          <div style={{ marginTop: 20, textAlign: 'center' }}>
+            <Button type="primary" loading={operating} onClick={() => handleRun()}>
+              Recollect Data
+            </Button>
+          </div>
+        ),
+        onCancel: () => {
+          setVersion(version + 1);
+        },
+      });
     }
   };
 
+  const handleChangeScopeConfig = () => {
+    modal.success({
+      closable: true,
+      centered: true,
+      width: 550,
+      title: 'Scope Config Saved',
+      content: 'Please re-transform data to apply the updated scope config.',
+      footer: (
+        <div style={{ marginTop: 20, textAlign: 'center' }}>
+          <Button type="primary" loading={operating} onClick={() => handleRun({ skipCollectors: true })}>
+            Re-transform now
+          </Button>
+        </div>
+      ),
+      onCancel: () => {
+        setVersion(version + 1);
+      },
+    });
+  };
+
   return (
     <PageHeader
       breadcrumbs={
@@ -165,7 +215,7 @@ export const BlueprintConnectionDetailPage = () => {
     >
       <S.Top>
         <span>
-          If you would like to edit the Data Scope or Scope Config of this Connection, please{' '}
+          To manage the complete data scope and scope config for this Connection, please{' '}
           <ExternalLink link={`/connections/${connection.plugin}/${connection.id}`}>
             go to the Connection detail page
           </ExternalLink>
@@ -192,13 +242,6 @@ export const BlueprintConnectionDetailPage = () => {
           <Button type="primary" icon={<FormOutlined />} onClick={handleShowDataScope}>
             Manage Data Scope
           </Button>
-          {pluginConfig.scopeConfig && (
-            <ExternalLink style={{ marginLeft: 8 }} link={PATHS.CONNECTION(connection.plugin, connection.id)}>
-              <Button type="primary" icon={<FormOutlined />}>
-                Edit Scope Config
-              </Button>
-            </ExternalLink>
-          )}
         </Flex>
         <Table
           rowKey="id"
@@ -212,7 +255,16 @@ export const BlueprintConnectionDetailPage = () => {
             {
               title: 'Scope Config',
               key: 'scopeConfig',
-              render: (_, { scopeConfigId, scopeConfigName }) => (scopeConfigId ? scopeConfigName : 'N/A'),
+              render: (_, { id, scopeConfigId, scopeConfigName }) => (
+                <ScopeConfig
+                  plugin={plugin}
+                  connectionId={connectionId}
+                  scopeId={id}
+                  id={scopeConfigId}
+                  name={scopeConfigName}
+                  onSuccess={handleChangeScopeConfig}
+                />
+              ),
             },
           ]}
           dataSource={scopes}
@@ -228,6 +280,7 @@ export const BlueprintConnectionDetailPage = () => {
           onSubmit={handleChangeDataScope}
         />
       </Modal>
+      {contextHolder}
     </PageHeader>
   );
 };
diff --git a/config-ui/src/routes/connection/connection.tsx b/config-ui/src/routes/connection/connection.tsx
index 957a793a7..70ca4dc4d 100644
--- a/config-ui/src/routes/connection/connection.tsx
+++ b/config-ui/src/routes/connection/connection.tsx
@@ -18,21 +18,21 @@
 
 import { useState, useMemo } from 'react';
 import { useParams, useNavigate, Link } from 'react-router-dom';
-import { DeleteOutlined, PlusOutlined, NodeIndexOutlined, LinkOutlined, ClearOutlined } from '@ant-design/icons';
+import { DeleteOutlined, PlusOutlined, LinkOutlined, ClearOutlined } from '@ant-design/icons';
 import { theme, Space, Table, Button, Modal, message } from 'antd';
 
 import API from '@/api';
 import { PageHeader, Message } from '@/components';
 import { PATHS } from '@/config';
 import { useAppDispatch, useAppSelector } from '@/hooks';
-import { selectConnection, removeConnection, showTips } from '@/features';
+import { selectConnection, removeConnection } from '@/features';
 import { useRefreshData } from '@/hooks';
 import {
   ConnectionStatus,
   DataScopeRemote,
   getPluginConfig,
   getPluginScopeId,
-  ScopeConfigForm,
+  ScopeConfig,
   ScopeConfigSelect,
 } from '@/plugins';
 import { IConnection } from '@/types';
@@ -56,7 +56,6 @@ export const Connection = () => {
   const [pageSize, setPageSize] = useState(10);
   const [scopeId, setScopeId] = useState<ID>();
   const [scopeIds, setScopeIds] = useState<ID[]>([]);
-  const [scopeConfigId, setScopeConfigId] = useState<ID>();
   const [conflict, setConflict] = useState<string[]>([]);
   const [errorMsg, setErrorMsg] = useState('');
 
@@ -97,7 +96,7 @@ export const Connection = () => {
   const handleHideDialog = () => {
     setType(undefined);
   };
-  
+
   const handleShowDeleteDialog = () => {
     setType('deleteConnection');
   };
@@ -217,12 +216,19 @@ export const Connection = () => {
     );
 
     if (success) {
-      setVersion((v) => v + 1);
-      dispatch(showTips({ type: 'scope-config-changed' }));
       handleHideDialog();
+      setVersion(version + 1);
+      message.success(
+        'Scope Config(s) have been updated. If you would like to re-transform or re-collect the data in the related project(s), please go to the Project page and do so.',
+      );
     }
   };
 
+  const handleScopeConfigChange = () => {
+    // TO-DO: check scope config change will effect the scope config
+    setVersion(version + 1);
+  };
+
   return (
     <PageHeader
       breadcrumbs={[
@@ -250,7 +256,7 @@ export const Connection = () => {
               style={{ marginLeft: 8 }}
               type="primary"
               disabled={!scopeIds.length}
-              icon={<NodeIndexOutlined />}
+              icon={<LinkOutlined />}
               onClick={() => handleShowScopeConfigSelectDialog(scopeIds)}
             >
               Associate Scope Config
@@ -290,22 +296,16 @@ export const Connection = () => {
             {
               title: 'Scope Config',
               key: 'scopeConfig',
-              align: 'center',
               width: 400,
               render: (_, { id, configId, configName }) => (
-                <>
-                  <span>{configId ? configName : 'N/A'}</span>
-                  {pluginConfig.scopeConfig && (
-                    <Button
-                      type="link"
-                      icon={<LinkOutlined />}
-                      onClick={() => {
-                        handleShowScopeConfigSelectDialog([id]);
-                        setScopeConfigId(configId);
-                      }}
-                    />
-                  )}
-                </>
+                <ScopeConfig
+                  plugin={plugin}
+                  connectionId={connectionId}
+                  scopeId={id}
+                  id={configId}
+                  name={configName}
+                  onSuccess={handleScopeConfigChange}
+                />
               ),
             },
             {
@@ -332,10 +332,10 @@ export const Connection = () => {
             pageSize,
             total,
             onChange: setPage,
-            onShowSizeChange: (current,size)=> {
+            onShowSizeChange: (_, size) => {
               setPage(1);
               setPageSize(size);
-            }
+            },
           }}
           rowSelection={{
             selectedRowKeys: scopeIds,
@@ -436,24 +436,12 @@ export const Connection = () => {
           }
           onCancel={handleHideDialog}
         >
-          {plugin === 'tapd' ? (
-            <ScopeConfigForm
-              plugin={plugin}
-              connectionId={connectionId}
-              scopeId={scopeIds[0]}
-              scopeConfigId={scopeConfigId}
-              onCancel={handleHideDialog}
-              onSubmit={handleAssociateScopeConfig}
-            />
-          ) : (
-            <ScopeConfigSelect
-              plugin={plugin}
-              connectionId={connectionId}
-              scopeConfigId={scopeConfigId}
-              onCancel={handleHideDialog}
-              onSubmit={handleAssociateScopeConfig}
-            />
-          )}
+          <ScopeConfigSelect
+            plugin={plugin}
+            connectionId={connectionId}
+            onCancel={handleHideDialog}
+            onSubmit={handleAssociateScopeConfig}
+          />
         </Modal>
       )}
       {type === 'deleteConnectionFailed' && (
diff --git a/config-ui/src/routes/layout/layout.tsx b/config-ui/src/routes/layout/layout.tsx
index bf54a57e9..168a203b0 100644
--- a/config-ui/src/routes/layout/layout.tsx
+++ b/config-ui/src/routes/layout/layout.tsx
@@ -16,38 +16,22 @@
  *
  */
 
-import { useState, useEffect, useRef } from 'react';
+import { useState, useEffect } from 'react';
 import { useLoaderData, Outlet, useNavigate, useLocation } from 'react-router-dom';
-import { CSSTransition } from 'react-transition-group';
-import { CloseOutlined } from '@ant-design/icons';
-import { Layout as AntdLayout, Menu, Flex, Divider, Tooltip, Button } from 'antd';
+import { Layout as AntdLayout, Menu, Divider } from 'antd';
 
-import API from '@/api';
-import { PageLoading, Logo, ExternalLink, Message } from '@/components';
-import { PATHS } from '@/config';
-import {
-  init,
-  selectError,
-  selectStatus,
-  selectTipsShow,
-  selectTipsType,
-  selectTipsPayload,
-  hideTips,
-} from '@/features';
+import { PageLoading, Logo, ExternalLink } from '@/components';
+import { init, selectError, selectStatus } from '@/features';
 import { OnboardCard } from '@/routes/onboard/components';
 import { useAppDispatch, useAppSelector } from '@/hooks';
-import { operator } from '@/utils';
 
 import { menuItems, menuItemsMatch, headerItems } from './config';
-import * as S from './styled';
-import './tips-transition.css';
 
 const { Sider, Header, Content, Footer } = AntdLayout;
 
 export const Layout = () => {
   const [openKeys, setOpenKeys] = useState<string[]>([]);
   const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
-  const [operating, setOperating] = useState(false);
 
   const { version, plugins } = useLoaderData() as { version: string; plugins: string[] };
 
@@ -57,11 +41,6 @@ export const Layout = () => {
   const dispatch = useAppDispatch();
   const status = useAppSelector(selectStatus);
   const error = useAppSelector(selectError);
-  const tipsShow = useAppSelector(selectTipsShow);
-  const tipsType = useAppSelector(selectTipsType);
-  const tipsPayload = useAppSelector(selectTipsPayload);
-
-  const tipsRef = useRef(null);
 
   useEffect(() => {
     dispatch(init(plugins));
@@ -96,26 +75,6 @@ export const Layout = () => {
     throw error.message;
   }
 
-  const handleRunBP = async () => {
-    if (!tipsPayload) {
-      return;
-    }
-
-    const { blueprintId, pname } = tipsPayload;
-
-    const [success] = await operator(
-      () => API.blueprint.trigger(tipsPayload.blueprintId, { skipCollectors: false, fullSync: false }),
-      {
-        setOperating,
-        formatMessage: () => 'Trigger blueprint successful.',
-      },
-    );
-
-    if (success) {
-      navigate(pname ? PATHS.PROJECT(pname) : PATHS.BLUEPRINT(blueprintId));
-    }
-  };
-
   return (
     <AntdLayout style={{ height: '100vh' }}>
       <Sider>
@@ -173,29 +132,6 @@ export const Layout = () => {
             </Footer>
           )}
         </Content>
-        <CSSTransition in={!!tipsShow} unmountOnExit timeout={300} nodeRef={tipsRef} classNames="tips">
-          <S.Tips ref={tipsRef}>
-            <div className="content">
-              {tipsType === 'data-scope-changed' && (
-                <Flex gap="middle">
-                  <Message content="The change of Data Scope(s) will affect the metrics of this project. Would you like to recollect the data to get them updated?" />
-                  <Button type="primary" loading={operating} onClick={handleRunBP}>
-                    Recollect Data
-                  </Button>
-                </Flex>
-              )}
-              {tipsType === 'scope-config-changed' && (
-                <Message
-                  content="Scope Config(s) have been updated. If you would like to re-transform or re-collect the data in the related
-              project(s), please go to the Project page and do so."
-                />
-              )}
-            </div>
-            <Tooltip title="Close">
-              <Button shape="circle" ghost icon={<CloseOutlined />} onClick={() => dispatch(hideTips())} />
-            </Tooltip>
-          </S.Tips>
-        </CSSTransition>
       </AntdLayout>
     </AntdLayout>
   );
diff --git a/config-ui/src/routes/layout/styled.ts b/config-ui/src/routes/layout/styled.ts
deleted file mode 100644
index 3129357a4..000000000
--- a/config-ui/src/routes/layout/styled.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 styled from 'styled-components';
-
-export const Tips = styled.div`
-  display: flex;
-  align-items: center;
-  padding: 12px 24px;
-  color: #fff;
-  background-color: #3c5088;
-
-  .content {
-    flex: auto;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-`;
diff --git a/config-ui/src/routes/layout/tips-transition.css b/config-ui/src/routes/layout/tips-transition.css
deleted file mode 100644
index 2751ae3e6..000000000
--- a/config-ui/src/routes/layout/tips-transition.css
+++ /dev/null
@@ -1,38 +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.
- *
- */
-
-.tips-enter {
-  opacity: 0;
-  transform: scale(0.8);
-}
-
-.tips-enter-active {
-  opacity: 1;
-  transform: translateX(0);
-  transition: opacity 300ms, transform 300ms;
-}
-
-.tips-exit {
-  opacity: 1;
-}
-
-.tips-exit-active {
-  opacity: 0;
-  transform: scale(0.8);
-  transition: opacity 300ms, transform 300ms;
-}
diff --git a/config-ui/yarn.lock b/config-ui/yarn.lock
index 92046a1b9..2ef12da13 100644
--- a/config-ui/yarn.lock
+++ b/config-ui/yarn.lock
@@ -1646,7 +1646,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.5.5, @babel/runtime@n [...]
+"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.8.4":
   version: 7.24.0
   resolution: "@babel/runtime@npm:7.24.0"
   dependencies:
@@ -2668,15 +2668,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/react-transition-group@npm:^4.4.10":
-  version: 4.4.10
-  resolution: "@types/react-transition-group@npm:4.4.10"
-  dependencies:
-    "@types/react": "*"
-  checksum: fe2ea11f70251e9f79f368e198c18fd469b1d4f1e1d44e4365845b44e15974b0ec925100036f449b023b0ca3480a82725c5f0a73040e282ad32ec7b0def9b57c
-  languageName: node
-  linkType: hard
-
 "@types/react@npm:*, @types/react@npm:^18.2.56":
   version: 18.2.60
   resolution: "@types/react@npm:18.2.60"
@@ -3675,7 +3666,6 @@ __metadata:
     "@types/react-copy-to-clipboard": ^5.0.7
     "@types/react-dom": ^18.2.19
     "@types/react-router-dom": ^5.3.3
-    "@types/react-transition-group": ^4.4.10
     "@types/styled-components": ^5.1.34
     "@vitejs/plugin-react": ^4.2.1
     ahooks: ^3.7.10
@@ -3704,7 +3694,6 @@ __metadata:
     react-medium-image-zoom: ^5.1.10
     react-redux: ^9.1.0
     react-router-dom: ^6.22.2
-    react-transition-group: ^4.4.5
     redux: ^5.0.1
     rehype-raw: ^7.0.0
     styled-components: ^6.1.8
@@ -3967,16 +3956,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"dom-helpers@npm:^5.0.1":
-  version: 5.2.1
-  resolution: "dom-helpers@npm:5.2.1"
-  dependencies:
-    "@babel/runtime": ^7.8.7
-    csstype: ^3.0.2
-  checksum: 863ba9e086f7093df3376b43e74ce4422571d404fc9828bf2c56140963d5edf0e56160f9b2f3bb61b282c07f8fc8134f023c98fd684bddcb12daf7b0f14d951c
-  languageName: node
-  linkType: hard
-
 "dot-case@npm:^3.0.4":
   version: 3.0.4
   resolution: "dot-case@npm:3.0.4"
@@ -7052,7 +7031,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1":
+"prop-types@npm:^15.8.1":
   version: 15.8.1
   resolution: "prop-types@npm:15.8.1"
   dependencies:
@@ -7786,21 +7765,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-transition-group@npm:^4.4.5":
-  version: 4.4.5
-  resolution: "react-transition-group@npm:4.4.5"
-  dependencies:
-    "@babel/runtime": ^7.5.5
-    dom-helpers: ^5.0.1
-    loose-envify: ^1.4.0
-    prop-types: ^15.6.2
-  peerDependencies:
-    react: ">=16.6.0"
-    react-dom: ">=16.6.0"
-  checksum: 75602840106aa9c6545149d6d7ae1502fb7b7abadcce70a6954c4b64a438ff1cd16fc77a0a1e5197cdd72da398f39eb929ea06f9005c45b132ed34e056ebdeb1
-  languageName: node
-  linkType: hard
-
 "react@npm:^18.2.0":
   version: 18.2.0
   resolution: "react@npm:18.2.0"