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 55135f9d6 refactor: use redux to rewrite onboard (#8012)
55135f9d6 is described below
commit 55135f9d63a708d980edf5dd8b4ddd46c8be20d7
Author: 青湛 <[email protected]>
AuthorDate: Mon Sep 9 14:54:55 2024 +1200
refactor: use redux to rewrite onboard (#8012)
---
config-ui/src/app/store.ts | 2 +
.../context.tsx => features/onboard/index.ts} | 32 +----
config-ui/src/features/onboard/slice.ts | 131 +++++++++++++++++++++
config-ui/src/routes/app/app.tsx | 10 +-
config-ui/src/routes/app/loader.ts | 7 --
config-ui/src/routes/layout/layout.tsx | 10 +-
config-ui/src/routes/onboard/components/card.tsx | 44 +++----
config-ui/src/routes/onboard/components/tour.tsx | 8 +-
config-ui/src/routes/onboard/index.tsx | 108 ++++++-----------
config-ui/src/routes/onboard/step-0.tsx | 40 ++-----
config-ui/src/routes/onboard/step-1.tsx | 27 ++---
config-ui/src/routes/onboard/step-2.tsx | 54 +++------
config-ui/src/routes/onboard/step-3.tsx | 27 ++---
config-ui/src/routes/onboard/step-4.tsx | 52 ++------
14 files changed, 264 insertions(+), 288 deletions(-)
diff --git a/config-ui/src/app/store.ts b/config-ui/src/app/store.ts
index 8d2ce0c34..8fec5046c 100644
--- a/config-ui/src/app/store.ts
+++ b/config-ui/src/app/store.ts
@@ -19,10 +19,12 @@
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import { connectionsSlice } from '@/features';
+import { onboardSlice } from '@/features/onboard';
export const store = configureStore({
reducer: {
connections: connectionsSlice.reducer,
+ onboard: onboardSlice.reducer,
},
});
diff --git a/config-ui/src/routes/onboard/context.tsx
b/config-ui/src/features/onboard/index.ts
similarity index 55%
rename from config-ui/src/routes/onboard/context.tsx
rename to config-ui/src/features/onboard/index.ts
index e21d12d2b..513ab48a7 100644
--- a/config-ui/src/routes/onboard/context.tsx
+++ b/config-ui/src/features/onboard/index.ts
@@ -16,34 +16,4 @@
*
*/
-import { createContext } from 'react';
-
-export type Record = {
- plugin: string;
- connectionId: ID;
- blueprintId: ID;
- pipelineId: ID;
- scopeName: string;
-};
-
-const initialValue: {
- step: number;
- records: Record[];
- done: boolean;
- projectName?: string;
- plugin?: string;
- setStep: (value: number) => void;
- setRecords: (value: Record[]) => void;
- setProjectName: (value: string) => void;
- setPlugin: (value: string) => void;
-} = {
- step: 0,
- records: [],
- done: false,
- setStep: () => {},
- setRecords: () => {},
- setProjectName: () => {},
- setPlugin: () => {},
-};
-
-export const Context = createContext(initialValue);
+export * from './slice';
diff --git a/config-ui/src/features/onboard/slice.ts
b/config-ui/src/features/onboard/slice.ts
new file mode 100644
index 000000000..1ddebf576
--- /dev/null
+++ b/config-ui/src/features/onboard/slice.ts
@@ -0,0 +1,131 @@
+/*
+ * 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 { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+
+import API from '@/api';
+import type { RootState } from '@/app/store';
+import type { IStatus } from '@/types';
+
+type DataType = {
+ initial: boolean;
+ step: number;
+ records: Array<{
+ plugin: string;
+ connectionId: ID;
+ blueprintId: ID;
+ pipelineId: ID;
+ scopeName: string;
+ }>;
+ projectName: string;
+ plugin: string;
+ done: boolean;
+};
+
+export const request = createAsyncThunk('onboard/request', async () => {
+ const res = await API.store.get('onboard');
+ return res;
+});
+
+export const update = createAsyncThunk('onboard/update', async (payload:
Partial<DataType>, { getState }) => {
+ const { data } = (getState() as RootState).onboard;
+ const res = await API.store.set('onboard', {
+ ...data,
+ ...payload,
+ step: payload.step ?? data.step + 1,
+ });
+ return res;
+});
+
+export const done = createAsyncThunk('onboard/done', async (_, { getState })
=> {
+ const { data } = (getState() as RootState).onboard;
+ await API.store.set('onboard', {
+ ...data,
+ done: true,
+ });
+ return {};
+});
+
+const initialState: { status: IStatus; data: DataType } = {
+ status: 'idle',
+ data: {
+ initial: false,
+ step: 0,
+ records: [],
+ projectName: '',
+ plugin: '',
+ done: false,
+ },
+};
+
+export const onboardSlice = createSlice({
+ name: 'onboard',
+ initialState,
+ reducers: {
+ previous: (state) => {
+ state.data.step -= 1;
+ },
+ changeProjectName: (state, action) => {
+ state.data.projectName = action.payload;
+ },
+ changePlugin: (state, action) => {
+ state.data.plugin = action.payload;
+ },
+ changeRecords: (state, action) => {
+ state.data.records = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(request.pending, (state) => {
+ state.status = 'loading';
+ })
+ .addCase(request.fulfilled, (state, action) => {
+ state.status = 'success';
+ state.data = {
+ ...action.payload,
+ initial: action.payload?.initial ?? false,
+ step: action.payload?.step ?? 0,
+ records: action.payload?.records ?? [],
+ done: action.payload?.done ?? false,
+ };
+ })
+ .addCase(update.fulfilled, (state, action) => {
+ state.data = {
+ ...state.data,
+ ...action.payload,
+ };
+ })
+ .addCase(done.fulfilled, (state) => {
+ state.data.done = true;
+ });
+ },
+});
+
+export default onboardSlice.reducer;
+
+export const { previous, changeProjectName, changePlugin, changeRecords } =
onboardSlice.actions;
+
+export const selectStatus = (state: RootState) => state.onboard.status;
+
+export const selectOnboard = (state: RootState) => state.onboard.data;
+
+export const selectRecord = (state: RootState) => {
+ const { plugin, records } = state.onboard.data;
+ return records.find((it) => it.plugin === plugin);
+};
diff --git a/config-ui/src/routes/app/app.tsx b/config-ui/src/routes/app/app.tsx
index 72a6b75aa..51c3ce0c7 100644
--- a/config-ui/src/routes/app/app.tsx
+++ b/config-ui/src/routes/app/app.tsx
@@ -19,8 +19,10 @@
import { useEffect } from 'react';
import { useNavigate, useLoaderData, Outlet } from 'react-router-dom';
+import { PageLoading } from '@/components';
import { init } from '@/features';
-import { useAppDispatch } from '@/hooks';
+import { request as requestOnboard, selectStatus } from '@/features/onboard';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { setUpRequestInterceptor } from '@/utils';
export const App = () => {
@@ -29,11 +31,17 @@ export const App = () => {
const { version, plugins } = useLoaderData() as { version: string; plugins:
string[] };
const dispatch = useAppDispatch();
+ const status = useAppSelector(selectStatus);
useEffect(() => {
setUpRequestInterceptor(navigate);
dispatch(init({ version, plugins }));
+ dispatch(requestOnboard());
}, []);
+ if (status === 'loading') {
+ return <PageLoading />;
+ }
+
return <Outlet />;
};
diff --git a/config-ui/src/routes/app/loader.ts
b/config-ui/src/routes/app/loader.ts
index 51d532f6e..dad6d1267 100644
--- a/config-ui/src/routes/app/loader.ts
+++ b/config-ui/src/routes/app/loader.ts
@@ -16,7 +16,6 @@
*
*/
-import { redirect } from 'react-router-dom';
import { intersection } from 'lodash';
import API from '@/api';
@@ -27,12 +26,6 @@ type Props = {
};
export const appLoader = async ({ request }: Props) => {
- const onboard = await API.store.get('onboard');
-
- if (!onboard) {
- return redirect('/onboard');
- }
-
let fePlugins = getRegisterPlugins();
const bePlugins = await API.plugin.list();
diff --git a/config-ui/src/routes/layout/layout.tsx
b/config-ui/src/routes/layout/layout.tsx
index 8e45dd96e..a67036a5c 100644
--- a/config-ui/src/routes/layout/layout.tsx
+++ b/config-ui/src/routes/layout/layout.tsx
@@ -17,12 +17,13 @@
*/
import { useState, useEffect, useMemo } from 'react';
-import { Outlet, useNavigate, useLocation } from 'react-router-dom';
+import { Outlet, useNavigate, useLocation, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Layout as AntdLayout, Menu, Divider } from 'antd';
import { PageLoading, Logo, ExternalLink } from '@/components';
-import { selectError, selectStatus, selectVersion } from '@/features';
+import { selectError, selectStatus, selectVersion } from
'@/features/connections';
+import { selectOnboard } from '@/features/onboard';
import { OnboardCard } from '@/routes/onboard/components';
import { useAppSelector } from '@/hooks';
@@ -39,6 +40,7 @@ export const Layout = () => {
const navigate = useNavigate();
const { pathname } = useLocation();
+ const { initial } = useAppSelector(selectOnboard);
const status = useAppSelector(selectStatus);
const error = useAppSelector(selectError);
const version = useAppSelector(selectVersion);
@@ -77,6 +79,10 @@ export const Layout = () => {
throw error.message;
}
+ if (!initial) {
+ return <Navigate to="/onboard" />;
+ }
+
return (
<AntdLayout style={{ height: '100%', overflow: 'hidden' }}>
<Helmet>
diff --git a/config-ui/src/routes/onboard/components/card.tsx
b/config-ui/src/routes/onboard/components/card.tsx
index 3c59ee082..b73786a29 100644
--- a/config-ui/src/routes/onboard/components/card.tsx
+++ b/config-ui/src/routes/onboard/components/card.tsx
@@ -16,14 +16,14 @@
*
*/
-import { useState, useMemo } from 'react';
+import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { CloseOutlined, LoadingOutlined, CheckCircleFilled, CloseCircleFilled
} from '@ant-design/icons';
import { theme, Card, Flex, Progress, Space, Button, Modal } from 'antd';
import API from '@/api';
-import { useRefreshData, useAutoRefresh } from '@/hooks';
-import { operator } from '@/utils';
+import { selectOnboard, selectRecord, done as doneFuc } from
'@/features/onboard';
+import { useAppDispatch, useAppSelector, useAutoRefresh } from '@/hooks';
import { DashboardURLMap } from '../step-4';
@@ -32,24 +32,21 @@ interface Props {
}
export const OnboardCard = ({ style }: Props) => {
- const [oeprating, setOperating] = useState(false);
- const [version, setVersion] = useState(0);
-
const navigate = useNavigate();
+ const dispatch = useAppDispatch();
+ const { step, plugin, done } = useAppSelector(selectOnboard);
+ const record = useAppSelector(selectRecord);
+
const {
token: { green5, orange5, red5 },
} = theme.useToken();
const [modal, contextHolder] = Modal.useModal();
- const { ready, data } = useRefreshData(() => API.store.get('onboard'),
[version]);
-
- const record = useMemo(() => (data ? data.records.find((it: any) =>
it.plugin === data.plugin) : null), [data]);
-
const tasksRes = useAutoRefresh(
async () => {
- if ((data && data.done) || !record) {
+ if (done || !record) {
return;
}
@@ -64,7 +61,7 @@ export const OnboardCard = ({ style }: Props) => {
);
const status = useMemo(() => {
- if (!data || data.step !== 4) {
+ if (step !== 4) {
return 'prepare';
}
@@ -83,30 +80,21 @@ export const OnboardCard = ({ style }: Props) => {
default:
return 'running';
}
- }, [data, tasksRes]);
+ }, [step, tasksRes]);
const handleClose = async () => {
modal.confirm({
width: 600,
title: 'Permanently close this entry?',
content: 'You will not be able to get back to the onboarding session
again.',
- okButtonProps: {
- loading: oeprating,
- },
okText: 'Confirm',
- onOk: async () => {
- const [success] = await operator(() => API.store.set('onboard', {
...data, done: true }), {
- setOperating,
- });
-
- if (success) {
- setVersion(version + 1);
- }
+ onOk() {
+ dispatch(doneFuc());
},
});
};
- if (!ready || !data || data.done) {
+ if (done) {
return null;
}
@@ -115,7 +103,7 @@ export const OnboardCard = ({ style }: Props) => {
<Flex style={{ paddingRight: 50 }} align="center"
justify="space-between">
<Flex align="center">
{status === 'prepare' && (
- <Progress type="circle" size={30} format={() => `${data.step}/3`}
percent={(data.step / 3) * 100} />
+ <Progress type="circle" size={30} format={() => `${step}/3`}
percent={(step / 3) * 100} />
)}
{status === 'running' && <LoadingOutlined />}
{status === 'success' && <CheckCircleFilled style={{ color: green5
}} />}
@@ -157,7 +145,7 @@ export const OnboardCard = ({ style }: Props) => {
)}
{status === 'success' && (
<Space>
- <Button type="primary" onClick={() =>
window.open(DashboardURLMap[data.plugin])}>
+ <Button type="primary" onClick={() =>
window.open(DashboardURLMap[plugin])}>
Check Dashboard
</Button>
<Button onClick={handleClose}>Finish</Button>
@@ -168,7 +156,7 @@ export const OnboardCard = ({ style }: Props) => {
<Button type="primary" onClick={() => navigate('/onboard')}>
Details
</Button>
- <Button onClick={() =>
window.open(DashboardURLMap[data.plugin])}>Check Dashboard</Button>
+ <Button onClick={() => window.open(DashboardURLMap[plugin])}>Check
Dashboard</Button>
</Space>
)}
</Flex>
diff --git a/config-ui/src/routes/onboard/components/tour.tsx
b/config-ui/src/routes/onboard/components/tour.tsx
index 875ce2e18..bac65ce67 100644
--- a/config-ui/src/routes/onboard/components/tour.tsx
+++ b/config-ui/src/routes/onboard/components/tour.tsx
@@ -18,8 +18,8 @@
import { Tour } from 'antd';
-import API from '@/api';
-import { useRefreshData } from '@/hooks';
+import { selectOnboard } from '@/features/onboard';
+import { useAppSelector } from '@/hooks';
interface Props {
nameRef: React.RefObject<HTMLInputElement>;
@@ -28,7 +28,7 @@ interface Props {
}
export const OnboardTour = ({ nameRef, connectionRef, configRef }: Props) => {
- const { ready, data } = useRefreshData(() => API.store.get('onboard'), []);
+ const { step, done } = useAppSelector(selectOnboard);
const steps = [
{
@@ -49,7 +49,7 @@ export const OnboardTour = ({ nameRef, connectionRef,
configRef }: Props) => {
},
];
- if (!ready || !data || data.step !== 4 || data.done) {
+ if (step !== 4 || done) {
return null;
}
diff --git a/config-ui/src/routes/onboard/index.tsx
b/config-ui/src/routes/onboard/index.tsx
index 061be2033..239a4b973 100644
--- a/config-ui/src/routes/onboard/index.tsx
+++ b/config-ui/src/routes/onboard/index.tsx
@@ -16,19 +16,15 @@
*
*/
-import React, { useState, useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, Navigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { CloseOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { theme, Layout, Modal } from 'antd';
-import API from '@/api';
-import { PageLoading } from '@/components';
+import { selectOnboard } from '@/features/onboard';
import { PATHS } from '@/config';
-import { useRefreshData } from '@/hooks';
+import { useAppSelector } from '@/hooks';
-import type { Record } from './context';
-import { Context } from './context';
import { Step0 } from './step-0';
import { Step1 } from './step-1';
import { Step2 } from './step-2';
@@ -59,30 +55,16 @@ interface Props {
}
export const Onboard = ({ logo, title }: Props) => {
- const [step, setStep] = useState(0);
- const [records, setRecords] = useState<Record[]>([]);
- const [projectName, setProjectName] = useState<string>();
- const [plugin, setPlugin] = useState<string>();
-
const navigate = useNavigate();
+ const { step, done } = useAppSelector(selectOnboard);
+
const {
token: { colorPrimary },
} = theme.useToken();
const [modal, contextHolder] = Modal.useModal();
- const { ready, data } = useRefreshData(() => API.store.get('onboard'));
-
- useEffect(() => {
- if (ready && data) {
- setStep(data.step);
- setRecords(data.records);
- setProjectName(data.projectName);
- setPlugin(data.plugin);
- }
- }, [ready, data]);
-
const handleClose = () => {
modal.confirm({
width: 820,
@@ -94,58 +76,44 @@ export const Onboard = ({ logo, title }: Props) => {
});
};
- if (!ready) {
- return <PageLoading />;
+ if (done) {
+ return <Navigate to="/projects" />;
}
return (
- <Context.Provider
- value={{
- step,
- records,
- done: false,
- projectName,
- plugin,
- setStep,
- setRecords,
- setProjectName: setProjectName,
- setPlugin: setPlugin,
- }}
- >
+ <Layout style={{ minHeight: '100vh' }}>
<Helmet>
<title>Onboard - {brandName}</title>
</Helmet>
- <Layout style={{ minHeight: '100vh' }}>
- <S.Inner>
- {step === 0 ? (
- <Step0 logo={logo} title={title} />
- ) : (
- <>
- <S.Header>
- <h1>Connect to your first repository</h1>
- <CloseOutlined style={{ fontSize: 18, color: '#70727F',
cursor: 'pointer' }} onClick={handleClose} />
- </S.Header>
- <S.Content>
- {[1, 2, 3].includes(step) && (
- <S.Step>
- {steps.map((it) => (
- <S.StepItem key={it.step} $actived={it.step === step}
$activedColor={colorPrimary}>
- <span>{it.step}</span>
- <span>{it.title}</span>
- </S.StepItem>
- ))}
- </S.Step>
- )}
- {step === 1 && <Step1 />}
- {step === 2 && <Step2 />}
- {step === 3 && <Step3 />}
- {step === 4 && <Step4 />}
- </S.Content>
- </>
- )}
- </S.Inner>
- {contextHolder}
- </Layout>
- </Context.Provider>
+ <S.Inner>
+ {step === 0 ? (
+ <Step0 logo={logo} title={title} />
+ ) : (
+ <>
+ <S.Header>
+ <h1>Connect to your first repository</h1>
+ <CloseOutlined style={{ fontSize: 18, color: '#70727F', cursor:
'pointer' }} onClick={handleClose} />
+ </S.Header>
+ <S.Content>
+ {[1, 2, 3].includes(step) && (
+ <S.Step>
+ {steps.map((it) => (
+ <S.StepItem key={it.step} $actived={it.step === step}
$activedColor={colorPrimary}>
+ <span>{it.step}</span>
+ <span>{it.title}</span>
+ </S.StepItem>
+ ))}
+ </S.Step>
+ )}
+ {step === 1 && <Step1 />}
+ {step === 2 && <Step2 />}
+ {step === 3 && <Step3 />}
+ {step === 4 && <Step4 />}
+ </S.Content>
+ </>
+ )}
+ </S.Inner>
+ {contextHolder}
+ </Layout>
);
};
diff --git a/config-ui/src/routes/onboard/step-0.tsx
b/config-ui/src/routes/onboard/step-0.tsx
index 5e504bd42..c015515f2 100644
--- a/config-ui/src/routes/onboard/step-0.tsx
+++ b/config-ui/src/routes/onboard/step-0.tsx
@@ -16,19 +16,17 @@
*
*/
-import { useState, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { ExclamationCircleOutlined, CloseOutlined } from '@ant-design/icons';
import { Modal, Flex, Button } from 'antd';
import styled from 'styled-components';
-import API from '@/api';
import { Logo } from '@/components';
import { PATHS } from '@/config';
+import { update } from '@/features/onboard';
+import { useAppDispatch } from '@/hooks';
import { operator } from '@/utils';
-import { Context } from './context';
-
const Wrapper = styled.div`
.logo {
display: flex;
@@ -65,14 +63,11 @@ interface Props {
}
export const Step0 = ({ logo = <Logo direction="horizontal" />, title =
'DevLake' }: Props) => {
- const [operating, setOperating] = useState(false);
-
const navigate = useNavigate();
+ const dispatch = useAppDispatch();
const [modal, contextHolder] = Modal.useModal();
- const { step, records, done, projectName, plugin, setStep } =
useContext(Context);
-
const handleClose = () => {
modal.confirm({
width: 820,
@@ -80,15 +75,10 @@ export const Step0 = ({ logo = <Logo direction="horizontal"
/>, title = 'DevLake
content: 'You can get back to this session via the card on top of the
Projects page.',
icon: <ExclamationCircleOutlined />,
okText: 'Confirm',
- onOk: async () => {
- const [success] = await operator(
- () => API.store.set('onboard', { step: 0, records, done,
projectName, plugin }),
- {
- setOperating,
- hideToast: true,
- },
- );
-
+ async onOk() {
+ const [success] = await operator(() => dispatch(update({ initial:
true, step: 0 })).unwrap(), {
+ hideToast: true,
+ });
if (success) {
navigate(PATHS.ROOT());
}
@@ -96,20 +86,6 @@ export const Step0 = ({ logo = <Logo direction="horizontal"
/>, title = 'DevLake
});
};
- const handleSubmit = async () => {
- const [success] = await operator(
- async () => API.store.set('onboard', { step: 1, records, done,
projectName, plugin }),
- {
- setOperating,
- hideToast: true,
- },
- );
-
- if (success) {
- setStep(step + 1);
- }
- };
-
return (
<Wrapper>
{contextHolder}
@@ -123,7 +99,7 @@ export const Step0 = ({ logo = <Logo direction="horizontal"
/>, title = 'DevLake
</h1>
<h4>With just a few clicks, you can integrate your initial DevOps tool
and observe engineering metrics.</h4>
<div className="action">
- <Button block size="large" type="primary" loading={operating}
onClick={handleSubmit}>
+ <Button block size="large" type="primary" onClick={() =>
dispatch(update({}))}>
Connect to your first repository
</Button>
</div>
diff --git a/config-ui/src/routes/onboard/step-1.tsx
b/config-ui/src/routes/onboard/step-1.tsx
index a6c16632a..a6582c446 100644
--- a/config-ui/src/routes/onboard/step-1.tsx
+++ b/config-ui/src/routes/onboard/step-1.tsx
@@ -16,24 +16,26 @@
*
*/
-import { useState, useEffect, useContext } from 'react';
+import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Input, Flex, Button, message } from 'antd';
import API from '@/api';
import { Block, Markdown } from '@/components';
import { PATHS } from '@/config';
+import { selectOnboard, update, previous, changeProjectName, changePlugin }
from '@/features/onboard';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { ConnectionSelect } from '@/plugins';
import { operator } from '@/utils';
-import { Context } from './context';
import * as S from './styled';
export const Step1 = () => {
const [QA, setQA] = useState('');
const [operating, setOperating] = useState(false);
- const { step, records, done, projectName, plugin, setStep, setProjectName,
setPlugin } = useContext(Context);
+ const dispatch = useAppDispatch();
+ const { projectName, plugin } = useAppSelector(selectOnboard);
useEffect(() => {
fetch(`/onboard/step-1/${plugin ? plugin : 'default'}.md`)
@@ -56,14 +58,7 @@ export const Step1 = () => {
return;
}
- const [success] = await operator(() => API.store.set('onboard', { step: 2,
records, done, projectName, plugin }), {
- setOperating,
- hideToast: true,
- });
-
- if (success) {
- setStep(step + 1);
- }
+ dispatch(update({}));
};
return (
@@ -79,16 +74,16 @@ export const Step1 = () => {
style={{ width: 386 }}
placeholder="Your Project Name"
value={projectName}
- onChange={(e) => setProjectName(e.target.value)}
+ onChange={(e) => dispatch(changeProjectName(e.target.value))}
/>
</Block>
<Block
title="Data Connection"
description={
- <p>
+ <>
For self-managed GitLab/GitHub/Bitbucket, please skip the
onboarding and configure via{' '}
<Link to={PATHS.CONNECTIONS()}>Data Connections</Link>.
- </p>
+ </>
}
required
>
@@ -117,14 +112,14 @@ export const Step1 = () => {
},
]}
value={plugin}
- onChange={setPlugin}
+ onChange={(p) => dispatch(changePlugin(p))}
/>
</Block>
</div>
<Markdown className="qa">{QA}</Markdown>
</S.StepContent>
<Flex style={{ marginTop: 64 }} justify="space-between">
- <Button ghost type="primary" loading={operating} onClick={() =>
setStep(step - 1)}>
+ <Button ghost type="primary" loading={operating} onClick={() =>
dispatch(previous())}>
Previous Step
</Button>
<Button type="primary" loading={operating} disabled={!projectName ||
!plugin} onClick={handleSubmit}>
diff --git a/config-ui/src/routes/onboard/step-2.tsx
b/config-ui/src/routes/onboard/step-2.tsx
index cd2eba4dc..c306e026c 100644
--- a/config-ui/src/routes/onboard/step-2.tsx
+++ b/config-ui/src/routes/onboard/step-2.tsx
@@ -16,20 +16,21 @@
*
*/
-import { useState, useContext, useEffect, useMemo } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Flex, Button, Tooltip } from 'antd';
import API from '@/api';
import { Markdown } from '@/components';
import { PATHS } from '@/config';
+import { selectOnboard, previous, update } from '@/features/onboard';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { getPluginConfig } from '@/plugins';
import { ConnectionToken } from
'@/plugins/components/connection-form/fields/token';
import { ConnectionUsername } from
'@/plugins/components/connection-form/fields/username';
import { ConnectionPassword } from
'@/plugins/components/connection-form/fields/password';
import { operator } from '@/utils';
-import { Context } from './context';
import * as S from './styled';
const paramsMap: Record<string, any> = {
@@ -48,12 +49,12 @@ const paramsMap: Record<string, any> = {
export const Step2 = () => {
const [QA, setQA] = useState('');
- const [operating, setOperating] = useState(false);
const [testing, setTesting] = useState(false);
const [testStaus, setTestStatus] = useState(false);
const [payload, setPayload] = useState<any>({});
- const { step, records, done, projectName, plugin, setStep, setRecords } =
useContext(Context);
+ const dispatch = useAppDispatch();
+ const { plugin, records } = useAppSelector(selectOnboard);
const config = useMemo(() => getPluginConfig(plugin as string), [plugin]);
@@ -91,38 +92,17 @@ export const Step2 = () => {
return;
}
- const [success] = await operator(
- async () => {
- const connection = await API.connection.create(plugin, {
- name: `${plugin}-${Date.now()}`,
- ...paramsMap[plugin],
- ...payload,
- });
-
- const newRecords = [
- ...records,
- { plugin, connectionId: connection.id, blueprintId: '', pipelineId:
'', scopeName: '' },
- ];
-
- setRecords(newRecords);
-
- await API.store.set('onboard', {
- step: 3,
- records: newRecords,
- done,
- projectName,
- plugin,
- });
- },
- {
- setOperating,
- hideToast: true,
- },
- );
+ const connection = await API.connection.create(plugin, {
+ name: `${plugin}-${Date.now()}`,
+ ...paramsMap[plugin],
+ ...payload,
+ });
- if (success) {
- setStep(step + 1);
- }
+ dispatch(
+ update({
+ records: [...records, { plugin, connectionId: connection.id,
blueprintId: '', pipelineId: '', scopeName: '' }],
+ }),
+ );
};
if (!plugin) {
@@ -205,10 +185,10 @@ export const Step2 = () => {
<Markdown className="qa">{QA}</Markdown>
</S.StepContent>
<Flex style={{ marginTop: 36 }} justify="space-between">
- <Button ghost type="primary" loading={operating} onClick={() =>
setStep(step - 1)}>
+ <Button ghost type="primary" onClick={() => dispatch(previous())}>
Previous Step
</Button>
- <Button type="primary" loading={operating} disabled={!testStaus}
onClick={handleSubmit}>
+ <Button type="primary" disabled={!testStaus} onClick={handleSubmit}>
Next Step
</Button>
</Flex>
diff --git a/config-ui/src/routes/onboard/step-3.tsx
b/config-ui/src/routes/onboard/step-3.tsx
index 4ee900bfa..58dcf85e9 100644
--- a/config-ui/src/routes/onboard/step-3.tsx
+++ b/config-ui/src/routes/onboard/step-3.tsx
@@ -16,16 +16,17 @@
*
*/
-import { useState, useContext, useEffect, useMemo } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { Flex, Button } from 'antd';
import dayjs from 'dayjs';
import API from '@/api';
import { Markdown } from '@/components';
+import { selectOnboard, previous, update } from '@/features/onboard';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { DataScopeRemote, getPluginScopeId } from '@/plugins';
import { operator, formatTime } from '@/utils';
-import { Context } from './context';
import * as S from './styled';
export const Step3 = () => {
@@ -33,7 +34,8 @@ export const Step3 = () => {
const [operating, setOperating] = useState(false);
const [scopes, setScopes] = useState<any[]>([]);
- const { step, records, done, projectName, plugin, setStep, setRecords } =
useContext(Context);
+ const dispatch = useAppDispatch();
+ const { projectName, plugin, records } = useAppSelector(selectOnboard);
useEffect(() => {
fetch(`/onboard/step-3/${plugin}.md`)
@@ -51,7 +53,7 @@ export const Step3 = () => {
return;
}
- const [success] = await operator(
+ const [success, res] = await operator(
async () => {
// 1. create a new project
const { blueprint } = await API.project.create({
@@ -89,7 +91,7 @@ export const Step3 = () => {
// 5. get current run pipeline
const pipeline = await API.blueprint.pipelines(blueprint.id);
- const newRecords = records.map((it) =>
+ return records.map((it) =>
it.plugin !== plugin
? it
: {
@@ -99,17 +101,6 @@ export const Step3 = () => {
scopeName: scopes[0]?.fullName ?? scopes[0].name,
},
);
-
- setRecords(newRecords);
-
- // 6. update store
- await API.store.set('onboard', {
- step: 4,
- records: newRecords,
- done,
- projectName,
- plugin,
- });
},
{
setOperating,
@@ -118,7 +109,7 @@ export const Step3 = () => {
);
if (success) {
- setStep(step + 1);
+ dispatch(update({ records: res }));
}
};
@@ -142,7 +133,7 @@ export const Step3 = () => {
<Markdown className="qa">{QA}</Markdown>
</S.StepContent>
<Flex style={{ marginTop: 36 }} justify="space-between">
- <Button ghost type="primary" loading={operating} onClick={() =>
setStep(step - 1)}>
+ <Button ghost type="primary" loading={operating} onClick={() =>
dispatch(previous())}>
Previous Step
</Button>
<Button type="primary" loading={operating} disabled={!scopes.length}
onClick={handleSubmit}>
diff --git a/config-ui/src/routes/onboard/step-4.tsx
b/config-ui/src/routes/onboard/step-4.tsx
index eb1be9688..332e08b6b 100644
--- a/config-ui/src/routes/onboard/step-4.tsx
+++ b/config-ui/src/routes/onboard/step-4.tsx
@@ -16,19 +16,19 @@
*
*/
-import { useState, useContext, useMemo } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useState, useMemo } from 'react';
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { theme, Progress, Space, Button } from 'antd';
import styled from 'styled-components';
import API from '@/api';
import { ExternalLink } from '@/components';
+import { selectOnboard, selectRecord, update } from '@/features/onboard';
+import { useAppDispatch, useAppSelector } from '@/hooks';
import { useAutoRefresh } from '@/hooks';
import { operator } from '@/utils';
import { Logs } from './components';
-import { Context } from './context';
const Wrapper = styled.div`
margin-top: 150px;
@@ -103,11 +103,9 @@ const getStatus = (data: any) => {
export const Step4 = () => {
const [operating, setOperating] = useState(false);
- const navigate = useNavigate();
-
- const { step, records, done, projectName, plugin, setRecords } =
useContext(Context);
-
- const record = useMemo(() => records.find((it) => it.plugin === plugin),
[plugin, records]);
+ const dispatch = useAppDispatch();
+ const { plugin, records } = useAppSelector(selectOnboard);
+ const record = useAppSelector(selectRecord);
const { data } = useAutoRefresh(
async () => {
@@ -171,32 +169,12 @@ export const Step4 = () => {
token: { green5, orange5, red5 },
} = theme.useToken();
- const handleFinish = async () => {
- const [success] = await operator(
- () =>
- API.store.set('onboard', {
- step,
- records,
- done: true,
- projectName,
- plugin,
- }),
- {
- setOperating,
- },
- );
-
- if (success) {
- navigate('/');
- }
- };
-
const handleRecollectData = async () => {
if (!record) {
return null;
}
- const [success] = await operator(
+ const [success, res] = await operator(
async () => {
// 1. re trigger this bulueprint
await API.blueprint.trigger(record.blueprintId, { skipCollectors:
false, fullSync: false });
@@ -204,7 +182,7 @@ export const Step4 = () => {
// 2. get current run pipeline
const pipeline = await API.blueprint.pipelines(record.blueprintId);
- const newRecords = records.map((it) =>
+ return records.map((it) =>
it.plugin !== plugin
? it
: {
@@ -212,17 +190,6 @@ export const Step4 = () => {
pipelineId: pipeline.pipelines[0].id,
},
);
-
- setRecords(newRecords);
-
- // 3. update store
- await API.store.set('onboard', {
- step: 4,
- records: newRecords,
- done,
- projectName,
- plugin,
- });
},
{
setOperating,
@@ -230,6 +197,7 @@ export const Step4 = () => {
);
if (success) {
+ dispatch(update({ step: 4, records: res }));
}
};
@@ -261,7 +229,7 @@ export const Step4 = () => {
<Button type="primary" onClick={() =>
window.open(DashboardURLMap[plugin])}>
Check Dashboard
</Button>
- <Button loading={operating} onClick={handleFinish}>
+ <Button loading={operating} onClick={() => dispatch(update({
step: 4, done: true }))}>
Finish and Exit
</Button>
</Space>