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

[incubator-devlake] branch refactor-6026 created (now a71b17ddd)

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

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


      at a71b17ddd refactor(config-ui): remove field connection type from connection

This branch includes the following new commits:

     new 464f36451 refactor(config-ui): remove connections context
     new 2857b1027 refactor(config-ui): use redux to create connections store
     new a71b17ddd refactor(config-ui): remove field connection type from connection

The 3 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] 02/03: refactor(config-ui): use redux to create connections store

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

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

commit 2857b10278bab3614c4e2748c0cd968282236e1b
Author: mintsweet <0x...@gmail.com>
AuthorDate: Wed Oct 18 19:01:12 2023 +1300

    refactor(config-ui): use redux to create connections store
---
 config-ui/package.json                             |   3 +
 config-ui/src/api/connection/types.ts              |   2 +
 config-ui/src/{main.tsx => app/hook.ts}            |  10 +-
 config-ui/src/{main.tsx => app/store.ts}           |  14 +-
 .../{main.tsx => features/connections/index.ts}    |   8 +-
 .../{main.tsx => features/connections/name.tsx}    |  16 +-
 config-ui/src/features/connections/slice.ts        | 122 +++++++++++++
 config-ui/src/features/connections/utils.ts        |  45 +++++
 config-ui/src/{main.tsx => features/index.ts}      |   7 +-
 config-ui/src/main.tsx                             |   9 +-
 .../components/add-connection-dialog/index.tsx     |  11 +-
 .../pages/blueprint/detail/configuration-panel.tsx |  20 +--
 config-ui/src/pages/blueprint/home/index.tsx       |  37 ++--
 config-ui/src/pages/connection/detail/index.tsx    |  19 +-
 config-ui/src/pages/connection/home/index.tsx      |  74 ++++----
 config-ui/src/pages/project/home/index.tsx         |  25 +--
 .../plugins/components/connection-form/index.tsx   |  26 +--
 .../plugins/components/connection-list/index.tsx   |  16 +-
 .../plugins/components/connection-status/index.tsx |  33 ++--
 .../register/webhook/components/create-dialog.tsx  |   4 -
 .../register/webhook/components/delete-dialog.tsx  |   4 -
 .../register/webhook/components/edit-dialog.tsx    |   4 -
 .../src/plugins/register/webhook/connection.tsx    |   5 +-
 config-ui/src/routes/layout/layout.tsx             | 198 +++++++++++----------
 .../connection/types.ts => types/connection.ts}    |  47 ++---
 config-ui/src/{main.tsx => types/index.ts}         |   7 +-
 config-ui/yarn.lock                                | 133 +++++++++++++-
 27 files changed, 588 insertions(+), 311 deletions(-)

diff --git a/config-ui/package.json b/config-ui/package.json
index e0e49a479..fb4cf6220 100644
--- a/config-ui/package.json
+++ b/config-ui/package.json
@@ -27,6 +27,7 @@
     "@blueprintjs/datetime2": "^1.0.10",
     "@blueprintjs/popover2": "^2.0.10",
     "@blueprintjs/select": "^5.0.10",
+    "@reduxjs/toolkit": "^1.9.7",
     "ahooks": "^3.7.8",
     "axios": "^0.21.4",
     "classnames": "^2.3.2",
@@ -39,8 +40,10 @@
     "react-copy-to-clipboard": "^5.1.0",
     "react-dom": "17.0.2",
     "react-is": "^18.2.0",
+    "react-redux": "^8.1.3",
     "react-router-dom": "^6.14.1",
     "react-transition-group": "^4.4.5",
+    "redux": "^4.2.1",
     "styled-components": "^5.3.6"
   },
   "devDependencies": {
diff --git a/config-ui/src/api/connection/types.ts b/config-ui/src/api/connection/types.ts
index a61f954a7..2d83c0c2b 100644
--- a/config-ui/src/api/connection/types.ts
+++ b/config-ui/src/api/connection/types.ts
@@ -27,6 +27,8 @@ export type Connection = {
   proxy: string;
   apiKey?: string;
   dbUrl?: string;
+  appId?: string;
+  secretKey?: string;
 };
 
 export type ConnectionForm = {
diff --git a/config-ui/src/main.tsx b/config-ui/src/app/hook.ts
similarity index 72%
copy from config-ui/src/main.tsx
copy to config-ui/src/app/hook.ts
index 682db7df5..c945a1986 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/app/hook.ts
@@ -16,9 +16,9 @@
  *
  */
 
-import ReactDOM from 'react-dom';
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import type { RootState, AppDispatch } from './store';
 
-import { App } from './App';
-import './index.css';
-
-ReactDOM.render(<App />, document.getElementById('root'));
+type DispatchFunc = () => AppDispatch;
+export const useAppDispatch: DispatchFunc = useDispatch;
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
diff --git a/config-ui/src/main.tsx b/config-ui/src/app/store.ts
similarity index 64%
copy from config-ui/src/main.tsx
copy to config-ui/src/app/store.ts
index 682db7df5..4e27d7f1d 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/app/store.ts
@@ -16,9 +16,15 @@
  *
  */
 
-import ReactDOM from 'react-dom';
+import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
+import ConnectionSlice from '@/features/connections/slice';
 
-import { App } from './App';
-import './index.css';
+export const store = configureStore({
+  reducer: {
+    connections: ConnectionSlice,
+  },
+});
 
-ReactDOM.render(<App />, document.getElementById('root'));
+export type AppDispatch = typeof store.dispatch;
+export type RootState = ReturnType<typeof store.getState>;
+export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>;
diff --git a/config-ui/src/main.tsx b/config-ui/src/features/connections/index.ts
similarity index 84%
copy from config-ui/src/main.tsx
copy to config-ui/src/features/connections/index.ts
index 682db7df5..51ef44992 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/features/connections/index.ts
@@ -16,9 +16,5 @@
  *
  */
 
-import ReactDOM from 'react-dom';
-
-import { App } from './App';
-import './index.css';
-
-ReactDOM.render(<App />, document.getElementById('root'));
+export * from './slice';
+export * from './name';
diff --git a/config-ui/src/main.tsx b/config-ui/src/features/connections/name.tsx
similarity index 65%
copy from config-ui/src/main.tsx
copy to config-ui/src/features/connections/name.tsx
index 682db7df5..51482969d 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/features/connections/name.tsx
@@ -16,9 +16,17 @@
  *
  */
 
-import ReactDOM from 'react-dom';
+import { useAppSelector } from '@/app/hook';
 
-import { App } from './App';
-import './index.css';
+import { selectConnection } from './slice';
+import { IConnection } from '@/types';
 
-ReactDOM.render(<App />, document.getElementById('root'));
+interface Props {
+  plugin: string;
+  connectionId: ID;
+}
+
+export const ConnectionName = ({ plugin, connectionId }: Props) => {
+  const connection = useAppSelector((state) => selectConnection(state, `${plugin}-${connectionId}`)) as IConnection;
+  return <span>{connection.name}</span>;
+};
diff --git a/config-ui/src/features/connections/slice.ts b/config-ui/src/features/connections/slice.ts
new file mode 100644
index 000000000..66b534d1f
--- /dev/null
+++ b/config-ui/src/features/connections/slice.ts
@@ -0,0 +1,122 @@
+/*
+ * 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, createAsyncThunk } from '@reduxjs/toolkit';
+import { flatten } from 'lodash';
+
+import API from '@/api';
+import type { ConnectionForm } from '@/api/connection/types';
+import { RootState } from '@/app/store';
+import { PluginConfig } from '@/plugins';
+import { IConnection, IConnectionStatus } from '@/types';
+
+import { transformConnection } from './utils';
+
+const initialState: {
+  connections: IConnection[];
+} = {
+  connections: [],
+};
+
+export const init = createAsyncThunk('connections/init', async () => {
+  const res = await Promise.all(
+    PluginConfig.map(async ({ plugin }) => {
+      const connections = await API.connection.list(plugin);
+      return connections.map((connection) => transformConnection(plugin, connection));
+    }),
+  );
+  return flatten(res);
+});
+
+export const fetchConnections = createAsyncThunk('connections/fetchConnections', async (plugin: string) => {
+  const connections = await API.connection.list(plugin);
+  return {
+    plugin,
+    connections: connections.map((connection) => transformConnection(plugin, connection)),
+  };
+});
+
+export const testConnection = createAsyncThunk(
+  'connections/testConnection',
+  async ({ unique, plugin, endpoint, proxy, token, username, password, authMethod, secretKey, appId }: IConnection) => {
+    const res = await API.connection.test(plugin, {
+      endpoint,
+      proxy,
+      token,
+      username,
+      password,
+      authMethod,
+      secretKey,
+      appId,
+    });
+
+    return {
+      unique,
+      status: res.success ? IConnectionStatus.ONLINE : IConnectionStatus.OFFLINE,
+    };
+  },
+);
+
+export const addConnection = createAsyncThunk('connections/addConnection', async ({ plugin, ...payload }: any) => {
+  const connection = await API.connection.create(plugin, payload);
+  return transformConnection(plugin, connection);
+});
+
+export const updateConnection = createAsyncThunk('connections/updateConnection', async (payload: ConnectionForm) => {});
+
+export const slice = createSlice({
+  name: 'connections',
+  initialState,
+  reducers: {},
+  extraReducers(builder) {
+    builder
+      .addCase(init.fulfilled, (state, action) => {
+        state.connections = action.payload;
+      })
+      .addCase(fetchConnections.fulfilled, (state, action) => {
+        state.connections = state.connections.concat(action.payload.connections);
+      })
+      .addCase(addConnection.fulfilled, (state, action) => {
+        state.connections.push(action.payload);
+      })
+      .addCase(testConnection.pending, (state, action) => {
+        const existingConnection = state.connections.find((cs) => cs.unique === action.meta.arg.unique);
+        if (existingConnection) {
+          existingConnection.status = IConnectionStatus.TESTING;
+        }
+      })
+      .addCase(testConnection.fulfilled, (state, action) => {
+        const existingConnection = state.connections.find((cs) => cs.unique === action.payload.unique);
+        if (existingConnection) {
+          existingConnection.status = action.payload.status;
+        }
+      });
+  },
+});
+
+export const {} = slice.actions;
+
+export default slice.reducer;
+
+export const selectAllConnections = (state: RootState) => state.connections.connections;
+
+export const selectConnections = (state: RootState, plugin: string) =>
+  state.connections.connections.filter((connection) => connection.plugin === plugin);
+
+export const selectConnection = (state: RootState, unique: string) =>
+  state.connections.connections.find((cs) => cs.unique === unique);
diff --git a/config-ui/src/features/connections/utils.ts b/config-ui/src/features/connections/utils.ts
new file mode 100644
index 000000000..00161a4ca
--- /dev/null
+++ b/config-ui/src/features/connections/utils.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 * as T from '@/api/connection/types';
+import type { PluginConfigType } from '@/plugins';
+import { PluginConfig } from '@/plugins';
+
+import { IConnection, IConnectionStatus } from '@/types';
+
+export const transformConnection = (plugin: string, connection: T.Connection): IConnection => {
+  const config = PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType;
+  return {
+    unique: `${plugin}-${connection.id}`,
+    plugin,
+    pluginName: config.name,
+    id: connection.id,
+    name: connection.name,
+    status: IConnectionStatus.IDLE,
+    icon: config.icon,
+    isBeta: config.isBeta ?? false,
+    endpoint: connection.endpoint,
+    proxy: connection.proxy,
+    authMethod: connection.authMethod,
+    token: connection.token,
+    username: connection.username,
+    password: connection.password,
+    appId: connection.appId,
+    secretKey: connection.secretKey,
+  };
+};
diff --git a/config-ui/src/main.tsx b/config-ui/src/features/index.ts
similarity index 84%
copy from config-ui/src/main.tsx
copy to config-ui/src/features/index.ts
index 682db7df5..4e6e8a4de 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/features/index.ts
@@ -16,9 +16,4 @@
  *
  */
 
-import ReactDOM from 'react-dom';
-
-import { App } from './App';
-import './index.css';
-
-ReactDOM.render(<App />, document.getElementById('root'));
+export * from './connections';
diff --git a/config-ui/src/main.tsx b/config-ui/src/main.tsx
index 682db7df5..155d47978 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/main.tsx
@@ -17,8 +17,15 @@
  */
 
 import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
 
 import { App } from './App';
+import { store } from './app/store';
 import './index.css';
 
-ReactDOM.render(<App />, document.getElementById('root'));
+ReactDOM.render(
+  <Provider store={store}>
+    <App />
+  </Provider>,
+  document.getElementById('root'),
+);
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
index e648a0bad..6ef875322 100644
--- 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
@@ -19,10 +19,11 @@
 import { useState, useMemo } from 'react';
 import { Button, Intent } from '@blueprintjs/core';
 
+import { useAppSelector } from '@/app/hook';
 import { Dialog, FormItem, Selector, Buttons } from '@/components';
-import { useConnections } from '@/hooks';
-import { DataScopeSelect, getPluginScopeId } from '@/plugins';
-import type { ConnectionItemType } from '@/store';
+import { selectAllConnections } from '@/features';
+import { DataScopeSelect } from '@/plugins';
+import { IConnection } from '@/types';
 
 interface Props {
   disabled: string[];
@@ -32,9 +33,9 @@ interface Props {
 
 export const AddConnectionDialog = ({ disabled = [], onCancel, onSubmit }: Props) => {
   const [step, setStep] = useState(1);
-  const [selectedConnection, setSelectedConnection] = useState<ConnectionItemType>();
+  const [selectedConnection, setSelectedConnection] = useState<IConnection>();
 
-  const { connections } = useConnections({ filterPlugin: ['webhook'] });
+  const connections = useAppSelector(selectAllConnections);
 
   const disabledItems = useMemo(
     () => connections.filter((cs) => (disabled.length ? disabled.includes(cs.unique) : false)),
diff --git a/config-ui/src/pages/blueprint/detail/configuration-panel.tsx b/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
index 4ab0c135d..9890aea04 100644
--- a/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
+++ b/config-ui/src/pages/blueprint/detail/configuration-panel.tsx
@@ -23,7 +23,7 @@ import { Button, Intent } from '@blueprintjs/core';
 import API from '@/api';
 import { IconButton, Table, NoData, Buttons } from '@/components';
 import { getCron } from '@/config';
-import { useConnections } from '@/hooks';
+import { ConnectionName } from '@/features';
 import { getPluginConfig } from '@/plugins';
 import { formatTime, operator } from '@/utils';
 
@@ -52,20 +52,16 @@ export const ConfigurationPanel = ({ from, blueprint, onRefresh, onChangeTab }:
     setRawPlan(JSON.stringify(blueprint.plan, null, '  '));
   }, [blueprint]);
 
-  const { onGet } = useConnections();
-
   const connections = useMemo(
     () =>
       blueprint.connections
         .filter((cs) => cs.pluginName !== 'webhook')
         .map((cs: any) => {
-          const unique = `${cs.pluginName}-${cs.connectionId}`;
           const plugin = getPluginConfig(cs.pluginName);
-          const connection = onGet(unique);
           return {
-            unique,
+            plugin: plugin.plugin,
+            connectionId: cs.connectionId,
             icon: plugin.icon,
-            name: connection?.name,
             scope: cs.scopes,
           };
         })
@@ -205,10 +201,10 @@ export const ConfigurationPanel = ({ from, blueprint, onRefresh, onChangeTab }:
               </Buttons>
               <S.ConnectionList>
                 {connections.map((cs) => (
-                  <S.ConnectionItem key={cs.unique}>
+                  <S.ConnectionItem key={`${cs.plugin}-${cs.connectionId}`}>
                     <div className="title">
                       <img src={cs.icon} alt="" />
-                      <span>{cs.name}</span>
+                      <ConnectionName plugin={cs.plugin} connectionId={cs.connectionId} />
                     </div>
                     <div className="count">
                       <span>{cs.scope.length} data scope</span>
@@ -217,8 +213,8 @@ export const ConfigurationPanel = ({ from, blueprint, onRefresh, onChangeTab }:
                       <Link
                         to={
                           from === FromEnum.blueprint
-                            ? `/blueprints/${blueprint.id}/${cs.unique}`
-                            : `/projects/${encodeName(blueprint.projectName)}/${cs.unique}`
+                            ? `/blueprints/${blueprint.id}/${cs.plugin}-${cs.connectionId}`
+                            : `/projects/${encodeName(blueprint.projectName)}/${cs.plugin}-${cs.connectionId}`
                         }
                       >
                         Edit Data Scope and Scope Config
@@ -273,7 +269,7 @@ export const ConfigurationPanel = ({ from, blueprint, onRefresh, onChangeTab }:
       )}
       {type === 'add-connection' && (
         <AddConnectionDialog
-          disabled={connections.map((cs) => cs.unique)}
+          disabled={connections.map((cs) => `${cs.plugin}-${cs.connectionId}`)}
           onCancel={handleCancel}
           onSubmit={(connection) =>
             handleUpdate({
diff --git a/config-ui/src/pages/blueprint/home/index.tsx b/config-ui/src/pages/blueprint/home/index.tsx
index 96946ebee..98c0d7d49 100644
--- a/config-ui/src/pages/blueprint/home/index.tsx
+++ b/config-ui/src/pages/blueprint/home/index.tsx
@@ -24,10 +24,11 @@ import dayjs from 'dayjs';
 import API from '@/api';
 import { PageHeader, Table, IconButton, TextTooltip, Dialog } from '@/components';
 import { getCronOptions, cronPresets, getCron } from '@/config';
-import { useConnections, useRefreshData } from '@/hooks';
+import { ConnectionName } from '@/features';
+import { useRefreshData } from '@/hooks';
 import { formatTime, operator } from '@/utils';
 
-import { ModeEnum } from '../types';
+import { ModeEnum, BlueprintType } from '../types';
 
 import * as S from './styled';
 
@@ -41,29 +42,13 @@ export const BlueprintHomePage = () => {
   const [mode, setMode] = useState(ModeEnum.normal);
   const [saving, setSaving] = useState(false);
 
-  const { onGet } = useConnections();
   const { ready, data } = useRefreshData(
     () => API.blueprint.list({ type: type.toLocaleUpperCase(), page, pageSize }),
     [version, type, page, pageSize],
   );
 
   const [options, presets] = useMemo(() => [getCronOptions(), cronPresets.map((preset) => preset.config)], []);
-  const [dataSource, total] = useMemo(
-    () => [
-      (data?.blueprints ?? []).map((it) => {
-        const connections =
-          it.connections
-            .filter((cs) => cs.pluginName !== 'webhook')
-            .map((cs) => onGet(`${cs.pluginName}-${cs.connectionId}`) || `${cs.pluginName}-${cs.connectionId}`) ?? [];
-        return {
-          ...it,
-          connections: connections.map((cs) => cs.name),
-        };
-      }),
-      data?.count ?? 0,
-    ],
-    [data],
-  );
+  const [dataSource, total] = useMemo(() => [data?.blueprints ?? [], data?.count ?? 0], [data]);
 
   const handleShowDialog = () => setIsOpen(true);
   const handleHideDialog = () => {
@@ -144,11 +129,21 @@ export const BlueprintHomePage = () => {
               dataIndex: ['mode', 'connections'],
               key: 'connections',
               align: 'center',
-              render: ({ mode, connections }) => {
+              render: ({ mode, connections }: Pick<BlueprintType, 'mode' | 'connections'>) => {
                 if (mode === ModeEnum.advanced) {
                   return 'Advanced Mode';
                 }
-                return connections.join(',');
+                return (
+                  <>
+                    {connections.map((it) => (
+                      <ConnectionName
+                        key={`${it.pluginName}-${it.connectionId}`}
+                        plugin={it.pluginName}
+                        connectionId={it.connectionId}
+                      />
+                    ))}
+                  </>
+                );
               },
             },
             {
diff --git a/config-ui/src/pages/connection/detail/index.tsx b/config-ui/src/pages/connection/detail/index.tsx
index 908b403ac..2668392f3 100644
--- a/config-ui/src/pages/connection/detail/index.tsx
+++ b/config-ui/src/pages/connection/detail/index.tsx
@@ -16,13 +16,15 @@
  *
  */
 
-import { useState, useEffect, useMemo } from 'react';
+import { useState, useMemo } from 'react';
 import { useParams, useNavigate, Link } from 'react-router-dom';
 import { Button, Intent } from '@blueprintjs/core';
 
 import API from '@/api';
+import { useAppSelector } from '@/app/hook';
 import { PageHeader, Buttons, Dialog, IconButton, Table, Message, toast } from '@/components';
-import { useTips, useConnections, useRefreshData } from '@/hooks';
+import { selectConnection } from '@/features';
+import { useTips, useRefreshData } from '@/hooks';
 import ClearImg from '@/images/icons/clear.svg';
 import {
   ConnectionForm,
@@ -33,6 +35,7 @@ import {
   ScopeConfigForm,
   ScopeConfigSelect,
 } from '@/plugins';
+import { IConnection } from '@/types';
 import { operator } from '@/utils';
 
 import * as S from './styled';
@@ -68,15 +71,15 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => {
   const [conflict, setConflict] = useState<string[]>([]);
   const [errorMsg, setErrorMsg] = useState('');
 
+  const connection = useAppSelector((state) => selectConnection(state, `${plugin}-${connectionId}`)) as IConnection;
   const navigate = useNavigate();
-  const { onGet, onTest, onRefresh } = useConnections();
   const { setTips } = useTips();
   const { ready, data } = useRefreshData(
     () => API.scope.list(plugin, connectionId, { page, pageSize, blueprint: true }),
     [version, page, pageSize],
   );
 
-  const { unique, status, name, icon } = onGet(`${plugin}-${connectionId}`) || {};
+  const { name, icon } = connection;
 
   const pluginConfig = useMemo(() => getPluginConfig(plugin), [plugin]);
 
@@ -94,10 +97,6 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => {
     [data],
   );
 
-  useEffect(() => {
-    onTest(`${plugin}-${connectionId}`);
-  }, [plugin, connectionId]);
-
   const handleHideDialog = () => {
     setType(undefined);
   };
@@ -129,7 +128,6 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => {
 
     if (res.status === 'success') {
       toast.success('Delete Connection Successful.');
-      onRefresh(plugin);
       navigate('/connections');
     } else if (res.status === 'conflict') {
       setType('deleteConnectionFailed');
@@ -146,7 +144,6 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => {
   };
 
   const handleUpdate = () => {
-    onRefresh(plugin);
     handleHideDialog();
   };
 
@@ -250,7 +247,7 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => {
       extra={
         <S.PageHeaderExtra>
           <span style={{ marginRight: 4 }}>Status:</span>
-          <ConnectionStatus status={status} unique={unique} onTest={onTest} />
+          <ConnectionStatus connection={connection} />
           <Buttons style={{ marginLeft: 8 }}>
             <Button outlined intent={Intent.PRIMARY} icon="annotation" text="Edit" onClick={handleShowUpdateDialog} />
             <Button intent={Intent.DANGER} icon="trash" text="Delete" onClick={handleShowDeleteDialog} />
diff --git a/config-ui/src/pages/connection/home/index.tsx b/config-ui/src/pages/connection/home/index.tsx
index d0dcccbcc..5df3a6ad6 100644
--- a/config-ui/src/pages/connection/home/index.tsx
+++ b/config-ui/src/pages/connection/home/index.tsx
@@ -20,10 +20,10 @@ import { useState, useMemo } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { Tag, Intent } from '@blueprintjs/core';
 
+import { useAppSelector } from '@/app/hook';
 import { Dialog } from '@/components';
-import { useConnections } from '@/hooks';
-import type { PluginConfigType } from '@/plugins';
-import { PluginConfig, PluginType, ConnectionList, ConnectionForm } from '@/plugins';
+import { selectAllConnections } from '@/features/connections';
+import { PluginConfig, PluginConfigType, ConnectionList, ConnectionForm } from '@/plugins';
 
 import * as S from './styled';
 
@@ -31,26 +31,22 @@ export const ConnectionHomePage = () => {
   const [type, setType] = useState<'list' | 'form'>();
   const [pluginConfig, setPluginConfig] = useState<PluginConfigType>();
 
-  const { connections, onRefresh } = useConnections();
+  const connections = useAppSelector(selectAllConnections);
+
   const navigate = useNavigate();
 
-  const [plugins, webhook] = useMemo(
-    () => [
-      PluginConfig.filter((p) => p.type === PluginType.Connection && p.plugin !== 'webhook').map((p) => ({
+  const plugins = useMemo(
+    () =>
+      PluginConfig.map((p) => ({
         ...p,
         count: connections.filter((cs) => cs.plugin === p.plugin).length,
       })),
-      {
-        ...(PluginConfig.find((p) => p.plugin === 'webhook') as PluginConfigType),
-        count: connections.filter((cs) => cs.plugin === 'webhook').length,
-      },
-    ],
     [connections],
   );
 
-  const handleShowListDialog = (config: PluginConfigType) => {
+  const handleShowListDialog = (pluginConfig: PluginConfigType) => {
     setType('list');
-    setPluginConfig(config);
+    setPluginConfig(pluginConfig);
   };
 
   const handleShowFormDialog = () => {
@@ -62,8 +58,7 @@ export const ConnectionHomePage = () => {
     setPluginConfig(undefined);
   };
 
-  const handleCreateSuccess = async (plugin: string, id: ID) => {
-    onRefresh(plugin);
+  const handleSuccessAfter = async (plugin: string, id: ID) => {
     navigate(`/connections/${plugin}/${id}`);
   };
 
@@ -82,18 +77,20 @@ export const ConnectionHomePage = () => {
           You can create and manage data connections for the following data sources and use them in your Projects.
         </h5>
         <ul>
-          {plugins.map((p) => (
-            <li key={p.plugin} onClick={() => handleShowListDialog(p)}>
-              <img src={p.icon} alt="" />
-              <span className="name">{p.name}</span>
-              <S.Count>{p.count ? `${p.count} connections` : 'No connection'}</S.Count>
-              {p.isBeta && (
-                <Tag intent={Intent.WARNING} round>
-                  beta
-                </Tag>
-              )}
-            </li>
-          ))}
+          {plugins
+            .filter((p) => p.plugin !== 'webhook')
+            .map((p) => (
+              <li key={p.plugin} onClick={() => handleShowListDialog(p)}>
+                <img src={p.icon} alt="" />
+                <span className="name">{p.name}</span>
+                <S.Count>{p.count ? `${p.count} connections` : 'No connection'}</S.Count>
+                {p.isBeta && (
+                  <Tag intent={Intent.WARNING} round>
+                    beta
+                  </Tag>
+                )}
+              </li>
+            ))}
         </ul>
       </div>
       <div className="block">
@@ -103,11 +100,20 @@ export const ConnectionHomePage = () => {
           DORA metrics, etc.
         </h5>
         <ul>
-          <li onClick={() => handleShowListDialog(webhook)}>
-            <img src={webhook.icon} alt="" />
-            <span className="name">{webhook.name}</span>
-            <S.Count>{webhook.count ? `${webhook.count} connections` : 'No connection'}</S.Count>
-          </li>
+          {plugins
+            .filter((p) => p.plugin === 'webhook')
+            .map((p) => (
+              <li key={p.plugin} onClick={() => handleShowListDialog(p)}>
+                <img src={p.icon} alt="" />
+                <span className="name">{p.name}</span>
+                <S.Count>{p.count ? `${p.count} connections` : 'No connection'}</S.Count>
+                {p.isBeta && (
+                  <Tag intent={Intent.WARNING} round>
+                    beta
+                  </Tag>
+                )}
+              </li>
+            ))}
         </ul>
       </div>
       {type === 'list' && pluginConfig && (
@@ -141,7 +147,7 @@ export const ConnectionHomePage = () => {
         >
           <ConnectionForm
             plugin={pluginConfig.plugin}
-            onSuccess={(id) => handleCreateSuccess(pluginConfig.plugin, id)}
+            onSuccess={(id) => handleSuccessAfter(pluginConfig.plugin, id)}
           />
         </Dialog>
       )}
diff --git a/config-ui/src/pages/project/home/index.tsx b/config-ui/src/pages/project/home/index.tsx
index 54b167d44..ef79ce889 100644
--- a/config-ui/src/pages/project/home/index.tsx
+++ b/config-ui/src/pages/project/home/index.tsx
@@ -24,7 +24,8 @@ import dayjs from 'dayjs';
 import API from '@/api';
 import { PageHeader, Table, Dialog, ExternalLink, IconButton, toast } from '@/components';
 import { getCron, cronPresets } from '@/config';
-import { useConnections, useRefreshData } from '@/hooks';
+import { ConnectionName } from '@/features';
+import { useRefreshData } from '@/hooks';
 import { DOC_URL } from '@/release';
 import { formatTime, operator } from '@/utils';
 import { PipelineStatus } from '@/routes/pipeline';
@@ -44,7 +45,6 @@ export const ProjectHomePage = () => {
   const [saving, setSaving] = useState(false);
 
   const { ready, data } = useRefreshData(() => API.project.list({ page, pageSize }), [version, page, pageSize]);
-  const { onGet } = useConnections();
 
   const navigate = useNavigate();
 
@@ -139,14 +139,19 @@ export const ProjectHomePage = () => {
             dataIndex: 'connections',
             key: 'connections',
             render: (val: BlueprintType['connections']) =>
-              !val || !val.length
-                ? 'N/A'
-                : val
-                    .map((it) => {
-                      const cs = onGet(`${it.pluginName}-${it.connectionId}`);
-                      return cs?.name;
-                    })
-                    .join(', '),
+              !val || !val.length ? (
+                'N/A'
+              ) : (
+                <>
+                  {val.map((it) => (
+                    <ConnectionName
+                      key={`${it.pluginName}-${it.connectionId}`}
+                      plugin={it.pluginName}
+                      connectionId={it.connectionId}
+                    />
+                  ))}
+                </>
+              ),
           },
           {
             title: 'Sync Frequency',
diff --git a/config-ui/src/plugins/components/connection-form/index.tsx b/config-ui/src/plugins/components/connection-form/index.tsx
index 4ef0a9573..fbfcaa9f5 100644
--- a/config-ui/src/plugins/components/connection-form/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/index.tsx
@@ -21,9 +21,10 @@ import { Button, Intent } from '@blueprintjs/core';
 import { pick } from 'lodash';
 
 import API from '@/api';
-import { ExternalLink, PageLoading, Buttons } from '@/components';
-import { useRefreshData } from '@/hooks';
-import { getPluginConfig } from '@/plugins';
+import { useAppDispatch, useAppSelector } from '@/app/hook';
+import { ExternalLink, Buttons } from '@/components';
+import { selectConnection } from '@/features/connections';
+import { PluginConfig, PluginConfigType } from '@/plugins';
 import { operator } from '@/utils';
 
 import { Form } from './fields';
@@ -40,23 +41,18 @@ export const ConnectionForm = ({ plugin, connectionId, onSuccess }: Props) => {
   const [errors, setErrors] = useState<Record<string, any>>({});
   const [operating, setOperating] = useState(false);
 
+  const dispatch = useAppDispatch();
+  const connection = useAppSelector((state) => selectConnection(state, `${plugin}-${connectionId}`));
+
   const {
     name,
     connection: { docLink, fields, initialValues },
-  } = useMemo(() => getPluginConfig(plugin), [plugin]);
+  } = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType, [plugin]);
 
   const disabled = useMemo(() => {
     return Object.values(errors).some((value) => value);
   }, [errors]);
 
-  const { ready, data } = useRefreshData(async () => {
-    if (!connectionId) {
-      return {};
-    }
-
-    return API.connection.get(plugin, connectionId);
-  }, [plugin, connectionId]);
-
   const handleTest = async () => {
     await operator(
       () =>
@@ -98,10 +94,6 @@ export const ConnectionForm = ({ plugin, connectionId, onSuccess }: Props) => {
     }
   };
 
-  if (connectionId && !ready) {
-    return <PageLoading />;
-  }
-
   return (
     <S.Wrapper>
       <S.Tips>
@@ -112,7 +104,7 @@ export const ConnectionForm = ({ plugin, connectionId, onSuccess }: Props) => {
         <Form
           name={name}
           fields={fields}
-          initialValues={{ ...initialValues, ...data }}
+          initialValues={{ ...initialValues, ...(connection ?? {}) }}
           values={values}
           errors={errors}
           setValues={setValues}
diff --git a/config-ui/src/plugins/components/connection-list/index.tsx b/config-ui/src/plugins/components/connection-list/index.tsx
index 634dd7dad..d113c1788 100644
--- a/config-ui/src/plugins/components/connection-list/index.tsx
+++ b/config-ui/src/plugins/components/connection-list/index.tsx
@@ -19,8 +19,9 @@
 import { Link } from 'react-router-dom';
 import { Button, Intent } from '@blueprintjs/core';
 
+import { useAppSelector } from '@/app/hook';
 import { Table } from '@/components';
-import { useConnections } from '@/hooks';
+import { selectConnections } from '@/features/connections';
 import { ConnectionStatus } from '@/plugins';
 
 import { WebHookConnection } from '@/plugins/register/webhook';
@@ -31,16 +32,12 @@ interface Props {
 }
 
 export const ConnectionList = ({ plugin, onCreate }: Props) => {
+  const connections = useAppSelector((state) => selectConnections(state, plugin));
+
   if (plugin === 'webhook') {
     return <WebHookConnection />;
   }
 
-  return <BaseList plugin={plugin} onCreate={onCreate} />;
-};
-
-const BaseList = ({ plugin, onCreate }: Props) => {
-  const { connections, onTest } = useConnections();
-
   return (
     <>
       <Table
@@ -53,10 +50,9 @@ const BaseList = ({ plugin, onCreate }: Props) => {
           },
           {
             title: 'Status',
-            dataIndex: ['status', 'unique'],
             key: 'status',
             width: 150,
-            render: ({ status, unique }) => <ConnectionStatus status={status} unique={unique} onTest={onTest} />,
+            render: (_, row) => <ConnectionStatus connection={row} />,
           },
           {
             title: '',
@@ -66,7 +62,7 @@ const BaseList = ({ plugin, onCreate }: Props) => {
             render: ({ plugin, id }) => <Link to={`/connections/${plugin}/${id}`}>Details</Link>,
           },
         ]}
-        dataSource={connections.filter((cs) => cs.plugin === plugin)}
+        dataSource={connections}
         noData={{
           text: 'There is no data connection yet. Please add a new connection.',
         }}
diff --git a/config-ui/src/plugins/components/connection-status/index.tsx b/config-ui/src/plugins/components/connection-status/index.tsx
index 3475194d4..9e8c83e5f 100644
--- a/config-ui/src/plugins/components/connection-status/index.tsx
+++ b/config-ui/src/plugins/components/connection-status/index.tsx
@@ -18,8 +18,10 @@
 
 import styled from 'styled-components';
 
+import { useAppDispatch } from '@/app/hook';
 import { IconButton } from '@/components';
-import { ConnectionStatusEnum } from '@/store';
+import { testConnection } from '@/features/connections';
+import { IConnection, IConnectionStatus, IPlugin } from '@/types';
 
 const Wrapper = styled.div`
   display: inline-flex;
@@ -35,29 +37,28 @@ const Wrapper = styled.div`
 `;
 
 const STATUS_MAP = {
-  [`${ConnectionStatusEnum.NULL}`]: 'Test',
-  [`${ConnectionStatusEnum.TESTING}`]: 'Testing',
-  [`${ConnectionStatusEnum.ONLINE}`]: 'Connected',
-  [`${ConnectionStatusEnum.OFFLINE}`]: 'Disconnected',
+  [`${IConnectionStatus.IDLE}`]: 'Test',
+  [`${IConnectionStatus.TESTING}`]: 'Testing',
+  [`${IConnectionStatus.ONLINE}`]: 'Connected',
+  [`${IConnectionStatus.OFFLINE}`]: 'Disconnected',
 };
 
 interface Props {
-  status: ConnectionStatusEnum;
-  unique: string;
-  onTest: (unique: string) => void;
+  connection: IConnection;
 }
 
-export const ConnectionStatus = ({ status, unique, onTest }: Props) => {
+export const ConnectionStatus = ({ connection }: Props) => {
+  const { status } = connection;
+
+  const dispatch = useAppDispatch();
+
+  const handleTest = () => dispatch(testConnection(connection));
+
   return (
     <Wrapper>
       <span className={status}>{STATUS_MAP[status]}</span>
-      {status !== ConnectionStatusEnum.ONLINE && (
-        <IconButton
-          loading={status === ConnectionStatusEnum.TESTING}
-          icon="repeat"
-          tooltip="Retry"
-          onClick={() => onTest(unique)}
-        />
+      {status !== IConnectionStatus.ONLINE && (
+        <IconButton loading={status === IConnectionStatus.TESTING} icon="repeat" tooltip="Retry" onClick={handleTest} />
       )}
     </Wrapper>
   );
diff --git a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
index 9d4406bba..36b7aeda8 100644
--- a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
@@ -21,7 +21,6 @@ import { InputGroup, Icon } from '@blueprintjs/core';
 
 import API from '@/api';
 import { Dialog, FormItem, CopyText, ExternalLink } from '@/components';
-import { useConnections } from '@/hooks';
 import { operator } from '@/utils';
 
 import * as S from '../styled';
@@ -44,8 +43,6 @@ export const CreateDialog = ({ isOpen, onCancel, onSubmitAfter }: Props) => {
     apiKey: '',
   });
 
-  const { onRefresh } = useConnections();
-
   const prefix = useMemo(() => `${window.location.origin}/api`, []);
 
   const handleSubmit = async () => {
@@ -88,7 +85,6 @@ export const CreateDialog = ({ isOpen, onCancel, onSubmitAfter }: Props) => {
 }'`,
         apiKey: res.apiKey,
       });
-      onRefresh('webhook');
       onSubmitAfter?.(res.id);
     }
   };
diff --git a/config-ui/src/plugins/register/webhook/components/delete-dialog.tsx b/config-ui/src/plugins/register/webhook/components/delete-dialog.tsx
index 2479f1754..806a26d5e 100644
--- a/config-ui/src/plugins/register/webhook/components/delete-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/delete-dialog.tsx
@@ -20,7 +20,6 @@ import { useState } from 'react';
 
 import API from '@/api';
 import { Dialog, Message } from '@/components';
-import { useConnections } from '@/hooks';
 import { operator } from '@/utils';
 
 interface Props {
@@ -32,15 +31,12 @@ interface Props {
 export const DeleteDialog = ({ initialId, onCancel, onSubmitAfter }: Props) => {
   const [operating, setOperating] = useState(false);
 
-  const { onRefresh } = useConnections();
-
   const handleSubmit = async () => {
     const [success] = await operator(() => API.plugin.webhook.remove(initialId), {
       setOperating,
     });
 
     if (success) {
-      onRefresh('webhook');
       onSubmitAfter?.(initialId);
       onCancel();
     }
diff --git a/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx b/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
index a9fb55cc7..f656cdf7c 100644
--- a/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/edit-dialog.tsx
@@ -21,7 +21,6 @@ import { InputGroup } from '@blueprintjs/core';
 
 import API from '@/api';
 import { Dialog, FormItem } from '@/components';
-import { useConnections } from '@/hooks';
 import { operator } from '@/utils';
 
 interface Props {
@@ -33,8 +32,6 @@ export const EditDialog = ({ initialId, onCancel }: Props) => {
   const [name, setName] = useState('');
   const [operating, setOperating] = useState(false);
 
-  const { onRefresh } = useConnections({ plugin: 'webhook' });
-
   useEffect(() => {
     (async () => {
       const res = await API.plugin.webhook.get(initialId);
@@ -48,7 +45,6 @@ export const EditDialog = ({ initialId, onCancel }: Props) => {
     });
 
     if (success) {
-      onRefresh('webhook');
       onCancel();
     }
   };
diff --git a/config-ui/src/plugins/register/webhook/connection.tsx b/config-ui/src/plugins/register/webhook/connection.tsx
index ffde52da7..07e68224c 100644
--- a/config-ui/src/plugins/register/webhook/connection.tsx
+++ b/config-ui/src/plugins/register/webhook/connection.tsx
@@ -19,8 +19,9 @@
 import { useState } from 'react';
 import { Button, Intent } from '@blueprintjs/core';
 
+import { useAppSelector } from '@/app/hook';
 import { Buttons, Table, ColumnType, ExternalLink, IconButton } from '@/components';
-import { useConnections } from '@/hooks';
+import { selectConnections } from '@/features/connections';
 import { DOC_URL } from '@/release';
 
 import { CreateDialog, ViewDialog, EditDialog, DeleteDialog } from './components';
@@ -40,7 +41,7 @@ export const WebHookConnection = ({ filterIds, onCreateAfter, onDeleteAfter }: P
   const [type, setType] = useState<Type>();
   const [currentID, setCurrentID] = useState<ID>();
 
-  const { connections } = useConnections({ plugin: 'webhook' });
+  const connections = useAppSelector((state) => selectConnections(state, 'webhook'));
 
   const handleHideDialog = () => {
     setType(undefined);
diff --git a/config-ui/src/routes/layout/layout.tsx b/config-ui/src/routes/layout/layout.tsx
index 9ad858571..cc5b4b260 100644
--- a/config-ui/src/routes/layout/layout.tsx
+++ b/config-ui/src/routes/layout/layout.tsx
@@ -16,14 +16,16 @@
  *
  */
 
-import { useRef } from 'react';
+import { useEffect, useRef } from 'react';
 import { useLoaderData, Outlet, useNavigate, useLocation } from 'react-router-dom';
 import { CSSTransition } from 'react-transition-group';
 import { Menu, MenuItem, Navbar, Alignment } from '@blueprintjs/core';
 
+import { useAppDispatch } from '@/app/hook';
 import { Logo, ExternalLink, IconButton } from '@/components';
+import { init } from '@/features';
 import { DOC_URL } from '@/release';
-import { TipsContextProvider, TipsContextConsumer, ConnectionContextProvider } from '@/store';
+import { TipsContextProvider, TipsContextConsumer } from '@/store';
 
 import DashboardIcon from '@/images/icons/dashboard.svg';
 import FileIcon from '@/images/icons/file.svg';
@@ -39,6 +41,12 @@ import './tips-transition.css';
 export const Layout = () => {
   const { version } = useLoaderData() as Awaited<ReturnType<typeof loader>>;
 
+  const dispatch = useAppDispatch();
+
+  useEffect(() => {
+    dispatch(init());
+  }, []);
+
   const navigate = useNavigate();
   const { pathname } = useLocation();
 
@@ -65,101 +73,99 @@ export const Layout = () => {
     <TipsContextProvider>
       <TipsContextConsumer>
         {({ tips, setTips }) => (
-          <ConnectionContextProvider>
-            <S.Wrapper>
-              <S.Sider>
-                <Logo />
-                <Menu className="menu">
-                  {menu.map((it) => {
-                    const paths = [it.path, ...(it.children ?? []).map((cit) => cit.path)];
-                    const active = !!paths.find((path) => pathname.includes(path));
-                    return (
-                      <MenuItem
-                        key={it.key}
-                        className="menu-item"
-                        text={it.title}
-                        icon={it.icon}
-                        active={active}
-                        onClick={() => handlePushPath(it)}
-                      >
-                        {it.children?.map((cit) => (
-                          <MenuItem
-                            key={cit.key}
-                            className="sub-menu-item"
-                            text={
-                              <S.SiderMenuItem>
-                                <span>{cit.title}</span>
-                              </S.SiderMenuItem>
-                            }
-                            icon={cit.icon}
-                            active={pathname.includes(cit.path)}
-                            disabled={cit.disabled}
-                            onClick={() => handlePushPath(cit)}
-                          />
-                        ))}
-                      </MenuItem>
-                    );
-                  })}
-                </Menu>
-                <div className="copyright">
-                  <div>Apache 2.0 License</div>
-                  <div className="version">{version}</div>
-                </div>
-              </S.Sider>
-              <S.Main>
-                <S.Header>
-                  <Navbar.Group align={Alignment.RIGHT}>
-                    <S.DashboardIcon>
-                      <ExternalLink link={getGrafanaUrl()}>
-                        <img src={DashboardIcon} alt="dashboards" />
-                        <span>Dashboards</span>
-                      </ExternalLink>
-                    </S.DashboardIcon>
-                    <Navbar.Divider />
-                    <ExternalLink link={DOC_URL.TUTORIAL}>
-                      <img src={FileIcon} alt="documents" />
-                      <span>Docs</span>
-                    </ExternalLink>
-                    <Navbar.Divider />
-                    <ExternalLink link="/api/swagger/index.html">
-                      <img src={APIIcon} alt="api" />
-                      <span>API</span>
-                    </ExternalLink>
-                    <Navbar.Divider />
-                    <a
-                      href="https://github.com/apache/incubator-devlake"
-                      rel="noreferrer"
-                      target="_blank"
-                      className="navIconLink"
-                    >
-                      <img src={GitHubIcon} alt="github" />
-                      <span>GitHub</span>
-                    </a>
-                    <Navbar.Divider />
-                    <a
-                      href="https://join.slack.com/t/devlake-io/shared_invite/zt-17b6vuvps-x98pqseoUagM7EAmKC82xQ"
-                      rel="noreferrer"
-                      target="_blank"
+          <S.Wrapper>
+            <S.Sider>
+              <Logo />
+              <Menu className="menu">
+                {menu.map((it) => {
+                  const paths = [it.path, ...(it.children ?? []).map((cit) => cit.path)];
+                  const active = !!paths.find((path) => pathname.includes(path));
+                  return (
+                    <MenuItem
+                      key={it.key}
+                      className="menu-item"
+                      text={it.title}
+                      icon={it.icon}
+                      active={active}
+                      onClick={() => handlePushPath(it)}
                     >
-                      <img src={SlackIcon} alt="slack" />
-                      <span>Slack</span>
-                    </a>
-                  </Navbar.Group>
-                </S.Header>
-                <S.Inner>
-                  <S.Content>
-                    <Outlet />
-                  </S.Content>
-                </S.Inner>
-                <CSSTransition in={!!tips} unmountOnExit timeout={300} nodeRef={tipsRef} classNames="tips">
-                  <S.Tips ref={tipsRef}>
-                    <div className="content">{tips}</div>
-                    <IconButton style={{ color: '#fff' }} icon="cross" tooltip="Close" onClick={() => setTips('')} />
-                  </S.Tips>
-                </CSSTransition>
-              </S.Main>
-            </S.Wrapper>
-          </ConnectionContextProvider>
+                      {it.children?.map((cit) => (
+                        <MenuItem
+                          key={cit.key}
+                          className="sub-menu-item"
+                          text={
+                            <S.SiderMenuItem>
+                              <span>{cit.title}</span>
+                            </S.SiderMenuItem>
+                          }
+                          icon={cit.icon}
+                          active={pathname.includes(cit.path)}
+                          disabled={cit.disabled}
+                          onClick={() => handlePushPath(cit)}
+                        />
+                      ))}
+                    </MenuItem>
+                  );
+                })}
+              </Menu>
+              <div className="copyright">
+                <div>Apache 2.0 License</div>
+                <div className="version">{version}</div>
+              </div>
+            </S.Sider>
+            <S.Main>
+              <S.Header>
+                <Navbar.Group align={Alignment.RIGHT}>
+                  <S.DashboardIcon>
+                    <ExternalLink link={getGrafanaUrl()}>
+                      <img src={DashboardIcon} alt="dashboards" />
+                      <span>Dashboards</span>
+                    </ExternalLink>
+                  </S.DashboardIcon>
+                  <Navbar.Divider />
+                  <ExternalLink link={DOC_URL.TUTORIAL}>
+                    <img src={FileIcon} alt="documents" />
+                    <span>Docs</span>
+                  </ExternalLink>
+                  <Navbar.Divider />
+                  <ExternalLink link="/api/swagger/index.html">
+                    <img src={APIIcon} alt="api" />
+                    <span>API</span>
+                  </ExternalLink>
+                  <Navbar.Divider />
+                  <a
+                    href="https://github.com/apache/incubator-devlake"
+                    rel="noreferrer"
+                    target="_blank"
+                    className="navIconLink"
+                  >
+                    <img src={GitHubIcon} alt="github" />
+                    <span>GitHub</span>
+                  </a>
+                  <Navbar.Divider />
+                  <a
+                    href="https://join.slack.com/t/devlake-io/shared_invite/zt-17b6vuvps-x98pqseoUagM7EAmKC82xQ"
+                    rel="noreferrer"
+                    target="_blank"
+                  >
+                    <img src={SlackIcon} alt="slack" />
+                    <span>Slack</span>
+                  </a>
+                </Navbar.Group>
+              </S.Header>
+              <S.Inner>
+                <S.Content>
+                  <Outlet />
+                </S.Content>
+              </S.Inner>
+              <CSSTransition in={!!tips} unmountOnExit timeout={300} nodeRef={tipsRef} classNames="tips">
+                <S.Tips ref={tipsRef}>
+                  <div className="content">{tips}</div>
+                  <IconButton style={{ color: '#fff' }} icon="cross" tooltip="Close" onClick={() => setTips('')} />
+                </S.Tips>
+              </CSSTransition>
+            </S.Main>
+          </S.Wrapper>
         )}
       </TipsContextConsumer>
     </TipsContextProvider>
diff --git a/config-ui/src/api/connection/types.ts b/config-ui/src/types/connection.ts
similarity index 65%
copy from config-ui/src/api/connection/types.ts
copy to config-ui/src/types/connection.ts
index a61f954a7..6225fd63b 100644
--- a/config-ui/src/api/connection/types.ts
+++ b/config-ui/src/types/connection.ts
@@ -16,43 +16,28 @@
  *
  */
 
-export type Connection = {
+export enum IConnectionStatus {
+  IDLE = 'idle',
+  TESTING = 'testing',
+  ONLINE = 'online',
+  OFFLINE = 'offline',
+}
+
+export interface IConnection {
+  unique: string;
+  plugin: string;
+  pluginName: string;
   id: ID;
   name: string;
+  status: IConnectionStatus;
+  icon: string;
+  isBeta: boolean;
   endpoint: string;
-  authMethod?: string;
-  token?: string;
-  username?: string;
-  password?: string;
   proxy: string;
-  apiKey?: string;
-  dbUrl?: string;
-};
-
-export type ConnectionForm = {
-  name: string;
-  endpoint?: string;
   authMethod?: string;
+  token?: string;
   username?: string;
   password?: string;
-  token?: string;
   appId?: string;
   secretKey?: string;
-  enableGraphql?: boolean;
-  proxy: string;
-  rateLimitPerHour?: number;
-  dbUrl?: string;
-};
-
-export type ConnectionTest = {
-  message: string;
-  success: boolean;
-  login?: string;
-  installations?: Array<{
-    id: number;
-    account: {
-      login: string;
-    };
-  }>;
-  warning?: string;
-};
+}
diff --git a/config-ui/src/main.tsx b/config-ui/src/types/index.ts
similarity index 84%
copy from config-ui/src/main.tsx
copy to config-ui/src/types/index.ts
index 682db7df5..fcc98feb8 100644
--- a/config-ui/src/main.tsx
+++ b/config-ui/src/types/index.ts
@@ -16,9 +16,4 @@
  *
  */
 
-import ReactDOM from 'react-dom';
-
-import { App } from './App';
-import './index.css';
-
-ReactDOM.render(<App />, document.getElementById('root'));
+export * from './connection';
diff --git a/config-ui/yarn.lock b/config-ui/yarn.lock
index 587e396f0..c6fbdf122 100644
--- a/config-ui/yarn.lock
+++ b/config-ui/yarn.lock
@@ -1452,6 +1452,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.9.2":
+  version: 7.23.2
+  resolution: "@babel/runtime@npm:7.23.2"
+  dependencies:
+    regenerator-runtime: ^0.14.0
+  checksum: 6c4df4839ec75ca10175f636d6362f91df8a3137f86b38f6cd3a4c90668a0fe8e9281d320958f4fbd43b394988958585a17c3aab2a4ea6bf7316b22916a371fb
+  languageName: node
+  linkType: hard
+
 "@babel/runtime@npm:^7.21.0":
   version: 7.22.5
   resolution: "@babel/runtime@npm:7.22.5"
@@ -2007,6 +2016,26 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@reduxjs/toolkit@npm:^1.9.7":
+  version: 1.9.7
+  resolution: "@reduxjs/toolkit@npm:1.9.7"
+  dependencies:
+    immer: ^9.0.21
+    redux: ^4.2.1
+    redux-thunk: ^2.4.2
+    reselect: ^4.1.8
+  peerDependencies:
+    react: ^16.9.0 || ^17.0.0 || ^18
+    react-redux: ^7.2.1 || ^8.0.2
+  peerDependenciesMeta:
+    react:
+      optional: true
+    react-redux:
+      optional: true
+  checksum: ac25dec73a5d2df9fc7fbe98c14ccc73919e5ee1d6f251db0d2ec8f90273f92ef39c26716704bf56b5a40189f72d94b4526dc3a8c7ac3986f5daf44442bcc364
+  languageName: node
+  linkType: hard
+
 "@remix-run/router@npm:1.7.1":
   version: 1.7.1
   resolution: "@remix-run/router@npm:1.7.1"
@@ -2052,6 +2081,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/hoist-non-react-statics@npm:^3.3.1":
+  version: 3.3.3
+  resolution: "@types/hoist-non-react-statics@npm:3.3.3"
+  dependencies:
+    "@types/react": "*"
+    hoist-non-react-statics: ^3.3.0
+  checksum: 107ac20ab36acdc83fb6bfca901e6f4f11307a0a307099c31ecf2a9875f8abffd731a2e1ee793162307e8aaee48fe9fd8d4e034fce88d5da480bc4178a3fc8d7
+  languageName: node
+  linkType: hard
+
 "@types/js-cookie@npm:^2.x.x":
   version: 2.2.7
   resolution: "@types/js-cookie@npm:2.2.7"
@@ -2185,6 +2224,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/use-sync-external-store@npm:^0.0.3":
+  version: 0.0.3
+  resolution: "@types/use-sync-external-store@npm:0.0.3"
+  checksum: 161ddb8eec5dbe7279ac971531217e9af6b99f7783213566d2b502e2e2378ea19cf5e5ea4595039d730aa79d3d35c6567d48599f69773a02ffcff1776ec2a44e
+  languageName: node
+  linkType: hard
+
 "@typescript-eslint/eslint-plugin@npm:^5.5.0":
   version: 5.55.0
   resolution: "@typescript-eslint/eslint-plugin@npm:5.55.0"
@@ -3016,6 +3062,7 @@ __metadata:
     "@blueprintjs/datetime2": ^1.0.10
     "@blueprintjs/popover2": ^2.0.10
     "@blueprintjs/select": ^5.0.10
+    "@reduxjs/toolkit": ^1.9.7
     "@types/file-saver": ^2.0.5
     "@types/node": ^18.15.1
     "@types/react": ^18.0.24
@@ -3045,8 +3092,10 @@ __metadata:
     react-copy-to-clipboard: ^5.1.0
     react-dom: 17.0.2
     react-is: ^18.2.0
+    react-redux: ^8.1.3
     react-router-dom: ^6.14.1
     react-transition-group: ^4.4.5
+    redux: ^4.2.1
     styled-components: ^5.3.6
     typescript: ^4.9.4
     vite: ^4.1.4
@@ -4372,7 +4421,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0":
+"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2":
   version: 3.3.2
   resolution: "hoist-non-react-statics@npm:3.3.2"
   dependencies:
@@ -4450,6 +4499,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"immer@npm:^9.0.21":
+  version: 9.0.21
+  resolution: "immer@npm:9.0.21"
+  checksum: 70e3c274165995352f6936695f0ef4723c52c92c92dd0e9afdfe008175af39fa28e76aafb3a2ca9d57d1fb8f796efc4dd1e1cc36f18d33fa5b74f3dfb0375432
+  languageName: node
+  linkType: hard
+
 "import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1":
   version: 3.3.0
   resolution: "import-fresh@npm:3.3.0"
@@ -5824,7 +5880,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"react-is@npm:^18.2.0":
+"react-is@npm:^18.0.0, react-is@npm:^18.2.0":
   version: 18.2.0
   resolution: "react-is@npm:18.2.0"
   checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e
@@ -5845,6 +5901,38 @@ __metadata:
   languageName: node
   linkType: hard
 
+"react-redux@npm:^8.1.3":
+  version: 8.1.3
+  resolution: "react-redux@npm:8.1.3"
+  dependencies:
+    "@babel/runtime": ^7.12.1
+    "@types/hoist-non-react-statics": ^3.3.1
+    "@types/use-sync-external-store": ^0.0.3
+    hoist-non-react-statics: ^3.3.2
+    react-is: ^18.0.0
+    use-sync-external-store: ^1.0.0
+  peerDependencies:
+    "@types/react": ^16.8 || ^17.0 || ^18.0
+    "@types/react-dom": ^16.8 || ^17.0 || ^18.0
+    react: ^16.8 || ^17.0 || ^18.0
+    react-dom: ^16.8 || ^17.0 || ^18.0
+    react-native: ">=0.59"
+    redux: ^4 || ^5.0.0-beta.0
+  peerDependenciesMeta:
+    "@types/react":
+      optional: true
+    "@types/react-dom":
+      optional: true
+    react-dom:
+      optional: true
+    react-native:
+      optional: true
+    redux:
+      optional: true
+  checksum: 192ea6f6053148ec80a4148ec607bc259403b937e515f616a1104ca5ab357e97e98b8245ed505a17afee67a72341d4a559eaca9607968b4a422aa9b44ba7eb89
+  languageName: node
+  linkType: hard
+
 "react-refresh@npm:^0.14.0":
   version: 0.14.0
   resolution: "react-refresh@npm:0.14.0"
@@ -5912,6 +6000,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"redux-thunk@npm:^2.4.2":
+  version: 2.4.2
+  resolution: "redux-thunk@npm:2.4.2"
+  peerDependencies:
+    redux: ^4
+  checksum: c7f757f6c383b8ec26152c113e20087818d18ed3edf438aaad43539e9a6b77b427ade755c9595c4a163b6ad3063adf3497e5fe6a36c68884eb1f1cfb6f049a5c
+  languageName: node
+  linkType: hard
+
+"redux@npm:^4.2.1":
+  version: 4.2.1
+  resolution: "redux@npm:4.2.1"
+  dependencies:
+    "@babel/runtime": ^7.9.2
+  checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd
+  languageName: node
+  linkType: hard
+
 "regenerate-unicode-properties@npm:^10.1.0":
   version: 10.1.0
   resolution: "regenerate-unicode-properties@npm:10.1.0"
@@ -5935,6 +6041,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"regenerator-runtime@npm:^0.14.0":
+  version: 0.14.0
+  resolution: "regenerator-runtime@npm:0.14.0"
+  checksum: 1c977ad82a82a4412e4f639d65d22be376d3ebdd30da2c003eeafdaaacd03fc00c2320f18120007ee700900979284fc78a9f00da7fb593f6e6eeebc673fba9a3
+  languageName: node
+  linkType: hard
+
 "regenerator-transform@npm:^0.15.1":
   version: 0.15.1
   resolution: "regenerator-transform@npm:0.15.1"
@@ -5980,6 +6093,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"reselect@npm:^4.1.8":
+  version: 4.1.8
+  resolution: "reselect@npm:4.1.8"
+  checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e
+  languageName: node
+  linkType: hard
+
 "resize-observer-polyfill@npm:^1.5.1":
   version: 1.5.1
   resolution: "resize-observer-polyfill@npm:1.5.1"
@@ -6809,6 +6929,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"use-sync-external-store@npm:^1.0.0":
+  version: 1.2.0
+  resolution: "use-sync-external-store@npm:1.2.0"
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a
+  languageName: node
+  linkType: hard
+
 "util-deprecate@npm:^1.0.1":
   version: 1.0.2
   resolution: "util-deprecate@npm:1.0.2"


[incubator-devlake] 01/03: refactor(config-ui): remove connections context

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

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

commit 464f3645103f6285e6ef7823935bd9d1228068be
Author: mintsweet <0x...@gmail.com>
AuthorDate: Wed Oct 18 15:23:47 2023 +1300

    refactor(config-ui): remove connections context
---
 config-ui/src/hooks/index.ts                |   1 -
 config-ui/src/hooks/use-connections.ts      |  47 -------
 config-ui/src/store/connections/context.tsx | 182 ----------------------------
 config-ui/src/store/connections/index.ts    |  20 ---
 config-ui/src/store/connections/types.ts    |  45 -------
 config-ui/src/store/index.ts                |   1 -
 6 files changed, 296 deletions(-)

diff --git a/config-ui/src/hooks/index.ts b/config-ui/src/hooks/index.ts
index cba922842..7f38b5d6f 100644
--- a/config-ui/src/hooks/index.ts
+++ b/config-ui/src/hooks/index.ts
@@ -17,7 +17,6 @@
  */
 
 export * from './use-auto-refresh';
-export * from './use-connections';
 export * from './use-refresh-data';
 export * from './use-tips';
 export * from './user-proxy-prefix';
diff --git a/config-ui/src/hooks/use-connections.ts b/config-ui/src/hooks/use-connections.ts
deleted file mode 100644
index f129c13ee..000000000
--- a/config-ui/src/hooks/use-connections.ts
+++ /dev/null
@@ -1,47 +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 { useContext } from 'react';
-
-import { ConnectionContext } from '@/store';
-
-type UseConnectionsProps = {
-  unique?: string;
-  plugin?: string;
-  filter?: string[];
-  filterBeta?: boolean;
-  filterPlugin?: string[];
-};
-
-export const useConnections = (props?: UseConnectionsProps) => {
-  const { unique, plugin, filter, filterBeta, filterPlugin } = props || {};
-
-  const { connections, onGet, onTest, onRefresh } = useContext(ConnectionContext);
-
-  return {
-    connection: unique ? connections.find((cs) => cs.unique === unique) : null,
-    connections: connections
-      .filter((cs) => (plugin ? cs.plugin === plugin : true))
-      .filter((cs) => (filter ? !filter.includes(cs.unique) : true))
-      .filter((cs) => (filterBeta ? !cs.isBeta : true))
-      .filter((cs) => (filterPlugin ? !filterPlugin.includes(cs.plugin) : true)),
-    onGet,
-    onTest,
-    onRefresh,
-  };
-};
diff --git a/config-ui/src/store/connections/context.tsx b/config-ui/src/store/connections/context.tsx
deleted file mode 100644
index 42e213dea..000000000
--- a/config-ui/src/store/connections/context.tsx
+++ /dev/null
@@ -1,182 +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, useEffect, useMemo } from 'react';
-
-import { PageLoading } from '@/components';
-
-import API from '@/api';
-import type { PluginConfigType } from '@/plugins';
-import { PluginConfig, PluginType } from '@/plugins';
-
-import type { ConnectionItemType } from './types';
-import { ConnectionStatusEnum } from './types';
-
-export const ConnectionContext = React.createContext<{
-  connections: ConnectionItemType[];
-  onGet: (unique: string) => ConnectionItemType;
-  onTest: (unique: string) => void;
-  onRefresh: (plugin?: string) => void;
-}>(undefined!);
-
-interface Props {
-  children?: React.ReactNode;
-}
-
-export const ConnectionContextProvider = ({ children, ...props }: Props) => {
-  const [loading, setLoading] = useState(true);
-  const [connections, setConnections] = useState<ConnectionItemType[]>([]);
-
-  const plugins = useMemo(() => PluginConfig.filter((p) => p.type === PluginType.Connection), []);
-
-  const queryConnection = async (plugin: string) => {
-    try {
-      const res = await API.connection.list(plugin);
-      const { name, icon, isBeta, scopeConfig } = plugins.find((p) => p.plugin === plugin) as PluginConfigType;
-
-      return res.map((connection) => ({
-        ...connection,
-        plugin,
-        pluginName: name,
-        icon,
-        isBeta: isBeta ?? false,
-        entities: scopeConfig?.entities ?? [],
-      }));
-    } catch {
-      return [];
-    }
-  };
-
-  const testConnection = async ({
-    plugin,
-    endpoint,
-    proxy,
-    token,
-    username,
-    password,
-    authMethod,
-    secretKey,
-    appId,
-    dbUrl,
-  }: ConnectionItemType) => {
-    try {
-      const res = await API.connection.test(plugin, {
-        endpoint,
-        proxy,
-        token,
-        username,
-        password,
-        authMethod,
-        secretKey,
-        appId,
-        dbUrl,
-      });
-      return res.success ? ConnectionStatusEnum.ONLINE : ConnectionStatusEnum.OFFLINE;
-    } catch {
-      return ConnectionStatusEnum.OFFLINE;
-    }
-  };
-
-  const transformConnection = (connections: Omit<ConnectionItemType, 'unique' | 'status'>[]) => {
-    return connections.map((it) => ({
-      unique: `${it.plugin}-${it.id}`,
-      plugin: it.plugin,
-      pluginName: it.pluginName,
-      id: it.id,
-      name: it.name,
-      status: ConnectionStatusEnum.NULL,
-      icon: it.icon,
-      isBeta: it.isBeta,
-      entities: it.entities,
-      endpoint: it.endpoint,
-      proxy: it.proxy,
-      token: it.token,
-      username: it.username,
-      password: it.password,
-      authMethod: it.authMethod,
-      secretKey: it.secretKey,
-      appId: it.appId,
-      dbUrl: it.dbUrl,
-    }));
-  };
-
-  const handleGet = (unique: string) => {
-    return connections.find((cs) => cs.unique === unique) as ConnectionItemType;
-  };
-
-  const handleTest = async (unique: string) => {
-    setConnections((connections) =>
-      connections.map((cs) =>
-        cs.unique === unique
-          ? {
-              ...cs,
-              status: ConnectionStatusEnum.TESTING,
-            }
-          : cs,
-      ),
-    );
-
-    const connection = handleGet(unique);
-    const status = await testConnection(connection);
-
-    setConnections((connections) =>
-      connections.map((cs) =>
-        cs.unique === unique
-          ? {
-              ...cs,
-              status,
-            }
-          : cs,
-      ),
-    );
-  };
-
-  const handleRefresh = async (plugin?: string) => {
-    if (plugin) {
-      const res = await queryConnection(plugin);
-      setConnections([...connections.filter((cs) => cs.plugin !== plugin), ...transformConnection(res)]);
-      return;
-    }
-
-    const res = await Promise.all(plugins.map((cs) => queryConnection(cs.plugin)));
-
-    setConnections(transformConnection(res.flat()));
-    setLoading(false);
-  };
-
-  useEffect(() => {
-    handleRefresh();
-  }, []);
-
-  if (loading) {
-    return <PageLoading />;
-  }
-
-  return (
-    <ConnectionContext.Provider
-      value={{
-        connections,
-        onGet: handleGet,
-        onTest: handleTest,
-        onRefresh: handleRefresh,
-      }}
-    >
-      {children}
-    </ConnectionContext.Provider>
-  );
-};
diff --git a/config-ui/src/store/connections/index.ts b/config-ui/src/store/connections/index.ts
deleted file mode 100644
index 34ae5c1d0..000000000
--- a/config-ui/src/store/connections/index.ts
+++ /dev/null
@@ -1,20 +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 './types';
-export * from './context';
diff --git a/config-ui/src/store/connections/types.ts b/config-ui/src/store/connections/types.ts
deleted file mode 100644
index 07475889a..000000000
--- a/config-ui/src/store/connections/types.ts
+++ /dev/null
@@ -1,45 +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 enum ConnectionStatusEnum {
-  ONLINE = 'online',
-  OFFLINE = 'offline',
-  TESTING = 'testing',
-  NULL = 'null',
-}
-
-export type ConnectionItemType = {
-  unique: string;
-  plugin: string;
-  pluginName: string;
-  id: ID;
-  name: string;
-  status: ConnectionStatusEnum;
-  icon: string;
-  isBeta: boolean;
-  entities: string[];
-  endpoint: string;
-  proxy: string;
-  token?: string;
-  username?: string;
-  password?: string;
-  authMethod?: string;
-  appId?: string;
-  secretKey?: string;
-  dbUrl?: string;
-};
diff --git a/config-ui/src/store/index.ts b/config-ui/src/store/index.ts
index a247f9b21..378c0c717 100644
--- a/config-ui/src/store/index.ts
+++ b/config-ui/src/store/index.ts
@@ -16,5 +16,4 @@
  *
  */
 
-export * from './connections';
 export * from './tips';


[incubator-devlake] 03/03: refactor(config-ui): remove field connection type from connection

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

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

commit a71b17ddd399845c9974502e2c69850bd10465ce
Author: mintsweet <0x...@gmail.com>
AuthorDate: Wed Oct 18 20:17:23 2023 +1300

    refactor(config-ui): remove field connection type from connection
---
 config-ui/src/plugins/register/azure/config.tsx    |  2 -
 config-ui/src/plugins/register/bamboo/config.ts    |  2 -
 .../src/plugins/register/bitbucket/config.tsx      |  2 -
 config-ui/src/plugins/register/github/config.tsx   |  2 -
 config-ui/src/plugins/register/gitlab/config.tsx   |  2 -
 config-ui/src/plugins/register/jenkins/config.ts   |  2 -
 config-ui/src/plugins/register/jira/config.tsx     |  2 -
 .../src/plugins/register/pagerduty/config.tsx      |  2 -
 config-ui/src/plugins/register/sonarqube/config.ts |  2 -
 config-ui/src/plugins/register/tapd/config.tsx     |  2 -
 .../plugins/register/teambition/assets/icon.svg    |  5 --
 .../src/plugins/register/teambition/config.tsx     | 97 ----------------------
 .../register/teambition/connection-fields/index.ts | 20 -----
 .../teambition/connection-fields/styled.ts         | 32 -------
 .../teambition/connection-fields/tenant-id.tsx     | 72 ----------------
 .../teambition/connection-fields/tenant-type.tsx   | 76 -----------------
 config-ui/src/plugins/register/teambition/index.ts | 19 -----
 config-ui/src/plugins/register/webhook/config.ts   |  2 -
 config-ui/src/plugins/register/zentao/config.tsx   |  2 -
 config-ui/src/plugins/types.ts                     |  6 --
 config-ui/src/plugins/utils.ts                     |  3 +-
 21 files changed, 1 insertion(+), 353 deletions(-)

diff --git a/config-ui/src/plugins/register/azure/config.tsx b/config-ui/src/plugins/register/azure/config.tsx
index 500a4e389..799d15a3e 100644
--- a/config-ui/src/plugins/register/azure/config.tsx
+++ b/config-ui/src/plugins/register/azure/config.tsx
@@ -20,13 +20,11 @@ import { ExternalLink } from '@/components';
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 import { BaseURL } from './connection-fields';
 
 export const AzureConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'azuredevops',
   name: 'Azure DevOps',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/bamboo/config.ts b/config-ui/src/plugins/register/bamboo/config.ts
index 10ccbf44b..b2c78850c 100644
--- a/config-ui/src/plugins/register/bamboo/config.ts
+++ b/config-ui/src/plugins/register/bamboo/config.ts
@@ -19,12 +19,10 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const BambooConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'bamboo',
   name: 'Bamboo',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/bitbucket/config.tsx b/config-ui/src/plugins/register/bitbucket/config.tsx
index 11c03bb3d..97840f382 100644
--- a/config-ui/src/plugins/register/bitbucket/config.tsx
+++ b/config-ui/src/plugins/register/bitbucket/config.tsx
@@ -19,12 +19,10 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const BitBucketConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'bitbucket',
   name: 'BitBucket',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/github/config.tsx b/config-ui/src/plugins/register/github/config.tsx
index fc255675f..f79b56eaa 100644
--- a/config-ui/src/plugins/register/github/config.tsx
+++ b/config-ui/src/plugins/register/github/config.tsx
@@ -21,13 +21,11 @@ import { pick } from 'lodash';
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 import { Token, Graphql, GithubApp, Authentication } from './connection-fields';
 
 export const GitHubConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'github',
   name: 'GitHub',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/gitlab/config.tsx b/config-ui/src/plugins/register/gitlab/config.tsx
index 53d11d044..74bd7f007 100644
--- a/config-ui/src/plugins/register/gitlab/config.tsx
+++ b/config-ui/src/plugins/register/gitlab/config.tsx
@@ -20,12 +20,10 @@ import { ExternalLink } from '@/components';
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const GitLabConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'gitlab',
   name: 'GitLab',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/jenkins/config.ts b/config-ui/src/plugins/register/jenkins/config.ts
index fc77932ac..fb08df4e7 100644
--- a/config-ui/src/plugins/register/jenkins/config.ts
+++ b/config-ui/src/plugins/register/jenkins/config.ts
@@ -19,12 +19,10 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const JenkinsConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'jenkins',
   name: 'Jenkins',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/jira/config.tsx b/config-ui/src/plugins/register/jira/config.tsx
index 3daa049f5..ce81ecd44 100644
--- a/config-ui/src/plugins/register/jira/config.tsx
+++ b/config-ui/src/plugins/register/jira/config.tsx
@@ -19,13 +19,11 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 import { Auth } from './connection-fields';
 
 export const JiraConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'jira',
   name: 'Jira',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/pagerduty/config.tsx b/config-ui/src/plugins/register/pagerduty/config.tsx
index 5c115711f..b7f7ab351 100644
--- a/config-ui/src/plugins/register/pagerduty/config.tsx
+++ b/config-ui/src/plugins/register/pagerduty/config.tsx
@@ -19,12 +19,10 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const PagerDutyConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'pagerduty',
   name: 'PagerDuty',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/sonarqube/config.ts b/config-ui/src/plugins/register/sonarqube/config.ts
index db6bfe17e..c64db0837 100644
--- a/config-ui/src/plugins/register/sonarqube/config.ts
+++ b/config-ui/src/plugins/register/sonarqube/config.ts
@@ -19,12 +19,10 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const SonarQubeConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'sonarqube',
   name: 'SonarQube',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/tapd/config.tsx b/config-ui/src/plugins/register/tapd/config.tsx
index 0625f6aab..a3a2026b3 100644
--- a/config-ui/src/plugins/register/tapd/config.tsx
+++ b/config-ui/src/plugins/register/tapd/config.tsx
@@ -20,13 +20,11 @@ import { ExternalLink } from '@/components';
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import { DataScope } from './data-scope';
 import Icon from './assets/icon.svg';
 
 export const TAPDConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'tapd',
   name: 'TAPD',
   icon: Icon,
diff --git a/config-ui/src/plugins/register/teambition/assets/icon.svg b/config-ui/src/plugins/register/teambition/assets/icon.svg
deleted file mode 100644
index 93d227f72..000000000
--- a/config-ui/src/plugins/register/teambition/assets/icon.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M24.8601 7.75227C24.8606 6.58118 25.3105 5.45494 26.1169 4.60578C26.9234 3.75663 28.025 3.24932 29.1945 3.18845C26.7937 3.06965 24.1226 3 21.345 3C10.6606 3 2 3.98733 2 5.20407C2 6.42082 10.6606 7.40814 21.345 7.40814V12.3243H28.9487C28.7076 12.3059 28.4704 12.2535 28.244 12.1686C27.2724 11.9079 26.414 11.3336 25.8021 10.5351C25.1902 9.73651 24.8591 8.7583 24.8601 7.75227Z" fill="#BDCEFB"/>
-<path d="M21.345 7.40329C10.6606 7.40329 2 6.41596 2 5.19922V13.1757C2 14.0892 6.86288 14.8717 13.7987 15.2241V30.9066L21.4024 32.3609L21.345 7.40329Z" fill="#7497F7"/>
-<path d="M29.428 12.3237C31.953 12.3237 34 10.2768 34 7.7517C34 5.22665 31.953 3.17969 29.428 3.17969C26.9029 3.17969 24.856 5.22665 24.856 7.7517C24.856 10.2768 26.9029 12.3237 29.428 12.3237Z" fill="#7497F7"/>
-</svg>
diff --git a/config-ui/src/plugins/register/teambition/config.tsx b/config-ui/src/plugins/register/teambition/config.tsx
deleted file mode 100644
index 079ca4d1a..000000000
--- a/config-ui/src/plugins/register/teambition/config.tsx
+++ /dev/null
@@ -1,97 +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 { DOC_URL } from '@/release';
-
-import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
-
-import Icon from './assets/icon.svg';
-import { ConnectionTenantId, ConnectionTenantType } from './connection-fields';
-
-export const TeambitionConfig: PluginConfigType = {
-  type: PluginType.Pipeline,
-  plugin: 'teambition',
-  name: 'Teambition',
-  isBeta: true,
-  icon: Icon,
-  sort: 100,
-  connection: {
-    docLink: DOC_URL.PLUGIN.TEAMBITION.BASIS,
-    initialValues: {
-      endpoint: 'https://open.teambition.com/api/',
-      tenantType: 'organization',
-    },
-    fields: [
-      'name',
-      {
-        key: 'endpoint',
-        subLabel: 'You do not need to enter the endpoint URL, because all versions use the same URL.',
-        disabled: true,
-      },
-      {
-        key: 'appId',
-        label: 'Application App Id',
-        subLabel: 'Your teambition application App Id.',
-      },
-      {
-        key: 'secretKey',
-        label: 'Application Secret Key',
-        subLabel: 'Your teambition application App Secret.',
-      },
-      ({ initialValues, values, errors, setValues, setErrors }: any) => (
-        <ConnectionTenantId
-          key="tenantId"
-          name="tenantId"
-          value={values.tenantId ?? ''}
-          error={errors.tenantId ?? ''}
-          setValue={(value) => setValues({ tenantId: value })}
-          setError={(value) => setErrors({ tenantId: value })}
-          initialValue={initialValues.tenantId}
-        />
-      ),
-      ({ initialValues, values, errors, setValues, setErrors }: any) => (
-        <ConnectionTenantType
-          key="tenantType"
-          name="tenantType"
-          value={values.tenantType ?? ''}
-          error={errors.tenantType ?? ''}
-          setValue={(value) => setValues({ tenantType: value })}
-          setError={(value) => setErrors({ tenantType: value })}
-          initialValue={initialValues.tenantType}
-        />
-      ),
-      'proxy',
-      {
-        key: 'rateLimitPerHour',
-        subLabel:
-          'By default, DevLake uses dynamic rate limit for optimized data collection for Teambition. But you can adjust the collection speed by entering a fixed value. Please note: the rate limit setting applies to all tokens you have entered above.',
-        learnMore: DOC_URL.PLUGIN.TEAMBITION.RATE_LIMIT,
-        externalInfo: 'Teambition does not specify a maximum value of rate limit.',
-        defaultValue: 5000,
-      },
-    ],
-  },
-  dataScope: {
-    title: '',
-  },
-  scopeConfig: {
-    entities: ['TICKET'],
-    transformation: {},
-  },
-};
diff --git a/config-ui/src/plugins/register/teambition/connection-fields/index.ts b/config-ui/src/plugins/register/teambition/connection-fields/index.ts
deleted file mode 100644
index b2481eb94..000000000
--- a/config-ui/src/plugins/register/teambition/connection-fields/index.ts
+++ /dev/null
@@ -1,20 +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 './tenant-id';
-export * from './tenant-type';
diff --git a/config-ui/src/plugins/register/teambition/connection-fields/styled.ts b/config-ui/src/plugins/register/teambition/connection-fields/styled.ts
deleted file mode 100644
index 11e47a3d0..000000000
--- a/config-ui/src/plugins/register/teambition/connection-fields/styled.ts
+++ /dev/null
@@ -1,32 +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 Label = styled.label`
-  font-size: 16px;
-  font-weight: 600;
-`;
-
-export const LabelInfo = styled.i`
-  color: #ff8b8b;
-`;
-
-export const LabelDescription = styled.p`
-  margin: 0;
-`;
diff --git a/config-ui/src/plugins/register/teambition/connection-fields/tenant-id.tsx b/config-ui/src/plugins/register/teambition/connection-fields/tenant-id.tsx
deleted file mode 100644
index a5d1a64d4..000000000
--- a/config-ui/src/plugins/register/teambition/connection-fields/tenant-id.tsx
+++ /dev/null
@@ -1,72 +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.
- *
- */
-/*
- * 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 } from 'react';
-import { FormGroup, InputGroup } from '@blueprintjs/core';
-
-import * as S from './styled';
-
-interface Props {
-  name: string;
-  initialValue: string;
-  value: string;
-  error: string;
-  setValue: (value: string) => void;
-  setError: (error: string) => void;
-}
-
-export const ConnectionTenantId = ({ initialValue, value, setValue, setError }: Props) => {
-  useEffect(() => {
-    setValue(initialValue);
-  }, [initialValue]);
-
-  useEffect(() => {
-    setError(value ? '' : 'TenantId is required');
-  }, [value]);
-
-  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    setValue(e.target.value);
-  };
-
-  return (
-    <FormGroup
-      label={<S.Label>Tenant Id</S.Label>}
-      labelInfo={<S.LabelInfo>*</S.LabelInfo>}
-      subLabel={<S.LabelDescription>Your teambition organization id.</S.LabelDescription>}
-    >
-      <InputGroup placeholder="Your TenantId" value={value} onChange={handleChange} />
-    </FormGroup>
-  );
-};
diff --git a/config-ui/src/plugins/register/teambition/connection-fields/tenant-type.tsx b/config-ui/src/plugins/register/teambition/connection-fields/tenant-type.tsx
deleted file mode 100644
index 613f48b87..000000000
--- a/config-ui/src/plugins/register/teambition/connection-fields/tenant-type.tsx
+++ /dev/null
@@ -1,76 +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.
- *
- */
-/*
- * 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 } from 'react';
-import { FormGroup, InputGroup } from '@blueprintjs/core';
-
-import * as S from './styled';
-
-interface Props {
-  name: string;
-  initialValue: string;
-  value: string;
-  error: string;
-  setValue: (value: string) => void;
-  setError: (error: string) => void;
-}
-
-export const ConnectionTenantType = ({ initialValue, value, setValue, setError }: Props) => {
-  useEffect(() => {
-    setValue(initialValue);
-  }, [initialValue]);
-
-  useEffect(() => {
-    setError(value ? '' : 'TenantType is required');
-  }, [value]);
-
-  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    setValue(e.target.value);
-  };
-
-  return (
-    <FormGroup
-      label={<S.Label>Tenant Type</S.Label>}
-      labelInfo={<S.LabelInfo>*</S.LabelInfo>}
-      subLabel={
-        <S.LabelDescription>
-          You do not need to enter the tenant type, because teambition only supports 'organization' type currently.
-        </S.LabelDescription>
-      }
-    >
-      <InputGroup disabled={true} placeholder="Your API Tenant Type" value={value} onChange={handleChange} />
-    </FormGroup>
-  );
-};
diff --git a/config-ui/src/plugins/register/teambition/index.ts b/config-ui/src/plugins/register/teambition/index.ts
deleted file mode 100644
index de415db39..000000000
--- a/config-ui/src/plugins/register/teambition/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 './config';
diff --git a/config-ui/src/plugins/register/webhook/config.ts b/config-ui/src/plugins/register/webhook/config.ts
index 25088545d..b0f74c680 100644
--- a/config-ui/src/plugins/register/webhook/config.ts
+++ b/config-ui/src/plugins/register/webhook/config.ts
@@ -17,14 +17,12 @@
  */
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import Icon from './assets/icon.svg';
 
 export const WebhookConfig: PluginConfigType = {
   plugin: 'webhook',
   name: 'Webhook',
-  type: PluginType.Connection,
   icon: Icon,
   sort: 100,
   connection: {
diff --git a/config-ui/src/plugins/register/zentao/config.tsx b/config-ui/src/plugins/register/zentao/config.tsx
index c5ee0ccb1..705225dc7 100644
--- a/config-ui/src/plugins/register/zentao/config.tsx
+++ b/config-ui/src/plugins/register/zentao/config.tsx
@@ -19,13 +19,11 @@
 import { DOC_URL } from '@/release';
 
 import type { PluginConfigType } from '../../types';
-import { PluginType } from '../../types';
 
 import { DBUrl } from './connection-fields';
 import Icon from './assets/icon.svg';
 
 export const ZenTaoConfig: PluginConfigType = {
-  type: PluginType.Connection,
   plugin: 'zentao',
   name: 'ZenTao',
   icon: Icon,
diff --git a/config-ui/src/plugins/types.ts b/config-ui/src/plugins/types.ts
index b79e6c7c7..941a4715e 100644
--- a/config-ui/src/plugins/types.ts
+++ b/config-ui/src/plugins/types.ts
@@ -16,13 +16,7 @@
  *
  */
 
-export enum PluginType {
-  Connection = 'connection',
-  Pipeline = 'pipeline',
-}
-
 export type PluginConfigType = {
-  type: PluginType;
   plugin: string;
   name: string;
   icon: string;
diff --git a/config-ui/src/plugins/utils.ts b/config-ui/src/plugins/utils.ts
index f58667ffd..23e7af5f4 100644
--- a/config-ui/src/plugins/utils.ts
+++ b/config-ui/src/plugins/utils.ts
@@ -19,7 +19,7 @@
 import PluginIcon from '@/images/plugin-icon.svg';
 
 import { PluginConfig } from './config';
-import { PluginConfigType, PluginType } from './types';
+import { PluginConfigType } from './types';
 
 export const getPluginScopeId = (plugin: string, scope: any) => {
   switch (plugin) {
@@ -46,7 +46,6 @@ export const getPluginConfig = (name: string): PluginConfigType => {
   let pluginConfig = PluginConfig.find((plugin) => plugin.plugin === name) as PluginConfigType;
   if (!pluginConfig) {
     pluginConfig = {
-      type: PluginType.Pipeline,
       plugin: name,
       name: name,
       icon: PluginIcon,