Skip to content

Commit

Permalink
refactor: use redux to rewrite onboard (#8012)
Browse files Browse the repository at this point in the history
  • Loading branch information
mintsweet authored Sep 9, 2024
1 parent d74af26 commit 55135f9
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 288 deletions.
2 changes: 2 additions & 0 deletions config-ui/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
131 changes: 131 additions & 0 deletions config-ui/src/features/onboard/slice.ts
Original file line number Diff line number Diff line change
@@ -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);
};
10 changes: 9 additions & 1 deletion config-ui/src/routes/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -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 />;
};
7 changes: 0 additions & 7 deletions config-ui/src/routes/app/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*
*/

import { redirect } from 'react-router-dom';
import { intersection } from 'lodash';

import API from '@/api';
Expand All @@ -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();

Expand Down
10 changes: 8 additions & 2 deletions config-ui/src/routes/layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
Expand Down Expand Up @@ -77,6 +79,10 @@ export const Layout = () => {
throw error.message;
}

if (!initial) {
return <Navigate to="/onboard" />;
}

return (
<AntdLayout style={{ height: '100%', overflow: 'hidden' }}>
<Helmet>
Expand Down
44 changes: 16 additions & 28 deletions config-ui/src/routes/onboard/components/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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;
}

Expand All @@ -64,7 +61,7 @@ export const OnboardCard = ({ style }: Props) => {
);

const status = useMemo(() => {
if (!data || data.step !== 4) {
if (step !== 4) {
return 'prepare';
}

Expand All @@ -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;
}

Expand All @@ -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 }} />}
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
Loading

0 comments on commit 55135f9

Please sign in to comment.