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/02/08 05:27:25 UTC

[incubator-devlake] branch main updated: refactor(config-ui): simplify miller-columns for each plugin (#4344)

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 c21cc52c5 refactor(config-ui): simplify miller-columns for each plugin (#4344)
c21cc52c5 is described below

commit c21cc52c556ef0f74b47f43dea1f9b04c32f106a
Author: 青湛 <0x...@gmail.com>
AuthorDate: Wed Feb 8 13:27:20 2023 +0800

    refactor(config-ui): simplify miller-columns for each plugin (#4344)
---
 config-ui/package-lock.json                        |  6 +-
 config-ui/package.json                             |  2 +-
 .../github/components/miller-columns/index.tsx     | 51 ++++++------
 .../miller-columns/use-miller-columns.ts           | 91 ++++++++++------------
 .../github/components/repo-selector/index.tsx      |  8 +-
 .../src/plugins/register/github/data-scope.tsx     |  8 +-
 .../gitlab/components/miller-columns/index.tsx     | 53 +++++++------
 .../miller-columns/use-miller-columns.ts           | 67 ++++++----------
 .../src/plugins/register/gitlab/data-scope.tsx     |  8 +-
 .../jenkins/components/miller-columns/index.tsx    | 29 ++++---
 .../miller-columns/use-miller-columns.ts           | 41 +++-------
 .../jira/components/miller-columns/index.tsx       | 33 ++++----
 .../miller-columns/use-miller-columns.ts           |  8 +-
 13 files changed, 190 insertions(+), 215 deletions(-)

diff --git a/config-ui/package-lock.json b/config-ui/package-lock.json
index 187d74be7..ed8db4f1a 100644
--- a/config-ui/package-lock.json
+++ b/config-ui/package-lock.json
@@ -10247,9 +10247,9 @@
       }
     },
     "miller-columns-select": {
-      "version": "1.0.0-beta.0",
-      "resolved": "https://registry.npmjs.org/miller-columns-select/-/miller-columns-select-1.0.0-beta.0.tgz",
-      "integrity": "sha512-CyC8daCoxlbXKpgRRA08HiYvlhsz/snzidJTtkhQIh6HLwa+tBL2N42BPkfz7ZEyW4K8WB/GP4l0bq3zun0APA==",
+      "version": "1.0.0-beta.3",
+      "resolved": "https://registry.npmjs.org/miller-columns-select/-/miller-columns-select-1.0.0-beta.3.tgz",
+      "integrity": "sha512-+4c1IvEs/1siMoPu5sa2dYW42W6W1jAdQXFjtAETLY1OCbRaY6Xem55tqPO7rdzDVPLT1edHNKiqS98poEG4uQ==",
       "requires": {
         "classnames": "^2.3.2",
         "react-infinite-scroll-component": "^6.1.0",
diff --git a/config-ui/package.json b/config-ui/package.json
index ea2fa3304..325187016 100644
--- a/config-ui/package.json
+++ b/config-ui/package.json
@@ -35,7 +35,7 @@
     "dotenv": "^10.0.0",
     "dotenv-webpack": "^7.0.3",
     "file-saver": "^2.0.5",
-    "miller-columns-select": "^1.0.0-beta.0",
+    "miller-columns-select": "^1.0.0-beta.3",
     "react": "17.0.2",
     "react-copy-to-clipboard": "^5.1.0",
     "react-dom": "17.0.2",
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 51510fe75..733793a2c 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
@@ -17,14 +17,15 @@
  */
 
 import React, { useState, useEffect } from 'react';
-import type { ID, ColumnType } from 'miller-columns-select';
+import type { McsID, McsItem, McsColumn } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import { Loading } from '@/components';
 
 import type { ScopeItemType } from '../../types';
 
-import { useMillerColumns, UseMillerColumnsProps } from './use-miller-columns';
+import type { UseMillerColumnsProps, ExtraType } from './use-miller-columns';
+import { useMillerColumns } from './use-miller-columns';
 import * as S from './styled';
 
 interface Props extends UseMillerColumnsProps {
@@ -33,9 +34,9 @@ interface Props extends UseMillerColumnsProps {
 }
 
 export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Props) => {
-  const [selectedIds, setSelectedIds] = useState<ID[]>([]);
+  const [selectedIds, setSelectedIds] = useState<McsID[]>([]);
 
-  const { items, getHasMore, onExpandItem, onScrollColumn } = useMillerColumns({
+  const { items, getHasMore, onExpand, onScroll } = useMillerColumns({
     connectionId,
   });
 
@@ -43,24 +44,30 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
     setSelectedIds(selectedItems.map((it) => it.githubId));
   }, [selectedItems]);
 
-  const handleChangeItems = (selectedIds: ID[]) => {
-    const result = items
-      .filter((it) => (selectedIds.length ? selectedIds.includes(it.id) : false))
-      .map((it) => ({
+  const handleChangeItems = (selectedIds: McsID[]) => {
+    const result = selectedIds.map((id) => {
+      const selectedItem = selectedItems.find((it) => it.githubId === id);
+      if (selectedItem) {
+        return selectedItem;
+      }
+
+      const item = items.find((it) => it.id === id) as McsItem<ExtraType>;
+      return {
         connectionId,
-        githubId: it.githubId,
-        name: it.name,
-        ownerId: it.ownerId,
-        language: it.language,
-        description: it.description,
-        cloneUrl: it.cloneUrl,
-        HTMLUrl: it.HTMLUrl,
-      }));
+        githubId: item.githubId,
+        name: item.name,
+        ownerId: item.ownerId,
+        language: item.language,
+        description: item.description,
+        cloneUrl: item.cloneUrl,
+        HTMLUrl: item.HTMLUrl,
+      };
+    });
 
     onChangeItems(result);
   };
 
-  const renderTitle = (column: ColumnType<any>) => {
+  const renderTitle = (column: McsColumn) => {
     return !column.parentId && <S.ColumnTitle>Organizations/Owners</S.ColumnTitle>;
   };
 
@@ -70,17 +77,17 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
 
   return (
     <MillerColumnsSelect
-      columnCount={2}
-      columnHeight={300}
+      items={items}
       getCanExpand={(it) => it.type === 'org'}
       getHasMore={getHasMore}
+      onExpand={onExpand}
+      onScroll={onScroll}
+      columnCount={2}
+      columnHeight={300}
       renderTitle={renderTitle}
       renderLoading={renderLoading}
-      items={items}
       selectedIds={selectedIds}
       onSelectItemIds={handleChangeItems}
-      onExpandItem={onExpandItem}
-      onScrollColumn={onScrollColumn}
     />
   );
 };
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 1bb248a8a..dcafcba97 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
@@ -17,7 +17,7 @@
  */
 
 import { useState, useEffect, useMemo, useCallback } from 'react';
-import { ItemType, ColumnType } from 'miller-columns-select';
+import { McsID, McsItem } from 'miller-columns-select';
 
 import { useProxyPrefix } from '@/hooks';
 
@@ -26,15 +26,11 @@ import * as API from '../../api';
 
 const DEFAULT_PAGE_SIZE = 30;
 
-type ExtraType = {
+export type ExtraType = {
   type: 'org' | 'repo';
 } & ScopeItemType;
 
-type GitHubItemType = ItemType<ExtraType>;
-
-type GitHubColumnType = ColumnType<ExtraType>;
-
-type MapPageType = Record<ID | 'root', number>;
+type MapPageType = Record<McsID | 'root', number>;
 
 export interface UseMillerColumnsProps {
   connectionId: string | number;
@@ -42,9 +38,8 @@ export interface UseMillerColumnsProps {
 
 export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
   const [user, setUser] = useState<any>({});
-  const [items, setItems] = useState<GitHubItemType[]>([]);
-  const [expandedIds, setExpandedIds] = useState<ID[]>([]);
-  const [loadedIds, setLoadedIds] = useState<ID[]>([]);
+  const [items, setItems] = useState<McsItem<ExtraType>[]>([]);
+  const [loadedIds, setLoadedIds] = useState<McsID[]>([]);
   const [mapPage, setMapPage] = useState<MapPageType>({});
 
   const prefix = useProxyPrefix({
@@ -52,7 +47,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     connectionId,
   });
 
-  const formatOrgs = (orgs: any, parentId: ID | null = null) =>
+  const formatOrgs = (orgs: any, parentId: McsID | null = null) =>
     orgs.map((it: any) => ({
       parentId,
       id: it.id,
@@ -60,7 +55,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
       type: 'org',
     }));
 
-  const formatRepos = (repos: any, parentId: ID | null = null) =>
+  const formatRepos = (repos: any, parentId: McsID | null = null) =>
     repos.map((it: any) => ({
       parentId,
       id: it.id,
@@ -76,7 +71,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     }));
 
   const setLoaded = useCallback(
-    (loaded: boolean, id: ID, nextPage: number) => {
+    (loaded: boolean, id: McsID, nextPage: number) => {
       if (loaded) {
         setLoadedIds([...loadedIds, id]);
       } else {
@@ -110,13 +105,11 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     })();
   }, [prefix]);
 
-  const onExpandItem = useCallback(
-    async (item: GitHubItemType) => {
-      if (expandedIds.includes(item.id)) {
-        return;
-      }
+  const onExpand = useCallback(
+    async (id: McsID) => {
+      const item = items.find((it) => it.id === id) as McsItem<ExtraType>;
 
-      const isUser = item.id === user.login;
+      const isUser = id === user.login;
       const repos = isUser
         ? await API.getUserRepos(prefix, user.login, {
             page: 1,
@@ -128,54 +121,54 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
           });
 
       const loaded = !repos.length || repos.length < DEFAULT_PAGE_SIZE;
-      setLoaded(loaded, item.id, 2);
-      setExpandedIds([...expandedIds, item.id]);
-      setItems([...items, ...formatRepos(repos, item.id)]);
+      setLoaded(loaded, id, 2);
+      setItems([...items, ...formatRepos(repos, id)]);
     },
     [items, prefix],
   );
 
-  const onScrollColumn = async (column: GitHubColumnType) => {
-    const page = mapPage[column.parentId ?? 'root'];
-    const isUser = column.parentId === user.login;
-    const orgs = !column.parentId
-      ? await API.getUserOrgs(prefix, user.login, {
-          page,
-          per_page: DEFAULT_PAGE_SIZE,
-        })
-      : [];
-
-    const repos = column.parentId
-      ? isUser
+  const onScroll = async (id: McsID | null) => {
+    const page = mapPage[id ?? 'root'];
+    let orgs = [];
+    let repos = [];
+    let loaded = false;
+
+    if (id) {
+      const isUser = id === user.login;
+      const item = items.find((it) => it.id === id) as McsItem<ExtraType>;
+
+      repos = isUser
         ? await API.getUserRepos(prefix, user.login, {
             page,
             per_page: DEFAULT_PAGE_SIZE,
           })
-        : await API.getOrgRepos(prefix, column.parentTitle, {
+        : await API.getOrgRepos(prefix, item.title, {
             page,
             per_page: DEFAULT_PAGE_SIZE,
-          })
-      : [];
+          });
+
+      loaded = !repos.length || repos.length < DEFAULT_PAGE_SIZE;
+    } else {
+      orgs = await API.getUserOrgs(prefix, user.login, {
+        page,
+        per_page: DEFAULT_PAGE_SIZE,
+      });
 
-    const loaded = !column.parentId
-      ? !orgs.length || orgs.length < DEFAULT_PAGE_SIZE
-      : !repos.length || repos.length < DEFAULT_PAGE_SIZE;
+      loaded = !orgs.length || orgs.length < DEFAULT_PAGE_SIZE;
+    }
 
-    setLoaded(loaded, column.parentId ?? 'root', page + 1);
-    setItems([...items, ...formatOrgs(orgs), ...formatRepos(repos, column.parentId)]);
+    setLoaded(loaded, id ?? 'root', page + 1);
+    setItems([...items, ...formatOrgs(orgs), ...formatRepos(repos, id)]);
   };
 
   return useMemo(
     () => ({
       items,
-      getHasMore(column: GitHubColumnType) {
-        if (loadedIds.includes(column.parentId ?? 'root')) {
-          return false;
-        }
-        return true;
+      getHasMore(id: McsID | null) {
+        return !loadedIds.includes(id ?? 'root');
       },
-      onExpandItem,
-      onScrollColumn,
+      onExpand,
+      onScroll,
     }),
     [items, loadedIds],
   );
diff --git a/config-ui/src/plugins/register/github/components/repo-selector/index.tsx b/config-ui/src/plugins/register/github/components/repo-selector/index.tsx
index 4151514c8..ce1d697b2 100644
--- a/config-ui/src/plugins/register/github/components/repo-selector/index.tsx
+++ b/config-ui/src/plugins/register/github/components/repo-selector/index.tsx
@@ -25,12 +25,11 @@ import { ScopeItemType } from '../../types';
 import { useRepoSelector, UseRepoSelectorProps } from './use-repo-selector';
 
 interface Props extends UseRepoSelectorProps {
-  disabledItems?: ScopeItemType[];
-  selectedItems?: ScopeItemType[];
-  onChangeItems?: (selectedItems: ScopeItemType[]) => void;
+  selectedItems: ScopeItemType[];
+  onChangeItems: (selectedItems: ScopeItemType[]) => void;
 }
 
-export const RepoSelector = ({ disabledItems, selectedItems, onChangeItems, ...props }: Props) => {
+export const RepoSelector = ({ selectedItems, onChangeItems, ...props }: Props) => {
   const { loading, items, onSearch } = useRepoSelector(props);
 
   return (
@@ -39,7 +38,6 @@ export const RepoSelector = ({ disabledItems, selectedItems, onChangeItems, ...p
       items={items}
       getKey={(it) => it.githubId}
       getName={(it) => it.name}
-      disabledItems={disabledItems}
       selectedItems={selectedItems}
       onChangeItems={onChangeItems}
       loading={loading}
diff --git a/config-ui/src/plugins/register/github/data-scope.tsx b/config-ui/src/plugins/register/github/data-scope.tsx
index 5101e3619..bebc03c96 100644
--- a/config-ui/src/plugins/register/github/data-scope.tsx
+++ b/config-ui/src/plugins/register/github/data-scope.tsx
@@ -29,18 +29,14 @@ interface Props {
 }
 
 export const GitHubDataScope = ({ connectionId, selectedItems, onChangeItems }: Props) => {
-  const handleChangeItems = (scope: ScopeItemType[]) => {
-    onChangeItems(scope);
-  };
-
   return (
     <>
       <h3>Repositories *</h3>
       <p>Select the repositories you would like to sync.</p>
-      <MillerColumns connectionId={connectionId} selectedItems={selectedItems} onChangeItems={handleChangeItems} />
+      <MillerColumns connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
       <h4>Add repositories outside of your organizations</h4>
       <p>Search for repositories and add to them</p>
-      <RepoSelector connectionId={connectionId} selectedItems={selectedItems} onChangeItems={handleChangeItems} />
+      <RepoSelector connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
     </>
   );
 };
diff --git a/config-ui/src/plugins/register/gitlab/components/miller-columns/index.tsx b/config-ui/src/plugins/register/gitlab/components/miller-columns/index.tsx
index 93ae0f2a0..68974626b 100644
--- a/config-ui/src/plugins/register/gitlab/components/miller-columns/index.tsx
+++ b/config-ui/src/plugins/register/gitlab/components/miller-columns/index.tsx
@@ -17,13 +17,14 @@
  */
 
 import React, { useEffect, useState } from 'react';
+import type { McsItem, McsColumn } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import { Loading } from '@/components';
 
 import type { ScopeItemType } from '../../types';
 
-import type { UseMillerColumnsProps, GitLabColumnType } from './use-miller-columns';
+import type { UseMillerColumnsProps, ExtraType } from './use-miller-columns';
 import { useMillerColumns } from './use-miller-columns';
 import * as S from './styled';
 
@@ -35,7 +36,7 @@ interface Props extends UseMillerColumnsProps {
 export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Props) => {
   const [selectedIds, setSelectedIds] = useState<ID[]>([]);
 
-  const { items, getHasMore, onExpandItem, onScrollColumn } = useMillerColumns({
+  const { items, getHasMore, onExpand, onScroll } = useMillerColumns({
     connectionId,
   });
 
@@ -44,27 +45,33 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
   }, [selectedItems]);
 
   const handleChangeItems = (selectedIds: ID[]) => {
-    const result = items
-      .filter((it) => (selectedIds.length ? selectedIds.includes(it.id) : false))
-      .map((it) => ({
+    const result = selectedIds.map((id) => {
+      const selectedItem = selectedItems.find((it) => it.gitlabId === id);
+      if (selectedItem) {
+        return selectedItem;
+      }
+
+      const item = items.find((it) => it.id === id) as McsItem<ExtraType>;
+      return {
         connectionId,
-        gitlabId: it.id,
-        name: it.name,
-        pathWithNamespace: it.pathWithNamespace,
-        creatorId: it.creatorId,
-        defaultBranch: it.defaultBranch,
-        description: it.description,
-        openIssuesCount: it.openIssuesCount,
-        starCount: it.starCount,
-        visibility: it.visibility,
-        webUrl: it.webUrl,
-        httpUrlToRepo: it.httpUrlToRepo,
-      }));
+        gitlabId: item.id,
+        name: item.name,
+        pathWithNamespace: item.pathWithNamespace,
+        creatorId: item.creatorId,
+        defaultBranch: item.defaultBranch,
+        description: item.description,
+        openIssuesCount: item.openIssuesCount,
+        starCount: item.starCount,
+        visibility: item.visibility,
+        webUrl: item.webUrl,
+        httpUrlToRepo: item.httpUrlToRepo,
+      };
+    });
 
     onChangeItems(result);
   };
 
-  const renderTitle = (column: GitLabColumnType) => {
+  const renderTitle = (column: McsColumn) => {
     return !column.parentId && <S.ColumnTitle>Subgroups/Projects</S.ColumnTitle>;
   };
 
@@ -74,17 +81,17 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
 
   return (
     <MillerColumnsSelect
-      columnCount={2.5}
-      columnHeight={300}
+      items={items}
       getCanExpand={(it) => it.type === 'group'}
       getHasMore={getHasMore}
+      onExpand={onExpand}
+      onScroll={onScroll}
+      columnCount={2.5}
+      columnHeight={300}
       renderTitle={renderTitle}
       renderLoading={renderLoading}
-      items={items}
       selectedIds={selectedIds}
       onSelectItemIds={handleChangeItems}
-      onExpandItem={onExpandItem}
-      onScrollColumn={onScrollColumn}
     />
   );
 };
diff --git a/config-ui/src/plugins/register/gitlab/components/miller-columns/use-miller-columns.ts b/config-ui/src/plugins/register/gitlab/components/miller-columns/use-miller-columns.ts
index 67a510f9b..8fe436ebf 100644
--- a/config-ui/src/plugins/register/gitlab/components/miller-columns/use-miller-columns.ts
+++ b/config-ui/src/plugins/register/gitlab/components/miller-columns/use-miller-columns.ts
@@ -17,7 +17,7 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import { ItemType, ColumnType } from 'miller-columns-select';
+import { McsID, McsItem } from 'miller-columns-select';
 
 import { useProxyPrefix } from '@/hooks';
 
@@ -26,17 +26,9 @@ import * as API from '../../api';
 
 const DEFAULT_PAGE_SIZE = 20;
 
-export type GitLabItemType = ItemType<
-  {
-    type: 'group' | 'project';
-  } & ScopeItemType
->;
-
-export type GitLabColumnType = ColumnType<
-  {
-    type: 'group' | 'project';
-  } & ScopeItemType
->;
+export type ExtraType = {
+  type: 'group' | 'project';
+} & ScopeItemType;
 
 type MapValueType = {
   groupPage: number;
@@ -53,8 +45,7 @@ export interface UseMillerColumnsProps {
 
 export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
   const [user, setUser] = useState<any>({});
-  const [items, setItems] = useState<GitLabItemType[]>([]);
-  const [expandedIds, setExpandedIds] = useState<ID[]>([]);
+  const [items, setItems] = useState<McsItem<ExtraType>[]>([]);
   const [map, setMap] = useState<MapType>({});
 
   const prefix = useProxyPrefix({
@@ -62,7 +53,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     connectionId,
   });
 
-  const formatGroups = (arr: any, parentId: ID | null = null): GitLabItemType[] =>
+  const formatGroups = (arr: any, parentId: ID | null = null): McsItem<ExtraType>[] =>
     arr.map((it: any) => ({
       parentId,
       id: it.id,
@@ -70,7 +61,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
       type: 'group',
     }));
 
-  const formatProjects = (arr: any, parentId: ID | null = null): GitLabItemType[] =>
+  const formatProjects = (arr: any, parentId: ID | null = null): McsItem<ExtraType>[] =>
     arr.map((it: any) => ({
       parentId,
       id: it.id,
@@ -132,17 +123,13 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     })();
   }, [prefix]);
 
-  const onExpandItem = async (item: GitLabItemType) => {
-    if (expandedIds.includes(item.id)) {
-      return;
-    }
-
+  const onExpand = async (id: McsID) => {
     let groupLoaded = false;
     let projectLoaded = false;
     let groups = [];
     let projects = [];
 
-    groups = await API.getGroupSubgroups(prefix, item.id, {
+    groups = await API.getGroupSubgroups(prefix, id, {
       page: 1,
       per_page: DEFAULT_PAGE_SIZE,
     });
@@ -150,7 +137,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     groupLoaded = !groups.length || groups.length < DEFAULT_PAGE_SIZE;
 
     if (groupLoaded) {
-      projects = await API.getGroupProjects(prefix, item.id, {
+      projects = await API.getGroupProjects(prefix, id, {
         page: 1,
         per_page: DEFAULT_PAGE_SIZE,
       });
@@ -158,18 +145,17 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
       projectLoaded = !projects.length || projects.length < DEFAULT_PAGE_SIZE;
     }
 
-    setLoaded(item.id, {
+    setLoaded(id, {
       groupLoaded,
       groupPage: groupLoaded ? 1 : 2,
       projectLoaded,
       projectPage: projectLoaded ? 1 : 2,
     });
-    setExpandedIds([...expandedIds, item.id]);
-    setItems([...items, ...formatGroups(groups, item.id), ...formatProjects(projects, item.id)]);
+    setItems([...items, ...formatGroups(groups, id), ...formatProjects(projects, id)]);
   };
 
-  const onScrollColumn = async (column: GitLabColumnType) => {
-    const mapValue = map[column.parentId ?? 'root'];
+  const onScroll = async (id: McsID | null) => {
+    const mapValue = map[id ?? 'root'];
 
     let groupLoaded = mapValue.groupLoaded;
     let projectLoaded = mapValue.projectLoaded;
@@ -177,8 +163,8 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     let projects = [];
 
     if (!groupLoaded) {
-      groups = column.parentId
-        ? await API.getGroupSubgroups(prefix, column.parentId, {
+      groups = id
+        ? await API.getGroupSubgroups(prefix, id, {
             page: mapValue.groupPage,
             per_page: DEFAULT_PAGE_SIZE,
           })
@@ -189,8 +175,8 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
 
       groupLoaded = !groups.length || groups.length < DEFAULT_PAGE_SIZE;
     } else if (!projectLoaded) {
-      projects = column.parentId
-        ? await API.getGroupProjects(prefix, column.parentId, {
+      projects = id
+        ? await API.getGroupProjects(prefix, id, {
             page: mapValue.projectPage,
             per_page: DEFAULT_PAGE_SIZE,
           })
@@ -202,27 +188,24 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
       projectLoaded = !projects.length || projects.length < DEFAULT_PAGE_SIZE;
     }
 
-    setLoaded(column.parentId ?? 'root', {
+    setLoaded(id ?? 'root', {
       groupLoaded,
       groupPage: groupLoaded ? mapValue.groupPage : mapValue.groupPage + 1,
       projectLoaded,
       projectPage: projectLoaded ? mapValue.projectPage : mapValue.projectPage + 1,
     });
-    setItems([...items, ...formatGroups(groups, column.parentId), ...formatProjects(projects, column.parentId)]);
+    setItems([...items, ...formatGroups(groups, id), ...formatProjects(projects, id)]);
   };
 
   return useMemo(
     () => ({
       items,
-      getHasMore(column: GitLabColumnType) {
-        const mapValue = map[column.parentId ?? 'root'];
-        if (mapValue?.groupLoaded && mapValue?.projectLoaded) {
-          return false;
-        }
-        return true;
+      getHasMore(id: McsID | null) {
+        const mapValue = map[id ?? 'root'];
+        return !(mapValue?.groupLoaded && mapValue?.projectLoaded);
       },
-      onExpandItem,
-      onScrollColumn,
+      onExpand,
+      onScroll,
     }),
     [items, map],
   );
diff --git a/config-ui/src/plugins/register/gitlab/data-scope.tsx b/config-ui/src/plugins/register/gitlab/data-scope.tsx
index c8465e21d..081f7b1cd 100644
--- a/config-ui/src/plugins/register/gitlab/data-scope.tsx
+++ b/config-ui/src/plugins/register/gitlab/data-scope.tsx
@@ -29,18 +29,14 @@ interface Props {
 }
 
 export const GitLabDataScope = ({ connectionId, selectedItems, onChangeItems }: Props) => {
-  const handleChangeItems = (scope: ScopeItemType[]) => {
-    onChangeItems(scope);
-  };
-
   return (
     <>
       <h4>Projects *</h4>
       <p>Select the project you would like to sync.</p>
-      <MillerColumns connectionId={connectionId} selectedItems={selectedItems} onChangeItems={handleChangeItems} />
+      <MillerColumns connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
       <h5>Add repositories outside of your projects</h5>
       <p>Search for repositories and add to them</p>
-      <ProjectSelector connectionId={connectionId} selectedItems={selectedItems} onChangeItems={handleChangeItems} />
+      <ProjectSelector connectionId={connectionId} selectedItems={selectedItems} onChangeItems={onChangeItems} />
     </>
   );
 };
diff --git a/config-ui/src/plugins/register/jenkins/components/miller-columns/index.tsx b/config-ui/src/plugins/register/jenkins/components/miller-columns/index.tsx
index b3a413008..5851f6d94 100644
--- a/config-ui/src/plugins/register/jenkins/components/miller-columns/index.tsx
+++ b/config-ui/src/plugins/register/jenkins/components/miller-columns/index.tsx
@@ -17,13 +17,14 @@
  */
 
 import React, { useState, useEffect } from 'react';
+import type { McsItem } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import { Loading } from '@/components';
 
 import type { ScopeItemType } from '../../types';
 
-import type { UseMillerColumnsProps } from './use-miller-columns';
+import type { UseMillerColumnsProps, ExtraType } from './use-miller-columns';
 import { useMillerColumns } from './use-miller-columns';
 
 interface Props extends UseMillerColumnsProps {
@@ -34,7 +35,7 @@ interface Props extends UseMillerColumnsProps {
 export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Props) => {
   const [selectedIds, setSelectedIds] = useState<ID[]>([]);
 
-  const { items, getHasMore, onExpandItem } = useMillerColumns({
+  const { items, getHasMore, onExpand } = useMillerColumns({
     connectionId,
   });
 
@@ -43,13 +44,19 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
   }, [selectedItems]);
 
   const handleChangeItems = (selectedIds: ID[]) => {
-    const result = items
-      .filter((it) => (selectedIds.length ? selectedIds.includes(it.id) : false))
-      .map((it: any) => ({
+    const result = selectedIds.map((id) => {
+      const selectedItem = selectedItems.find((it) => it.jobFullName === id);
+      if (selectedItem) {
+        return selectedItem;
+      }
+
+      const item = items.find((it) => it.id === id) as McsItem<ExtraType>;
+      return {
         connectionId,
-        jobFullName: it.id,
-        name: it.id,
-      }));
+        jobFullName: item.id as string,
+        name: item.id,
+      };
+    });
 
     onChangeItems(result);
   };
@@ -60,15 +67,15 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
 
   return (
     <MillerColumnsSelect
+      items={items}
+      getCanExpand={(it) => it.type === 'folder'}
+      onExpand={onExpand}
       columnCount={2.5}
       columnHeight={300}
-      getCanExpand={(it) => it.type === 'folder'}
       getHasMore={getHasMore}
       renderLoading={renderLoading}
-      items={items}
       selectedIds={selectedIds}
       onSelectItemIds={handleChangeItems}
-      onExpandItem={onExpandItem}
     />
   );
 };
diff --git a/config-ui/src/plugins/register/jenkins/components/miller-columns/use-miller-columns.ts b/config-ui/src/plugins/register/jenkins/components/miller-columns/use-miller-columns.ts
index aa19f1bd6..527ddef9d 100644
--- a/config-ui/src/plugins/register/jenkins/components/miller-columns/use-miller-columns.ts
+++ b/config-ui/src/plugins/register/jenkins/components/miller-columns/use-miller-columns.ts
@@ -17,33 +17,24 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import type { ItemType, ColumnType } from 'miller-columns-select';
+import type { McsID, McsItem } from 'miller-columns-select';
 
 import { useProxyPrefix } from '@/hooks';
 
 import type { ScopeItemType } from '../../types';
 import * as API from '../../api';
 
-export type JenkinsItemType = ItemType<
-  {
-    type: 'folder' | 'file';
-  } & ScopeItemType
->;
-
-export type JenkinsColumnType = ColumnType<
-  {
-    type: 'folder' | 'file';
-  } & ScopeItemType
->;
+export type ExtraType = {
+  type: 'folder' | 'file';
+} & ScopeItemType;
 
 export interface UseMillerColumnsProps {
   connectionId: ID;
 }
 
 export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
-  const [items, setItems] = useState<JenkinsItemType[]>([]);
+  const [items, setItems] = useState<McsItem<ExtraType>[]>([]);
   const [loadedIds, setLoadedIds] = useState<ID[]>([]);
-  const [expandedIds, setExpandedIds] = useState<ID[]>([]);
 
   const prefix = useProxyPrefix({
     plugin: 'jenkins',
@@ -66,28 +57,20 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
     })();
   }, [prefix]);
 
-  const onExpandItem = async (item: JenkinsItemType) => {
-    if (expandedIds.includes(item.id)) {
-      return;
-    }
-
-    const res = await API.getJobChildJobs(prefix, (item.id as string).split('/').join('/job/'));
+  const onExpand = async (id: McsID) => {
+    const res = await API.getJobChildJobs(prefix, (id as string).split('/').join('/job/'));
 
-    setExpandedIds([...expandedIds, item.id]);
-    setLoadedIds([...loadedIds, item.id]);
-    setItems([...items, ...formatJobs(res.jobs, item.id)]);
+    setLoadedIds([...loadedIds, id]);
+    setItems([...items, ...formatJobs(res.jobs, id)]);
   };
 
   return useMemo(
     () => ({
       items,
-      getHasMore(column: JenkinsColumnType) {
-        if (loadedIds.includes(column.parentId ?? 'root')) {
-          return false;
-        }
-        return true;
+      getHasMore(id: McsID | null) {
+        return !loadedIds.includes(id ?? 'root');
       },
-      onExpandItem,
+      onExpand,
     }),
     [items, loadedIds],
   );
diff --git a/config-ui/src/plugins/register/jira/components/miller-columns/index.tsx b/config-ui/src/plugins/register/jira/components/miller-columns/index.tsx
index f1ef8d6c2..7bc1a7a70 100644
--- a/config-ui/src/plugins/register/jira/components/miller-columns/index.tsx
+++ b/config-ui/src/plugins/register/jira/components/miller-columns/index.tsx
@@ -17,6 +17,7 @@
  */
 
 import React, { useState, useEffect } from 'react';
+import type { McsItem } from 'miller-columns-select';
 import MillerColumnsSelect from 'miller-columns-select';
 
 import { Loading } from '@/components';
@@ -34,7 +35,7 @@ interface Props extends UseMillerColumnsProps {
 export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Props) => {
   const [selectedIds, setSelectedIds] = useState<ID[]>([]);
 
-  const { items, getHasMore, onScrollColumn } = useMillerColumns({
+  const { items, getHasMore, onScroll } = useMillerColumns({
     connectionId,
   });
 
@@ -43,16 +44,22 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
   }, [selectedItems]);
 
   const handleChangeItems = (selectedIds: ID[]) => {
-    const result = items
-      .filter((it) => (selectedIds.length ? selectedIds.includes(it.id) : false))
-      .map((it) => ({
+    const result = selectedIds.map((id) => {
+      const selectedItem = selectedItems.find((it) => it.boardId === id);
+      if (selectedItem) {
+        return selectedItem;
+      }
+
+      const item = items.find((it) => it.id === id) as McsItem<ScopeItemType>;
+      return {
         connectionId,
-        boardId: it.boardId,
-        name: it.name,
-        self: it.self,
-        type: it.type,
-        projectId: it.projectId,
-      }));
+        boardId: item.boardId,
+        name: item.name,
+        self: item.self,
+        type: item.type,
+        projectId: item.projectId,
+      };
+    });
 
     onChangeItems(result);
   };
@@ -63,14 +70,14 @@ export const MillerColumns = ({ connectionId, selectedItems, onChangeItems }: Pr
 
   return (
     <MillerColumnsSelect
+      items={items}
+      getHasMore={getHasMore}
+      onScroll={onScroll}
       columnCount={1}
       columnHeight={300}
-      getHasMore={getHasMore}
       renderLoading={renderLoading}
-      items={items}
       selectedIds={selectedIds}
       onSelectItemIds={handleChangeItems}
-      onScrollColumn={onScrollColumn}
     />
   );
 };
diff --git a/config-ui/src/plugins/register/jira/components/miller-columns/use-miller-columns.ts b/config-ui/src/plugins/register/jira/components/miller-columns/use-miller-columns.ts
index e92da3183..41d7981da 100644
--- a/config-ui/src/plugins/register/jira/components/miller-columns/use-miller-columns.ts
+++ b/config-ui/src/plugins/register/jira/components/miller-columns/use-miller-columns.ts
@@ -17,7 +17,7 @@
  */
 
 import { useState, useEffect, useMemo } from 'react';
-import type { ItemType } from 'miller-columns-select';
+import type { McsItem } from 'miller-columns-select';
 
 import { useProxyPrefix } from '@/hooks';
 
@@ -26,14 +26,12 @@ import * as API from '../../api';
 
 const DEFAULT_PAGE_SIZE = 50;
 
-type JIRAItemType = ItemType<ScopeItemType>;
-
 export interface UseMillerColumnsProps {
   connectionId: ID;
 }
 
 export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
-  const [items, setItems] = useState<JIRAItemType[]>([]);
+  const [items, setItems] = useState<McsItem<ScopeItemType>[]>([]);
   const [isLast, setIsLast] = useState(false);
   const [page, setPage] = useState(1);
 
@@ -71,7 +69,7 @@ export const useMillerColumns = ({ connectionId }: UseMillerColumnsProps) => {
       getHasMore() {
         return !isLast;
       },
-      onScrollColumn() {
+      onScroll() {
         setPage(page + 1);
       },
     }),