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/12/14 04:26:18 UTC

(incubator-devlake) branch refactor-6307 created (now 572c6310e)

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

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


      at 572c6310e refactor(config-ui): use new ui select to replace selector

This branch includes the following new commits:

     new 572c6310e refactor(config-ui): use new ui select to replace selector

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



(incubator-devlake) 01/01: refactor(config-ui): use new ui select to replace selector

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

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

commit 572c6310e25caed748793d07c208d0e1f37b6cae
Author: mintsweet <0x...@gmail.com>
AuthorDate: Thu Dec 14 17:26:06 2023 +1300

    refactor(config-ui): use new ui select to replace selector
---
 config-ui/src/components/index.ts                  |   1 -
 config-ui/src/components/selector/index.ts         |  20 ---
 .../components/selector/multi-selector/index.tsx   | 147 ---------------------
 .../src/components/selector/selector/index.tsx     |  91 -------------
 .../plugins/components/data-scope-select/index.tsx |  19 ++-
 .../plugins/components/scope-config-form/index.tsx |  16 +--
 .../plugins/register/bitbucket/transformation.tsx  |  54 ++++----
 .../src/plugins/register/jira/transformation.tsx   |  91 +++++++------
 .../src/plugins/register/tapd/transformation.tsx   | 106 ++++++---------
 config-ui/src/routes/api-keys/api-keys.tsx         |  23 ++--
 10 files changed, 142 insertions(+), 426 deletions(-)

diff --git a/config-ui/src/components/index.ts b/config-ui/src/components/index.ts
index 707c3506d..143c30b7f 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -24,5 +24,4 @@ export * from './logo';
 export * from './message';
 export * from './no-data';
 export * from './page-header';
-export * from './selector';
 export * from './tooltip';
diff --git a/config-ui/src/components/selector/index.ts b/config-ui/src/components/selector/index.ts
deleted file mode 100644
index eb2f71e3d..000000000
--- a/config-ui/src/components/selector/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 './selector';
-export * from './multi-selector';
diff --git a/config-ui/src/components/selector/multi-selector/index.tsx b/config-ui/src/components/selector/multi-selector/index.tsx
deleted file mode 100644
index 4d48c6c4b..000000000
--- a/config-ui/src/components/selector/multi-selector/index.tsx
+++ /dev/null
@@ -1,147 +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 { MenuItem, Checkbox, Intent } from '@blueprintjs/core';
-import { MultiSelect2 } from '@blueprintjs/select';
-import { useDebounce } from 'ahooks';
-
-interface Props<T> {
-  placeholder?: string;
-  loading?: boolean;
-  disabled?: boolean;
-  items: T[];
-  disabledItems?: T[];
-  getKey?: (item: T) => string | number;
-  getName?: (item: T) => string;
-  getIcon?: (item: T) => string;
-  selectedItems?: T[];
-  onChangeItems?: (selectedItems: T[]) => void;
-  noResult?: string;
-  onQueryChange?: (query: string) => void;
-}
-
-export const MultiSelector = <T,>({
-  placeholder,
-  loading = false,
-  disabled = false,
-  items,
-  disabledItems = [],
-  getKey = (it) => it as string,
-  getName = (it) => it as string,
-  getIcon,
-  onChangeItems,
-  noResult = 'No results found.',
-  onQueryChange,
-  ...props
-}: Props<T>) => {
-  const [selectedItems, setSelectedItems] = useState<T[]>([]);
-  const [query, setQuery] = useState('');
-  const search = useDebounce(query, { wait: 500 });
-
-  const filteredItems = useMemo(
-    () =>
-      items.filter((it) => {
-        const name = getName(it);
-        return name.toLocaleLowerCase().includes(search.toLocaleLowerCase());
-      }),
-    [items, search],
-  );
-
-  useEffect(() => {
-    setSelectedItems(props.selectedItems ?? []);
-  }, [props.selectedItems]);
-
-  const tagRenderer = (item: T) => {
-    const name = getName(item);
-    return <span>{name}</span>;
-  };
-
-  const itemRenderer = (item: T, { handleClick }: any) => {
-    const key = getKey(item);
-    const name = getName(item);
-    const icon = getIcon?.(item);
-    const selected = !!selectedItems.find((it) => getKey(it) === key);
-    const disabled = !!disabledItems.find((it) => getKey(it) === key);
-
-    return (
-      <MenuItem
-        key={key}
-        disabled={selected || disabled}
-        onClick={(e) => {
-          e.preventDefault();
-          handleClick();
-        }}
-        labelElement={icon ? <img src={icon} width={16} alt="" /> : null}
-        text={<Checkbox disabled={selected || disabled} checked={selected || disabled} readOnly label={name} />}
-      />
-    );
-  };
-
-  const handleItemSelect = (item: T) => {
-    const newSelectedItems = [...selectedItems, item];
-    if (onChangeItems) {
-      onChangeItems(newSelectedItems);
-    } else {
-      setSelectedItems(newSelectedItems);
-    }
-  };
-
-  const handleItemRemove = (item: T) => {
-    const newSelectedItems = selectedItems.filter((it) => getKey(it) !== getKey(item));
-    if (onChangeItems) {
-      onChangeItems(newSelectedItems);
-    } else {
-      setSelectedItems(newSelectedItems);
-    }
-  };
-
-  const handleQueryChange = (query: string) => {
-    if (onQueryChange && query) {
-      onQueryChange(query);
-    } else {
-      setQuery(query);
-    }
-  };
-
-  return (
-    <MultiSelect2
-      disabled={disabled}
-      resetOnSelect
-      fill
-      placeholder={placeholder ?? 'Select...'}
-      items={filteredItems}
-      // https://github.com/palantir/blueprint/issues/3596
-      // set activeItem to null will fixed the scrollBar to top when the selectedItems changed
-      activeItem={null}
-      selectedItems={selectedItems}
-      itemRenderer={itemRenderer}
-      tagRenderer={tagRenderer}
-      tagInputProps={{
-        tagProps: {
-          intent: Intent.PRIMARY,
-          minimal: true,
-        },
-      }}
-      onItemSelect={handleItemSelect}
-      onRemove={handleItemRemove}
-      onQueryChange={handleQueryChange}
-      noResults={<MenuItem disabled={true} text={loading ? 'Fetching...' : noResult} />}
-    />
-  );
-};
diff --git a/config-ui/src/components/selector/selector/index.tsx b/config-ui/src/components/selector/selector/index.tsx
deleted file mode 100644
index e425c5139..000000000
--- a/config-ui/src/components/selector/selector/index.tsx
+++ /dev/null
@@ -1,91 +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 { MenuItem, Button, Alignment } from '@blueprintjs/core';
-import { Select2 } from '@blueprintjs/select';
-
-interface Props<T> {
-  items: T[];
-  disabledItems?: T[];
-  getKey?: (item: T) => ID;
-  getName?: (item: T) => string;
-  getIcon?: (item: T) => string;
-  selectedItem?: T;
-  onChangeItem?: (selectedItem: T) => void;
-}
-
-export const Selector = <T,>({
-  items,
-  disabledItems = [],
-  getKey = (it) => it as string,
-  getName = (it) => it as string,
-  getIcon,
-  onChangeItem,
-  ...props
-}: Props<T>) => {
-  const [selectedItem, setSelectedItem] = useState<T>();
-
-  useEffect(() => {
-    setSelectedItem(props.selectedItem);
-  }, [props.selectedItem]);
-
-  const btnText = useMemo(() => (selectedItem ? getName(selectedItem) : 'Select...'), [selectedItem]);
-
-  const itemPredicate = (query: string, item: T) => {
-    const name = getName(item);
-    return name.toLowerCase().indexOf(query.toLowerCase()) >= 0;
-  };
-
-  const itemRenderer = (item: T, { handleClick }: any) => {
-    const key = getKey(item);
-    const name = getName(item);
-    const icon = getIcon?.(item) ?? null;
-    const disabled = !!disabledItems.find((it) => getKey(it) === getKey(item)) || selectedItem === item;
-
-    return (
-      <MenuItem
-        key={key}
-        disabled={disabled}
-        text={<span>{name}</span>}
-        icon={icon ? <img src={icon} alt="" width={20} /> : null}
-        onClick={handleClick}
-      />
-    );
-  };
-
-  const handleItemSelect = (item: T) => {
-    if (onChangeItem) {
-      onChangeItem(item);
-    } else {
-      setSelectedItem(item);
-    }
-  };
-
-  return (
-    <Select2
-      items={items}
-      activeItem={selectedItem}
-      itemPredicate={itemPredicate}
-      itemRenderer={itemRenderer}
-      onItemSelect={handleItemSelect}
-    >
-      <Button outlined fill alignText={Alignment.LEFT} rightIcon="caret-down" text={btnText} />
-    </Select2>
-  );
-};
diff --git a/config-ui/src/plugins/components/data-scope-select/index.tsx b/config-ui/src/plugins/components/data-scope-select/index.tsx
index f9bf2254e..3f1e86092 100644
--- a/config-ui/src/plugins/components/data-scope-select/index.tsx
+++ b/config-ui/src/plugins/components/data-scope-select/index.tsx
@@ -17,14 +17,14 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import { Flex } from 'antd';
+import { Flex, Select } from 'antd';
 import { Button, Intent } from '@blueprintjs/core';
 import { useDebounce } from 'ahooks';
 import type { McsItem } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import API from '@/api';
-import { Loading, Block, ExternalLink, Message, MultiSelector } from '@/components';
+import { Loading, Block, ExternalLink, Message } from '@/components';
 import { useRefreshData } from '@/hooks';
 import { getPluginScopeId } from '@/plugins';
 
@@ -133,15 +133,14 @@ export const DataScopeSelect = ({
               <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data Scope" />
             </Flex>
           )}
-          <MultiSelector
+          <Select
             loading={!ready}
-            items={searchItems}
-            getName={(it) => it.name}
-            getKey={(it) => getPluginScopeId(plugin, it)}
-            noResult="No Data Scopes Available."
-            onQueryChange={(query) => setQuery(query)}
-            selectedItems={searchItems.filter((it) => selectedIds.includes(getPluginScopeId(plugin, it)))}
-            onChangeItems={(selectedItems) => setSelectedIds(selectedItems.map((it) => getPluginScopeId(plugin, it)))}
+            showSearch
+            mode="multiple"
+            options={searchItems.map((it) => ({ label: it.fullName, value: getPluginScopeId(plugin, it) }))}
+            value={selectedIds}
+            onChange={(value) => setSelectedIds(value)}
+            onSearch={(value) => setQuery(value)}
           />
           <MillerColumnsSelect
             showSelectAll
diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx
index abb6c18db..4d1be1e88 100644
--- a/config-ui/src/plugins/components/scope-config-form/index.tsx
+++ b/config-ui/src/plugins/components/scope-config-form/index.tsx
@@ -18,11 +18,11 @@
 
 import { useState, useEffect, useMemo } from 'react';
 import { omit } from 'lodash';
-import { Flex, Form, Input, Card, Alert, Divider } from 'antd';
+import { Flex, Form, Input, Card, Alert, Divider, Select } from 'antd';
 import { Button, Intent } from '@blueprintjs/core';
 
 import API from '@/api';
-import { ExternalLink, Block, MultiSelector, Message } from '@/components';
+import { ExternalLink, Block, Message } from '@/components';
 import { transformEntities, EntitiesLabel } from '@/config';
 import { getPluginConfig } from '@/plugins';
 import { GitHubTransformation } from '@/plugins/register/github';
@@ -145,12 +145,12 @@ export const ScopeConfigForm = ({
               }
               required
             >
-              <MultiSelector
-                items={transformEntities(config.scopeConfig?.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))}
+              <Select
+                style={{ width: '100%' }}
+                mode="multiple"
+                options={transformEntities(config.scopeConfig?.entities ?? [])}
+                value={entities}
+                onChange={(value) => setEntities(value)}
               />
             </Block>
             {showWarning && (
diff --git a/config-ui/src/plugins/register/bitbucket/transformation.tsx b/config-ui/src/plugins/register/bitbucket/transformation.tsx
index f0562a538..6bae7b8a7 100644
--- a/config-ui/src/plugins/register/bitbucket/transformation.tsx
+++ b/config-ui/src/plugins/register/bitbucket/transformation.tsx
@@ -18,9 +18,9 @@
 
 import { useMemo, useState, useEffect } from 'react';
 import { CaretRightOutlined } from '@ant-design/icons';
-import { theme, Collapse, Tag, Form, Input, Checkbox } from 'antd';
+import { theme, Collapse, Tag, Form, Input, Checkbox, Select } from 'antd';
 
-import { ExternalLink, HelpTooltip, MultiSelector } from '@/components';
+import { ExternalLink, HelpTooltip } from '@/components';
 import { DOC_URL } from '@/release';
 
 import ExampleJpg from './assets/bitbucket-example.jpg';
@@ -127,55 +127,53 @@ const renderCollapseItems = ({
       children: (
         <div className="list">
           <Form.Item label="TODO">
-            <MultiSelector
-              items={ALL_STATES}
-              disabledItems={selectedStates}
-              selectedItems={transformation.issueStatusTodo ? transformation.issueStatusTodo.split(',') : []}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={ALL_STATES.map((it) => ({ label: it, value: it }))}
+              value={transformation.issueStatusTodo ? transformation.issueStatusTodo.split(',') : []}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
-                  issueStatusTodo: selectedItems.join(','),
+                  issueStatusTodo: value.join(','),
                 })
               }
             />
           </Form.Item>
           <Form.Item label="IN-PROGRESS">
-            <MultiSelector
-              items={ALL_STATES}
-              disabledItems={selectedStates}
-              selectedItems={
-                transformation.issueStatusInProgress ? transformation.issueStatusInProgress.split(',') : []
-              }
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={ALL_STATES.map((it) => ({ label: it, value: it }))}
+              value={transformation.issueStatusInProgress ? transformation.issueStatusInProgress.split(',') : []}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
-                  issueStatusInProgress: selectedItems.join(','),
+                  issueStatusInProgress: value.join(','),
                 })
               }
             />
           </Form.Item>
           <Form.Item label="DONE">
-            <MultiSelector
-              items={ALL_STATES}
-              disabledItems={selectedStates}
-              selectedItems={transformation.issueStatusDone ? transformation.issueStatusDone.split(',') : []}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={ALL_STATES.map((it) => ({ label: it, value: it }))}
+              value={transformation.issueStatusDone ? transformation.issueStatusDone.split(',') : []}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
-                  issueStatusDone: selectedItems.join(','),
+                  issueStatusDone: value.join(','),
                 })
               }
             />
           </Form.Item>
           <Form.Item label="OTHER">
-            <MultiSelector
-              items={ALL_STATES}
-              disabledItems={selectedStates}
-              selectedItems={transformation.issueStatusOther ? transformation.issueStatusOther.split(',') : []}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={ALL_STATES.map((it) => ({ label: it, value: it }))}
+              value={transformation.issueStatusOther ? transformation.issueStatusOther.split(',') : []}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
-                  issueStatusOther: selectedItems.join(','),
+                  issueStatusOther: value.join(','),
                 })
               }
             />
diff --git a/config-ui/src/plugins/register/jira/transformation.tsx b/config-ui/src/plugins/register/jira/transformation.tsx
index 7964674e5..90fd62037 100644
--- a/config-ui/src/plugins/register/jira/transformation.tsx
+++ b/config-ui/src/plugins/register/jira/transformation.tsx
@@ -19,10 +19,10 @@
 import { useState, useEffect, useMemo } from 'react';
 import { uniqWith } from 'lodash';
 import { CaretRightOutlined } from '@ant-design/icons';
-import { theme, Collapse, Tag, Form } from 'antd';
+import { theme, Collapse, Tag, Form, Select } from 'antd';
 
 import API from '@/api';
-import { PageLoading, HelpTooltip, ExternalLink, MultiSelector, Selector } from '@/components';
+import { PageLoading, HelpTooltip, ExternalLink } from '@/components';
 import { useProxyPrefix, useRefreshData } from '@/hooks';
 import { DOC_URL } from '@/release';
 
@@ -165,14 +165,26 @@ const renderCollapseItems = ({
   transformation: any;
   onChangeTransformation: any;
   connectionId: ID;
-  issueTypes: any;
+  issueTypes: Array<{
+    id: string;
+    name: string;
+  }>;
   fields: Array<{
     id: string;
     name: string;
   }>;
-  requirementItems: any;
-  bugItems: any;
-  incidentItems: any;
+  requirementItems: Array<{
+    id: string;
+    name: string;
+  }>;
+  bugItems: Array<{
+    id: string;
+    name: string;
+  }>;
+  incidentItems: Array<{
+    id: string;
+    name: string;
+  }>;
   transformaType: any;
 }) =>
   [
@@ -194,18 +206,18 @@ const renderCollapseItems = ({
             <ExternalLink link={DOC_URL.METRICS.MTTR}>DORA - Median Time to Restore Service</ExternalLink>, etc.
           </p>
           <Form.Item label="Requirement">
-            <MultiSelector
-              items={issueTypes}
-              disabledItems={[...bugItems, ...incidentItems]}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              getIcon={(it) => it.iconUrl}
-              selectedItems={requirementItems}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={issueTypes.map((it) => ({ label: it.name, value: it.id }))}
+              value={requirementItems.map((it) => it.id)}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
-                    ...transformaType(selectedItems, StandardType.Requirement),
+                    ...transformaType(
+                      requirementItems.filter((it) => value.includes(it.id)),
+                      StandardType.Requirement,
+                    ),
                     ...transformaType(bugItems, StandardType.Bug),
                     ...transformaType(incidentItems, StandardType.Incident),
                   },
@@ -214,19 +226,19 @@ const renderCollapseItems = ({
             />
           </Form.Item>
           <Form.Item label="Bug">
-            <MultiSelector
-              items={issueTypes}
-              disabledItems={[...requirementItems, ...incidentItems]}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              getIcon={(it) => it.iconUrl}
-              selectedItems={bugItems}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={issueTypes.map((it) => ({ label: it.name, value: it.id }))}
+              value={bugItems.map((it) => it.id)}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
                     ...transformaType(requirementItems, StandardType.Requirement),
-                    ...transformaType(selectedItems, StandardType.Bug),
+                    ...transformaType(
+                      bugItems.filter((it) => value.includes(it.id)),
+                      StandardType.Bug,
+                    ),
                     ...transformaType(incidentItems, StandardType.Incident),
                   },
                 })
@@ -243,20 +255,21 @@ const renderCollapseItems = ({
               </>
             }
           >
-            <MultiSelector
-              items={issueTypes}
-              disabledItems={[...requirementItems, ...bugItems]}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              getIcon={(it) => it.iconUrl}
-              selectedItems={incidentItems}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={issueTypes.map((it) => ({ label: it.name, value: it.id }))}
+              value={incidentItems.map((it) => it.id)}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
                     ...transformaType(requirementItems, StandardType.Requirement),
                     ...transformaType(bugItems, StandardType.Bug),
-                    ...transformaType(selectedItems, StandardType.Incident),
+                    ...transformaType(
+                      incidentItems.filter((it) => value.includes(it.id)),
+
+                      StandardType.Incident,
+                    ),
                   },
                 })
               }
@@ -270,15 +283,13 @@ const renderCollapseItems = ({
               </>
             }
           >
-            <Selector
-              items={fields}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItem={fields.find((it) => it.id === transformation.storyPointField)}
-              onChangeItem={(selectedItem) =>
+            <Select
+              options={fields.map((it) => ({ label: it.name, value: it.id }))}
+              value={transformation.storyPointField}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
-                  storyPointField: selectedItem.id,
+                  storyPointField: value,
                 })
               }
             />
diff --git a/config-ui/src/plugins/register/tapd/transformation.tsx b/config-ui/src/plugins/register/tapd/transformation.tsx
index cb4d9e624..b010ed7de 100644
--- a/config-ui/src/plugins/register/tapd/transformation.tsx
+++ b/config-ui/src/plugins/register/tapd/transformation.tsx
@@ -19,10 +19,10 @@
 import { useEffect, useState } from 'react';
 import { uniqWith } from 'lodash';
 import { CaretRightOutlined } from '@ant-design/icons';
-import { theme, Collapse, Tag, Form } from 'antd';
+import { theme, Collapse, Tag, Form, Select } from 'antd';
 
 import API from '@/api';
-import { MultiSelector, PageLoading } from '@/components';
+import { PageLoading } from '@/components';
 import { useProxyPrefix, useRefreshData } from '@/hooks';
 
 enum StandardType {
@@ -211,20 +211,15 @@ const renderCollapseItems = ({
             and `Bug age` in built-in dashboards.
           </p>
           <Form.Item label="Requirement">
-            <MultiSelector
-              items={typeList}
-              disabledItems={typeList.filter((v) => [...bugTypeList, ...incidentTypeList].includes(v.id))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={typeList.filter((v) => featureTypeList.includes(v.id))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={typeList.map((it) => ({ label: it.name, value: it.id }))}
+              value={featureTypeList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
-                    ...transformaType(
-                      selectedItems.map((v) => v.id),
-                      StandardType.Requirement,
-                    ),
+                    ...transformaType(value, StandardType.Requirement),
                     ...transformaType(bugTypeList, StandardType.Bug),
                     ...transformaType(incidentTypeList, StandardType.Incident),
                   },
@@ -233,21 +228,16 @@ const renderCollapseItems = ({
             />
           </Form.Item>
           <Form.Item label="Bug">
-            <MultiSelector
-              items={typeList}
-              disabledItems={typeList.filter((v) => [...featureTypeList, ...incidentTypeList].includes(v.id))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={typeList.filter((v) => bugTypeList.includes(v.id))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={typeList.map((it) => ({ label: it.name, value: it.id }))}
+              value={bugTypeList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
                     ...transformaType(featureTypeList, StandardType.Requirement),
-                    ...transformaType(
-                      selectedItems.map((v) => v.id),
-                      StandardType.Bug,
-                    ),
+                    ...transformaType(value, StandardType.Bug),
                     ...transformaType(incidentTypeList, StandardType.Incident),
                   },
                 })
@@ -264,22 +254,17 @@ const renderCollapseItems = ({
               </>
             }
           >
-            <MultiSelector
-              items={typeList}
-              disabledItems={typeList.filter((v) => [...featureTypeList, ...bugTypeList].includes(v.id))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={typeList.filter((v) => incidentTypeList.includes(v.id))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={typeList.map((it) => ({ label: it.name, value: it.id }))}
+              value={incidentTypeList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   typeMappings: {
                     ...transformaType(featureTypeList, StandardType.Requirement),
                     ...transformaType(bugTypeList, StandardType.Bug),
-                    ...transformaType(
-                      selectedItems.map((v) => v.id),
-                      StandardType.Incident,
-                    ),
+                    ...transformaType(value, StandardType.Incident),
                   },
                 })
               }
@@ -290,20 +275,15 @@ const renderCollapseItems = ({
             Delivery Rate` in built-in dashboards.
           </p>
           <Form.Item label="TODO">
-            <MultiSelector
-              items={statusList}
-              disabledItems={statusList.filter((v) => [...inProgressStatusList, ...doneStatusList].includes(v.name))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={statusList.filter((v) => todoStatusList.includes(v.name))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={statusList.map((it) => ({ label: it.name, value: it.id }))}
+              value={todoStatusList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   statusMappings: {
-                    ...transformaType(
-                      selectedItems.map((v) => v.name),
-                      StandardStatus.Todo,
-                    ),
+                    ...transformaType(value, StandardStatus.Todo),
                     ...transformaType(inProgressStatusList, StandardStatus.InProgress),
                     ...transformaType(doneStatusList, StandardStatus.Done),
                   },
@@ -312,21 +292,16 @@ const renderCollapseItems = ({
             />
           </Form.Item>
           <Form.Item label="IN-PROGRESS">
-            <MultiSelector
-              items={statusList}
-              disabledItems={statusList.filter((v) => [...todoStatusList, ...doneStatusList].includes(v.name))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={statusList.filter((v) => inProgressStatusList.includes(v.name))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={statusList.map((it) => ({ label: it.name, value: it.id }))}
+              value={inProgressStatusList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   statusMappings: {
                     ...transformaType(todoStatusList, StandardStatus.Todo),
-                    ...transformaType(
-                      selectedItems.map((v) => v.name),
-                      StandardStatus.InProgress,
-                    ),
+                    ...transformaType(value, StandardStatus.InProgress),
                     ...transformaType(doneStatusList, StandardStatus.Done),
                   },
                 })
@@ -334,22 +309,17 @@ const renderCollapseItems = ({
             />
           </Form.Item>
           <Form.Item label="DONE">
-            <MultiSelector
-              items={statusList}
-              disabledItems={statusList.filter((v) => [...todoStatusList, ...inProgressStatusList].includes(v.name))}
-              getKey={(it) => it.id}
-              getName={(it) => it.name}
-              selectedItems={statusList.filter((v) => doneStatusList.includes(v.name))}
-              onChangeItems={(selectedItems) =>
+            <Select
+              mode="multiple"
+              options={statusList.map((it) => ({ label: it.name, value: it.id }))}
+              value={doneStatusList}
+              onChange={(value) =>
                 onChangeTransformation({
                   ...transformation,
                   statusMappings: {
                     ...transformaType(todoStatusList, StandardStatus.Todo),
                     ...transformaType(inProgressStatusList, StandardStatus.InProgress),
-                    ...transformaType(
-                      selectedItems.map((v) => v.name),
-                      StandardStatus.Done,
-                    ),
+                    ...transformaType(value, StandardStatus.Done),
                   },
                 })
               }
diff --git a/config-ui/src/routes/api-keys/api-keys.tsx b/config-ui/src/routes/api-keys/api-keys.tsx
index 252ac553f..6271b4674 100644
--- a/config-ui/src/routes/api-keys/api-keys.tsx
+++ b/config-ui/src/routes/api-keys/api-keys.tsx
@@ -17,12 +17,12 @@
  */
 
 import { useState, useMemo } from 'react';
-import { Table, Modal, Input } from 'antd';
+import { Table, Modal, Input, Select } from 'antd';
 import { Button, Tag, Intent } from '@blueprintjs/core';
 import dayjs from 'dayjs';
 
 import API from '@/api';
-import { PageHeader, Block, Selector, ExternalLink, CopyText, Message } from '@/components';
+import { PageHeader, Block, ExternalLink, CopyText, Message } from '@/components';
 import { useRefreshData } from '@/hooks';
 import { operator, formatTime } from '@/utils';
 
@@ -53,8 +53,8 @@ export const ApiKeys = () => {
   const [dataSource, total] = useMemo(() => [data?.apikeys ?? [], data?.count ?? 0], [data]);
   const hasError = useMemo(() => !form.name || !form.allowedPath, [form]);
 
-  const timeSelectedItem = useMemo(() => {
-    return C.timeOptions.find((it) => it.value === form.expiredAt || !it.value);
+  const timeSelectedValue = useMemo(() => {
+    return C.timeOptions.find((it) => it.value === form.expiredAt || !it.value)?.value;
   }, [form.expiredAt]);
 
   const handleCancel = () => {
@@ -174,15 +174,12 @@ export const ApiKeys = () => {
             />
           </Block>
           <Block title="Expiration" description="Set an expiration time for your API key." required>
-            <div style={{ width: 386 }}>
-              <Selector
-                items={C.timeOptions}
-                getKey={(it) => it.value}
-                getName={(it) => it.label}
-                selectedItem={timeSelectedItem}
-                onChangeItem={(it) => setForm({ ...form, expiredAt: it.value ? it.value : undefined })}
-              />
-            </div>
+            <Select
+              style={{ width: 386 }}
+              options={C.timeOptions}
+              value={timeSelectedValue}
+              onChange={(value) => setForm({ ...form, expiredAt: value ? value : undefined })}
+            />
           </Block>
           <Block
             title="Allowed Path"