Skip to content

[Fix]: #1848 remove unnecessary calls and optimization #1877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/packages/lowcoder/src/api/datasourceApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ export class DatasourceApi extends Api {
return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`, {...res});
}

static getDatasourceById(id: string): AxiosPromise<GenericApiResponse<Datasource>> {
return Api.get(`${DatasourceApi.url}/${id}`);
}

static createDatasource(
datasourceConfig: Partial<Datasource>
): AxiosPromise<GenericApiResponse<Datasource>> {
Expand Down
10 changes: 10 additions & 0 deletions client/packages/lowcoder/src/pages/ApplicationV2/FolderView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Helmet } from "react-helmet";
import { trans } from "i18n";
import {ApplicationPaginationType} from "@lowcoder-ee/util/pagination/type";
import {fetchFolderElements} from "@lowcoder-ee/util/pagination/axios";
import { fetchFolderElements as fetchFolderElementsRedux } from "../../redux/reduxActions/folderActions";
import { getUser } from "../../redux/selectors/usersSelectors";

function getBreadcrumbs(
folder: FolderMeta,
Expand Down Expand Up @@ -52,6 +54,7 @@ export function FolderView() {

const element = useSelector(folderElementsSelector);
const allFolders = useSelector(foldersSelector);
const user = useSelector(getUser);

const folder = allFolders.filter((f) => f.folderId === folderId)[0] || {};
const breadcrumbs = getBreadcrumbs(folder, allFolders, [
Expand All @@ -61,6 +64,13 @@ export function FolderView() {
},
]);

// Fetch folder data for breadcrumbs if not available
useEffect(() => {
if (allFolders.length === 0 && user.currentOrgId) {
dispatch(fetchFolderElementsRedux({}));
}
}, [allFolders.length, user.currentOrgId, dispatch]);

useEffect( () => {
try{
fetchFolderElements({
Expand Down
67 changes: 35 additions & 32 deletions client/packages/lowcoder/src/pages/ApplicationV2/HomeResOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { HomeResTypeEnum } from "../../types/homeRes";
import { exportApplicationAsJSONFile } from "./components/AppImport";
import { CustomModal, EditPopover, EditPopoverItemType, PointIcon } from "lowcoder-design";
import { HomeResInfo } from "../../util/homeResUtils";
import { recycleApplication } from "../../redux/reduxActions/applicationActions";
import { deleteFolder } from "../../redux/reduxActions/folderActions";
import { useDispatch } from "react-redux";
import React, { useState } from "react";
import styled from "styled-components";
Expand All @@ -13,6 +11,9 @@ import { useParams } from "react-router-dom";
import { AppTypeEnum } from "constants/applicationConstants";
import { CopyModal } from "pages/common/copyModal";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import ApplicationApi from "../../api/applicationApi";
import { FolderApi } from "../../api/folderApi";
import { ReduxActionTypes } from "constants/reduxActionConstants";

const PopoverIcon = styled(PointIcon)`
cursor: pointer;
Expand Down Expand Up @@ -80,23 +81,20 @@ export const HomeResOptions = (props: {
type: HomeResInfo[res.type].name,
name: <b>{res.name}</b>,
}),
onConfirm: () =>{
new Promise((resolve, reject) => {
dispatch(
recycleApplication(
{ applicationId: res.id, folderId: folderId },
() => {
messageInstance.success(trans("success"));
resolve(true);
},
() => reject()
)
);
onConfirm: async () => {
try {
await ApplicationApi.recycleApplication({
applicationId: res.id,
folderId: folderId || ""
});
messageInstance.success(trans("success"));
setTimeout(() => {
setModify(!modify);
}, 200);
})

} catch (error) {
console.error("Failed to recycle application:", error);
messageInstance.error("Failed to delete application");
}
},
confirmBtnType: "delete",
okText: trans("home.moveToTrash"),
Expand All @@ -122,22 +120,27 @@ export const HomeResOptions = (props: {
type: HomeResInfo[res.type].name.toLowerCase(),
name: <b>{res.name}</b>,
}),
onConfirm: () =>{
new Promise((resolve, reject) => {
dispatch(
deleteFolder(
{ folderId: res.id, parentFolderId: folderId },
() => {
messageInstance.success(trans("home.deleteSuccessMsg"));
resolve(true);
},
() => reject()
)
);
})
setTimeout(() => {
setModify(!modify);
}, 200);
onConfirm: async () => {
try {
await FolderApi.deleteFolder({
folderId: res.id,
parentFolderId: folderId || ""
});

// Update Redux state to remove deleted folder from dropdown
dispatch({
type: ReduxActionTypes.DELETE_FOLDER_SUCCESS,
payload: { folderId: res.id, parentFolderId: folderId || "" }
});

messageInstance.success(trans("home.deleteSuccessMsg"));
setTimeout(() => {
setModify(!modify);
}, 200);
} catch (error) {
console.error("Failed to delete folder:", error);
messageInstance.error("Failed to delete folder");
}
},
confirmBtnType: "delete",
okText: trans("delete"),
Expand Down
45 changes: 20 additions & 25 deletions client/packages/lowcoder/src/pages/ApplicationV2/TrashTableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import styled from "styled-components";
import { useDispatch } from "react-redux";
import { HomeResInfo } from "../../util/homeResUtils";
import { HomeResTypeEnum } from "../../types/homeRes";
import { deleteApplication, restoreApplication } from "../../redux/reduxActions/applicationActions";
import { HomeRes } from "./HomeLayout";
import { trans, transToNode } from "../../i18n";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { BrandedIcon } from "@lowcoder-ee/components/BrandedIcon";
import ApplicationApi from "../../api/applicationApi";

const OperationWrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -123,17 +123,18 @@ export const TrashTableView = (props: { resources: HomeRes[] , setModify: any, m
style={{ padding: "0 8px", width: "fit-content", minWidth: "52px" }}
buttonType={"blue"}
className={"home-datasource-edit-button"}
onClick={() =>{
dispatch(
restoreApplication({ applicationId: item.id }, () => {
messageInstance.success(trans("home.recoverSuccessMsg"));
})
)
onClick={async () => {
try {
await ApplicationApi.restoreApplication({ applicationId: item.id });
messageInstance.success(trans("home.recoverSuccessMsg"));
setTimeout(() => {
setModify(!modify);
setModify(!modify);
}, 200);
}
}
} catch (error) {
console.error("Failed to restore application:", error);
messageInstance.error("Failed to restore application");
}
}}
>
{trans("recover")}
</EditBtn>
Expand All @@ -148,27 +149,21 @@ export const TrashTableView = (props: { resources: HomeRes[] , setModify: any, m
type: HomeResInfo[item.type].name.toLowerCase(),
name: <b>{item.name}</b>,
}),
onConfirm: () =>{
new Promise((resolve, reject) => {
dispatch(
deleteApplication(
{ applicationId: item.id },
() => {
messageInstance.success(trans("home.deleteSuccessMsg"));
resolve(true);
},
() => reject()
)
);
})
onConfirm: async () => {
try {
await ApplicationApi.deleteApplication({ applicationId: item.id });
messageInstance.success(trans("home.deleteSuccessMsg"));
setTimeout(() => {
setModify(!modify);
setModify(!modify);
}, 200);
} catch (error) {
console.error("Failed to delete application:", error);
messageInstance.error("Failed to delete application permanently");
}
},
confirmBtnType: "delete",
okText: trans("delete"),
})

}
style={{ marginLeft: "12px", width: "76px" }}
>
Expand Down
9 changes: 0 additions & 9 deletions client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,6 @@ export default function ApplicationHome() {
setIsPreloadCompleted(true);
}, [org, orgHomeId]);

useEffect(() => {
// Check if we need to fetch data (either no folders or no applications)
if (allFoldersCount !== 0 && allAppCount !== 0) {
return;
}

user.currentOrgId && dispatch(fetchFolderElements({}));
}, [dispatch, allFoldersCount, allAppCount, user.currentOrgId]);

if (fetchingUser || !isPreloadCompleted) {
return <ProductLoading />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import styled from "styled-components";
import history from "../../util/history";
import { default as Button } from "antd/es/button";
import { useCallback, useMemo, useState } from "react";
import { Spin } from "antd";
import { useCallback, useEffect, useMemo, useState } from "react";
import { CopyTextButton, DocIcon, PackUpIcon, TacoButton } from "lowcoder-design";
import { useDatasourceForm } from "./form/useDatasourceForm";
import { useParams } from "react-router-dom";
import { DATASOURCE_URL } from "../../constants/routesURL";
import { useSelector } from "react-redux";
import { getDataSource, getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
import { getDataSourceTypes } from "../../redux/selectors/datasourceSelectors";
import { trans } from "i18n";
import { DatasourceType } from "@lowcoder-ee/constants/queryConstants";
import { getDatasourceTutorial } from "@lowcoder-ee/util/tutorialUtils";
import { getDataSourceFormManifest } from "./getDataSourceFormManifest";
import DataSourceIcon from "components/DataSourceIcon";
import { Helmet } from "react-helmet";

import { DatasourceApi } from "@lowcoder-ee/api/datasourceApi";
import { DatasourceInfo } from "@lowcoder-ee/api/datasourceApi";
import { GenericApiResponse } from "../../api/apiResponses";
import { Datasource } from "@lowcoder-ee/constants/datasourceConstants";
import { AxiosResponse } from "axios";
const Wrapper = styled.div`
display: flex;
justify-content: center;
Expand Down Expand Up @@ -154,16 +159,44 @@ type DatasourcePathParams = {

export const DatasourceEditPage = () => {
const { datasourceId, datasourceType } = useParams<DatasourcePathParams>();
const datasourceList = useSelector(getDataSource);
const datasourceTypes = useSelector(getDataSourceTypes);
const [isReady, setIsReady] = useState(true);

const datasourceInfo = useMemo(() => {

const [datasourceInfo, setDatasourceInfo] = useState<DatasourceInfo | undefined>();
const [loading, setLoading] = useState(false);

// Fetch individual datasource when editing
useEffect(() => {
if (!datasourceId) {
return undefined;
setDatasourceInfo(undefined);
return;
}
return datasourceList.find((info) => info.datasource.id === datasourceId);
}, [datasourceId, datasourceList]);

const fetchDatasource = async () => {
setLoading(true);
try {
const response: AxiosResponse<GenericApiResponse<Datasource>> = await DatasourceApi.getDatasourceById(datasourceId);
if (response.data.success) {
// Transform to DatasourceInfo format
setDatasourceInfo({
datasource: response.data.data,
edit: true, // Assume editable since user reached edit page
});
} else {
console.error('API returned error:', response.data);
setDatasourceInfo(undefined);
}
} catch (error: any) {
console.error('Failed to fetch datasource:', error);
setDatasourceInfo(undefined);
} finally {
setLoading(false);
}
};

fetchDatasource();
}, [datasourceId]);

const dataSourceTypeInfo = useMemo(() => {
if (datasourceId) {
Expand All @@ -181,6 +214,26 @@ export const DatasourceEditPage = () => {
setIsReady(isReady);
}, []);

// Show loading state while fetching datasource
if (loading) {
return (
<Wrapper>
<ContentWrapper>
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '400px',
flexDirection: 'column',
gap: '16px'
}}>
<Spin size="large" />
</div>
</ContentWrapper>
</Wrapper>
);
}

if (!finalDataSourceType) {
return null;
}
Expand Down
11 changes: 7 additions & 4 deletions client/packages/lowcoder/src/pages/datasource/datasourceList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import styled from "styled-components";
import { EditPopover, PointIcon, Search, TacoButton } from "lowcoder-design";
import React, {useEffect, useState} from "react";
import {useEffect, useState} from "react";
import { useDispatch, useSelector } from "react-redux";
import { getDataSource, getDataSourceLoading, getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors";
import { getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors";
import { deleteDatasource } from "../../redux/reduxActions/datasourceActions";
import { isEmpty } from "lodash";
import history from "../../util/history";
Expand Down Expand Up @@ -113,7 +113,6 @@ export const DatasourceList = () => {
const [modify, setModify] = useState(false);
const currentUser = useSelector(getUser);
const orgId = currentUser.currentOrgId;
const datasourceLoading = useSelector(getDataSourceLoading);
const plugins = useSelector(getDataSourceTypesMap);
interface ElementsState {
elements: DatasourceInfo[];
Expand All @@ -123,6 +122,7 @@ export const DatasourceList = () => {
const [elements, setElements] = useState<ElementsState>({ elements: [], total: 0 });
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [paginationLoading, setPaginationLoading] = useState(false);

useEffect(()=> {
const timer = setTimeout(() => {
Expand All @@ -133,6 +133,7 @@ export const DatasourceList = () => {
}, [searchValue])

useEffect( () => {
setPaginationLoading(true);
fetchDatasourcePagination(
{
orgId: orgId,
Expand All @@ -146,6 +147,8 @@ export const DatasourceList = () => {
}
else
console.error("ERROR: fetchFolderElements", result.error)
}).finally(() => {
setPaginationLoading(false);
})
}, [currentPage, pageSize, searchValues, modify]
)
Expand Down Expand Up @@ -195,7 +198,7 @@ export const DatasourceList = () => {
<BodyWrapper>
<StyledTable
loading={{
spinning: datasourceLoading,
spinning: paginationLoading,
indicator: <LoadingOutlined spin style={{ fontSize: 30 }} />
}}
rowClassName={(record: any) => (!record.edit ? "datasource-can-not-edit" : "")}
Expand Down
Loading
Loading