diff --git a/client/packages/lowcoder/index.html b/client/packages/lowcoder/index.html
index f3019a0cd..b9f940e01 100644
--- a/client/packages/lowcoder/index.html
+++ b/client/packages/lowcoder/index.html
@@ -28,6 +28,8 @@
display: flex;
pointer-events: none;
flex-direction: column;
+ top: 0;
+ z-index: 10000;
}
#loading svg {
animation: breath 1s linear infinite;
diff --git a/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx b/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx
index c8a0f093e..4d827381c 100644
--- a/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx
+++ b/client/packages/lowcoder/src/components/DraggableTree/DraggableItem.tsx
@@ -15,7 +15,7 @@ const Wrapper = styled.div<{
$itemHeight?: number;
}>`
position: relative;
- width: 100%;
+ width: auto;
height: ${(props) => props.$itemHeight ?? 30}px;
/* border: 1px solid #d7d9e0; */
border-radius: 4px;
diff --git a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx
index 68c355ec3..3d49e438a 100644
--- a/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx
+++ b/client/packages/lowcoder/src/components/DraggableTree/DroppableMenuItem.tsx
@@ -6,6 +6,7 @@ import { DraggableTreeContext } from "./DraggableTreeContext";
import DroppablePlaceholder from "./DroppablePlaceHolder";
import { DraggableTreeNode, DraggableTreeNodeItemRenderProps, IDragData, IDropData } from "./types";
import { checkDroppableFlag } from "./util";
+import { Flex } from "antd";
const DraggableMenuItemWrapper = styled.div`
position: relative;
@@ -88,29 +89,34 @@ export default function DraggableMenuItem(props: IDraggableMenuItemProps) {
disabled={isDragging || disabled}
/>
)}
- {
- setDragNodeRef(node);
- setDropNodeRef(node);
- }}
- {...dragListeners}
- >
- {renderContent?.({
- node: item,
- isOver,
- path,
- isOverlay,
- hasChildren: items.length > 0,
- dragging: !!(isDragging || parentDragging),
- isFolded: isFold,
- onDelete: () => onDelete?.(path),
- onToggleFold: () => context.toggleFold(id),
- }) || null}
-
+
+ {
+ setDragNodeRef(node);
+ setDropNodeRef(node);
+ }}
+ {...dragListeners}
+ >
+ ⣿
+
+
+ {renderContent?.({
+ node: item,
+ isOver,
+ path,
+ isOverlay,
+ hasChildren: items.length > 0,
+ dragging: !!(isDragging || parentDragging),
+ isFolded: isFold,
+ onDelete: () => onDelete?.(path),
+ onToggleFold: () => context.toggleFold(id),
+ }) || null}
+
+
{items.length > 0 && !isFold && (
diff --git a/client/packages/lowcoder/src/comps/comps/rootComp.tsx b/client/packages/lowcoder/src/comps/comps/rootComp.tsx
index 5fede0b07..83fe577c9 100644
--- a/client/packages/lowcoder/src/comps/comps/rootComp.tsx
+++ b/client/packages/lowcoder/src/comps/comps/rootComp.tsx
@@ -34,7 +34,7 @@ import { ExternalEditorContext } from "util/context/ExternalEditorContext";
import { useUserViewMode } from "util/hooks";
import React from "react";
import { isEqual } from "lodash";
-
+import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading";
const EditorView = lazy(
() => import("pages/editor/editorView"),
);
@@ -138,6 +138,7 @@ const RootView = React.memo((props: RootViewProps) => {
{comp.children.queries.children[key].getView()}
))}
+
diff --git a/client/packages/lowcoder/src/constants/reduxActionConstants.ts b/client/packages/lowcoder/src/constants/reduxActionConstants.ts
index be3cd6271..6df5991f2 100644
--- a/client/packages/lowcoder/src/constants/reduxActionConstants.ts
+++ b/client/packages/lowcoder/src/constants/reduxActionConstants.ts
@@ -9,7 +9,7 @@ export const ReduxActionTypes = {
FETCH_RAW_CURRENT_USER_SUCCESS: "FETCH_RAW_CURRENT_USER_SUCCESS",
FETCH_API_KEYS: "FETCH_API_KEYS",
FETCH_API_KEYS_SUCCESS: "FETCH_API_KEYS_SUCCESS",
-
+ MOVE_TO_FOLDER2_SUCCESS: "MOVE_TO_FOLDER2_SUCCESS",
/* plugin RELATED */
FETCH_DATA_SOURCE_TYPES: "FETCH_DATA_SOURCE_TYPES",
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index a4672903e..999890d01 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -2785,6 +2785,7 @@ export const en = {
"switch": "Switch Component: "
},
"module": {
+ "folderNotEmpty": "Folder is not empty",
"emptyText": "No Data",
"docLink": "Read More About Modules...",
"documentationText" : "Modules are complete Applications, that can get included and repeated in other Applications and it functions just like a single component. As modules can get embedded, they need to be able to interact with your outside apps or websites. This four settings help to support communication with a Module.",
diff --git a/client/packages/lowcoder/src/index.ts b/client/packages/lowcoder/src/index.ts
index 086d19d0e..2072fc849 100644
--- a/client/packages/lowcoder/src/index.ts
+++ b/client/packages/lowcoder/src/index.ts
@@ -24,7 +24,7 @@ if (!window.ResizeObserver) {
window.ResizeObserver = ResizeObserver;
}
-function hideLoading() {
+export function hideLoading() {
// hide loading
const node = document.getElementById("loading");
if (node) {
@@ -42,7 +42,7 @@ debug(`REACT_APP_LOG_LEVEL:, ${REACT_APP_LOG_LEVEL}`);
try {
bootstrap();
- hideLoading();
+ // hideLoading();
} catch (e) {
log.error(e);
}
diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
index c6fd5f91f..fc2f7536a 100644
--- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
+++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx
@@ -73,7 +73,7 @@ import AppEditor from "../editor/AppEditor";
import { fetchDeploymentIdAction } from "@lowcoder-ee/redux/reduxActions/configActions";
import { getDeploymentId } from "@lowcoder-ee/redux/selectors/configSelectors";
import { SimpleSubscriptionContextProvider } from '@lowcoder-ee/util/context/SimpleSubscriptionContext';
-
+import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading";
const TabLabel = styled.div`
font-weight: 500;
`;
@@ -222,6 +222,7 @@ export default function ApplicationHome() {
return (
+
string
+ checkName: (val: string) => string
+};
+
+
+
+function buildTree(elementRecord: Record>): NodeType {
+ const elements = elementRecord[""];
+ const elementMap: Record = {};
+ let rootNode: NodeType = {
+ name: "root",
+ id: "",
+ isFolder: true,
+ children: [],
+ rename: val => rootNode.name = val,
+ checkName: val => val
}
- &:hover {
- cursor: grab;
- .module-icon {
- box-shadow: 0 0 5px 0 rgba(49, 94, 251, 0.15);
- border-color: ${BorderActiveShadowColor};
- transform: scale(1.2);
- }
- .module-name {
- color: ${ActiveTextColor};
+
+ // Initialize all folders and applications as NodeType
+ for (const element of elements) {
+ if (element.folder) {
+ elementMap[element.folderId] = {
+ name: element.name,
+ id: element.folderId,
+ isFolder: true,
+ children: [],
+ rename: val => elementMap[element.folderId].name = val,
+ checkName: val => val
+ };
+
+ // Process subapplications inside the folder
+ for (const app of element.subApplications || []) {
+ if (!!app && app.applicationType === AppTypeEnum.Module) {
+ const appNode: NodeType = {
+ name: app.name,
+ id: app.applicationId,
+ containerSize: app.containerSize,
+ isFolder: false,
+ children: [],
+ module: app,
+ rename: val => appNode.name = val,
+ checkName: val => val
+ };
+ elementMap[element.folderId].children.push(appNode); // Add applications as children of the folder
+ }
+ }
+ } else {
+ if (element.applicationType === AppTypeEnum.Module) {
+ elementMap[element.applicationId] = {
+ name: element.name,
+ containerSize: element.containerSize,
+ id: element.applicationId,
+ isFolder: false,
+ children: [],
+ module: element,
+ rename: val => elementMap[element.applicationId].name = val,
+ checkName: val => val
+ };
+ }
}
}
- .module-icon {
- transition: all 200ms linear;
- margin-right: 8px;
- width: 40px;
- height: 40px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid ${BorderColor};
- border-radius: 4px;
- }
- .module-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- overflow: hidden;
- }
- .module-name {
- line-height: 1.5;
- font-size: 13px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .module-desc {
- line-height: 1.5;
- font-size: 12px;
- color: ${GreyTextColor};
+
+ // Build the tree structure
+ for (const element of elements) {
+ if (element.folder) {
+ const parentId = element.parentFolderId;
+ if (parentId && elementMap[parentId]) {
+ elementMap[parentId].children.push(elementMap[element.folderId]);
+ } else {
+ rootNode.children.push(elementMap[element.folderId]);
+ }
+ } else if (elementMap[element.applicationId]) {
+ rootNode.children.push(elementMap[element.applicationId]);
+ }
}
-`;
+ rootNode.children.sort((a, b) => {
+ if (a.isFolder && !b.isFolder) {
+ return -1; // a is a isFolder and should come first
+ } else if (!a.isFolder && b.isFolder) {
+ return 1; // b is a folder and should come first
+ } else {
+ return 0; // both are folders or both are not, keep original order
+ }
+ });
+ return rootNode;
+}
+
interface ModuleItemProps {
meta: ApplicationMeta;
onDrag: (type: string) => void;
+ isOverlay: boolean;
+ selectedID: string;
+ setSelectedID: (id: string) => void;
+ selectedType: boolean;
+ setSelectedType: (id: boolean) => void;
+ resComp: NodeType;
+ id: string;
}
function ModuleItem(props: ModuleItemProps) {
const compType = "module";
- const { meta } = props;
+ const {
+ meta ,
+ isOverlay,
+ selectedID,
+ setSelectedID,
+ selectedType,
+ setSelectedType,
+ resComp,
+ id
+ } = props;
+ const dispatch = useDispatch();
+ const type = resComp.isFolder;
+ const name = resComp.name;
+ const [error, setError] = useState(undefined);
+ const [editing, setEditing] = useState(false);
+ const readOnly = useSelector(showAppSnapshotSelector);
+ const isSelected = type === selectedType && id === selectedID;
+ const handleFinishRename = (value: string) => {
+ if (value !== "") {
+ let success = false;
+ let compId = name;
+ if (resComp.rename) {
+ compId = resComp.rename(value);
+ success = !!compId;
+ } else {
+ compId = name;
+ success = true;
+ }
+ if (success) {
+ setSelectedID(compId);
+ setSelectedType(type);
+ setError(undefined);
+ try {
+ dispatch(updateAppMetaAction({
+ applicationId: selectedID,
+ name: value
+ }));
+ } catch (error) {
+ console.error("Error: Rename module in extension:", error);
+ throw error;
+ }
+ }
+ setError(undefined);
+ }
+ setError(undefined);
+ };
+
+ const handleNameChange = (value: string) => {
+ value === "" ? setError("Cannot Be Empty") : setError(undefined);
+ };
return (
{
+ e.stopPropagation();
e.dataTransfer.setData("compType", compType);
e.dataTransfer.setDragImage(TransparentImg, 0, 0);
draggingUtils.setData("compType", compType);
@@ -99,58 +241,501 @@ function ModuleItem(props: ModuleItemProps) {
props.onDrag(compType);
}}
>
-
-
-
-
-
{props.meta.name}
-
{formatTimestamp(props.meta.createAt)}
+
+
+
+
setEditing(editing)}
+ />
+
+
);
}
+const HighlightBorder = styled.div<{ $active: boolean; $foldable: boolean; $level: number }>`
+ max-width: 100%;
+ flex: 1;
+ display: flex;
+ padding-left: ${(props) => props.$level * 20 + (props.$foldable ? 0 : 14)}px;
+ border-radius: 4px;
+ border: 1px solid ${(props) => (props.$active ? BorderActiveColor : "transparent")};
+ align-items: center;
+ justify-content: space-between;
+`;
+
+interface ColumnDivProps {
+ $color?: boolean;
+ $isOverlay: boolean;
+}
+
+const ColumnDiv = styled.div
`
+ width: 100%;
+ height: 25px;
+ display: flex;
+ user-select: none;
+ padding-left: 2px;
+ padding-right: 15px;
+ background-color: ${(props) => (props.$isOverlay ? "rgba(255, 255, 255, 0.11)" : "")};
+
+ &&& {
+ background-color: ${(props) => (props.$color && !props.$isOverlay ? "#f2f7fc" : null)};
+ }
+
+ &:hover {
+ background-color: #f2f7fc80;
+ cursor: pointer;
+ }
+
+ .taco-edit-text-wrapper {
+ width: 100%;
+ height: 21px;
+ line-height: 21px;
+ color: #222222;
+ margin-left: 0;
+ font-size: 13px;
+ padding-left: 0;
+
+ &:hover {
+ background-color: transparent;
+ }
+ }
+
+ .taco-edit-text-input {
+ width: 100%;
+ height: 21px;
+ line-height: 21px;
+ color: #222222;
+ margin-left: 0;
+ font-size: 13px;
+ background-color: #fdfdfd;
+ border: 1px solid #3377ff;
+ border-radius: 2px;
+
+ &:focus {
+ border-color: #3377ff;
+ box-shadow: 0 0 0 2px #d6e4ff;
+ }
+ }
+`;
+
+const FoldIconBtn = styled.div`
+ width: 12px;
+ height: 12px;
+ display: flex;
+ margin-right: 2px;
+`;
+
+const Icon = styled(PointIcon)`
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+ flex-shrink: 0;
+ color: ${NormalMenuIconColor};
+
+ &:hover {
+ color: #315efb;
+ }
+`;
+
+interface ModuleSidebarItemProps extends DraggableTreeNodeItemRenderProps {
+ id: string;
+ resComp: NodeType;
+ onCopy: () => void;
+ onSelect: () => void;
+ onDelete: () => void;
+ onToggleFold: () => void;
+ selectedID: string;
+ setSelectedID: (id: string) => void;
+ selectedType: boolean;
+ setSelectedType: (id: boolean) => void;
+}
+
+const empty = (
+
+ {trans("rightPanel.emptyModules")}
+ {
+ const appId = app.applicationInfoView.applicationId;
+ const url = APPLICATION_VIEW_URL(appId, "edit");
+ window.open(url);
+ }}
+ />
+ >
+ }
+ />
+);
+
+function ModuleSidebarItem(props: ModuleSidebarItemProps) {
+ const dispatch = useDispatch();
+ const {
+ id,
+ resComp,
+ isOver,
+ isOverlay,
+ path,
+ isFolded,
+ selectedID,
+ setSelectedID,
+ selectedType,
+ setSelectedType,
+ onDelete,
+ onCopy,
+ onSelect,
+ onToggleFold,
+ } = props;
+ const { onDrag } = useContext(RightContext);
+ const [error, setError] = useState(undefined);
+ const [editing, setEditing] = useState(false);
+ const readOnly = useSelector(showAppSnapshotSelector);
+ const level = path.length - 1;
+ const type = resComp.isFolder;
+ const name = resComp.name;
+ const isSelected = type === selectedType && id === selectedID;
+ const isFolder = type;
+
+ const handleFinishRename = (value: string) => {
+ if (value !== ""){
+ let success = false;
+ let compId = name;
+ if (resComp.rename) {
+ compId = resComp.rename(value);
+ success = !!compId;
+ } else {
+ compId = name;
+ success = true;
+ }
+ if (success) {
+ setSelectedID(compId);
+ setSelectedType(type);
+ setError(undefined);
+ try{
+ dispatch(updateFolder({ id: selectedID, name: value }));
+ } catch (error) {
+ console.error("Error: Delete module in extension:", error);
+ throw error;
+ }
+
+ }
+ setError(undefined);
+ }
+ };
+
+ const handleNameChange = (value: string) => {
+ value === "" ? setError("Cannot Be Empty") : setError(undefined);
+ };
+
+ const handleClickItem = () => {
+ if (isFolder) {
+ onToggleFold();
+ }
+ onSelect();
+ };
+
+ return (
+
+
+ {isFolder && {!isFolded ? : }}
+ { isFolder ?
+ <>
+
+
+
setEditing(editing)}
+ />
+
+
+ > :
+ }
+ {!readOnly && !isOverlay && (
+ onDelete()}>
+
+
+ )}
+
+
+ );
+}
+
export default function ModulePanel() {
const dispatch = useDispatch();
- const modules = useSelector(modulesSelector);
- const { onDrag, searchValue } = useContext(RightContext);
- const { applicationId } = useContext(ExternalEditorContext);
+ let elements = useSelector(folderElementsSelector);
+ const { searchValue } = useContext(RightContext);
+ const [selectedID, setSelectedID] = useState("");
+ const [selectedType, setSelectedType] = useState(false);
+ let sourceFolderId : string = "";
+ let sourceId : string = "";
+ let folderId : string = "";
+ const tree = buildTree(elements);
+ const getById = (id: string): NodeType | undefined => getByIdFromNode(tree, id);
+ let popedItemSourceId = "";
useEffect(() => {
dispatch(fetchAllModules({}));
}, [dispatch]);
- const filteredModules = modules.filter((i) => {
- if (i.applicationId === applicationId || i.applicationType !== AppTypeEnum.Module) {
- return false;
+ const moveModule = () => {
+ try{
+ if (sourceId !== "") {
+ dispatch(
+ moveToFolder(
+ {
+ sourceFolderId: sourceFolderId!,
+ sourceId: sourceId!,
+ folderId: folderId!,
+ moveFlag: true
+ },
+ () => {
+
+
+ },
+ () => {}
+ )
+ );
+ }
+ } catch (error) {
+ console.error("Error: Move module in extension:", error);
+ throw error;
+ } finally {
+ folderId = "";
+ sourceId = "";
+ sourceFolderId = "";
+ }
+
+ }
+
+ const getByIdFromNode = (root: NodeType | null, id: string): NodeType | undefined => {
+ if (!root) {
+ return;
}
- return i.name?.toLowerCase()?.includes(searchValue.trim()?.toLowerCase()) || !searchValue?.trim();
- });
- const items = filteredModules.map((i) => (
-
- ));
- const empty = (
-
- {trans("rightPanel.emptyModules")}
- {
- const appId = app.applicationInfoView.applicationId;
- const url = APPLICATION_VIEW_URL(appId, "edit");
- window.open(url);
- }}
- />
- >
+ if (root.id === id) {
+ return root;
+ }
+
+ for (const child of root.children) {
+ const result = getByIdFromNode(child, id);
+ if (result) {
+ return result;
}
- />
- );
+ }
+ return;
+ }
+ const convertRefTree = (treeNode: NodeType) => { //Convert elements into tree
+ const moduleResComp = getById(treeNode.id);
+ const currentNodeType = moduleResComp?.isFolder;
+
+ const childrenItems = treeNode.children
+ .map((i) => convertRefTree(i as NodeType))
+ .filter((i): i is DraggableTreeNode => !!i);
+ const node: DraggableTreeNode = {
+ id: moduleResComp?.id,
+ canDropBefore: (source) => {
+ if (currentNodeType) {
+ return source?.isFolder!;
+ }
+
+ return !source?.isFolder;
+ },
+ canDropAfter: (source) => {
+ if (
+ !currentNodeType &&
+ source?.isFolder
+ ) {
+ return false;
+ }
+ return true;
+ },
+ canDropIn: (source) => {
+ if (!currentNodeType) {
+ return false;
+ }
+ if (!source) {
+ return true;
+ }
+ if (source.isFolder) {
+ return false;
+ }
+ return true;
+ },
+ items: childrenItems,
+ data: moduleResComp,
+ addSubItem(value) {
+ folderId = node.id!;
+ moveModule();
+ },
+ deleteItem(index) {
+ sourceFolderId = node.id!;
+ sourceId = node.items[index].id!;
+
+ },
+ addItem(value) {
+ folderId = node.id!;
+ moveModule();
+ },
+ moveItem(from, to) {
+ },
+ };
+
+ if (
+ searchValue &&
+ moduleResComp &&
+ !moduleResComp.name.toLowerCase().includes(searchValue.toLowerCase()) &&
+ childrenItems.length === 0
+ ) {
+ return;
+ }
+ return node;
+ };
+ const node = convertRefTree(tree);
+ function onCopy(type: boolean, id: string) {
+ }
+
+ function onSelect(type: boolean, id: string, meta: any) {
+ setSelectedID(id);
+ setSelectedType(type);
+ }
+
+ function onDelete(type: boolean, id: string, node: NodeType) {
+ if (type) {
+ if (node.children.length) {
+ messageInstance.error(trans("module.folderNotEmpty"))
+ } else {
+ try {
+ dispatch(
+ deleteFolder(
+ {folderId: id, parentFolderId: ""},
+ () => {
+ messageInstance.success(trans("home.deleteSuccessMsg"));
+ },
+ () => {
+ messageInstance.error(trans("error"))
+ }
+ )
+ );
+ } catch (error) {
+ console.error("Error: Remove folder in extension:", error);
+ throw error;
+ }
+ }
+ } else {
+ try {
+ CustomModal.confirm({
+ title: trans("home.moveToTrash"),
+ content: transToNode("home.moveToTrashSubTitle", {
+ type: "",
+ name: "This file",
+ }),
+ onConfirm: () => {
+ dispatch(
+ recycleApplication(
+ {
+ applicationId: id,
+ folderId: popedItemSourceId,
+ },
+ () => {
+ messageInstance.success(trans("success"));
+
+ },
+ () => {
+ messageInstance.error(trans("error"));
+ }
+ )
+ )
+ },
+ confirmBtnType: "delete",
+ okText: trans("home.moveToTrash"),
+ onCancel: () => {}
+ });
+ } catch (error) {
+ console.error("Error: Remove module in extension:", error);
+ throw error;
+ }
+ }
+ }
return (
<>
{trans("rightPanel.moduleListTitle")}
- {items.length > 0 ? items : empty}
+ {node?.items.length ?
+ node={node!}
+ disable={!!searchValue}
+ unfoldAll={!!searchValue}
+ showSubInDragOverlay={false}
+ showDropInPositionLine={false}
+ showPositionLineDot
+ positionLineDotDiameter={4}
+ positionLineHeight={1}
+ itemHeight={25}
+ positionLineIndent={(path, dropInAsSub) => {
+ const indent = 2 + (path.length - 1) * 30;
+ if (dropInAsSub) {
+ return indent + 12;
+ }
+ return indent;
+ }}
+ renderItemContent={(params) => {
+ const { node, onToggleFold, onDelete: onDeleteTreeItem, ...otherParams } = params;
+ const resComp = node.data;
+ if (!resComp) {
+ return null;
+ }
+ const id = resComp.id;
+ const isFolder = resComp.isFolder;
+ return (
+ onCopy(isFolder, id)}
+ onSelect={() => onSelect(isFolder, id, resComp)}
+ selectedID={selectedID}
+ setSelectedID={setSelectedID}
+ selectedType={selectedType}
+ setSelectedType={setSelectedType}
+ onDelete={() => {
+ (onDelete(isFolder, id, resComp))
+ }}
+ {...otherParams}
+ />
+ );
+ }}
+ /> : empty}
>
);
}
diff --git a/client/packages/lowcoder/src/pages/userAuth/index.tsx b/client/packages/lowcoder/src/pages/userAuth/index.tsx
index 40e7a1bc1..d33b48fde 100644
--- a/client/packages/lowcoder/src/pages/userAuth/index.tsx
+++ b/client/packages/lowcoder/src/pages/userAuth/index.tsx
@@ -11,7 +11,7 @@ import { fetchConfigAction } from "redux/reduxActions/configActions";
import { fetchUserAction } from "redux/reduxActions/userActions";
import LoginAdmin from "./loginAdmin";
import _ from "lodash";
-
+import {LoadingBarHideTrigger} from "@lowcoder-ee/util/hideLoading";
export default function UserAuth() {
const dispatch = useDispatch();
const location = useLocation();
@@ -50,6 +50,7 @@ export default function UserAuth() {
fetchUserAfterAuthSuccess,
}}
>
+
diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts
index c27cb8d50..4326cb2ec 100644
--- a/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts
+++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/folderReducer.ts
@@ -37,10 +37,24 @@ export const folderReducer = createReducer(initialState, {
state: FolderReduxState,
action: ReduxAction
): FolderReduxState => {
+ const deleteArray : number[] = [];
const elements = { ...state.folderElements };
- elements[action.payload.folderId ?? ""] = elements[action.payload.folderId ?? ""]?.filter(
- (e) => e.folder || (!e.folder && e.applicationId !== action.payload.applicationId)
- );
+ elements[""] = elements[""].map((item, index) => {
+ if(item.folder) {
+ const tempSubApplications = item.subApplications?.filter(e => e.applicationId !== action.payload.applicationId);
+ return { ...item, subApplications: tempSubApplications };
+ } else {
+ if (item.applicationId !== action.payload.applicationId)
+ return item;
+ else {
+ deleteArray.push(index);
+ return item;
+ }
+ }
+ });
+ deleteArray.map(item => {
+ elements[""].splice(item, 1);
+ })
return {
...state,
folderElements: elements,
@@ -55,6 +69,14 @@ export const folderReducer = createReducer(initialState, {
elements[action.payload.folderId ?? ""] = elements[action.payload.folderId ?? ""]?.map((e) => {
if (!e.folder && e.applicationId === action.payload.applicationId) {
return { ...e, ...action.payload };
+ } else {
+ if (e.folder) {
+ if (e.subApplications?.map(item => {
+ if (item.applicationId === action.payload.applicationId)
+ item.name = action.payload.name
+ })){
+ }
+ }
}
return e;
});
@@ -88,7 +110,7 @@ export const folderReducer = createReducer(initialState, {
action.payload.parentFolderId ?? ""
]?.map((e) => {
if (e.folder && e.folderId === action.payload.folderId) {
- return { ...action.payload, name: action.payload.name };
+ return { ...e, name: action.payload.name};
}
return e;
});
@@ -107,7 +129,7 @@ export const folderReducer = createReducer(initialState, {
state: FolderReduxState,
action: ReduxAction
): FolderReduxState => {
- const elements = { ...state.folderElements };
+ let elements = { ...state.folderElements };
elements[action.payload.sourceFolderId ?? ""] = elements[
action.payload.sourceFolderId ?? ""
]?.filter(
@@ -120,6 +142,59 @@ export const folderReducer = createReducer(initialState, {
folderElements: elements,
};
},
+ [ReduxActionTypes.MOVE_TO_FOLDER2_SUCCESS]: (
+ state: FolderReduxState,
+ action: ReduxAction
+ ): FolderReduxState => {
+ let elements = { ...state.folderElements };
+ const { sourceId, folderId, sourceFolderId } = action.payload;
+ if(sourceFolderId === "") {
+ const tempItem = elements[""]?.find(e =>
+ !e.folder && e.applicationId === sourceId
+ );
+ elements[""] = elements[""]?.filter(e => e.folder || (e.applicationId !== sourceId));
+ elements[""] = elements[""].map(item => {
+ if(item.folder && item.folderId === folderId && tempItem !== undefined && !tempItem.folder) {
+ item.subApplications?.push(tempItem);
+ }
+ return item;
+ })
+ } else{
+ let tempIndex: number | undefined;
+ let tempNode: any;
+ let temp = elements[""].map((item, index) => {
+ if (item.folderId === sourceFolderId && item.folder) {
+ const tempSubApplications = item.subApplications?.filter(e =>
+ (e.folder && e.folderId !== sourceId) ||
+ (!e.folder && e.applicationId !== sourceId)
+ );
+ tempNode = item.subApplications?.filter(e =>
+ (e.folder && e.folderId === sourceId) ||
+ (!e.folder && e.applicationId === sourceId)
+ );
+ return { ...item, subApplications: tempSubApplications };
+ }
+ if (item.folderId === folderId && item.folder) {
+ tempIndex = index;
+ return item;
+ }
+ return item;
+ });
+ if (tempIndex !== undefined) {
+ const targetItem = temp[tempIndex];
+ if (targetItem.folder && Array.isArray(targetItem.subApplications)) {
+ targetItem.subApplications.push(tempNode[0]);
+ }
+ } else {
+ temp.push(tempNode[0]);
+ }
+ elements[""] = temp;
+ }
+ return {
+ ...state,
+ folderElements: elements,
+ };
+ },
[ReduxActionTypes.DELETE_FOLDER_SUCCESS]: (
state: FolderReduxState,
action: ReduxAction
diff --git a/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts b/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts
index ba288b89a..5c00aafe6 100644
--- a/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts
+++ b/client/packages/lowcoder/src/redux/reduxActions/folderActions.ts
@@ -58,6 +58,7 @@ export interface MoveToFolderPayload {
sourceFolderId: string;
sourceId: string;
folderId: string;
+ moveFlag?: boolean;
}
export const moveToFolder = (
diff --git a/client/packages/lowcoder/src/redux/sagas/folderSagas.ts b/client/packages/lowcoder/src/redux/sagas/folderSagas.ts
index 65a39f030..62b74659e 100644
--- a/client/packages/lowcoder/src/redux/sagas/folderSagas.ts
+++ b/client/packages/lowcoder/src/redux/sagas/folderSagas.ts
@@ -84,14 +84,16 @@ export function* deleteFolderSaga(action: ReduxActionWithCallbacks) {
try {
+ const { moveFlag } = action.payload;
+ delete action.payload.moveFlag;
const response: AxiosResponse> = yield FolderApi.moveToFolder(
action.payload
);
const isValidResponse: boolean = validateResponse(response);
-
+ const type = moveFlag ? ReduxActionTypes.MOVE_TO_FOLDER2_SUCCESS : ReduxActionTypes.MOVE_TO_FOLDER_SUCCESS;
if (isValidResponse) {
yield put({
- type: ReduxActionTypes.MOVE_TO_FOLDER_SUCCESS,
+ type,
payload: action.payload,
});
action.onSuccessCallback && action.onSuccessCallback(response);
diff --git a/client/packages/lowcoder/src/util/hideLoading.tsx b/client/packages/lowcoder/src/util/hideLoading.tsx
new file mode 100644
index 000000000..f4c12c345
--- /dev/null
+++ b/client/packages/lowcoder/src/util/hideLoading.tsx
@@ -0,0 +1,10 @@
+import {useEffect} from "react";
+import {hideLoading} from "@lowcoder-ee/index";
+
+export const LoadingBarHideTrigger = function(props: any) {
+ useEffect(() => {
+ setTimeout(() => hideLoading(), 300);
+ }, []);
+
+ return <>>
+};
\ No newline at end of file
diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile
index 2c2bc5c27..5ecbbd579 100644
--- a/deploy/docker/Dockerfile
+++ b/deploy/docker/Dockerfile
@@ -17,6 +17,7 @@ COPY server/api-service/lowcoder-server/src/main/resources/application.yaml /low
# Add bootstrapfile
COPY deploy/docker/api-service/entrypoint.sh /lowcoder/api-service/entrypoint.sh
COPY deploy/docker/api-service/init.sh /lowcoder/api-service/init.sh
+ENV JAVA_OPTS="-Xmx2G -Xms512M"
RUN chmod +x /lowcoder/api-service/*.sh
##
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java
index dded29c35..548d6e439 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistoryArchivedSnapshotRepository.java
@@ -1,6 +1,6 @@
package org.lowcoder.domain.application.repository;
-import org.lowcoder.domain.application.model.ApplicationHistorySnapshot;
+import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
@@ -11,7 +11,7 @@
import java.time.Instant;
@Repository
-public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMongoRepository {
+public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMongoRepository {
@Query(value = "{ 'applicationId': ?0, $and: [" +
"{$or: [ { 'context.operations': { $elemMatch: { 'compName': ?1 } } }, { $expr: { $eq: [?1, null] } } ]}, " +
@@ -20,7 +20,7 @@ public interface ApplicationHistoryArchivedSnapshotRepository extends ReactiveMo
"{$or: [ { 'createdAt': { $lte: ?4} }, { $expr: { $eq: [?4, null] } } ] } " +
"]}",
fields = "{applicationId : 1, context: 1, createdBy : 1, createdAt : 1}")
- Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable);
+ Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable);
Mono countByApplicationId(String applicationId);
}
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java
index 809decfd6..eabf2caf6 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationHistorySnapshotRepository.java
@@ -1,6 +1,6 @@
package org.lowcoder.domain.application.repository;
-import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS;
+import org.lowcoder.domain.application.model.ApplicationHistorySnapshot;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
@@ -11,7 +11,7 @@
import java.time.Instant;
@Repository
-public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepository {
+public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepository {
@Query(value = "{ 'applicationId': ?0, $and: [" +
"{$or: [ { 'context.operations': { $elemMatch: { 'compName': ?1 } } }, { $expr: { $eq: [?1, null] } } ]}, " +
@@ -20,7 +20,7 @@ public interface ApplicationHistorySnapshotRepository extends ReactiveMongoRepos
"{$or: [ { 'createdAt': { $lte: ?4} }, { $expr: { $eq: [?4, null] } } ] } " +
"]}",
fields = "{applicationId : 1, context: 1, createdBy : 1, createdAt : 1}")
- Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable);
+ Flux findAllByApplicationId(String applicationId, String compName, String theme, Instant createdAtFrom, Instant createdAtTo, Pageable pageable);
Mono countByApplicationId(String applicationId);
}
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java
index fd4a79f82..f4e5b3fcf 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationHistorySnapshotService.java
@@ -13,12 +13,13 @@ public interface ApplicationHistorySnapshotService {
Mono createHistorySnapshot(String applicationId, Map dsl, Map context, String userId);
- Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest);
- Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest);
+ Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest);
+ Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest);
Mono countByApplicationId(String applicationId);
+ Mono countByApplicationIdArchived(String applicationId);
- Mono getHistorySnapshotDetail(String historySnapshotId);
+ Mono getHistorySnapshotDetail(String historySnapshotId);
- Mono getHistorySnapshotDetailArchived(String historySnapshotId);
+ Mono getHistorySnapshotDetailArchived(String historySnapshotId);
}
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java
index c47b39955..2d4aba44a 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/impl/ApplicationHistorySnapshotServiceImpl.java
@@ -29,24 +29,24 @@ public class ApplicationHistorySnapshotServiceImpl implements ApplicationHistory
@Override
public Mono createHistorySnapshot(String applicationId, Map dsl, Map context, String userId) {
- ApplicationHistorySnapshotTS applicationHistorySnapshotTS = new ApplicationHistorySnapshotTS();
- applicationHistorySnapshotTS.setApplicationId(applicationId);
- applicationHistorySnapshotTS.setDsl(dsl);
- applicationHistorySnapshotTS.setContext(context);
- return repository.save(applicationHistorySnapshotTS)
+ ApplicationHistorySnapshot applicationHistorySnapshot = new ApplicationHistorySnapshot();
+ applicationHistorySnapshot.setApplicationId(applicationId);
+ applicationHistorySnapshot.setDsl(dsl);
+ applicationHistorySnapshot.setContext(context);
+ return repository.save(applicationHistorySnapshot)
.thenReturn(true)
.onErrorReturn(false);
}
@Override
- public Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) {
+ public Mono> listAllHistorySnapshotBriefInfo(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) {
return repository.findAllByApplicationId(applicationId, compName, theme, from, to, pageRequest.withSort(Direction.DESC, "id"))
.collectList()
.onErrorMap(Exception.class, e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_FAILURE, "FETCH_HISTORY_SNAPSHOT_FAILURE"));
}
@Override
- public Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) {
+ public Mono> listAllHistorySnapshotBriefInfoArchived(String applicationId, String compName, String theme, Instant from, Instant to, PageRequest pageRequest) {
return repositoryArchived.findAllByApplicationId(applicationId, compName, theme, from, to, pageRequest.withSort(Direction.DESC, "id"))
.collectList()
.onErrorMap(Exception.class, e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_FAILURE, "FETCH_HISTORY_SNAPSHOT_FAILURE"));
@@ -59,16 +59,23 @@ public Mono countByApplicationId(String applicationId) {
e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE, "FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE"));
}
+ @Override
+ public Mono countByApplicationIdArchived(String applicationId) {
+ return repositoryArchived.countByApplicationId(applicationId)
+ .onErrorMap(Exception.class,
+ e -> ofException(BizError.FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE, "FETCH_HISTORY_SNAPSHOT_COUNT_FAILURE"));
+ }
+
@Override
- public Mono getHistorySnapshotDetail(String historySnapshotId) {
+ public Mono getHistorySnapshotDetail(String historySnapshotId) {
return repository.findById(historySnapshotId)
.switchIfEmpty(deferredError(INVALID_HISTORY_SNAPSHOT, "INVALID_HISTORY_SNAPSHOT", historySnapshotId));
}
@Override
- public Mono getHistorySnapshotDetailArchived(String historySnapshotId) {
+ public Mono getHistorySnapshotDetailArchived(String historySnapshotId) {
return repositoryArchived.findById(historySnapshotId)
.switchIfEmpty(deferredError(INVALID_HISTORY_SNAPSHOT, "INVALID_HISTORY_SNAPSHOT", historySnapshotId));
}
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java
index 6b800720c..981000caf 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/UserServiceImpl.java
@@ -418,7 +418,7 @@ protected Map convertConnections(Set connections) {
return connections.stream()
.filter(connection -> !AuthSourceConstants.EMAIL.equals(connection.getSource()) &&
!AuthSourceConstants.PHONE.equals(connection.getSource()))
- .collect(Collectors.toMap(Connection::getSource, Connection::getRawUserInfo));
+ .collect(Collectors.toMap(Connection::getAuthId, Connection::getRawUserInfo));
}
protected String convertEmail(Set connections) {
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java
index ed7079598..3c91f7ce7 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java
@@ -162,12 +162,12 @@ public Mono>> getApplications(@RequestPar
@RequestParam(required = false) ApplicationStatus applicationStatus,
@RequestParam(defaultValue = "true") boolean withContainerSize,
@RequestParam(required = false) String name,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize) {
ApplicationType applicationTypeEnum = applicationType == null ? null : ApplicationType.fromValue(applicationType);
var flux = userHomeApiService.getAllAuthorisedApplications4CurrentOrgMember(applicationTypeEnum, applicationStatus, withContainerSize, name).cache();
Mono countMono = flux.count();
- var flux1 = flux.skip((long) pageNum * pageSize);
+ var flux1 = flux.skip((long) (pageNum - 1) * pageSize);
if(pageSize > 0) flux1 = flux1.take(pageSize);
return flux1.collectList().zipWith(countMono)
.map(tuple -> PageResponseView.success(tuple.getT1(), pageNum, pageSize, Math.toIntExact(tuple.getT2())));
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java
index 4eed69ee2..cef119847 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java
@@ -167,7 +167,7 @@ public Mono>> getApplications(@RequestPar
@RequestParam(required = false) ApplicationStatus applicationStatus,
@RequestParam(defaultValue = "true") boolean withContainerSize,
@RequestParam(required = false) String name,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize);
@Operation(
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java
index 6b6d94a51..b5a6381d7 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationHistorySnapshotController.java
@@ -13,6 +13,7 @@
import org.lowcoder.domain.application.service.ApplicationService;
import org.lowcoder.domain.permission.model.ResourceAction;
import org.lowcoder.domain.permission.service.ResourcePermissionService;
+import org.lowcoder.domain.user.model.User;
import org.lowcoder.domain.user.service.UserService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@@ -54,7 +55,7 @@ public Mono> create(@RequestBody ApplicationHistorySnapsho
@Override
public Mono>> listAllHistorySnapshotBriefInfo(@PathVariable String applicationId,
- @RequestParam(defaultValue = "0") int page,
+ @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam String compName,
@RequestParam String theme,
@@ -69,15 +70,15 @@ public Mono>> listAllHistorySnapshotBriefInfo(@
.flatMap(__ -> applicationHistorySnapshotService.listAllHistorySnapshotBriefInfo(applicationId, compName, theme, from, to, pagination.toPageRequest()))
.flatMap(snapshotList -> {
Mono> snapshotBriefInfoList = multiBuild(snapshotList,
- ApplicationHistorySnapshotTS::getCreatedBy,
+ ApplicationHistorySnapshot::getCreatedBy,
userService::getByIds,
- (applicationHistorySnapshotTS, user) -> new ApplicationHistorySnapshotBriefInfo(
- applicationHistorySnapshotTS.getId(),
- applicationHistorySnapshotTS.getContext(),
- applicationHistorySnapshotTS.getCreatedBy(),
+ (applicationHistorySnapshot, user) -> new ApplicationHistorySnapshotBriefInfo(
+ applicationHistorySnapshot.getId(),
+ applicationHistorySnapshot.getContext(),
+ applicationHistorySnapshot.getCreatedBy(),
user.getName(),
user.getAvatarUrl(),
- applicationHistorySnapshotTS.getCreatedAt().toEpochMilli()
+ applicationHistorySnapshot.getCreatedAt().toEpochMilli()
)
);
@@ -91,7 +92,7 @@ public Mono>> listAllHistorySnapshotBriefInfo(@
@Override
public Mono>> listAllHistorySnapshotBriefInfoArchived(@PathVariable String applicationId,
- @RequestParam(defaultValue = "0") int page,
+ @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam String compName,
@RequestParam String theme,
@@ -106,19 +107,19 @@ public Mono>> listAllHistorySnapshotBriefInfoAr
.flatMap(__ -> applicationHistorySnapshotService.listAllHistorySnapshotBriefInfoArchived(applicationId, compName, theme, from, to, pagination.toPageRequest()))
.flatMap(snapshotList -> {
Mono> snapshotBriefInfoList = multiBuild(snapshotList,
- ApplicationHistorySnapshot::getCreatedBy,
+ ApplicationHistorySnapshotTS::getCreatedBy,
userService::getByIds,
- (applicationHistorySnapshot, user) -> new ApplicationHistorySnapshotBriefInfo(
- applicationHistorySnapshot.getId(),
- applicationHistorySnapshot.getContext(),
- applicationHistorySnapshot.getCreatedBy(),
+ (applicationHistorySnapshotTS, user) -> new ApplicationHistorySnapshotBriefInfo(
+ applicationHistorySnapshotTS.getId(),
+ applicationHistorySnapshotTS.getContext(),
+ applicationHistorySnapshotTS.getCreatedBy(),
user.getName(),
user.getAvatarUrl(),
- applicationHistorySnapshot.getCreatedAt().toEpochMilli()
+ applicationHistorySnapshotTS.getCreatedAt().toEpochMilli()
)
);
- Mono applicationHistorySnapshotCount = applicationHistorySnapshotService.countByApplicationId(applicationId);
+ Mono applicationHistorySnapshotCount = applicationHistorySnapshotService.countByApplicationIdArchived(applicationId);
return Mono.zip(snapshotBriefInfoList, applicationHistorySnapshotCount)
.map(tuple -> ImmutableMap.of("list", tuple.getT1(), "count", tuple.getT2()));
@@ -133,7 +134,7 @@ public Mono> getHistorySnapshotDsl(@PathVar
.delayUntil(visitor -> resourcePermissionService.checkResourcePermissionWithError(visitor, applicationId,
ResourceAction.EDIT_APPLICATIONS))
.flatMap(__ -> applicationHistorySnapshotService.getHistorySnapshotDetail(snapshotId))
- .map(ApplicationHistorySnapshotTS::getDsl)
+ .map(ApplicationHistorySnapshot::getDsl)
.zipWhen(applicationService::getAllDependentModulesFromDsl)
.map(tuple -> {
Map applicationDsl = tuple.getT1();
@@ -155,7 +156,7 @@ public Mono> getHistorySnapshotDslArchived(
.delayUntil(visitor -> resourcePermissionService.checkResourcePermissionWithError(visitor, applicationId,
ResourceAction.EDIT_APPLICATIONS))
.flatMap(__ -> applicationHistorySnapshotService.getHistorySnapshotDetailArchived(snapshotId))
- .map(ApplicationHistorySnapshot::getDsl)
+ .map(ApplicationHistorySnapshotTS::getDsl)
.zipWhen(applicationService::getAllDependentModulesFromDsl)
.map(tuple -> {
Map applicationDsl = tuple.getT1();
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java
index 254e78037..cb0df9241 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleController.java
@@ -106,7 +106,7 @@ public Mono>> getRecycledBundles() {
@Override
public Mono> getElements(@PathVariable String bundleId,
@RequestParam(value = "applicationType", required = false) ApplicationType applicationType,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize) {
String objectId = gidService.convertBundleIdToObjectId(bundleId);
var flux = bundleApiService.getElements(objectId, applicationType).cache();
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java
index 8674c62b5..8d668c1b7 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/bundle/BundleEndpoints.java
@@ -123,7 +123,7 @@ public interface BundleEndpoints
@GetMapping("/{bundleId}/elements")
public Mono> getElements(@PathVariable String bundleId,
@RequestParam(value = "applicationType", required = false) ApplicationType applicationType,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize);
@Operation(
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java
index 31cf49494..24a77dd29 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderController.java
@@ -70,12 +70,12 @@ public Mono> update(@RequestBody Folder folder) {
public Mono> getElements(@RequestParam(value = "id", required = false) String folderId,
@RequestParam(value = "applicationType", required = false) ApplicationType applicationType,
@RequestParam(required = false) String name,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize) {
String objectId = gidService.convertFolderIdToObjectId(folderId);
var flux = folderApiService.getElements(objectId, applicationType, name).cache();
var countMono = flux.count();
- var flux1 = flux.skip((long) pageNum * pageSize);
+ var flux1 = flux.skip((long) (pageNum - 1) * pageSize);
if(pageSize > 0) flux1 = flux1.take(pageSize);
return flux1.collectList()
.delayUntil(__ -> folderApiService.upsertLastViewTime(objectId))
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java
index 43e5ce785..2c6279084 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/FolderEndpoints.java
@@ -71,7 +71,7 @@ public interface FolderEndpoints
public Mono> getElements(@RequestParam(value = "id", required = false) String folderId,
@RequestParam(value = "applicationType", required = false) ApplicationType applicationType,
@RequestParam(required = false) String name,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize);
@Operation(
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java
index d478bcfc2..4e7facb99 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupController.java
@@ -75,7 +75,7 @@ public Mono> delete(@PathVariable String groupId) {
}
@Override
- public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize) {
return groupApiService.getGroups().flatMap(groupList -> {
if(groupList.isEmpty()) return Mono.just(new GroupListResponseView<>(ResponseView.SUCCESS,
@@ -99,7 +99,7 @@ public Mono>> getOrgGroups(@RequestParam(r
.filter(orgMember -> !orgMember.isAdmin() && !orgMember.isSuperAdmin() &&
devMembers.stream().noneMatch(devMember -> devMember.getUserId().equals(orgMember.getUserId()))).toList().size();
- var subList = groupList.subList(pageNum * pageSize, pageSize <= 0?groupList.size():pageNum * pageSize + pageSize);
+ var subList = groupList.subList((pageNum - 1) * pageSize, pageSize <= 0?groupList.size():pageNum * pageSize);
return new GroupListResponseView<>(ResponseView.SUCCESS,
"",
subList,
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java
index 4f0825333..e2f8bfa7a 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/GroupEndpoints.java
@@ -63,7 +63,7 @@ public Mono> update(@PathVariable String groupId,
description = "Retrieve a list of User Groups within Lowcoder, providing an overview of available groups, based on the access rights of the currently impersonated User."
)
@GetMapping("/list")
- public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ public Mono>> getOrgGroups(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize);
@Operation(
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java
index b0acc8cf1..2b2a9dd75 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationController.java
@@ -50,7 +50,7 @@ public class OrganizationController implements OrganizationEndpoints
@Override
public Mono> getOrganizationByUser(@PathVariable String email,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize) {
var flux = userService.findByEmailDeep(email).flux().flatMap(user -> orgMemberService.getAllActiveOrgs(user.getId()))
.flatMap(orgMember -> organizationService.getById(orgMember.getOrgId()))
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java
index 734012033..38332e892 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/usermanagement/OrganizationEndpoints.java
@@ -46,7 +46,7 @@ public interface OrganizationEndpoints
)
@GetMapping("/byuser/{email}")
public Mono> getOrganizationByUser(@PathVariable String email,
- @RequestParam(required = false, defaultValue = "0") Integer pageNum,
+ @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "0") Integer pageSize);
@Operation(
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java
index 051c3e006..03141d6bb 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/Pagination.java
@@ -64,7 +64,7 @@ public int size() {
@NotNull
public static Mono> fluxToPageResponseView(Integer pageNum, Integer pageSize, Flux> flux) {
var countMono = flux.count();
- var flux1 = flux.skip((long) pageNum * pageSize);
+ var flux1 = flux.skip((long) (pageNum - 1) * pageSize);
if(pageSize > 0) flux1 = flux1.take(pageSize);
return flux1.collectList().zipWith(countMono)
.map(tuple -> PageResponseView.success(tuple.getT1(), pageNum, pageSize, Math.toIntExact(tuple.getT2())));
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java
index ddf0422ab..a51a74e09 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/migrations/DatabaseChangelog.java
@@ -4,6 +4,9 @@
import com.github.cloudyrock.mongock.ChangeSet;
import com.github.cloudyrock.mongock.driver.mongodb.springdata.v4.decorator.impl.MongockTemplate;
import com.github.f4b6a3.uuid.UuidCreator;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.result.DeleteResult;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.lowcoder.domain.application.model.Application;
@@ -44,6 +47,7 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -313,41 +317,86 @@ private int getMongoDBVersion(MongockTemplate mongoTemplate) {
@ChangeSet(order = "026", id = "add-time-series-snapshot-history", author = "")
public void addTimeSeriesSnapshotHistory(MongockTemplate mongoTemplate, CommonConfig commonConfig) {
int mongoVersion = getMongoDBVersion(mongoTemplate);
- if (mongoVersion < 5) {
- log.warn("MongoDB version is below 5. Time-series collections are not supported. Upgrade the MongoDB version.");
- }
- // Create the time-series collection if it doesn't exist
- if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) {
- if(mongoVersion < 5) {
- mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class);
- } else {
- mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class, CollectionOptions.empty().timeSeries("createdAt"));
+ Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS);
+
+ if (mongoVersion >= 5) {
+ // MongoDB version >= 5: Use manual insert query
+ if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) {
+ mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class,
+ CollectionOptions.empty().timeSeries("createdAt"));
}
+
+ // Aggregation pipeline to fetch the records
+ List aggregationPipeline = Arrays.asList(
+ new Document("$match", new Document("createdAt", new Document("$lte", thresholdDate))),
+ new Document("$project", new Document()
+ .append("applicationId", 1)
+ .append("dsl", 1)
+ .append("context", 1)
+ .append("createdAt", 1)
+ .append("createdBy", 1)
+ .append("modifiedBy", 1)
+ .append("updatedAt", 1)
+ .append("id", "$_id")) // Map `_id` to `id` if needed
+ );
+
+ MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot");
+ MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS");
+
+ // Fetch results and insert them into the time-series collection
+ try (MongoCursor cursor = sourceCollection.aggregate(aggregationPipeline).iterator()) {
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+ targetCollection.insertOne(document); // Insert into the time-series collection
+ }
+ }
+
+ // Delete the migrated records
+ Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate));
+ DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class);
+
+ log.info("Deleted {} records from the source collection.", deleteResult.getDeletedCount());
+ } else {
+ // MongoDB version < 5: Use aggregation with $out
+ if (!mongoTemplate.collectionExists(ApplicationHistorySnapshotTS.class)) {
+ mongoTemplate.createCollection(ApplicationHistorySnapshotTS.class); // Create a regular collection
+ }
+
+ // Aggregation pipeline with $out
+ List aggregationPipeline = Arrays.asList(
+ new Document("$match", new Document("createdAt", new Document("$lte", thresholdDate))),
+ new Document("$project", new Document()
+ .append("applicationId", 1)
+ .append("dsl", 1)
+ .append("context", 1)
+ .append("createdAt", 1)
+ .append("createdBy", 1)
+ .append("modifiedBy", 1)
+ .append("updatedAt", 1)
+ .append("id", "$_id")), // Map `_id` to `id` if needed
+ new Document("$out", "applicationHistorySnapshotTS") // Write directly to the target collection
+ );
+
+ mongoTemplate.getDb()
+ .getCollection("applicationHistorySnapshot")
+ .aggregate(aggregationPipeline)
+ .toCollection();
+
+ // Delete the migrated records
+ Query deleteQuery = new Query(Criteria.where("createdAt").gte(thresholdDate));
+ DeleteResult deleteResult = mongoTemplate.remove(deleteQuery, ApplicationHistorySnapshot.class);
+
+ log.info("Deleted {} records from the source collection.", deleteResult.getDeletedCount());
}
- Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS);
- List snapshots = mongoTemplate.find(new Query().addCriteria(Criteria.where("createdAt").gte(thresholdDate)), ApplicationHistorySnapshot.class);
- snapshots.forEach(snapshot -> {
- ApplicationHistorySnapshotTS applicationHistorySnapshotTS = new ApplicationHistorySnapshotTS();
- applicationHistorySnapshotTS.setApplicationId(snapshot.getApplicationId());
- applicationHistorySnapshotTS.setDsl(snapshot.getDsl());
- applicationHistorySnapshotTS.setContext(snapshot.getContext());
- applicationHistorySnapshotTS.setCreatedAt(snapshot.getCreatedAt());
- applicationHistorySnapshotTS.setCreatedBy(snapshot.getCreatedBy());
- applicationHistorySnapshotTS.setModifiedBy(snapshot.getModifiedBy());
- applicationHistorySnapshotTS.setUpdatedAt(snapshot.getUpdatedAt());
- applicationHistorySnapshotTS.setId(snapshot.getId());
- mongoTemplate.insert(applicationHistorySnapshotTS);
- mongoTemplate.remove(snapshot);
- });
- // Ensure indexes if needed
+ // Ensure indexes on the new collection
ensureIndexes(mongoTemplate, ApplicationHistorySnapshotTS.class,
makeIndex("applicationId"),
- makeIndex("createdAt")
- );
+ makeIndex("createdAt"));
}
+
private void addGidField(MongockTemplate mongoTemplate, String collectionName) {
// Create a query to match all documents
Query query = new Query();
diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java
index 2fa516379..28108f51a 100644
--- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java
+++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/runner/task/ArchiveSnapshotTask.java
@@ -2,12 +2,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.lowcoder.domain.application.model.ApplicationHistorySnapshot;
-import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS;
import org.lowcoder.sdk.config.CommonConfig;
import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -16,6 +12,11 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.model.Filters;
+import org.bson.Document;
+
@Slf4j
@RequiredArgsConstructor
@Component
@@ -24,23 +25,122 @@ public class ArchiveSnapshotTask {
private final CommonConfig commonConfig;
private final MongoTemplate mongoTemplate;
- @Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.DAYS)
+ @Scheduled(initialDelay = 0, fixedRate = 1, timeUnit = TimeUnit.DAYS)
public void archive() {
+ int mongoVersion = getMongoDBVersion();
Instant thresholdDate = Instant.now().minus(commonConfig.getQuery().getAppSnapshotKeepDuration(), ChronoUnit.DAYS);
- List snapshots = mongoTemplate.find(new Query().addCriteria(Criteria.where("createdAt").lte(thresholdDate)), ApplicationHistorySnapshotTS.class);
- snapshots.forEach(snapshot -> {
- ApplicationHistorySnapshot applicationHistorySnapshot = new ApplicationHistorySnapshot();
- applicationHistorySnapshot.setApplicationId(snapshot.getApplicationId());
- applicationHistorySnapshot.setDsl(snapshot.getDsl());
- applicationHistorySnapshot.setContext(snapshot.getContext());
- applicationHistorySnapshot.setCreatedAt(snapshot.getCreatedAt());
- applicationHistorySnapshot.setCreatedBy(snapshot.getCreatedBy());
- applicationHistorySnapshot.setModifiedBy(snapshot.getModifiedBy());
- applicationHistorySnapshot.setUpdatedAt(snapshot.getUpdatedAt());
- applicationHistorySnapshot.setId(snapshot.getId());
- mongoTemplate.insert(applicationHistorySnapshot);
- mongoTemplate.remove(snapshot);
- });
+
+ if (mongoVersion >= 5) {
+ archiveForVersion5AndAbove(thresholdDate);
+ } else {
+ archiveForVersionBelow5(thresholdDate);
+ }
+ }
+
+ private int getMongoDBVersion() {
+ Document buildInfo = mongoTemplate.getDb().runCommand(new Document("buildInfo", 1));
+ String version = buildInfo.getString("version");
+ return Integer.parseInt(version.split("\\.")[0]); // Parse major version
}
+ private void archiveForVersion5AndAbove(Instant thresholdDate) {
+ log.info("Running archival for MongoDB version >= 5");
+
+ MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot");
+ MongoCollection targetCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshotTS");
+
+ long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate));
+ log.info("Total documents to archive: {}", totalDocuments);
+
+ long processedCount = 0;
+
+ try (MongoCursor cursor = sourceCollection.find(Filters.lte("createdAt", thresholdDate)).iterator()) {
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+
+ // Transform the document for the target collection
+ document.put("id", document.getObjectId("_id")); // Map `_id` to `id`
+ document.remove("_id");
+
+ // Insert the document into the target collection
+ try {
+ targetCollection.insertOne(document);
+ } catch (Exception e) {
+ log.error("Failed to insert document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage());
+ continue;
+ }
+
+ // Remove the document from the source collection
+ try {
+ sourceCollection.deleteOne(Filters.eq("_id", document.getObjectId("id")));
+ } catch (Exception e) {
+ log.error("Failed to delete document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage());
+ continue;
+ }
+
+ processedCount++;
+ log.info("Processed document {} / {}", processedCount, totalDocuments);
+ }
+ } catch (Exception e) {
+ log.error("Failed during archival process. Error: {}", e.getMessage());
+ }
+
+ log.info("Archival process completed. Total documents archived: {}", processedCount);
+ }
+
+ private void archiveForVersionBelow5(Instant thresholdDate) {
+ log.info("Running archival for MongoDB version < 5");
+
+ MongoCollection sourceCollection = mongoTemplate.getDb().getCollection("applicationHistorySnapshot");
+
+ long totalDocuments = sourceCollection.countDocuments(Filters.lte("createdAt", thresholdDate));
+ log.info("Total documents to archive: {}", totalDocuments);
+
+ long processedCount = 0;
+
+ try (MongoCursor cursor = sourceCollection.find(Filters.lte("createdAt", thresholdDate)).iterator()) {
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+
+ // Transform the document for the target collection
+ document.put("id", document.getObjectId("_id")); // Map `_id` to `id`
+ document.remove("_id");
+
+ // Use aggregation with $out for the single document
+ try {
+ sourceCollection.aggregate(List.of(
+ Filters.eq("_id", document.getObjectId("id")),
+ new Document("$project", new Document()
+ .append("applicationId", document.get("applicationId"))
+ .append("dsl", document.get("dsl"))
+ .append("context", document.get("context"))
+ .append("createdAt", document.get("createdAt"))
+ .append("createdBy", document.get("createdBy"))
+ .append("modifiedBy", document.get("modifiedBy"))
+ .append("updatedAt", document.get("updatedAt"))
+ .append("id", document.get("id"))),
+ new Document("$out", "applicationHistorySnapshotTS")
+ )).first();
+ } catch (Exception e) {
+ log.error("Failed to aggregate and insert document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage());
+ continue;
+ }
+
+ // Remove the document from the source collection
+ try {
+ sourceCollection.deleteOne(Filters.eq("_id", document.getObjectId("id")));
+ } catch (Exception e) {
+ log.error("Failed to delete document with ID {}. Error: {}", document.getObjectId("id"), e.getMessage());
+ continue;
+ }
+
+ processedCount++;
+ log.info("Processed document {} / {}", processedCount, totalDocuments);
+ }
+ } catch (Exception e) {
+ log.error("Failed during archival process. Error: {}", e.getMessage());
+ }
+
+ log.info("Archival process completed. Total documents archived: {}", processedCount);
+ }
}
diff --git a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java
index fb7109134..81c0cb56d 100644
--- a/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java
+++ b/server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/service/impl/ApplicationHistorySnapshotServiceTest.java
@@ -4,7 +4,7 @@
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.lowcoder.domain.application.model.ApplicationHistorySnapshotTS;
+import org.lowcoder.domain.application.model.ApplicationHistorySnapshot;
import org.lowcoder.domain.application.service.ApplicationHistorySnapshotService;
import org.lowcoder.sdk.models.HasIdAndAuditing;
import org.springframework.beans.factory.annotation.Autowired;
@@ -47,8 +47,8 @@ public void testServiceMethods() {
.assertNext(list -> {
assertEquals(2, list.size());
- ApplicationHistorySnapshotTS first = list.get(0);
- ApplicationHistorySnapshotTS second = list.get(1);
+ ApplicationHistorySnapshot first = list.get(0);
+ ApplicationHistorySnapshot second = list.get(1);
assertTrue(first.getCreatedAt().isAfter(second.getCreatedAt()));
assertNull(first.getDsl());
@@ -66,7 +66,7 @@ public void testServiceMethods() {
StepVerifier.create(service.listAllHistorySnapshotBriefInfo(applicationId, null, null, null, null, PageRequest.of(1, 1)))
.assertNext(list -> {
assertEquals(1, list.size());
- ApplicationHistorySnapshotTS one = list.get(0);
+ ApplicationHistorySnapshot one = list.get(0);
assertNull(one.getDsl());
assertEquals(ImmutableMap.of("context", "context1"), one.getContext());
assertEquals(applicationId, one.getApplicationId());