This is an automated email from the ASF dual-hosted git repository. mintsweet pushed a commit to branch fix-5853 in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
commit c8a4af1c461044ea765108db5b4d391b749bf8c1 Author: mintsweet <[email protected]> AuthorDate: Tue Aug 15 20:55:55 2023 +1200 fix(config-ui): add pagination params for all tables --- config-ui/src/global.d.ts | 5 + .../pages/blueprint/connection-detail/index.tsx | 4 +- config-ui/src/pages/blueprint/home/api.ts | 9 +- config-ui/src/pages/blueprint/home/index.tsx | 64 ++++++------- config-ui/src/pages/connection/detail/api.ts | 3 +- config-ui/src/pages/connection/detail/index.tsx | 19 +++- config-ui/src/pages/project/home/api.ts | 2 +- config-ui/src/pages/project/home/index.tsx | 16 +++- .../plugins/components/data-scope-select/api.ts | 9 +- .../plugins/components/data-scope-select/index.tsx | 103 +++++++++++---------- 10 files changed, 136 insertions(+), 98 deletions(-) diff --git a/config-ui/src/global.d.ts b/config-ui/src/global.d.ts index 0f3a050aa..c65a6126b 100644 --- a/config-ui/src/global.d.ts +++ b/config-ui/src/global.d.ts @@ -18,6 +18,11 @@ type ID = string | number; +type Pagination = { + page?: number; + pageSize?: number; +}; + declare module '*.svg' { const content: any; export default content; diff --git a/config-ui/src/pages/blueprint/connection-detail/index.tsx b/config-ui/src/pages/blueprint/connection-detail/index.tsx index 2c66ce8cd..92a98708e 100644 --- a/config-ui/src/pages/blueprint/connection-detail/index.tsx +++ b/config-ui/src/pages/blueprint/connection-detail/index.tsx @@ -50,7 +50,7 @@ export const BlueprintConnectionDetailPage = () => { const { ready, data } = useRefreshData(async () => { const [plugin, connectionId] = unique.split('-'); - const [blueprint, connection, scopes] = await Promise.all([ + const [blueprint, connection, scopesRes] = await Promise.all([ getBlueprint(pname, bid), API.getConnection(plugin, connectionId), API.getDataScopes(plugin, connectionId), @@ -68,7 +68,7 @@ export const BlueprintConnectionDetailPage = () => { id: +connectionId, name: connection.name, }, - scopes: scopes.filter((sc: any) => scopeIds.includes(getPluginScopeId(plugin, sc))), + scopes: scopesRes.scopes.filter((sc: any) => scopeIds.includes(getPluginScopeId(plugin, sc))), }; }, [version, pname, bid]); diff --git a/config-ui/src/pages/blueprint/home/api.ts b/config-ui/src/pages/blueprint/home/api.ts index 62e7c80ed..d6554b6aa 100644 --- a/config-ui/src/pages/blueprint/home/api.ts +++ b/config-ui/src/pages/blueprint/home/api.ts @@ -20,17 +20,12 @@ import { request } from '@/utils'; import { BlueprintType } from '../types'; -type GetBlueprintsParams = { - page: number; - pageSize: number; -}; - -type GetBlueprintResponse = { +type ResponseType = { blueprints: Array<BlueprintType>; count: number; }; -export const getBlueprints = (params: GetBlueprintsParams): Promise<GetBlueprintResponse> => +export const getBlueprints = (params: Pagination & { type: string }): Promise<ResponseType> => request('/blueprints', { data: params }); export const createBlueprint = (payload: any) => diff --git a/config-ui/src/pages/blueprint/home/index.tsx b/config-ui/src/pages/blueprint/home/index.tsx index f37e3d558..1541bb657 100644 --- a/config-ui/src/pages/blueprint/home/index.tsx +++ b/config-ui/src/pages/blueprint/home/index.tsx @@ -32,43 +32,37 @@ import * as API from './api'; import * as S from './styled'; export const BlueprintHomePage = () => { - const [type, setType] = useState('all'); const [version, setVersion] = useState(1); + const [type, setType] = useState('all'); + const [page, setPage] = useState(1); + const [pageSize] = useState(20); const [isOpen, setIsOpen] = useState(false); const [name, setName] = useState(''); const [mode, setMode] = useState(ModeEnum.normal); const [saving, setSaving] = useState(false); const { onGet } = useConnections(); - const { ready, data } = useRefreshData(() => API.getBlueprints({ page: 1, pageSize: 200 }), [version]); + const { ready, data } = useRefreshData( + () => API.getBlueprints({ type: type.toLocaleUpperCase(), page, pageSize }), + [version, type, page, pageSize], + ); const [options, presets] = useMemo(() => [getCronOptions(), cronPresets.map((preset) => preset.config)], []); - const dataSource = useMemo( - () => - (data?.blueprints ?? []) - .filter((it) => { - switch (type) { - case 'all': - return true; - case 'manual': - return it.isManual; - case 'custom': - return !presets.includes(it.cronConfig); - default: - return !it.isManual && it.cronConfig === type; - } - }) - .map((it) => { - const connections = - it.settings?.connections - .filter((cs) => cs.plugin !== 'webhook') - .map((cs) => onGet(`${cs.plugin}-${cs.connectionId}`) || `${cs.plugin}-${cs.connectionId}`) ?? []; - return { - ...it, - connections: connections.map((cs) => cs.name), - }; - }), - [data, type], + const [dataSource, total] = useMemo( + () => [ + (data?.blueprints ?? []).map((it) => { + const connections = + it.settings?.connections + .filter((cs) => cs.plugin !== 'webhook') + .map((cs) => onGet(`${cs.plugin}-${cs.connectionId}`) || `${cs.plugin}-${cs.connectionId}`) ?? []; + return { + ...it, + connections: connections.map((cs) => cs.name), + }; + }), + data?.count ?? 0, + ], + [data], ); const handleShowDialog = () => setIsOpen(true); @@ -123,12 +117,12 @@ export const BlueprintHomePage = () => { <div className="action"> <ButtonGroup> <Button intent={type === 'all' ? Intent.PRIMARY : Intent.NONE} text="All" onClick={() => setType('all')} /> - {options.map(({ label, value }) => ( + {options.map(({ label }) => ( <Button - key={value} - intent={type === value ? Intent.PRIMARY : Intent.NONE} + key={label} + intent={type === label ? Intent.PRIMARY : Intent.NONE} text={label} - onClick={() => setType(value)} + onClick={() => setType(label)} /> ))} </ButtonGroup> @@ -221,6 +215,12 @@ export const BlueprintHomePage = () => { }, ]} dataSource={dataSource} + pagination={{ + page, + pageSize, + total, + onChange: setPage, + }} noData={{ text: 'There is no Blueprint yet. Please add a new Blueprint here or from a Project.', btnText: 'New Blueprint', diff --git a/config-ui/src/pages/connection/detail/api.ts b/config-ui/src/pages/connection/detail/api.ts index 23a7270ed..9b8a999a0 100644 --- a/config-ui/src/pages/connection/detail/api.ts +++ b/config-ui/src/pages/connection/detail/api.ts @@ -21,10 +21,11 @@ import { request } from '@/utils'; export const deleteConnection = (plugin: string, id: ID) => request(`/plugins/${plugin}/connections/${id}`, { method: 'delete' }); -export const getDataScopes = (plugin: string, id: ID) => +export const getDataScopes = (plugin: string, id: ID, payload: Pagination) => request(`/plugins/${plugin}/connections/${id}/scopes`, { data: { blueprints: true, + ...payload, }, }); diff --git a/config-ui/src/pages/connection/detail/index.tsx b/config-ui/src/pages/connection/detail/index.tsx index dc8d3493e..1456a4409 100644 --- a/config-ui/src/pages/connection/detail/index.tsx +++ b/config-ui/src/pages/connection/detail/index.tsx @@ -60,6 +60,8 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => { >(); const [operating, setOperating] = useState(false); const [version, setVersion] = useState(1); + const [page, setPage] = useState(1); + const [pageSize] = useState(10); const [scopeId, setScopeId] = useState<ID>(); const [scopeIds, setScopeIds] = useState<ID[]>([]); const [scopeConfigId, setScopeConfigId] = useState<ID>(); @@ -69,12 +71,17 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => { const navigate = useNavigate(); const { onGet, onTest, onRefresh } = useConnections(); const { setTips } = useTips(); - const { ready, data } = useRefreshData(() => API.getDataScopes(plugin, connectionId), [version]); + const { ready, data } = useRefreshData( + () => API.getDataScopes(plugin, connectionId, { page, pageSize }), + [version, page, pageSize], + ); const { unique, status, name, icon } = onGet(`${plugin}-${connectionId}`) || {}; const pluginConfig = useMemo(() => getPluginConfig(plugin), [plugin]); + const [dataSource, total] = useMemo(() => [data?.scopes ?? [], data?.count ?? 0], [data]); + useEffect(() => { onTest(`${plugin}-${connectionId}`); }, [plugin, connectionId]); @@ -322,7 +329,13 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => { ), }, ]} - dataSource={data} + dataSource={dataSource} + pagination={{ + page, + pageSize, + total, + onChange: setPage, + }} noData={{ text: 'Add data to this connection.', btnText: 'Add Data Scope', @@ -383,7 +396,7 @@ const ConnectionDetail = ({ plugin, connectionId }: Props) => { <DataScopeSelectRemote plugin={plugin} connectionId={connectionId} - disabledScope={data} + disabledScope={dataSource} onCancel={handleHideDialog} onSubmit={handleCreateDataScope} /> diff --git a/config-ui/src/pages/project/home/api.ts b/config-ui/src/pages/project/home/api.ts index e8dea41c7..539e91b38 100644 --- a/config-ui/src/pages/project/home/api.ts +++ b/config-ui/src/pages/project/home/api.ts @@ -33,7 +33,7 @@ type GetProjectsResponse = { blueprint: BlueprintType; lastPipeline: PipelineType; }>; - counts: number; + count: number; }; export const getProjects = (params: GetProjectsParams): Promise<GetProjectsResponse> => diff --git a/config-ui/src/pages/project/home/index.tsx b/config-ui/src/pages/project/home/index.tsx index 49d61723b..c9dd39005 100644 --- a/config-ui/src/pages/project/home/index.tsx +++ b/config-ui/src/pages/project/home/index.tsx @@ -36,19 +36,21 @@ import * as S from './styled'; export const ProjectHomePage = () => { const [version, setVersion] = useState(1); + const [page, setPage] = useState(1); + const [pageSize] = useState(20); const [isOpen, setIsOpen] = useState(false); const [name, setName] = useState(''); const [enableDora, setEnableDora] = useState(true); const [saving, setSaving] = useState(false); - const { ready, data } = useRefreshData(() => API.getProjects({ page: 1, pageSize: 200 }), [version]); + const { ready, data } = useRefreshData(() => API.getProjects({ page, pageSize }), [version, page, pageSize]); const { onGet } = useConnections(); const navigate = useNavigate(); const presets = useMemo(() => cronPresets.map((preset) => preset.config), []); - const dataSource = useMemo( - () => + const [dataSource, total] = useMemo( + () => [ (data?.projects ?? []).map((it) => { return { name: it.name, @@ -60,6 +62,8 @@ export const ProjectHomePage = () => { lastRunStatus: it.lastPipeline?.status, }; }), + data?.count ?? 0, + ], [data], ); @@ -190,6 +194,12 @@ export const ProjectHomePage = () => { }, ]} dataSource={dataSource} + pagination={{ + page, + pageSize, + total, + onChange: setPage, + }} noData={{ text: 'Add new projects to see engineering metrics based on projects.', btnText: 'New Project', diff --git a/config-ui/src/plugins/components/data-scope-select/api.ts b/config-ui/src/plugins/components/data-scope-select/api.ts index a6b1cb541..941dcbdc7 100644 --- a/config-ui/src/plugins/components/data-scope-select/api.ts +++ b/config-ui/src/plugins/components/data-scope-select/api.ts @@ -19,10 +19,15 @@ import { request } from '@/utils'; type ParamsType = { - searchTerm: string; + searchTerm?: string; +} & Pagination; + +type ResponseType = { + scopes: Array<{ name: string }>; + count: number; }; -export const getDataScope = (plugin: string, connectionId: ID, params?: ParamsType) => +export const getDataScope = (plugin: string, connectionId: ID, params?: ParamsType): Promise<ResponseType> => request(`/plugins/${plugin}/connections/${connectionId}/scopes`, { data: params, }); 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 145e87778..fdf64de6d 100644 --- a/config-ui/src/plugins/components/data-scope-select/index.tsx +++ b/config-ui/src/plugins/components/data-scope-select/index.tsx @@ -16,11 +16,13 @@ * */ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { Button, Intent } from '@blueprintjs/core'; import { useDebounce } from 'ahooks'; +import type { McsID, McsItem } from 'miller-columns-select'; +import MillerColumnsSelect from 'miller-columns-select'; -import { PageLoading, FormItem, ExternalLink, Message, Buttons, MultiSelector, Table } from '@/components'; +import { FormItem, ExternalLink, Message, Buttons, MultiSelector } from '@/components'; import { useRefreshData } from '@/hooks'; import { getPluginScopeId } from '@/plugins'; @@ -44,38 +46,59 @@ export const DataScopeSelect = ({ onSubmit, onCancel, }: Props) => { - const [version, setVersion] = useState(1); - const [scopeIds, setScopeIds] = useState<ID[]>([]); const [query, setQuery] = useState(''); + const [items, setItems] = useState<McsItem<{ data: any }>[]>([]); + const [selectedItems, setSelecteItems] = useState<any>([]); + const [page, setPage] = useState(1); + const [pageSize] = useState(10); + const [total, setTotal] = useState(0); useEffect(() => { - setScopeIds((initialScope ?? []).map((sc: any) => getPluginScopeId(plugin, sc)) ?? []); + setSelecteItems(initialScope ?? []); }, []); + const selectedIds = useMemo(() => selectedItems.map((it: any) => getPluginScopeId(plugin, it)), [selectedItems]); + + const handleChangeSelectItemsIds = (ids: McsID[]) => { + setSelecteItems(items.filter((it) => ids.includes(it.id)).map((it) => it.data)); + }; + + const getDataScope = async (page: number) => { + const res = await API.getDataScope(plugin, connectionId, { page, pageSize }); + setItems([ + ...items, + ...res.scopes.map((sc) => ({ + parentId: null, + id: getPluginScopeId(plugin, sc), + title: sc.name, + data: sc, + })), + ]); + if (page === 1) { + setTotal(res.count); + } + }; + + useEffect(() => { + getDataScope(page); + }, [page]); + const search = useDebounce(query, { wait: 500 }); - const { ready, data } = useRefreshData(() => API.getDataScope(plugin, connectionId), [version]); - const { ready: searchReady, data: searchItems } = useRefreshData<[{ name: string }]>( + const { ready, data } = useRefreshData( () => API.getDataScope(plugin, connectionId, { searchTerm: search }), [search], ); - const handleRefresh = () => setVersion((v) => v + 1); - - const handleSubmit = () => { - const scope = data.filter((it: any) => scopeIds.includes(getPluginScopeId(plugin, it))); - onSubmit?.(scope); - }; + const handleScroll = () => setPage(page + 1); - if (!ready || !data) { - return <PageLoading />; - } + const handleSubmit = () => onSubmit?.(selectedItems); return ( <FormItem label="Select Data Scope" subLabel={ - data.length ? ( + items.length ? ( <> 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{' '} @@ -93,7 +116,7 @@ export const DataScopeSelect = ({ } required > - {data.length ? ( + {items.length ? ( <S.Wrapper> {showWarning ? ( <Message @@ -109,48 +132,34 @@ export const DataScopeSelect = ({ /> ) : ( <Buttons> - <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data Scope" onClick={handleRefresh} /> + <Button intent={Intent.PRIMARY} icon="refresh" text="Refresh Data Scope" /> </Buttons> )} <div className="search"> <MultiSelector - loading={!searchReady} - items={searchItems ?? []} + loading={!ready} + items={data?.scopes ?? []} getName={(it: any) => it.name} getKey={(it) => getPluginScopeId(plugin, it)} noResult="No Data Scopes Available." onQueryChange={(query) => setQuery(query)} - selectedItems={data.filter((it: any) => scopeIds.includes(getPluginScopeId(plugin, it))) ?? []} - onChangeItems={(items) => setScopeIds(items.map((it: any) => getPluginScopeId(plugin, it)))} + selectedItems={selectedItems} + onChangeItems={setSelecteItems} /> </div> - <Table - noShadow - loading={!ready} - columns={[ - { - title: 'Data Scope', - dataIndex: 'name', - key: 'name', - }, - { - title: 'Scope Config', - dataIndex: 'scopeConfig', - key: 'scopeConfig', - render: (_, row) => (row.scopeConfigId ? row.scopeConfig?.name : 'N/A'), - }, - ]} - dataSource={data} - rowSelection={{ - getRowKey: (data) => getPluginScopeId(plugin, data), - type: 'checkbox', - selectedRowKeys: scopeIds as string[], - onChange: (selectedRowKeys) => setScopeIds(selectedRowKeys), - }} + <MillerColumnsSelect + showSelectAll + columnCount={1} + columnHeight={200} + items={items} + getHasMore={() => items.length < total} + onScroll={handleScroll} + selectedIds={selectedIds} + onSelectItemIds={handleChangeSelectItemsIds} /> <Buttons position="bottom" align="right"> <Button outlined intent={Intent.PRIMARY} text="Cancel" onClick={onCancel} /> - <Button disabled={!scopeIds.length} intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} /> + <Button disabled={!selectedItems.length} intent={Intent.PRIMARY} text="Save" onClick={handleSubmit} /> </Buttons> </S.Wrapper> ) : (
