Add function for getting tree nodes instead of ids
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 20 Aug 2018 08:36:53 +0000 (10:36 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 20 Aug 2018 08:36:53 +0000 (10:36 +0200)
Feature #14013

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

src/models/tree.test.ts
src/models/tree.ts
src/services/collection-files-service/collection-manifest-mapper.ts
src/services/collection-service/collection-service-files-response.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts
src/store/collections/uploader/collection-uploader-actions.ts
src/store/tree-picker/tree-picker-reducer.test.ts
src/views-components/collection-panel-files/collection-panel-files.ts
src/views-components/tree-picker/tree-picker.ts

index 708cf4045c75b73fae7650b8a3bba789a46362bc..375a012054f9bea3a26a7bd8033642b44697ea24 100644 (file)
@@ -30,7 +30,7 @@ describe('Tree', () => {
             { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' },
             { children: [], id: 'Node 3', parent: 'Node 2', value: 'Value 1' }
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
-        expect(Tree.getNodeAncestors('Node 3')(newTree)).toEqual(['Node 1', 'Node 2']);
+        expect(Tree.getNodeAncestorsIds('Node 3')(newTree)).toEqual(['Node 1', 'Node 2']);
     });
 
     it('gets node descendants', () => {
@@ -41,7 +41,7 @@ describe('Tree', () => {
             { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
             { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
-        expect(Tree.getNodeDescendants('Node 1')(newTree)).toEqual(['Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
+        expect(Tree.getNodeDescendantsIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
     });
 
     it('gets root descendants', () => {
@@ -52,7 +52,7 @@ describe('Tree', () => {
             { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
             { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
-        expect(Tree.getNodeDescendants('')(newTree)).toEqual(['Node 1', 'Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
+        expect(Tree.getNodeDescendantsIds('')(newTree)).toEqual(['Node 1', 'Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']);
     });
 
     it('gets node children', () => {
@@ -63,7 +63,7 @@ describe('Tree', () => {
             { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' },
             { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
-        expect(Tree.getNodeChildren('Node 1')(newTree)).toEqual(['Node 2', 'Node 3']);
+        expect(Tree.getNodeChildrenIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3']);
     });
 
     it('gets root children', () => {
@@ -74,7 +74,7 @@ describe('Tree', () => {
             { children: [], id: 'Node 3', parent: '', value: 'Value 1' },
             { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }
         ].reduce((tree, node) => Tree.setNode(node)(tree), tree);
-        expect(Tree.getNodeChildren('')(newTree)).toEqual(['Node 1', 'Node 3']);
+        expect(Tree.getNodeChildrenIds('')(newTree)).toEqual(['Node 1', 'Node 3']);
     });
 
     it('maps tree', () => {
index 8b66e50da7961df58c5c60f0b8fd23556f749467..a5fb49cff4a3adb3945cdcfbcb1a1ec6b4ac5080 100644 (file)
@@ -6,7 +6,7 @@ export type Tree<T> = Record<string, TreeNode<T>>;
 
 export const TREE_ROOT_ID = '';
 
-export interface TreeNode<T> {
+export interface TreeNode<T = any> {
     children: string[];
     value: T;
     id: string;
@@ -21,7 +21,7 @@ export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
     const [newTree] = [tree]
         .map(tree => getNode(node.id)(tree) === node
             ? tree
-            : {...tree, [node.id]: node})
+            : { ...tree, [node.id]: node })
         .map(addChild(node.parent, node.id));
     return newTree;
 };
@@ -46,25 +46,32 @@ export const setNodeValueWith = <T>(mapFn: (value: T) => T) => (id: string) => (
 };
 
 export const mapTreeValues = <T, R>(mapFn: (value: T) => R) => (tree: Tree<T>): Tree<R> =>
-    getNodeDescendants('')(tree)
+    getNodeDescendantsIds('')(tree)
         .map(id => getNode(id)(tree))
         .map(mapNodeValue(mapFn))
         .reduce((newTree, node) => setNode(node)(newTree), createTree<R>());
 
-export const mapTree = <T, R>(mapFn: (node: TreeNode<T>) => TreeNode<R>) => (tree: Tree<T>): Tree<R> =>
-    getNodeDescendants('')(tree)
+export const mapTree = <T, R = T>(mapFn: (node: TreeNode<T>) => TreeNode<R>) => (tree: Tree<T>): Tree<R> =>
+    getNodeDescendantsIds('')(tree)
         .map(id => getNode(id)(tree))
         .map(mapFn)
         .reduce((newTree, node) => setNode(node)(newTree), createTree<R>());
 
-export const getNodeAncestors = (id: string) => <T>(tree: Tree<T>): string[] => {
+export const getNodeAncestors = (id: string) => <T>(tree: Tree<T>) =>
+    mapIdsToNodes(getNodeAncestorsIds(id)(tree))(tree);
+
+
+export const getNodeAncestorsIds = (id: string) => <T>(tree: Tree<T>): string[] => {
     const node = getNode(id)(tree);
     return node && node.parent
-        ? [...getNodeAncestors(node.parent)(tree), node.parent]
+        ? [...getNodeAncestorsIds(node.parent)(tree), node.parent]
         : [];
 };
 
-export const getNodeDescendants = (id: string, limit = Infinity) => <T>(tree: Tree<T>): string[] => {
+export const getNodeDescendants = (id: string, limit = Infinity) => <T>(tree: Tree<T>) =>
+    mapIdsToNodes(getNodeDescendantsIds(id, limit)(tree))(tree);
+
+export const getNodeDescendantsIds = (id: string, limit = Infinity) => <T>(tree: Tree<T>): string[] => {
     const node = getNode(id)(tree);
     const children = node ? node.children :
         id === TREE_ROOT_ID
@@ -75,12 +82,18 @@ export const getNodeDescendants = (id: string, limit = Infinity) => <T>(tree: Tr
         .concat(limit < 1
             ? []
             : children
-                .map(id => getNodeDescendants(id, limit - 1)(tree))
+                .map(id => getNodeDescendantsIds(id, limit - 1)(tree))
                 .reduce((nodes, nodeChildren) => [...nodes, ...nodeChildren], []));
 };
 
-export const getNodeChildren = (id: string) => <T>(tree: Tree<T>): string[] =>
-    getNodeDescendants(id, 0)(tree);
+export const getNodeChildren = (id: string) => <T>(tree: Tree<T>) =>
+    mapIdsToNodes(getNodeChildrenIds(id)(tree))(tree);
+
+export const getNodeChildrenIds = (id: string) => <T>(tree: Tree<T>): string[] =>
+    getNodeDescendantsIds(id, 0)(tree);
+
+export const mapIdsToNodes = (ids: string[]) => <T>(tree: Tree<T>) =>
+    ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode<T> => node !== undefined);
 
 const mapNodeValue = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
     ({ ...node, value: mapFn(node.value) });
index c3fd43ead1d0d4c013e066f0331712710af9f38f..0c7e91deecf4aba5bc9465569c40118f9401d536 100644 (file)
@@ -4,11 +4,11 @@
 
 import { uniqBy, groupBy } from 'lodash';
 import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "~/models/keep-manifest";
-import { TreeNode, setNode, createTree, getNodeDescendants, getNodeValue } from '~/models/tree';
+import { TreeNode, setNode, createTree, getNodeDescendantsIds, getNodeValue } from '~/models/tree';
 import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file';
 
 export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => {
-    const values = getNodeDescendants('')(tree).map(id => getNodeValue(id)(tree));
+    const values = getNodeDescendantsIds('')(tree).map(id => getNodeValue(id)(tree));
     const files = values.filter(value => value && value.type === CollectionFileType.FILE) as CollectionFile[];
     const fileGroups = groupBy(files, file => file.path);
     return Object
index 581a6fa6613e34e002fd847ddbe9c9f10c71116f..b8a7970d58d4310d8228b8d0c80fb04188614810 100644 (file)
@@ -3,8 +3,8 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { createCollectionFilesTree, CollectionDirectory, CollectionFile, CollectionFileType, createCollectionDirectory, createCollectionFile } from "../../models/collection-file";
-import { Tree, mapTree, getNodeChildren, getNode, TreeNode } from "../../models/tree";
-import { getTagValue } from "../../common/xml";
+import { getTagValue } from "~/common/xml";
+import { getNodeChildren, Tree, mapTree } from '~/models/tree';
 
 export const parseFilesResponse = (document: Document) => {
     const files = extractFilesData(document);
@@ -13,10 +13,8 @@ export const parseFilesResponse = (document: Document) => {
 };
 
 export const sortFilesTree = (tree: Tree<CollectionDirectory | CollectionFile>) => {
-    return mapTree(node => {
-        const children = getNodeChildren(node.id)(tree)
-            .map(id => getNode(id)(tree))
-            .filter(node => node !== undefined) as TreeNode<CollectionDirectory | CollectionFile>[];
+    return mapTree<CollectionDirectory | CollectionFile>(node => {
+        const children = getNodeChildren(node.id)(tree);
 
         children.sort((a, b) =>
             a.value.type !== b.value.type
@@ -24,7 +22,7 @@ export const sortFilesTree = (tree: Tree<CollectionDirectory | CollectionFile>)
                 : a.value.name.localeCompare(b.value.name)
         );
         return { ...node, children: children.map(child => child.id) };
-    })(tree) as Tree<CollectionDirectory | CollectionFile>;
+    })(tree);
 };
 
 export const extractFilesData = (document: Document) => {
index cedfbebef5aded305b3237e125ee80857c96361b..3d8308013e7dcda1b016017b81fa22b0b589abba 100644 (file)
@@ -9,7 +9,7 @@ import { ServiceRepository } from "~/services/services";
 import { RootState } from "../../store";
 import { snackbarActions } from "../../snackbar/snackbar-actions";
 import { dialogActions } from "../../dialog/dialog-actions";
-import { getNodeValue, getNodeDescendants } from "~/models/tree";
+import { getNodeValue, getNodeDescendants } from '~/models/tree';
 import { CollectionPanelDirectory, CollectionPanelFile } from "./collection-panel-files-state";
 
 export const collectionPanelFilesAction = unionize({
@@ -44,8 +44,7 @@ export const removeCollectionsSelectedFiles = () =>
     (dispatch: Dispatch, getState: () => RootState) => {
         const tree = getState().collectionPanelFiles;
         const allFiles = getNodeDescendants('')(tree)
-            .map(id => getNodeValue(id)(tree))
-            .filter(file => file !== undefined) as Array<CollectionPanelDirectory | CollectionPanelFile>;
+            .map(node => node.value);
 
         const selectedDirectories = allFiles.filter(file => file.selected && file.type === CollectionFileType.DIRECTORY);
         const selectedFiles = allFiles.filter(file => file.selected && !selectedDirectories.some(dir => dir.id === file.path));
index 08b60308c42bb9d4f3dfdeb72eae23f4b45946de..dde622a7d7ca5c06ddd9287b653addd156459213 100644 (file)
@@ -4,7 +4,7 @@
 
 import { CollectionPanelFilesState, CollectionPanelFile, CollectionPanelDirectory, mapCollectionFileToCollectionPanelFile, mergeCollectionPanelFilesStates } from "./collection-panel-files-state";
 import { CollectionPanelFilesAction, collectionPanelFilesAction } from "./collection-panel-files-actions";
-import { createTree, mapTreeValues, getNode, setNode, getNodeAncestors, getNodeDescendants, setNodeValueWith, mapTree } from "~/models/tree";
+import { createTree, mapTreeValues, getNode, setNode, getNodeAncestorsIds, getNodeDescendantsIds, setNodeValueWith, mapTree } from "~/models/tree";
 import { CollectionFileType } from "~/models/collection-file";
 
 export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
@@ -44,7 +44,7 @@ const toggleSelected = (id: string) => (tree: CollectionPanelFilesState) =>
 const toggleDescendants = (id: string) => (tree: CollectionPanelFilesState) => {
     const node = getNode(id)(tree);
     if (node && node.value.type === CollectionFileType.DIRECTORY) {
-        return getNodeDescendants(id)(tree)
+        return getNodeDescendantsIds(id)(tree)
             .reduce((newTree, id) =>
                 setNodeValueWith(v => ({ ...v, selected: node.value.selected }))(id)(newTree), tree);
     }
@@ -52,7 +52,7 @@ const toggleDescendants = (id: string) => (tree: CollectionPanelFilesState) => {
 };
 
 const toggleAncestors = (id: string) => (tree: CollectionPanelFilesState) => {
-    const ancestors = getNodeAncestors(id)(tree).reverse();
+    const ancestors = getNodeAncestorsIds(id)(tree).reverse();
     return ancestors.reduce((newTree, parent) => parent ? toggleParentNode(parent)(newTree) : newTree, tree);
 };
 
index 351f3b5c953bbeb67629a72da3297e5b5b11d807..0fa55d836cd9510d1840ffb450b9e57801a4a34b 100644 (file)
@@ -27,9 +27,9 @@ export const collectionUploaderActions = unionize({
     SET_UPLOAD_PROGRESS: ofType<{ fileId: number, loaded: number, total: number, currentTime: number }>(),\r
     CLEAR_UPLOAD: ofType()\r
 }, {\r
-        tag: 'type',\r
-        value: 'payload'\r
-    });\r
+    tag: 'type',\r
+    value: 'payload'\r
+});\r
 \r
 export type CollectionUploaderAction = UnionOf<typeof collectionUploaderActions>;\r
 \r
index 3248cb2efba7f06af804c0b4f6a169de02170a67..b092d5ac2a89c80ed29b0d525bacee3790e24821 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { createTree, getNodeValue, getNodeChildren } from "~/models/tree";
+import { createTree, getNodeValue, getNodeChildrenIds } from "~/models/tree";
 import { TreePickerNode, createTreePickerNode } from "./tree-picker";
 import { treePickerReducer } from "./tree-picker-reducer";
 import { treePickerActions } from "./tree-picker-actions";
@@ -31,7 +31,7 @@ describe('TreePickerReducer', () => {
         const tree = createTree<TreePickerNode>();
         const subNode = createTreePickerNode({ id: '1.1', value: '1.1' });
         const newTree = treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [subNode] }));
-        expect(getNodeChildren('')(newTree)).toEqual(['1.1']);
+        expect(getNodeChildrenIds('')(newTree)).toEqual(['1.1']);
     });
 
     it('LOAD_TREE_PICKER_NODE_SUCCESS', () => {
@@ -41,7 +41,7 @@ describe('TreePickerReducer', () => {
         const [newTree] = [tree]
             .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node] })))
             .map(tree => treePickerReducer(tree, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode] })));
-        expect(getNodeChildren('1')(newTree)).toEqual(['1.1']);
+        expect(getNodeChildrenIds('1')(newTree)).toEqual(['1.1']);
         expect(getNodeValue('1')(newTree)).toEqual({
             ...createTreePickerNode({ id: '1', value: '1' }),
             status: TreeItemStatus.LOADED
index 21706e856624c57aacd59c90749c6051fdd636da..3e99e10a709bca515d695c4ed734023bc229748a 100644 (file)
@@ -12,7 +12,7 @@ import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import { contextMenuActions } from "~/store/context-menu/context-menu-actions";
 import { ContextMenuKind } from "../context-menu/context-menu";
-import { Tree, getNodeChildren, getNode } from "~/models/tree";
+import { Tree, getNodeChildrenIds, getNode } from "~/models/tree";
 import { CollectionFileType } from "~/models/collection-file";
 import { openUploadCollectionFilesDialog } from '~/store/collections/uploader/collection-uploader-actions';
 
@@ -23,7 +23,7 @@ const memoizedMapStateToProps = () => {
     return (state: RootState): Pick<CollectionPanelFilesProps, "items"> => {
         if (prevState !== state.collectionPanelFiles) {
             prevState = state.collectionPanelFiles;
-            prevTree = getNodeChildren('')(state.collectionPanelFiles)
+            prevTree = getNodeChildrenIds('')(state.collectionPanelFiles)
                 .map(collectionItemToTreeItem(state.collectionPanelFiles));
         }
         return {
@@ -80,7 +80,7 @@ const collectionItemToTreeItem = (tree: Tree<CollectionPanelDirectory | Collecti
                 type: node.value.type
             },
             id: node.id,
-            items: getNodeChildren(node.id)(tree)
+            items: getNodeChildrenIds(node.id)(tree)
                 .map(collectionItemToTreeItem(tree)),
             open: node.value.type === CollectionFileType.DIRECTORY ? !node.value.collapsed : false,
             selected: node.value.selected,
index 09a07443f26b9097ddcccfa9ded1b95d2dea5d85..ba9ccb916efd38d955badedae7e6a7418871d354 100644 (file)
@@ -6,7 +6,7 @@ import { connect } from "react-redux";
 import { Tree, TreeProps, TreeItem } from "~/components/tree/tree";
 import { RootState } from "~/store/store";
 import { TreePicker as TTreePicker, TreePickerNode, createTreePickerNode } from "~/store/tree-picker/tree-picker";
-import { getNodeValue, getNodeChildren } from "~/models/tree";
+import { getNodeValue, getNodeChildrenIds } from "~/models/tree";
 
 const memoizedMapStateToProps = () => {
     let prevState: TTreePicker;
@@ -15,7 +15,7 @@ const memoizedMapStateToProps = () => {
     return (state: RootState): Pick<TreeProps<any>, 'items'> => {
         if (prevState !== state.treePicker) {
             prevState = state.treePicker;
-            prevTree = getNodeChildren('')(state.treePicker)
+            prevTree = getNodeChildrenIds('')(state.treePicker)
                 .map(treePickerToTreeItems(state.treePicker));
         }
         return {
@@ -33,7 +33,7 @@ export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)
 const treePickerToTreeItems = (tree: TTreePicker) =>
     (id: string): TreeItem<any> => {
         const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ id: '', value: 'InvalidNode' });
-        const items = getNodeChildren(node.id)(tree)
+        const items = getNodeChildrenIds(node.id)(tree)
             .map(treePickerToTreeItems(tree));
         return {
             active: node.selected,