From 1656f73110ae72ea99a9b165e25e8636a20674b1 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Tue, 27 May 2025 19:45:57 +0500 Subject: [PATCH 1/4] add environments in redux --- .../src/constants/reduxActionConstants.ts | 5 +++ .../reducers/uiReducers/enterpriseReducer.ts | 33 +++++++++++++++++-- .../redux/reduxActions/enterpriseActions.ts | 16 +++++++++ .../src/redux/sagas/enterpriseSagas.ts | 16 ++++++++- .../redux/selectors/enterpriseSelectors.ts | 4 +++ .../src/util/context/EnterpriseContext.tsx | 3 +- 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder/src/constants/reduxActionConstants.ts b/client/packages/lowcoder/src/constants/reduxActionConstants.ts index 6dd42de3d6..aea840a5c6 100644 --- a/client/packages/lowcoder/src/constants/reduxActionConstants.ts +++ b/client/packages/lowcoder/src/constants/reduxActionConstants.ts @@ -172,6 +172,11 @@ export const ReduxActionTypes = { /* Enterprise Edition */ FETCH_ENTERPRISE_LICENSE : "FETCH_ENTERPRISE_LICENSE", SET_ENTERPRISE_LICENSE : "SET_ENTERPRISE_LICENSE", + + /* Environments */ + FETCH_ENVIRONMENTS : "FETCH_ENVIRONMENTS", + FETCH_ENVIRONMENTS_SUCCESS: "FETCH_ENVIRONMENTS_SUCCESS", + FETCH_ENVIRONMENTS_FAILURE: "FETCH_ENVIRONMENTS_FAILURE", /* Branding Setting */ FETCH_BRANDING_SETTING : "FETCH_BRANDING_SETTING", diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts index 4f2c81459b..83ac8cd7c3 100644 --- a/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts +++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts @@ -1,11 +1,14 @@ import { BrandingConfig, BrandingSettingResponse, EnterpriseLicenseResponse } from "@lowcoder-ee/api/enterpriseApi"; import { createReducer } from "@lowcoder-ee/util/reducerUtils"; import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants"; - +import { Environment } from "pages/setting/environments/types/environment.types"; export interface EnterpriseReduxState { enterprise: EnterpriseLicenseResponse, globalBranding?: BrandingConfig, workspaceBranding?: BrandingConfig, + environments: Environment[], + environmentsLoading: boolean, + environmentsError: string | null, } const initialState: EnterpriseReduxState = { @@ -13,7 +16,10 @@ const initialState: EnterpriseReduxState = { eeActive: false, remainingAPICalls: 0, eeLicenses: [], - } + }, + environments: [], + environmentsLoading: false, + environmentsError: null, }; const enterpriseReducer = createReducer(initialState, { @@ -38,6 +44,29 @@ const enterpriseReducer = createReducer(initialState, { ...state, workspaceBranding: action.payload, }), + + [ReduxActionTypes.FETCH_ENVIRONMENTS]: ( + state: EnterpriseReduxState + ) => ({ + ...state, + environmentsLoading: true, + }), + [ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS]: ( + state: EnterpriseReduxState, + action: ReduxAction + ) => ({ + ...state, + environments: action.payload, + environmentsLoading: false, + }), + [ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE]: ( + state: EnterpriseReduxState, + action: ReduxAction + ) => ({ + ...state, + environmentsLoading: false, + environmentsError: action.payload, + }), }); export default enterpriseReducer; diff --git a/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts b/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts index 85e2b9b195..2231c45c28 100644 --- a/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts +++ b/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts @@ -1,5 +1,6 @@ import { EnterpriseLicenseResponse, FetchBrandingSettingPayload } from "@lowcoder-ee/api/enterpriseApi"; import { ReduxActionTypes } from "constants/reduxActionConstants"; +import { Environment } from "pages/setting/environments/types/environment.types"; export const fetchEnterpriseLicense = () => ({ type: ReduxActionTypes.FETCH_ENTERPRISE_LICENSE, @@ -16,3 +17,18 @@ export const fetchBrandingSetting = (payload: FetchBrandingSettingPayload) => { payload, }; }; + +export const fetchEnvironments = () => ({ + type: ReduxActionTypes.FETCH_ENVIRONMENTS, +}); + +export const fetchEnvironmentsSuccess = (environments: Environment[]) => ({ + type: ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS, + payload: environments, +}); + +export const fetchEnvironmentsFailure = (error: string) => ({ + type: ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE, + payload: error, +}); + diff --git a/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts b/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts index 20d19e37f0..bacadfa4a0 100644 --- a/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts +++ b/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts @@ -1,7 +1,10 @@ import { call, put, takeLatest } from 'redux-saga/effects'; import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants"; -import { setEnterpriseLicense } from "redux/reduxActions/enterpriseActions"; +import { setEnterpriseLicense, fetchEnvironmentsSuccess, fetchEnvironmentsFailure } from "redux/reduxActions/enterpriseActions"; import { BrandingSettingResponse, EnterpriseLicenseResponse, FetchBrandingSettingPayload, getBranding, getEnterpriseLicense } from "api/enterpriseApi"; +import { getEnvironments } from "pages/setting/environments/services/environments.service"; +import { Environment } from "pages/setting/environments/types/environment.types"; + import { AxiosResponse } from 'axios'; function* fetchEnterpriseLicenseSaga(): Generator { @@ -14,6 +17,16 @@ function* fetchEnterpriseLicenseSaga(): Generator { + try { + const environments: Environment[] = yield call(getEnvironments); + yield put(fetchEnvironmentsSuccess(environments)); + } catch (error) { + console.error('Failed to fetch environments:', error); + yield put(fetchEnvironmentsFailure(error as string)); + } +} + function* fetchBrandingSettingSaga(action: ReduxAction) { try { const response: BrandingSettingResponse = yield getBranding(action.payload.orgId); @@ -45,4 +58,5 @@ function* fetchBrandingSettingSaga(action: ReduxAction state.ui.enterprise?.enterprise?.eeActive ?? false; @@ -25,3 +26,6 @@ export const getGlobalBrandingSetting = (state: AppState) => { export const getWorkspaceBrandingSetting = (state: AppState) => { return state.ui.enterprise?.workspaceBranding; } +// Environment selectors +export const selectEnvironments = (state: AppState) => + state.ui.enterprise?.environments ?? []; \ No newline at end of file diff --git a/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx b/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx index 57f821cabf..d377810687 100644 --- a/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx +++ b/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; -import { fetchEnterpriseLicense } from 'redux/reduxActions/enterpriseActions'; +import { fetchEnterpriseLicense, fetchEnvironments } from 'redux/reduxActions/enterpriseActions'; import { selectEnterpriseEditionStatus } from '@lowcoder-ee/redux/selectors/enterpriseSelectors'; import { useDispatch, useSelector } from 'react-redux'; import { isEEEnvironment } from "util/envUtils"; @@ -23,6 +23,7 @@ export const EnterpriseProvider: React.FC = ({ children }) => { if (isEEEnvironment()) { // Fetch the enterprise license only if we're in an EE environment dispatch(fetchEnterpriseLicense()); + dispatch(fetchEnvironments()); } else { // Set the state to false for non-EE environments // setEEActiveState(false); From abd6c3625b2af5d520b9bf6eb81f53db88b0b233 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Tue, 27 May 2025 21:30:40 +0500 Subject: [PATCH 2/4] use redux environments in environments UI --- .../setting/environments/Environments.tsx | 27 ++++++-------- .../setting/environments/EnvironmentsList.tsx | 37 +++++++++++-------- .../components/DeployItemModal.tsx | 6 ++- .../context/SingleEnvironmentContext.tsx | 17 ++++----- .../src/redux/sagas/enterpriseSagas.ts | 4 +- .../redux/selectors/enterpriseSelectors.ts | 8 +++- 6 files changed, 54 insertions(+), 45 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx index 41081435de..afdb01fca9 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx @@ -1,31 +1,28 @@ // client/packages/lowcoder/src/pages/setting/environments/Environments.tsx import React from "react"; import { Switch, Route, useRouteMatch } from "react-router-dom"; -import { EnvironmentProvider } from "./context/EnvironmentContext"; import EnvironmentRoutes from "./routes/EnvironmentRoutes"; import EnvironmentsList from "./EnvironmentsList"; /** * Top-level Environments component - * Provides the EnvironmentProvider at the top level + * No longer needs the EnvironmentProvider since we use Redux */ const Environments: React.FC = () => { const { path } = useRouteMatch(); return ( - - - {/* Environment list route */} - - - - - {/* All routes that need a specific environment */} - - - - - + + {/* Environment list route */} + + + + + {/* All routes that need a specific environment */} + + + + ); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx index 8d8fa2c435..1c7e787946 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx @@ -1,8 +1,10 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd"; import { SearchOutlined, CloudServerOutlined, SyncOutlined, PlusOutlined} from "@ant-design/icons"; import { useHistory } from "react-router-dom"; -import { useEnvironmentContext } from "./context/EnvironmentContext"; +import { useSelector, useDispatch } from "react-redux"; +import { selectEnvironments, selectEnvironmentsLoading, selectEnvironmentsError } from "redux/selectors/enterpriseSelectors"; +import { fetchEnvironments } from "redux/reduxActions/enterpriseActions"; import { Environment } from "./types/environment.types"; import EnvironmentsTable from "./components/EnvironmentsTable"; import CreateEnvironmentModal from "./components/CreateEnvironmentModal"; @@ -17,23 +19,28 @@ const { Title, Text } = Typography; * Displays a table of environments */ const EnvironmentsList: React.FC = () => { - // Use the shared context instead of a local hook - const { - environments, - isLoading, - error, - refreshEnvironments - } = useEnvironmentContext(); + // Use Redux state instead of context + const dispatch = useDispatch(); + const environments = useSelector(selectEnvironments); + const isLoading = useSelector(selectEnvironmentsLoading); + const error = useSelector(selectEnvironmentsError); // State for search input const [searchText, setSearchText] = useState(""); - const [isRefreshing, setIsRefreshing] = useState(false); const [isCreateModalVisible, setIsCreateModalVisible] = useState(false); const [isCreating, setIsCreating] = useState(false); // Hook for navigation const history = useHistory(); + // Load environments on component mount + useEffect(() => { + // Only fetch if environments are not already loaded + if (environments.length === 0 && !isLoading) { + dispatch(fetchEnvironments()); + } + }, [dispatch, environments.length, isLoading]); + // Filter environments based on search text const filteredEnvironments = environments.filter((env) => { const searchLower = searchText.toLowerCase(); @@ -62,10 +69,8 @@ const EnvironmentsList: React.FC = () => { }; // Handle refresh - const handleRefresh = async () => { - setIsRefreshing(true); - await refreshEnvironments(); - setIsRefreshing(false); + const handleRefresh = () => { + dispatch(fetchEnvironments()); }; // Handle create environment @@ -73,7 +78,7 @@ const EnvironmentsList: React.FC = () => { setIsCreating(true); try { await createEnvironment(environmentData); - await refreshEnvironments(); // Refresh the list after creation + dispatch(fetchEnvironments()); // Refresh the list after creation } catch (error) { console.error("Failed to create environment:", error); throw error; // Re-throw to let the modal handle the error display @@ -153,7 +158,7 @@ const EnvironmentsList: React.FC = () => { Create Environment