diff --git a/backend/plugins/webhook/api/deployments.go b/backend/plugins/webhook/api/deployments.go index 70c90f9e2f5..366cf11b71c 100644 --- a/backend/plugins/webhook/api/deployments.go +++ b/backend/plugins/webhook/api/deployments.go @@ -21,7 +21,6 @@ import ( "crypto/md5" "fmt" "net/http" - "strings" "time" "github.com/apache/incubator-devlake/core/dal" @@ -135,20 +134,16 @@ func CreateDeploymentAndDeploymentCommits(connection *models.WebhookConnection, request.Environment = devops.PRODUCTION } duration := float64(request.FinishedDate.Sub(*request.StartedDate).Milliseconds() / 1e3) - name := request.Name - if name == "" { - var commitShaList []string - for _, commit := range request.DeploymentCommits { - commitShaList = append(commitShaList, commit.CommitSha) - } - name = fmt.Sprintf(`deploy %s to %s`, strings.Join(commitShaList, ","), request.Environment) - } createdDate := time.Now() if request.CreatedDate != nil { createdDate = *request.CreatedDate } else if request.StartedDate != nil { createdDate = *request.StartedDate } + name := request.Name + if name == "" { + name = fmt.Sprintf(`deploy to %s at %s`, request.Environment, createdDate.Format(time.RFC3339)) + } // prepare deploymentCommits and deployment records // queuedDuration := dateInfo.CalculateQueueDuration() diff --git a/config-ui/package.json b/config-ui/package.json index 99d10bf1997..62fe1e327ad 100644 --- a/config-ui/package.json +++ b/config-ui/package.json @@ -24,6 +24,11 @@ "dependencies": { "@ahooksjs/use-url-state": "^3.5.1", "@ant-design/icons": "^5.3.0", + "@fontsource/roboto": "^5.0.14", + "@mints/miller-columns": "^2.0.0-beta.1", + "@mui/icons-material": "^5.16.7", + "@mui/material": "^5.16.7", + "@mui/styled-engine-sc": "^6.0.0-alpha.18", "@reduxjs/toolkit": "^2.2.1", "ahooks": "^3.7.10", "antd": "^5.14.2", @@ -34,7 +39,6 @@ "dayjs": "^1.11.10", "file-saver": "^2.0.5", "lodash": "^4.17.21", - "miller-columns-select": "1.4.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", @@ -69,5 +73,8 @@ "typescript": "^5.1.6", "vite": "^5.1.4", "vite-plugin-svgr": "^4.2.0" + }, + "resolutions": { + "@mui/styled-engine": "npm:@mui/styled-engine-sc@^6.0.0-alpha.18" } } diff --git a/config-ui/src/api/scope/index.ts b/config-ui/src/api/scope/index.ts index 7007314f694..b9d19a6729a 100644 --- a/config-ui/src/api/scope/index.ts +++ b/config-ui/src/api/scope/index.ts @@ -91,7 +91,7 @@ export const searchRemote = ( plugin: string, connectionId: ID, data: SearchRemoteQuery, -): Promise<{ children: RemoteScope[]; count: number }> => +): Promise<{ children: RemoteScope[]; page: number; pageSize: number }> => request(`/plugins/${plugin}/connections/${connectionId}/search-remote-scopes`, { method: 'get', data, diff --git a/config-ui/src/app/routrer.tsx b/config-ui/src/app/routrer.tsx index ccb5ef1646f..f59b0a58a97 100644 --- a/config-ui/src/app/routrer.tsx +++ b/config-ui/src/app/routrer.tsx @@ -45,6 +45,10 @@ const PATH_PREFIX = import.meta.env.DEVLAKE_PATH_PREFIX ?? '/'; export const router = createBrowserRouter( [ + { + path: 'db-migrate', + element: , + }, { path: '/', element: , @@ -54,10 +58,6 @@ export const router = createBrowserRouter( index: true, element: , }, - { - path: 'db-migrate', - element: , - }, { path: 'onboard', element: , diff --git a/config-ui/src/features/connections/slice.ts b/config-ui/src/features/connections/slice.ts index 8e2309bc0e0..01d1db6af08 100644 --- a/config-ui/src/features/connections/slice.ts +++ b/config-ui/src/features/connections/slice.ts @@ -33,7 +33,7 @@ const initialState: { connections: IConnection[]; webhooks: IWebhook[]; } = { - status: 'idle', + status: 'loading', plugins: [], connections: [], webhooks: [], diff --git a/config-ui/src/features/onboard/slice.ts b/config-ui/src/features/onboard/slice.ts index 11400f43d6a..6542c2c12b5 100644 --- a/config-ui/src/features/onboard/slice.ts +++ b/config-ui/src/features/onboard/slice.ts @@ -69,7 +69,7 @@ export const done = createAsyncThunk('onboard/done', async (_, { getState }) => }); const initialState: { status: IStatus; error?: unknown; data: DataType } = { - status: 'idle', + status: 'loading', data: { initial: true, step: 0, diff --git a/config-ui/src/features/version/slice.ts b/config-ui/src/features/version/slice.ts index 873d9a46ed6..f335f08684a 100644 --- a/config-ui/src/features/version/slice.ts +++ b/config-ui/src/features/version/slice.ts @@ -32,7 +32,7 @@ const initialState: { error?: unknown; version: string; } = { - status: 'idle', + status: 'loading', version: '', }; diff --git a/config-ui/src/plugins/components/data-scope-remote/search-local.tsx b/config-ui/src/plugins/components/data-scope-remote/search-local.tsx index 70660c71fc5..716abf0e074 100644 --- a/config-ui/src/plugins/components/data-scope-remote/search-local.tsx +++ b/config-ui/src/plugins/components/data-scope-remote/search-local.tsx @@ -16,20 +16,50 @@ * */ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useReducer, useCallback } from 'react'; import { CheckCircleFilled, SearchOutlined } from '@ant-design/icons'; -import { Space, Tag, Button, Input, Modal, message } from 'antd'; -import type { McsID, McsItem, McsColumn } from 'miller-columns-select'; -import { MillerColumnsSelect } from 'miller-columns-select'; +import { Space, Tag, Button, Input, Modal } from 'antd'; +import { MillerColumns } from '@mints/miller-columns'; import { useDebounce } from 'ahooks'; import API from '@/api'; -import { Loading, Block, Message } from '@/components'; -import { IPluginConfig } from '@/types'; +import { Block, Loading, Message } from '@/components'; +import type { IPluginConfig } from '@/types'; -import * as T from './types'; import * as S from './styled'; +type StateType = { + status: string; + scope: any[]; + originData: any[]; +}; + +const reducer = ( + state: StateType, + action: { type: string; payload?: Pick, 'scope' | 'originData'> }, +) => { + switch (action.type) { + case 'LOADING': + return { + ...state, + status: 'loading', + }; + case 'APPEND': + return { + ...state, + scope: [...state.scope, ...(action.payload?.scope ?? [])], + originData: [...state.originData, ...(action.payload?.originData ?? [])], + }; + case 'DONE': + return { + ...state, + status: 'done', + }; + default: + return state; + } +}; + interface Props { mode: 'single' | 'multiple'; plugin: string; @@ -40,149 +70,85 @@ interface Props { onChange: (selectedScope: any[]) => void; } -let canceling = false; - export const SearchLocal = ({ mode, plugin, connectionId, config, disabledScope, selectedScope, onChange }: Props) => { - const [miller, setMiller] = useState<{ - items: McsItem[]; - loadedIds: ID[]; - expandedIds: ID[]; - errorId?: ID | null; - nextTokenMap: Record; - }>({ - items: [], - loadedIds: [], - expandedIds: [], - nextTokenMap: {}, - }); - const [open, setOpen] = useState(false); - const [status, setStatus] = useState('init'); - - const [query, setQuery] = useState(''); - const search = useDebounce(query, { wait: 500 }); - - const scopes = useMemo( - () => - search - ? miller.items - .filter((it) => it.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())) - .filter((it) => it.type !== 'group') - .map((it) => ({ - ...it, - parentId: null, - })) - : miller.items, - [search, miller.items], - ); + const [search, setSearch] = useState(''); - const getItems = async ({ - groupId, - currentPageToken, - loadAll, - }: { - groupId: ID | null; - currentPageToken?: string; - loadAll?: boolean; - }) => { - if (canceling) { - canceling = false; - setStatus('init'); - return; - } + const [{ status, scope, originData }, dispatch] = useReducer(reducer, { + status: 'idle', + scope: [], + originData: [], + }); - let newItems: McsItem[] = []; - let nextPageToken = ''; - let errorId: ID | null; + const searchDebounce = useDebounce(search, { wait: 500 }); + + const request = useCallback( + async (groupId?: string | number, params?: any) => { + if (scope.length) { + return { + data: searchDebounce + ? scope + .filter((it) => it.title.includes(searchDebounce) && !it.canExpand) + .map((it) => ({ ...it, parentId: null })) + : scope.filter((it) => it.parentId === (groupId ?? null)), + hasMore: status === 'loading' ? true : false, + originData, + }; + } - try { const res = await API.scope.remote(plugin, connectionId, { - groupId, - pageToken: currentPageToken, + groupId: groupId ?? null, + pageToken: params?.nextPageToken, }); - newItems = (res.children ?? []).map((it) => ({ - ...it, - title: it.name, + const data = res.children.map((it) => ({ + parentId: it.parentId, + id: it.id, + title: it.name ?? it.fullName, + canExpand: it.type === 'group', })); - nextPageToken = res.nextPageToken; - } catch (err: any) { - errorId = groupId; - message.error(err.response.data.message); - } - - if (nextPageToken) { - setMiller((m) => ({ - ...m, - items: [...m.items, ...newItems], - expandedIds: [...m.expandedIds, groupId ?? 'root'], - nextTokenMap: { - ...m.nextTokenMap, - [`${groupId ? groupId : 'root'}`]: nextPageToken, + return { + data, + hasMore: !!res.nextPageToken, + params: { + nextPageToken: res.nextPageToken, }, - })); - - if (loadAll) { - await getItems({ groupId, currentPageToken: nextPageToken, loadAll }); - } - } else { - setMiller((m) => ({ - ...m, - items: [...m.items, ...newItems], - expandedIds: [...m.expandedIds, groupId ?? 'root'], - loadedIds: [...m.loadedIds, groupId ?? 'root'], - errorId, - })); + originData: res.children, + }; + }, + [plugin, connectionId, scope, status, searchDebounce], + ); - const groupItems = newItems.filter((it) => it.type === 'group'); + const handleRequestAll = async () => { + setOpen(false); + dispatch({ type: 'LOADING' }); - if (loadAll && groupItems.length) { - groupItems.forEach(async (it) => await getItems({ groupId: it.id, loadAll: true })); - } - } - }; + const getData = async (groupId?: string | number, currentPageToken?: string) => { + const res = await API.scope.remote(plugin, connectionId, { + groupId: groupId ?? null, + pageToken: currentPageToken, + }); - useEffect(() => { - getItems({ groupId: null }); - }, []); + const data = res.children.map((it) => ({ + parentId: it.parentId, + id: it.id, + title: it.name ?? it.fullName, + canExpand: it.type === 'group', + })); - useEffect(() => { - if ( - miller.items.length && - !miller.items.filter((it) => it.type === 'group' && !miller.loadedIds.includes(it.id)).length - ) { - setStatus('loaded'); - } - }, [miller]); + dispatch({ type: 'APPEND', payload: { scope: data, originData: res.children } }); - const handleLoadAllScopes = async () => { - setOpen(false); - setStatus('loading'); + if (res.nextPageToken) { + await getData(groupId, res.nextPageToken); + } - if (!miller.loadedIds.includes('root')) { - await getItems({ - groupId: null, - currentPageToken: miller.nextTokenMap['root'], - loadAll: true, - }); - } + await Promise.all(data.filter((it) => it.canExpand).map((it) => getData(it.id))); + }; - const noLoadedItems = miller.items.filter((it) => it.type === 'group' && !miller.loadedIds.includes(it.id)); - if (noLoadedItems.length) { - noLoadedItems.forEach(async (it) => { - await getItems({ - groupId: it.id, - currentPageToken: miller.nextTokenMap[it.id], - loadAll: true, - }); - }); - } - }; + await getData(); - const handleCancelLoadAllScopes = () => { - setStatus('cancel'); - canceling = true; + dispatch({ type: 'DONE' }); }; return ( @@ -209,59 +175,54 @@ export const SearchLocal = ({ mode, plugin, connectionId, config, disabledScope, {(status === 'loading' || status === 'cancel') && ( - Loading: {miller.items.length} scopes found - + Loading: {scope.length} scopes found )} - {status === 'loaded' && ( + {status === 'done' && ( - {miller.items.length} scopes found + {scope.length} scopes found )} - {status === 'init' && ( + {status === 'idle' && ( - )} - {status === 'loaded' && ( - } value={query} onChange={(e) => setQuery(e.target.value)} /> + {status === 'done' && ( + } value={search} onChange={(e) => setSearch(e.target.value)} /> )} - it.type === 'group'} - getHasMore={(id) => !miller.loadedIds.includes(id ?? 'root')} - getHasError={(id) => id === miller.errorId} - onExpand={(id: McsID) => getItems({ groupId: id })} - onScroll={(id: McsID | null) => - getItems({ groupId: id, currentPageToken: miller.nextTokenMap[id ?? 'root'] }) - } - renderTitle={(column: McsColumn) => - !column.parentId && + mode={mode} + renderTitle={(id) => + !id && config.millerColumn?.firstColumnTitle && ( {config.millerColumn.firstColumnTitle} ) } renderLoading={() => } renderError={() => Something Error} + selectable disabledIds={(disabledScope ?? []).map((it) => it.id)} selectedIds={selectedScope.map((it) => it.id)} - onSelectItemIds={(selectedIds: ID[]) => onChange(miller.items.filter((it) => selectedIds.includes(it.id)))} - expandedIds={miller.expandedIds} + onSelectedIds={(ids, data) => onChange((data ?? []).filter((it) => ids.includes(it.id)))} /> - setOpen(false)}> + setOpen(false)}> diff --git a/config-ui/src/plugins/components/data-scope-remote/search-remote.tsx b/config-ui/src/plugins/components/data-scope-remote/search-remote.tsx index 4679b856409..0f6710fe8ec 100644 --- a/config-ui/src/plugins/components/data-scope-remote/search-remote.tsx +++ b/config-ui/src/plugins/components/data-scope-remote/search-remote.tsx @@ -16,19 +16,16 @@ * */ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useCallback } from 'react'; import { SearchOutlined } from '@ant-design/icons'; -import { Space, Tag, Input, message } from 'antd'; -import type { McsID, McsItem, McsColumn } from 'miller-columns-select'; -import MillerColumnsSelect from 'miller-columns-select'; +import { Space, Tag, Input } from 'antd'; import { useDebounce } from 'ahooks'; -import { uniqBy } from 'lodash'; +import { MillerColumns } from '@mints/miller-columns'; import API from '@/api'; -import { Loading, Block } from '@/components'; -import { IPluginConfig } from '@/types'; +import { Block, Loading } from '@/components'; +import type { IPluginConfig } from '@/types'; -import * as T from './types'; import * as S from './styled'; interface Props { @@ -42,116 +39,67 @@ interface Props { } export const SearchRemote = ({ mode, plugin, connectionId, config, disabledScope, selectedScope, onChange }: Props) => { - const [miller, setMiller] = useState<{ - items: McsItem[]; - loadedIds: ID[]; - errorId?: ID | null; - nextTokenMap: Record; - }>({ - items: [], - loadedIds: [], - nextTokenMap: {}, - }); - - const [search, setSearch] = useState<{ - loading: boolean; - items: McsItem[]; - currentItems: McsItem[]; - query: string; - page: number; - total: number; - }>({ - loading: true, - items: [], - currentItems: [], - query: '', - page: 1, - total: 0, - }); - - const searchDebounce = useDebounce(search.query, { wait: 500 }); - - const allItems = useMemo( - () => - uniqBy( - [...miller.items, ...search.items].filter((it) => it.type === 'scope'), - 'id', - ), - [miller.items, search.items], + const [search, setSearch] = useState(''); + + const searchDebounce = useDebounce(search, { wait: 500 }); + + const request = useCallback( + async (groupId?: string | number, params?: any) => { + let data = []; + let hasMore = false; + let newParams = {}; + let originData = []; + + if (!searchDebounce) { + const res = await API.scope.remote(plugin, connectionId, { + groupId: groupId ?? null, + pageToken: params?.pageToken, + }); + + data = res.children.map((it) => ({ + parentId: it.parentId, + id: it.id, + title: it.name ?? it.fullName, + canExpand: it.type === 'group', + })); + + hasMore = !!res.nextPageToken; + newParams = { + pageToken: res.nextPageToken, + }; + originData = res.children; + } else { + const res = await API.scope.searchRemote(plugin, connectionId, { + search: searchDebounce, + page: params?.page ?? 1, + pageSize: 20, + }); + + data = res.children.map((it) => ({ + parentId: it.parentId, + id: it.id, + title: it.fullName ?? it.name, + canExpand: it.type === 'group', + })); + + hasMore = res.children.length === res.pageSize; + newParams = { + page: (params?.page ?? 0) + 1, + count: (params?.count ?? 0) + res.children.length, + }; + originData = res.children; + } + + return { + data, + hasMore, + params: newParams, + originData, + }; + }, + [plugin, connectionId, searchDebounce], ); - const getItems = async (groupId: ID | null, currentPageToken?: string) => { - let newItems: McsItem[] = []; - let nextPageToken = ''; - let errorId: ID | null; - - try { - const res = await API.scope.remote(plugin, connectionId, { - groupId, - pageToken: currentPageToken, - }); - - newItems = (res.children ?? []).map((it) => ({ - ...it, - title: it.name, - })); - - nextPageToken = res.nextPageToken; - } catch (err: any) { - errorId = groupId; - message.error(err.response.data.message); - } - - if (nextPageToken && newItems.length) { - setMiller((m) => ({ - ...m, - items: [...m.items, ...newItems], - nextTokenMap: { - ...m.nextTokenMap, - [`${groupId ? groupId : 'root'}`]: nextPageToken, - }, - })); - } else { - setMiller((m) => ({ - ...m, - items: [...m.items, ...newItems], - loadedIds: [...m.loadedIds, groupId ?? 'root'], - errorId, - })); - } - }; - - useEffect(() => { - getItems(null); - }, []); - - const searchItems = async () => { - if (!searchDebounce) return; - - const res = await API.scope.searchRemote(plugin, connectionId, { - search: searchDebounce, - page: search.page, - pageSize: 20, - }); - - const newItems = (res.children ?? []).map((it) => ({ - ...it, - title: it.fullName ?? it.name, - })); - - setSearch((s) => ({ - ...s, - loading: false, - items: [...allItems, ...newItems], - currentItems: newItems, - total: res.count, - })); - }; - - useEffect(() => { - searchItems(); - }, [searchDebounce, search.page]); - return ( <> @@ -176,47 +124,31 @@ export const SearchRemote = ({ mode, plugin, connectionId, config, disabledScope } placeholder={config.searchPlaceholder ?? 'Search'} - value={search.query} - onChange={(e) => setSearch({ ...search, query: e.target.value, loading: true, currentItems: [] })} + value={search} + onChange={(e) => setSearch(e.target.value)} + /> + + !id && + config.millerColumn?.firstColumnTitle && ( + {config.millerColumn.firstColumnTitle} + ) + } + renderLoading={() => } + selectable + disabledIds={disabledScope.map((it) => it.id)} + selectedIds={selectedScope.map((it) => it.id)} + onSelectedIds={(ids, data) => onChange((data ?? []).filter((it) => ids.includes(it.id)))} /> - {!searchDebounce ? ( - it.type === 'group'} - getHasMore={(id) => !miller.loadedIds.includes(id ?? 'root')} - getHasError={(id) => id === miller.errorId} - onExpand={(id: McsID) => getItems(id, miller.nextTokenMap[id])} - onScroll={(id: McsID | null) => getItems(id, miller.nextTokenMap[id ?? 'root'])} - renderTitle={(column: McsColumn) => - !column.parentId && - config.millerColumn?.firstColumnTitle && ( - {config.millerColumn.firstColumnTitle} - ) - } - renderLoading={() => } - renderError={() => Something Error} - disabledIds={(disabledScope ?? []).map((it) => it.id)} - selectedIds={selectedScope.map((it) => it.id)} - onSelectItemIds={(selectedIds: ID[]) => onChange(allItems.filter((it) => selectedIds.includes(it.id)))} - /> - ) : ( - false} - getHasMore={() => search.loading} - onScroll={() => setSearch({ ...search, page: search.page + 1 })} - renderLoading={() => } - disabledIds={(disabledScope ?? []).map((it) => it.id)} - selectedIds={selectedScope.map((it) => it.id)} - onSelectItemIds={(selectedIds: ID[]) => onChange(allItems.filter((it) => selectedIds.includes(it.id)))} - /> - )} ); 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 213b7ed25a4..02f252ef491 100644 --- a/config-ui/src/plugins/components/data-scope-select/index.tsx +++ b/config-ui/src/plugins/components/data-scope-select/index.tsx @@ -16,16 +16,14 @@ * */ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { RedoOutlined, PlusOutlined } from '@ant-design/icons'; -import { Flex, Select, Button } from 'antd'; +import { Flex, Button, Input, Space, Tag } from 'antd'; import { useDebounce } from 'ahooks'; -import type { McsItem } from 'miller-columns-select'; -import MillerColumnsSelect from 'miller-columns-select'; +import { MillerColumns } from '@mints/miller-columns'; import API from '@/api'; import { Loading, Block, ExternalLink, Message } from '@/components'; -import { useRefreshData } from '@/hooks'; import { getPluginScopeId } from '@/plugins'; interface Props { @@ -45,74 +43,51 @@ export const DataScopeSelect = ({ onSubmit, onCancel, }: Props) => { - const [loading, setLoading] = useState(false); - const [query, setQuery] = useState(''); - const [items, setItems] = useState[]>([]); const [selectedIds, setSelectedIds] = useState([]); - // const [selectedItems, setSelecteItems] = useState([]); - const [page, setPage] = useState(1); - const [pageSize] = useState(10); - const [total, setTotal] = useState(0); + const [originData, setOriginData] = useState([]); + const [search, setSearch] = useState(''); + const [version, setVersion] = useState(0); + + const searchDebounce = useDebounce(search, { wait: 500 }); useEffect(() => { setSelectedIds((initialScope ?? []).map((sc) => sc.id)); }, []); - const getDataScope = async (page: number) => { - if (page === 1) { - setLoading(true); - } + const request = useCallback( + async (_?: string | number, params?: any) => { + const res = await API.scope.list(plugin, connectionId, { + page: params?.page ?? 1, + pageSize: 20, + searchTerm: searchDebounce, + }); - const res = await API.scope.list(plugin, connectionId, { page, pageSize }); - setItems((items) => [ - ...items, - ...res.scopes.map((sc) => ({ + const data = res.scopes.map((it) => ({ parentId: null, - id: getPluginScopeId(plugin, sc.scope), - title: sc.scope.fullName ?? sc.scope.name, - data: sc.scope, - })), - ]); - - setTotal(res.count); - setLoading(false); - }; - - useEffect(() => { - getDataScope(page); - }, [page]); - - const search = useDebounce(query, { wait: 500 }); - - const { ready, data } = useRefreshData( - async () => await API.scope.list(plugin, connectionId, { searchTerm: search }), - [search], - ); - - const searchOptions = useMemo( - () => - data?.scopes.map((sc) => ({ - label: sc.scope.fullName ?? sc.scope.name, - value: getPluginScopeId(plugin, sc.scope), - })) ?? [], - [data], + id: getPluginScopeId(plugin, it.scope), + title: it.scope.fullName ?? it.scope.name, + canExpand: false, + })); + + return { + data, + hasMore: res.count > (params?.page ?? 1) * 20, + params: { + page: (params?.page ?? 1) + 1, + }, + originData: res.scopes, + }; + }, + [plugin, connectionId, searchDebounce, version], ); - const handleScroll = () => setPage(page + 1); - const handleSubmit = () => onSubmit?.(selectedIds); - const handleRefresh = () => { - setQuery(''); - setItems([]); - getDataScope(1); - }; - return ( Select the data scope in this Connection that you wish to associate with this Project. If you wish to add more Data Scope to this Connection, please{' '} @@ -130,65 +105,80 @@ export const DataScopeSelect = ({ } required > - {loading ? ( - - ) : items.length ? ( - - {showWarning ? ( - - Unchecking Data Scope below will only remove it from the current Project and will not delete the - historical data. If you would like to delete the data of Data Scope, please{' '} - go to the Connection page - . - - } - /> + + {showWarning ? ( + + Unchecking Data Scope below will only remove it from the current Project and will not delete the + historical data. If you would like to delete the data of Data Scope, please{' '} + go to the Connection page. + + } + /> + ) : ( + + + + )} + + {selectedIds.length ? ( + selectedIds.map((id) => { + const item = originData.find((it) => getPluginScopeId(plugin, it.scope) === `${id}`); + return ( + setSelectedIds(selectedIds.filter((it) => it !== id))} + > + {item?.scope.fullName ?? item?.scope.name} + + ); + }) ) : ( - - - + Please select scope... )} -