This is an automated email from the ASF dual-hosted git repository. mintsweet pushed a commit to branch feat-7407 in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
commit 5175a054638826b06531edd97256289d50dcbf9a Author: mintsweet <[email protected]> AuthorDate: Mon May 6 15:27:56 2024 +1200 feat: add some tips when updating scope config --- config-ui/src/api/scope-config/index.ts | 5 ++ .../src/api/scope-config/{index.ts => types.ts} | 25 ++------ .../src/plugins/components/scope-config/index.tsx | 75 +++++++++++++++++----- .../routes/blueprint/connection-detail/index.tsx | 3 +- config-ui/src/routes/connection/connection.tsx | 50 +++++++++++++-- 5 files changed, 117 insertions(+), 41 deletions(-) diff --git a/config-ui/src/api/scope-config/index.ts b/config-ui/src/api/scope-config/index.ts index caaa789d7..41547d9c0 100644 --- a/config-ui/src/api/scope-config/index.ts +++ b/config-ui/src/api/scope-config/index.ts @@ -18,6 +18,8 @@ import { request } from '@/utils'; +import { ICheck } from './types'; + export const list = (plugin: string, connectionId: ID) => request(`/plugins/${plugin}/connections/${connectionId}/scope-configs`); @@ -35,3 +37,6 @@ export const update = (plugin: string, connectionId: ID, id: ID, data: any) => method: 'patch', data, }); + +export const check = (plugin: string, id: ID): Promise<ICheck> => + request(`/plugins/${plugin}/scope-config/${id}/projects`); diff --git a/config-ui/src/api/scope-config/index.ts b/config-ui/src/api/scope-config/types.ts similarity index 53% copy from config-ui/src/api/scope-config/index.ts copy to config-ui/src/api/scope-config/types.ts index caaa789d7..4c779c658 100644 --- a/config-ui/src/api/scope-config/index.ts +++ b/config-ui/src/api/scope-config/types.ts @@ -16,22 +16,9 @@ * */ -import { request } from '@/utils'; - -export const list = (plugin: string, connectionId: ID) => - request(`/plugins/${plugin}/connections/${connectionId}/scope-configs`); - -export const get = (plugin: string, connectionId: ID, id: ID) => - request(`/plugins/${plugin}/connections/${connectionId}/scope-configs/${id}`); - -export const create = (plugin: string, connectionId: ID, data: any) => - request(`/plugins/${plugin}/connections/${connectionId}/scope-configs`, { - method: 'post', - data, - }); - -export const update = (plugin: string, connectionId: ID, id: ID, data: any) => - request(`/plugins/${plugin}/connections/${connectionId}/scope-configs/${id}`, { - method: 'patch', - data, - }); +export type ICheck = { + count: number; + projects: Array<{ + name: string; + }>; +}; diff --git a/config-ui/src/plugins/components/scope-config/index.tsx b/config-ui/src/plugins/components/scope-config/index.tsx index 7dd5b7e05..0bc83870d 100644 --- a/config-ui/src/plugins/components/scope-config/index.tsx +++ b/config-ui/src/plugins/components/scope-config/index.tsx @@ -18,10 +18,11 @@ import { useState } from 'react'; import { LinkOutlined, EditOutlined } from '@ant-design/icons'; -import { Button, Modal } from 'antd'; +import { theme, Button, Modal, Flex, Space } from 'antd'; import styled from 'styled-components'; import API from '@/api'; +import { Message } from '@/components'; import { operator } from '@/utils'; import { PluginName } from '../plugin-name'; @@ -34,16 +35,39 @@ interface Props { plugin: string; connectionId: ID; scopeId: ID; + scopeName: string; id?: ID; name?: string; - onSuccess?: () => void; + onSuccess?: (id?: ID) => void; } -export const ScopeConfig = ({ plugin, connectionId, scopeId, id, name, onSuccess }: Props) => { - const [type, setType] = useState<'associate' | 'update'>(); +export const ScopeConfig = ({ plugin, connectionId, scopeId, scopeName, id, name, onSuccess }: Props) => { + const [type, setType] = useState<'associate' | 'update' | 'relatedProjects'>(); + const [relatedProjects, setRelatedProjects] = useState<Array<{ name: string; scopes: string[] }>>([]); + + const { + token: { colorPrimary }, + } = theme.useToken(); const handleHideDialog = () => setType(undefined); + const handleCheckScopeConfig = async () => { + if (!id) return; + + const [success, res] = await operator(() => API.scopeConfig.check(plugin, id), { hideToast: true }); + + if (success) { + const projects = res.projects.map((it: any) => ({ name: it.name, scopes: [] })); + + if (projects.length !== 1) { + setRelatedProjects(projects); + setType('relatedProjects'); + } else { + setType('update'); + } + } + }; + const handleAssociate = async (trId: ID) => { const [success] = await operator( () => API.scope.update(plugin, connectionId, scopeId, { scopeConfigId: trId !== 'None' ? +trId : null }), @@ -54,13 +78,13 @@ export const ScopeConfig = ({ plugin, connectionId, scopeId, id, name, onSuccess if (success) { handleHideDialog(); - onSuccess?.(); + onSuccess?.(id); } }; const handleUpdate = (trId: ID) => { handleHideDialog(); - onSuccess?.(); + onSuccess?.(id); }; return ( @@ -74,17 +98,7 @@ export const ScopeConfig = ({ plugin, connectionId, scopeId, id, name, onSuccess setType('associate'); }} /> - {id && ( - <Button - size="small" - type="link" - icon={<EditOutlined />} - onClick={() => { - // TO-DO: check if the scope config is associated with any scope - setType('update'); - }} - /> - )} + {id && <Button size="small" type="link" icon={<EditOutlined />} onClick={handleCheckScopeConfig} />} {type === 'associate' && ( <Modal open @@ -127,6 +141,33 @@ export const ScopeConfig = ({ plugin, connectionId, scopeId, id, name, onSuccess /> </Modal> )} + {type === 'relatedProjects' && ( + <Modal + open + width={830} + centered + footer={null} + title={`Edit '${name}' for '${scopeName}'`} + onCancel={handleHideDialog} + > + <Message content="The change will apply to all following projects:" /> + <ul style={{ marginTop: 15, marginLeft: 30 }}> + {relatedProjects.map((it) => ( + <li style={{ color: colorPrimary }}> + {it.name}:{it.scopes.join(',')} + </li> + ))} + </ul> + <Flex justify="end"> + <Space> + <Button onClick={handleHideDialog}>Cancel</Button> + <Button type="primary" onClick={() => setType('update')}> + Continue + </Button> + </Space> + </Flex> + </Modal> + )} </Wrapper> ); }; diff --git a/config-ui/src/routes/blueprint/connection-detail/index.tsx b/config-ui/src/routes/blueprint/connection-detail/index.tsx index b686d9e81..08c8d8a06 100644 --- a/config-ui/src/routes/blueprint/connection-detail/index.tsx +++ b/config-ui/src/routes/blueprint/connection-detail/index.tsx @@ -263,11 +263,12 @@ export const BlueprintConnectionDetailPage = () => { { title: 'Scope Config', key: 'scopeConfig', - render: (_, { id, scopeConfigId, scopeConfigName }) => ( + render: (_, { id, name, scopeConfigId, scopeConfigName }) => ( <ScopeConfig plugin={plugin} connectionId={connectionId} scopeId={id} + scopeName={name} id={scopeConfigId} name={scopeConfigName} onSuccess={handleChangeScopeConfig} diff --git a/config-ui/src/routes/connection/connection.tsx b/config-ui/src/routes/connection/connection.tsx index 8b4913903..ba6d5d66a 100644 --- a/config-ui/src/routes/connection/connection.tsx +++ b/config-ui/src/routes/connection/connection.tsx @@ -69,10 +69,13 @@ export const Connection = () => { token: { colorPrimary }, } = theme.useToken(); + const [modal, contextHolder] = Modal.useModal(); + const dispatch = useAppDispatch(); const connection = useAppSelector((state) => selectConnection(state, `${plugin}-${connectionId}`)) as IConnection; const navigate = useNavigate(); + const { ready, data } = useRefreshData( () => API.scope.list(plugin, connectionId, { page, pageSize, blueprints: true }), [version, page, pageSize], @@ -227,9 +230,46 @@ export const Connection = () => { } }; - const handleScopeConfigChange = () => { - // TO-DO: check scope config change will effect the scope config - setVersion(version + 1); + const handleScopeConfigChange = async (scopeConfigId?: ID) => { + if (!scopeConfigId) { + return; + } + + const [success, res] = await operator(() => API.scopeConfig.check(plugin, scopeConfigId), { hideToast: true }); + + if (success) { + modal.success({ + closable: true, + centered: true, + width: 830, + title: 'Scope Config Saved', + content: ( + <> + <div style={{ marginBottom: 16 }}> + The listed projects are impacted. Please re-transform the data to apply the updated scope config. + </div> + <ul> + {res.projects.map((it: any) => ( + <li style={{ marginBottom: 10 }}> + <Space> + <span>{it.name}</span> + <Button + size="small" + type="link" + onClick={() => navigate(PATHS.PROJECT(it.name, { tab: 'status' }))} + > + Re-transform Data + </Button> + </Space> + </li> + ))} + </ul> + </> + ), + footer: null, + onCancel: () => setVersion(version + 1), + }); + } }; return ( @@ -305,11 +345,12 @@ export const Connection = () => { title: 'Scope Config', key: 'scopeConfig', width: 400, - render: (_, { id, configId, configName }) => ( + render: (_, { id, name, configId, configName }) => ( <ScopeConfig plugin={plugin} connectionId={connectionId} scopeId={id} + scopeName={name} id={configId} name={configName} onSuccess={handleScopeConfigChange} @@ -515,6 +556,7 @@ export const Connection = () => { )} </Modal> )} + {contextHolder} </PageHeader> ); };
