This is an automated email from the ASF dual-hosted git repository. mintsweet pushed a commit to branch feat-6247 in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
commit 06923a212880ee4382d8fb7720be01a684e28530 Author: mintsweet <[email protected]> AuthorDate: Tue Nov 21 16:28:55 2023 +1300 feat(config-ui): use new connections page to replace old --- config-ui/src/app/routrer.tsx | 4 +- config-ui/src/pages/connection/home/index.tsx | 158 ------------------ config-ui/src/pages/connection/index.ts | 1 - config-ui/src/routes/connection/connections.tsx | 181 +++++++++++++++++++++ .../src/{pages => routes}/connection/index.ts | 3 +- .../home => routes/connection}/styled.ts | 49 ++++-- 6 files changed, 218 insertions(+), 178 deletions(-) diff --git a/config-ui/src/app/routrer.tsx b/config-ui/src/app/routrer.tsx index 8ca565965..f3bb7615a 100644 --- a/config-ui/src/app/routrer.tsx +++ b/config-ui/src/app/routrer.tsx @@ -19,7 +19,6 @@ import { createBrowserRouter, Navigate, json } from 'react-router-dom'; import { - ConnectionHomePage, ConnectionDetailPage, ProjectHomePage, ProjectDetailPage, @@ -29,6 +28,7 @@ import { } from '@/pages'; import { Layout, loader as layoutLoader } from '@/routes/layout'; import { Error, ErrorEnum } from '@/routes/error'; +import { Connections } from '@/routes/connection'; import { Pipelines, Pipeline } from '@/routes/pipeline'; import { ApiKeys } from '@/routes/api-keys'; @@ -53,7 +53,7 @@ export const router = createBrowserRouter([ }, { path: 'connections', - element: <ConnectionHomePage />, + element: <Connections />, }, { path: 'connections/:plugin/:id', diff --git a/config-ui/src/pages/connection/home/index.tsx b/config-ui/src/pages/connection/home/index.tsx deleted file mode 100644 index 75d18ccfd..000000000 --- a/config-ui/src/pages/connection/home/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { useState, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { Tag, Intent } from '@blueprintjs/core'; - -import { useAppSelector } from '@/app/hook'; -import { Dialog } from '@/components'; -import { selectPlugins, selectAllConnections, selectWebhooks } from '@/features/connections'; -import { getPluginConfig, ConnectionList, ConnectionForm } from '@/plugins'; - -import * as S from './styled'; - -export const ConnectionHomePage = () => { - const [type, setType] = useState<'list' | 'form'>(); - const [plugin, setPlugin] = useState(''); - - const plugins = useAppSelector(selectPlugins); - const connections = useAppSelector(selectAllConnections); - const webhooks = useAppSelector(selectWebhooks); - - const navigate = useNavigate(); - - const pluginConfig = useMemo(() => getPluginConfig(plugin), [plugin]); - - const handleShowListDialog = (plugin: string) => { - setType('list'); - setPlugin(plugin); - }; - - const handleShowFormDialog = () => { - setType('form'); - }; - - const handleHideDialog = () => { - setType(undefined); - setPlugin(''); - }; - - const handleSuccessAfter = async (plugin: string, id: ID) => { - navigate(`/connections/${plugin}/${id}`); - }; - - return ( - <S.Wrapper> - <div className="block"> - <h1>Connections</h1> - <h5> - Create and manage data connections from the following data sources or Webhooks to be used in syncing data in - your Projects. - </h5> - </div> - {!!plugins.filter((plugin) => plugin !== 'webhook').length && ( - <div className="block"> - <h2>Data Connections</h2> - <h5> - You can create and manage data connections for the following data sources and use them in your Projects. - </h5> - <ul> - {plugins - .filter((plugin) => plugin !== 'webhook') - .map((plugin) => { - const pluginConfig = getPluginConfig(plugin); - const connectionCount = connections.filter((cs) => cs.plugin === plugin).length; - return ( - <li key={plugin} onClick={() => handleShowListDialog(plugin)}> - {/* <img src={pluginConfig.icon} alt="" /> */} - <span className="name">{pluginConfig.name}</span> - <S.Count>{connectionCount ? `${connectionCount} connections` : 'No connection'}</S.Count> - {pluginConfig.isBeta && ( - <Tag intent={Intent.WARNING} round> - beta - </Tag> - )} - </li> - ); - })} - </ul> - </div> - )} - {plugins.includes('webhook') && ( - <div className="block"> - <h2>Webhooks</h2> - <h5> - You can use webhooks to import deployments and incidents from the unsupported data integrations to calculate - DORA metrics, etc. - </h5> - <ul> - {plugins - .filter((plugin) => plugin === 'webhook') - .map((plugin) => { - const pluginConfig = getPluginConfig(plugin); - const connectionCount = webhooks.length; - return ( - <li key={plugin} onClick={() => handleShowListDialog(plugin)}> - {/* <img src={pluginConfig.icon} alt="" /> */} - <span className="name">{pluginConfig.name}</span> - <S.Count>{connectionCount ? `${connectionCount} connections` : 'No connection'}</S.Count> - </li> - ); - })} - </ul> - </div> - )} - {type === 'list' && pluginConfig && ( - <Dialog - style={{ width: 820 }} - isOpen - title={ - <S.DialogTitle> - {/* <img src={pluginConfig.icon} alt="" /> */} - <span>Manage Connections: {pluginConfig.name}</span> - </S.DialogTitle> - } - footer={null} - onCancel={handleHideDialog} - > - <ConnectionList plugin={pluginConfig.plugin} onCreate={handleShowFormDialog} /> - </Dialog> - )} - {type === 'form' && pluginConfig && ( - <Dialog - style={{ width: 820 }} - isOpen - title={ - <S.DialogTitle> - {/* <img src={pluginConfig.icon} alt="" /> */} - <span>Manage Connections: {pluginConfig.name}</span> - </S.DialogTitle> - } - footer={null} - onCancel={handleHideDialog} - > - <ConnectionForm - plugin={pluginConfig.plugin} - onSuccess={(id) => handleSuccessAfter(pluginConfig.plugin, id)} - /> - </Dialog> - )} - </S.Wrapper> - ); -}; diff --git a/config-ui/src/pages/connection/index.ts b/config-ui/src/pages/connection/index.ts index d5131a681..d30c832b9 100644 --- a/config-ui/src/pages/connection/index.ts +++ b/config-ui/src/pages/connection/index.ts @@ -16,5 +16,4 @@ * */ -export * from './home'; export * from './detail'; diff --git a/config-ui/src/routes/connection/connections.tsx b/config-ui/src/routes/connection/connections.tsx new file mode 100644 index 000000000..7aa101030 --- /dev/null +++ b/config-ui/src/routes/connection/connections.tsx @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { theme, Badge, Modal } from 'antd'; +import { chunk } from 'lodash'; + +import { useAppSelector } from '@/app/hook'; +import { selectPlugins, selectAllConnections, selectWebhooks } from '@/features/connections'; +import { getPluginConfig, ConnectionList, ConnectionForm } from '@/plugins'; + +import * as S from './styled'; + +export const Connections = () => { + const [type, setType] = useState<'list' | 'form'>(); + const [plugin, setPlugin] = useState(''); + + const { + token: { colorPrimary }, + } = theme.useToken(); + + const pluginConfig = useMemo(() => getPluginConfig(plugin), [plugin]); + + const navigate = useNavigate(); + + const plugins = useAppSelector(selectPlugins); + const connections = useAppSelector(selectAllConnections); + const webhooks = useAppSelector(selectWebhooks); + + const [firstPlugins, secondPlugins] = chunk( + plugins.filter((p) => p !== 'webhook'), + 6, + ); + + const handleShowListDialog = (plugin: string) => { + setType('list'); + setPlugin(plugin); + }; + + const handleShowFormDialog = () => { + setType('form'); + }; + + const handleHideDialog = () => { + setType(undefined); + setPlugin(''); + }; + + const handleSuccessAfter = async (plugin: string, id: ID) => { + navigate(`/connections/${plugin}/${id}`); + }; + + return ( + <S.Wrapper theme={colorPrimary}> + <h1>Connections</h1> + <h5> + Create and manage data connections from the following data sources or Webhooks to be used in syncing data in + your Projects. + </h5> + <h2>Data Connections</h2> + <h5>You can create and manage data connections for the following data sources and use them in your Projects.</h5> + <h4>A-N</h4> + <ul> + {firstPlugins.map((plugin) => { + const pluginConfig = getPluginConfig(plugin); + const connectionCount = connections.filter((cs) => cs.plugin === plugin).length; + return ( + <li key={plugin} onClick={() => handleShowListDialog(plugin)}> + <span className="logo">{pluginConfig.icon({ color: colorPrimary })}</span> + <span className="name">{pluginConfig.name}</span> + <span className="count"> + {connectionCount ? ( + <Badge color={colorPrimary} text={`${connectionCount} connections`} /> + ) : ( + 'No connection' + )} + </span> + </li> + ); + })} + </ul> + <h4>O-Z</h4> + <ul> + {secondPlugins.map((plugin) => { + const pluginConfig = getPluginConfig(plugin); + const connectionCount = connections.filter((cs) => cs.plugin === plugin).length; + return ( + <li key={plugin} onClick={() => handleShowListDialog(plugin)}> + <span className="logo">{pluginConfig.icon({ color: colorPrimary })}</span> + <span className="name">{pluginConfig.name}</span> + <span className="count"> + {connectionCount ? ( + <Badge color={colorPrimary} text={`${connectionCount} connections`} /> + ) : ( + 'No connection' + )} + </span> + </li> + ); + })} + </ul> + <h2>Webhooks</h2> + <h5> + You can use webhooks to import deployments and incidents from the unsupported data integrations to calculate + DORA metrics, etc. + </h5> + <ul> + {plugins + .filter((plugin) => plugin === 'webhook') + .map((plugin) => { + const pluginConfig = getPluginConfig(plugin); + const connectionCount = webhooks.length; + return ( + <li key={plugin} onClick={() => handleShowListDialog(plugin)}> + <span className="logo">{pluginConfig.icon({ color: colorPrimary })}</span> + <span className="name">{pluginConfig.name}</span> + <span className="count"> + {connectionCount ? ( + <Badge color={colorPrimary} text={`${connectionCount} connections`} /> + ) : ( + 'No connection' + )} + </span> + </li> + ); + })} + </ul> + {type === 'list' && pluginConfig && ( + <Modal + open + width={820} + title={ + <S.ModalTitle> + <span className="icon">{pluginConfig.icon({ color: colorPrimary })}</span> + <span className="name">Manage Connections: {pluginConfig.name}</span> + </S.ModalTitle> + } + footer={null} + onCancel={handleHideDialog} + > + <ConnectionList plugin={pluginConfig.plugin} onCreate={handleShowFormDialog} /> + </Modal> + )} + {type === 'form' && pluginConfig && ( + <Modal + open + width={820} + title={ + <S.ModalTitle> + <span className="icon">{pluginConfig.icon({ color: colorPrimary })}</span> + <span className="name">Manage Connections: {pluginConfig.name}</span> + </S.ModalTitle> + } + footer={null} + onCancel={handleHideDialog} + > + <ConnectionForm + plugin={pluginConfig.plugin} + onSuccess={(id) => handleSuccessAfter(pluginConfig.plugin, id)} + /> + </Modal> + )} + </S.Wrapper> + ); +}; diff --git a/config-ui/src/pages/connection/index.ts b/config-ui/src/routes/connection/index.ts similarity index 94% copy from config-ui/src/pages/connection/index.ts copy to config-ui/src/routes/connection/index.ts index d5131a681..4e6e8a4de 100644 --- a/config-ui/src/pages/connection/index.ts +++ b/config-ui/src/routes/connection/index.ts @@ -16,5 +16,4 @@ * */ -export * from './home'; -export * from './detail'; +export * from './connections'; diff --git a/config-ui/src/pages/connection/home/styled.ts b/config-ui/src/routes/connection/styled.ts similarity index 78% rename from config-ui/src/pages/connection/home/styled.ts rename to config-ui/src/routes/connection/styled.ts index 6c5cf8c0b..42aa8a6fb 100644 --- a/config-ui/src/pages/connection/home/styled.ts +++ b/config-ui/src/routes/connection/styled.ts @@ -18,16 +18,30 @@ import styled from 'styled-components'; -export const Wrapper = styled.div` +export const Wrapper = styled.div<{ theme: string }>` + h2 { + margin-top: 36px; + } + h5 { margin-top: 12px; font-weight: 400; } - .block + .block { - margin-top: 36px; - } + h4 { + position: relative; + margin-top: 24px; + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 48px; + height: 4px; + background-color: ${({ theme }) => theme}; + } + } ul { display: flex; align-items: center; @@ -53,9 +67,15 @@ export const Wrapper = styled.div` background-color: #eeeeee; } - & > img { + & > .logo { width: 60px; + height: 60px; margin-bottom: 8px; + + & > svg { + width: 100%; + height: 100%; + } } & > .name { @@ -75,24 +95,23 @@ export const Wrapper = styled.div` } } - & > .bp5-tag { - position: absolute; - top: 0; - right: 0; + & > .count { + color: #70727f; } } `; -export const Count = styled.span` - color: #70727f; -`; - -export const DialogTitle = styled.div` +export const ModalTitle = styled.div` display: flex; align-items: center; - img { + .icon { margin-right: 8px; width: 24px; + + & > svg { + width: 100%; + height: 100%; + } } `;
