[arvados-workbench2] created: 2.4.0-353-gf8f6c21d

git repository hosting git at public.arvados.org
Mon Nov 28 16:58:15 UTC 2022


        at  f8f6c21d5162f38e6e644a628b9caae6e1e23f88 (commit)


commit f8f6c21d5162f38e6e644a628b9caae6e1e23f88
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Thu Nov 24 13:36:04 2022 -0500

    19690: metadata and portabledatahash columns up
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/common/formatters.ts b/src/common/formatters.ts
index 1fbf1710..c03bf04b 100644
--- a/src/common/formatters.ts
+++ b/src/common/formatters.ts
@@ -2,114 +2,139 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { PropertyValue } from "models/search-bar";
-import { Vocabulary, getTagKeyLabel, getTagValueLabel } from "models/vocabulary";
+import { PropertyValue } from 'models/search-bar';
+import {
+  Vocabulary,
+  getTagKeyLabel,
+  getTagValueLabel,
+} from 'models/vocabulary';
 
 export const formatDate = (isoDate?: string | null, utc: boolean = false) => {
-    if (isoDate) {
-        const date = new Date(isoDate);
-        let text: string;
-        if (utc) {
-            text = date.toUTCString();
-        }
-        else {
-            text = date.toLocaleString();
-        }
-        return text === 'Invalid Date' ? "(none)" : text;
+  if (isoDate) {
+    const date = new Date(isoDate);
+    let text: string;
+    if (utc) {
+      text = date.toUTCString();
+    } else {
+      text = date.toLocaleString();
     }
-    return "(none)";
+    return text === 'Invalid Date' ? '(none)' : text;
+  }
+  return '(none)';
 };
 
 export const formatFileSize = (size?: number | string) => {
-    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}`;
-            }
-        }
+  if (typeof size === 'number') {
+    if (size === 0) {
+      return '0 B';
     }
-    if ((typeof size === "string" && size === '') || size === undefined) {
-        return '';
+
+    for (const { base, unit } of FILE_SIZES) {
+      if (size >= base) {
+        return `${(size / base).toFixed()} ${unit}`;
+      }
     }
-    return "0 B";
+  }
+  if ((typeof size === 'string' && size === '') || size === undefined) {
+    return '';
+  }
+  return '0 B';
 };
 
 export const formatTime = (time: number, seconds?: boolean) => {
-    const minutes = Math.floor(time / (1000 * 60) % 60).toFixed(0);
-    const hours = Math.floor(time / (1000 * 60 * 60)).toFixed(0);
+  const minutes = Math.floor((time / (1000 * 60)) % 60).toFixed(0);
+  const hours = Math.floor(time / (1000 * 60 * 60)).toFixed(0);
 
-    if (seconds) {
-        const seconds = Math.floor(time / (1000) % 60).toFixed(0);
-        return hours + "h " + minutes + "m " + seconds + "s";
-    }
+  if (seconds) {
+    const seconds = Math.floor((time / 1000) % 60).toFixed(0);
+    return hours + 'h ' + minutes + 'm ' + seconds + 's';
+  }
 
-    return hours + "h " + minutes + "m";
+  return hours + 'h ' + minutes + 'm';
 };
 
 export const getTimeDiff = (endTime: string, startTime: string) => {
-    return new Date(endTime).getTime() - new Date(startTime).getTime();
+  return new Date(endTime).getTime() - new Date(startTime).getTime();
 };
 
 export const formatProgress = (loaded: number, total: number) => {
-    const progress = loaded >= 0 && total > 0 ? loaded * 100 / total : 0;
-    return `${progress.toFixed(2)}%`;
+  const progress = loaded >= 0 && total > 0 ? (loaded * 100) / total : 0;
+  return `${progress.toFixed(2)}%`;
 };
 
-export function formatUploadSpeed(prevLoaded: number, loaded: number, prevTime: number, currentTime: number) {
-    const speed = loaded > prevLoaded && currentTime > prevTime
-        ? (loaded - prevLoaded) / (currentTime - prevTime)
-        : 0;
+export function formatUploadSpeed(
+  prevLoaded: number,
+  loaded: number,
+  prevTime: number,
+  currentTime: number
+) {
+  const speed =
+    loaded > prevLoaded && currentTime > prevTime
+      ? (loaded - prevLoaded) / (currentTime - prevTime)
+      : 0;
 
-    return `${(speed / 1000).toFixed(2)} MB/s`;
+  return `${(speed / 1000).toFixed(2)} MB/s`;
 }
 
 const FILE_SIZES = [
-    {
-        base: 1099511627776,
-        unit: "TB"
-    },
-    {
-        base: 1073741824,
-        unit: "GB"
-    },
-    {
-        base: 1048576,
-        unit: "MB"
-    },
-    {
-        base: 1024,
-        unit: "KB"
-    },
-    {
-        base: 1,
-        unit: "B"
-    }
+  {
+    base: 1099511627776,
+    unit: 'TB',
+  },
+  {
+    base: 1073741824,
+    unit: 'GB',
+  },
+  {
+    base: 1048576,
+    unit: 'MB',
+  },
+  {
+    base: 1024,
+    unit: 'KB',
+  },
+  {
+    base: 1,
+    unit: 'B',
+  },
 ];
 
-export const formatPropertyValue = (pv: PropertyValue, vocabulary?: Vocabulary) => {
-    if (vocabulary && pv.keyID && pv.valueID) {
-        return `${getTagKeyLabel(pv.keyID, vocabulary)}: ${getTagValueLabel(pv.keyID, pv.valueID!, vocabulary)}`;
-    }
-    if (pv.key) {
-        return pv.value
-            ? `${pv.key}: ${pv.value}`
-            : pv.key;
-    }
-    return "";
+export const formatPropertyValue = (
+  pv: PropertyValue,
+  vocabulary?: Vocabulary
+) => {
+  if (vocabulary && pv.keyID && pv.valueID) {
+    return `${getTagKeyLabel(pv.keyID, vocabulary)}: ${getTagValueLabel(
+      pv.keyID,
+      pv.valueID!,
+      vocabulary
+    )}`;
+  }
+  if (pv.key) {
+    return pv.value ? `${pv.key}: ${pv.value}` : pv.key;
+  }
+  return '';
 };
 
 export const formatContainerCost = (cost: number): string => {
-    const decimalPlaces = 3;
+  const decimalPlaces = 3;
 
-    const factor = Math.pow(10, decimalPlaces);
-    const rounded = Math.round(cost*factor)/factor;
-    if (cost > 0 && rounded === 0) {
-        // Display min value of 0.001
-        return `$${1/factor}`;
-    } else {
-        // Otherwise use rounded value to proper decimal places
-        return `$${rounded}`;
-    }
+  const factor = Math.pow(10, decimalPlaces);
+  const rounded = Math.round(cost * factor) / factor;
+  if (cost > 0 && rounded === 0) {
+    // Display min value of 0.001
+    return `$${1 / factor}`;
+  } else {
+    // Otherwise use rounded value to proper decimal places
+    return `$${rounded}`;
+  }
+};
+
+export const formatObjectProperties = (untypedObj: Object) => {
+  type kVPair = [string, string];
+  let formattedObject: Array<kVPair> = [];
+  for (const key in untypedObj) {
+    formattedObject.push([key, untypedObj[key]]);
+  }
+  return formattedObject;
 };
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index dc19ff65..9c0bccb0 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -30,7 +30,7 @@ import {
     SetupIcon,
     InactiveIcon,
 } from 'components/icon/icon';
-import { formatDate, formatFileSize, formatTime } from 'common/formatters';
+import { formatDate, formatFileSize, formatTime, formatObjectProperties} from 'common/formatters';
 import { resourceLabel } from 'common/labels';
 import { connect, DispatchProp } from 'react-redux';
 import { RootState } from 'store/store';
@@ -723,13 +723,25 @@ export const ResourceOwnerName = connect(
         return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
     })((props: { owner: string }) => renderOwner(props.owner));
 
-const renderUUID = (uuid:string) => <Typography>{uuid}</Typography>
-
 export const ResourceUUID = connect(
     (state: RootState, props: { uuid: string }) => {
-        const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+        const resource = getResource<CollectionResource>(props.uuid)(state.resources);
         return { uuid: resource ? resource.uuid : '' };
-    })((props: { uuid: string }) => renderUUID(props.uuid));
+    })((props: { uuid: string }) => renderUuid({uuid: props.uuid}));
+
+const renderMetadata = (metadata:any) => {
+    return <>{formatObjectProperties(metadata).map((property, i)=>
+        <Typography key={i} noWrap>{property[0]}: {property[1]}</Typography>
+    )}</>
+}
+
+export const ResourceMetadata = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<CollectionResource>(props.uuid)(state.resources);
+        const metadata = resource && Object.keys(resource.properties).length ? {...resource.properties} : {}
+        if(resource && resource.portableDataHash) metadata['Portable Data Hash'] = resource.portableDataHash
+        return { properties: metadata };
+    })((props: { properties: string }) => renderMetadata(props.properties));
     
 const renderDescription = (description: string)=>{
     const truncatedDescription = description ? description.slice(0, 18) + '...' : '-'
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 2958271e..ef9fdc9e 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -24,6 +24,7 @@ import {
     ProcessStatus,
     ResourceType,
     ResourceUUID,
+    ResourceMetadata,
     ResourceDescription,
     ResourceOwnerWithName
 } from 'views-components/data-explorer/renderers';
@@ -74,7 +75,8 @@ export enum ProjectPanelColumnNames {
     LAST_MODIFIED = "Last modified",
     TRASH_AT = "Trash at",
     DELETE_AT = "Delete at",
-    DESCRIPTION = "Description"
+    DESCRIPTION = "Description",
+    METADATA = "Metadata"
 }
 
 export interface ProjectPanelFilter extends DataTableFilterItem {
@@ -121,21 +123,28 @@ export const projectPanelColumns: DataColumns<string> = [
     },
     {
         name: ProjectPanelColumnNames.UUID,
-        selected: false,
+        selected: true,
         configurable: true,
         filters: createTree(),
         render: uuid => <ResourceUUID uuid={uuid}/>
     },
     {
-        name: ProjectPanelColumnNames.CREATED_AT,
+        name: ProjectPanelColumnNames.METADATA,
         selected: true,
         configurable: true,
         filters: createTree(),
+        render: uuid => <ResourceMetadata uuid={uuid}/>
+    },
+    {
+        name: ProjectPanelColumnNames.CREATED_AT,
+        selected: false,
+        configurable: true,
+        filters: createTree(),
         render: uuid =><ResourceCreatedAtDate uuid={uuid}/>
     },
     {
         name: ProjectPanelColumnNames.LAST_MODIFIED,
-        selected: true,
+        selected: false,
         configurable: true,
         sortDirection: SortDirection.DESC,
         filters: createTree(),

commit d4d173d042ad06d8382657797b6aae2d61cdd3d7
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Wed Nov 23 22:37:28 2022 -0500

    19690: tidy up
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/components/column-selector/column-selector.tsx b/src/components/column-selector/column-selector.tsx
index 3ab38d4d..5fbef6b6 100644
--- a/src/components/column-selector/column-selector.tsx
+++ b/src/components/column-selector/column-selector.tsx
@@ -30,17 +30,7 @@ export type ColumnSelectorProps = ColumnSelectorDataProps & WithStyles<CssRules>
 
 export const ColumnSelector = withStyles(styles)(
     ({ columns, onColumnToggle, classes }: ColumnSelectorProps) =>
-    {
-//         // console.log('COLUMN_SELECTOR',columns)
-//     columns = [...columns, {
-//         name:'bananas',
-//         selected: false,
-//         configurable: true, filters:{}, render: (uuid)=><ResourceName uuid='bananas'/>
-//     }
-// ]
-// //lisa
-//     // console.log('COLUMN_SELECTOR',columns)
-       return <Popover triggerComponent={ColumnSelectorTrigger}>
+        <Popover triggerComponent={ColumnSelectorTrigger}>
             <Paper>
                 <List dense>
                     {columns
@@ -62,7 +52,7 @@ export const ColumnSelector = withStyles(styles)(
                         )}
                 </List>
             </Paper>
-        </Popover>}
+        </Popover>
 );
 
 export const ColumnSelectorTrigger = (props: IconButtonProps) =>
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 67f9bfba..e4eef593 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -141,9 +141,6 @@ export const DataExplorer = withStyles(styles)(
 
         componentDidMount() {
             if (this.props.onSetColumns) {
-                //lisa
-                // console.log('DATA_EXPLORER_CDM:',this.props)
-                
                 this.props.onSetColumns(this.props.columns);
             }
             // Component just mounted, so we need to show the loading indicator.
@@ -163,8 +160,6 @@ export const DataExplorer = withStyles(styles)(
                 paperKey, fetchMode, currentItemUuid, title,
                 doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, elementPath
             } = this.props;
-            //lisa
-// console.log('DATA_EXPLORER_TSX', this.props)
             return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props["data-cy"]}>
                 <Grid container direction="column" wrap="nowrap" className={classes.container}>
                     <div>
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index 9b675843..e0803908 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -87,7 +87,6 @@ type DataTableProps<T> = DataTableDataProps<T> & WithStyles<CssRules>;
 export const DataTable = withStyles(styles)(
     class Component<T> extends React.Component<DataTableProps<T>> {
         render() {
-            // console.log('DATA_TABLE, RENDER:' , this)
             const { items, classes, working } = this.props;
             return <div className={classes.root}>
                 <div className={classes.content}>
@@ -98,7 +97,6 @@ export const DataTable = withStyles(styles)(
                             </TableRow>
                         </TableHead>
                         <TableBody className={classes.tableBody}>
-                            {/* {console.log('TABLEBODY>ITEMS',items, "THIS ?", this)} */}
                             { !working && items.map(this.renderBodyRow) }
                         </TableBody>
                     </Table>
@@ -169,12 +167,10 @@ export const DataTable = withStyles(styles)(
                 onContextMenu={this.handleRowContextMenu(item)}
                 onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item)}
                 selected={item === currentItemUuid}>
-                {this.mapVisibleColumns((column, index) => {
-                    // console.log('RENDERBODYROW', column.render(item))
-                    return <TableCell key={column.key || index} className={currentRoute === '/workflows' ? classes.tableCellWorkflows : classes.tableCell}>
+                {this.mapVisibleColumns((column, index) => <TableCell key={column.key || index} className={currentRoute === '/workflows' ? classes.tableCellWorkflows : classes.tableCell}>
                         {column.render(item)}
                     </TableCell>
-        })}
+                )}
             </TableRow>;
         }
 
diff --git a/src/store/data-explorer/data-explorer-middleware-service.ts b/src/store/data-explorer/data-explorer-middleware-service.ts
index c747d533..0b8d6deb 100644
--- a/src/store/data-explorer/data-explorer-middleware-service.ts
+++ b/src/store/data-explorer/data-explorer-middleware-service.ts
@@ -39,8 +39,6 @@ export const getDataExplorerColumnFilters = <T>(
   columnName: string
 ): DataTableFilters => {
   const column = columns.find((c) => c.name === columnName);
-  //lisa
-  //   console.log('DATA_EXPLORER_MIDD, GETDEXCOLUMNFILTERS', column);
   return column ? column.filters : createTree();
 };
 
diff --git a/src/store/data-explorer/data-explorer-middleware.ts b/src/store/data-explorer/data-explorer-middleware.ts
index b99af64a..7d0655e4 100644
--- a/src/store/data-explorer/data-explorer-middleware.ts
+++ b/src/store/data-explorer/data-explorer-middleware.ts
@@ -82,8 +82,6 @@ export const dataExplorerMiddleware =
                       getState().dataExplorer,
                       service.getId()
                     );
-                    //lisa
-                    // console.log('DE_MIDDLEWARE', de);
                     const complete =
                       de.requestState === DataTableRequestState.PENDING;
                     dispatch(
diff --git a/src/store/data-explorer/data-explorer-reducer.ts b/src/store/data-explorer/data-explorer-reducer.ts
index e1f4ef2a..997b6011 100644
--- a/src/store/data-explorer/data-explorer-reducer.ts
+++ b/src/store/data-explorer/data-explorer-reducer.ts
@@ -50,7 +50,6 @@ export const dataExplorerReducer = (
   state: DataExplorerState = {},
   action: DataExplorerAction
 ) => {
-  //   console.log('DATA_EXPLORERE_REDUCER, satate:', state);
   return dataExplorerActions.match(action, {
     CLEAR: ({ id }) =>
       update(state, id, (explorer) => ({
@@ -112,8 +111,6 @@ export const dataExplorerReducer = (
 };
 export const getDataExplorer = (state: DataExplorerState, id: string) => {
   const returnValue = state[id] || initialDataExplorer;
-  //lisa
-  //   console.log('GETDATAEXPLORER RETURN:', state[id]);
   return returnValue;
 };
 
diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx
index 79f530a0..46aca455 100644
--- a/src/views-components/data-explorer/data-explorer.tsx
+++ b/src/views-components/data-explorer/data-explorer.tsx
@@ -22,16 +22,12 @@ interface Props {
 }
 
 const mapStateToProps = (state: RootState, { id }: Props) => {
-    // console.log('DATA_EXPLORER, MSTP GLOBAL STATE:', state)
-    const test = 'foo'
     const progress = state.progressIndicator.find(p => p.id === id);
     const dataExplorerState = getDataExplorer(state.dataExplorer, id);
     const currentRoute = state.router.location ? state.router.location.pathname : '';
     const currentRefresh = localStorage.getItem(LAST_REFRESH_TIMESTAMP) || '';
     const currentItemUuid = currentRoute === '/workflows' ? state.properties.workflowPanelDetailsUuid : state.detailsPanel.resourceUuid;
-// console.log('DATA_EXPLORER, MSTP FILTERED:', {...dataExplorerState})
     return {
-        foo: test,
         ...dataExplorerState,
         working: !!progress?.working,
         currentRefresh: currentRefresh,
diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx
index f6f5048d..3251c729 100644
--- a/src/views/groups-panel/groups-panel.tsx
+++ b/src/views/groups-panel/groups-panel.tsx
@@ -85,7 +85,6 @@ export const GroupsPanel = withStyles(styles)(connect(
     class GroupsPanel extends React.Component<GroupsPanelProps & WithStyles<CssRules>> {
 
         render() {
-            console.log('GROUPSPANEL', this)
             return (
                 <div className={this.props.classes.root}><DataExplorer
                     id={GROUPS_PANEL_ID}

commit 48adcab305593e4e869c2f622b00a1f794d55c50
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Wed Nov 23 22:27:03 2022 -0500

    19690: fixed UUID display
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 0e7c0cd5..dc19ff65 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -723,6 +723,13 @@ export const ResourceOwnerName = connect(
         return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
     })((props: { owner: string }) => renderOwner(props.owner));
 
+const renderUUID = (uuid:string) => <Typography>{uuid}</Typography>
+
+export const ResourceUUID = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+        return { uuid: resource ? resource.uuid : '' };
+    })((props: { uuid: string }) => renderUUID(props.uuid));
     
 const renderDescription = (description: string)=>{
     const truncatedDescription = description ? description.slice(0, 18) + '...' : '-'
@@ -733,8 +740,9 @@ export const ResourceDescription = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
         //testing---------------
-        const containerRequestDescription = "This is a description for a Container Request, also known as a 'Process'. I'm still not 100% sure why one term is used over the other in practice, but I'm new here so I expect it will become clear to me when it's appropriate. This long bit of text is for testing purposes. -LK"
+        const containerRequestDescription = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
         if (resource && !resource.description && resource.kind === ResourceKind.PROCESS) resource.description = containerRequestDescription
+        //testing---------------
         return { description: resource ? resource.description : '' };
     })((props: { description: string }) => renderDescription(props.description));
 
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index bf7ab85a..2958271e 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -19,9 +19,11 @@ import {
     ResourceFileSize,
     ResourceCreatedAtDate,
     ResourceLastModifiedDate,
+    ResourceTrashDate,
     ResourceDeleteDate,
     ProcessStatus,
     ResourceType,
+    ResourceUUID,
     ResourceDescription,
     ResourceOwnerWithName
 } from 'views-components/data-explorer/renderers';
@@ -70,7 +72,8 @@ export enum ProjectPanelColumnNames {
     UUID = "UUID",
     CREATED_AT = "Date created",
     LAST_MODIFIED = "Last modified",
-    DELETE_AT = "Delete At",
+    TRASH_AT = "Trash at",
+    DELETE_AT = "Delete at",
     DESCRIPTION = "Description"
 }
 
@@ -116,17 +119,16 @@ export const projectPanelColumns: DataColumns<string> = [
         filters: createTree(),
         render: uuid => <ResourceFileSize uuid={uuid} />
     },
-    
     {
         name: ProjectPanelColumnNames.UUID,
         selected: false,
         configurable: true,
         filters: createTree(),
-        render: uuid =><>{uuid}</>
+        render: uuid => <ResourceUUID uuid={uuid}/>
     },
     {
         name: ProjectPanelColumnNames.CREATED_AT,
-        selected: false,
+        selected: true,
         configurable: true,
         filters: createTree(),
         render: uuid =><ResourceCreatedAtDate uuid={uuid}/>
@@ -139,9 +141,17 @@ export const projectPanelColumns: DataColumns<string> = [
         filters: createTree(),
         render: uuid => <ResourceLastModifiedDate uuid={uuid} />
     },
+    {
+        name: ProjectPanelColumnNames.TRASH_AT,
+        selected: false,
+        configurable: true,
+        sortDirection: SortDirection.DESC,
+        filters: createTree(),
+        render: uuid => <ResourceTrashDate uuid={uuid} />
+    },
     {
         name: ProjectPanelColumnNames.DELETE_AT,
-        selected: true,
+        selected: false,
         configurable: true,
         sortDirection: SortDirection.DESC,
         filters: createTree(),

commit 031e32ec7eeeeb5b92448ec906856009148efea6
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Wed Nov 23 21:41:48 2022 -0500

    19690: fixed stray null description
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/components/column-selector/column-selector.tsx b/src/components/column-selector/column-selector.tsx
index 63934ddb..3ab38d4d 100644
--- a/src/components/column-selector/column-selector.tsx
+++ b/src/components/column-selector/column-selector.tsx
@@ -10,7 +10,6 @@ import { Popover } from "../popover/popover";
 import { IconButtonProps } from '@material-ui/core/IconButton';
 import { DataColumns } from '../data-table/data-table';
 import { ArvadosTheme } from "common/custom-theme";
-import { ResourceName } from 'views-components/data-explorer/renderers';
 
 interface ColumnSelectorDataProps {
     columns: DataColumns<any>;
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index ea4b8512..0e7c0cd5 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -725,14 +725,14 @@ export const ResourceOwnerName = connect(
 
     
 const renderDescription = (description: string)=>{
-    const truncatedDescription = description.slice(0, 18) + '...'
+    const truncatedDescription = description ? description.slice(0, 18) + '...' : '-'
     return <Typography title={description}>{truncatedDescription}</Typography>;
 }
     
 export const ResourceDescription = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-        //testing
+        //testing---------------
         const containerRequestDescription = "This is a description for a Container Request, also known as a 'Process'. I'm still not 100% sure why one term is used over the other in practice, but I'm new here so I expect it will become clear to me when it's appropriate. This long bit of text is for testing purposes. -LK"
         if (resource && !resource.description && resource.kind === ResourceKind.PROCESS) resource.description = containerRequestDescription
         return { description: resource ? resource.description : '' };

commit 1f2ddfbf602cfa8c0dca0d81e3f2676904dd4c80
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Wed Nov 23 17:59:56 2022 -0500

    19690: description hover hacked in
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 47e5b287..ea4b8512 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -664,16 +664,16 @@ export const ResourceWorkflowStatus = connect(
         };
     })((props: { ownerUuid?: string, uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
 
-export const ResourceLastModifiedDate = connect(
+export const ResourceCreatedAtDate = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-        return { date: resource ? resource.modifiedAt : '' };
+        return { date: resource ? resource.createdAt : '' };
     })((props: { date: string }) => renderDate(props.date));
-
-export const ResourceCreatedAtDate = connect(
+    
+export const ResourceLastModifiedDate = connect(
     (state: RootState, props: { uuid: string }) => {
         const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-        return { date: resource ? resource.createdAt : '' };
+        return { date: resource ? resource.modifiedAt : '' };
     })((props: { date: string }) => renderDate(props.date));
 
 export const ResourceTrashDate = connect(
@@ -723,6 +723,21 @@ export const ResourceOwnerName = connect(
         return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
     })((props: { owner: string }) => renderOwner(props.owner));
 
+    
+const renderDescription = (description: string)=>{
+    const truncatedDescription = description.slice(0, 18) + '...'
+    return <Typography title={description}>{truncatedDescription}</Typography>;
+}
+    
+export const ResourceDescription = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+        //testing
+        const containerRequestDescription = "This is a description for a Container Request, also known as a 'Process'. I'm still not 100% sure why one term is used over the other in practice, but I'm new here so I expect it will become clear to me when it's appropriate. This long bit of text is for testing purposes. -LK"
+        if (resource && !resource.description && resource.kind === ResourceKind.PROCESS) resource.description = containerRequestDescription
+        return { description: resource ? resource.description : '' };
+    })((props: { description: string }) => renderDescription(props.description));
+
 const userFromID =
     connect(
         (state: RootState, props: { uuid: string }) => {
diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx
index 3251c729..f6f5048d 100644
--- a/src/views/groups-panel/groups-panel.tsx
+++ b/src/views/groups-panel/groups-panel.tsx
@@ -85,6 +85,7 @@ export const GroupsPanel = withStyles(styles)(connect(
     class GroupsPanel extends React.Component<GroupsPanelProps & WithStyles<CssRules>> {
 
         render() {
+            console.log('GROUPSPANEL', this)
             return (
                 <div className={this.props.classes.root}><DataExplorer
                     id={GROUPS_PANEL_ID}
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index c2cc8f3c..bf7ab85a 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -17,10 +17,12 @@ import { SortDirection } from 'components/data-table/data-column';
 import { ResourceKind, Resource } from 'models/resource';
 import {
     ResourceFileSize,
-    ResourceLastModifiedDate,
     ResourceCreatedAtDate,
+    ResourceLastModifiedDate,
+    ResourceDeleteDate,
     ProcessStatus,
     ResourceType,
+    ResourceDescription,
     ResourceOwnerWithName
 } from 'views-components/data-explorer/renderers';
 import { ProjectIcon } from 'components/icon/icon';
@@ -65,9 +67,11 @@ export enum ProjectPanelColumnNames {
     TYPE = "Type",
     OWNER = "Owner",
     FILE_SIZE = "File size",
-    LAST_MODIFIED = "Last modified",
     UUID = "UUID",
-    CREATED_AT = "Created"
+    CREATED_AT = "Date created",
+    LAST_MODIFIED = "Last modified",
+    DELETE_AT = "Delete At",
+    DESCRIPTION = "Description"
 }
 
 export interface ProjectPanelFilter extends DataTableFilterItem {
@@ -112,6 +116,21 @@ export const projectPanelColumns: DataColumns<string> = [
         filters: createTree(),
         render: uuid => <ResourceFileSize uuid={uuid} />
     },
+    
+    {
+        name: ProjectPanelColumnNames.UUID,
+        selected: false,
+        configurable: true,
+        filters: createTree(),
+        render: uuid =><>{uuid}</>
+    },
+    {
+        name: ProjectPanelColumnNames.CREATED_AT,
+        selected: false,
+        configurable: true,
+        filters: createTree(),
+        render: uuid =><ResourceCreatedAtDate uuid={uuid}/>
+    },
     {
         name: ProjectPanelColumnNames.LAST_MODIFIED,
         selected: true,
@@ -121,18 +140,19 @@ export const projectPanelColumns: DataColumns<string> = [
         render: uuid => <ResourceLastModifiedDate uuid={uuid} />
     },
     {
-        name: ProjectPanelColumnNames.UUID,
+        name: ProjectPanelColumnNames.DELETE_AT,
         selected: true,
         configurable: true,
+        sortDirection: SortDirection.DESC,
         filters: createTree(),
-        render: uuid =><>{uuid}</>
+        render: uuid => <ResourceDeleteDate uuid={uuid} />
     },
     {
-        name: ProjectPanelColumnNames.CREATED_AT,
+        name: ProjectPanelColumnNames.DESCRIPTION,
         selected: true,
         configurable: true,
         filters: createTree(),
-        render: uuid =><ResourceCreatedAtDate uuid={uuid}/>
+        render: uuid =><ResourceDescription uuid={uuid}/>
     }
 ];
 

commit 859f5402ff0b0629119693a3956acbc439df7bbb
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Wed Nov 23 10:11:19 2022 -0500

    19690: created and deleteAt columns up
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com

diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index cf83fa6f..9b675843 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -98,7 +98,7 @@ export const DataTable = withStyles(styles)(
                             </TableRow>
                         </TableHead>
                         <TableBody className={classes.tableBody}>
-                            {console.log('TABLEBODY>ITEMS',items, "THIS ?", this)}
+                            {/* {console.log('TABLEBODY>ITEMS',items, "THIS ?", this)} */}
                             { !working && items.map(this.renderBodyRow) }
                         </TableBody>
                     </Table>
@@ -170,7 +170,7 @@ export const DataTable = withStyles(styles)(
                 onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item)}
                 selected={item === currentItemUuid}>
                 {this.mapVisibleColumns((column, index) => {
-                    console.log('RENDERBODYROW', column.render(item))
+                    // console.log('RENDERBODYROW', column.render(item))
                     return <TableCell key={column.key || index} className={currentRoute === '/workflows' ? classes.tableCellWorkflows : classes.tableCell}>
                         {column.render(item)}
                     </TableCell>
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index e1fcae4b..0ad3fb8e 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -170,7 +170,6 @@ export const loadWorkbench =
     const { auth, router } = getState();
     const { user } = auth;
     if (user) {
-      //   console.log('PREJECTPANELCOLUMNS', projectPanelColumns);
       dispatch(
         projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns })
       );
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 032b73e1..c2cc8f3c 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -18,6 +18,7 @@ import { ResourceKind, Resource } from 'models/resource';
 import {
     ResourceFileSize,
     ResourceLastModifiedDate,
+    ResourceCreatedAtDate,
     ProcessStatus,
     ResourceType,
     ResourceOwnerWithName
@@ -65,7 +66,8 @@ export enum ProjectPanelColumnNames {
     OWNER = "Owner",
     FILE_SIZE = "File size",
     LAST_MODIFIED = "Last modified",
-    UUID = "UUID"
+    UUID = "UUID",
+    CREATED_AT = "Created"
 }
 
 export interface ProjectPanelFilter extends DataTableFilterItem {
@@ -122,9 +124,15 @@ export const projectPanelColumns: DataColumns<string> = [
         name: ProjectPanelColumnNames.UUID,
         selected: true,
         configurable: true,
-        sortDirection: SortDirection.DESC,
         filters: createTree(),
         render: uuid =><>{uuid}</>
+    },
+    {
+        name: ProjectPanelColumnNames.CREATED_AT,
+        selected: true,
+        configurable: true,
+        filters: createTree(),
+        render: uuid =><ResourceCreatedAtDate uuid={uuid}/>
     }
 ];
 

commit c4975431c61ee21b9eaf10b00aa88e5d9858de59
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Mon Nov 21 10:44:22 2022 -0500

    19690: uuid column goodish
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/src/components/column-selector/column-selector.tsx b/src/components/column-selector/column-selector.tsx
index 441d8ec8..63934ddb 100644
--- a/src/components/column-selector/column-selector.tsx
+++ b/src/components/column-selector/column-selector.tsx
@@ -31,14 +31,16 @@ export type ColumnSelectorProps = ColumnSelectorDataProps & WithStyles<CssRules>
 
 export const ColumnSelector = withStyles(styles)(
     ({ columns, onColumnToggle, classes }: ColumnSelectorProps) =>
-    {console.log('COLUMN_SELECTOR',columns)
-    columns = [...columns, {
-        name:'bananas',
-        selected: false,
-        configurable: true, filters:{}, render: (uuid)=><ResourceName uuid='uuid'/>
-    }
-]
-    console.log('COLUMN_SELECTOR',columns)
+    {
+//         // console.log('COLUMN_SELECTOR',columns)
+//     columns = [...columns, {
+//         name:'bananas',
+//         selected: false,
+//         configurable: true, filters:{}, render: (uuid)=><ResourceName uuid='bananas'/>
+//     }
+// ]
+// //lisa
+//     // console.log('COLUMN_SELECTOR',columns)
        return <Popover triggerComponent={ColumnSelectorTrigger}>
             <Paper>
                 <List dense>
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index cf45e24f..67f9bfba 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -140,8 +140,10 @@ export const DataExplorer = withStyles(styles)(
         }
 
         componentDidMount() {
-            // console.log('DATA_EXPLORER:',this.props)
             if (this.props.onSetColumns) {
+                //lisa
+                // console.log('DATA_EXPLORER_CDM:',this.props)
+                
                 this.props.onSetColumns(this.props.columns);
             }
             // Component just mounted, so we need to show the loading indicator.
@@ -161,7 +163,8 @@ export const DataExplorer = withStyles(styles)(
                 paperKey, fetchMode, currentItemUuid, title,
                 doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, elementPath
             } = this.props;
-
+            //lisa
+// console.log('DATA_EXPLORER_TSX', this.props)
             return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props["data-cy"]}>
                 <Grid container direction="column" wrap="nowrap" className={classes.container}>
                     <div>
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index 10446d1a..cf83fa6f 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -98,6 +98,7 @@ export const DataTable = withStyles(styles)(
                             </TableRow>
                         </TableHead>
                         <TableBody className={classes.tableBody}>
+                            {console.log('TABLEBODY>ITEMS',items, "THIS ?", this)}
                             { !working && items.map(this.renderBodyRow) }
                         </TableBody>
                     </Table>
@@ -168,11 +169,12 @@ export const DataTable = withStyles(styles)(
                 onContextMenu={this.handleRowContextMenu(item)}
                 onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item)}
                 selected={item === currentItemUuid}>
-                {this.mapVisibleColumns((column, index) => (
-                    <TableCell key={column.key || index} className={currentRoute === '/workflows' ? classes.tableCellWorkflows : classes.tableCell}>
+                {this.mapVisibleColumns((column, index) => {
+                    console.log('RENDERBODYROW', column.render(item))
+                    return <TableCell key={column.key || index} className={currentRoute === '/workflows' ? classes.tableCellWorkflows : classes.tableCell}>
                         {column.render(item)}
                     </TableCell>
-                ))}
+        })}
             </TableRow>;
         }
 
diff --git a/src/store/data-explorer/data-explorer-middleware-service.ts b/src/store/data-explorer/data-explorer-middleware-service.ts
index 71a6ee6a..c747d533 100644
--- a/src/store/data-explorer/data-explorer-middleware-service.ts
+++ b/src/store/data-explorer/data-explorer-middleware-service.ts
@@ -2,44 +2,59 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Dispatch, MiddlewareAPI } from "redux";
-import { RootState } from "../store";
-import { DataColumns } from "components/data-table/data-table";
+import { Dispatch, MiddlewareAPI } from 'redux';
+import { RootState } from '../store';
+import { DataColumns } from 'components/data-table/data-table';
 import { DataExplorer } from './data-explorer-reducer';
 import { ListResults } from 'services/common-service/common-service';
-import { createTree } from "models/tree";
-import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
+import { createTree } from 'models/tree';
+import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
 
 export abstract class DataExplorerMiddlewareService {
-    protected readonly id: string;
-
-    protected constructor(id: string) {
-        this.id = id;
-    }
-
-    public getId() {
-        return this.id;
-    }
-
-    public getColumnFilters<T>(columns: DataColumns<T>, columnName: string): DataTableFilters {
-        return getDataExplorerColumnFilters(columns, columnName);
-    }
-
-    abstract requestItems(api: MiddlewareAPI<Dispatch, RootState>, criteriaChanged?: boolean): Promise<void>;
+  protected readonly id: string;
+
+  protected constructor(id: string) {
+    this.id = id;
+  }
+
+  public getId() {
+    return this.id;
+  }
+
+  public getColumnFilters<T>(
+    columns: DataColumns<T>,
+    columnName: string
+  ): DataTableFilters {
+    return getDataExplorerColumnFilters(columns, columnName);
+  }
+
+  abstract requestItems(
+    api: MiddlewareAPI<Dispatch, RootState>,
+    criteriaChanged?: boolean
+  ): Promise<void>;
 }
 
-export const getDataExplorerColumnFilters = <T>(columns: DataColumns<T>, columnName: string): DataTableFilters => {
-    const column = columns.find(c => c.name === columnName);
-    return column ? column.filters : createTree();
+export const getDataExplorerColumnFilters = <T>(
+  columns: DataColumns<T>,
+  columnName: string
+): DataTableFilters => {
+  const column = columns.find((c) => c.name === columnName);
+  //lisa
+  //   console.log('DATA_EXPLORER_MIDD, GETDEXCOLUMNFILTERS', column);
+  return column ? column.filters : createTree();
 };
 
 export const dataExplorerToListParams = (dataExplorer: DataExplorer) => ({
-    limit: dataExplorer.rowsPerPage,
-    offset: dataExplorer.page * dataExplorer.rowsPerPage
+  limit: dataExplorer.rowsPerPage,
+  offset: dataExplorer.page * dataExplorer.rowsPerPage,
 });
 
-export const listResultsToDataExplorerItemsMeta = <R>({ itemsAvailable, offset, limit }: ListResults<R>) => ({
-    itemsAvailable,
-    page: Math.floor(offset / limit),
-    rowsPerPage: limit
+export const listResultsToDataExplorerItemsMeta = <R>({
+  itemsAvailable,
+  offset,
+  limit,
+}: ListResults<R>) => ({
+  itemsAvailable,
+  page: Math.floor(offset / limit),
+  rowsPerPage: limit,
 });
diff --git a/src/store/data-explorer/data-explorer-middleware.ts b/src/store/data-explorer/data-explorer-middleware.ts
index efe51fe3..b99af64a 100644
--- a/src/store/data-explorer/data-explorer-middleware.ts
+++ b/src/store/data-explorer/data-explorer-middleware.ts
@@ -1,4 +1,3 @@
-
 // Copyright (C) The Arvados Authors. All rights reserved.
 //
 // SPDX-License-Identifier: AGPL-3.0
@@ -6,74 +5,113 @@
 import { Dispatch } from 'redux';
 import { RootState } from 'store/store';
 import { ServiceRepository } from 'services/services';
-import { Middleware } from "redux";
-import { dataExplorerActions, bindDataExplorerActions, DataTableRequestState } from "./data-explorer-action";
-import { getDataExplorer } from "./data-explorer-reducer";
-import { DataExplorerMiddlewareService } from "./data-explorer-middleware-service";
+import { Middleware } from 'redux';
+import {
+  dataExplorerActions,
+  bindDataExplorerActions,
+  DataTableRequestState,
+} from './data-explorer-action';
+import { getDataExplorer } from './data-explorer-reducer';
+import { DataExplorerMiddlewareService } from './data-explorer-middleware-service';
 
-export const dataExplorerMiddleware = (service: DataExplorerMiddlewareService): Middleware => api => next => {
+export const dataExplorerMiddleware =
+  (service: DataExplorerMiddlewareService): Middleware =>
+  (api) =>
+  (next) => {
     const actions = bindDataExplorerActions(service.getId());
 
-    return action => {
-        const handleAction = <T extends { id: string }>(handler: (data: T) => void) =>
-            (data: T) => {
-                next(action);
-                if (data.id === service.getId()) {
-                    handler(data);
-                }
-            };
-        dataExplorerActions.match(action, {
-            SET_PAGE: handleAction(() => {
-                api.dispatch(actions.REQUEST_ITEMS(false));
-            }),
-            SET_ROWS_PER_PAGE: handleAction(() => {
-                api.dispatch(actions.REQUEST_ITEMS(true));
-            }),
-            SET_FILTERS: handleAction(() => {
-                api.dispatch(actions.RESET_PAGINATION());
-                api.dispatch(actions.REQUEST_ITEMS(true));
-            }),
-            TOGGLE_SORT: handleAction(() => {
-                api.dispatch(actions.REQUEST_ITEMS(true));
-            }),
-            SET_EXPLORER_SEARCH_VALUE: handleAction(() => {
-                api.dispatch(actions.RESET_PAGINATION());
-                api.dispatch(actions.REQUEST_ITEMS(true));
-            }),
-            REQUEST_ITEMS: handleAction(({ criteriaChanged }) => {
-                api.dispatch<any>(async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-                    while (true) {
-                        let de = getDataExplorer(getState().dataExplorer, service.getId());
-                        switch (de.requestState) {
-                            case DataTableRequestState.IDLE:
-                                // Start a new request.
-                                try {
-                                    dispatch(actions.SET_REQUEST_STATE({ requestState: DataTableRequestState.PENDING }));
-                                    await service.requestItems(api, criteriaChanged);
-                                } catch {
-                                    dispatch(actions.SET_REQUEST_STATE({ requestState: DataTableRequestState.NEED_REFRESH }));
-                                }
-                                // Now check if the state is still PENDING, if it moved to NEED_REFRESH
-                                // then we need to reissue requestItems
-                                de = getDataExplorer(getState().dataExplorer, service.getId());
-                                const complete = (de.requestState === DataTableRequestState.PENDING);
-                                dispatch(actions.SET_REQUEST_STATE({ requestState: DataTableRequestState.IDLE }));
-                                if (complete) {
-                                    return;
-                                }
-                                break;
-                            case DataTableRequestState.PENDING:
-                                // State is PENDING, move it to NEED_REFRESH so that when the current request finishes it starts a new one.
-                                dispatch(actions.SET_REQUEST_STATE({ requestState: DataTableRequestState.NEED_REFRESH }));
-                                return;
-                            case DataTableRequestState.NEED_REFRESH:
-                                // Nothing to do right now.
-                                return;
-                        }
+    return (action) => {
+      const handleAction =
+        <T extends { id: string }>(handler: (data: T) => void) =>
+        (data: T) => {
+          next(action);
+          if (data.id === service.getId()) {
+            handler(data);
+          }
+        };
+      dataExplorerActions.match(action, {
+        SET_PAGE: handleAction(() => {
+          api.dispatch(actions.REQUEST_ITEMS(false));
+        }),
+        SET_ROWS_PER_PAGE: handleAction(() => {
+          api.dispatch(actions.REQUEST_ITEMS(true));
+        }),
+        SET_FILTERS: handleAction(() => {
+          api.dispatch(actions.RESET_PAGINATION());
+          api.dispatch(actions.REQUEST_ITEMS(true));
+        }),
+        TOGGLE_SORT: handleAction(() => {
+          api.dispatch(actions.REQUEST_ITEMS(true));
+        }),
+        SET_EXPLORER_SEARCH_VALUE: handleAction(() => {
+          api.dispatch(actions.RESET_PAGINATION());
+          api.dispatch(actions.REQUEST_ITEMS(true));
+        }),
+        REQUEST_ITEMS: handleAction(({ criteriaChanged }) => {
+          api.dispatch<any>(
+            async (
+              dispatch: Dispatch,
+              getState: () => RootState,
+              services: ServiceRepository
+            ) => {
+              while (true) {
+                let de = getDataExplorer(
+                  getState().dataExplorer,
+                  service.getId()
+                );
+                switch (de.requestState) {
+                  case DataTableRequestState.IDLE:
+                    // Start a new request.
+                    try {
+                      dispatch(
+                        actions.SET_REQUEST_STATE({
+                          requestState: DataTableRequestState.PENDING,
+                        })
+                      );
+                      await service.requestItems(api, criteriaChanged);
+                    } catch {
+                      dispatch(
+                        actions.SET_REQUEST_STATE({
+                          requestState: DataTableRequestState.NEED_REFRESH,
+                        })
+                      );
                     }
-                });
-            }),
-            default: () => next(action)
-        });
+                    // Now check if the state is still PENDING, if it moved to NEED_REFRESH
+                    // then we need to reissue requestItems
+                    de = getDataExplorer(
+                      getState().dataExplorer,
+                      service.getId()
+                    );
+                    //lisa
+                    // console.log('DE_MIDDLEWARE', de);
+                    const complete =
+                      de.requestState === DataTableRequestState.PENDING;
+                    dispatch(
+                      actions.SET_REQUEST_STATE({
+                        requestState: DataTableRequestState.IDLE,
+                      })
+                    );
+                    if (complete) {
+                      return;
+                    }
+                    break;
+                  case DataTableRequestState.PENDING:
+                    // State is PENDING, move it to NEED_REFRESH so that when the current request finishes it starts a new one.
+                    dispatch(
+                      actions.SET_REQUEST_STATE({
+                        requestState: DataTableRequestState.NEED_REFRESH,
+                      })
+                    );
+                    return;
+                  case DataTableRequestState.NEED_REFRESH:
+                    // Nothing to do right now.
+                    return;
+                }
+              }
+            }
+          );
+        }),
+        default: () => next(action),
+      });
     };
-};
+  };
diff --git a/src/store/data-explorer/data-explorer-reducer.ts b/src/store/data-explorer/data-explorer-reducer.ts
index 4ae01adf..e1f4ef2a 100644
--- a/src/store/data-explorer/data-explorer-reducer.ts
+++ b/src/store/data-explorer/data-explorer-reducer.ts
@@ -110,8 +110,12 @@ export const dataExplorerReducer = (
     default: () => state,
   });
 };
-export const getDataExplorer = (state: DataExplorerState, id: string) =>
-  state[id] || initialDataExplorer;
+export const getDataExplorer = (state: DataExplorerState, id: string) => {
+  const returnValue = state[id] || initialDataExplorer;
+  //lisa
+  //   console.log('GETDATAEXPLORER RETURN:', state[id]);
+  return returnValue;
+};
 
 export const getSortColumn = (dataExplorer: DataExplorer) =>
   dataExplorer.columns.find(
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index 0a348431..e1fcae4b 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -3,40 +3,46 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from 'redux';
-import { RootState } from "store/store";
-import { getUserUuid } from "common/getuser";
+import { RootState } from 'store/store';
+import { getUserUuid } from 'common/getuser';
 import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
-import { favoritePanelActions, loadFavoritePanel } from 'store/favorite-panel/favorite-panel-action';
 import {
-    getProjectPanelCurrentUuid,
-    openProjectPanel,
-    projectPanelActions,
-    setIsProjectPanelTrashed
+  favoritePanelActions,
+  loadFavoritePanel,
+} from 'store/favorite-panel/favorite-panel-action';
+import {
+  getProjectPanelCurrentUuid,
+  openProjectPanel,
+  projectPanelActions,
+  setIsProjectPanelTrashed,
 } from 'store/project-panel/project-panel-action';
 import {
-    activateSidePanelTreeItem,
-    initSidePanelTree,
-    loadSidePanelTreeProjects,
-    SidePanelTreeCategory
+  activateSidePanelTreeItem,
+  initSidePanelTree,
+  loadSidePanelTreeProjects,
+  SidePanelTreeCategory,
 } from 'store/side-panel-tree/side-panel-tree-actions';
 import { updateResources } from 'store/resources/resources-actions';
 import { projectPanelColumns } from 'views/project-panel/project-panel';
 import { favoritePanelColumns } from 'views/favorite-panel/favorite-panel';
 import { matchRootRoute } from 'routes/routes';
 import {
-    setBreadcrumbs,
-    setGroupDetailsBreadcrumbs,
-    setGroupsBreadcrumbs,
-    setProcessBreadcrumbs,
-    setSharedWithMeBreadcrumbs,
-    setSidePanelBreadcrumbs,
-    setTrashBreadcrumbs,
-    setUsersBreadcrumbs,
-    setMyAccountBreadcrumbs,
-    setUserProfileBreadcrumbs,
+  setBreadcrumbs,
+  setGroupDetailsBreadcrumbs,
+  setGroupsBreadcrumbs,
+  setProcessBreadcrumbs,
+  setSharedWithMeBreadcrumbs,
+  setSidePanelBreadcrumbs,
+  setTrashBreadcrumbs,
+  setUsersBreadcrumbs,
+  setMyAccountBreadcrumbs,
+  setUserProfileBreadcrumbs,
 } from 'store/breadcrumbs/breadcrumbs-actions';
-import { navigateTo, navigateToRootProject } from 'store/navigation/navigation-action';
+import {
+  navigateTo,
+  navigateToRootProject,
+} from 'store/navigation/navigation-action';
 import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
 import { ServiceRepository } from 'services/services';
 import { getResource } from 'store/resources/resources';
@@ -50,17 +56,23 @@ import * as processesActions from 'store/processes/processes-actions';
 import * as processMoveActions from 'store/processes/process-move-actions';
 import * as processUpdateActions from 'store/processes/process-update-actions';
 import * as processCopyActions from 'store/processes/process-copy-actions';
-import { trashPanelColumns } from "views/trash-panel/trash-panel";
-import { loadTrashPanel, trashPanelActions } from "store/trash-panel/trash-panel-action";
+import { trashPanelColumns } from 'views/trash-panel/trash-panel';
+import {
+  loadTrashPanel,
+  trashPanelActions,
+} from 'store/trash-panel/trash-panel-action';
 import { loadProcessPanel } from 'store/process-panel/process-panel-actions';
 import {
-    loadSharedWithMePanel,
-    sharedWithMePanelActions
+  loadSharedWithMePanel,
+  sharedWithMePanelActions,
 } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
 import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
 import { workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions';
 import { loadSshKeysPanel } from 'store/auth/auth-action-ssh';
-import { loadLinkAccountPanel, linkAccountPanelActions } from 'store/link-account-panel/link-account-panel-actions';
+import {
+  loadLinkAccountPanel,
+  linkAccountPanelActions,
+} from 'store/link-account-panel/link-account-panel-actions';
 import { loadSiteManagerPanel } from 'store/auth/auth-action-session';
 import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view';
 import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
@@ -70,11 +82,14 @@ import { FilterBuilder } from 'services/api/filter-builder';
 import { GroupContentsResource } from 'services/groups-service/groups-service';
 import { MatchCases, ofType, unionize, UnionOf } from 'common/unionize';
 import { loadRunProcessPanel } from 'store/run-process-panel/run-process-panel-actions';
-import { collectionPanelActions, loadCollectionPanel } from "store/collection-panel/collection-panel-action";
-import { CollectionResource } from "models/collection";
 import {
-    loadSearchResultsPanel,
-    searchResultsPanelActions
+  collectionPanelActions,
+  loadCollectionPanel,
+} from 'store/collection-panel/collection-panel-action';
+import { CollectionResource } from 'models/collection';
+import {
+  loadSearchResultsPanel,
+  searchResultsPanelActions,
 } from 'store/search-results-panel/search-results-panel-actions';
 import { searchResultsPanelColumns } from 'views/search-results-panel/search-results-panel-view';
 import { loadVirtualMachinesPanel } from 'store/virtual-machines/virtual-machines-actions';
@@ -82,23 +97,41 @@ import { loadRepositoriesPanel } from 'store/repositories/repositories-actions';
 import { loadKeepServicesPanel } from 'store/keep-services/keep-services-actions';
 import { loadUsersPanel, userBindedActions } from 'store/users/users-actions';
 import * as userProfilePanelActions from 'store/user-profile/user-profile-actions';
-import { linkPanelActions, loadLinkPanel } from 'store/link-panel/link-panel-actions';
+import {
+  linkPanelActions,
+  loadLinkPanel,
+} from 'store/link-panel/link-panel-actions';
 import { linkPanelColumns } from 'views/link-panel/link-panel-root';
 import { userPanelColumns } from 'views/user-panel/user-panel';
-import { loadApiClientAuthorizationsPanel, apiClientAuthorizationsActions } from 'store/api-client-authorizations/api-client-authorizations-actions';
+import {
+  loadApiClientAuthorizationsPanel,
+  apiClientAuthorizationsActions,
+} from 'store/api-client-authorizations/api-client-authorizations-actions';
 import { apiClientAuthorizationPanelColumns } from 'views/api-client-authorization-panel/api-client-authorization-panel-root';
 import * as groupPanelActions from 'store/groups-panel/groups-panel-actions';
 import { groupsPanelColumns } from 'views/groups-panel/groups-panel';
 import * as groupDetailsPanelActions from 'store/group-details-panel/group-details-panel-actions';
-import { groupDetailsMembersPanelColumns, groupDetailsPermissionsPanelColumns } from 'views/group-details-panel/group-details-panel';
-import { DataTableFetchMode } from "components/data-table/data-table";
-import { loadPublicFavoritePanel, publicFavoritePanelActions } from 'store/public-favorites-panel/public-favorites-action';
+import {
+  groupDetailsMembersPanelColumns,
+  groupDetailsPermissionsPanelColumns,
+} from 'views/group-details-panel/group-details-panel';
+import { DataTableFetchMode } from 'components/data-table/data-table';
+import {
+  loadPublicFavoritePanel,
+  publicFavoritePanelActions,
+} from 'store/public-favorites-panel/public-favorites-action';
 import { publicFavoritePanelColumns } from 'views/public-favorites-panel/public-favorites-panel';
-import { loadCollectionsContentAddressPanel, collectionsContentAddressActions } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
+import {
+  loadCollectionsContentAddressPanel,
+  collectionsContentAddressActions,
+} from 'store/collections-content-address-panel/collections-content-address-panel-actions';
 import { collectionContentAddressPanelColumns } from 'views/collection-content-address-panel/collection-content-address-panel';
 import { subprocessPanelActions } from 'store/subprocess-panel/subprocess-panel-actions';
 import { subprocessPanelColumns } from 'views/subprocess-panel/subprocess-panel-root';
-import { loadAllProcessesPanel, allProcessesPanelActions } from '../all-processes-panel/all-processes-panel-action';
+import {
+  loadAllProcessesPanel,
+  allProcessesPanelActions,
+} from '../all-processes-panel/all-processes-panel-action';
 import { allProcessesPanelColumns } from 'views/all-processes-panel/all-processes-panel';
 import { AdminMenuIcon } from 'components/icon/icon';
 import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root';
@@ -106,497 +139,759 @@ import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-
 export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
 
 export const isWorkbenchLoading = (state: RootState) => {
-    const progress = getProgressIndicator(WORKBENCH_LOADING_SCREEN)(state.progressIndicator);
-    return progress ? progress.working : false;
+  const progress = getProgressIndicator(WORKBENCH_LOADING_SCREEN)(
+    state.progressIndicator
+  );
+  return progress ? progress.working : false;
 };
 
-export const handleFirstTimeLoad = (action: any) =>
-    async (dispatch: Dispatch<any>, getState: () => RootState) => {
-        try {
-            await dispatch(action);
-        } finally {
-            if (isWorkbenchLoading(getState())) {
-                dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
-            }
-        }
-    };
-
-export const loadWorkbench = () =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
-        const { auth, router } = getState();
-        const { user } = auth;
-        if (user) {
-            dispatch(projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
-            dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns }));
-            dispatch(allProcessesPanelActions.SET_COLUMNS({ columns: allProcessesPanelColumns }));
-            dispatch(publicFavoritePanelActions.SET_COLUMNS({ columns: publicFavoritePanelColumns }));
-            dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
-            dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
-            dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
-            dispatch(searchResultsPanelActions.SET_FETCH_MODE({ fetchMode: DataTableFetchMode.INFINITE }));
-            dispatch(searchResultsPanelActions.SET_COLUMNS({ columns: searchResultsPanelColumns }));
-            dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
-            dispatch(groupPanelActions.GroupsPanelActions.SET_COLUMNS({ columns: groupsPanelColumns }));
-            dispatch(groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({ columns: groupDetailsMembersPanelColumns }));
-            dispatch(groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({ columns: groupDetailsPermissionsPanelColumns }));
-            dispatch(userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({ columns: userProfileGroupsColumns }));
-            dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
-            dispatch(apiClientAuthorizationsActions.SET_COLUMNS({ columns: apiClientAuthorizationPanelColumns }));
-            dispatch(collectionsContentAddressActions.SET_COLUMNS({ columns: collectionContentAddressPanelColumns }));
-            dispatch(subprocessPanelActions.SET_COLUMNS({ columns: subprocessPanelColumns }));
-
-            if (services.linkAccountService.getAccountToLink()) {
-                dispatch(linkAccountPanelActions.HAS_SESSION_DATA());
-            }
-
-            dispatch<any>(initSidePanelTree());
-            if (router.location) {
-                const match = matchRootRoute(router.location.pathname);
-                if (match) {
-                    dispatch<any>(navigateToRootProject);
-                }
-            }
-        } else {
-            dispatch(userIsNotAuthenticated);
+export const handleFirstTimeLoad =
+  (action: any) =>
+  async (dispatch: Dispatch<any>, getState: () => RootState) => {
+    try {
+      await dispatch(action);
+    } finally {
+      if (isWorkbenchLoading(getState())) {
+        dispatch(
+          progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN)
+        );
+      }
+    }
+  };
+
+export const loadWorkbench =
+  () =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
+    const { auth, router } = getState();
+    const { user } = auth;
+    if (user) {
+      //   console.log('PREJECTPANELCOLUMNS', projectPanelColumns);
+      dispatch(
+        projectPanelActions.SET_COLUMNS({ columns: projectPanelColumns })
+      );
+      dispatch(
+        favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns })
+      );
+      dispatch(
+        allProcessesPanelActions.SET_COLUMNS({
+          columns: allProcessesPanelColumns,
+        })
+      );
+      dispatch(
+        publicFavoritePanelActions.SET_COLUMNS({
+          columns: publicFavoritePanelColumns,
+        })
+      );
+      dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
+      dispatch(
+        sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })
+      );
+      dispatch(
+        workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns })
+      );
+      dispatch(
+        searchResultsPanelActions.SET_FETCH_MODE({
+          fetchMode: DataTableFetchMode.INFINITE,
+        })
+      );
+      dispatch(
+        searchResultsPanelActions.SET_COLUMNS({
+          columns: searchResultsPanelColumns,
+        })
+      );
+      dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
+      dispatch(
+        groupPanelActions.GroupsPanelActions.SET_COLUMNS({
+          columns: groupsPanelColumns,
+        })
+      );
+      dispatch(
+        groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({
+          columns: groupDetailsMembersPanelColumns,
+        })
+      );
+      dispatch(
+        groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({
+          columns: groupDetailsPermissionsPanelColumns,
+        })
+      );
+      dispatch(
+        userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({
+          columns: userProfileGroupsColumns,
+        })
+      );
+      dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
+      dispatch(
+        apiClientAuthorizationsActions.SET_COLUMNS({
+          columns: apiClientAuthorizationPanelColumns,
+        })
+      );
+      dispatch(
+        collectionsContentAddressActions.SET_COLUMNS({
+          columns: collectionContentAddressPanelColumns,
+        })
+      );
+      dispatch(
+        subprocessPanelActions.SET_COLUMNS({ columns: subprocessPanelColumns })
+      );
+
+      if (services.linkAccountService.getAccountToLink()) {
+        dispatch(linkAccountPanelActions.HAS_SESSION_DATA());
+      }
+
+      dispatch<any>(initSidePanelTree());
+      if (router.location) {
+        const match = matchRootRoute(router.location.pathname);
+        if (match) {
+          dispatch<any>(navigateToRootProject);
         }
-    };
+      }
+    } else {
+      dispatch(userIsNotAuthenticated);
+    }
+  };
 
 export const loadFavorites = () =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch) => {
-            dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.FAVORITES));
-            dispatch<any>(loadFavoritePanel());
-            dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.FAVORITES));
-        });
+  handleFirstTimeLoad((dispatch: Dispatch) => {
+    dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.FAVORITES));
+    dispatch<any>(loadFavoritePanel());
+    dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.FAVORITES));
+  });
 
 export const loadCollectionContentAddress = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadCollectionsContentAddressPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadCollectionsContentAddressPanel());
+  }
+);
 
 export const loadTrash = () =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch) => {
-            dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
-            dispatch<any>(loadTrashPanel());
-            dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.TRASH));
-        });
+  handleFirstTimeLoad((dispatch: Dispatch) => {
+    dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+    dispatch<any>(loadTrashPanel());
+    dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.TRASH));
+  });
 
 export const loadAllProcesses = () =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch) => {
-            dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.ALL_PROCESSES));
-            dispatch<any>(loadAllProcessesPanel());
-            dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.ALL_PROCESSES));
-        }
+  handleFirstTimeLoad((dispatch: Dispatch) => {
+    dispatch<any>(
+      activateSidePanelTreeItem(SidePanelTreeCategory.ALL_PROCESSES)
     );
+    dispatch<any>(loadAllProcessesPanel());
+    dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.ALL_PROCESSES));
+  });
 
 export const loadProject = (uuid: string) =>
-    handleFirstTimeLoad(
-        async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
-            const userUuid = getUserUuid(getState());
-            dispatch(setIsProjectPanelTrashed(false));
-            if (!userUuid) {
-                return;
-            }
-            if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
-                // Load another users home projects
-                dispatch(finishLoadingProject(uuid));
-            } else if (userUuid !== uuid) {
-                await dispatch(finishLoadingProject(uuid));
-                const match = await loadGroupContentsResource({ uuid, userUuid, services });
-                match({
-                    OWNED: async () => {
-                        await dispatch(activateSidePanelTreeItem(uuid));
-                        dispatch<any>(setSidePanelBreadcrumbs(uuid));
-                    },
-                    SHARED: async () => {
-                        await dispatch(activateSidePanelTreeItem(uuid));
-                        dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
-                    },
-                    TRASHED: async () => {
-                        await dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
-                        dispatch<any>(setTrashBreadcrumbs(uuid));
-                        dispatch(setIsProjectPanelTrashed(true));
-                    }
-                });
-            } else {
-                await dispatch(finishLoadingProject(userUuid));
-                await dispatch(activateSidePanelTreeItem(userUuid));
-                dispatch<any>(setSidePanelBreadcrumbs(userUuid));
-            }
+  handleFirstTimeLoad(
+    async (
+      dispatch: Dispatch<any>,
+      getState: () => RootState,
+      services: ServiceRepository
+    ) => {
+      const userUuid = getUserUuid(getState());
+      dispatch(setIsProjectPanelTrashed(false));
+      if (!userUuid) {
+        return;
+      }
+      if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
+        // Load another users home projects
+        dispatch(finishLoadingProject(uuid));
+      } else if (userUuid !== uuid) {
+        await dispatch(finishLoadingProject(uuid));
+        const match = await loadGroupContentsResource({
+          uuid,
+          userUuid,
+          services,
+        });
+        match({
+          OWNED: async () => {
+            await dispatch(activateSidePanelTreeItem(uuid));
+            dispatch<any>(setSidePanelBreadcrumbs(uuid));
+          },
+          SHARED: async () => {
+            await dispatch(activateSidePanelTreeItem(uuid));
+            dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
+          },
+          TRASHED: async () => {
+            await dispatch(
+              activateSidePanelTreeItem(SidePanelTreeCategory.TRASH)
+            );
+            dispatch<any>(setTrashBreadcrumbs(uuid));
+            dispatch(setIsProjectPanelTrashed(true));
+          },
         });
+      } else {
+        await dispatch(finishLoadingProject(userUuid));
+        await dispatch(activateSidePanelTreeItem(userUuid));
+        dispatch<any>(setSidePanelBreadcrumbs(userUuid));
+      }
+    }
+  );
 
-export const createProject = (data: projectCreateActions.ProjectCreateFormDialogData) =>
-    async (dispatch: Dispatch) => {
-        const newProject = await dispatch<any>(projectCreateActions.createProject(data));
-        if (newProject) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Project has been successfully created.",
-                hideDuration: 2000,
-                kind: SnackbarKind.SUCCESS
-            }));
-            await dispatch<any>(loadSidePanelTreeProjects(newProject.ownerUuid));
-            dispatch<any>(navigateTo(newProject.uuid));
-        }
-    };
-
-export const moveProject = (data: MoveToFormDialogData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        try {
-            const oldProject = getResource(data.uuid)(getState().resources);
-            const oldOwnerUuid = oldProject ? oldProject.ownerUuid : '';
-            const movedProject = await dispatch<any>(projectMoveActions.moveProject(data));
-            if (movedProject) {
-                dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Project has been moved', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-                if (oldProject) {
-                    await dispatch<any>(loadSidePanelTreeProjects(oldProject.ownerUuid));
-                }
-                dispatch<any>(reloadProjectMatchingUuid([oldOwnerUuid, movedProject.ownerUuid, movedProject.uuid]));
-            }
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
-
-export const updateProject = (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
-    async (dispatch: Dispatch) => {
-        const updatedProject = await dispatch<any>(projectUpdateActions.updateProject(data));
-        if (updatedProject) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Project has been successfully updated.",
-                hideDuration: 2000,
-                kind: SnackbarKind.SUCCESS
-            }));
-            await dispatch<any>(loadSidePanelTreeProjects(updatedProject.ownerUuid));
-            dispatch<any>(reloadProjectMatchingUuid([updatedProject.ownerUuid, updatedProject.uuid]));
-        }
-    };
-
-export const updateGroup = (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
-    async (dispatch: Dispatch) => {
-        const updatedGroup = await dispatch<any>(groupPanelActions.updateGroup(data));
-        if (updatedGroup) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Group has been successfully updated.",
-                hideDuration: 2000,
-                kind: SnackbarKind.SUCCESS
-            }));
-            await dispatch<any>(loadSidePanelTreeProjects(updatedGroup.ownerUuid));
-            dispatch<any>(reloadProjectMatchingUuid([updatedGroup.ownerUuid, updatedGroup.uuid]));
+export const createProject =
+  (data: projectCreateActions.ProjectCreateFormDialogData) =>
+  async (dispatch: Dispatch) => {
+    const newProject = await dispatch<any>(
+      projectCreateActions.createProject(data)
+    );
+    if (newProject) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Project has been successfully created.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+      await dispatch<any>(loadSidePanelTreeProjects(newProject.ownerUuid));
+      dispatch<any>(navigateTo(newProject.uuid));
+    }
+  };
+
+export const moveProject =
+  (data: MoveToFormDialogData) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    try {
+      const oldProject = getResource(data.uuid)(getState().resources);
+      const oldOwnerUuid = oldProject ? oldProject.ownerUuid : '';
+      const movedProject = await dispatch<any>(
+        projectMoveActions.moveProject(data)
+      );
+      if (movedProject) {
+        dispatch(
+          snackbarActions.OPEN_SNACKBAR({
+            message: 'Project has been moved',
+            hideDuration: 2000,
+            kind: SnackbarKind.SUCCESS,
+          })
+        );
+        if (oldProject) {
+          await dispatch<any>(loadSidePanelTreeProjects(oldProject.ownerUuid));
         }
-    };
+        dispatch<any>(
+          reloadProjectMatchingUuid([
+            oldOwnerUuid,
+            movedProject.ownerUuid,
+            movedProject.uuid,
+          ])
+        );
+      }
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
+
+export const updateProject =
+  (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
+  async (dispatch: Dispatch) => {
+    const updatedProject = await dispatch<any>(
+      projectUpdateActions.updateProject(data)
+    );
+    if (updatedProject) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Project has been successfully updated.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+      await dispatch<any>(loadSidePanelTreeProjects(updatedProject.ownerUuid));
+      dispatch<any>(
+        reloadProjectMatchingUuid([
+          updatedProject.ownerUuid,
+          updatedProject.uuid,
+        ])
+      );
+    }
+  };
+
+export const updateGroup =
+  (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
+  async (dispatch: Dispatch) => {
+    const updatedGroup = await dispatch<any>(
+      groupPanelActions.updateGroup(data)
+    );
+    if (updatedGroup) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Group has been successfully updated.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+      await dispatch<any>(loadSidePanelTreeProjects(updatedGroup.ownerUuid));
+      dispatch<any>(
+        reloadProjectMatchingUuid([updatedGroup.ownerUuid, updatedGroup.uuid])
+      );
+    }
+  };
 
 export const loadCollection = (uuid: string) =>
-    handleFirstTimeLoad(
-        async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
-            const userUuid = getUserUuid(getState());
-            if (userUuid) {
-                const match = await loadGroupContentsResource({ uuid, userUuid, services });
-                match({
-                    OWNED: collection => {
-                        dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource));
-                        dispatch(updateResources([collection]));
-                        dispatch(activateSidePanelTreeItem(collection.ownerUuid));
-                        dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
-                        dispatch(loadCollectionPanel(collection.uuid));
-                    },
-                    SHARED: collection => {
-                        dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource));
-                        dispatch(updateResources([collection]));
-                        dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
-                        dispatch(activateSidePanelTreeItem(collection.ownerUuid));
-                        dispatch(loadCollectionPanel(collection.uuid));
-                    },
-                    TRASHED: collection => {
-                        dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource));
-                        dispatch(updateResources([collection]));
-                        dispatch(setTrashBreadcrumbs(''));
-                        dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
-                        dispatch(loadCollectionPanel(collection.uuid));
-                    },
-                });
-            }
+  handleFirstTimeLoad(
+    async (
+      dispatch: Dispatch<any>,
+      getState: () => RootState,
+      services: ServiceRepository
+    ) => {
+      const userUuid = getUserUuid(getState());
+      if (userUuid) {
+        const match = await loadGroupContentsResource({
+          uuid,
+          userUuid,
+          services,
         });
+        match({
+          OWNED: (collection) => {
+            dispatch(
+              collectionPanelActions.SET_COLLECTION(
+                collection as CollectionResource
+              )
+            );
+            dispatch(updateResources([collection]));
+            dispatch(activateSidePanelTreeItem(collection.ownerUuid));
+            dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
+            dispatch(loadCollectionPanel(collection.uuid));
+          },
+          SHARED: (collection) => {
+            dispatch(
+              collectionPanelActions.SET_COLLECTION(
+                collection as CollectionResource
+              )
+            );
+            dispatch(updateResources([collection]));
+            dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
+            dispatch(activateSidePanelTreeItem(collection.ownerUuid));
+            dispatch(loadCollectionPanel(collection.uuid));
+          },
+          TRASHED: (collection) => {
+            dispatch(
+              collectionPanelActions.SET_COLLECTION(
+                collection as CollectionResource
+              )
+            );
+            dispatch(updateResources([collection]));
+            dispatch(setTrashBreadcrumbs(''));
+            dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+            dispatch(loadCollectionPanel(collection.uuid));
+          },
+        });
+      }
+    }
+  );
 
-export const createCollection = (data: collectionCreateActions.CollectionCreateFormDialogData) =>
-    async (dispatch: Dispatch) => {
-        const collection = await dispatch<any>(collectionCreateActions.createCollection(data));
-        if (collection) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Collection has been successfully created.",
-                hideDuration: 2000,
-                kind: SnackbarKind.SUCCESS
-            }));
-            dispatch<any>(updateResources([collection]));
-            dispatch<any>(navigateTo(collection.uuid));
-        }
-    };
-
-export const copyCollection = (data: CopyFormDialogData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        try {
-            const copyToProject = getResource(data.ownerUuid)(getState().resources);
-            const collection = await dispatch<any>(collectionCopyActions.copyCollection(data));
-            if (copyToProject && collection) {
-                dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
-                dispatch(snackbarActions.OPEN_SNACKBAR({
-                    message: 'Collection has been copied.',
-                    hideDuration: 3000,
-                    kind: SnackbarKind.SUCCESS,
-                    link: collection.ownerUuid
-                }));
-            }
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
-
-export const moveCollection = (data: MoveToFormDialogData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        try {
-            const collection = await dispatch<any>(collectionMoveActions.moveCollection(data));
-            dispatch<any>(updateResources([collection]));
-            dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been moved.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
+export const createCollection =
+  (data: collectionCreateActions.CollectionCreateFormDialogData) =>
+  async (dispatch: Dispatch) => {
+    const collection = await dispatch<any>(
+      collectionCreateActions.createCollection(data)
+    );
+    if (collection) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Collection has been successfully created.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+      dispatch<any>(updateResources([collection]));
+      dispatch<any>(navigateTo(collection.uuid));
+    }
+  };
+
+export const copyCollection =
+  (data: CopyFormDialogData) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    try {
+      const copyToProject = getResource(data.ownerUuid)(getState().resources);
+      const collection = await dispatch<any>(
+        collectionCopyActions.copyCollection(data)
+      );
+      if (copyToProject && collection) {
+        dispatch<any>(reloadProjectMatchingUuid([copyToProject.uuid]));
+        dispatch(
+          snackbarActions.OPEN_SNACKBAR({
+            message: 'Collection has been copied.',
+            hideDuration: 3000,
+            kind: SnackbarKind.SUCCESS,
+            link: collection.ownerUuid,
+          })
+        );
+      }
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
+
+export const moveCollection =
+  (data: MoveToFormDialogData) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    try {
+      const collection = await dispatch<any>(
+        collectionMoveActions.moveCollection(data)
+      );
+      dispatch<any>(updateResources([collection]));
+      dispatch<any>(reloadProjectMatchingUuid([collection.ownerUuid]));
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Collection has been moved.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
 
 export const loadProcess = (uuid: string) =>
-    handleFirstTimeLoad(
-        async (dispatch: Dispatch, getState: () => RootState) => {
-            dispatch<any>(loadProcessPanel(uuid));
-            const process = await dispatch<any>(processesActions.loadProcess(uuid));
-            await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
-            dispatch<any>(setProcessBreadcrumbs(uuid));
-            dispatch<any>(loadDetailsPanel(uuid));
-        });
+  handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState) => {
+    dispatch<any>(loadProcessPanel(uuid));
+    const process = await dispatch<any>(processesActions.loadProcess(uuid));
+    await dispatch<any>(
+      activateSidePanelTreeItem(process.containerRequest.ownerUuid)
+    );
+    dispatch<any>(setProcessBreadcrumbs(uuid));
+    dispatch<any>(loadDetailsPanel(uuid));
+  });
+
+export const updateProcess =
+  (data: processUpdateActions.ProcessUpdateFormDialogData) =>
+  async (dispatch: Dispatch) => {
+    try {
+      const process = await dispatch<any>(
+        processUpdateActions.updateProcess(data)
+      );
+      if (process) {
+        dispatch(
+          snackbarActions.OPEN_SNACKBAR({
+            message: 'Process has been successfully updated.',
+            hideDuration: 2000,
+            kind: SnackbarKind.SUCCESS,
+          })
+        );
+        dispatch<any>(updateResources([process]));
+        dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+      }
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
 
-export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialogData) =>
-    async (dispatch: Dispatch) => {
-        try {
-            const process = await dispatch<any>(processUpdateActions.updateProcess(data));
-            if (process) {
-                dispatch(snackbarActions.OPEN_SNACKBAR({
-                    message: "Process has been successfully updated.",
-                    hideDuration: 2000,
-                    kind: SnackbarKind.SUCCESS
-                }));
-                dispatch<any>(updateResources([process]));
-                dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
-            }
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
-
-export const moveProcess = (data: MoveToFormDialogData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        try {
-            const process = await dispatch<any>(processMoveActions.moveProcess(data));
-            dispatch<any>(updateResources([process]));
-            dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been moved.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
-
-export const copyProcess = (data: CopyFormDialogData) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        try {
-            const process = await dispatch<any>(processCopyActions.copyProcess(data));
-            dispatch<any>(updateResources([process]));
-            dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been copied.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
-        } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
-        }
-    };
+export const moveProcess =
+  (data: MoveToFormDialogData) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    try {
+      const process = await dispatch<any>(processMoveActions.moveProcess(data));
+      dispatch<any>(updateResources([process]));
+      dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Process has been moved.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
+
+export const copyProcess =
+  (data: CopyFormDialogData) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    try {
+      const process = await dispatch<any>(processCopyActions.copyProcess(data));
+      dispatch<any>(updateResources([process]));
+      dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: 'Process has been copied.',
+          hideDuration: 2000,
+          kind: SnackbarKind.SUCCESS,
+        })
+      );
+    } catch (e) {
+      dispatch(
+        snackbarActions.OPEN_SNACKBAR({
+          message: e.message,
+          hideDuration: 2000,
+          kind: SnackbarKind.ERROR,
+        })
+      );
+    }
+  };
 
 export const resourceIsNotLoaded = (uuid: string) =>
-    snackbarActions.OPEN_SNACKBAR({
-        message: `Resource identified by ${uuid} is not loaded.`,
-        kind: SnackbarKind.ERROR
-    });
+  snackbarActions.OPEN_SNACKBAR({
+    message: `Resource identified by ${uuid} is not loaded.`,
+    kind: SnackbarKind.ERROR,
+  });
 
 export const userIsNotAuthenticated = snackbarActions.OPEN_SNACKBAR({
-    message: 'User is not authenticated',
-    kind: SnackbarKind.ERROR
+  message: 'User is not authenticated',
+  kind: SnackbarKind.ERROR,
 });
 
 export const couldNotLoadUser = snackbarActions.OPEN_SNACKBAR({
-    message: 'Could not load user',
-    kind: SnackbarKind.ERROR
+  message: 'Could not load user',
+  kind: SnackbarKind.ERROR,
 });
 
-export const reloadProjectMatchingUuid = (matchingUuids: string[]) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
-        if (currentProjectPanelUuid && matchingUuids.some(uuid => uuid === currentProjectPanelUuid)) {
-            dispatch<any>(loadProject(currentProjectPanelUuid));
-        }
-    };
+export const reloadProjectMatchingUuid =
+  (matchingUuids: string[]) =>
+  async (
+    dispatch: Dispatch,
+    getState: () => RootState,
+    services: ServiceRepository
+  ) => {
+    const currentProjectPanelUuid = getProjectPanelCurrentUuid(getState());
+    if (
+      currentProjectPanelUuid &&
+      matchingUuids.some((uuid) => uuid === currentProjectPanelUuid)
+    ) {
+      dispatch<any>(loadProject(currentProjectPanelUuid));
+    }
+  };
 
-export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) => {
+export const loadSharedWithMe = handleFirstTimeLoad(
+  async (dispatch: Dispatch) => {
     dispatch<any>(loadSharedWithMePanel());
-    await dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
-    await dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
-});
+    await dispatch<any>(
+      activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME)
+    );
+    await dispatch<any>(
+      setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME)
+    );
+  }
+);
 
 export const loadRunProcess = handleFirstTimeLoad(
-    async (dispatch: Dispatch) => {
-        await dispatch<any>(loadRunProcessPanel());
-    }
+  async (dispatch: Dispatch) => {
+    await dispatch<any>(loadRunProcessPanel());
+  }
 );
 
 export const loadPublicFavorites = () =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch) => {
-            dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES));
-            dispatch<any>(loadPublicFavoritePanel());
-            dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES));
-        });
+  handleFirstTimeLoad((dispatch: Dispatch) => {
+    dispatch<any>(
+      activateSidePanelTreeItem(SidePanelTreeCategory.PUBLIC_FAVORITES)
+    );
+    dispatch<any>(loadPublicFavoritePanel());
+    dispatch<any>(
+      setSidePanelBreadcrumbs(SidePanelTreeCategory.PUBLIC_FAVORITES)
+    );
+  });
 
 export const loadSearchResults = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadSearchResultsPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadSearchResultsPanel());
+  }
+);
 
 export const loadLinks = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadLinkPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadLinkPanel());
+  }
+);
 
 export const loadVirtualMachines = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadVirtualMachinesPanel());
-        dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadVirtualMachinesPanel());
+    dispatch(setBreadcrumbs([{ label: 'Virtual Machines' }]));
+  }
+);
 
 export const loadVirtualMachinesAdmin = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadVirtualMachinesPanel());
-        dispatch(setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }]));
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadVirtualMachinesPanel());
+    dispatch(
+      setBreadcrumbs([{ label: 'Virtual Machines Admin', icon: AdminMenuIcon }])
+    );
+  }
+);
 
 export const loadRepositories = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadRepositoriesPanel());
-        dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadRepositoriesPanel());
+    dispatch(setBreadcrumbs([{ label: 'Repositories' }]));
+  }
+);
 
 export const loadSshKeys = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadSshKeysPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadSshKeysPanel());
+  }
+);
 
 export const loadSiteManager = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadSiteManagerPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadSiteManagerPanel());
+  }
+);
 
 export const loadUserProfile = (userUuid?: string) =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch<any>) => {
-            if (userUuid) {
-                dispatch(setUserProfileBreadcrumbs(userUuid));
-                dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
-            } else {
-                dispatch(setMyAccountBreadcrumbs());
-                dispatch(userProfilePanelActions.loadUserProfilePanel());
-            }
-        }
-    );
+  handleFirstTimeLoad((dispatch: Dispatch<any>) => {
+    if (userUuid) {
+      dispatch(setUserProfileBreadcrumbs(userUuid));
+      dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
+    } else {
+      dispatch(setMyAccountBreadcrumbs());
+      dispatch(userProfilePanelActions.loadUserProfilePanel());
+    }
+  });
 
 export const loadLinkAccount = handleFirstTimeLoad(
-    (dispatch: Dispatch<any>) => {
-        dispatch(loadLinkAccountPanel());
-    });
+  (dispatch: Dispatch<any>) => {
+    dispatch(loadLinkAccountPanel());
+  }
+);
 
 export const loadKeepServices = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadKeepServicesPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadKeepServicesPanel());
+  }
+);
 
 export const loadUsers = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadUsersPanel());
-        dispatch(setUsersBreadcrumbs());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadUsersPanel());
+    dispatch(setUsersBreadcrumbs());
+  }
+);
 
 export const loadApiClientAuthorizations = handleFirstTimeLoad(
-    async (dispatch: Dispatch<any>) => {
-        await dispatch(loadApiClientAuthorizationsPanel());
-    });
+  async (dispatch: Dispatch<any>) => {
+    await dispatch(loadApiClientAuthorizationsPanel());
+  }
+);
 
 export const loadGroupsPanel = handleFirstTimeLoad(
-    (dispatch: Dispatch<any>) => {
-        dispatch(setGroupsBreadcrumbs());
-        dispatch(groupPanelActions.loadGroupsPanel());
-    });
-
+  (dispatch: Dispatch<any>) => {
+    dispatch(setGroupsBreadcrumbs());
+    dispatch(groupPanelActions.loadGroupsPanel());
+  }
+);
 
 export const loadGroupDetailsPanel = (groupUuid: string) =>
-    handleFirstTimeLoad(
-        (dispatch: Dispatch<any>) => {
-            dispatch(setGroupDetailsBreadcrumbs(groupUuid));
-            dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
-        });
-
-const finishLoadingProject = (project: GroupContentsResource | string) =>
-    async (dispatch: Dispatch<any>) => {
-        const uuid = typeof project === 'string' ? project : project.uuid;
-        dispatch(openProjectPanel(uuid));
-        dispatch(loadDetailsPanel(uuid));
-        if (typeof project !== 'string') {
-            dispatch(updateResources([project]));
-        }
-    };
+  handleFirstTimeLoad((dispatch: Dispatch<any>) => {
+    dispatch(setGroupDetailsBreadcrumbs(groupUuid));
+    dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
+  });
+
+const finishLoadingProject =
+  (project: GroupContentsResource | string) =>
+  async (dispatch: Dispatch<any>) => {
+    const uuid = typeof project === 'string' ? project : project.uuid;
+    dispatch(openProjectPanel(uuid));
+    dispatch(loadDetailsPanel(uuid));
+    if (typeof project !== 'string') {
+      dispatch(updateResources([project]));
+    }
+  };
 
 const loadGroupContentsResource = async (params: {
-    uuid: string,
-    userUuid: string,
-    services: ServiceRepository
+  uuid: string;
+  userUuid: string;
+  services: ServiceRepository;
 }) => {
-    const filters = new FilterBuilder()
-        .addEqual('uuid', params.uuid)
-        .getFilters();
-    const { items } = await params.services.groupsService.contents(params.userUuid, {
-        filters,
-        recursive: true,
-        includeTrash: true,
-    });
-    const resource = items.shift();
-    let handler: GroupContentsHandler;
-    if (resource) {
-        handler = (resource.kind === ResourceKind.COLLECTION || resource.kind === ResourceKind.PROJECT) && resource.isTrashed
-            ? groupContentsHandlers.TRASHED(resource)
-            : groupContentsHandlers.OWNED(resource);
+  const filters = new FilterBuilder()
+    .addEqual('uuid', params.uuid)
+    .getFilters();
+  const { items } = await params.services.groupsService.contents(
+    params.userUuid,
+    {
+      filters,
+      recursive: true,
+      includeTrash: true,
+    }
+  );
+  const resource = items.shift();
+  let handler: GroupContentsHandler;
+  if (resource) {
+    handler =
+      (resource.kind === ResourceKind.COLLECTION ||
+        resource.kind === ResourceKind.PROJECT) &&
+      resource.isTrashed
+        ? groupContentsHandlers.TRASHED(resource)
+        : groupContentsHandlers.OWNED(resource);
+  } else {
+    const kind = extractUuidKind(params.uuid);
+    let resource: GroupContentsResource;
+    if (kind === ResourceKind.COLLECTION) {
+      resource = await params.services.collectionService.get(params.uuid);
+    } else if (kind === ResourceKind.PROJECT) {
+      resource = await params.services.projectService.get(params.uuid);
     } else {
-        const kind = extractUuidKind(params.uuid);
-        let resource: GroupContentsResource;
-        if (kind === ResourceKind.COLLECTION) {
-            resource = await params.services.collectionService.get(params.uuid);
-        } else if (kind === ResourceKind.PROJECT) {
-            resource = await params.services.projectService.get(params.uuid);
-        } else {
-            resource = await params.services.containerRequestService.get(params.uuid);
-        }
-        handler = groupContentsHandlers.SHARED(resource);
+      resource = await params.services.containerRequestService.get(params.uuid);
     }
-    return (cases: MatchCases<typeof groupContentsHandlersRecord, GroupContentsHandler, void>) =>
-        groupContentsHandlers.match(handler, cases);
-
+    handler = groupContentsHandlers.SHARED(resource);
+  }
+  return (
+    cases: MatchCases<
+      typeof groupContentsHandlersRecord,
+      GroupContentsHandler,
+      void
+    >
+  ) => groupContentsHandlers.match(handler, cases);
 };
 
 const groupContentsHandlersRecord = {
-    TRASHED: ofType<GroupContentsResource>(),
-    SHARED: ofType<GroupContentsResource>(),
-    OWNED: ofType<GroupContentsResource>(),
+  TRASHED: ofType<GroupContentsResource>(),
+  SHARED: ofType<GroupContentsResource>(),
+  OWNED: ofType<GroupContentsResource>(),
 };
 
 const groupContentsHandlers = unionize(groupContentsHandlersRecord);
diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx
index 4d552339..79f530a0 100644
--- a/src/views-components/data-explorer/data-explorer.tsx
+++ b/src/views-components/data-explorer/data-explorer.tsx
@@ -23,6 +23,7 @@ interface Props {
 
 const mapStateToProps = (state: RootState, { id }: Props) => {
     // console.log('DATA_EXPLORER, MSTP GLOBAL STATE:', state)
+    const test = 'foo'
     const progress = state.progressIndicator.find(p => p.id === id);
     const dataExplorerState = getDataExplorer(state.dataExplorer, id);
     const currentRoute = state.router.location ? state.router.location.pathname : '';
@@ -30,6 +31,7 @@ const mapStateToProps = (state: RootState, { id }: Props) => {
     const currentItemUuid = currentRoute === '/workflows' ? state.properties.workflowPanelDetailsUuid : state.detailsPanel.resourceUuid;
 // console.log('DATA_EXPLORER, MSTP FILTERED:', {...dataExplorerState})
     return {
+        foo: test,
         ...dataExplorerState,
         working: !!progress?.working,
         currentRefresh: currentRefresh,
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index d9d14ae3..032b73e1 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -64,7 +64,8 @@ export enum ProjectPanelColumnNames {
     TYPE = "Type",
     OWNER = "Owner",
     FILE_SIZE = "File size",
-    LAST_MODIFIED = "Last modified"
+    LAST_MODIFIED = "Last modified",
+    UUID = "UUID"
 }
 
 export interface ProjectPanelFilter extends DataTableFilterItem {
@@ -116,6 +117,14 @@ export const projectPanelColumns: DataColumns<string> = [
         sortDirection: SortDirection.DESC,
         filters: createTree(),
         render: uuid => <ResourceLastModifiedDate uuid={uuid} />
+    },
+    {
+        name: ProjectPanelColumnNames.UUID,
+        selected: true,
+        configurable: true,
+        sortDirection: SortDirection.DESC,
+        filters: createTree(),
+        render: uuid =><>{uuid}</>
     }
 ];
 

commit 4fd42aef3c07c9e4c43bf873afe45e0043755ba0
Author: Lisa Knox <lisaknox83 at gmail.com>
Date:   Sat Nov 19 17:19:59 2022 -0500

    19690: dummy option now displays in colimn-selector
    
    Arvados-DCO-1.1-Signed-off-by: Lisa Knox <lisa.knox at curii.com>

diff --git a/public/config.json b/public/config.json
new file mode 100644
index 00000000..01af6d67
--- /dev/null
+++ b/public/config.json
@@ -0,0 +1,3 @@
+{
+  "API_HOST":"tordo.arvadosapi.com"
+}
\ No newline at end of file
diff --git a/src/common/config.ts b/src/common/config.ts
index 574445df..724fb795 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -2,321 +2,352 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import Axios from "axios";
+import Axios from 'axios';
 
-export const WORKBENCH_CONFIG_URL = process.env.REACT_APP_ARVADOS_CONFIG_URL || "/config.json";
+export const WORKBENCH_CONFIG_URL =
+  process.env.REACT_APP_ARVADOS_CONFIG_URL || '/config.json';
 
 interface WorkbenchConfig {
-    API_HOST: string;
-    VOCABULARY_URL?: string;
-    FILE_VIEWERS_CONFIG_URL?: string;
+  API_HOST: string;
+  VOCABULARY_URL?: string;
+  FILE_VIEWERS_CONFIG_URL?: string;
 }
 
 export interface ClusterConfigJSON {
-    API: {
-        UnfreezeProjectRequiresAdmin: boolean
-    },
-    ClusterID: string;
-    RemoteClusters: {
-        [key: string]: {
-            ActivateUsers: boolean
-            Host: string
-            Insecure: boolean
-            Proxy: boolean
-            Scheme: string
-        }
+  API: {
+    UnfreezeProjectRequiresAdmin: boolean;
+  };
+  ClusterID: string;
+  RemoteClusters: {
+    [key: string]: {
+      ActivateUsers: boolean;
+      Host: string;
+      Insecure: boolean;
+      Proxy: boolean;
+      Scheme: string;
     };
-    Mail?: {
-        SupportEmailAddress: string;
+  };
+  Mail?: {
+    SupportEmailAddress: string;
+  };
+  Services: {
+    Controller: {
+      ExternalURL: string;
     };
-    Services: {
-        Controller: {
-            ExternalURL: string
-        }
-        Workbench1: {
-            ExternalURL: string
-        }
-        Workbench2: {
-            ExternalURL: string
-        }
-        Websocket: {
-            ExternalURL: string
-        }
-        WebDAV: {
-            ExternalURL: string
-        },
-        WebDAVDownload: {
-            ExternalURL: string
-        },
-        WebShell: {
-            ExternalURL: string
-        }
+    Workbench1: {
+      ExternalURL: string;
     };
-    Workbench: {
-        DisableSharingURLsUI: boolean;
-        ArvadosDocsite: string;
-        FileViewersConfigURL: string;
-        WelcomePageHTML: string;
-        InactivePageHTML: string;
-        SSHHelpPageHTML: string;
-        SSHHelpHostSuffix: string;
-        SiteName: string;
-        IdleTimeout: string;
+    Workbench2: {
+      ExternalURL: string;
     };
-    Login: {
-        LoginCluster: string;
-        Google: {
-            Enable: boolean;
-        }
-        LDAP: {
-            Enable: boolean;
-        }
-        OpenIDConnect: {
-            Enable: boolean;
-        }
-        PAM: {
-            Enable: boolean;
-        }
-        SSO: {
-            Enable: boolean;
-        }
-        Test: {
-            Enable: boolean;
-        }
+    Websocket: {
+      ExternalURL: string;
     };
-    Collections: {
-        ForwardSlashNameSubstitution: string;
-        ManagedProperties?: {
-            [key: string]: {
-                Function: string,
-                Value: string,
-                Protected?: boolean,
-            }
-        },
-        TrustAllContent: boolean
+    WebDAV: {
+      ExternalURL: string;
     };
-    Volumes: {
-        [key: string]: {
-            StorageClasses: {
-                [key: string]: boolean;
-            }
-        }
+    WebDAVDownload: {
+      ExternalURL: string;
+    };
+    WebShell: {
+      ExternalURL: string;
+    };
+  };
+  Workbench: {
+    DisableSharingURLsUI: boolean;
+    ArvadosDocsite: string;
+    FileViewersConfigURL: string;
+    WelcomePageHTML: string;
+    InactivePageHTML: string;
+    SSHHelpPageHTML: string;
+    SSHHelpHostSuffix: string;
+    SiteName: string;
+    IdleTimeout: string;
+  };
+  Login: {
+    LoginCluster: string;
+    Google: {
+      Enable: boolean;
+    };
+    LDAP: {
+      Enable: boolean;
+    };
+    OpenIDConnect: {
+      Enable: boolean;
     };
+    PAM: {
+      Enable: boolean;
+    };
+    SSO: {
+      Enable: boolean;
+    };
+    Test: {
+      Enable: boolean;
+    };
+  };
+  Collections: {
+    ForwardSlashNameSubstitution: string;
+    ManagedProperties?: {
+      [key: string]: {
+        Function: string;
+        Value: string;
+        Protected?: boolean;
+      };
+    };
+    TrustAllContent: boolean;
+  };
+  Volumes: {
+    [key: string]: {
+      StorageClasses: {
+        [key: string]: boolean;
+      };
+    };
+  };
 }
 
 export class Config {
-    baseUrl!: string;
-    keepWebServiceUrl!: string;
-    keepWebInlineServiceUrl!: string;
-    remoteHosts!: {
-        [key: string]: string
-    };
-    rootUrl!: string;
-    uuidPrefix!: string;
-    websocketUrl!: string;
-    workbenchUrl!: string;
-    workbench2Url!: string;
-    vocabularyUrl!: string;
-    fileViewersConfigUrl!: string;
-    loginCluster!: string;
-    clusterConfig!: ClusterConfigJSON;
-    apiRevision!: number;
+  baseUrl!: string;
+  keepWebServiceUrl!: string;
+  keepWebInlineServiceUrl!: string;
+  remoteHosts!: {
+    [key: string]: string;
+  };
+  rootUrl!: string;
+  uuidPrefix!: string;
+  websocketUrl!: string;
+  workbenchUrl!: string;
+  workbench2Url!: string;
+  vocabularyUrl!: string;
+  fileViewersConfigUrl!: string;
+  loginCluster!: string;
+  clusterConfig!: ClusterConfigJSON;
+  apiRevision!: number;
 }
 
 export const buildConfig = (clusterConfig: ClusterConfigJSON): Config => {
-    const clusterConfigJSON = removeTrailingSlashes(clusterConfig);
-    const config = new Config();
-    config.rootUrl = clusterConfigJSON.Services.Controller.ExternalURL;
-    config.baseUrl = `${config.rootUrl}/${ARVADOS_API_PATH}`;
-    config.uuidPrefix = clusterConfigJSON.ClusterID;
-    config.websocketUrl = clusterConfigJSON.Services.Websocket.ExternalURL;
-    config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL;
-    config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL;
-    config.keepWebServiceUrl = clusterConfigJSON.Services.WebDAVDownload.ExternalURL;
-    config.keepWebInlineServiceUrl = clusterConfigJSON.Services.WebDAV.ExternalURL;
-    config.loginCluster = clusterConfigJSON.Login.LoginCluster;
-    config.clusterConfig = clusterConfigJSON;
-    config.apiRevision = 0;
-    mapRemoteHosts(clusterConfigJSON, config);
-    return config;
+  const clusterConfigJSON = removeTrailingSlashes(clusterConfig);
+  const config = new Config();
+  config.rootUrl = clusterConfigJSON.Services.Controller.ExternalURL;
+  config.baseUrl = `${config.rootUrl}/${ARVADOS_API_PATH}`;
+  config.uuidPrefix = clusterConfigJSON.ClusterID;
+  config.websocketUrl = clusterConfigJSON.Services.Websocket.ExternalURL;
+  config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL;
+  config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL;
+  config.keepWebServiceUrl =
+    clusterConfigJSON.Services.WebDAVDownload.ExternalURL;
+  config.keepWebInlineServiceUrl =
+    clusterConfigJSON.Services.WebDAV.ExternalURL;
+  config.loginCluster = clusterConfigJSON.Login.LoginCluster;
+  config.clusterConfig = clusterConfigJSON;
+  config.apiRevision = 0;
+  mapRemoteHosts(clusterConfigJSON, config);
+  return config;
 };
 
 export const getStorageClasses = (config: Config): string[] => {
-    const classes: Set<string> = new Set(['default']);
-    const volumes = config.clusterConfig.Volumes;
-    Object.keys(volumes).forEach(v => {
-        Object.keys(volumes[v].StorageClasses || {}).forEach(sc => {
-            if (volumes[v].StorageClasses[sc]) {
-                classes.add(sc);
-            }
-        });
+  const classes: Set<string> = new Set(['default']);
+  const volumes = config.clusterConfig.Volumes;
+  Object.keys(volumes).forEach((v) => {
+    Object.keys(volumes[v].StorageClasses || {}).forEach((sc) => {
+      if (volumes[v].StorageClasses[sc]) {
+        classes.add(sc);
+      }
     });
-    return Array.from(classes);
+  });
+  return Array.from(classes);
 };
 
 const getApiRevision = async (apiUrl: string) => {
-    try {
-        const dd = (await Axios.get<any>(`${apiUrl}/${DISCOVERY_DOC_PATH}`)).data;
-        return parseInt(dd.revision, 10) || 0;
-    } catch {
-        console.warn("Unable to get API Revision number, defaulting to zero. Some features may not work properly.");
-        return 0;
-    }
+  try {
+    const dd = (await Axios.get<any>(`${apiUrl}/${DISCOVERY_DOC_PATH}`)).data;
+    return parseInt(dd.revision, 10) || 0;
+  } catch {
+    console.warn(
+      'Unable to get API Revision number, defaulting to zero. Some features may not work properly.'
+    );
+    return 0;
+  }
 };
 
-const removeTrailingSlashes = (config: ClusterConfigJSON): ClusterConfigJSON => {
-    const svcs: any = {};
-    Object.keys(config.Services).forEach((s) => {
-        svcs[s] = config.Services[s];
-        if (svcs[s].hasOwnProperty('ExternalURL')) {
-            svcs[s].ExternalURL = svcs[s].ExternalURL.replace(/\/+$/, '');
-        }
-    });
-    return { ...config, Services: svcs };
+const removeTrailingSlashes = (
+  config: ClusterConfigJSON
+): ClusterConfigJSON => {
+  const svcs: any = {};
+  Object.keys(config.Services).forEach((s) => {
+    svcs[s] = config.Services[s];
+    if (svcs[s].hasOwnProperty('ExternalURL')) {
+      svcs[s].ExternalURL = svcs[s].ExternalURL.replace(/\/+$/, '');
+    }
+  });
+  return { ...config, Services: svcs };
 };
 
 export const fetchConfig = () => {
-    return Axios
-        .get<WorkbenchConfig>(WORKBENCH_CONFIG_URL + "?nocache=" + (new Date()).getTime())
-        .then(response => response.data)
-        .catch(() => {
-            console.warn(`There was an exception getting the Workbench config file at ${WORKBENCH_CONFIG_URL}. Using defaults instead.`);
-            return Promise.resolve(getDefaultConfig());
-        })
-        .then(workbenchConfig => {
-            if (workbenchConfig.API_HOST === undefined) {
-                throw new Error(`Unable to start Workbench. API_HOST is undefined in ${WORKBENCH_CONFIG_URL} or the environment.`);
-            }
-            return Axios.get<ClusterConfigJSON>(getClusterConfigURL(workbenchConfig.API_HOST)).then(async response => {
-                const apiRevision = await getApiRevision(response.data.Services.Controller.ExternalURL.replace(/\/+$/, ''));
-                const config = { ...buildConfig(response.data), apiRevision };
-                const warnLocalConfig = (varName: string) => console.warn(
-                    `A value for ${varName} was found in ${WORKBENCH_CONFIG_URL}. To use the Arvados centralized configuration instead, \
-remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`);
+  return Axios.get<WorkbenchConfig>(
+    WORKBENCH_CONFIG_URL + '?nocache=' + new Date().getTime()
+  )
+    .then((response) => response.data)
+    .catch(() => {
+      console.warn(
+        `There was an exception getting the Workbench config file at ${WORKBENCH_CONFIG_URL}. Using defaults instead.`
+      );
+      return Promise.resolve(getDefaultConfig());
+    })
+    .then((workbenchConfig) => {
+      if (workbenchConfig.API_HOST === undefined) {
+        throw new Error(
+          `Unable to start Workbench. API_HOST is undefined in ${WORKBENCH_CONFIG_URL} or the environment.`
+        );
+      }
+      return Axios.get<ClusterConfigJSON>(
+        getClusterConfigURL(workbenchConfig.API_HOST)
+      ).then(async (response) => {
+        const apiRevision = await getApiRevision(
+          response.data.Services.Controller.ExternalURL.replace(/\/+$/, '')
+        );
+        const config = { ...buildConfig(response.data), apiRevision };
+        const warnLocalConfig = (varName: string) =>
+          console.warn(
+            `A value for ${varName} was found in ${WORKBENCH_CONFIG_URL}. To use the Arvados centralized configuration instead, \
+remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`
+          );
 
-                // Check if the workbench config has an entry for vocabulary and file viewer URLs
-                // If so, use these values (even if it is an empty string), but print a console warning.
-                // Otherwise, use the cluster config.
-                let fileViewerConfigUrl;
-                if (workbenchConfig.FILE_VIEWERS_CONFIG_URL !== undefined) {
-                    warnLocalConfig("FILE_VIEWERS_CONFIG_URL");
-                    fileViewerConfigUrl = workbenchConfig.FILE_VIEWERS_CONFIG_URL;
-                }
-                else {
-                    fileViewerConfigUrl = config.clusterConfig.Workbench.FileViewersConfigURL || "/file-viewers-example.json";
-                }
-                config.fileViewersConfigUrl = fileViewerConfigUrl;
+        // Check if the workbench config has an entry for vocabulary and file viewer URLs
+        // If so, use these values (even if it is an empty string), but print a console warning.
+        // Otherwise, use the cluster config.
+        let fileViewerConfigUrl;
+        if (workbenchConfig.FILE_VIEWERS_CONFIG_URL !== undefined) {
+          warnLocalConfig('FILE_VIEWERS_CONFIG_URL');
+          fileViewerConfigUrl = workbenchConfig.FILE_VIEWERS_CONFIG_URL;
+        } else {
+          fileViewerConfigUrl =
+            config.clusterConfig.Workbench.FileViewersConfigURL ||
+            '/file-viewers-example.json';
+        }
+        config.fileViewersConfigUrl = fileViewerConfigUrl;
 
-                if (workbenchConfig.VOCABULARY_URL !== undefined) {
-                    console.warn(`A value for VOCABULARY_URL was found in ${WORKBENCH_CONFIG_URL}. It will be ignored as the cluster already provides its own endpoint, you can safely remove it.`)
-                }
-                config.vocabularyUrl = getVocabularyURL(workbenchConfig.API_HOST);
+        if (workbenchConfig.VOCABULARY_URL !== undefined) {
+          console.warn(
+            `A value for VOCABULARY_URL was found in ${WORKBENCH_CONFIG_URL}. It will be ignored as the cluster already provides its own endpoint, you can safely remove it.`
+          );
+        }
+        config.vocabularyUrl = getVocabularyURL(workbenchConfig.API_HOST);
 
-                return { config, apiHost: workbenchConfig.API_HOST };
-            });
-        });
+        return { config, apiHost: workbenchConfig.API_HOST };
+      });
+    });
 };
 
 // Maps remote cluster hosts and removes the default RemoteCluster entry
-export const mapRemoteHosts = (clusterConfigJSON: ClusterConfigJSON, config: Config) => {
-    config.remoteHosts = {};
-    Object.keys(clusterConfigJSON.RemoteClusters).forEach(k => { config.remoteHosts[k] = clusterConfigJSON.RemoteClusters[k].Host; });
-    delete config.remoteHosts["*"];
+export const mapRemoteHosts = (
+  clusterConfigJSON: ClusterConfigJSON,
+  config: Config
+) => {
+  config.remoteHosts = {};
+  Object.keys(clusterConfigJSON.RemoteClusters).forEach((k) => {
+    config.remoteHosts[k] = clusterConfigJSON.RemoteClusters[k].Host;
+  });
+  delete config.remoteHosts['*'];
 };
 
-export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): ClusterConfigJSON => ({
-    API: {
-        UnfreezeProjectRequiresAdmin: false,
+export const mockClusterConfigJSON = (
+  config: Partial<ClusterConfigJSON>
+): ClusterConfigJSON => ({
+  API: {
+    UnfreezeProjectRequiresAdmin: false,
+  },
+  ClusterID: '',
+  RemoteClusters: {},
+  Services: {
+    Controller: { ExternalURL: '' },
+    Workbench1: { ExternalURL: '' },
+    Workbench2: { ExternalURL: '' },
+    Websocket: { ExternalURL: '' },
+    WebDAV: { ExternalURL: '' },
+    WebDAVDownload: { ExternalURL: '' },
+    WebShell: { ExternalURL: '' },
+  },
+  Workbench: {
+    DisableSharingURLsUI: false,
+    ArvadosDocsite: '',
+    FileViewersConfigURL: '',
+    WelcomePageHTML: '',
+    InactivePageHTML: '',
+    SSHHelpPageHTML: '',
+    SSHHelpHostSuffix: '',
+    SiteName: '',
+    IdleTimeout: '0s',
+  },
+  Login: {
+    LoginCluster: '',
+    Google: {
+      Enable: false,
     },
-    ClusterID: "",
-    RemoteClusters: {},
-    Services: {
-        Controller: { ExternalURL: "" },
-        Workbench1: { ExternalURL: "" },
-        Workbench2: { ExternalURL: "" },
-        Websocket: { ExternalURL: "" },
-        WebDAV: { ExternalURL: "" },
-        WebDAVDownload: { ExternalURL: "" },
-        WebShell: { ExternalURL: "" },
+    LDAP: {
+      Enable: false,
     },
-    Workbench: {
-        DisableSharingURLsUI: false,
-        ArvadosDocsite: "",
-        FileViewersConfigURL: "",
-        WelcomePageHTML: "",
-        InactivePageHTML: "",
-        SSHHelpPageHTML: "",
-        SSHHelpHostSuffix: "",
-        SiteName: "",
-        IdleTimeout: "0s",
+    OpenIDConnect: {
+      Enable: false,
     },
-    Login: {
-        LoginCluster: "",
-        Google: {
-            Enable: false,
-        },
-        LDAP: {
-            Enable: false,
-        },
-        OpenIDConnect: {
-            Enable: false,
-        },
-        PAM: {
-            Enable: false,
-        },
-        SSO: {
-            Enable: false,
-        },
-        Test: {
-            Enable: false,
-        },
+    PAM: {
+      Enable: false,
     },
-    Collections: {
-        ForwardSlashNameSubstitution: "",
-        TrustAllContent: false,
+    SSO: {
+      Enable: false,
     },
-    Volumes: {},
-    ...config
+    Test: {
+      Enable: false,
+    },
+  },
+  Collections: {
+    ForwardSlashNameSubstitution: '',
+    TrustAllContent: false,
+  },
+  Volumes: {},
+  ...config,
 });
 
 export const mockConfig = (config: Partial<Config>): Config => ({
-    baseUrl: "",
-    keepWebServiceUrl: "",
-    keepWebInlineServiceUrl: "",
-    remoteHosts: {},
-    rootUrl: "",
-    uuidPrefix: "",
-    websocketUrl: "",
-    workbenchUrl: "",
-    workbench2Url: "",
-    vocabularyUrl: "",
-    fileViewersConfigUrl: "",
-    loginCluster: "",
-    clusterConfig: mockClusterConfigJSON({}),
-    apiRevision: 0,
-    ...config
+  baseUrl: '',
+  keepWebServiceUrl: '',
+  keepWebInlineServiceUrl: '',
+  remoteHosts: {},
+  rootUrl: '',
+  uuidPrefix: '',
+  websocketUrl: '',
+  workbenchUrl: '',
+  workbench2Url: '',
+  vocabularyUrl: '',
+  fileViewersConfigUrl: '',
+  loginCluster: '',
+  clusterConfig: mockClusterConfigJSON({}),
+  apiRevision: 0,
+  ...config,
 });
 
 const getDefaultConfig = (): WorkbenchConfig => {
-    let apiHost = "";
-    const envHost = process.env.REACT_APP_ARVADOS_API_HOST;
-    if (envHost !== undefined) {
-        console.warn(`Using default API host ${envHost}.`);
-        apiHost = envHost;
-    }
-    else {
-        console.warn(`No API host was found in the environment. Workbench may not be able to communicate with Arvados components.`);
-    }
-    return {
-        API_HOST: apiHost,
-        VOCABULARY_URL: undefined,
-        FILE_VIEWERS_CONFIG_URL: undefined,
-    };
+  let apiHost = '';
+  const envHost = process.env.REACT_APP_ARVADOS_API_HOST;
+  if (envHost !== undefined) {
+    console.warn(`Using default API host ${envHost}.`);
+    apiHost = envHost;
+  } else {
+    console.warn(
+      `No API host was found in the environment. Workbench may not be able to communicate with Arvados components.`
+    );
+  }
+  return {
+    API_HOST: apiHost,
+    VOCABULARY_URL: undefined,
+    FILE_VIEWERS_CONFIG_URL: undefined,
+  };
 };
 
-export const ARVADOS_API_PATH = "arvados/v1";
-export const CLUSTER_CONFIG_PATH = "arvados/v1/config";
-export const VOCABULARY_PATH = "arvados/v1/vocabulary";
-export const DISCOVERY_DOC_PATH = "discovery/v1/apis/arvados/v1/rest";
-export const getClusterConfigURL = (apiHost: string) => `https://${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`;
-export const getVocabularyURL = (apiHost: string) => `https://${apiHost}/${VOCABULARY_PATH}?nocache=${(new Date()).getTime()}`;
+export const ARVADOS_API_PATH = 'arvados/v1';
+export const CLUSTER_CONFIG_PATH = 'arvados/v1/config';
+export const VOCABULARY_PATH = 'arvados/v1/vocabulary';
+export const DISCOVERY_DOC_PATH = 'discovery/v1/apis/arvados/v1/rest';
+export const getClusterConfigURL = (apiHost: string) =>
+  `https://${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${new Date().getTime()}`;
+export const getVocabularyURL = (apiHost: string) =>
+  `https://${apiHost}/${VOCABULARY_PATH}?nocache=${new Date().getTime()}`;
diff --git a/src/components/column-selector/column-selector.tsx b/src/components/column-selector/column-selector.tsx
index 5fbef6b6..441d8ec8 100644
--- a/src/components/column-selector/column-selector.tsx
+++ b/src/components/column-selector/column-selector.tsx
@@ -10,6 +10,7 @@ import { Popover } from "../popover/popover";
 import { IconButtonProps } from '@material-ui/core/IconButton';
 import { DataColumns } from '../data-table/data-table';
 import { ArvadosTheme } from "common/custom-theme";
+import { ResourceName } from 'views-components/data-explorer/renderers';
 
 interface ColumnSelectorDataProps {
     columns: DataColumns<any>;
@@ -30,7 +31,15 @@ export type ColumnSelectorProps = ColumnSelectorDataProps & WithStyles<CssRules>
 
 export const ColumnSelector = withStyles(styles)(
     ({ columns, onColumnToggle, classes }: ColumnSelectorProps) =>
-        <Popover triggerComponent={ColumnSelectorTrigger}>
+    {console.log('COLUMN_SELECTOR',columns)
+    columns = [...columns, {
+        name:'bananas',
+        selected: false,
+        configurable: true, filters:{}, render: (uuid)=><ResourceName uuid='uuid'/>
+    }
+]
+    console.log('COLUMN_SELECTOR',columns)
+       return <Popover triggerComponent={ColumnSelectorTrigger}>
             <Paper>
                 <List dense>
                     {columns
@@ -52,7 +61,7 @@ export const ColumnSelector = withStyles(styles)(
                         )}
                 </List>
             </Paper>
-        </Popover>
+        </Popover>}
 );
 
 export const ColumnSelectorTrigger = (props: IconButtonProps) =>
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index c7a296a6..cf45e24f 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -140,6 +140,7 @@ export const DataExplorer = withStyles(styles)(
         }
 
         componentDidMount() {
+            // console.log('DATA_EXPLORER:',this.props)
             if (this.props.onSetColumns) {
                 this.props.onSetColumns(this.props.columns);
             }
diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx
index d942234d..10446d1a 100644
--- a/src/components/data-table/data-table.tsx
+++ b/src/components/data-table/data-table.tsx
@@ -87,6 +87,7 @@ type DataTableProps<T> = DataTableDataProps<T> & WithStyles<CssRules>;
 export const DataTable = withStyles(styles)(
     class Component<T> extends React.Component<DataTableProps<T>> {
         render() {
+            // console.log('DATA_TABLE, RENDER:' , this)
             const { items, classes, working } = this.props;
             return <div className={classes.root}>
                 <div className={classes.content}>
diff --git a/src/store/data-explorer/data-explorer-reducer.ts b/src/store/data-explorer/data-explorer-reducer.ts
index 1e5cd88f..4ae01adf 100644
--- a/src/store/data-explorer/data-explorer-reducer.ts
+++ b/src/store/data-explorer/data-explorer-reducer.ts
@@ -3,134 +3,170 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import {
-    DataColumn,
-    resetSortDirection,
-    SortDirection,
-    toggleSortDirection
-} from "components/data-table/data-column";
-import { DataExplorerAction, dataExplorerActions, DataTableRequestState } from "./data-explorer-action";
-import { DataColumns, DataTableFetchMode } from "components/data-table/data-table";
-import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
+  DataColumn,
+  resetSortDirection,
+  SortDirection,
+  toggleSortDirection,
+} from 'components/data-table/data-column';
+import {
+  DataExplorerAction,
+  dataExplorerActions,
+  DataTableRequestState,
+} from './data-explorer-action';
+import {
+  DataColumns,
+  DataTableFetchMode,
+} from 'components/data-table/data-table';
+import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
 
 export interface DataExplorer {
-    fetchMode: DataTableFetchMode;
-    columns: DataColumns<any>;
-    items: any[];
-    itemsAvailable: number;
-    page: number;
-    rowsPerPage: number;
-    rowsPerPageOptions: number[];
-    searchValue: string;
-    working?: boolean;
-    requestState: DataTableRequestState;
+  fetchMode: DataTableFetchMode;
+  columns: DataColumns<any>;
+  items: any[];
+  itemsAvailable: number;
+  page: number;
+  rowsPerPage: number;
+  rowsPerPageOptions: number[];
+  searchValue: string;
+  working?: boolean;
+  requestState: DataTableRequestState;
 }
 
 export const initialDataExplorer: DataExplorer = {
-    fetchMode: DataTableFetchMode.PAGINATED,
-    columns: [],
-    items: [],
-    itemsAvailable: 0,
-    page: 0,
-    rowsPerPage: 50,
-    rowsPerPageOptions: [10, 20, 50, 100, 200, 500],
-    searchValue: "",
-    requestState: DataTableRequestState.IDLE
+  fetchMode: DataTableFetchMode.PAGINATED,
+  columns: [],
+  items: [],
+  itemsAvailable: 0,
+  page: 0,
+  rowsPerPage: 50,
+  rowsPerPageOptions: [10, 20, 50, 100, 200, 500],
+  searchValue: '',
+  requestState: DataTableRequestState.IDLE,
 };
 
 export type DataExplorerState = Record<string, DataExplorer>;
 
-export const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
-    dataExplorerActions.match(action, {
-        CLEAR: ({ id }) =>
-            update(state, id, explorer => ({ ...explorer, page: 0, itemsAvailable: 0, items: [] })),
-
-        RESET_PAGINATION: ({ id }) =>
-            update(state, id, explorer => ({ ...explorer, page: 0 })),
-
-        SET_FETCH_MODE: ({ id, fetchMode }) =>
-            update(state, id, explorer => ({ ...explorer, fetchMode })),
-
-        SET_COLUMNS: ({ id, columns }) =>
-            update(state, id, setColumns(columns)),
-
-        SET_FILTERS: ({ id, columnName, filters }) =>
-            update(state, id, mapColumns(setFilters(columnName, filters))),
-
-        SET_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
-            update(state, id, explorer => ({ ...explorer, items, itemsAvailable, page: page || 0, rowsPerPage })),
-
-        APPEND_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
-            update(state, id, explorer => ({
-                ...explorer,
-                items: state[id].items.concat(items),
-                itemsAvailable: state[id].itemsAvailable + itemsAvailable,
-                page,
-                rowsPerPage
-            })),
-
-        SET_PAGE: ({ id, page }) =>
-            update(state, id, explorer => ({ ...explorer, page })),
-
-        SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) =>
-            update(state, id, explorer => ({ ...explorer, rowsPerPage })),
-
-        SET_EXPLORER_SEARCH_VALUE: ({ id, searchValue }) =>
-            update(state, id, explorer => ({ ...explorer, searchValue })),
-
-        SET_REQUEST_STATE: ({ id, requestState }) =>
-            update(state, id, explorer => ({ ...explorer, requestState })),
-
-        TOGGLE_SORT: ({ id, columnName }) =>
-            update(state, id, mapColumns(toggleSort(columnName))),
-
-        TOGGLE_COLUMN: ({ id, columnName }) =>
-            update(state, id, mapColumns(toggleColumn(columnName))),
-
-        default: () => state
-    });
-
+export const dataExplorerReducer = (
+  state: DataExplorerState = {},
+  action: DataExplorerAction
+) => {
+  //   console.log('DATA_EXPLORERE_REDUCER, satate:', state);
+  return dataExplorerActions.match(action, {
+    CLEAR: ({ id }) =>
+      update(state, id, (explorer) => ({
+        ...explorer,
+        page: 0,
+        itemsAvailable: 0,
+        items: [],
+      })),
+
+    RESET_PAGINATION: ({ id }) =>
+      update(state, id, (explorer) => ({ ...explorer, page: 0 })),
+
+    SET_FETCH_MODE: ({ id, fetchMode }) =>
+      update(state, id, (explorer) => ({ ...explorer, fetchMode })),
+
+    SET_COLUMNS: ({ id, columns }) => update(state, id, setColumns(columns)),
+
+    SET_FILTERS: ({ id, columnName, filters }) =>
+      update(state, id, mapColumns(setFilters(columnName, filters))),
+
+    SET_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
+      update(state, id, (explorer) => ({
+        ...explorer,
+        items,
+        itemsAvailable,
+        page: page || 0,
+        rowsPerPage,
+      })),
+
+    APPEND_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
+      update(state, id, (explorer) => ({
+        ...explorer,
+        items: state[id].items.concat(items),
+        itemsAvailable: state[id].itemsAvailable + itemsAvailable,
+        page,
+        rowsPerPage,
+      })),
+
+    SET_PAGE: ({ id, page }) =>
+      update(state, id, (explorer) => ({ ...explorer, page })),
+
+    SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) =>
+      update(state, id, (explorer) => ({ ...explorer, rowsPerPage })),
+
+    SET_EXPLORER_SEARCH_VALUE: ({ id, searchValue }) =>
+      update(state, id, (explorer) => ({ ...explorer, searchValue })),
+
+    SET_REQUEST_STATE: ({ id, requestState }) =>
+      update(state, id, (explorer) => ({ ...explorer, requestState })),
+
+    TOGGLE_SORT: ({ id, columnName }) =>
+      update(state, id, mapColumns(toggleSort(columnName))),
+
+    TOGGLE_COLUMN: ({ id, columnName }) =>
+      update(state, id, mapColumns(toggleColumn(columnName))),
+
+    default: () => state,
+  });
+};
 export const getDataExplorer = (state: DataExplorerState, id: string) =>
-    state[id] || initialDataExplorer;
-
-export const getSortColumn = (dataExplorer: DataExplorer) => dataExplorer.columns.find((c: any) =>
-    !!c.sortDirection && c.sortDirection !== SortDirection.NONE);
-
-const update = (state: DataExplorerState, id: string, updateFn: (dataExplorer: DataExplorer) => DataExplorer) =>
-    ({ ...state, [id]: updateFn(getDataExplorer(state, id)) });
-
-const canUpdateColumns = (prevColumns: DataColumns<any>, nextColumns: DataColumns<any>) => {
-    if (prevColumns.length !== nextColumns.length) {
-        return true;
-    }
-    for (let i = 0; i < nextColumns.length; i++) {
-        const pc = prevColumns[i];
-        const nc = nextColumns[i];
-        if (pc.key !== nc.key || pc.name !== nc.name) {
-            return true;
-        }
+  state[id] || initialDataExplorer;
+
+export const getSortColumn = (dataExplorer: DataExplorer) =>
+  dataExplorer.columns.find(
+    (c: any) => !!c.sortDirection && c.sortDirection !== SortDirection.NONE
+  );
+
+const update = (
+  state: DataExplorerState,
+  id: string,
+  updateFn: (dataExplorer: DataExplorer) => DataExplorer
+) => ({ ...state, [id]: updateFn(getDataExplorer(state, id)) });
+
+const canUpdateColumns = (
+  prevColumns: DataColumns<any>,
+  nextColumns: DataColumns<any>
+) => {
+  if (prevColumns.length !== nextColumns.length) {
+    return true;
+  }
+  for (let i = 0; i < nextColumns.length; i++) {
+    const pc = prevColumns[i];
+    const nc = nextColumns[i];
+    if (pc.key !== nc.key || pc.name !== nc.name) {
+      return true;
     }
-    return false;
+  }
+  return false;
 };
 
-const setColumns = (columns: DataColumns<any>) =>
-    (dataExplorer: DataExplorer) =>
-        ({ ...dataExplorer, columns: canUpdateColumns(dataExplorer.columns, columns) ? columns : dataExplorer.columns });
-
-const mapColumns = (mapFn: (column: DataColumn<any>) => DataColumn<any>) =>
-    (dataExplorer: DataExplorer) =>
-        ({ ...dataExplorer, columns: dataExplorer.columns.map(mapFn) });
-
-const toggleSort = (columnName: string) =>
-    (column: DataColumn<any>) => column.name === columnName
-        ? toggleSortDirection(column)
-        : resetSortDirection(column);
-
-const toggleColumn = (columnName: string) =>
-    (column: DataColumn<any>) => column.name === columnName
-        ? { ...column, selected: !column.selected }
-        : column;
-
-const setFilters = (columnName: string, filters: DataTableFilters) =>
-    (column: DataColumn<any>) => column.name === columnName
-        ? { ...column, filters }
-        : column;
+const setColumns =
+  (columns: DataColumns<any>) => (dataExplorer: DataExplorer) => ({
+    ...dataExplorer,
+    columns: canUpdateColumns(dataExplorer.columns, columns)
+      ? columns
+      : dataExplorer.columns,
+  });
+
+const mapColumns =
+  (mapFn: (column: DataColumn<any>) => DataColumn<any>) =>
+  (dataExplorer: DataExplorer) => ({
+    ...dataExplorer,
+    columns: dataExplorer.columns.map(mapFn),
+  });
+
+const toggleSort = (columnName: string) => (column: DataColumn<any>) =>
+  column.name === columnName
+    ? toggleSortDirection(column)
+    : resetSortDirection(column);
+
+const toggleColumn = (columnName: string) => (column: DataColumn<any>) =>
+  column.name === columnName
+    ? { ...column, selected: !column.selected }
+    : column;
+
+const setFilters =
+  (columnName: string, filters: DataTableFilters) =>
+  (column: DataColumn<any>) =>
+    column.name === columnName ? { ...column, filters } : column;
diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx
index 06d97038..4d552339 100644
--- a/src/views-components/data-explorer/data-explorer.tsx
+++ b/src/views-components/data-explorer/data-explorer.tsx
@@ -22,12 +22,13 @@ interface Props {
 }
 
 const mapStateToProps = (state: RootState, { id }: Props) => {
+    // console.log('DATA_EXPLORER, MSTP GLOBAL STATE:', state)
     const progress = state.progressIndicator.find(p => p.id === id);
     const dataExplorerState = getDataExplorer(state.dataExplorer, id);
     const currentRoute = state.router.location ? state.router.location.pathname : '';
     const currentRefresh = localStorage.getItem(LAST_REFRESH_TIMESTAMP) || '';
     const currentItemUuid = currentRoute === '/workflows' ? state.properties.workflowPanelDetailsUuid : state.detailsPanel.resourceUuid;
-
+// console.log('DATA_EXPLORER, MSTP FILTERED:', {...dataExplorerState})
     return {
         ...dataExplorerState,
         working: !!progress?.working,

commit 95afa31b982ddf8468642002dded5a3875c95d25
Author: Stephen Smith <stephen at curii.com>
Date:   Wed Nov 2 17:17:18 2022 -0400

    19684: Catch object values in primitive parameters and external values in files/directories, show unsupported value message
    
    Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>

diff --git a/cypress/integration/process.spec.js b/cypress/integration/process.spec.js
index c8a510bf..732310f7 100644
--- a/cypress/integration/process.spec.js
+++ b/cypress/integration/process.spec.js
@@ -440,6 +440,9 @@ describe('Process tests', function() {
                                 "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
                             }
                         ]
+                    },
+                    {
+                        "$import": "import_path"
                     }
                 ]
             }
@@ -463,6 +466,9 @@ describe('Process tests', function() {
                         "basename": "11111111111111111111111111111111+03",
                         "class": "Directory",
                         "location": "keep:11111111111111111111111111111111+03"
+                    },
+                    {
+                        "$import": "import_path"
                     }
                 ]
             }
@@ -479,7 +485,10 @@ describe('Process tests', function() {
                 "input_int_array": [
                     1,
                     3,
-                    5
+                    5,
+                    {
+                        "$import": "import_path"
+                    }
                 ]
             }
         },
@@ -494,7 +503,10 @@ describe('Process tests', function() {
             input: {
                 "input_long_array": [
                     10,
-                    20
+                    20,
+                    {
+                        "$import": "import_path"
+                    }
                 ]
             }
         },
@@ -510,7 +522,10 @@ describe('Process tests', function() {
                 "input_float_array": [
                     10.2,
                     10.4,
-                    10.6
+                    10.6,
+                    {
+                        "$import": "import_path"
+                    }
                 ]
             }
         },
@@ -526,7 +541,10 @@ describe('Process tests', function() {
                 "input_double_array": [
                     20.1,
                     20.2,
-                    20.3
+                    20.3,
+                    {
+                        "$import": "import_path"
+                    }
                 ]
             }
         },
@@ -542,9 +560,78 @@ describe('Process tests', function() {
                 "input_string_array": [
                     "Hello",
                     "World",
-                    "!"
+                    "!",
+                    {
+                        "$import": "import_path"
+                    }
                 ]
             }
+        },
+        {
+            definition: {
+                "id": "#main/input_bool_include",
+                "type": "boolean"
+            },
+            input: {
+                "input_bool_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_int_include",
+                "type": "int"
+            },
+            input: {
+                "input_int_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_float_include",
+                "type": "float"
+            },
+            input: {
+                "input_float_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_string_include",
+                "type": "string"
+            },
+            input: {
+                "input_string_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_file_include",
+                "type": "File"
+            },
+            input: {
+                "input_file_include": {
+                    "$include": "include_path"
+                }
+            }
+        },
+        {
+            definition: {
+                "id": "#main/input_directory_include",
+                "type": "Directory"
+            },
+            input: {
+                "input_directory_include": {
+                    "$include": "include_path"
+                }
+            }
         }
     ];
 
@@ -920,13 +1007,21 @@ describe('Process tests', function() {
                     verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
                     verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
                     verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
+                    verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
                     verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
                     verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
-                    verifyIOParameter('input_int_array', null, null, ["1", "3", "5"]);
-                    verifyIOParameter('input_long_array', null, null, ["10", "20"]);
-                    verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6"]);
-                    verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3"]);
-                    verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!"]);
+                    verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
+                    verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
+                    verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
+                    verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
+                    verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
+                    verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
+                    verifyIOParameter('input_bool_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_int_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_float_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_string_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_file_include', null, null, "Cannot display value");
+                    verifyIOParameter('input_directory_include', null, null, "Cannot display value");
                 });
             cy.get('[data-cy=process-io-card] h6').contains('Outputs')
                 .parents('[data-cy=process-io-card]').within((ctx) => {
diff --git a/src/views/process-panel/process-io-card.tsx b/src/views/process-panel/process-io-card.tsx
index 90427659..4f93f48c 100644
--- a/src/views/process-panel/process-io-card.tsx
+++ b/src/views/process-panel/process-io-card.tsx
@@ -34,7 +34,8 @@ import {
     ImageOffIcon,
     OutputIcon,
     MaximizeIcon,
-    UnMaximizeIcon
+    UnMaximizeIcon,
+    InfoIcon
 } from 'components/icon/icon';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 import {
@@ -484,37 +485,33 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
     switch (true) {
         case isPrimitiveOfType(input, CWLType.BOOLEAN):
             const boolValue = (input as BooleanCommandInputParameter).value;
-
             return boolValue !== undefined &&
                     !(Array.isArray(boolValue) && boolValue.length === 0) ?
-                [{display: <pre>{String(boolValue)}</pre> }] :
+                [{display: renderPrimitiveValue(boolValue, false) }] :
                 [{display: <EmptyValue />}];
 
         case isPrimitiveOfType(input, CWLType.INT):
         case isPrimitiveOfType(input, CWLType.LONG):
             const intValue = (input as IntCommandInputParameter).value;
-
             return intValue !== undefined &&
                     // Missing values are empty array
                     !(Array.isArray(intValue) && intValue.length === 0) ?
-                [{display: <pre>{String(intValue)}</pre> }]
+                [{display: renderPrimitiveValue(intValue, false) }]
                 : [{display: <EmptyValue />}];
 
         case isPrimitiveOfType(input, CWLType.FLOAT):
         case isPrimitiveOfType(input, CWLType.DOUBLE):
             const floatValue = (input as FloatCommandInputParameter).value;
-
             return floatValue !== undefined &&
                     !(Array.isArray(floatValue) && floatValue.length === 0) ?
-                [{display: <pre>{String(floatValue)}</pre> }]:
+                [{display: renderPrimitiveValue(floatValue, false) }]:
                 [{display: <EmptyValue />}];
 
         case isPrimitiveOfType(input, CWLType.STRING):
             const stringValue = (input as StringCommandInputParameter).value || undefined;
-
             return stringValue !== undefined &&
                     !(Array.isArray(stringValue) && stringValue.length === 0) ?
-                [{display: <pre>{stringValue}</pre> }] :
+                [{display: renderPrimitiveValue(stringValue, false) }] :
                 [{display: <EmptyValue />}];
 
         case isPrimitiveOfType(input, CWLType.FILE):
@@ -526,14 +523,12 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
                 ...secondaryFiles
             ];
             const mainFilePdhUrl = mainFile ? getResourcePdhUrl(mainFile, pdh) : "";
-
             return files.length ?
                 files.map((file, i) => fileToProcessIOValue(file, (i > 0), auth, pdh, (i > 0 ? mainFilePdhUrl : ""))) :
                 [{display: <EmptyValue />}];
 
         case isPrimitiveOfType(input, CWLType.DIRECTORY):
             const directory = (input as DirectoryCommandInputParameter).value;
-
             return directory !== undefined &&
                     !(Array.isArray(directory) && directory.length === 0) ?
                 [directoryToProcessIOValue(directory, auth, pdh)] :
@@ -543,31 +538,28 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
             !(input.type instanceof Array) &&
             input.type.type === 'enum':
             const enumValue = (input as EnumCommandInputParameter).value;
-
-            return enumValue !== undefined ?
-                [{ display: <pre>{(input as EnumCommandInputParameter).value || ''}</pre> }] :
+            return enumValue !== undefined && enumValue ?
+                [{ display: <pre>{enumValue}</pre> }] :
                 [{display: <EmptyValue />}];
 
         case isArrayOfType(input, CWLType.STRING):
             const strArray = (input as StringArrayCommandInputParameter).value || [];
             return strArray.length ?
-                [{ display: <>{((input as StringArrayCommandInputParameter).value || []).map((val) => <Chip label={val} />)}</> }] :
+                [{ display: <>{strArray.map((val) => renderPrimitiveValue(val, true))}</> }] :
                 [{display: <EmptyValue />}];
 
         case isArrayOfType(input, CWLType.INT):
         case isArrayOfType(input, CWLType.LONG):
             const intArray = (input as IntArrayCommandInputParameter).value || [];
-
             return intArray.length ?
-                [{ display: <>{((input as IntArrayCommandInputParameter).value || []).map((val) => <Chip label={val} />)}</> }] :
+                [{ display: <>{intArray.map((val) => renderPrimitiveValue(val, true))}</> }] :
                 [{display: <EmptyValue />}];
 
         case isArrayOfType(input, CWLType.FLOAT):
         case isArrayOfType(input, CWLType.DOUBLE):
             const floatArray = (input as FloatArrayCommandInputParameter).value || [];
-
             return floatArray.length ?
-                [{ display: <>{floatArray.map((val) => <Chip label={val} />)}</> }] :
+                [{ display: <>{floatArray.map((val) => renderPrimitiveValue(val, true))}</> }] :
                 [{display: <EmptyValue />}];
 
         case isArrayOfType(input, CWLType.FILE):
@@ -591,13 +583,21 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
 
         case isArrayOfType(input, CWLType.DIRECTORY):
             const directories = (input as DirectoryArrayCommandInputParameter).value || [];
-
             return directories.length ?
                 directories.map(directory => directoryToProcessIOValue(directory, auth, pdh)) :
                 [{display: <EmptyValue />}];
 
         default:
-            return [];
+            return [{display: <UnsupportedValue />}];
+    }
+};
+
+const renderPrimitiveValue = (value: any, asChip: boolean) => {
+    const isObject = typeof value === 'object';
+    if (!isObject) {
+        return asChip ? <Chip label={String(value)} /> : <pre>{String(value)}</pre>;
+    } else {
+        return asChip ? <UnsupportedValueChip /> : <UnsupportedValue />;
     }
 };
 
@@ -668,6 +668,8 @@ const normalizeDirectoryLocation = (directory: Directory): Directory => {
 };
 
 const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?: string): ProcessIOValue => {
+    if (isExternalValue(directory)) {return {display: <UnsupportedValue />}}
+
     const normalizedDirectory = normalizeDirectoryLocation(directory);
     return {
         display: <KeepUrlPath auth={auth} res={normalizedDirectory} pdh={pdh}/>,
@@ -676,6 +678,8 @@ const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?:
 };
 
 const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, pdh: string | undefined, mainFilePdh: string): ProcessIOValue => {
+    if (isExternalValue(file)) {return {display: <UnsupportedValue />}}
+
     const resourcePdh = getResourcePdhUrl(file, pdh);
     return {
         display: <KeepUrlPath auth={auth} res={file} pdh={pdh}/>,
@@ -685,10 +689,22 @@ const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, p
     }
 };
 
+const isExternalValue = (val: any) =>
+    Object.keys(val).includes('$import') ||
+    Object.keys(val).includes('$include')
+
 const EmptyValue = withStyles(styles)(
     ({classes}: WithStyles<CssRules>) => <span className={classes.emptyValue}>No value</span>
 );
 
+const UnsupportedValue = withStyles(styles)(
+    ({classes}: WithStyles<CssRules>) => <span className={classes.emptyValue}>Cannot display value</span>
+);
+
+const UnsupportedValueChip = withStyles(styles)(
+    ({classes}: WithStyles<CssRules>) => <Chip icon={<InfoIcon />} label={"Cannot display value"} />
+);
+
 const ImagePlaceholder = withStyles(styles)(
     ({classes}: WithStyles<CssRules>) => <span className={classes.imagePlaceholder}><ImageIcon /></span>
 );

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list