[ARVADOS-WORKBENCH2] created: 2.1.0-89-gb7f96fb6

Git user git at public.arvados.org
Mon Nov 16 14:22:14 UTC 2020


        at  b7f96fb678f9b48c3762e0c1ab29157b11bda869 (commit)


commit b7f96fb678f9b48c3762e0c1ab29157b11bda869
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Nov 13 19:50:04 2020 -0300

    13494: Allows the user to open the versions tab by clicking on the version nr.
    
    This is done by syncing the details panel component state with the general app
    state where we currently store the active tab number.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index 5d3133ca..69b69d3b 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -37,7 +37,8 @@ export const loadDetailsPanel = (uuid: string) =>
         if (getState().detailsPanel.isOpened) {
             switch(extractUuidKind(uuid)) {
                 case ResourceKind.COLLECTION:
-                    dispatch<any>(refreshCollectionVersionsList(uuid));
+                    const c = getResource<CollectionResource>(uuid)(getState().resources);
+                    dispatch<any>(refreshCollectionVersionsList(c!.currentVersionUuid));
                     break;
                 default:
                     break;
@@ -46,10 +47,12 @@ export const loadDetailsPanel = (uuid: string) =>
         dispatch(detailsPanelActions.LOAD_DETAILS_PANEL(uuid));
     };
 
-export const openDetailsPanel = (uuid: string, tabNr: number = 0) =>
+export const openDetailsPanel = (uuid?: string, tabNr: number = 0) =>
     (dispatch: Dispatch) => {
-        dispatch<any>(loadDetailsPanel(uuid));
         dispatch(detailsPanelActions.OPEN_DETAILS_PANEL(tabNr));
+        if (uuid !== undefined) {
+            dispatch<any>(loadDetailsPanel(uuid));
+        }
     };
 
 export const openProjectPropertiesDialog = () =>
@@ -66,7 +69,7 @@ export const refreshCollectionVersionsList = (uuid: string) =>
             includeOldVersions: true,
             order: new OrderBuilder<CollectionResource>().addDesc("version").getOrder()
         });
-        dispatch(resourcesActions.SET_RESOURCES(versions.items.slice(1)));
+        dispatch(resourcesActions.SET_RESOURCES(versions.items));
     };
 
 export const deleteProjectProperty = (key: string, value: string) =>
diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx
index bf6e9a4e..fbe9ccc6 100644
--- a/src/views-components/details-panel/details-panel.tsx
+++ b/src/views-components/details-panel/details-panel.tsx
@@ -21,7 +21,7 @@ import { EmptyDetails } from "./empty-details";
 import { DetailsData } from "./details-data";
 import { DetailsResource } from "~/models/details";
 import { getResource } from '~/store/resources/resources';
-import { toggleDetailsPanel, SLIDE_TIMEOUT } from '~/store/details-panel/details-panel-action';
+import { toggleDetailsPanel, SLIDE_TIMEOUT, openDetailsPanel } from '~/store/details-panel/details-panel-action';
 import { FileDetails } from '~/views-components/details-panel/file-details';
 import { getNode } from '~/models/tree';
 
@@ -82,6 +82,7 @@ const mapStateToProps = ({ detailsPanel, resources, collectionPanelFiles }: Root
     const file = getNode(detailsPanel.resourceUuid)(collectionPanelFiles);
     return {
         isOpened: detailsPanel.isOpened,
+        tabNr: detailsPanel.tabNr,
         item: getItem(resource || (file && file.value) || EMPTY_RESOURCE),
     };
 };
@@ -89,12 +90,17 @@ const mapStateToProps = ({ detailsPanel, resources, collectionPanelFiles }: Root
 const mapDispatchToProps = (dispatch: Dispatch) => ({
     onCloseDrawer: () => {
         dispatch<any>(toggleDetailsPanel());
-    }
+    },
+    setActiveTab: (tabNr: number) => {
+        dispatch<any>(openDetailsPanel(undefined, tabNr));
+    },
 });
 
 export interface DetailsPanelDataProps {
     onCloseDrawer: () => void;
+    setActiveTab: (tabNr: number) => void;
     isOpened: boolean;
+    tabNr: number;
     item: DetailsData;
 }
 
@@ -103,12 +109,8 @@ type DetailsPanelProps = DetailsPanelDataProps & WithStyles<CssRules>;
 export const DetailsPanel = withStyles(styles)(
     connect(mapStateToProps, mapDispatchToProps)(
         class extends React.Component<DetailsPanelProps> {
-            state = {
-                tabsValue: 0
-            };
-
-            handleChange = (event: any, value: boolean) => {
-                this.setState({ tabsValue: value });
+            handleChange = (event: any, value: number) => {
+                this.props.setActiveTab(value);
             }
 
             render() {
@@ -129,8 +131,7 @@ export const DetailsPanel = withStyles(styles)(
             }
 
             renderContent() {
-                const { classes, onCloseDrawer, item } = this.props;
-                const { tabsValue } = this.state;
+                const { classes, onCloseDrawer, item, tabNr } = this.props;
                 return <Grid
                     container
                     direction="column"
@@ -161,16 +162,15 @@ export const DetailsPanel = withStyles(styles)(
                         </Grid>
                     </Grid>
                     <Grid item>
-                        <Tabs value={tabsValue} onChange={this.handleChange}>
+                        <Tabs onChange={this.handleChange}
+                            value={(item.getTabLabels().length >= tabNr+1) ? tabNr : 0}>
                             { item.getTabLabels().map((tabLabel, idx) =>
                                 <Tab key={`tab-label-${idx}`} disableRipple label={tabLabel} />)
                             }
                         </Tabs>
                     </Grid>
                     <Grid item xs className={this.props.classes.tabContainer} >
-                    {tabsValue !== undefined
-                        ? item.getDetails(tabsValue)
-                        : null}
+                        {item.getDetails(tabNr)}
                     </Grid>
                 </Grid >;
             }
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index f6df6212..0799c5cc 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -32,8 +32,10 @@ import { getUserUuid } from '~/common/getuser';
 import { getProgressIndicator } from '~/store/progress-indicator/progress-indicator-reducer';
 import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
 import { Link } from 'react-router-dom';
+import { Link as ButtonLink } from '@material-ui/core';
 
 type CssRules = 'root'
+    | 'button'
     | 'filesCard'
     | 'iconHeader'
     | 'tag'
@@ -51,6 +53,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         flexFlow: 'column',
         height: 'calc(100vh - 130px)', // (100% viewport height) - (top bar + breadcrumbs)
     },
+    button: {
+        cursor: 'pointer'
+    },
     filesCard: {
         marginBottom: theme.spacing.unit * 2,
         flex: 1,
@@ -167,7 +172,7 @@ export const CollectionPanel = withStyles(styles)(
                                         <Typography variant="caption">
                                             {item.description}
                                         </Typography>
-                                        <CollectionDetailsAttributes item={item} classes={classes} twoCol={true} />
+                                        <CollectionDetailsAttributes item={item} classes={classes} twoCol={true} showVersionBrowser={() => dispatch<any>(openDetailsPanel(item.uuid, 1))} />
                                         {(item.properties.container_request || item.properties.containerRequest) &&
                                             <span onClick={() => dispatch<any>(navigateToProcess(item.properties.container_request || item.properties.containerRequest))}>
                                                 <DetailsAttribute classLabel={classes.link} label='Link to process' />
@@ -278,11 +283,12 @@ export const CollectionPanel = withStyles(styles)(
     )
 );
 
-export const CollectionDetailsAttributes = (props: { item: CollectionResource, twoCol: boolean, classes?: Record<CssRules, string> }) => {
+export const CollectionDetailsAttributes = (props: { item: CollectionResource, twoCol: boolean, classes?: Record<CssRules, string>, showVersionBrowser?: () => void }) => {
     const item = props.item;
-    const classes = props.classes || { label: '', value: '' };
+    const classes = props.classes || { label: '', value: '', button: '' };
     const isOldVersion = item && item.currentVersionUuid !== item.uuid;
     const mdSize = props.twoCol ? 6 : 12;
+    const showVersionBrowser = props.showVersionBrowser;
     return <Grid container>
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute classLabel={classes.label} classValue={classes.value}
@@ -307,8 +313,16 @@ export const CollectionDetailsAttributes = (props: { item: CollectionResource, t
             </Grid>
         }
         <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                label='Version number' value={item.version} />
+            <DetailsAttribute
+                classLabel={classes.label} classValue={classes.value}
+                label='Version number'
+                value={ showVersionBrowser !== undefined
+                    ? <ButtonLink underline='none' className={classes.button} onClick={() => showVersionBrowser()}>
+                        {item.version}
+                    </ButtonLink>
+                    : item.version
+                }
+            />
         </Grid>
         <Grid item xs={12} md={mdSize}>
             <DetailsAttribute label='Created at' value={formatDate(item.createdAt)} />

commit 84a270e41c9ce69b5f6e1cb5e05881fadd420d1f
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Nov 13 16:54:35 2020 -0300

    13494: Forces version list retrieval when details panel is toggled on.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index cbce4210..5d3133ca 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -112,11 +112,14 @@ export const createProjectProperty = (data: TagProperty) =>
             dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.errors[0], hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
     };
-export const toggleDetailsPanel = () => (dispatch: Dispatch) => {
+export const toggleDetailsPanel = () => (dispatch: Dispatch, getState: () => RootState) => {
     // because of material-ui issue resizing details panel breaks tabs.
     // triggering window resize event fixes that.
     setTimeout(() => {
         window.dispatchEvent(new Event('resize'));
     }, SLIDE_TIMEOUT);
     dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+    if (getState().detailsPanel.isOpened) {
+        dispatch<any>(loadDetailsPanel(getState().detailsPanel.resourceUuid));
+    }
 };

commit 3c4424da163db13b80a2070eef1e73fe744fad9e
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Nov 13 15:16:19 2020 -0300

    13494: Adds content to the "versions" tab on collection's details panel.
    
    Old versions are requested only when the details panel is open.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index bf551b9f..14c3c48c 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -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>
diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts
index 15d5ef72..851ba84d 100644
--- a/src/store/collection-panel/collection-panel-action.ts
+++ b/src/store/collection-panel/collection-panel-action.ts
@@ -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 &&
diff --git a/src/store/details-panel/details-panel-action.ts b/src/store/details-panel/details-panel-action.ts
index c5d472ad..cbce4210 100644
--- a/src/store/details-panel/details-panel-action.ts
+++ b/src/store/details-panel/details-panel-action.ts
@@ -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();
diff --git a/src/store/details-panel/details-panel-reducer.ts b/src/store/details-panel/details-panel-reducer.ts
index 38c0edd5..6c32551c 100644
--- a/src/store/details-panel/details-panel-reducer.ts
+++ b/src/store/details-panel/details-panel-reducer.ts
@@ -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 }),
     });
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index 944c48cf..0416d815 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -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) =>
diff --git a/src/views-components/collection-panel-files/collection-panel-files.ts b/src/views-components/collection-panel-files/collection-panel-files.ts
index 9859f84b..91420edb 100644
--- a/src/views-components/collection-panel-files/collection-panel-files.ts
+++ b/src/views-components/collection-panel-files/collection-panel-files.ts
@@ -75,7 +75,7 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps,
         dispatch<any>(openCollectionFilesContextMenu(event, isWritable));
     },
     onFileClick: (id) => {
-        dispatch(openDetailsPanel(id));
+        dispatch<any>(openDetailsPanel(id));
     },
 });
 
diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx
index d2457559..51b09b4f 100644
--- a/src/views-components/details-panel/collection-details.tsx
+++ b/src/views-components/details-panel/collection-details.tsx
@@ -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
diff --git a/src/views/all-processes-panel/all-processes-panel.tsx b/src/views/all-processes-panel/all-processes-panel.tsx
index 650a0d95..d9a0fd90 100644
--- a/src/views/all-processes-panel/all-processes-panel.tsx
+++ b/src/views/all-processes-panel/all-processes-panel.tsx
@@ -138,7 +138,7 @@ export const AllProcessesPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
             render() {
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index feade60c..f6df6212 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -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));
                 }
             }
 
diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 6b5cd4c3..cad2f9ba 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -153,7 +153,7 @@ export const FavoritePanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
             render() {
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index d79b98cf..11223f22 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -178,7 +178,7 @@ export const ProjectPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
 
         }
diff --git a/src/views/shared-with-me-panel/shared-with-me-panel.tsx b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
index c9408752..9b4bcc85 100644
--- a/src/views/shared-with-me-panel/shared-with-me-panel.tsx
+++ b/src/views/shared-with-me-panel/shared-with-me-panel.tsx
@@ -77,7 +77,7 @@ export const SharedWithMePanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
         }
     )
diff --git a/src/views/trash-panel/trash-panel.tsx b/src/views/trash-panel/trash-panel.tsx
index dec5af51..3173876b 100644
--- a/src/views/trash-panel/trash-panel.tsx
+++ b/src/views/trash-panel/trash-panel.tsx
@@ -179,7 +179,7 @@ export const TrashPanel = withStyles(styles)(
             }
 
             handleRowClick = (uuid: string) => {
-                this.props.dispatch(loadDetailsPanel(uuid));
+                this.props.dispatch<any>(loadDetailsPanel(uuid));
             }
         }
     )

commit f62ba0aeff590ef1c57669d4f46e43b5ee169522
Merge: e4395f2b a0d117b3
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Nov 12 16:51:00 2020 -0300

    13494: Merge branch 'master' into 13494-collection-version-browser
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>


commit e4395f2bfb2a271303e9dc7f91bec6c890e792e3
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Nov 12 12:27:24 2020 -0300

    13494: Fixes edge case on file size formatter.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/common/formatters.ts b/src/common/formatters.ts
index 55fb0507..17917127 100644
--- a/src/common/formatters.ts
+++ b/src/common/formatters.ts
@@ -22,6 +22,8 @@ export const formatDate = (isoDate?: string | null, utc: boolean = false) => {
 
 export const formatFileSize = (size?: number) => {
     if (typeof size === "number") {
+        if (size === 0) { return "0 B"; }
+
         for (const { base, unit } of FILE_SIZES) {
             if (size >= base) {
                 return `${(size / base).toFixed()} ${unit}`;

commit f9a41a09f7f884aeaf4291d155acf4bab74e71d8
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Nov 12 12:24:54 2020 -0300

    13494: Sets "owner" column off by default on different panels.
    
    Also adds the column to "Public favorites" panel, but keep it off by default.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx
index 9c906eef..6b5cd4c3 100644
--- a/src/views/favorite-panel/favorite-panel.tsx
+++ b/src/views/favorite-panel/favorite-panel.tsx
@@ -84,7 +84,7 @@ export const favoritePanelColumns: DataColumns<string> = [
     },
     {
         name: FavoritePanelColumnNames.OWNER,
-        selected: true,
+        selected: false,
         configurable: true,
         filters: createTree(),
         render: uuid => <ResourceOwner uuid={uuid} />
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 687e17df..d79b98cf 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -15,7 +15,13 @@ import { DataTableFilterItem } from '~/components/data-table-filters/data-table-
 import { ContainerRequestState } from '~/models/container-request';
 import { SortDirection } from '~/components/data-table/data-column';
 import { ResourceKind, Resource, EditableResource } from '~/models/resource';
-import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
+import {
+    ResourceFileSize,
+    ResourceLastModifiedDate,
+    ProcessStatus,
+    ResourceType,
+    ResourceOwner
+} from '~/views-components/data-explorer/renderers';
 import { ProjectIcon } from '~/components/icon/icon';
 import { ResourceName } from '~/views-components/data-explorer/renderers';
 import { ResourcesState, getResourceWithEditableStatus } from '~/store/resources/resources';
@@ -82,7 +88,7 @@ export const projectPanelColumns: DataColumns<string> = [
     },
     {
         name: ProjectPanelColumnNames.OWNER,
-        selected: true,
+        selected: false,
         configurable: true,
         filters: createTree(),
         render: uuid => <ResourceOwner uuid={uuid} />
diff --git a/src/views/public-favorites-panel/public-favorites-panel.tsx b/src/views/public-favorites-panel/public-favorites-panel.tsx
index ab423a6e..635ac621 100644
--- a/src/views/public-favorites-panel/public-favorites-panel.tsx
+++ b/src/views/public-favorites-panel/public-favorites-panel.tsx
@@ -17,7 +17,8 @@ import {
     ResourceFileSize,
     ResourceLastModifiedDate,
     ResourceType,
-    ResourceName
+    ResourceName,
+    ResourceOwner
 } from '~/views-components/data-explorer/renderers';
 import { PublicFavoriteIcon } from '~/components/icon/icon';
 import { Dispatch } from 'redux';
@@ -81,6 +82,13 @@ export const publicFavoritePanelColumns: DataColumns<string> = [
         filters: getSimpleObjectTypeFilters(),
         render: uuid => <ResourceType uuid={uuid} />
     },
+    {
+        name: PublicFavoritePanelColumnNames.OWNER,
+        selected: false,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ResourceOwner uuid={uuid} />
+    },
     {
         name: PublicFavoritePanelColumnNames.FILE_SIZE,
         selected: true,

commit df0520719b15e7e230fb72f3e53ff36a34f4ba87
Merge: 419ab5c1 9ee35a64
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Nov 10 14:37:59 2020 -0300

    13493: Merge branch 'master' into 13494-collection-version-browser
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --cc src/views-components/details-panel/collection-details.tsx
index f2b7c131,625d8405..d2457559
--- a/src/views-components/details-panel/collection-details.tsx
+++ b/src/views-components/details-panel/collection-details.tsx
@@@ -14,26 -14,7 +14,26 @@@ export class CollectionDetails extends 
          return <CollectionIcon className={className} />;
      }
  
 -    getDetails() {
 -        return <CollectionDetailsAttributes item={this.item} twoCol={false} />;
 +    getTabLabels() {
 +        return ['Details', 'Versions'];
 +    }
 +
 +    getDetails(tabNumber: number) {
 +        switch (tabNumber) {
 +            case 0:
 +                return this.getCollectionInfo();
 +            case 1:
 +                return this.getVersionBrowser();
 +            default:
 +                return <div />;
 +        }
 +    }
 +
 +    private getCollectionInfo() {
-         return <CollectionDetailsAttributes item={this.item} />;
++        return <CollectionDetailsAttributes twoCol={false} item={this.item} />;
 +    }
 +
 +    private getVersionBrowser() {
 +        return <div />;
      }
  }

commit 419ab5c15711dd84bf69ed2a5a9052a39103fed8
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Oct 19 13:44:05 2020 -0300

    13494: Refactors detail panel. Removes unused 'Size' field.
    
    Detail panel now doesn't have a disabled 'Activity' tab that wasn't used
    by anything. Instead, it can be customized to have any number of tabs
    in addition to the default 'Details' tab.
    
    This lays the ground for the new collection panel's Versions tab.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx
index 1089706d..f2b7c131 100644
--- a/src/views-components/details-panel/collection-details.tsx
+++ b/src/views-components/details-panel/collection-details.tsx
@@ -14,7 +14,26 @@ export class CollectionDetails extends DetailsData<CollectionResource> {
         return <CollectionIcon className={className} />;
     }
 
-    getDetails() {
+    getTabLabels() {
+        return ['Details', 'Versions'];
+    }
+
+    getDetails(tabNumber: number) {
+        switch (tabNumber) {
+            case 0:
+                return this.getCollectionInfo();
+            case 1:
+                return this.getVersionBrowser();
+            default:
+                return <div />;
+        }
+    }
+
+    private getCollectionInfo() {
         return <CollectionDetailsAttributes item={this.item} />;
     }
+
+    private getVersionBrowser() {
+        return <div />;
+    }
 }
diff --git a/src/views-components/details-panel/details-data.tsx b/src/views-components/details-panel/details-data.tsx
index ca8e2cd7..68aa5787 100644
--- a/src/views-components/details-panel/details-data.tsx
+++ b/src/views-components/details-panel/details-data.tsx
@@ -12,10 +12,10 @@ export abstract class DetailsData<T extends DetailsResource = DetailsResource> {
         return this.item.name || 'Projects';
     }
 
-    abstract getIcon(className?: string): React.ReactElement<any>;
-    abstract getDetails(): React.ReactElement<any>;
-
-    getActivity(): React.ReactElement<any> {
-        return <div />;
+    getTabLabels(): string[] {
+        return ['Details'];
     }
+
+    abstract getIcon(className?: string): React.ReactElement<any>;
+    abstract getDetails(tabNr?: number): React.ReactElement<any>;
 }
diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx
index b6b0cdf1..bf6e9a4e 100644
--- a/src/views-components/details-panel/details-panel.tsx
+++ b/src/views-components/details-panel/details-panel.tsx
@@ -162,14 +162,15 @@ export const DetailsPanel = withStyles(styles)(
                     </Grid>
                     <Grid item>
                         <Tabs value={tabsValue} onChange={this.handleChange}>
-                            <Tab disableRipple label="Details" />
-                            <Tab disableRipple label="Activity" disabled />
+                            { item.getTabLabels().map((tabLabel, idx) =>
+                                <Tab key={`tab-label-${idx}`} disableRipple label={tabLabel} />)
+                            }
                         </Tabs>
                     </Grid>
                     <Grid item xs className={this.props.classes.tabContainer} >
-                        {tabsValue === 0
-                            ? item.getDetails()
-                            : null}
+                    {tabsValue !== undefined
+                        ? item.getDetails(tabsValue)
+                        : null}
                     </Grid>
                 </Grid >;
             }
diff --git a/src/views-components/details-panel/process-details.tsx b/src/views-components/details-panel/process-details.tsx
index 2fbdd313..aa1b3a1d 100644
--- a/src/views-components/details-panel/process-details.tsx
+++ b/src/views-components/details-panel/process-details.tsx
@@ -20,14 +20,11 @@ export class ProcessDetails extends DetailsData<ProcessResource> {
     getDetails() {
         return <div>
             <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
-            <DetailsAttribute label='Size' value='---' />
             <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid} />
 
-            {/* Missing attr */}
             <DetailsAttribute label='Status' value={this.item.state} />
             <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
 
-            {/* Missing attrs */}
             <DetailsAttribute label='Started at' value={formatDate(this.item.createdAt)} />
             <DetailsAttribute label='Finished at' value={formatDate(this.item.expiresAt)} />
 
diff --git a/src/views-components/details-panel/project-details.tsx b/src/views-components/details-panel/project-details.tsx
index 1be04b00..b901abce 100644
--- a/src/views-components/details-panel/project-details.tsx
+++ b/src/views-components/details-panel/project-details.tsx
@@ -59,14 +59,10 @@ const ProjectDetailsComponent = connect(null, mapDispatchToProps)(
     withStyles(styles)(
         ({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
             <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
-            {/* Missing attr */}
-            <DetailsAttribute label='Size' value='---' />
             <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid} lowercaseValue={true} />
             <DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
             <DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
             <DetailsAttribute label='Project UUID' linkToUuid={project.uuid} value={project.uuid} />
-            {/* Missing attr */}
-            {/*<DetailsAttribute label='File size' value='1.4 GB' />*/}
             <DetailsAttribute label='Description'>
                 {project.description ?
                     <RichTextEditorLink

commit a00f9424fb242f23b73e598715c1b55291c26a04
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Oct 19 12:01:46 2020 -0300

    13494: Minor UI fixes.
    
    * Removes useless 'Size' field on the collections detail pane.
    * Adds version number field.
    * Avoids event propagation on the collection icon so that when clicked it
      just opens the detail pane without collapsing the info pane.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index f41e8967..d3566f36 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -261,9 +261,10 @@ export const CollectionPanel = withStyles(styles)(
                 this.props.dispatch<any>(deleteCollectionTag(key, value));
             }
 
-            openCollectionDetails = () => {
+            openCollectionDetails = (e: React.MouseEvent<HTMLElement>) => {
                 const { item } = this.props;
                 if (item) {
+                    e.stopPropagation();
                     this.props.dispatch(openDetailsPanel(item.uuid));
                 }
             }

commit 0c46ea8a69b345729809290883271706119e7165
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Oct 19 11:14:58 2020 -0300

    13494: Adds ability to customize the search label on DataExplorer.
    
    Also, fixed DataExplorer's default search label as it was showing
    'Search files' on wrong contexts (ie: Projects panel).
    
    Also, hides the search feature on the Collection PDH panel, as it doesn't
    make sense to search for files on different collections with identical
    contents, and also kind of gets stuck when searching for collection names
    and having one result, the panel auto navigates to the collection panel.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index 29f20be2..6a9f0f40 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -84,6 +84,7 @@ export const CollectionPanelFilesComponent = ({ onItemMenuOpen, onSearchChange,
                     <span className={classes.cardHeaderContentTitle}>Files</span>
                     <SearchInput
                         value={searchValue}
+                        label='Search files'
                         onSearch={setSearchValue} />
                 </div>
             }
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 7107bd70..28ae86cd 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -47,6 +47,7 @@ interface DataExplorerDataProps<T> {
     items: T[];
     itemsAvailable: number;
     columns: DataColumns<T>;
+    searchLabel?: string;
     searchValue: string;
     rowsPerPage: number;
     rowsPerPageOptions: number[];
@@ -90,7 +91,7 @@ export const DataExplorer = withStyles(styles)(
         render() {
             const {
                 columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
-                rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
+                rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
                 paperKey, fetchMode, currentItemUuid, title
@@ -101,6 +102,7 @@ export const DataExplorer = withStyles(styles)(
                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
                         <div className={classes.searchBox}>
                             {!hideSearchInput && <SearchInput
+                                label={searchLabel}
                                 value={searchValue}
                                 onSearch={onSearch} />}
                         </div>
diff --git a/src/components/search-input/search-input.tsx b/src/components/search-input/search-input.tsx
index 3b4ab35a..02c193c2 100644
--- a/src/components/search-input/search-input.tsx
+++ b/src/components/search-input/search-input.tsx
@@ -34,6 +34,7 @@ const styles: StyleRulesCallback<CssRules> = theme => {
 
 interface SearchInputDataProps {
     value: string;
+    label?: string;
 }
 
 interface SearchInputActionProps {
@@ -45,6 +46,7 @@ type SearchInputProps = SearchInputDataProps & SearchInputActionProps & WithStyl
 
 interface SearchInputState {
     value: string;
+    label: string;
 }
 
 export const DEFAULT_SEARCH_DEBOUNCE = 1000;
@@ -52,7 +54,8 @@ export const DEFAULT_SEARCH_DEBOUNCE = 1000;
 export const SearchInput = withStyles(styles)(
     class extends React.Component<SearchInputProps> {
         state: SearchInputState = {
-            value: ""
+            value: "",
+            label: ""
         };
 
         timeout: number;
@@ -60,14 +63,14 @@ export const SearchInput = withStyles(styles)(
         render() {
             return <form onSubmit={this.handleSubmit}>
                 <FormControl>
-                    <InputLabel>Search files</InputLabel>
+                    <InputLabel>{this.state.label}</InputLabel>
                     <Input
                         type="text"
                         value={this.state.value}
                         onChange={this.handleChange}
                         endAdornment={
                             <InputAdornment position="end">
-                                <Tooltip title='Search files'>
+                                <Tooltip title='Search'>
                                     <IconButton
                                         onClick={this.handleSubmit}>
                                         <SearchIcon />
@@ -80,7 +83,10 @@ export const SearchInput = withStyles(styles)(
         }
 
         componentDidMount() {
-            this.setState({ value: this.props.value });
+            this.setState({
+                value: this.props.value,
+                label: this.props.label || 'Search'
+            });
         }
 
         componentWillReceiveProps(nextProps: SearchInputProps) {
diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
index 925c5848..038fea2f 100644
--- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx
+++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
@@ -145,6 +145,7 @@ export const CollectionsContentAddressPanel = withStyles(styles)(
                     </Button>
                     <DataExplorer
                         id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
+                        hideSearchInput
                         onRowClick={this.props.onItemClick}
                         onRowDoubleClick={this.props.onItemDoubleClick}
                         onContextMenu={this.props.onContextMenu}

commit a4c6c777aac949f1d989610ac4846b20a76e6568
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Fri Oct 16 11:04:53 2020 -0300

    13494: Shows correct collection icon on DataTables.
    
    Also, adds 'Status' column on collection by PDH panel, consistently with
    the search results listings.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/services/groups-service/groups-service.ts b/src/services/groups-service/groups-service.ts
index 281aa921..f61b9eff 100644
--- a/src/services/groups-service/groups-service.ts
+++ b/src/services/groups-service/groups-service.ts
@@ -9,6 +9,7 @@ import { AxiosInstance, AxiosRequestConfig } from "axios";
 import { CollectionResource } from "~/models/collection";
 import { ProjectResource } from "~/models/project";
 import { ProcessResource } from "~/models/process";
+import { WorkflowResource } from "~/models/workflow";
 import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
 import { ApiActions } from "~/services/api/api-actions";
 import { GroupResource } from "~/models/group";
@@ -31,7 +32,8 @@ export interface SharedArguments extends ListArguments {
 export type GroupContentsResource =
     CollectionResource |
     ProjectResource |
-    ProcessResource;
+    ProcessResource |
+    WorkflowResource;
 
 export class GroupsService<T extends GroupResource = GroupResource> extends TrashableResourceService<T> {
 
@@ -73,5 +75,6 @@ export class GroupsService<T extends GroupResource = GroupResource> extends Tras
 export enum GroupContentsResourcePrefix {
     COLLECTION = "collections",
     PROJECT = "groups",
-    PROCESS = "container_requests"
+    PROCESS = "container_requests",
+    WORKFLOW = "workflows"
 }
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 8afa4532..138c76ef 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
 import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
 import { ResourceKind, TrashableResource } from '~/models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, WorkflowIcon, ShareIcon } from '~/components/icon/icon';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon } from '~/components/icon/icon';
 import { formatDate, formatFileSize, formatTime } from '~/common/formatters';
 import { resourceLabel } from '~/common/labels';
 import { connect, DispatchProp } from 'react-redux';
@@ -28,10 +28,10 @@ import { withResourceData } from '~/views-components/data-explorer/with-resource
 import { CollectionResource } from '~/models/collection';
 import { IllegalNamingWarning } from '~/components/warning/warning';
 
-const renderName = (dispatch: Dispatch, item: { name: string; uuid: string, kind: string }) =>
+const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
         <Grid item>
-            {renderIcon(item.kind)}
+            {renderIcon(item)}
         </Grid>
         <Grid item>
             <Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
@@ -52,15 +52,18 @@ const renderName = (dispatch: Dispatch, item: { name: string; uuid: string, kind
 export const ResourceName = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-        return resource || { name: '', uuid: '', kind: '' };
-    })((resource: { name: string; uuid: string, kind: string } & DispatchProp<any>) => renderName(resource.dispatch, resource));
+        return resource;
+    })((resource: GroupContentsResource & DispatchProp<any>) => renderName(resource.dispatch, resource));
 
-const renderIcon = (kind: string) => {
-    switch (kind) {
+const renderIcon = (item: GroupContentsResource) => {
+    switch (item.kind) {
         case ResourceKind.PROJECT:
             return <ProjectIcon />;
         case ResourceKind.COLLECTION:
-            return <CollectionIcon />;
+            if (item.uuid === item.currentVersionUuid) {
+                return <CollectionIcon />;
+            }
+            return <CollectionOldVersionIcon />;
         case ResourceKind.PROCESS:
             return <ProcessIcon />;
         case ResourceKind.WORKFLOW:
@@ -74,10 +77,10 @@ const renderDate = (date?: string) => {
     return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
 };
 
-const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ownerUuid: string }) =>
+const renderWorkflowName = (item: WorkflowResource) =>
     <Grid container alignItems="center" wrap="nowrap" spacing={16}>
         <Grid item>
-            {renderIcon(item.kind)}
+            {renderIcon(item)}
         </Grid>
         <Grid item>
             <Typography color="primary" style={{ width: '100px' }}>
@@ -89,7 +92,7 @@ const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ow
 export const ResourceWorkflowName = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
-        return resource || { name: '', uuid: '', kind: '', ownerUuid: '' };
+        return resource;
     })(renderWorkflowName);
 
 const getPublicUuid = (uuidPrefix: string) => {
diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
index b652b502..925c5848 100644
--- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx
+++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx
@@ -20,7 +20,7 @@ import { navigateTo } from '~/store/navigation/navigation-action';
 import { DataColumns } from '~/components/data-table/data-table';
 import { SortDirection } from '~/components/data-table/data-column';
 import { createTree } from '~/models/tree';
-import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate } from '~/views-components/data-explorer/renderers';
+import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate, ResourceStatus } from '~/views-components/data-explorer/renderers';
 
 type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
 
@@ -59,6 +59,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 
 enum CollectionContentAddressPanelColumnNames {
     COLLECTION_WITH_THIS_ADDRESS = "Collection with this address",
+    STATUS = "Status",
     LOCATION = "Location",
     LAST_MODIFIED = "Last modified"
 }
@@ -72,6 +73,13 @@ export const collectionContentAddressPanelColumns: DataColumns<string> = [
         filters: createTree(),
         render: uuid => <ResourceName uuid={uuid} />
     },
+    {
+        name: CollectionContentAddressPanelColumnNames.STATUS,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid => <ResourceStatus uuid={uuid} />
+    },
     {
         name: CollectionContentAddressPanelColumnNames.LOCATION,
         selected: true,

commit db1c226e212aaafff8f8d1c6ddfb092e247db5fe
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Thu Oct 15 18:05:55 2020 -0300

    13494: Allows accesing old collections versions through PDH.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/services/common-service/common-service.ts b/src/services/common-service/common-service.ts
index d605611f..8e00c4ad 100644
--- a/src/services/common-service/common-service.ts
+++ b/src/services/common-service/common-service.ts
@@ -22,6 +22,7 @@ export interface ListArguments {
     select?: string[];
     distinct?: boolean;
     count?: string;
+    includeOldVersions?: boolean;
 }
 
 export interface ListResults<T> {
@@ -116,7 +117,7 @@ export class CommonService<T> {
     list(args: ListArguments = {}): Promise<ListResults<T>> {
         const { filters, order, ...other } = args;
         const params = {
-            ...other,
+            ...CommonService.mapKeys(_.snakeCase)(other),
             filters: filters ? `[${filters}]` : undefined,
             order: order ? order : undefined
         };
diff --git a/src/store/collections-content-address-panel/collections-content-address-middleware-service.ts b/src/store/collections-content-address-panel/collections-content-address-middleware-service.ts
index a68d13bd..e18922a7 100644
--- a/src/store/collections-content-address-panel/collections-content-address-middleware-service.ts
+++ b/src/store/collections-content-address-panel/collections-content-address-middleware-service.ts
@@ -59,7 +59,8 @@ export class CollectionsWithSameContentAddressMiddlewareService extends DataExpl
                     filters: new FilterBuilder()
                         .addEqual('portable_data_hash', contentAddress)
                         .addILike("name", dataExplorer.searchValue)
-                        .getFilters()
+                        .getFilters(),
+                    includeOldVersions: true
                 });
                 const userUuids = response.items.map(it => {
                     if (extractUuidKind(it.ownerUuid) === ResourceKind.USER) {

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list