15610: Avoids loading the file list on big collections, offers manual loading.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 15 Jun 2020 19:03:58 +0000 (16:03 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 15 Jun 2020 19:03:58 +0000 (16:03 -0300)
After the previous performance enhancements, 75% of the time spent to show
the collection's files goes to the WebDAV request + parsing, so to avoid
inadvertently freezing the UI, when the file_count field passes a predefined
value (now 40k files), the user gets the option to manually load the file
listing by clicking on a button.

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

src/components/collection-panel-files/collection-panel-files.tsx
src/store/collection-panel/collection-panel-action.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/store/collection-panel/collection-panel-reducer.ts
src/views/collection-panel/collection-panel.tsx

index 3a2d55fbc95c87d1285572c661d7bad894bacdfc..4d7e938495b4934cfb02699f55e3ce337e1b34f7 100644 (file)
@@ -14,12 +14,14 @@ export interface CollectionPanelFilesProps {
     items: Array<TreeItem<FileTreeData>>;
     isWritable: boolean;
     isLoading: boolean;
+    tooManyFiles: boolean;
     onUploadDataClick: () => void;
     onItemMenuOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>, isWritable: boolean) => void;
     onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, isWritable: boolean) => void;
     onSelectionToggle: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
     onCollapseToggle: (id: string, status: TreeItemStatus) => void;
     onFileClick: (id: string) => void;
+    loadFilesFunc: () => void;
     currentItemUuid?: string;
 }
 
@@ -55,7 +57,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 export const CollectionPanelFiles =
     withStyles(styles)(
         ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes,
-            isWritable, isLoading, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
+            isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
             <Card data-cy='collection-files-panel' className={classes.root}>
                 <CardHeader
                     title="Files"
@@ -72,26 +74,33 @@ export const CollectionPanelFiles =
                             Upload data
                         </Button>
                     } />
-                <CardHeader
-                    className={classes.cardSubheader}
-                    action={
-                        <Tooltip title="More options" disableFocusListener>
-                            <IconButton
-                                data-cy='collection-files-panel-options-btn'
-                                onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
-                                <CustomizeTableIcon />
-                            </IconButton>
-                        </Tooltip>
-                    } />
-                <Grid container justify="space-between">
-                    <Typography variant="caption" className={classes.nameHeader}>
-                        Name
-                    </Typography>
-                    <Typography variant="caption" className={classes.fileSizeHeader}>
-                        File size
-                    </Typography>
-                </Grid>
-                { isLoading
-                ? <div className={classes.centeredLabel}>(loading files...)</div>
-                : <FileTree onMenuOpen={(ev, item) => onItemMenuOpen(ev, item, isWritable)} {...treeProps} /> }
+                { tooManyFiles
+                ? <div className={classes.centeredLabel}>
+                        File listing may take some time, please click to browse: <Button onClick={loadFilesFunc}><DownloadIcon/>Show files</Button>
+                </div>
+                : <>
+                    <CardHeader
+                        className={classes.cardSubheader}
+                        action={
+                            <Tooltip title="More options" disableFocusListener>
+                                <IconButton
+                                    data-cy='collection-files-panel-options-btn'
+                                    onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
+                                    <CustomizeTableIcon />
+                                </IconButton>
+                            </Tooltip>
+                        } />
+                    <Grid container justify="space-between">
+                        <Typography variant="caption" className={classes.nameHeader}>
+                            Name
+                        </Typography>
+                        <Typography variant="caption" className={classes.fileSizeHeader}>
+                            File size
+                        </Typography>
+                    </Grid>
+                    { isLoading
+                    ? <div className={classes.centeredLabel}>(loading files...)</div>
+                    : <FileTree onMenuOpen={(ev, item) => onItemMenuOpen(ev, item, isWritable)} {...treeProps} /> }
+                </>
+                }
             </Card>);
index 9922d8b58ab9768b925aca4f6e17e19f7474244b..35c3c3d39a1065bfaea03bfe5fa3faab851b7f6d 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from "redux";
-import { loadCollectionFiles } from "./collection-panel-files/collection-panel-files-actions";
+import { loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from "./collection-panel-files/collection-panel-files-actions";
 import { CollectionResource } from '~/models/collection';
 import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
 import { createTree } from "~/models/tree";
@@ -21,7 +21,8 @@ import { addProperty, deleteProperty } from "~/lib/resource-properties";
 export const collectionPanelActions = unionize({
     SET_COLLECTION: ofType<CollectionResource>(),
     LOAD_COLLECTION: ofType<{ uuid: string }>(),
-    LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>()
+    LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
+    LOAD_BIG_COLLECTIONS: ofType<boolean>(),
 });
 
 export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
@@ -36,7 +37,10 @@ export const loadCollectionPanel = (uuid: string) =>
         dispatch(loadDetailsPanel(collection.uuid));
         dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection }));
         dispatch(resourcesActions.SET_RESOURCES([collection]));
-        dispatch<any>(loadCollectionFiles(collection.uuid));
+        if (collection.fileCount <= COLLECTION_PANEL_LOAD_FILES_THRESHOLD &&
+            !getState().collectionPanel.loadBigCollections) {
+            dispatch<any>(loadCollectionFiles(collection.uuid));
+        }
         return collection;
     };
 
index fe93eef2fed1576f2fbb71796d32ece808342d74..204d4c0e1dbe8f4da27ba74313f560a9a33909fb 100644 (file)
@@ -27,6 +27,7 @@ export const collectionPanelFilesAction = unionize({
 export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
 
 export const COLLECTION_PANEL_LOAD_FILES = 'collectionPanelLoadFiles';
+export const COLLECTION_PANEL_LOAD_FILES_THRESHOLD = 40000;
 
 export const loadCollectionFiles = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
index f09b019873e98e09b082a638a3f12a5b0eea93b2..18590181fb0e79fe150226a66baa9ee51a1f9e64 100644 (file)
@@ -7,15 +7,22 @@ import { CollectionResource } from "~/models/collection";
 
 export interface CollectionPanelState {
     item: CollectionResource | null;
+    loadBigCollections: boolean;
 }
 
 const initialState = {
-    item: null
+    item: null,
+    loadBigCollections: false,
 };
 
 export const collectionPanelReducer = (state: CollectionPanelState = initialState, action: CollectionPanelAction) =>
     collectionPanelActions.match(action, {
         default: () => state,
-        SET_COLLECTION: (item) => ({ ...state, item }),
-        LOAD_COLLECTION_SUCCESS: ({ item }) => ({ ...state, item })
+        SET_COLLECTION: (item) => ({
+             ...state,
+             item,
+             loadBigCollections: false,
+        }),
+        LOAD_COLLECTION_SUCCESS: ({ item }) => ({ ...state, item }),
+        LOAD_BIG_COLLECTIONS: (loadBigCollections) => ({ ...state, loadBigCollections}),
     });
index 27a685410116fb238e44c50f7a2808aef142b1d2..1cfa48de7d5d63d494108ce6b12f869bcec14a2a 100644 (file)
@@ -16,7 +16,7 @@ import { DetailsAttribute } from '~/components/details-attribute/details-attribu
 import { CollectionResource } from '~/models/collection';
 import { CollectionPanelFiles } from '~/views-components/collection-panel-files/collection-panel-files';
 import { CollectionTagForm } from './collection-tag-form';
-import { deleteCollectionTag, navigateToProcess } from '~/store/collection-panel/collection-panel-action';
+import { deleteCollectionTag, navigateToProcess, collectionPanelActions } from '~/store/collection-panel/collection-panel-action';
 import { getResource } from '~/store/resources/resources';
 import { openContextMenu } from '~/store/context-menu/context-menu-actions';
 import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
@@ -29,7 +29,7 @@ import { GroupResource } from '~/models/group';
 import { UserResource } from '~/models/user';
 import { getUserUuid } from '~/common/getuser';
 import { getProgressIndicator } from '~/store/progress-indicator/progress-indicator-reducer';
-import { COLLECTION_PANEL_LOAD_FILES } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
+import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
 
 type CssRules = 'card' | 'iconHeader' | 'tag' | 'label' | 'value' | 'link' | 'centeredLabel' | 'readOnlyIcon';
 
@@ -73,6 +73,7 @@ interface CollectionPanelDataProps {
     item: CollectionResource;
     isWritable: boolean;
     isLoadingFiles: boolean;
+    tooManyFiles: boolean;
 }
 
 type CollectionPanelProps = CollectionPanelDataProps & DispatchProp
@@ -93,11 +94,12 @@ export const CollectionPanel = withStyles(styles)(
         }
         const loadingFilesIndicator = getProgressIndicator(COLLECTION_PANEL_LOAD_FILES)(state.progressIndicator);
         const isLoadingFiles = loadingFilesIndicator && loadingFilesIndicator!.working || false;
-        return { item, isWritable, isLoadingFiles };
+        const tooManyFiles = !state.collectionPanel.loadBigCollections && item && item.fileCount > COLLECTION_PANEL_LOAD_FILES_THRESHOLD || false;
+        return { item, isWritable, isLoadingFiles, tooManyFiles };
     })(
         class extends React.Component<CollectionPanelProps> {
             render() {
-                const { classes, item, dispatch, isWritable, isLoadingFiles } = this.props;
+                const { classes, item, dispatch, isWritable, isLoadingFiles, tooManyFiles } = this.props;
                 return item
                     ? <>
                         <Card data-cy='collection-info-panel' className={classes.card}>
@@ -188,7 +190,15 @@ export const CollectionPanel = withStyles(styles)(
                             </CardContent>
                         </Card>
                         <div className={classes.card}>
-                            <CollectionPanelFiles isWritable={isWritable} isLoading={isLoadingFiles} />
+                            <CollectionPanelFiles
+                                isWritable={isWritable}
+                                isLoading={isLoadingFiles}
+                                tooManyFiles={tooManyFiles}
+                                loadFilesFunc={() => {
+                                    dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true));
+                                    dispatch<any>(loadCollectionFiles(this.props.item.uuid));
+                                }
+                            } />
                         </div>
                     </>
                     : null;