15610: Enhances performance on tree handling.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Fri, 12 Jun 2020 18:07:23 +0000 (15:07 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Fri, 12 Jun 2020 18:13:47 +0000 (15:13 -0300)
Large trees (eg: large collection file hierarchies) handling performance boost
by doing in-place data manipulations instead of functional programming style
data copying.
This enables workbench2 to gracefully show mid-sized collections of around
50k items without making the user wait too much for the UI to respond.

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

src/models/collection-file.ts
src/models/tree.ts
src/services/collection-service/collection-service-files-response.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts
src/views-components/projects-tree-picker/generic-projects-tree-picker.tsx

index 97afcac6ca70d4f5d7fc0f8f005e980b3597d7eb..3951d272ee8f3d046c96036be440fef8187f9e91 100644 (file)
@@ -66,7 +66,6 @@ export const createCollectionFilesTree = (data: Array<CollectionDirectory | Coll
             selected: false,
             expanded: false,
             status: TreeNodeStatus.INITIAL
-
         })(tree), createTree<CollectionDirectory | CollectionFile>());
 };
 
index de2f7b71a1b6861ccd6af42e262f652d8e7d67b0..c7713cbcf08fc996429ff0905e601f14fd6a4ec8 100644 (file)
@@ -43,12 +43,13 @@ export const appendSubtree = <T>(id: string, subtree: Tree<T>) => (tree: Tree<T>
     )(subtree) as Tree<T>;
 
 export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
-    return pipe(
-        (tree: Tree<T>) => getNode(node.id)(tree) === node
-            ? tree
-            : { ...tree, [node.id]: node },
-        addChild(node.parent, node.id)
-    )(tree);
+    if (tree[node.id] && tree[node.id] === node) { return tree; }
+
+    tree[node.id] = node;
+    if (tree[node.parent]) {
+        tree[node.parent].children = Array.from(new Set([...tree[node.parent].children, node.id]));
+    }
+    return tree;
 };
 
 export const getNodeValue = (id: string) => <T>(tree: Tree<T>) => {
@@ -156,7 +157,6 @@ export const toggleNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
             toggleAncestorsSelection(id),
             toggleDescendantsSelection(id))(tree)
         : tree;
-
 };
 
 export const selectNode = (id: string) => <T>(tree: Tree<T>) => {
@@ -235,23 +235,3 @@ const getRootNodeChildrenIds = <T>(tree: Tree<T>) =>
     Object
         .keys(tree)
         .filter(id => getNode(id)(tree)!.parent === TREE_ROOT_ID);
-
-
-const addChild = (parentId: string, childId: string) => <T>(tree: Tree<T>): Tree<T> => {
-    if (childId === "") {
-        return tree;
-    }
-    const node = getNode(parentId)(tree);
-    if (node) {
-        const children = node.children.some(id => id === childId)
-            ? node.children
-            : [...node.children, childId];
-
-        const newNode = children === node.children
-            ? node
-            : { ...node, children };
-
-        return setNode(newNode)(tree);
-    }
-    return tree;
-};
index 2e726d0bc8e0798fbae17df24f412f920d54906a..5e6f7b83f0ecf7628546ce5101dd75c7e52f6629 100644 (file)
@@ -50,7 +50,6 @@ export const extractFilesData = (document: Document) => {
             return getTagValue(element, 'D:resourcetype', '')
                 ? createCollectionDirectory(data)
                 : createCollectionFile({ ...data, size });
-
         });
 };
 
index 57961538708c900b56631e47e33639a90d66a559..08a717596f62df17779a590ebe4c7bb56d75327d 100644 (file)
@@ -8,23 +8,25 @@ import { createTree, mapTreeValues, getNode, setNode, getNodeAncestorsIds, getNo
 import { CollectionFileType } from "~/models/collection-file";
 
 export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
+    // Low-level tree handling setNode() func does in-place data modifications
+    // for performance reasons, so we pass a copy of 'state' to avoid side effects.
     return collectionPanelFilesAction.match(action, {
         SET_COLLECTION_FILES: files =>
-            mergeCollectionPanelFilesStates(state, mapTree(mapCollectionFileToCollectionPanelFile)(files)),
+            mergeCollectionPanelFilesStates({...state}, mapTree(mapCollectionFileToCollectionPanelFile)(files)),
 
         TOGGLE_COLLECTION_FILE_COLLAPSE: data =>
-            toggleCollapse(data.id)(state),
+            toggleCollapse(data.id)({...state}),
 
-        TOGGLE_COLLECTION_FILE_SELECTION: data => [state]
+        TOGGLE_COLLECTION_FILE_SELECTION: data => [{...state}]
             .map(toggleSelected(data.id))
             .map(toggleAncestors(data.id))
             .map(toggleDescendants(data.id))[0],
 
         SELECT_ALL_COLLECTION_FILES: () =>
-            mapTreeValues(v => ({ ...v, selected: true }))(state),
+            mapTreeValues(v => ({ ...v, selected: true }))({...state}),
 
         UNSELECT_ALL_COLLECTION_FILES: () =>
-            mapTreeValues(v => ({ ...v, selected: false }))(state),
+            mapTreeValues(v => ({ ...v, selected: false }))({...state}),
 
         default: () => state
     }) as CollectionPanelFilesState;
index 8e27d445d1a70faa090900975ea8926659dceaea..07b1ad816a6d0475469e5438e08b7351b777282f 100644 (file)
@@ -33,7 +33,7 @@ export interface ProjectsTreePickerDataProps {
     showSelection?: boolean;
     relatedTreePickers?: string[];
     disableActivation?: string[];
-    loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void;
+    loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, includeFiles?: boolean) => void;
 }
 
 export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & Partial<PickedTreePickerProps>;