Add getting file size and number of collection files in main panel and side panel
authorDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 16 Oct 2018 11:44:23 +0000 (13:44 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Tue, 16 Oct 2018 11:44:40 +0000 (13:44 +0200)
Feature #14349

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

src/models/collection.ts
src/models/tree.ts
src/store/project-panel/project-panel-middleware-service.ts
src/views-components/data-explorer/renderers.tsx
src/views-components/details-panel/collection-details.tsx
src/views-components/details-panel/project-details.tsx
src/views/collection-panel/collection-panel.tsx

index f8e38f9a0fac227bd2cfeccd62654723f9c66ef5..735ffa83d8fbda4bb18e660c28bf6277dbb9f688 100644 (file)
@@ -14,6 +14,8 @@ export interface CollectionResource extends TrashableResource {
     replicationDesired: number;
     replicationConfirmed: number;
     replicationConfirmedAt: string;
+    fileSize: number;
+    fileCount: number;
 }
 
 export const getCollectionUrl = (uuid: string) => {
index f0b53b46f46fd03906cfd81cde9ce63f45a0028a..613601109a44efd0ba69feb517f8ae347380573b 100644 (file)
@@ -86,7 +86,7 @@ export const getNodeDescendantsIds = (id: string, limit = Infinity) => <T>(tree:
     const node = getNode(id)(tree);
     const children = node ? node.children :
         id === TREE_ROOT_ID
-            ? getRootNodeChildren(tree)
+            ? getRootNodeChildrenIds(tree)
             : [];
 
     return children
@@ -207,11 +207,12 @@ const toggleParentNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
 const mapNodeValue = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
     ({ ...node, value: mapFn(node.value) });
 
-const getRootNodeChildren = <T>(tree: Tree<T>) =>
+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> => {
     const node = getNode(parentId)(tree);
     if (node) {
index 09e76ae28b99ef30697228df98f97b78531559f7..27efcd035ecaba857a99e8d0b3f5b534f93c860f 100644 (file)
@@ -2,7 +2,12 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { DataExplorerMiddlewareService, getDataExplorerColumnFilters, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '../data-explorer/data-explorer-middleware-service';
+import {
+    DataExplorerMiddlewareService,
+    dataExplorerToListParams,
+    getDataExplorerColumnFilters,
+    listResultsToDataExplorerItemsMeta
+} from '../data-explorer/data-explorer-middleware-service';
 import { ProjectPanelColumnNames, ProjectPanelFilter } from "~/views/project-panel/project-panel";
 import { RootState } from "../store";
 import { DataColumns } from "~/components/data-table/data-table";
@@ -10,19 +15,23 @@ import { ServiceRepository } from "~/services/services";
 import { SortDirection } from "~/components/data-table/data-column";
 import { OrderBuilder, OrderDirection } from "~/services/api/order-builder";
 import { FilterBuilder } from "~/services/api/filter-builder";
-import { GroupContentsResourcePrefix, GroupContentsResource } from "~/services/groups-service/groups-service";
+import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
 import { updateFavorites } from "../favorites/favorites-actions";
-import { projectPanelActions, PROJECT_PANEL_CURRENT_UUID } from './project-panel-action';
+import { PROJECT_PANEL_CURRENT_UUID, projectPanelActions } from './project-panel-action';
 import { Dispatch, MiddlewareAPI } from "redux";
 import { ProjectResource } from "~/models/project";
-import { updateResources } from "~/store/resources/resources-actions";
+import { resourcesActions, updateResources } from "~/store/resources/resources-actions";
 import { getProperty } from "~/store/properties/properties";
 import { snackbarActions, SnackbarKind } from '../snackbar/snackbar-actions';
 import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions.ts';
 import { DataExplorer, getDataExplorer } from '../data-explorer/data-explorer-reducer';
 import { ListResults } from '~/services/common-service/common-resource-service';
 import { loadContainers } from '../processes/processes-actions';
-import { ResourceKind } from '~/models/resource';
+import { Resource, ResourceKind } from '~/models/resource';
+import { getResource } from "~/store/resources/resources";
+import { CollectionResource } from "~/models/collection";
+import { getNode, getNodeDescendantsIds, TreeNode } from "~/models/tree";
+import { CollectionDirectory, CollectionFile, CollectionFileType } from "~/models/collection-file";
 
 export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -42,8 +51,10 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
                 api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
                 const response = await this.services.groupsService.contents(projectUuid, getParams(dataExplorer));
                 api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId()));
-                api.dispatch<any>(updateFavorites(response.items.map(item => item.uuid)));
+                const resourceUuids = response.items.map(item => item.uuid);
+                api.dispatch<any>(updateFavorites(resourceUuids));
                 api.dispatch(updateResources(response.items));
+                api.dispatch<any>(updateFilesInfo(resourceUuids));
                 await api.dispatch<any>(loadMissingProcessesInformation(response.items));
                 api.dispatch(setItems(response));
             } catch (e) {
@@ -76,6 +87,30 @@ export const loadMissingProcessesInformation = (resources: GroupContentsResource
         }
     };
 
+export const updateFilesInfo = (resourceUuids: string[]) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const resources = await Promise.all(resourceUuids.map(async uuid => {
+            const resource = getResource<CollectionResource>(uuid)(getState().resources);
+            if (resource && resource.kind === ResourceKind.COLLECTION) {
+                const files = await services.collectionService.files(uuid);
+                const flattenFiles: (TreeNode<CollectionFile | CollectionDirectory> | undefined)[] = getNodeDescendantsIds('')(files).map(id => getNode(id)(files));
+                let fileSize = 0;
+                let fileCount = 0;
+                if (flattenFiles) {
+                    fileCount = flattenFiles.length;
+                    fileSize = flattenFiles.reduce((acc, f) => {
+                        return acc + (f && f.value.type === CollectionFileType.FILE ? f.value.size : 0);
+                    }, 0);
+                }
+
+                resource.fileCount = fileCount;
+                resource.fileSize = fileSize;
+            }
+            return resource;
+        }));
+        dispatch(resourcesActions.SET_RESOURCES(resources.filter(res => res) as Resource[]));
+    };
+
 export const setItems = (listResults: ListResults<GroupContentsResource>) =>
     projectPanelActions.SET_ITEMS({
         ...listResultsToDataExplorerItemsMeta(listResults),
index 12e1be7805c61c6f6674e282af0456cd78ce6670..e76600ed9667ccc41ba02485fa085038a4f37cd4 100644 (file)
@@ -19,6 +19,7 @@ import { compose } from 'redux';
 import { WorkflowResource } from '~/models/workflow';
 import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view';
 import { getUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
+import { CollectionResource } from "~/models/collection";
 
 export const renderName = (item: { name: string; uuid: string, kind: string }) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
@@ -149,8 +150,8 @@ export const renderFileSize = (fileSize?: number) =>
 
 export const ResourceFileSize = connect(
     (state: RootState, props: { uuid: string }) => {
-        const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-        return {};
+        const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+        return { fileSize: resource ? resource.fileSize : 0 };
     })((props: { fileSize?: number }) => renderFileSize(props.fileSize));
 
 export const renderOwner = (owner: string) =>
index 721418ef5463c5f043cae43c4a71b4b94db4242d..aec99110f1e2f740ecf7039d8fc20ec221e266f8 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { CollectionIcon } from '~/components/icon/icon';
 import { CollectionResource } from '~/models/collection';
-import { formatDate } from '~/common/formatters';
+import { formatDate, formatFileSize } from '~/common/formatters';
 import { resourceLabel } from '~/common/labels';
 import { ResourceKind } from '~/models/resource';
 import { DetailsData } from "./details-data";
@@ -28,8 +28,8 @@ export class CollectionDetails extends DetailsData<CollectionResource> {
             <DetailsAttribute label='Collection UUID' link={this.item.uuid} value={this.item.uuid} />
             <DetailsAttribute label='Content address' link={this.item.portableDataHash} value={this.item.portableDataHash} />
             {/* Missing attrs */}
-            <DetailsAttribute label='Number of files' value='20' />
-            <DetailsAttribute label='Content size' value='54 MB' />
+            <DetailsAttribute label='Number of files' value={this.item.fileCount} />
+            <DetailsAttribute label='Content size' value={formatFileSize(this.item.fileSize)} />
         </div>;
     }
 }
index 331952f259e7ff357cd48a2e088007064e5a2a8d..18affbacfd0698e6ea3f5eef3b071f6eede3827f 100644 (file)
@@ -27,12 +27,12 @@ export class ProjectDetails extends DetailsData<ProjectResource> {
             <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
             <DetailsAttribute label='Created at' value={formatDate(this.item.createdAt)} />
             {/* Missing attr */}
-            <DetailsAttribute label='File size' value='1.4 GB' />
+            {/*<DetailsAttribute label='File size' value='1.4 GB' />*/}
             <DetailsAttribute label='Description'>
-                {this.item.description ? 
+                {this.item.description ?
                     <RichTextEditorLink
                         title={`Description of ${this.item.name}`}
-                        content={this.item.description} 
+                        content={this.item.description}
                         label='Show full description' />
                     : '---'
                 }
index 96c2fc96f06d9a80f4ee454fd24e932c71ea40f8..7defc072f93205a952688159a2eb6391929edc32 100644 (file)
@@ -22,6 +22,7 @@ import { snackbarActions } from '~/store/snackbar/snackbar-actions';
 import { getResource } from '~/store/resources/resources';
 import { openContextMenu } from '~/store/context-menu/context-menu-actions';
 import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { formatFileSize } from "~/common/formatters";
 
 type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'label' | 'value';
 
@@ -99,9 +100,9 @@ export const CollectionPanel = withStyles(styles)(
                                             </Tooltip>
                                         </DetailsAttribute>
                                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                                            label='Number of files' value='14' />
+                                            label='Number of files' value={item && item.fileCount} />
                                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                                            label='Content size' value='54 MB' />
+                                            label='Content size' value={item && formatFileSize(item.fileSize)} />
                                         <DetailsAttribute classLabel={classes.label} classValue={classes.value}
                                             label='Owner' value={item && item.ownerUuid} />
                                     </Grid>