You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/05/31 01:57:07 UTC

[incubator-devlake] branch main updated: feat(config-ui): improve the content about scope config (#5320)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new eda853733 feat(config-ui): improve the content about scope config (#5320)
eda853733 is described below

commit eda8537334e836295406bbde041abda06feaf668
Author: 青湛 <0x...@gmail.com>
AuthorDate: Wed May 31 09:57:03 2023 +0800

    feat(config-ui): improve the content about scope config (#5320)
    
    * feat(config-ui): support params position in buttons component
    
    * feat(config-ui): improve the style for card component
    
    * refactor(config-ui): adjust the type for table rowKey
    
    * fix(config-ui): depends error in github miller columns
    
    * refactor(config-ui): some code format
    
    * refactor(config-ui): remove plugin component about transformation
    
    * refactor(config-ui): remove content about transformation type
    
    * feat(config-ui): add plugin component about scope config
    
    * feat(config-ui): improve the content about scope config
---
 config-ui/src/components/buttons/index.tsx         |  22 ++-
 config-ui/src/components/card/index.tsx            |   4 +
 .../components/table/hooks/use-row-selection.ts    |  10 +-
 config-ui/src/global.d.ts                          |  10 --
 .../pages/blueprint/connection-detail/index.tsx    |  43 ++---
 .../pages/blueprint/detail/panel/configuration.tsx |   8 +-
 config-ui/src/pages/connection/detail/api.ts       |  11 +-
 config-ui/src/pages/connection/detail/index.tsx    | 111 +++++++++++-
 config-ui/src/pages/connection/detail/styled.ts    |   9 -
 config-ui/src/plugins/components/index.ts          |   5 +-
 .../{transformation => scope-config-form}/api.ts   |  14 +-
 .../fields/additional-settings.tsx                 |   0
 .../fields/index.ts                                |   0
 .../fields/styled.ts                               |   0
 .../plugins/components/scope-config-form/index.tsx | 198 +++++++++++++++++++++
 .../misc.ts                                        |   0
 .../api.ts => scope-config-form/styled.ts}         |   5 +-
 .../api.ts                                         |  19 +-
 .../components/scope-config-select/index.tsx       | 106 +++++++++++
 .../index.ts => scope-config-select/styled.ts}     |   6 +-
 .../components/transformation-form/index.tsx       | 154 ----------------
 .../components/transformation-form/styled.ts       |  39 ----
 .../components/transformation-select/index.tsx     | 145 ---------------
 .../components/transformation-select/styled.ts     |  36 ----
 .../plugins/components/transformation/index.tsx    | 180 -------------------
 .../plugins/components/transformation/styled.ts    |  52 ------
 config-ui/src/plugins/config.ts                    |   5 +-
 .../github/components/miller-columns/index.tsx     |   2 +-
 .../miller-columns/use-miller-columns.ts           |   4 +-
 config-ui/src/plugins/register/github/config.tsx   |   5 +-
 .../github/connection-fields/authentication.tsx    |  15 +-
 .../github/connection-fields/githubapp.tsx         |  28 +--
 .../register/github/connection-fields/index.ts     |   2 +-
 .../register/github/connection-fields/token.tsx    |   2 +-
 .../register/jira/connection-fields/styled.ts      |   1 -
 config-ui/src/plugins/register/tapd/config.tsx     |   1 -
 config-ui/src/plugins/types.ts                     |   1 -
 config-ui/src/store/connections/context.tsx        |   2 -
 config-ui/src/utils/operator.ts                    |   2 -
 39 files changed, 525 insertions(+), 732 deletions(-)

diff --git a/config-ui/src/components/buttons/index.tsx b/config-ui/src/components/buttons/index.tsx
index ac5dd647e..1c411f2d3 100644
--- a/config-ui/src/components/buttons/index.tsx
+++ b/config-ui/src/components/buttons/index.tsx
@@ -18,10 +18,19 @@
 
 import styled from 'styled-components';
 
-const Wrapper = styled.div<{ align: 'left' | 'right' | 'center' }>`
+const Wrapper = styled.div<{ position: 'top' | 'bottom'; align: 'left' | 'right' | 'center' }>`
   display: flex;
   align-items: center;
-  margin-top: 24px;
+
+  ${({ position }) => {
+    if (position === 'top') {
+      return 'margin-bottom: 24px;';
+    }
+
+    if (position === 'bottom') {
+      return 'margin-top: 24px;';
+    }
+  }}
 
   ${({ align }) => {
     if (align === 'left') {
@@ -43,10 +52,15 @@ const Wrapper = styled.div<{ align: 'left' | 'right' | 'center' }>`
 `;
 
 interface Props {
+  position?: 'top' | 'bottom';
   align?: 'left' | 'right' | 'center';
   children: React.ReactNode;
 }
 
-export const Buttons = ({ align = 'right', children }: Props) => {
-  return <Wrapper align={align}>{children}</Wrapper>;
+export const Buttons = ({ position = 'bottom', align = 'right', children }: Props) => {
+  return (
+    <Wrapper position={position} align={align}>
+      {children}
+    </Wrapper>
+  );
 };
diff --git a/config-ui/src/components/card/index.tsx b/config-ui/src/components/card/index.tsx
index 2aa84c8a0..418b1f065 100644
--- a/config-ui/src/components/card/index.tsx
+++ b/config-ui/src/components/card/index.tsx
@@ -23,4 +23,8 @@ export const Card = styled.div`
   background-color: #fff;
   box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
   border-radius: 8px;
+
+  & + & {
+    margin-top: 24px;
+  }
 `;
diff --git a/config-ui/src/components/table/hooks/use-row-selection.ts b/config-ui/src/components/table/hooks/use-row-selection.ts
index 358122e48..ab56bdd81 100644
--- a/config-ui/src/components/table/hooks/use-row-selection.ts
+++ b/config-ui/src/components/table/hooks/use-row-selection.ts
@@ -21,15 +21,15 @@ import { useState, useEffect, useMemo } from 'react';
 export interface UseRowSelectionProps<T> {
   dataSource: T[];
   rowSelection?: {
-    rowKey: string;
+    rowKey: ID;
     type?: 'checkbox' | 'radio';
-    selectedRowKeys?: string[];
-    onChange?: (selectedRowKeys: string[]) => void;
+    selectedRowKeys?: ID[];
+    onChange?: (selectedRowKeys: ID[]) => void;
   };
 }
 
 export const useRowSelection = <T>({ dataSource, rowSelection }: UseRowSelectionProps<T>) => {
-  const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
+  const [selectedKeys, setSelectedKeys] = useState<ID[]>([]);
 
   const {
     rowKey = 'key',
@@ -48,7 +48,7 @@ export const useRowSelection = <T>({ dataSource, rowSelection }: UseRowSelection
 
   const handleChecked = (data: T) => {
     const key = (data as any)[rowKey];
-    let result: string[] = selectedKeys;
+    let result: ID[] = selectedKeys;
 
     switch (true) {
       case !selectedKeys.includes(key) && type === 'radio':
diff --git a/config-ui/src/global.d.ts b/config-ui/src/global.d.ts
index a1c271815..0f3a050aa 100644
--- a/config-ui/src/global.d.ts
+++ b/config-ui/src/global.d.ts
@@ -18,16 +18,6 @@
 
 type ID = string | number;
 
-type MixConnection = {
-  unique: string;
-  plugin: string;
-  connectionId: ID;
-  name: string;
-  icon: string;
-  scope: Array<any>;
-  transformationType?: 'none' | 'for-connection' | 'for-scope';
-};
-
 declare module '*.svg' {
   const content: any;
   export default content;
diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx b/config-ui/src/pages/blueprint/connection-detail/index.tsx
index 01929b4d6..657fd570c 100644
--- a/config-ui/src/pages/blueprint/connection-detail/index.tsx
+++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx
@@ -24,7 +24,7 @@ import { Popover2 } from '@blueprintjs/popover2';
 import { Dialog, PageHeader, PageLoading } from '@/components';
 import { EntitiesLabel } from '@/config';
 import { useRefreshData } from '@/hooks';
-import { DataScopeSelect, getPluginConfig, Transformation } from '@/plugins';
+import { DataScopeSelect, getPluginConfig } from '@/plugins';
 
 import * as API from './api';
 import * as S from './styled';
@@ -59,7 +59,6 @@ export const BlueprintConnectionDetailPage = () => {
         icon: config.icon,
         scope,
         origin,
-        transformationType: config.transformationType,
       },
     };
   }, [version]);
@@ -73,33 +72,6 @@ export const BlueprintConnectionDetailPage = () => {
   const handleShowDataScope = () => setIsOpen(true);
   const handleHideDataScope = () => setIsOpen(false);
 
-  const handleChangeDataScope = async (connections: MixConnection[]) => {
-    const [connection] = connections;
-    await API.updateBlueprint(blueprint.id, {
-      ...blueprint,
-      settings: {
-        ...blueprint.settings,
-        connections: blueprint.settings.connections.map((cs: any) => {
-          if (cs.plugin === connection.plugin && cs.connectionId === connection.connectionId) {
-            return {
-              ...cs,
-              scopes: connection.scope.map((sc: any) => ({
-                id: `${sc.id}`,
-                entities: sc.entities,
-              })),
-            };
-          }
-          return cs;
-        }),
-      },
-    });
-    setVersion((v) => v + 1);
-  };
-
-  const handleChangeTransformation = () => {
-    setVersion((v) => v + 1);
-  };
-
   const handleRemoveConnection = async () => {
     await API.updateBlueprint(blueprint.id, {
       ...blueprint,
@@ -113,6 +85,11 @@ export const BlueprintConnectionDetailPage = () => {
     history.push(pname ? `/projects/:${pname}` : `/blueprints/${blueprint.id}`);
   };
 
+  const handleChangeDataScope = (scope: any) => {
+    console.log(scope);
+    setVersion((v) => v + 1);
+  };
+
   return (
     <PageHeader
       breadcrumbs={[
@@ -161,7 +138,6 @@ export const BlueprintConnectionDetailPage = () => {
           ))}
         </ul>
       </S.Entities>
-      <Transformation connections={[connection]} noFooter onSubmit={handleChangeTransformation} />
       <Dialog
         isOpen={isOpen}
         title="Change Data Scope"
@@ -169,7 +145,12 @@ export const BlueprintConnectionDetailPage = () => {
         style={{ width: 820 }}
         onCancel={handleHideDataScope}
       >
-        <DataScopeSelect plugin={connection.plugin} connectionId={connection.connectionId} />
+        <DataScopeSelect
+          plugin={connection.plugin}
+          connectionId={connection.connectionId}
+          onCancel={handleHideDataScope}
+          onSubmit={handleChangeDataScope}
+        />
       </Dialog>
     </PageHeader>
   );
diff --git a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
index 32c1b78c7..8d7b88220 100644
--- a/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/configuration.tsx
@@ -83,6 +83,10 @@ export const Configuration = ({ blueprint, operating, onUpdate }: Props) => {
     setType('add-connection');
   };
 
+  const handleAddConnection = (value: any) => {
+    console.log(value);
+  };
+
   return (
     <S.ConfigurationPanel>
       <div className="block">
@@ -201,9 +205,7 @@ export const Configuration = ({ blueprint, operating, onUpdate }: Props) => {
           onSubmit={(payload) => onUpdate(payload, handleCancel)}
         />
       )}
-      {type === 'add-connection' && (
-        <AddConnectionDialog onCancel={handleCancel} onSubmit={(value) => console.log(value)} />
-      )}
+      {type === 'add-connection' && <AddConnectionDialog onCancel={handleCancel} onSubmit={handleAddConnection} />}
     </S.ConfigurationPanel>
   );
 };
diff --git a/config-ui/src/pages/connection/detail/api.ts b/config-ui/src/pages/connection/detail/api.ts
index 055719883..60a50e4d8 100644
--- a/config-ui/src/pages/connection/detail/api.ts
+++ b/config-ui/src/pages/connection/detail/api.ts
@@ -21,7 +21,16 @@ import { request } from '@/utils';
 export const deleteConnection = (plugin: string, id: ID) =>
   request(`/plugins/${plugin}/connections/${id}`, { method: 'delete' });
 
-export const getDataScope = (plugin: string, id: ID) => request(`/plugins/${plugin}/connections/${id}/scopes`);
+export const getDataScopes = (plugin: string, id: ID) => request(`/plugins/${plugin}/connections/${id}/scopes`);
+
+export const getDataScope = (plugin: string, id: ID, scopeId: ID) =>
+  request(`/plugins/${plugin}/connections/${id}/scopes/${scopeId}`);
+
+export const updateDataScope = (plugin: string, id: ID, scopeId: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${id}/scopes/${scopeId}`, {
+    method: 'patch',
+    data: payload,
+  });
 
 export const deleteDataScope = (plugin: string, id: ID, scopeId: ID, onlyData: boolean) =>
   request(`/plugins/${plugin}/connections/${id}/scopes/${scopeId}`, {
diff --git a/config-ui/src/pages/connection/detail/index.tsx b/config-ui/src/pages/connection/detail/index.tsx
index a82eba8c4..0d7945497 100644
--- a/config-ui/src/pages/connection/detail/index.tsx
+++ b/config-ui/src/pages/connection/detail/index.tsx
@@ -20,9 +20,16 @@ import { useState } from 'react';
 import { useParams, useHistory } from 'react-router-dom';
 import { Button, Icon, Intent } from '@blueprintjs/core';
 
-import { PageHeader, Dialog, IconButton, Table } from '@/components';
+import { PageHeader, Buttons, Dialog, IconButton, Table } from '@/components';
 import { useTips, useConnections, useRefreshData } from '@/hooks';
-import { ConnectionForm, ConnectionStatus, DataScopeSelectRemote, getPluginId } from '@/plugins';
+import {
+  ConnectionForm,
+  ConnectionStatus,
+  DataScopeSelectRemote,
+  getPluginId,
+  ScopeConfigForm,
+  ScopeConfigSelect,
+} from '@/plugins';
 import { operator } from '@/utils';
 
 import * as API from './api';
@@ -35,16 +42,23 @@ interface Props {
 
 const ConnectionDetail = ({ plugin, id }: Props) => {
   const [type, setType] = useState<
-    'deleteConnection' | 'updateConnection' | 'createDataScope' | 'clearDataScope' | 'deleteDataScope'
+    | 'deleteConnection'
+    | 'updateConnection'
+    | 'createDataScope'
+    | 'clearDataScope'
+    | 'deleteDataScope'
+    | 'associateScopeConfig'
   >();
   const [operating, setOperating] = useState(false);
   const [version, setVersion] = useState(1);
   const [scopeId, setScopeId] = useState<ID>();
+  const [scopeIds, setScopeIds] = useState<ID[]>([]);
+  const [scopeConfigId, setScopeConfigId] = useState<ID>();
 
   const history = useHistory();
   const { onGet, onTest, onRefresh } = useConnections();
   const { setTips } = useTips();
-  const { ready, data } = useRefreshData(() => API.getDataScope(plugin, id), [version]);
+  const { ready, data } = useRefreshData(() => API.getDataScopes(plugin, id), [version]);
 
   const { unique, status, name, icon } = onGet(`${plugin}-${id}`);
 
@@ -123,6 +137,36 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
     }
   };
 
+  const handleShowScopeConfigSelectDialog = (scopeIds: ID[]) => {
+    setType('associateScopeConfig');
+    setScopeIds(scopeIds);
+  };
+
+  const handleAssociateScopeConfig = async (trId: ID) => {
+    const [success] = await operator(
+      () =>
+        Promise.all(
+          scopeIds.map(async (scopeId) => {
+            const scope = await API.getDataScope(plugin, id, scopeId);
+            return API.updateDataScope(plugin, id, scopeId, {
+              ...scope,
+              scopeConfigId: +trId,
+            });
+          }),
+        ),
+      {
+        setOperating,
+        formatMessage: () => `Associate scope config successful.`,
+      },
+    );
+
+    if (success) {
+      setVersion((v) => v + 1);
+      handleShowTips();
+      handleHideDialog();
+    }
+  };
+
   return (
     <PageHeader
       breadcrumbs={[
@@ -137,9 +181,17 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
           <ConnectionStatus status={status} unique={unique} onTest={onTest} />
           <IconButton icon="annotation" tooltip="Edit Connection" onClick={handleShowUpdateDialog} />
         </div>
-        <div className="action">
+        <Buttons position="top" align="left">
           <Button intent={Intent.PRIMARY} icon="add" text="Add Data Scope" onClick={handleShowCreateDataScopeDialog} />
-        </div>
+          {plugin !== 'tapd' && (
+            <Button
+              intent={Intent.PRIMARY}
+              icon="many-to-one"
+              text="Associate Scope Config"
+              onClick={() => handleShowScopeConfigSelectDialog(scopeIds)}
+            />
+          )}
+        </Buttons>
         <Table
           loading={!ready}
           columns={[
@@ -148,6 +200,27 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
               dataIndex: 'name',
               key: 'name',
             },
+            {
+              title: 'Scope Config',
+              dataIndex: 'scopeConfigName',
+              key: 'scopeConfig',
+              width: 400,
+              render: (val, row) => (
+                <>
+                  <span>{val ?? 'No Scope Config'}</span>
+                  <IconButton
+                    icon="link"
+                    tooltip="Associate Scope Config"
+                    onClick={() => {
+                      handleShowScopeConfigSelectDialog([row[getPluginId(plugin)]]);
+                      if (plugin === 'tapd') {
+                        setScopeConfigId(row.scopeConfigId);
+                      }
+                    }}
+                  />
+                </>
+              ),
+            },
             {
               title: '',
               dataIndex: getPluginId(plugin),
@@ -175,6 +248,11 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
             btnText: 'Add Data Scope',
             onCreate: handleShowCreateDataScopeDialog,
           }}
+          rowSelection={{
+            rowKey: getPluginId(plugin),
+            selectedRowKeys: scopeIds,
+            onChange: (selectedRowKeys) => setScopeIds(selectedRowKeys),
+          }}
         />
       </S.Wrapper>
       {type === 'deleteConnection' && (
@@ -269,6 +347,27 @@ const ConnectionDetail = ({ plugin, id }: Props) => {
           </S.DialogBody>
         </Dialog>
       )}
+      {type === 'associateScopeConfig' && (
+        <Dialog isOpen style={{ width: 820 }} footer={null} title="Associate Scope Config" onCancel={handleHideDialog}>
+          {plugin === 'tapd' ? (
+            <ScopeConfigForm
+              plugin={plugin}
+              connectionId={id}
+              scopeId={scopeIds[0]}
+              scopeConfigId={scopeConfigId}
+              onCancel={handleHideDialog}
+              onSubmit={handleAssociateScopeConfig}
+            />
+          ) : (
+            <ScopeConfigSelect
+              plugin={plugin}
+              connectionId={id}
+              onCancel={handleHideDialog}
+              onSubmit={handleAssociateScopeConfig}
+            />
+          )}
+        </Dialog>
+      )}
     </PageHeader>
   );
 };
diff --git a/config-ui/src/pages/connection/detail/styled.ts b/config-ui/src/pages/connection/detail/styled.ts
index ce2815f10..8a166f523 100644
--- a/config-ui/src/pages/connection/detail/styled.ts
+++ b/config-ui/src/pages/connection/detail/styled.ts
@@ -23,15 +23,6 @@ export const Wrapper = styled.div`
     display: flex;
     align-items: center;
     justify-content: flex-end;
-
-    & > .bp4-popover2-target {
-      margin-left: 8px;
-    }
-  }
-
-  .action {
-    margin-top: 36px;
-    margin-bottom: 24px;
   }
 `;
 
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index b7fc05447..194f390e3 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -23,6 +23,5 @@ export * from './data-scope-miller-columns';
 export * from './data-scope-search';
 export * from './data-scope-select';
 export * from './data-scope-select-remote';
-export * from './transformation';
-export * from './transformation-form';
-export * from './transformation-select';
+export * from './scope-config-form';
+export * from './scope-config-select';
diff --git a/config-ui/src/plugins/components/transformation/api.ts b/config-ui/src/plugins/components/scope-config-form/api.ts
similarity index 62%
rename from config-ui/src/plugins/components/transformation/api.ts
rename to config-ui/src/plugins/components/scope-config-form/api.ts
index ef0f79387..0e13fd9f2 100644
--- a/config-ui/src/plugins/components/transformation/api.ts
+++ b/config-ui/src/plugins/components/scope-config-form/api.ts
@@ -18,11 +18,17 @@
 
 import { request } from '@/utils';
 
-export const getDataScope = (plugin: string, connectionId: ID, scopeId: ID) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`);
+export const getScopeConfig = (plugin: string, connectionId: ID, id: ID) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs/${id}`);
 
-export const updateDataScope = (plugin: string, connectionId: ID, scopeId: ID, payload: any) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/scopes/${scopeId}`, {
+export const createScopeConfig = (plugin: string, connectionId: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs`, {
+    method: 'post',
+    data: payload,
+  });
+
+export const updateScopeConfig = (plugin: string, connectionId: ID, id: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs/${id}`, {
     method: 'patch',
     data: payload,
   });
diff --git a/config-ui/src/plugins/components/transformation-form/fields/additional-settings.tsx b/config-ui/src/plugins/components/scope-config-form/fields/additional-settings.tsx
similarity index 100%
rename from config-ui/src/plugins/components/transformation-form/fields/additional-settings.tsx
rename to config-ui/src/plugins/components/scope-config-form/fields/additional-settings.tsx
diff --git a/config-ui/src/plugins/components/transformation-form/fields/index.ts b/config-ui/src/plugins/components/scope-config-form/fields/index.ts
similarity index 100%
copy from config-ui/src/plugins/components/transformation-form/fields/index.ts
copy to config-ui/src/plugins/components/scope-config-form/fields/index.ts
diff --git a/config-ui/src/plugins/components/transformation-form/fields/styled.ts b/config-ui/src/plugins/components/scope-config-form/fields/styled.ts
similarity index 100%
rename from config-ui/src/plugins/components/transformation-form/fields/styled.ts
rename to config-ui/src/plugins/components/scope-config-form/fields/styled.ts
diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx
new file mode 100644
index 000000000..0637d2ee9
--- /dev/null
+++ b/config-ui/src/plugins/components/scope-config-form/index.tsx
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { useState, useEffect, useMemo } from 'react';
+import { omit } from 'lodash';
+import { InputGroup, Button, Intent } from '@blueprintjs/core';
+
+import { Alert, ExternalLink, Card, FormItem, MultiSelector, Buttons, Divider } from '@/components';
+import { transformEntities, EntitiesLabel } from '@/config';
+import { getPluginConfig } from '@/plugins';
+import { GitHubTransformation } from '@/plugins/register/github';
+import { JiraTransformation } from '@/plugins/register/jira';
+import { GitLabTransformation } from '@/plugins/register/gitlab';
+import { JenkinsTransformation } from '@/plugins/register/jenkins';
+import { BitbucketTransformation } from '@/plugins/register/bitbucket';
+import { AzureTransformation } from '@/plugins/register/azure';
+import { TapdTransformation } from '@/plugins/register/tapd';
+import { operator } from '@/utils';
+
+import { AdditionalSettings } from './fields';
+import { TIPS_MAP } from './misc';
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+  plugin: string;
+  connectionId: ID;
+  scopeId?: ID;
+  scopeConfigId?: ID;
+  onCancel?: () => void;
+  onSubmit?: (trId: string) => void;
+}
+
+export const ScopeConfigForm = ({ plugin, connectionId, scopeId, scopeConfigId, onCancel, onSubmit }: Props) => {
+  const [step, setStep] = useState(1);
+  const [name, setName] = useState('');
+  const [entities, setEntities] = useState<string[]>([]);
+  const [transformation, setTransformation] = useState<any>({});
+  const [hasRefDiff, setHasRefDiff] = useState(false);
+  const [operating, setOperating] = useState(false);
+
+  const config = useMemo(() => getPluginConfig(plugin), []);
+
+  useEffect(() => {
+    setHasRefDiff(!!config.transformation.refdiff);
+  }, [config.transformation]);
+
+  useEffect(() => {
+    if (!scopeConfigId) return;
+
+    (async () => {
+      try {
+        const res = await API.getScopeConfig(plugin, connectionId, scopeConfigId);
+        setName(res.name);
+        setEntities(res.entities);
+        setTransformation(omit(res, ['id', 'connectionId', 'name', 'entities', 'createdAt', 'updatedAt']));
+      } catch {}
+    })();
+  }, [scopeConfigId]);
+
+  const handleNextStep = () => {
+    setStep(2);
+  };
+
+  const handleSubmit = async () => {
+    const [success, res] = await operator(
+      () =>
+        !scopeConfigId
+          ? API.createScopeConfig(plugin, connectionId, { name, entities, ...transformation })
+          : API.updateScopeConfig(plugin, connectionId, scopeConfigId, { name, entities, ...transformation }),
+      {
+        setOperating,
+        formatMessage: () => (!scopeConfigId ? 'Create scope config successful.' : 'Update scope config successful'),
+      },
+    );
+
+    if (success) {
+      onCancel?.();
+      onSubmit?.(res.id);
+    }
+  };
+
+  return (
+    <S.Wrapper>
+      {TIPS_MAP[plugin] && (
+        <Alert style={{ marginBottom: 24 }}>
+          To learn about how {TIPS_MAP[plugin].name} transformation is used in DevLake,{' '}
+          <ExternalLink link={TIPS_MAP[plugin].link}>check out this doc</ExternalLink>.
+        </Alert>
+      )}
+      {step === 1 && (
+        <>
+          <Card>
+            <FormItem
+              label="Scope Config Name"
+              subLabel="Give this Scope Config a unique name so that you can identify it in the future."
+              required
+            >
+              <InputGroup placeholder="My Scope Config 1" value={name} onChange={(e) => setName(e.target.value)} />
+            </FormItem>
+          </Card>
+          <Card>
+            <FormItem
+              label="Data Entities"
+              subLabel={
+                <>
+                  Select the data entities you wish to collect for the Data Scope.
+                  <ExternalLink link="">Learn about data entities</ExternalLink>
+                </>
+              }
+              required
+            >
+              <MultiSelector
+                items={transformEntities(config.entities)}
+                getKey={(it) => it.value}
+                getName={(it) => it.label}
+                selectedItems={entities.map((it) => ({ label: EntitiesLabel[it], value: it }))}
+                onChangeItems={(its) => setEntities(its.map((it) => it.value))}
+              />
+            </FormItem>
+          </Card>
+          <Buttons>
+            <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+            <Button disabled={!name || !entities.length} intent={Intent.PRIMARY} text="Next" onClick={handleNextStep} />
+          </Buttons>
+        </>
+      )}
+      {step === 2 && (
+        <>
+          <Card>
+            {plugin === 'github' && (
+              <GitHubTransformation transformation={transformation} setTransformation={setTransformation} />
+            )}
+
+            {plugin === 'jira' && (
+              <JiraTransformation
+                connectionId={connectionId}
+                transformation={transformation}
+                setTransformation={setTransformation}
+              />
+            )}
+
+            {plugin === 'gitlab' && (
+              <GitLabTransformation transformation={transformation} setTransformation={setTransformation} />
+            )}
+
+            {plugin === 'jenkins' && (
+              <JenkinsTransformation transformation={transformation} setTransformation={setTransformation} />
+            )}
+
+            {plugin === 'bitbucket' && (
+              <BitbucketTransformation transformation={transformation} setTransformation={setTransformation} />
+            )}
+
+            {plugin === 'azuredevops' && (
+              <AzureTransformation transformation={transformation} setTransformation={setTransformation} />
+            )}
+
+            {plugin === 'tapd' && scopeId && (
+              <TapdTransformation
+                connectionId={connectionId}
+                scopeId={scopeId}
+                transformation={transformation}
+                setTransformation={setTransformation}
+              />
+            )}
+
+            {hasRefDiff && (
+              <>
+                <Divider />
+                <AdditionalSettings transformation={transformation} setTransformation={setTransformation} />
+              </>
+            )}
+          </Card>
+          <Buttons>
+            <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+            <Button loading={operating} intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} />
+          </Buttons>
+        </>
+      )}
+    </S.Wrapper>
+  );
+};
diff --git a/config-ui/src/plugins/components/transformation-form/misc.ts b/config-ui/src/plugins/components/scope-config-form/misc.ts
similarity index 100%
rename from config-ui/src/plugins/components/transformation-form/misc.ts
rename to config-ui/src/plugins/components/scope-config-form/misc.ts
diff --git a/config-ui/src/plugins/components/transformation-select/api.ts b/config-ui/src/plugins/components/scope-config-form/styled.ts
similarity index 81%
rename from config-ui/src/plugins/components/transformation-select/api.ts
rename to config-ui/src/plugins/components/scope-config-form/styled.ts
index e1d6c5cc6..4bf74a53c 100644
--- a/config-ui/src/plugins/components/transformation-select/api.ts
+++ b/config-ui/src/plugins/components/scope-config-form/styled.ts
@@ -16,7 +16,6 @@
  *
  */
 
-import { request } from '@/utils';
+import styled from 'styled-components';
 
-export const getTransformations = (plugin: string, connectionId: ID) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/transformation_rules`);
+export const Wrapper = styled.div``;
diff --git a/config-ui/src/plugins/components/transformation-form/api.ts b/config-ui/src/plugins/components/scope-config-select/api.ts
similarity index 58%
rename from config-ui/src/plugins/components/transformation-form/api.ts
rename to config-ui/src/plugins/components/scope-config-select/api.ts
index 9bfc6c4c3..8d35d854a 100644
--- a/config-ui/src/plugins/components/transformation-form/api.ts
+++ b/config-ui/src/plugins/components/scope-config-select/api.ts
@@ -18,19 +18,20 @@
 
 import { request } from '@/utils';
 
-export const getTransformation = (plugin: string, connectionId: ID, tid: ID) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/transformation_rules/${tid}`, {
-    method: 'get',
-  });
+export const getScopeConfigs = (plugin: string, connectionId: ID) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs`);
+
+export const getScopeConfig = (plugin: string, connectionId: ID, id: ID) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs/${id}`);
 
-export const createTransformation = (plugin: string, connectionId: ID, paylod: any) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/transformation_rules`, {
+export const createScopeConfig = (plugin: string, connectionId: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs`, {
     method: 'post',
-    data: paylod,
+    data: payload,
   });
 
-export const updateTransformation = (plugin: string, connectionId: ID, tid: ID, payload: any) =>
-  request(`/plugins/${plugin}/connections/${connectionId}/transformation_rules/${tid}`, {
+export const updateScopeConfig = (plugin: string, connectionId: ID, id: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${connectionId}/scope_configs/${id}`, {
     method: 'patch',
     data: payload,
   });
diff --git a/config-ui/src/plugins/components/scope-config-select/index.tsx b/config-ui/src/plugins/components/scope-config-select/index.tsx
new file mode 100644
index 000000000..18fbcb6bd
--- /dev/null
+++ b/config-ui/src/plugins/components/scope-config-select/index.tsx
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { useState, useMemo } from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+
+import { Buttons, Table, IconButton, Dialog } from '@/components';
+import { useRefreshData } from '@/hooks';
+
+import { ScopeConfigForm } from '../scope-config-form';
+
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+  plugin: string;
+  connectionId: ID;
+  onCancel?: () => void;
+  onSubmit?: (trId: string) => void;
+}
+
+export const ScopeConfigSelect = ({ plugin, connectionId, onCancel, onSubmit }: Props) => {
+  const [version, setVersion] = useState(1);
+  const [trId, setTrId] = useState<string>();
+  const [isOpen, setIsOpen] = useState(false);
+  const [updatedId, setUpdatedId] = useState<ID>();
+
+  const { ready, data } = useRefreshData(() => API.getScopeConfigs(plugin, connectionId), [version]);
+
+  const dataSource = useMemo(() => (data ? data : []), [data]);
+
+  const handleShowDialog = () => {
+    setIsOpen(true);
+  };
+
+  const handleHideDialog = () => {
+    setIsOpen(false);
+  };
+
+  const handleUpdate = async (id: ID) => {
+    setUpdatedId(id);
+    handleShowDialog();
+  };
+
+  const handleSubmit = async () => {
+    handleHideDialog();
+    setVersion((v) => v + 1);
+  };
+
+  return (
+    <S.Wrapper>
+      <Buttons position="top" align="left">
+        <Button icon="add" intent={Intent.PRIMARY} text="Add New Scope Config" onClick={handleShowDialog} />
+      </Buttons>
+      <Table
+        loading={!ready}
+        columns={[
+          { title: 'Name', dataIndex: 'name', key: 'name' },
+          {
+            title: '',
+            dataIndex: 'id',
+            key: 'id',
+            width: 100,
+            render: (id) => <IconButton icon="annotation" tooltip="Edit" onClick={() => handleUpdate(id)} />,
+          },
+        ]}
+        dataSource={dataSource}
+        rowSelection={{
+          rowKey: 'id',
+          type: 'radio',
+          selectedRowKeys: trId ? [`${trId}`] : [],
+          onChange: (selectedRowKeys) => setTrId(`${selectedRowKeys[0]}`),
+        }}
+        noShadow
+      />
+      <Buttons>
+        <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
+        <Button disabled={!trId} intent={Intent.PRIMARY} text="Save" onClick={() => trId && onSubmit?.(trId)} />
+      </Buttons>
+      <Dialog style={{ width: 820 }} footer={null} isOpen={isOpen} title="Add Scope Config" onCancel={handleHideDialog}>
+        <ScopeConfigForm
+          plugin={plugin}
+          connectionId={connectionId}
+          scopeConfigId={updatedId}
+          onCancel={onCancel}
+          onSubmit={handleSubmit}
+        />
+      </Dialog>
+    </S.Wrapper>
+  );
+};
diff --git a/config-ui/src/plugins/components/transformation-form/fields/index.ts b/config-ui/src/plugins/components/scope-config-select/styled.ts
similarity index 87%
rename from config-ui/src/plugins/components/transformation-form/fields/index.ts
rename to config-ui/src/plugins/components/scope-config-select/styled.ts
index f4a5a16e6..7ccc32dbe 100644
--- a/config-ui/src/plugins/components/transformation-form/fields/index.ts
+++ b/config-ui/src/plugins/components/scope-config-select/styled.ts
@@ -16,4 +16,8 @@
  *
  */
 
-export * from './additional-settings';
+import styled from 'styled-components';
+
+export const Wrapper = styled.div``;
+
+export const DialogBody = styled.div``;
diff --git a/config-ui/src/plugins/components/transformation-form/index.tsx b/config-ui/src/plugins/components/transformation-form/index.tsx
deleted file mode 100644
index 4b20f9394..000000000
--- a/config-ui/src/plugins/components/transformation-form/index.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useEffect, useMemo, useState } from 'react';
-import { Button, InputGroup, Intent } from '@blueprintjs/core';
-
-import { Card, ExternalLink, PageLoading, Divider } from '@/components';
-import { useRefreshData } from '@/hooks';
-import { operator } from '@/utils';
-import { getPluginConfig } from '@/plugins';
-import { GitHubTransformation } from '@/plugins/register/github';
-import { JiraTransformation } from '@/plugins/register/jira';
-import { GitLabTransformation } from '@/plugins/register/gitlab';
-import { JenkinsTransformation } from '@/plugins/register/jenkins';
-import { BitbucketTransformation } from '@/plugins/register/bitbucket';
-import { AzureTransformation } from '@/plugins/register/azure';
-import { TapdTransformation } from '@/plugins/register/tapd';
-
-import { TIPS_MAP } from './misc';
-import { AdditionalSettings } from './fields';
-import * as API from './api';
-import * as S from './styled';
-
-interface Props {
-  plugin: string;
-  connectionId: ID;
-  scopeId: ID;
-  id?: ID;
-  onCancel?: (transformationRule?: any) => void;
-}
-
-export const TransformationForm = ({ plugin, connectionId, scopeId, id, onCancel }: Props) => {
-  const [saving, setSaving] = useState(false);
-  const [name, setName] = useState('');
-  const [transformation, setTransformation] = useState({});
-  const [hasRefDiff, setHasRefDiff] = useState(false);
-
-  const config = useMemo(() => getPluginConfig(plugin), []);
-
-  const { ready, data } = useRefreshData(async () => {
-    if (!id) return null;
-    return API.getTransformation(plugin, connectionId, id);
-  }, [id]);
-
-  useEffect(() => {
-    setTransformation(data ?? config.transformation);
-    setHasRefDiff(!!config.transformation.refdiff);
-    setName(data?.name ?? '');
-  }, [data, config.transformation]);
-
-  const handleSubmit = async () => {
-    const [success, res] = await operator(
-      () =>
-        id
-          ? API.updateTransformation(plugin, connectionId, id, { ...transformation, name })
-          : API.createTransformation(plugin, connectionId, { ...transformation, name }),
-      {
-        setOperating: setSaving,
-        formatMessage: () => 'Transformation created successfully',
-      },
-    );
-
-    if (success) {
-      onCancel?.(res);
-    }
-  };
-
-  if (!ready) {
-    return <PageLoading />;
-  }
-
-  return (
-    <S.Wrapper>
-      {TIPS_MAP[plugin] && (
-        <S.Tips>
-          To learn about how {TIPS_MAP[plugin].name} transformation is used in DevLake,{' '}
-          <ExternalLink link={TIPS_MAP[plugin].link}>check out this doc</ExternalLink>.
-        </S.Tips>
-      )}
-
-      <Card style={{ marginTop: 24 }}>
-        <h3>Transformation Name *</h3>
-        <p>Give this set of transformation rules a unique name so that you can identify it in the future.</p>
-        <InputGroup placeholder="Enter Transformation Name" value={name} onChange={(e) => setName(e.target.value)} />
-      </Card>
-
-      <Card style={{ marginTop: 24 }}>
-        {plugin === 'github' && (
-          <GitHubTransformation transformation={transformation} setTransformation={setTransformation} />
-        )}
-
-        {plugin === 'jira' && (
-          <JiraTransformation
-            connectionId={connectionId}
-            transformation={transformation}
-            setTransformation={setTransformation}
-          />
-        )}
-
-        {plugin === 'gitlab' && (
-          <GitLabTransformation transformation={transformation} setTransformation={setTransformation} />
-        )}
-
-        {plugin === 'jenkins' && (
-          <JenkinsTransformation transformation={transformation} setTransformation={setTransformation} />
-        )}
-
-        {plugin === 'bitbucket' && (
-          <BitbucketTransformation transformation={transformation} setTransformation={setTransformation} />
-        )}
-
-        {plugin === 'azuredevops' && (
-          <AzureTransformation transformation={transformation} setTransformation={setTransformation} />
-        )}
-
-        {plugin === 'tapd' && (
-          <TapdTransformation
-            connectionId={connectionId}
-            scopeId={scopeId}
-            transformation={transformation}
-            setTransformation={setTransformation}
-          />
-        )}
-
-        {hasRefDiff && (
-          <>
-            <Divider />
-            <AdditionalSettings transformation={transformation} setTransformation={setTransformation} />
-          </>
-        )}
-      </Card>
-
-      <S.Btns>
-        <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={() => onCancel?.(undefined)} />
-        <Button intent={Intent.PRIMARY} disabled={!name} loading={saving} text="Save" onClick={handleSubmit} />
-      </S.Btns>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/plugins/components/transformation-form/styled.ts b/config-ui/src/plugins/components/transformation-form/styled.ts
deleted file mode 100644
index cc7bea2fe..000000000
--- a/config-ui/src/plugins/components/transformation-form/styled.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import styled from 'styled-components';
-
-export const Wrapper = styled.div``;
-
-export const Tips = styled.div`
-  padding: 24px;
-  background: #f0f4fe;
-  border: 1px solid #bdcefb;
-  border-radius: 4px;
-`;
-
-export const Btns = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: end;
-  margin-top: 24px;
-
-  .bp4-button + .bp4-button {
-    margin-left: 4px;
-  }
-`;
diff --git a/config-ui/src/plugins/components/transformation-select/index.tsx b/config-ui/src/plugins/components/transformation-select/index.tsx
deleted file mode 100644
index ac1440fd6..000000000
--- a/config-ui/src/plugins/components/transformation-select/index.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useState, useEffect, useMemo } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { Dialog, PageLoading, Table, IconButton } from '@/components';
-import { useRefreshData } from '@/hooks';
-import { TransformationForm } from '@/plugins';
-
-import * as API from './api';
-import * as S from './styled';
-
-interface Props {
-  plugin: string;
-  connectionId: ID;
-  scopeId: ID;
-  transformationId?: ID;
-  transformationType: MixConnection['transformationType'];
-  onCancel: () => void;
-  onSubmit: (tid: ID) => void;
-}
-
-export const TransformationSelect = ({
-  plugin,
-  connectionId,
-  scopeId,
-  transformationId,
-  transformationType,
-  onCancel,
-  onSubmit,
-}: Props) => {
-  const [step, setStep] = useState(1);
-  const [type, setType] = useState<'add' | 'edit'>('add');
-  const [selectedId, setSelectedId] = useState<ID>();
-  const [updatedId, setUpdatedId] = useState<ID>();
-
-  useEffect(() => setSelectedId(transformationId), [transformationId]);
-
-  useEffect(() => {
-    setStep(transformationType === 'for-scope' ? 2 : 1);
-    setType(transformationType === 'for-scope' && transformationId ? 'edit' : 'add');
-    setUpdatedId(transformationType === 'for-scope' && transformationId ? transformationId : undefined);
-  }, [transformationId, transformationType]);
-
-  const { ready, data } = useRefreshData(() => API.getTransformations(plugin, connectionId), [step]);
-
-  const title = useMemo(() => {
-    switch (true) {
-      case step === 1:
-        return 'Associate Transformation';
-      case type === 'add':
-        return 'Add New Transformation';
-      case type === 'edit':
-        return 'Edit Transformation';
-    }
-  }, [step, type]);
-
-  const handleNewTransformation = () => {
-    setStep(2);
-    setType('add');
-  };
-
-  const handleEditTransformation = (id: ID) => {
-    setStep(2);
-    setType('edit');
-    setUpdatedId(id);
-  };
-
-  const handleReset = (tr?: any) => {
-    if (transformationType === 'for-scope') {
-      return tr ? onSubmit(tr.id) : onCancel();
-    }
-    setStep(1);
-    setUpdatedId('');
-  };
-
-  const handleSubmit = () => !!selectedId && onSubmit(selectedId);
-
-  return (
-    <Dialog isOpen title={title} footer={null} style={{ width: 960 }} onCancel={onCancel}>
-      {!ready || !data ? (
-        <PageLoading />
-      ) : step === 1 ? (
-        <S.Wrapper>
-          <S.Aciton>
-            <Button intent={Intent.PRIMARY} icon="add" onClick={handleNewTransformation}>
-              Add New Transformation
-            </Button>
-          </S.Aciton>
-          <Table
-            columns={[
-              { title: 'Transformation', dataIndex: 'name', key: 'name' },
-              {
-                title: '',
-                dataIndex: 'id',
-                key: 'id',
-                width: 100,
-                align: 'right',
-                render: (id) => (
-                  <IconButton icon="annotation" tooltip="Edit" onClick={() => handleEditTransformation(id)} />
-                ),
-              },
-            ]}
-            dataSource={data}
-            rowSelection={{
-              rowKey: 'id',
-              type: 'radio',
-              selectedRowKeys: selectedId ? [`${selectedId}`] : [],
-              onChange: (selectedRowKeys) => setSelectedId(selectedRowKeys[0]),
-            }}
-            noShadow
-          />
-          <S.Btns>
-            <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} />
-            <Button disabled={!selectedId} intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} />
-          </S.Btns>
-        </S.Wrapper>
-      ) : (
-        <TransformationForm
-          plugin={plugin}
-          connectionId={connectionId}
-          scopeId={scopeId}
-          id={updatedId}
-          onCancel={handleReset}
-        />
-      )}
-    </Dialog>
-  );
-};
diff --git a/config-ui/src/plugins/components/transformation-select/styled.ts b/config-ui/src/plugins/components/transformation-select/styled.ts
deleted file mode 100644
index c4c1bee23..000000000
--- a/config-ui/src/plugins/components/transformation-select/styled.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import styled from 'styled-components';
-
-export const Wrapper = styled.div``;
-
-export const Aciton = styled.div`
-  margin-bottom: 16px;
-`;
-
-export const Btns = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: end;
-  margin-top: 24px;
-
-  .bp4-button + .bp4-button {
-    margin-left: 4px;
-  }
-`;
diff --git a/config-ui/src/plugins/components/transformation/index.tsx b/config-ui/src/plugins/components/transformation/index.tsx
deleted file mode 100644
index 407cc275d..000000000
--- a/config-ui/src/plugins/components/transformation/index.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useState } from 'react';
-import { Button, Intent } from '@blueprintjs/core';
-
-import { IconButton, Table } from '@/components';
-import { getPluginId, TransformationSelect } from '@/plugins';
-
-import * as API from './api';
-import * as S from './styled';
-
-interface Props {
-  connections: MixConnection[];
-  cancelBtnProps?: {
-    text?: string;
-  };
-  submitBtnProps?: {
-    text?: string;
-    loading?: boolean;
-  };
-  noFooter?: boolean;
-  onCancel?: () => void;
-  onSubmit?: (connections: MixConnection[]) => void;
-  onNext?: () => void;
-}
-
-export const Transformation = ({
-  connections,
-  cancelBtnProps,
-  submitBtnProps,
-  noFooter,
-  onCancel,
-  onSubmit,
-  onNext,
-}: Props) => {
-  const [selected, setSelected] = useState<Record<string, string[]>>({});
-  const [connection, setConnection] = useState<MixConnection>();
-  const [tid, setTid] = useState<ID>();
-
-  const handleCancel = () => {
-    setConnection(undefined);
-    setTid(undefined);
-  };
-
-  const handleSubmit = async (tid: ID, connection: MixConnection, connections: MixConnection[]) => {
-    const { unique, plugin, connectionId } = connection;
-    const scopeIds = selected[unique];
-    const scopes = await Promise.all(
-      scopeIds.map(async (scopeId) => {
-        const scope = await API.getDataScope(plugin, connectionId, scopeId);
-        return await API.updateDataScope(plugin, connectionId, scopeId, {
-          ...scope,
-          transformationRuleId: tid,
-        });
-      }),
-    );
-    onSubmit?.(
-      connections.map((cs) => {
-        if (cs.unique !== unique) {
-          return cs;
-        }
-
-        const scope = cs.scope.map((sc) => {
-          if (!scopeIds.includes(sc[getPluginId(cs.plugin)])) {
-            return sc;
-          }
-          return scopes.find((it) => it[getPluginId(cs.plugin)] === sc[getPluginId(cs.plugin)]);
-        });
-
-        return { ...cs, scope };
-      }),
-    );
-    setSelected({
-      ...selected,
-      [`${unique}`]: [],
-    });
-    handleCancel();
-  };
-
-  return (
-    <S.List>
-      {connections.map((cs) => (
-        <S.Item key={cs.unique}>
-          {connections.length !== 1 && (
-            <S.Title>
-              <img src={cs.icon} alt="" />
-              <span>{cs.name}</span>
-            </S.Title>
-          )}
-          {cs.transformationType === 'for-connection' && (
-            <S.Action>
-              <Button
-                intent={Intent.PRIMARY}
-                icon="many-to-one"
-                disabled={!selected[cs.unique] || !selected[cs.unique].length}
-                onClick={() => setConnection(cs)}
-              >
-                Associate Transformation
-              </Button>
-            </S.Action>
-          )}
-          <Table
-            columns={[
-              { title: 'Data Scope', dataIndex: 'name', key: 'name' },
-              {
-                title: 'Transformation',
-                dataIndex: 'transformationRuleName',
-                key: 'transformation',
-                align: 'center',
-                render: (val, row) =>
-                  cs.transformationType === 'none' ? (
-                    'N/A'
-                  ) : (
-                    <div>
-                      <span>{val ?? 'N/A'}</span>
-                      <IconButton
-                        icon="one-to-one"
-                        tooltip="Associate Transformation"
-                        onClick={() => {
-                          setSelected({
-                            ...selected,
-                            [`${cs.unique}`]: [row[getPluginId(cs.plugin)]],
-                          });
-                          setConnection(cs);
-                          setTid(row.transformationRuleId);
-                        }}
-                      />
-                    </div>
-                  ),
-              },
-            ]}
-            dataSource={cs.scope}
-            rowSelection={
-              cs.transformationType === 'for-connection'
-                ? {
-                    rowKey: getPluginId(cs.plugin),
-                    selectedRowKeys: selected[cs.unique],
-                    onChange: (selectedRowKeys) => setSelected({ ...selected, [`${cs.unique}`]: selectedRowKeys }),
-                  }
-                : undefined
-            }
-          />
-        </S.Item>
-      ))}
-      {!noFooter && (
-        <S.Btns>
-          <Button outlined intent={Intent.PRIMARY} text="Previous Step" onClick={onCancel} {...cancelBtnProps} />
-          <Button intent={Intent.PRIMARY} text="Next Step" onClick={onNext} {...submitBtnProps} />
-        </S.Btns>
-      )}
-      {connection && (
-        <TransformationSelect
-          plugin={connection.plugin}
-          connectionId={connection.connectionId}
-          scopeId={selected[connection.unique][0]}
-          transformationId={tid}
-          transformationType={connection.transformationType}
-          onCancel={handleCancel}
-          onSubmit={(tid) => handleSubmit(tid, connection, connections)}
-        />
-      )}
-    </S.List>
-  );
-};
diff --git a/config-ui/src/plugins/components/transformation/styled.ts b/config-ui/src/plugins/components/transformation/styled.ts
deleted file mode 100644
index 7062ec37e..000000000
--- a/config-ui/src/plugins/components/transformation/styled.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import styled from 'styled-components';
-
-export const List = styled.div``;
-
-export const Item = styled.div`
-  margin-bottom: 36px;
-
-  &:last-child {
-    margin-bottom: 0;
-  }
-`;
-
-export const Title = styled.div`
-  display: flex;
-  align-items: center;
-  margin-bottom: 16px;
-
-  img {
-    margin-right: 4px;
-    width: 24px;
-    height: 24px;
-  }
-`;
-
-export const Action = styled.div`
-  margin-bottom: 16px;
-`;
-
-export const Btns = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 36px;
-`;
diff --git a/config-ui/src/plugins/config.ts b/config-ui/src/plugins/config.ts
index 29fefa6c4..1f907f71b 100644
--- a/config-ui/src/plugins/config.ts
+++ b/config-ui/src/plugins/config.ts
@@ -78,8 +78,5 @@ export function getPluginConfig(pluginName: string): PluginConfigType {
       name: pluginName,
     } as PluginConfigType;
   }
-  return {
-    ...pluginConfig,
-    transformationType: pluginConfig.transformationType || (pluginConfig.transformation ? 'for-connection' : 'none'),
-  };
+  return pluginConfig;
 }
diff --git a/config-ui/src/plugins/register/github/components/miller-columns/index.tsx b/config-ui/src/plugins/register/github/components/miller-columns/index.tsx
index 842de51c9..316eb52f4 100644
--- a/config-ui/src/plugins/register/github/components/miller-columns/index.tsx
+++ b/config-ui/src/plugins/register/github/components/miller-columns/index.tsx
@@ -48,7 +48,7 @@ export const MillerColumns = ({ connectionId, disabledItems, selectedItems, onCh
 
   useEffect(() => {
     setDisabledIds((disabledItems ?? []).map((it) => it.githubId));
-  }, [disabledIds]);
+  }, [disabledItems]);
 
   const handleChangeItems = (selectedIds: McsID[]) => {
     const result = selectedIds.map((id) => {
diff --git a/config-ui/src/plugins/register/github/components/miller-columns/use-miller-columns.ts b/config-ui/src/plugins/register/github/components/miller-columns/use-miller-columns.ts
index 04f003218..6625e63c2 100644
--- a/config-ui/src/plugins/register/github/components/miller-columns/use-miller-columns.ts
+++ b/config-ui/src/plugins/register/github/components/miller-columns/use-miller-columns.ts
@@ -105,7 +105,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
               title: appInstallationRepos.repositories[0].owner.login,
               type: 'org',
             } as any,
-          ])
+          ]);
         }
       } else {
         const user = await API.getUser(prefix);
@@ -127,7 +127,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
           },
           ...formatOrgs(orgs),
         ]);
-    }
+      }
     })();
   }, [prefix]);
 
diff --git a/config-ui/src/plugins/register/github/config.tsx b/config-ui/src/plugins/register/github/config.tsx
index 43a9db085..c488355ff 100644
--- a/config-ui/src/plugins/register/github/config.tsx
+++ b/config-ui/src/plugins/register/github/config.tsx
@@ -45,7 +45,7 @@ export const GitHubConfig: PluginConfigType = {
           server: ' ',
         },
       },
-      ({ initialValues, values, errors, setValues, setErrors }: any) => (
+      ({ initialValues, values, setValues }: any) => (
         <Authentication
           key="authMethod"
           initialValue={initialValues.authMethod ?? ''}
@@ -54,8 +54,9 @@ export const GitHubConfig: PluginConfigType = {
         />
       ),
       ({ initialValues, values, errors, setValues, setErrors }: any) =>
-        (values.authMethod || initialValues.authMethod) == 'AccessToken' ? (
+        (values.authMethod || initialValues.authMethod) === 'AccessToken' ? (
           <Token
+            key="token"
             endpoint={values.endpoint}
             proxy={values.proxy}
             initialValue={initialValues.token ?? ''}
diff --git a/config-ui/src/plugins/register/github/connection-fields/authentication.tsx b/config-ui/src/plugins/register/github/connection-fields/authentication.tsx
index 8ddb6547d..9cb6e3d0b 100644
--- a/config-ui/src/plugins/register/github/connection-fields/authentication.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/authentication.tsx
@@ -28,20 +28,21 @@ interface Props {
 }
 
 export const Authentication = ({ initialValue, value, setValue }: Props) => {
-
   useEffect(() => {
     setValue(initialValue);
   }, [initialValue]);
 
   return (
     <FormGroup label={<S.Label>Authentication type</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
-      <RadioGroup inline selectedValue={value || initialValue} onChange={(e) => {
-        setValue((e.target as any).value);
-      }}>
+      <RadioGroup
+        inline
+        selectedValue={value || initialValue}
+        onChange={(e) => {
+          setValue((e.target as any).value);
+        }}
+      >
         <Radio value="AccessToken">Github Access Token</Radio>
-        <Radio value="AppKey">
-          Github App
-        </Radio>
+        <Radio value="AppKey">Github App</Radio>
       </RadioGroup>
     </FormGroup>
   );
diff --git a/config-ui/src/plugins/register/github/connection-fields/githubapp.tsx b/config-ui/src/plugins/register/github/connection-fields/githubapp.tsx
index 6691c9025..5637ad73e 100644
--- a/config-ui/src/plugins/register/github/connection-fields/githubapp.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/githubapp.tsx
@@ -69,10 +69,14 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
         secretKey: '',
         installationId: '',
       });
-    }
+    };
   }, [value.appId, value.secretKey, value.installationId]);
 
-  const testConfiguration = async (appId?: string, secretKey?: string, installationId?: number): Promise<GithubAppSettings> => {
+  const testConfiguration = async (
+    appId?: string,
+    secretKey?: string,
+    installationId?: number,
+  ): Promise<GithubAppSettings> => {
     if (!endpoint || !appId || !secretKey) {
       return {
         appId,
@@ -110,7 +114,7 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
   };
 
   const handleChangeAppId = (value: string) => {
-    setSettings({ ...settings, appId: value });;
+    setSettings({ ...settings, appId: value });
   };
 
   const handleChangeClientSecret = (value: string) => {
@@ -127,7 +131,6 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
     setSettings(res);
   };
 
-
   useEffect(() => {
     checkConfig(initialValue.appId, initialValue.secretKey, initialValue.installationId);
   }, [initialValue.appId, initialValue.secretKey, initialValue.installationId, endpoint]);
@@ -136,7 +139,6 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
     setValue({ appId: settings.appId, secretKey: settings.secretKey, installationId: settings.installationId });
   }, [settings.appId, settings.secretKey, settings.installationId]);
 
-
   return (
     <FormGroup
       label={<S.Label>Github App settings</S.Label>}
@@ -144,9 +146,7 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
       subLabel={
         <S.LabelDescription>
           Input information about your Github App{' '}
-          <ExternalLink link="https://TODO">
-            Learn how to create a github app
-          </ExternalLink>
+          <ExternalLink link="https://TODO">Learn how to create a github app</ExternalLink>
         </S.LabelDescription>
       }
     >
@@ -184,7 +184,7 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
       <S.Input>
         <Select2
           items={settings.installations ?? []}
-          activeItem={settings.installations?.find(e => e.id === settings.installationId)}
+          activeItem={settings.installations?.find((e) => e.id === settings.installationId)}
           itemPredicate={(query, item) => item.account.login.toLowerCase().includes(query.toLowerCase())}
           itemRenderer={(item, { handleClick, handleFocus, modifiers }) => {
             return (
@@ -197,7 +197,7 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
                 onFocus={handleFocus}
                 roleStructure="listoption"
                 text={item.account.login}
-            />
+              />
             );
           }}
           onItemSelect={(item) => {
@@ -207,9 +207,13 @@ export const GithubApp = ({ endpoint, proxy, initialValue, value, error, setValu
           popoverProps={{ minimal: true }}
         >
           <Button
-            text={settings.installations?.find(e => e.id === settings.installationId)?.account.login ?? 'Select App installation'}
+            text={
+              settings.installations?.find((e) => e.id === settings.installationId)?.account.login ??
+              'Select App installation'
+            }
             rightIcon="double-caret-vertical"
-            placeholder="Select App installation" />
+            placeholder="Select App installation"
+          />
         </Select2>
       </S.Input>
     </FormGroup>
diff --git a/config-ui/src/plugins/register/github/connection-fields/index.ts b/config-ui/src/plugins/register/github/connection-fields/index.ts
index 8e0a150a7..1775eec52 100644
--- a/config-ui/src/plugins/register/github/connection-fields/index.ts
+++ b/config-ui/src/plugins/register/github/connection-fields/index.ts
@@ -19,4 +19,4 @@
 export * from './token';
 export * from './graphql';
 export * from './githubapp';
-export * from './authentication';
\ No newline at end of file
+export * from './authentication';
diff --git a/config-ui/src/plugins/register/github/connection-fields/token.tsx b/config-ui/src/plugins/register/github/connection-fields/token.tsx
index b811a072d..38dd31ba4 100644
--- a/config-ui/src/plugins/register/github/connection-fields/token.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/token.tsx
@@ -88,7 +88,7 @@ export const Token = ({ endpoint, proxy, initialValue, value, error, setValue, s
 
     return () => {
       setError('');
-    }
+    };
   }, [value]);
 
   useEffect(() => {
diff --git a/config-ui/src/plugins/register/jira/connection-fields/styled.ts b/config-ui/src/plugins/register/jira/connection-fields/styled.ts
index 7833a8536..11e47a3d0 100644
--- a/config-ui/src/plugins/register/jira/connection-fields/styled.ts
+++ b/config-ui/src/plugins/register/jira/connection-fields/styled.ts
@@ -16,7 +16,6 @@
  *
  */
 
-import { Colors } from '@blueprintjs/core';
 import styled from 'styled-components';
 
 export const Label = styled.label`
diff --git a/config-ui/src/plugins/register/tapd/config.tsx b/config-ui/src/plugins/register/tapd/config.tsx
index 1c36f8a8e..8be01df61 100644
--- a/config-ui/src/plugins/register/tapd/config.tsx
+++ b/config-ui/src/plugins/register/tapd/config.tsx
@@ -72,7 +72,6 @@ export const TAPDConfig: PluginConfigType = {
     ],
   },
   entities: ['TICKET', 'CROSS'],
-  transformationType: 'for-scope',
   transformation: {
     typeMappings: {},
     statusMappings: {},
diff --git a/config-ui/src/plugins/types.ts b/config-ui/src/plugins/types.ts
index 4dbe61edc..772368d46 100644
--- a/config-ui/src/plugins/types.ts
+++ b/config-ui/src/plugins/types.ts
@@ -35,5 +35,4 @@ export type PluginConfigType = {
   };
   entities: string[];
   transformation: any;
-  transformationType?: 'none' | 'for-connection' | 'for-scope';
 };
diff --git a/config-ui/src/store/connections/context.tsx b/config-ui/src/store/connections/context.tsx
index b9a1fcea0..caec3a22a 100644
--- a/config-ui/src/store/connections/context.tsx
+++ b/config-ui/src/store/connections/context.tsx
@@ -112,8 +112,6 @@ export const ConnectionContextProvider = ({ children, ...props }: Props) => {
     }));
   };
 
-
-
   const handleGet = (unique: string) => {
     return connections.find((cs) => cs.unique === unique) as ConnectionItemType;
   };
diff --git a/config-ui/src/utils/operator.ts b/config-ui/src/utils/operator.ts
index e144179f9..d1aeb1d4c 100644
--- a/config-ui/src/utils/operator.ts
+++ b/config-ui/src/utils/operator.ts
@@ -16,8 +16,6 @@
  *
  */
 
-import { Intent } from '@blueprintjs/core';
-
 import { toast } from '@/components';
 
 export type OperateConfig = {