This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 9251c42a7 feat: use redux rewrite project detail (#8095)
9251c42a7 is described below
commit 9251c42a7599d3b20aae1d0868a25024b3d7bb33
Author: 青湛 <[email protected]>
AuthorDate: Tue Sep 24 21:09:56 2024 +1200
feat: use redux rewrite project detail (#8095)
---
config-ui/src/app/store.ts | 2 +
.../webhook.ts => features/project/index.ts} | 21 +------
config-ui/src/features/project/slice.ts | 65 ++++++++++++++++++++++
.../src/plugins/register/webhook/connection.tsx | 5 +-
.../routes/project/additional-settings/index.tsx | 11 ++--
.../src/routes/project/general-settings/index.tsx | 15 ++---
config-ui/src/routes/project/layout/index.tsx | 18 +++++-
config-ui/src/routes/project/webhook/index.tsx | 61 +++++++++++---------
config-ui/src/types/webhook.ts | 4 +-
9 files changed, 135 insertions(+), 67 deletions(-)
diff --git a/config-ui/src/app/store.ts b/config-ui/src/app/store.ts
index f95900a46..5932edc7f 100644
--- a/config-ui/src/app/store.ts
+++ b/config-ui/src/app/store.ts
@@ -21,12 +21,14 @@ import { configureStore, ThunkAction, Action } from
'@reduxjs/toolkit';
import { versionSlice } from '@/features/version';
import { connectionsSlice } from '@/features/connections';
import { onboardSlice } from '@/features/onboard';
+import { projectSlice } from '@/features/project';
export const store = configureStore({
reducer: {
version: versionSlice.reducer,
connections: connectionsSlice.reducer,
onboard: onboardSlice.reducer,
+ project: projectSlice.reducer,
},
});
diff --git a/config-ui/src/types/webhook.ts
b/config-ui/src/features/project/index.ts
similarity index 66%
copy from config-ui/src/types/webhook.ts
copy to config-ui/src/features/project/index.ts
index 2035bf675..513ab48a7 100644
--- a/config-ui/src/types/webhook.ts
+++ b/config-ui/src/features/project/index.ts
@@ -16,23 +16,4 @@
*
*/
-export interface IWebhookAPI {
- id: number;
- name: string;
- postIssuesEndpoint: string;
- closeIssuesEndpoint: string;
- postPipelineDeployTaskEndpoint: string;
- apiKey: {
- id: number;
- apiKey: string;
- };
-}
-
-export interface IWebhook {
- id: number;
- name: string;
- postIssuesEndpoint: string;
- closeIssuesEndpoint: string;
- postPipelineDeployTaskEndpoint: string;
- apiKeyId: number;
-}
+export * from './slice';
diff --git a/config-ui/src/features/project/slice.ts
b/config-ui/src/features/project/slice.ts
new file mode 100644
index 000000000..0c59dba73
--- /dev/null
+++ b/config-ui/src/features/project/slice.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+
+import API from '@/api';
+import type { RootState } from '@/app/store';
+import type { IStatus, IProject } from '@/types';
+
+export const request = createAsyncThunk('project/request', async (name:
string) => {
+ const res = await API.project.get(name);
+ return res;
+});
+
+const initialState: { status: IStatus; data?: IProject } = {
+ status: 'loading',
+};
+
+export const projectSlice = createSlice({
+ name: 'project',
+ initialState,
+ reducers: {
+ updateBlueprint: (state, action) => {
+ if (state.data) {
+ state.data.blueprint = action.payload;
+ }
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(request.pending, (state) => {
+ state.status = 'loading';
+ })
+ .addCase(request.fulfilled, (state, action) => {
+ state.status = 'success';
+ state.data = action.payload;
+ })
+ .addCase(request.rejected, (state) => {
+ state.status = 'failed';
+ });
+ },
+});
+
+export const { updateBlueprint } = projectSlice.actions;
+
+export default projectSlice.reducer;
+
+export const selectProjectStatus = (state: RootState) => state.project.status;
+
+export const selectProject = (state: RootState) => state.project.data;
diff --git a/config-ui/src/plugins/register/webhook/connection.tsx
b/config-ui/src/plugins/register/webhook/connection.tsx
index 32bfd6f5e..c83f1813d 100644
--- a/config-ui/src/plugins/register/webhook/connection.tsx
+++ b/config-ui/src/plugins/register/webhook/connection.tsx
@@ -115,7 +115,10 @@ export const WebHookConnection = ({ filterIds, fromProject
= false, onAssociate,
title="Remove this Webhook?"
okText="Confirm"
onCancel={handleHideDialog}
- onOk={() => onRemove?.(currentID)}
+ onOk={() => {
+ onRemove?.(currentID);
+ handleHideDialog();
+ }}
>
<Message content="This will only remove the webhook from this
project. To permanently delete the webhook, please visit the Connections page."
/>
</Modal>
diff --git a/config-ui/src/routes/project/additional-settings/index.tsx
b/config-ui/src/routes/project/additional-settings/index.tsx
index 5d670bb77..feac60215 100644
--- a/config-ui/src/routes/project/additional-settings/index.tsx
+++ b/config-ui/src/routes/project/additional-settings/index.tsx
@@ -17,12 +17,13 @@
*/
import { useEffect, useState } from 'react';
-import { useParams, useNavigate } from 'react-router-dom';
+import { 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 { useRefreshData } from '@/hooks';
+import { selectProject } from '@/features/project';
+import { useAppSelector } from '@/hooks';
import { operator } from '@/utils';
const RegexPrIssueDefaultValue = '(?mi)(Closes)[\\s]*.*(((and )?#\\d+[ ]*)+)';
@@ -41,13 +42,10 @@ export const ProjectAdditionalSettings = () => {
});
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]);
+ const project = useAppSelector(selectProject);
useEffect(() => {
if (!project) {
@@ -107,7 +105,6 @@ export const ProjectAdditionalSettings = () => {
);
if (success) {
- setVersion((v) => v + 1);
navigate(`/projects/${encodeURIComponent(name)}`, {
state: {
tabId: 'settings',
diff --git a/config-ui/src/routes/project/general-settings/index.tsx
b/config-ui/src/routes/project/general-settings/index.tsx
index ef538afcc..4a7245a06 100644
--- a/config-ui/src/routes/project/general-settings/index.tsx
+++ b/config-ui/src/routes/project/general-settings/index.tsx
@@ -16,20 +16,15 @@
*
*/
-import { useParams } from 'react-router-dom';
-
-import API from '@/api';
-import { PageLoading } from '@/components';
-import { useRefreshData } from '@/hooks';
+import { selectProject } from '@/features/project';
+import { useAppSelector } from '@/hooks';
import { BlueprintDetail, FromEnum } from '@/routes';
export const ProjectGeneralSettings = () => {
- const { pname } = useParams() as { pname: string };
-
- const { ready, data: project } = useRefreshData(() =>
API.project.get(pname), [pname]);
+ const project = useAppSelector(selectProject);
- if (!ready || !project) {
- return <PageLoading />;
+ if (!project) {
+ return null;
}
return <BlueprintDetail id={project.blueprint.id} from={FromEnum.project} />;
diff --git a/config-ui/src/routes/project/layout/index.tsx
b/config-ui/src/routes/project/layout/index.tsx
index d96747a29..23d5e4dca 100644
--- a/config-ui/src/routes/project/layout/index.tsx
+++ b/config-ui/src/routes/project/layout/index.tsx
@@ -16,12 +16,14 @@
*
*/
-import { useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
import { useParams, useNavigate, useLocation, Link, Outlet } from
'react-router-dom';
import { RollbackOutlined } from '@ant-design/icons';
import { Layout, Menu } from 'antd';
-import { PageHeader } from '@/components';
+import { PageHeader, PageLoading } from '@/components';
+import { request, selectProjectStatus, selectProject } from
'@/features/project';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { ProjectSelector } from './project-selector';
import * as S from './styled';
@@ -86,6 +88,14 @@ export const ProjectLayout = () => {
const navigate = useNavigate();
const { pathname } = useLocation();
+ const dispatch = useAppDispatch();
+ const status = useAppSelector(selectProjectStatus);
+ const project = useAppSelector(selectProject);
+
+ useEffect(() => {
+ dispatch(request(pname));
+ }, [pname]);
+
const { paths, selectedKeys, title } = useMemo(() => {
const paths = pathname.split('/');
const key = paths.pop();
@@ -103,6 +113,10 @@ export const ProjectLayout = () => {
};
}, [pathname]);
+ if (status === 'loading' || !project) {
+ return <PageLoading />;
+ }
+
return (
<Layout style={{ height: '100%', overflow: 'hidden' }}>
<Sider width={240} style={{ padding: '36px 12px', backgroundColor:
'#F9F9FA', borderRight: '1px solid #E7E9F3' }}>
diff --git a/config-ui/src/routes/project/webhook/index.tsx
b/config-ui/src/routes/project/webhook/index.tsx
index 20e828f71..3c9d937f3 100644
--- a/config-ui/src/routes/project/webhook/index.tsx
+++ b/config-ui/src/routes/project/webhook/index.tsx
@@ -17,13 +17,15 @@
*/
import { useState, useMemo } from 'react';
-import { useParams, Link } from 'react-router-dom';
+import { 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 { selectProject, updateBlueprint } from '@/features/project';
+import { selectWebhooks } from '@/features/connections';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import type { WebhookItemType } from '@/plugins/register/webhook';
import { WebhookCreateDialog, WebhookSelectorDialog, WebHookConnection } from
'@/plugins/register/webhook';
import { operator } from '@/utils';
@@ -31,18 +33,20 @@ import { operator } from '@/utils';
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 dispatch = useAppDispatch();
+ const project = useAppSelector(selectProject);
+ const webhooks = useAppSelector(selectWebhooks);
const webhookIds = useMemo(
() =>
- data?.blueprint
- ? data?.blueprint.connections.filter((cs) => cs.pluginName ===
'webhook').map((cs: any) => cs.connectionId)
+ project?.blueprint
+ ? project?.blueprint.connections
+ .filter((cs) => cs.pluginName === 'webhook')
+ .filter((cs) => webhooks.map((wh) =>
wh.id).includes(cs.connectionId))
+ .map((cs: any) => cs.connectionId)
: [],
- [data],
+ [project],
);
const handleCancel = () => {
@@ -50,14 +54,16 @@ export const ProjectWebhook = () => {
};
const handleCreate = async (id: ID) => {
- if (!data) {
+ if (!project) {
return;
}
const payload = {
- ...data.blueprint,
+ ...project.blueprint,
connections: [
- ...data.blueprint.connections,
+ ...project.blueprint.connections.filter(
+ (cs) => cs.pluginName !== 'webhook' ||
webhookIds.includes(cs.connectionId),
+ ),
{
pluginName: 'webhook',
connectionId: id,
@@ -65,25 +71,28 @@ export const ProjectWebhook = () => {
],
};
- const [success] = await operator(() =>
API.blueprint.update(data.blueprint.id, payload), {
+ const [success] = await operator(() =>
API.blueprint.update(project.blueprint.id, payload), {
setOperating,
+ hideToast: true,
});
if (success) {
- setVersion(version + 1);
handleCancel();
+ dispatch(updateBlueprint(payload));
}
};
const handleSelect = async (items: WebhookItemType[]) => {
- if (!data) {
+ if (!project) {
return;
}
const payload = {
- ...data.blueprint,
+ ...project.blueprint,
connections: [
- ...data.blueprint.connections,
+ ...project.blueprint.connections.filter(
+ (cs) => cs.pluginName !== 'webhook' ||
webhookIds.includes(cs.connectionId),
+ ),
...items.map((it) => ({
pluginName: 'webhook',
connectionId: it.id,
@@ -91,33 +100,35 @@ export const ProjectWebhook = () => {
],
};
- const [success] = await operator(() =>
API.blueprint.update(data.blueprint.id, payload), {
+ const [success] = await operator(() =>
API.blueprint.update(project.blueprint.id, payload), {
setOperating,
});
if (success) {
- setVersion(version + 1);
handleCancel();
+ dispatch(updateBlueprint(payload));
}
};
const handleDelete = async (id: ID) => {
- if (!data) {
+ if (!project) {
return;
}
const payload = {
- ...data.blueprint,
- connections: data.blueprint.connections.filter((cs) => !(cs.pluginName
=== 'webhook' && cs.connectionId === id)),
+ ...project.blueprint,
+ connections: project.blueprint.connections
+ .filter((cs) => cs.pluginName !== 'webhook' ||
webhookIds.includes(cs.connectionId))
+ .filter((cs) => !(cs.pluginName === 'webhook' && cs.connectionId ===
id)),
};
- const [success] = await operator(() =>
API.blueprint.update(data.blueprint.id, payload), {
+ const [success] = await operator(() =>
API.blueprint.update(project.blueprint.id, payload), {
setOperating,
});
if (success) {
- setVersion(version + 1);
handleCancel();
+ dispatch(updateBlueprint(payload));
}
};
@@ -135,7 +146,7 @@ export const ProjectWebhook = () => {
<div style={{ marginTop: 16 }}>
To calculate DORA after receiving Webhook data immediately, you
can visit the{' '}
<b style={{ textDecoration: 'underline' }}>
- <Link
to={`/projects/${encodeURIComponent(pname)}/general-settings`}>Status tab</Link>
+ <Link to={`/projects/${encodeURIComponent(project?.name ??
'')}/general-settings`}>Status tab</Link>
</b>{' '}
of the Blueprint page and click on Run Now.
</div>
diff --git a/config-ui/src/types/webhook.ts b/config-ui/src/types/webhook.ts
index 2035bf675..5ea4b89cd 100644
--- a/config-ui/src/types/webhook.ts
+++ b/config-ui/src/types/webhook.ts
@@ -17,7 +17,7 @@
*/
export interface IWebhookAPI {
- id: number;
+ id: ID;
name: string;
postIssuesEndpoint: string;
closeIssuesEndpoint: string;
@@ -29,7 +29,7 @@ export interface IWebhookAPI {
}
export interface IWebhook {
- id: number;
+ id: ID;
name: string;
postIssuesEndpoint: string;
closeIssuesEndpoint: string;