13494: Adds content to the "versions" tab on collection's details panel.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Fri, 13 Nov 2020 18:16:19 +0000 (15:16 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Fri, 13 Nov 2020 18:16:19 +0000 (15:16 -0300)
Old versions are requested only when the details panel is open.

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

13 files changed:
src/components/collection-panel-files/collection-panel-files.tsx
src/store/collection-panel/collection-panel-action.ts
src/store/details-panel/details-panel-action.ts
src/store/details-panel/details-panel-reducer.ts
src/store/workbench/workbench-actions.ts
src/views-components/collection-panel-files/collection-panel-files.ts
src/views-components/details-panel/collection-details.tsx
src/views/all-processes-panel/all-processes-panel.tsx
src/views/collection-panel/collection-panel.tsx
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel.tsx
src/views/shared-with-me-panel/shared-with-me-panel.tsx
src/views/trash-panel/trash-panel.tsx

index bf551b9ffab15834d07c0052a70b193b3a95afc5..14c3c48c98011785939ce09f4a00ae9726022ae9 100644 (file)
@@ -119,10 +119,10 @@ export const CollectionPanelFilesComponent = ({ onItemMenuOpen, onSearchChange,
                 <Grid container justify="space-between">
                     <Typography variant="caption" className={classes.nameHeader}>
                         Name
-                </Typography>
+                    </Typography>
                     <Typography variant="caption" className={classes.fileSizeHeader}>
                         File size
-                </Typography>
+                    </Typography>
                 </Grid>
                 {isLoading
                     ? <div className={classes.centeredLabel}><CircularProgress /></div>
index 15d5ef72934f4d2fe3a26e15306e4e72ad9da864..851ba84d73e8d095b7be3a563149ee05e936c0cb 100644 (file)
@@ -3,7 +3,10 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from "redux";
-import { loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } 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 { RootState } from "~/store/store";
 import { ServiceRepository } from "~/services/services";
@@ -18,7 +21,6 @@ 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_BIG_COLLECTIONS: ofType<boolean>(),
 });
@@ -30,9 +32,8 @@ export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
 export const loadCollectionPanel = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const { collectionPanel: { item } } = getState();
-        dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid }));
         const collection = item ? item : await services.collectionService.get(uuid);
-        dispatch(loadDetailsPanel(collection.uuid));
+        dispatch<any>(loadDetailsPanel(collection.uuid));
         dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection }));
         dispatch(resourcesActions.SET_RESOURCES([collection]));
         if (collection.fileCount <= COLLECTION_PANEL_LOAD_FILES_THRESHOLD &&
index c5d472ade5a268de6d53ccaa39747b036438488e..cbce4210e5b055cb55217ae68a395305bc805342 100644 (file)
@@ -14,12 +14,16 @@ import { startSubmit, stopSubmit } from 'redux-form';
 import { resourcesActions } from '~/store/resources/resources-actions';
 import {snackbarActions, SnackbarKind} from '~/store/snackbar/snackbar-actions';
 import { addProperty, deleteProperty } from '~/lib/resource-properties';
+import { FilterBuilder } from '~/services/api/filter-builder';
+import { OrderBuilder } from '~/services/api/order-builder';
+import { CollectionResource } from '~/models/collection';
+import { extractUuidKind, ResourceKind } from '~/models/resource';
 
 export const SLIDE_TIMEOUT = 500;
 
 export const detailsPanelActions = unionize({
     TOGGLE_DETAILS_PANEL: ofType<{}>(),
-    OPEN_DETAILS_PANEL: ofType<string>(),
+    OPEN_DETAILS_PANEL: ofType<number>(),
     LOAD_DETAILS_PANEL: ofType<string>()
 });
 
@@ -28,15 +32,43 @@ export type DetailsPanelAction = UnionOf<typeof detailsPanelActions>;
 export const PROJECT_PROPERTIES_FORM_NAME = 'projectPropertiesFormName';
 export const PROJECT_PROPERTIES_DIALOG_NAME = 'projectPropertiesDialogName';
 
-export const loadDetailsPanel = (uuid: string) => detailsPanelActions.LOAD_DETAILS_PANEL(uuid);
+export const loadDetailsPanel = (uuid: string) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        if (getState().detailsPanel.isOpened) {
+            switch(extractUuidKind(uuid)) {
+                case ResourceKind.COLLECTION:
+                    dispatch<any>(refreshCollectionVersionsList(uuid));
+                    break;
+                default:
+                    break;
+            }
+        }
+        dispatch(detailsPanelActions.LOAD_DETAILS_PANEL(uuid));
+    };
 
-export const openDetailsPanel = (uuid: string) => detailsPanelActions.OPEN_DETAILS_PANEL(uuid);
+export const openDetailsPanel = (uuid: string, tabNr: number = 0) =>
+    (dispatch: Dispatch) => {
+        dispatch<any>(loadDetailsPanel(uuid));
+        dispatch(detailsPanelActions.OPEN_DETAILS_PANEL(tabNr));
+    };
 
 export const openProjectPropertiesDialog = () =>
     (dispatch: Dispatch) => {
         dispatch<any>(dialogActions.OPEN_DIALOG({ id: PROJECT_PROPERTIES_DIALOG_NAME, data: { } }));
     };
 
+export const refreshCollectionVersionsList = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const versions = await services.collectionService.list({
+            filters: new FilterBuilder()
+                .addEqual('current_version_uuid', uuid)
+                .getFilters(),
+            includeOldVersions: true,
+            order: new OrderBuilder<CollectionResource>().addDesc("version").getOrder()
+        });
+        dispatch(resourcesActions.SET_RESOURCES(versions.items.slice(1)));
+    };
+
 export const deleteProjectProperty = (key: string, value: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const { detailsPanel, resources } = getState();
index 38c0edd506d148c8442a98ad20e49bdfe8494d4d..6c32551cbf6926f9975e4bca40d1728295c54d05 100644 (file)
@@ -7,17 +7,19 @@ import { detailsPanelActions, DetailsPanelAction } from "./details-panel-action"
 export interface DetailsPanelState {
     resourceUuid: string;
     isOpened: boolean;
+    tabNr: number;
 }
 
 const initialState = {
     resourceUuid: '',
-    isOpened: false
+    isOpened: false,
+    tabNr: 0
 };
 
 export const detailsPanelReducer = (state: DetailsPanelState = initialState, action: DetailsPanelAction) =>
     detailsPanelActions.match(action, {
         default: () => state,
         LOAD_DETAILS_PANEL: resourceUuid => ({ ...state, resourceUuid }),
-        OPEN_DETAILS_PANEL: resourceUuid => ({ resourceUuid, isOpened: true }),
+        OPEN_DETAILS_PANEL: tabNr => ({ ...state, isOpened: true, tabNr }),
         TOGGLE_DETAILS_PANEL: () => ({ ...state, isOpened: !state.isOpened }),
     });
index 944c48cf8cc6006ee412182c952f765f05e1004d..0416d815805640bbe7bc69fb046cec7abc169a1d 100644 (file)
@@ -379,7 +379,7 @@ export const loadProcess = (uuid: string) =>
             const process = await dispatch<any>(processesActions.loadProcess(uuid));
             await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
             dispatch<any>(setProcessBreadcrumbs(uuid));
-            dispatch(loadDetailsPanel(uuid));
+            dispatch<any>(loadDetailsPanel(uuid));
         });
 
 export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialogData) =>
index 9859f84b9de2d0af578d90526f936ad30951b7f4..91420edb52e19932b7e5ea29756f1bdb5a3fb504 100644 (file)
@@ -75,7 +75,7 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps,
         dispatch<any>(openCollectionFilesContextMenu(event, isWritable));
     },
     onFileClick: (id) => {
-        dispatch(openDetailsPanel(id));
+        dispatch<any>(openDetailsPanel(id));
     },
 });
 
index d2457559e4d8207189df0c0818a9b698e60643c1..51b09b4faa48e43bf46f85088212b669bba9ce91 100644 (file)
@@ -7,6 +7,25 @@ import { CollectionIcon } from '~/components/icon/icon';
 import { CollectionResource } from '~/models/collection';
 import { DetailsData } from "./details-data";
 import { CollectionDetailsAttributes } from '~/views/collection-panel/collection-panel';
+import { RootState } from '~/store/store';
+import { filterResources, getResource } from '~/store/resources/resources';
+import { connect } from 'react-redux';
+import { Grid, ListItem, StyleRulesCallback, Typography, withStyles, WithStyles } from '@material-ui/core';
+import { formatDate, formatFileSize } from '~/common/formatters';
+import { Dispatch } from 'redux';
+import { navigateTo } from '~/store/navigation/navigation-action';
+
+export type CssRules = 'versionBrowserHeader' | 'selectedVersion';
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+    versionBrowserHeader: {
+        textAlign: 'center',
+        fontWeight: 'bold'
+    },
+    selectedVersion: {
+        fontWeight: 'bold'
+    }
+});
 
 export class CollectionDetails extends DetailsData<CollectionResource> {
 
@@ -34,6 +53,70 @@ export class CollectionDetails extends DetailsData<CollectionResource> {
     }
 
     private getVersionBrowser() {
-        return <div />;
+        return <CollectionVersionBrowser />;
     }
 }
+
+interface CollectionVersionBrowserProps {
+    currentCollection: CollectionResource | undefined;
+    versions: CollectionResource[];
+}
+
+interface CollectionVersionBrowserDispatchProps {
+    showVersion: (c: CollectionResource) => void;
+}
+
+const mapStateToProps = (state: RootState): CollectionVersionBrowserProps => {
+    const currentCollection = getResource<CollectionResource>(state.detailsPanel.resourceUuid)(state.resources);
+    const versions = currentCollection
+        && filterResources(rsc =>
+            (rsc as CollectionResource).currentVersionUuid === currentCollection.currentVersionUuid)(state.resources)
+                .sort((a: CollectionResource, b: CollectionResource) => b.version - a.version) as CollectionResource[]
+        || [];
+    return { currentCollection, versions };
+};
+
+const mapDispatchToProps = () =>
+    (dispatch: Dispatch): CollectionVersionBrowserDispatchProps => ({
+        showVersion: (collection) => dispatch<any>(navigateTo(collection.uuid)),
+    });
+
+const CollectionVersionBrowser = withStyles(styles)(
+    connect(mapStateToProps, mapDispatchToProps)(
+        ({ currentCollection, versions, showVersion, classes }: CollectionVersionBrowserProps & CollectionVersionBrowserDispatchProps & WithStyles<CssRules>) => {
+            return <>
+                <Grid container justify="space-between">
+                    <Typography variant="caption" className={classes.versionBrowserHeader}>
+                        Version
+                    </Typography>
+                    <Typography variant="caption" className={classes.versionBrowserHeader}>
+                        Size
+                    </Typography>
+                    <Typography variant="caption" className={classes.versionBrowserHeader}>
+                        Date
+                    </Typography>
+                </Grid>
+                { versions.map(item => {
+                    const isSelectedVersion = !!(currentCollection && currentCollection.uuid === item.uuid);
+                    return (
+                        <ListItem button
+                            className={isSelectedVersion ? 'selectedVersion' : ''}
+                            key={item.version}
+                            onClick={e => showVersion(item)}
+                            selected={isSelectedVersion}>
+                            <Grid container justify="space-between">
+                                <Typography variant="caption">
+                                    {item.version}
+                                </Typography>
+                                <Typography variant="caption">
+                                    {formatFileSize(item.fileSizeTotal)}
+                                </Typography>
+                                <Typography variant="caption">
+                                    {formatDate(item.modifiedAt)}
+                                </Typography>
+                            </Grid>
+                        </ListItem>
+                    );
+                })}
+            </>;
+        }));
\ No newline at end of file
index 650a0d95c840ddc1dc93ac8ba6195eb1abd64dd9..d9a0fd90a392f9d2d1197e07043084244e082cf1 100644 (file)
@@ -138,7 +138,7 @@ export const AllProcessesPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
             render() {
index feade60c1d500c86b9655f84beff95793dd434bd..f6df621284e71fe28e28a4f7eec5d9042ae68738 100644 (file)
@@ -266,7 +266,7 @@ export const CollectionPanel = withStyles(styles)(
                 const { item } = this.props;
                 if (item) {
                     e.stopPropagation();
-                    this.props.dispatch(openDetailsPanel(item.uuid));
+                    this.props.dispatch<any>(openDetailsPanel(item.uuid));
                 }
             }
 
index 6b5cd4c353a81ec3528a3711fd31a28f0725d4a2..cad2f9ba5b42d3c8f84c5df0db5b6d97694bf7be 100644 (file)
@@ -153,7 +153,7 @@ export const FavoritePanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
             render() {
index d79b98cf0d5ab2f93fd3b48b8fc4079be7fe134d..11223f225ec2e6a1bddb08175086e363149dc655 100644 (file)
@@ -178,7 +178,7 @@ export const ProjectPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
         }
index c9408752b4d6027dcc21742b12e615281b4e0241..9b4bcc8572d890d53d0f62abdfea6f0890e286c5 100644 (file)
@@ -77,7 +77,7 @@ export const SharedWithMePanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
         }
     )
index dec5af513f4d161a1e422aa591fcd48e730adb6c..3173876b8d17e5e5acef4b13d2c13807469cce1c 100644 (file)
@@ -179,7 +179,7 @@ export const TrashPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
         }
     )