This is an automated email from the ASF dual-hosted git repository. mintsweet pushed a commit to branch feat-8017 in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
commit f8b4efc4d97704aa42df9c897f1f5937bf7c2ed7 Author: mintsweet <[email protected]> AuthorDate: Wed Sep 25 15:54:48 2024 +1200 feat: add connection token check before collect data --- config-ui/src/api/project/index.ts | 11 +++- .../src/routes/blueprint/detail/status-panel.tsx | 75 ++++++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/config-ui/src/api/project/index.ts b/config-ui/src/api/project/index.ts index 8e79ee5fa..bc27b5e00 100644 --- a/config-ui/src/api/project/index.ts +++ b/config-ui/src/api/project/index.ts @@ -24,7 +24,16 @@ export const list = (data: Pagination & { keyword?: string }): Promise<{ count: export const get = (name: string): Promise<IProject> => request(`/projects/${encodeURIComponent(name)}`); -export const checkName = (name: string) => request(`/projects/${encodeURIComponent(name)}/check`); +export const check = ( + name: string, + data?: { check_token: 1 }, +): Promise<{ + exist: boolean; + tokens: Array<{ pluginName: string; connectionId: ID; success: boolean }>; +}> => + request(`/projects/${encodeURIComponent(name)}/check`, { + data, + }); export const create = (data: Pick<IProject, 'name' | 'description' | 'metrics'>) => request('/projects', { diff --git a/config-ui/src/routes/blueprint/detail/status-panel.tsx b/config-ui/src/routes/blueprint/detail/status-panel.tsx index 09d2fdf25..f9aaccc38 100644 --- a/config-ui/src/routes/blueprint/detail/status-panel.tsx +++ b/config-ui/src/routes/blueprint/detail/status-panel.tsx @@ -17,14 +17,15 @@ */ import { useState, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { MoreOutlined, DeleteOutlined } from '@ant-design/icons'; -import { Card, Modal, Switch, Button, Tooltip, Dropdown, Flex, Space } from 'antd'; +import { useNavigate, Link } from 'react-router-dom'; +import { MoreOutlined, DeleteOutlined, WarningOutlined } from '@ant-design/icons'; +import { theme, Card, Modal, Switch, Button, Tooltip, Dropdown, Flex, Space } from 'antd'; import API from '@/api'; import { Message } from '@/components'; import { getCron } from '@/config'; -import { useRefreshData } from '@/hooks'; +import { selectAllConnections } from '@/features/connections'; +import { useAppSelector, useRefreshData } from '@/hooks'; import { PipelineInfo, PipelineTasks, PipelineTable } from '@/routes/pipeline'; import { IBlueprint } from '@/types'; import { formatTime, operator } from '@/utils'; @@ -39,13 +40,22 @@ interface Props { } export const StatusPanel = ({ from, blueprint, pipelineId, onRefresh }: Props) => { - const [type, setType] = useState<'delete' | 'fullSync'>(); + const [type, setType] = useState<'delete' | 'fullSync' | 'checkTokenFailed'>(); const [page, setPage] = useState(1); const [pageSize] = useState(10); const [operating, setOperating] = useState(false); + const [connectionFailed, setConnectionFailed] = useState< + Array<{ unique: string; name: string; plugin: string; connectionId: ID }> + >([]); const navigate = useNavigate(); + const { + token: { orange5 }, + } = theme.useToken(); + + const connections = useAppSelector(selectAllConnections); + const cron = useMemo(() => getCron(blueprint.isManual, blueprint.cronConfig), [blueprint]); const { ready, data } = useRefreshData( @@ -64,6 +74,32 @@ export const StatusPanel = ({ from, blueprint, pipelineId, onRefresh }: Props) = skipCollectors?: boolean; fullSync?: boolean; }) => { + if (!skipCollectors && from === FromEnum.project) { + const [success, res] = await operator(() => API.project.check(blueprint.projectName, { check_token: 1 }), { + hideToast: true, + setOperating, + }); + + if (success && res.tokens.length) { + const connectionFailed = res.tokens + .filter((token: any) => !token.success) + .map((it: any) => { + const unique = `${it.pluginName}-${it.connectionId}`; + const connection = connections.find((c) => c.unique === unique); + return { + unique, + name: connection?.name ?? '', + plugin: it.pluginName, + connectionId: it.connectionId, + }; + }); + + setType('checkTokenFailed'); + setConnectionFailed(connectionFailed); + return; + } + } + const [success] = await operator(() => API.blueprint.trigger(blueprint.id, { skipCollectors, fullSync }), { setOperating, formatMessage: () => 'Trigger blueprint successful.', @@ -245,6 +281,35 @@ export const StatusPanel = ({ from, blueprint, pipelineId, onRefresh }: Props) = <Message content="This operation may take a long time as it will empty all of your existing data and re-collect it." /> </Modal> )} + + {type === 'checkTokenFailed' && ( + <Modal + open + title={ + <> + <WarningOutlined style={{ marginRight: 8, fontSize: 20, color: orange5 }} /> + <span>Invalid Token(s) Detected</span> + </> + } + width={820} + footer={null} + onCancel={() => { + handleResetType(); + setConnectionFailed([]); + }} + > + <p>There are invalid tokens in the following connections. Please update them before re-syncing the data.</p> + <ul style={{ paddingLeft: 20 }}> + {connectionFailed.map((it) => ( + <li key={it.unique} style={{ listStyle: 'initial' }}> + <Link to={`/connections/${it.plugin}/${it.connectionId}`} target="_blank"> + {it.name} + </Link> + </li> + ))} + </ul> + </Modal> + )} </Flex> ); };
