You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by mi...@apache.org on 2024/09/05 03:32:35 UTC
(incubator-devlake) branch feat-project-layout created (now 078e78ea2)
This is an automated email from the ASF dual-hosted git repository.
mintsweet pushed a change to branch feat-project-layout
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
at 078e78ea2 feat: use new project detail layout
This branch includes the following new commits:
new 078e78ea2 feat: use new project detail layout
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(incubator-devlake) 01/01: feat: use new project detail layout
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
mintsweet pushed a commit to branch feat-project-layout
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
commit 078e78ea299c800fa2b974e4cd3597bc5155e718
Author: mintsweet <0x...@gmail.com>
AuthorDate: Thu Sep 5 15:32:16 2024 +1200
feat: use new project detail layout
---
config-ui/src/api/project/index.ts | 2 +-
config-ui/src/app/routrer.tsx | 35 ++++--
config-ui/src/hooks/index.ts | 1 +
.../styled.ts => hooks/use-outside-click.ts} | 22 ++--
.../routes/blueprint/detail/blueprint-detail.tsx | 1 -
.../index.tsx} | 36 +++---
config-ui/src/routes/project/detail/index.tsx | 98 -----------------
.../styled.ts => general-settings/index.tsx} | 22 +++-
config-ui/src/routes/project/index.ts | 5 +-
config-ui/src/routes/project/layout/index.tsx | 102 +++++++++++++++++
.../src/routes/project/layout/project-selector.tsx | 122 +++++++++++++++++++++
.../routes/project/{detail => layout}/styled.ts | 52 ++++++++-
.../webhooks-panel.tsx => webhook/index.tsx} | 60 +++++-----
13 files changed, 393 insertions(+), 165 deletions(-)
diff --git a/config-ui/src/api/project/index.ts b/config-ui/src/api/project/index.ts
index 52e3d2351..6023ad8da 100644
--- a/config-ui/src/api/project/index.ts
+++ b/config-ui/src/api/project/index.ts
@@ -20,7 +20,7 @@ import type { IProject } from '@/types';
import { encodeName } from '@/routes';
import { request } from '@/utils';
-export const list = (data: Pagination): Promise<{ count: number; projects: IProject[] }> =>
+export const list = (data: Pagination & { keyword?: string }): Promise<{ count: number; projects: IProject[] }> =>
request('/projects', { data });
export const get = (name: string): Promise<IProject> => request(`/projects/${encodeName(name)}`);
diff --git a/config-ui/src/app/routrer.tsx b/config-ui/src/app/routrer.tsx
index ac3113db7..a11f599b9 100644
--- a/config-ui/src/app/routrer.tsx
+++ b/config-ui/src/app/routrer.tsx
@@ -27,7 +27,10 @@ import {
Connections,
Connection,
ProjectHomePage,
- ProjectDetailPage,
+ ProjectLayout,
+ ProjectGeneralSettings,
+ ProjectWebhook,
+ ProjectAdditionalSettings,
BlueprintHomePage,
BlueprintDetailPage,
BlueprintConnectionDetailPage,
@@ -52,6 +55,28 @@ export const router = createBrowserRouter([
path: `${PATH_PREFIX}/onboard`,
element: <Onboard />,
},
+ {
+ path: `${PATH_PREFIX}/projects/:pname`,
+ element: <ProjectLayout />,
+ children: [
+ {
+ index: true,
+ element: <Navigate to="general-settings" />,
+ },
+ {
+ path: 'general-settings',
+ element: <ProjectGeneralSettings />,
+ },
+ {
+ path: 'webhooks',
+ element: <ProjectWebhook />,
+ },
+ {
+ path: 'additional-settings',
+ element: <ProjectAdditionalSettings />,
+ },
+ ],
+ },
{
path: `${PATH_PREFIX}`,
element: <Layout />,
@@ -66,14 +91,6 @@ export const router = createBrowserRouter([
path: 'projects',
element: <ProjectHomePage />,
},
- {
- path: 'projects/:pname',
- element: <ProjectDetailPage />,
- },
- {
- path: 'projects/:pname/:unique',
- element: <BlueprintConnectionDetailPage />,
- },
{
path: 'connections',
element: <Connections />,
diff --git a/config-ui/src/hooks/index.ts b/config-ui/src/hooks/index.ts
index 076b27375..1d6200f14 100644
--- a/config-ui/src/hooks/index.ts
+++ b/config-ui/src/hooks/index.ts
@@ -18,5 +18,6 @@
export * from './extend-redux';
export * from './use-auto-refresh';
+export * from './use-outside-click';
export * from './use-refresh-data';
export * from './user-proxy-prefix';
diff --git a/config-ui/src/routes/project/detail/styled.ts b/config-ui/src/hooks/use-outside-click.ts
similarity index 60%
copy from config-ui/src/routes/project/detail/styled.ts
copy to config-ui/src/hooks/use-outside-click.ts
index f2ac11cfb..f1eb4fe7e 100644
--- a/config-ui/src/routes/project/detail/styled.ts
+++ b/config-ui/src/hooks/use-outside-click.ts
@@ -16,11 +16,19 @@
*
*/
-import styled from 'styled-components';
+import type { MutableRefObject } from 'react';
+import { useEffect } from 'react';
-export const Wrapper = styled.div``;
-
-export const DialogBody = styled.div`
- display: flex;
- align-items: center;
-`;
+export const useOutsideClick = (ref: MutableRefObject<HTMLDivElement | null>, cb: () => void) => {
+ useEffect(() => {
+ function handleClickOutside(event: any) {
+ if (ref.current && !ref.current.contains(event.target)) {
+ cb();
+ }
+ }
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [ref, cb]);
+};
diff --git a/config-ui/src/routes/blueprint/detail/blueprint-detail.tsx b/config-ui/src/routes/blueprint/detail/blueprint-detail.tsx
index 78a7b6e4f..b0b52cd11 100644
--- a/config-ui/src/routes/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/routes/blueprint/detail/blueprint-detail.tsx
@@ -67,7 +67,6 @@ export const BlueprintDetail = ({ id, from }: Props) => {
return (
<S.Wrapper>
<Tabs
- centered
items={[
{
key: 'status',
diff --git a/config-ui/src/routes/project/detail/settings-panel.tsx b/config-ui/src/routes/project/additional-settings/index.tsx
similarity index 92%
rename from config-ui/src/routes/project/detail/settings-panel.tsx
rename to config-ui/src/routes/project/additional-settings/index.tsx
index 5f3ce4048..11e72c074 100644
--- a/config-ui/src/routes/project/detail/settings-panel.tsx
+++ b/config-ui/src/routes/project/additional-settings/index.tsx
@@ -17,25 +17,18 @@
*/
import { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useParams, useNavigate } from 'react-router-dom';
import { Flex, Space, Card, Modal, Input, Checkbox, Button } from 'antd';
import API from '@/api';
import { Block, HelpTooltip, Message } from '@/components';
import { PATHS } from '@/config';
-import { IProject } from '@/types';
+import { useRefreshData } from '@/hooks';
import { operator } from '@/utils';
-import * as S from './styled';
-
const RegexPrIssueDefaultValue = '(?mi)(Closes)[\\s]*.*(((and )?#\\d+[ ]*)+)';
-interface Props {
- project: IProject;
- onRefresh: () => void;
-}
-
-export const SettingsPanel = ({ project, onRefresh }: Props) => {
+export const ProjectAdditionalSettings = () => {
const [name, setName] = useState('');
const [dora, setDora] = useState({
enable: false,
@@ -49,10 +42,19 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
});
const [operating, setOperating] = useState(false);
const [open, setOpen] = useState(false);
+ const [version, setVersion] = useState(0);
const navigate = useNavigate();
+ const { pname } = useParams() as { pname: string };
+
+ const { data: project } = useRefreshData(() => API.project.get(pname), [pname, version]);
+
useEffect(() => {
+ if (!project) {
+ return;
+ }
+
const dora = project.metrics.find((ms) => ms.pluginName === 'dora');
const linker = project.metrics.find((ms) => ms.pluginName === 'linker');
const issueTrace = project.metrics.find((ms) => ms.pluginName === 'issue_trace');
@@ -71,6 +73,10 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
}, [project]);
const handleUpdate = async () => {
+ if (!project) {
+ return;
+ }
+
const [success] = await operator(
() =>
API.project.update(project.name, {
@@ -102,7 +108,7 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
);
if (success) {
- onRefresh();
+ setVersion((v) => v + 1);
navigate(PATHS.PROJECT(name), {
state: {
tabId: 'settings',
@@ -120,6 +126,10 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
};
const handleDelete = async () => {
+ if (!project) {
+ return;
+ }
+
const [success] = await operator(() => API.project.remove(project.name), {
setOperating,
formatMessage: () => 'Delete project successful.',
@@ -215,9 +225,9 @@ export const SettingsPanel = ({ project, onRefresh }: Props) => {
onCancel={handleHideDeleteDialog}
onOk={handleDelete}
>
- <S.DialogBody>
+ <Flex align="center">
<Message content="This operation cannot be undone. Deleting a Data Connection will delete all data that have been collected in this Connection." />
- </S.DialogBody>
+ </Flex>
</Modal>
</Flex>
);
diff --git a/config-ui/src/routes/project/detail/index.tsx b/config-ui/src/routes/project/detail/index.tsx
deleted file mode 100644
index fb37a68eb..000000000
--- a/config-ui/src/routes/project/detail/index.tsx
+++ /dev/null
@@ -1,98 +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 { useEffect, useState } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
-import { Helmet } from 'react-helmet';
-import { Tabs } from 'antd';
-
-import API from '@/api';
-import { PageHeader, PageLoading } from '@/components';
-import { PATHS } from '@/config';
-import { useRefreshData } from '@/hooks';
-import { BlueprintDetail, FromEnum } from '@/routes';
-
-import { WebhooksPanel } from './webhooks-panel';
-import { SettingsPanel } from './settings-panel';
-import * as S from './styled';
-
-const brandName = import.meta.env.DEVLAKE_BRAND_NAME ?? 'DevLake';
-
-export const ProjectDetailPage = () => {
- const [version, setVersion] = useState(1);
- const [tabId, setTabId] = useState('blueprint');
-
- const { pname } = useParams() as { pname: string };
- const { state } = useLocation();
-
- useEffect(() => {
- setTabId(state?.tabId ?? 'blueprint');
- }, [state]);
-
- const { ready, data } = useRefreshData(() => API.project.get(pname), [pname, version]);
-
- const handleChangeTabId = (tabId: string) => {
- setTabId(tabId);
- };
-
- const handleRefresh = () => {
- setVersion((v) => v + 1);
- };
-
- if (!ready || !data) {
- return <PageLoading />;
- }
-
- return (
- <PageHeader
- breadcrumbs={[
- { name: 'Projects', path: PATHS.PROJECTS() },
- { name: data.name, path: PATHS.PROJECT(pname) },
- ]}
- >
- <Helmet>
- <title>
- {data.name} - {brandName}
- </title>
- </Helmet>
- <S.Wrapper>
- <Tabs
- items={[
- {
- key: 'blueprint',
- label: 'Blueprint',
- children: <BlueprintDetail id={data.blueprint.id} from={FromEnum.project} />,
- },
- {
- key: 'webhook',
- label: 'Webhooks',
- children: <WebhooksPanel project={data} onRefresh={handleRefresh} />,
- },
- {
- key: 'settings',
- label: 'Settings',
- children: <SettingsPanel project={data} onRefresh={handleRefresh} />,
- },
- ]}
- activeKey={tabId}
- onChange={handleChangeTabId}
- />
- </S.Wrapper>
- </PageHeader>
- );
-};
diff --git a/config-ui/src/routes/project/detail/styled.ts b/config-ui/src/routes/project/general-settings/index.tsx
similarity index 59%
copy from config-ui/src/routes/project/detail/styled.ts
copy to config-ui/src/routes/project/general-settings/index.tsx
index f2ac11cfb..ef538afcc 100644
--- a/config-ui/src/routes/project/detail/styled.ts
+++ b/config-ui/src/routes/project/general-settings/index.tsx
@@ -16,11 +16,21 @@
*
*/
-import styled from 'styled-components';
+import { useParams } from 'react-router-dom';
-export const Wrapper = styled.div``;
+import API from '@/api';
+import { PageLoading } from '@/components';
+import { useRefreshData } from '@/hooks';
+import { BlueprintDetail, FromEnum } from '@/routes';
-export const DialogBody = styled.div`
- display: flex;
- align-items: center;
-`;
+export const ProjectGeneralSettings = () => {
+ const { pname } = useParams() as { pname: string };
+
+ const { ready, data: project } = useRefreshData(() => API.project.get(pname), [pname]);
+
+ if (!ready || !project) {
+ return <PageLoading />;
+ }
+
+ return <BlueprintDetail id={project.blueprint.id} from={FromEnum.project} />;
+};
diff --git a/config-ui/src/routes/project/index.ts b/config-ui/src/routes/project/index.ts
index de11b8b83..59597a4c7 100644
--- a/config-ui/src/routes/project/index.ts
+++ b/config-ui/src/routes/project/index.ts
@@ -16,6 +16,9 @@
*
*/
+export * from './additional-settings';
export * from './utils';
export * from './home';
-export * from './detail';
+export * from './general-settings';
+export * from './layout';
+export * from './webhook';
diff --git a/config-ui/src/routes/project/layout/index.tsx b/config-ui/src/routes/project/layout/index.tsx
new file mode 100644
index 000000000..579fc16df
--- /dev/null
+++ b/config-ui/src/routes/project/layout/index.tsx
@@ -0,0 +1,102 @@
+/*
+ * 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 { useMemo } from 'react';
+import { useParams, useNavigate, useLocation, Outlet } from 'react-router-dom';
+import { RollbackOutlined } from '@ant-design/icons';
+import { Layout, Menu } from 'antd';
+
+import { PageHeader } from '@/components';
+import { PATHS } from '@/config';
+
+import { ProjectSelector } from './project-selector';
+import * as S from './styled';
+
+const { Sider, Content } = Layout;
+
+const items = [
+ {
+ key: 'general-settings',
+ label: 'General Settings',
+ style: {
+ paddingLeft: 8,
+ },
+ },
+ {
+ key: 'webhooks',
+ label: 'Webhooks',
+ style: {
+ paddingLeft: 8,
+ },
+ },
+ {
+ key: 'additional-settings',
+ label: 'Additional Settings',
+ style: {
+ paddingLeft: 8,
+ },
+ },
+];
+
+export const ProjectLayout = () => {
+ const { pname } = useParams() as { pname: string };
+ const navigate = useNavigate();
+ const { pathname } = useLocation();
+
+ const { selectedKeys, breadcrumbs } = useMemo(() => {
+ const key = pathname.split('/').pop();
+ const item = items.find((i) => i.key === key);
+
+ return {
+ selectedKeys: key ? [key] : [],
+ breadcrumbs: [
+ {
+ name: item?.label ?? '',
+ path: '',
+ },
+ ],
+ };
+ }, [pathname]);
+
+ return (
+ <Layout style={{ height: '100%', overflow: 'hidden' }}>
+ <Sider width={240} style={{ padding: '36px 12px', backgroundColor: '#F9F9FA', borderRight: '1px solid #E7E9F3' }}>
+ <S.Top onClick={() => navigate(PATHS.PROJECTS())}>
+ <RollbackOutlined />
+ <span className="back">Back to Projects</span>
+ </S.Top>
+ <ProjectSelector name={pname} />
+ <Menu
+ mode="inline"
+ style={{ backgroundColor: '#F9F9FA', border: 'none' }}
+ items={items}
+ selectedKeys={selectedKeys}
+ onClick={({ key }) => navigate(`${PATHS.PROJECT(pname)}/${key}`)}
+ />
+ </Sider>
+ <Layout>
+ <Content style={{ padding: '36px 48px', overflowY: 'auto' }}>
+ <p>Configurations / Projects / {pname} /</p>
+ <PageHeader breadcrumbs={breadcrumbs}>
+ <Outlet />
+ </PageHeader>
+ </Content>
+ </Layout>
+ </Layout>
+ );
+};
diff --git a/config-ui/src/routes/project/layout/project-selector.tsx b/config-ui/src/routes/project/layout/project-selector.tsx
new file mode 100644
index 000000000..bcfffd615
--- /dev/null
+++ b/config-ui/src/routes/project/layout/project-selector.tsx
@@ -0,0 +1,122 @@
+/*
+ * 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, useEffect, useReducer, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { CaretDownOutlined } from '@ant-design/icons';
+import { Input, Button, Flex } from 'antd';
+import { useDebounce } from 'ahooks';
+
+import { PageLoading } from '@/components';
+import { PATHS } from '@/config';
+import { useOutsideClick } from '@/hooks';
+import { operator } from '@/utils';
+
+import API from '@/api';
+
+import * as S from './styled';
+
+type StateType = { name: string }[];
+
+const reducer = (state: StateType, action: { type: string; payload: StateType }) => {
+ switch (action.type) {
+ case 'RESET':
+ return [...action.payload];
+ case 'APPEND':
+ return [...state, ...action.payload];
+ default:
+ return state;
+ }
+};
+
+interface Props {
+ name: string;
+}
+
+export const ProjectSelector = ({ name }: Props) => {
+ const [open, setOpen] = useState(false);
+ const [total, setTotal] = useState(0);
+ const [page, setPage] = useState(1);
+ const [pageSize] = useState(10);
+ const [keyword, setKeyword] = useState('');
+ const [operating, setOperating] = useState(false);
+
+ const [state, dispatch] = useReducer(reducer, []);
+
+ const ref = useRef(null);
+
+ useOutsideClick(ref, () => setOpen(false));
+
+ const navigate = useNavigate();
+
+ const keywordDebounce = useDebounce(keyword, { wait: 500 });
+
+ useEffect(() => {
+ setOperating(true);
+ (async () => {
+ const res = await API.project.list({ page: 1, pageSize, keyword: keywordDebounce });
+ dispatch({ type: 'RESET', payload: res.projects });
+ setTotal(res.count);
+ setOperating(false);
+ })();
+ }, [keywordDebounce]);
+
+ const handleAppend = async () => {
+ const [success, res] = await operator(() => API.project.list({ page: page + 1, pageSize, keyword }), {
+ hideToast: true,
+ setOperating,
+ });
+
+ if (success) {
+ setPage(page + 1);
+ dispatch({ type: 'APPEND', payload: res.projects });
+ }
+ };
+
+ return (
+ <S.ProjectSelector ref={ref}>
+ <h1 onClick={() => setOpen(true)}>
+ <span>{name}</span>
+ <CaretDownOutlined style={{ fontSize: 12 }} />
+ </h1>
+ {open && (
+ <S.Selector>
+ <Input.Search placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
+ {operating ? (
+ <PageLoading />
+ ) : state.length ? (
+ <ul>
+ {state.map((it) => (
+ <li key={it.name} onClick={() => navigate(PATHS.PROJECT(it.name))}>
+ {it.name}
+ </li>
+ ))}
+ </ul>
+ ) : (
+ <p style={{ textAlign: 'center' }}>No Results</p>
+ )}
+ {total > state.length && (
+ <Flex style={{ marginTop: 16 }} align="center" justify="center" onClick={handleAppend}>
+ <Button loading={operating}>Load More</Button>
+ </Flex>
+ )}
+ </S.Selector>
+ )}
+ </S.ProjectSelector>
+ );
+};
diff --git a/config-ui/src/routes/project/detail/styled.ts b/config-ui/src/routes/project/layout/styled.ts
similarity index 50%
rename from config-ui/src/routes/project/detail/styled.ts
rename to config-ui/src/routes/project/layout/styled.ts
index f2ac11cfb..722e8453c 100644
--- a/config-ui/src/routes/project/detail/styled.ts
+++ b/config-ui/src/routes/project/layout/styled.ts
@@ -18,9 +18,53 @@
import styled from 'styled-components';
-export const Wrapper = styled.div``;
+export const Top = styled.div`
+ cursor: pointer;
-export const DialogBody = styled.div`
- display: flex;
- align-items: center;
+ & > span.back {
+ margin-left: 8px;
+ text-decoration: underline;
+ }
+`;
+
+export const ProjectSelector = styled.div`
+ position: relative;
+ padding: 16px 8px;
+
+ & > h1 {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ }
+`;
+
+export const Selector = styled.div`
+ position: absolute;
+ top: 50px;
+ right: 0;
+ left: 0;
+ padding: 16px 8px;
+ background-color: #fff;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+ border-radius: 6px;
+ z-index: 1;
+
+ & > ul,
+ & > p {
+ margin-top: 16px;
+ }
+
+ & > ul > li {
+ display: flex;
+ padding: 8px 16px;
+ align-items: center;
+ min-height: 40px;
+ transition: background-color 0.3s;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+ }
`;
diff --git a/config-ui/src/routes/project/detail/webhooks-panel.tsx b/config-ui/src/routes/project/webhook/index.tsx
similarity index 80%
rename from config-ui/src/routes/project/detail/webhooks-panel.tsx
rename to config-ui/src/routes/project/webhook/index.tsx
index aefbbb7f2..fafd41d05 100644
--- a/config-ui/src/routes/project/detail/webhooks-panel.tsx
+++ b/config-ui/src/routes/project/webhook/index.tsx
@@ -17,32 +17,32 @@
*/
import { useState, useMemo } from 'react';
-import { Link } from 'react-router-dom';
+import { useParams, Link } from 'react-router-dom';
import { PlusOutlined } from '@ant-design/icons';
import { Alert, Button } from 'antd';
import API from '@/api';
import { NoData } from '@/components';
+import { useRefreshData } from '@/hooks';
import type { WebhookItemType } from '@/plugins/register/webhook';
import { WebhookCreateDialog, WebhookSelectorDialog, WebHookConnection } from '@/plugins/register/webhook';
-import { IProject } from '@/types';
import { operator } from '@/utils';
-interface Props {
- project: IProject;
- onRefresh: () => void;
-}
-
-export const WebhooksPanel = ({ project, onRefresh }: Props) => {
+export const ProjectWebhook = () => {
const [type, setType] = useState<'selectExist' | 'create'>();
const [operating, setOperating] = useState(false);
+ const [version, setVersion] = useState(0);
+
+ const { pname } = useParams() as { pname: string };
+
+ const { data } = useRefreshData(() => API.project.get(pname), [pname, version]);
const webhookIds = useMemo(
() =>
- project.blueprint
- ? project.blueprint.connections.filter((cs) => cs.pluginName === 'webhook').map((cs: any) => cs.connectionId)
+ data?.blueprint
+ ? data?.blueprint.connections.filter((cs) => cs.pluginName === 'webhook').map((cs: any) => cs.connectionId)
: [],
- [project],
+ [data],
);
const handleCancel = () => {
@@ -50,10 +50,14 @@ export const WebhooksPanel = ({ project, onRefresh }: Props) => {
};
const handleCreate = async (id: ID) => {
+ if (!data) {
+ return;
+ }
+
const payload = {
- ...project.blueprint,
+ ...data.blueprint,
connections: [
- ...project.blueprint.connections,
+ ...data.blueprint.connections,
{
pluginName: 'webhook',
connectionId: id,
@@ -61,20 +65,24 @@ export const WebhooksPanel = ({ project, onRefresh }: Props) => {
],
};
- const [success] = await operator(() => API.blueprint.update(project.blueprint.id, payload), {
+ const [success] = await operator(() => API.blueprint.update(data.blueprint.id, payload), {
setOperating,
});
if (success) {
- onRefresh();
+ setVersion(version + 1);
}
};
const handleSelect = async (items: WebhookItemType[]) => {
+ if (!data) {
+ return;
+ }
+
const payload = {
- ...project.blueprint,
+ ...data.blueprint,
connections: [
- ...project.blueprint.connections,
+ ...data.blueprint.connections,
...items.map((it) => ({
pluginName: 'webhook',
connectionId: it.id,
@@ -82,29 +90,31 @@ export const WebhooksPanel = ({ project, onRefresh }: Props) => {
],
};
- const [success] = await operator(() => API.blueprint.update(project.blueprint.id, payload), {
+ const [success] = await operator(() => API.blueprint.update(data.blueprint.id, payload), {
setOperating,
});
if (success) {
- onRefresh();
+ setVersion(version + 1);
}
};
const handleDelete = async (id: ID) => {
+ if (!data) {
+ return;
+ }
+
const payload = {
- ...project.blueprint,
- connections: project.blueprint.connections.filter(
- (cs) => !(cs.pluginName === 'webhook' && cs.connectionId === id),
- ),
+ ...data.blueprint,
+ connections: data.blueprint.connections.filter((cs) => !(cs.pluginName === 'webhook' && cs.connectionId === id)),
};
- const [success] = await operator(() => API.blueprint.update(project.blueprint.id, payload), {
+ const [success] = await operator(() => API.blueprint.update(data.blueprint.id, payload), {
setOperating,
});
if (success) {
- onRefresh();
+ setVersion(version + 1);
}
};