Skip to content

Commit 465dcef

Browse files
authored
Merge pull request #1716 from iamfaran/environments-selector
Add Environments in the Redux State
2 parents 0650f85 + 5df453d commit 465dcef

File tree

10 files changed

+133
-49
lines changed

10 files changed

+133
-49
lines changed

client/packages/lowcoder/src/constants/reduxActionConstants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ export const ReduxActionTypes = {
172172
/* Enterprise Edition */
173173
FETCH_ENTERPRISE_LICENSE : "FETCH_ENTERPRISE_LICENSE",
174174
SET_ENTERPRISE_LICENSE : "SET_ENTERPRISE_LICENSE",
175+
176+
/* Environments */
177+
FETCH_ENVIRONMENTS : "FETCH_ENVIRONMENTS",
178+
FETCH_ENVIRONMENTS_SUCCESS: "FETCH_ENVIRONMENTS_SUCCESS",
179+
FETCH_ENVIRONMENTS_FAILURE: "FETCH_ENVIRONMENTS_FAILURE",
175180

176181
/* Branding Setting */
177182
FETCH_BRANDING_SETTING : "FETCH_BRANDING_SETTING",

client/packages/lowcoder/src/pages/setting/environments/Environments.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
// client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
22
import React from "react";
33
import { Switch, Route, useRouteMatch } from "react-router-dom";
4-
import { EnvironmentProvider } from "./context/EnvironmentContext";
54
import EnvironmentRoutes from "./routes/EnvironmentRoutes";
65
import EnvironmentsList from "./EnvironmentsList";
76

87
/**
98
* Top-level Environments component
10-
* Provides the EnvironmentProvider at the top level
9+
* No longer needs the EnvironmentProvider since we use Redux
1110
*/
1211
const EnvironmentsSettings: React.FC = () => {
1312
const { path } = useRouteMatch();
1413

1514
return (
16-
<EnvironmentProvider>
17-
<Switch>
18-
{/* Environment list route */}
19-
<Route exact path={path}>
20-
<EnvironmentsList />
21-
</Route>
22-
23-
{/* All routes that need a specific environment */}
24-
<Route path={`${path}/:envId`}>
25-
<EnvironmentRoutes />
26-
</Route>
27-
</Switch>
28-
</EnvironmentProvider>
15+
<Switch>
16+
{/* Environment list route */}
17+
<Route exact path={path}>
18+
<EnvironmentsList />
19+
</Route>
20+
21+
{/* All routes that need a specific environment */}
22+
<Route path={`${path}/:envId`}>
23+
<EnvironmentRoutes />
24+
</Route>
25+
</Switch>
2926
);
3027
};
3128

client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useEffect, useRef } from "react";
22
import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd";
33
import { SearchOutlined, CloudServerOutlined, SyncOutlined, PlusOutlined} from "@ant-design/icons";
44
import { useHistory } from "react-router-dom";
5-
import { useEnvironmentContext } from "./context/EnvironmentContext";
5+
import { useSelector, useDispatch } from "react-redux";
6+
import { selectEnvironments, selectEnvironmentsLoading, selectEnvironmentsError } from "redux/selectors/enterpriseSelectors";
7+
import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
68
import { Environment } from "./types/environment.types";
79
import EnvironmentsTable from "./components/EnvironmentsTable";
810
import CreateEnvironmentModal from "./components/CreateEnvironmentModal";
@@ -17,23 +19,23 @@ const { Title, Text } = Typography;
1719
* Displays a table of environments
1820
*/
1921
const EnvironmentsList: React.FC = () => {
20-
// Use the shared context instead of a local hook
21-
const {
22-
environments,
23-
isLoading,
24-
error,
25-
refreshEnvironments
26-
} = useEnvironmentContext();
22+
// Use Redux state instead of context
23+
const dispatch = useDispatch();
24+
const environments = useSelector(selectEnvironments);
25+
const isLoading = useSelector(selectEnvironmentsLoading);
26+
const error = useSelector(selectEnvironmentsError);
2727

2828
// State for search input
2929
const [searchText, setSearchText] = useState("");
30-
const [isRefreshing, setIsRefreshing] = useState(false);
3130
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
3231
const [isCreating, setIsCreating] = useState(false);
32+
33+
3334

3435
// Hook for navigation
3536
const history = useHistory();
3637

38+
3739
// Filter environments based on search text
3840
const filteredEnvironments = environments.filter((env) => {
3941
const searchLower = searchText.toLowerCase();
@@ -62,18 +64,16 @@ const EnvironmentsList: React.FC = () => {
6264
};
6365

6466
// Handle refresh
65-
const handleRefresh = async () => {
66-
setIsRefreshing(true);
67-
await refreshEnvironments();
68-
setIsRefreshing(false);
67+
const handleRefresh = () => {
68+
dispatch(fetchEnvironments());
6969
};
7070

7171
// Handle create environment
7272
const handleCreateEnvironment = async (environmentData: Partial<Environment>) => {
7373
setIsCreating(true);
7474
try {
7575
await createEnvironment(environmentData);
76-
await refreshEnvironments(); // Refresh the list after creation
76+
dispatch(fetchEnvironments()); // Refresh the list after creation
7777
} catch (error) {
7878
console.error("Failed to create environment:", error);
7979
throw error; // Re-throw to let the modal handle the error display
@@ -153,7 +153,7 @@ const EnvironmentsList: React.FC = () => {
153153
Create Environment
154154
</Button>
155155
<Button
156-
icon={<SyncOutlined spin={isRefreshing} />}
156+
icon={<SyncOutlined spin={isLoading} />}
157157
onClick={handleRefresh}
158158
loading={isLoading}
159159
type="default"

client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import React, { useState, useEffect } from 'react';
33
import { Modal, Form, Select, Checkbox, Button, Spin, Input, Tag, Space, Alert } from 'antd';
44
import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
5+
import { useSelector } from 'react-redux';
6+
import { selectLicensedEnvironments, selectEnvironmentsLoading } from 'redux/selectors/enterpriseSelectors';
57
import { Environment } from '../types/environment.types';
68
import { DeployableItemConfig } from '../types/deployable-item.types';
7-
import { useEnvironmentContext } from '../context/EnvironmentContext';
89
import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils';
910
import { ExclamationCircleOutlined } from '@ant-design/icons';
1011
import { showFirstCredentialOverwriteConfirm, showSecondCredentialOverwriteConfirm } from './credentialConfirmations';
@@ -27,7 +28,8 @@ function DeployItemModal({
2728
onSuccess
2829
}: DeployItemModalProps) {
2930
const [form] = Form.useForm();
30-
const { environments, isLoading } = useEnvironmentContext();
31+
const licensedEnvironments = useSelector(selectLicensedEnvironments);
32+
const isLoading = useSelector(selectEnvironmentsLoading);
3133
const [deploying, setDeploying] = useState(false);
3234
const [credentialConfirmationStep, setCredentialConfirmationStep] = useState(0); // 0: not started, 1: first confirmation, 2: confirmed
3335

@@ -39,8 +41,8 @@ function DeployItemModal({
3941
}, [visible, form]);
4042

4143
// Filter out source environment from target list
42-
const targetEnvironments = environments.filter(
43-
(env: Environment) => env.environmentId !== sourceEnvironment.environmentId && env.isLicensed !== false
44+
const targetEnvironments = licensedEnvironments.filter(
45+
(env: Environment) => env.environmentId !== sourceEnvironment.environmentId
4446
);
4547

4648
// Handle credential checkbox change with double confirmation
@@ -82,7 +84,7 @@ function DeployItemModal({
8284

8385
try {
8486
const values = await form.validateFields();
85-
const targetEnv = environments.find(env => env.environmentId === values.targetEnvId);
87+
const targetEnv = licensedEnvironments.find(env => env.environmentId === values.targetEnvId);
8688

8789
if (!targetEnv) {
8890
messageInstance.error('Target environment not found');

client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import React, {
99
} from "react";
1010
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
1111
import { useParams } from "react-router-dom";
12+
import { useDispatch } from "react-redux";
13+
import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
1214
import { getEnvironmentById, updateEnvironment } from "../services/environments.service";
1315
import { Environment } from "../types/environment.types";
14-
import { useEnvironmentContext } from './EnvironmentContext';
15-
16+
1617
interface SingleEnvironmentContextState {
1718
// Environment data
1819
environment: Environment | null;
@@ -53,8 +54,8 @@ import React, {
5354
const { envId } = useParams<{ envId: string }>();
5455
const environmentId = propEnvironmentId || envId;
5556

56-
// Access the environments context to refresh the list
57-
const { refreshEnvironments } = useEnvironmentContext();
57+
// Use Redux dispatch to refresh environments instead of context
58+
const dispatch = useDispatch();
5859

5960
// State for environment data
6061
const [environment, setEnvironment] = useState<Environment | null>(null);
@@ -103,18 +104,16 @@ import React, {
103104
messageInstance.success("Environment updated successfully");
104105

105106
// Refresh both the single environment and environments list
106-
await Promise.all([
107-
fetchEnvironment(), // Refresh the current environment
108-
refreshEnvironments() // Refresh the environments list
109-
]);
107+
await fetchEnvironment(); // Refresh the current environment
108+
dispatch(fetchEnvironments()); // Refresh the environments list using Redux
110109

111110
return updatedEnv;
112111
} catch (err) {
113112
const errorMessage = err instanceof Error ? err.message : "Failed to update environment";
114113
messageInstance.error(errorMessage);
115114
throw err;
116115
}
117-
}, [environment, environmentId, fetchEnvironment, refreshEnvironments]);
116+
}, [environment, environmentId, fetchEnvironment, dispatch]);
118117

119118
// Load environment data when the component mounts or environmentId changes
120119
useEffect(() => {

client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import { BrandingConfig, BrandingSettingResponse, EnterpriseLicenseResponse } from "@lowcoder-ee/api/enterpriseApi";
22
import { createReducer } from "@lowcoder-ee/util/reducerUtils";
33
import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants";
4-
4+
import { Environment } from "pages/setting/environments/types/environment.types";
55
export interface EnterpriseReduxState {
66
enterprise: EnterpriseLicenseResponse,
77
globalBranding?: BrandingConfig,
88
workspaceBranding?: BrandingConfig,
9+
environments: Environment[],
10+
environmentsLoading: boolean,
11+
environmentsError: string | null,
912
}
1013

1114
const initialState: EnterpriseReduxState = {
1215
enterprise: {
1316
eeActive: false,
1417
remainingAPICalls: 0,
1518
eeLicenses: [],
16-
}
19+
},
20+
environments: [],
21+
environmentsLoading: false,
22+
environmentsError: null,
1723
};
1824

1925
const enterpriseReducer = createReducer(initialState, {
@@ -38,6 +44,29 @@ const enterpriseReducer = createReducer(initialState, {
3844
...state,
3945
workspaceBranding: action.payload,
4046
}),
47+
48+
[ReduxActionTypes.FETCH_ENVIRONMENTS]: (
49+
state: EnterpriseReduxState
50+
) => ({
51+
...state,
52+
environmentsLoading: true,
53+
}),
54+
[ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS]: (
55+
state: EnterpriseReduxState,
56+
action: ReduxAction<Environment[]>
57+
) => ({
58+
...state,
59+
environments: action.payload,
60+
environmentsLoading: false,
61+
}),
62+
[ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE]: (
63+
state: EnterpriseReduxState,
64+
action: ReduxAction<string>
65+
) => ({
66+
...state,
67+
environmentsLoading: false,
68+
environmentsError: action.payload,
69+
}),
4170
});
4271

4372
export default enterpriseReducer;

client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EnterpriseLicenseResponse, FetchBrandingSettingPayload } from "@lowcoder-ee/api/enterpriseApi";
22
import { ReduxActionTypes } from "constants/reduxActionConstants";
3+
import { Environment } from "pages/setting/environments/types/environment.types";
34

45
export const fetchEnterpriseLicense = () => ({
56
type: ReduxActionTypes.FETCH_ENTERPRISE_LICENSE,
@@ -16,3 +17,18 @@ export const fetchBrandingSetting = (payload: FetchBrandingSettingPayload) => {
1617
payload,
1718
};
1819
};
20+
21+
export const fetchEnvironments = () => ({
22+
type: ReduxActionTypes.FETCH_ENVIRONMENTS,
23+
});
24+
25+
export const fetchEnvironmentsSuccess = (environments: Environment[]) => ({
26+
type: ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS,
27+
payload: environments,
28+
});
29+
30+
export const fetchEnvironmentsFailure = (error: string) => ({
31+
type: ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE,
32+
payload: error,
33+
});
34+

client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { call, put, takeLatest } from 'redux-saga/effects';
22
import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants";
3-
import { setEnterpriseLicense } from "redux/reduxActions/enterpriseActions";
3+
import { setEnterpriseLicense, fetchEnvironmentsSuccess, fetchEnvironmentsFailure } from "redux/reduxActions/enterpriseActions";
44
import { BrandingSettingResponse, EnterpriseLicenseResponse, FetchBrandingSettingPayload, getBranding, getEnterpriseLicense } from "api/enterpriseApi";
5+
import { getEnvironmentsWithLicenseStatus } from "pages/setting/environments/services/environments.service";
6+
import { Environment } from "pages/setting/environments/types/environment.types";
7+
58
import { AxiosResponse } from 'axios';
69

710
function* fetchEnterpriseLicenseSaga(): Generator<any, void, EnterpriseLicenseResponse> {
@@ -14,6 +17,16 @@ function* fetchEnterpriseLicenseSaga(): Generator<any, void, EnterpriseLicenseRe
1417
}
1518
}
1619

20+
function* fetchEnvironmentsSaga(): Generator<any, void, Environment[]> {
21+
try {
22+
const environments: Environment[] = yield call(getEnvironmentsWithLicenseStatus);
23+
yield put(fetchEnvironmentsSuccess(environments));
24+
} catch (error) {
25+
console.error('Failed to fetch environments:', error);
26+
yield put(fetchEnvironmentsFailure(error as string));
27+
}
28+
}
29+
1730
function* fetchBrandingSettingSaga(action: ReduxAction<FetchBrandingSettingPayload>) {
1831
try {
1932
const response: BrandingSettingResponse = yield getBranding(action.payload.orgId);
@@ -45,4 +58,5 @@ function* fetchBrandingSettingSaga(action: ReduxAction<FetchBrandingSettingPaylo
4558
export default function* enterpriseSagas() {
4659
yield takeLatest(ReduxActionTypes.FETCH_ENTERPRISE_LICENSE, fetchEnterpriseLicenseSaga);
4760
yield takeLatest(ReduxActionTypes.FETCH_BRANDING_SETTING, fetchBrandingSettingSaga);
61+
yield takeLatest(ReduxActionTypes.FETCH_ENVIRONMENTS, fetchEnvironmentsSaga);
4862
}

client/packages/lowcoder/src/redux/selectors/enterpriseSelectors.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AppState } from "../reducers";
22

3+
34
export const selectEnterpriseEditionStatus = (state: AppState) =>
45
state.ui.enterprise?.enterprise?.eeActive ?? false;
56

@@ -25,3 +26,23 @@ export const getGlobalBrandingSetting = (state: AppState) => {
2526
export const getWorkspaceBrandingSetting = (state: AppState) => {
2627
return state.ui.enterprise?.workspaceBranding;
2728
}
29+
// Environment selectors
30+
export const selectEnvironments = (state: AppState) =>
31+
state.ui.enterprise?.environments ?? [];
32+
33+
export const selectEnvironmentsLoading = (state: AppState) =>
34+
state.ui.enterprise?.environmentsLoading ?? false;
35+
36+
export const selectEnvironmentsError = (state: AppState) =>
37+
state.ui.enterprise?.environmentsError ?? null;
38+
39+
export const selectUnlicensedEnvironments = (state: AppState) => {
40+
const environments = state.ui.enterprise?.environments ?? [];
41+
return environments.filter(env => env.isLicensed === false);
42+
};
43+
44+
export const selectLicensedEnvironments = (state: AppState) => {
45+
const environments = state.ui.enterprise?.environments ?? [];
46+
return environments.filter(env => env.isLicensed !== false);
47+
};
48+

client/packages/lowcoder/src/util/context/EnterpriseContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { createContext, useContext, useState, useEffect } from 'react';
2-
import { fetchEnterpriseLicense } from 'redux/reduxActions/enterpriseActions';
2+
import { fetchEnterpriseLicense, fetchEnvironments } from 'redux/reduxActions/enterpriseActions';
33
import { selectEnterpriseEditionStatus } from '@lowcoder-ee/redux/selectors/enterpriseSelectors';
44
import { useDispatch, useSelector } from 'react-redux';
55
import { isEEEnvironment } from "util/envUtils";
@@ -23,6 +23,7 @@ export const EnterpriseProvider: React.FC<ProviderProps> = ({ children }) => {
2323
if (isEEEnvironment()) {
2424
// Fetch the enterprise license only if we're in an EE environment
2525
dispatch(fetchEnterpriseLicense());
26+
dispatch(fetchEnvironments());
2627
} else {
2728
// Set the state to false for non-EE environments
2829
// setEEActiveState(false);

0 commit comments

Comments
 (0)