workflow-view-middleware-service
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 21 Sep 2018 12:04:06 +0000 (14:04 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 21 Sep 2018 12:04:06 +0000 (14:04 +0200)
Feature #13857

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

1  2 
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/store/workflow-panel/workflow-middleware-service.ts
src/views-components/data-explorer/data-explorer.tsx
src/views-components/main-content-bar/main-content-bar.tsx
src/views/workbench/workbench.tsx
src/views/workflow-panel/workflow-description-card.tsx
src/views/workflow-panel/workflow-panel.tsx

diff --combined src/store/store.ts
index 97be93d868927822df3129b8e60a681de2abb5dc,012b747425b72e714472a5b2a0cfe89d03dc2546..16d0d055e30d9c100f8985e071ceee1e63768dd8
@@@ -35,8 -35,6 +35,8 @@@ import { processPanelReducer } from '~/
  import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
  import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
  import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
 +import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
 +import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
  
  const composeEnhancers =
      (process.env.NODE_ENV === 'development' &&
@@@ -62,9 -60,6 +62,9 @@@ export function configureStore(history
      const sharedWithMePanelMiddleware = dataExplorerMiddleware(
          new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
      );
 +    const workflowPanelMiddleware = dataExplorerMiddleware(
 +        new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
 +    );
  
      const middlewares: Middleware[] = [
          routerMiddleware(history),
@@@ -73,6 -68,6 +73,7 @@@
          favoritePanelMiddleware,
          trashPanelMiddleware,
          sharedWithMePanelMiddleware,
++        workflowPanelMiddleware
      ];
      const enhancer = composeEnhancers(applyMiddleware(...middlewares));
      return createStore(rootReducer, enhancer);
index 9124c660ddf4c4c69c459dc2e7ac4039459d9d01,99f5bc70f47ba2ebe5297ef777ddbf5b2cecc8c2..5440fc902561868ac7a72f0ddc0b9636e166ebd6
@@@ -2,7 -2,7 +2,7 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { Dispatch } from 'redux';
+ import { Dispatch, AnyAction } from 'redux';
  import { RootState } from "../store";
  import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
  import { loadCollectionPanel } from '~/store/collection-panel/collection-panel-action';
@@@ -39,11 -39,31 +39,33 @@@ import { loadProcessPanel } from '~/sto
  import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
  import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
  import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
 +import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
 +import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel';
+ import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
+ import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer';
+ 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 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) => {
+         dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
          const { auth, router } = getState();
          const { user } = auth;
          if (user) {
@@@ -53,7 -73,6 +75,7 @@@
                  dispatch(favoritePanelActions.SET_COLUMNS({ columns: favoritePanelColumns }));
                  dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns }));
                  dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns }));
-                 dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns}));
++                dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns }));
                  dispatch<any>(initSidePanelTree());
                  if (router.location) {
                      const match = matchRootRoute(router.location.pathname);
      };
  
  export const loadFavorites = () =>
-     (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 loadTrash = () =>
-     (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 loadProject = (uuid: string) =>
-     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
-         dispatch(openProjectPanel(uuid));
-         await dispatch(activateSidePanelTreeItem(uuid));
-         dispatch(setProjectBreadcrumbs(uuid));
-         dispatch(loadDetailsPanel(uuid));
-     };
+     handleFirstTimeLoad(
+         async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+             dispatch(openProjectPanel(uuid));
+             await dispatch(activateSidePanelTreeItem(uuid));
+             dispatch(setProjectBreadcrumbs(uuid));
+             dispatch(loadDetailsPanel(uuid));
+         });
  
  export const createProject = (data: projectCreateActions.ProjectCreateFormDialogData) =>
      async (dispatch: Dispatch) => {
@@@ -136,12 -158,13 +161,13 @@@ export const updateProject = (data: pro
      };
  
  export const loadCollection = (uuid: string) =>
-     async (dispatch: Dispatch) => {
-         const collection = await dispatch<any>(loadCollectionPanel(uuid));
-         await dispatch<any>(activateSidePanelTreeItem(collection.ownerUuid));
-         dispatch<any>(setCollectionBreadcrumbs(collection.uuid));
-         dispatch(loadDetailsPanel(uuid));
-     };
+     handleFirstTimeLoad(
+         async (dispatch: Dispatch) => {
+             const collection = await dispatch<any>(loadCollectionPanel(uuid));
+             await dispatch<any>(activateSidePanelTreeItem(collection.ownerUuid));
+             dispatch<any>(setCollectionBreadcrumbs(collection.uuid));
+             dispatch(loadDetailsPanel(uuid));
+         });
  
  export const createCollection = (data: collectionCreateActions.CollectionCreateFormDialogData) =>
      async (dispatch: Dispatch) => {
@@@ -194,14 -217,14 +220,14 @@@ export const moveCollection = (data: Mo
      };
  
  export const loadProcess = (uuid: string) =>
-     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(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(loadDetailsPanel(uuid));
+         });
  
  export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialogData) =>
      async (dispatch: Dispatch) => {
@@@ -245,12 -268,13 +271,13 @@@ export const copyProcess = (data: CopyF
      };
  
  export const loadProcessLog = (uuid: string) =>
-     async (dispatch: Dispatch) => {
-         const process = await dispatch<any>(processesActions.loadProcess(uuid));
-         dispatch<any>(setProcessBreadcrumbs(uuid));
-         dispatch<any>(initProcessLogsPanel(uuid));
-         await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
-     };
+     handleFirstTimeLoad(
+         async (dispatch: Dispatch) => {
+             const process = await dispatch<any>(processesActions.loadProcess(uuid));
+             dispatch<any>(setProcessBreadcrumbs(uuid));
+             dispatch<any>(initProcessLogsPanel(uuid));
+             await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
+         });
  
  export const resourceIsNotLoaded = (uuid: string) =>
      snackbarActions.OPEN_SNACKBAR({
@@@ -273,14 -297,8 +300,14 @@@ export const reloadProjectMatchingUuid 
          }
      };
  
- export const loadSharedWithMe = (dispatch: Dispatch) => {
-     dispatch<any>(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+ export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) => {
      dispatch<any>(loadSharedWithMePanel());
-     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 loadWorkflow = (dispatch: Dispatch<any>) => {
++export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
 +    dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS));
-     dispatch(loadWorkflowPanel());
++    await dispatch(loadWorkflowPanel());
 +    dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
- };
++});
index 2ca5337e2f39d7ed7d8a525d8fd12c71f89243ea,0000000000000000000000000000000000000000..7fa2dd4f819422b96e9acc9489c8e62e5635ca7e
mode 100644,000000..100644
--- /dev/null
@@@ -1,75 -1,0 +1,77 @@@
- import { DataExplorer } from '~/store/data-explorer/data-explorer-reducer';
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import { ServiceRepository } from '~/services/services';
 +import { MiddlewareAPI, Dispatch } from 'redux';
 +import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service';
 +import { RootState } from '~/store/store';
 +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
-             const response = await this.services.workflowService;
-             api.dispatch(updateResources([]));
-             api.dispatch(setItems({ kind: '', offset: 4, limit: 4, items: [], itemsAvailable: 4 }));
++import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer';
 +import { updateResources } from '~/store/resources/resources-actions';
 +import { FilterBuilder } from '~/services/api/filter-builder';
 +import { SortDirection } from '~/components/data-table/data-column';
 +import { WorkflowPanelColumnNames } from '~/views/workflow-panel/workflow-panel';
 +import { OrderDirection, OrderBuilder } from '~/services/api/order-builder';
 +import { WorkflowResource } from '~/models/workflow';
 +import { ListResults } from '~/services/common-service/common-resource-service';
 +import { workflowPanelActions } from './workflow-panel-actions';
 +
 +export class WorkflowMiddlewareService extends DataExplorerMiddlewareService {
 +    constructor(private services: ServiceRepository, id: string) {
 +        super(id);
 +    }
 +
 +    async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
++        const state = api.getState();
++        const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
 +        try {
++            const response = await this.services.workflowService.list({ order: getOrder(dataExplorer) });
++            api.dispatch(updateResources(response.items));
++            api.dispatch(setItems(response));
 +        } catch {
 +            api.dispatch(couldNotFetchWorkflows());
 +        }
 +    }
 +}
 +
 +export const getParams = (dataExplorer: DataExplorer) => ({
 +    ...dataExplorerToListParams(dataExplorer),
 +    order: getOrder(dataExplorer),
 +    filters: getFilters(dataExplorer),
 +});
 +
 +export const getFilters = (dataExplorer: DataExplorer) => {
 +    const filters = new FilterBuilder()
 +        .addILike("name", dataExplorer.searchValue)
 +        .getFilters();
 +    return `[${filters}]`;
 +};
 +
 +export const getOrder = (dataExplorer: DataExplorer) => {
 +    const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
 +    const order = new OrderBuilder<WorkflowResource>();
 +    if (sortColumn) {
 +        const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC
 +            ? OrderDirection.ASC
 +            : OrderDirection.DESC;
 +        const columnName = sortColumn && sortColumn.name === WorkflowPanelColumnNames.NAME ? "name" : "modifiedAt";
 +        return order
 +            .addOrder(sortDirection, columnName)
 +            .getOrder();
 +    } else {
 +        return order.getOrder();
 +    }
 +};
 +
 +export const setItems = (listResults: ListResults<WorkflowResource>) =>
 +    workflowPanelActions.SET_ITEMS({
 +        ...listResultsToDataExplorerItemsMeta(listResults),
 +        items: listResults.items.map(resource => resource.uuid),
 +    });
 +
 +const couldNotFetchWorkflows = () =>
 +    snackbarActions.OPEN_SNACKBAR({
 +        message: 'Could not fetch workflows.',
 +        kind: SnackbarKind.ERROR
 +    });
index 74c3e64aaacf1139d5a90cff0c5ada69f40b14b2,74c3e64aaacf1139d5a90cff0c5ada69f40b14b2..17f2c77b0a44fdd3b235b1625c9d0ddf39ba430f
@@@ -15,7 -15,7 +15,7 @@@ import { DataColumns } from "~/componen
  interface Props {
      id: string;
      onRowClick: (item: any) => void;
--    onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
++    onContextMenu?: (event: React.MouseEvent<HTMLElement>, item: any) => void;
      onRowDoubleClick: (item: any) => void;
      extractKey?: (item: any) => React.Key;
  }
index 41442bba0d395e0f02f43d4ae6b3adb3bab00cbb,071b986ab57352050ef411e5cd6db7ecc3bd61ea..6fb419e36710aa187e0f28a79b46fba82ed5f0d7
@@@ -8,36 -8,25 +8,36 @@@ import { DetailsIcon } from "~/componen
  import { Breadcrumbs } from "~/views-components/breadcrumbs/breadcrumbs";
  import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
  import { connect } from 'react-redux';
 +import { RootState } from '~/store/store';
 +import { matchWorkflowRoute } from '~/routes/routes';
  
  interface MainContentBarProps {
      onDetailsPanelToggle: () => void;
 +    buttonVisible: boolean;
  }
  
- const isButtonVisible = ({ router }: RootState) => {
 -export const MainContentBar = connect(undefined, {
 -    onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
 -})((props: MainContentBarProps) =>
 -    <Toolbar>
 -        <Grid container>
 -            <Grid container item xs alignItems="center">
 -                <Breadcrumbs />
 -            </Grid>
 -            <Grid item>
 -                <Tooltip title="Additional Info">
 -                    <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
 -                        <DetailsIcon />
 -                    </IconButton>
 -                </Tooltip>
++const isWorkflowPath = ({ router }: RootState) => {
 +    const pathname = router.location ? router.location.pathname : '';
-     const match = !matchWorkflowRoute(pathname);
++    const match = matchWorkflowRoute(pathname);
 +    return !!match;
 +};
 +
 +export const MainContentBar = connect((state: RootState) => ({
-     buttonVisible: isButtonVisible(state)
++    buttonVisible: !isWorkflowPath(state)
 +}), {
 +        onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
 +    })((props: MainContentBarProps) =>
 +        <Toolbar>
 +            <Grid container>
 +                <Grid container item xs alignItems="center">
 +                    <Breadcrumbs />
 +                </Grid>
 +                <Grid item>
 +                    {props.buttonVisible ? <Tooltip title="Additional Info">
 +                        <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
 +                            <DetailsIcon />
 +                        </IconButton>
 +                    </Tooltip> : null}
 +                </Grid>
              </Grid>
 -        </Grid>
 -    </Toolbar>);
 +        </Toolbar>);
index 54336575742ea83c51a6eb7a1b4a35d2a5f82427,78918be0c65c687d5230fa622c215c9e04ca847b..692c40b819b8b68cea59fa8f59b9f34b9b5a30aa
@@@ -4,22 -4,15 +4,15 @@@
  
  import * as React from 'react';
  import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
- import { connect, DispatchProp } from "react-redux";
  import { Route, Switch } from "react-router";
- import { User } from "~/models/user";
- import { RootState } from "~/store/store";
- import { MainAppBar } from '~/views-components/main-app-bar/main-app-bar';
- import { push } from 'react-router-redux';
  import { ProjectPanel } from "~/views/project-panel/project-panel";
  import { DetailsPanel } from '~/views-components/details-panel/details-panel';
  import { ArvadosTheme } from '~/common/custom-theme';
- import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
  import { ContextMenu } from "~/views-components/context-menu/context-menu";
  import { FavoritePanel } from "../favorite-panel/favorite-panel";
  import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
  import { Snackbar } from '~/views-components/snackbar/snackbar';
  import { CollectionPanel } from '../collection-panel/collection-panel';
- import { AuthService } from "~/services/auth-service/auth-service";
  import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
  import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
  import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
@@@ -41,21 -34,17 +34,18 @@@ import { FilesUploadCollectionDialog } 
  import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
  import { TrashPanel } from "~/views/trash-panel/trash-panel";
  import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
- import { Grid, LinearProgress } from '@material-ui/core';
+ import { Grid } from '@material-ui/core';
  import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
  import SplitterLayout from 'react-splitter-layout';
  import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
- import { isSystemWorking } from "~/store/progress-indicator/progress-indicator-reducer";
 +import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
  
- type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content' | 'appBar';
+ type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      root: {
-         overflow: 'hidden',
-         width: '100vw',
-         height: '100vh',
-         paddingTop: theme.spacing.unit * 8
+         paddingTop: theme.spacing.unit * 7,
+         background: theme.palette.background.default
      },
      container: {
          position: 'relative'
          }
      },
      asidePanel: {
-         height: '100%',
-         background: theme.palette.background.default
+         paddingTop: theme.spacing.unit,
+         height: '100%'
      },
      contentWrapper: {
-         background: theme.palette.background.default,
-         minWidth: 0,
+         paddingTop: theme.spacing.unit,
+         minWidth: 0
      },
      content: {
          minWidth: 0,
          paddingLeft: theme.spacing.unit * 3,
          paddingRight: theme.spacing.unit * 3,
-     },
-     appBar: {
-         zIndex: 1,
      }
  });
  
- interface WorkbenchDataProps {
-     user?: User;
-     currentToken?: string;
-     working: boolean;
- }
- interface WorkbenchGeneralProps {
-     authService: AuthService;
-     buildInfo: string;
- }
- type WorkbenchProps = WorkbenchDataProps & WorkbenchGeneralProps & DispatchProp<any> & WithStyles<CssRules>;
+ type WorkbenchPanelProps = WithStyles<CssRules>;
  
- interface WorkbenchState {
-     searchText: string;
- }
- export const Workbench = withStyles(styles)(
-     connect<WorkbenchDataProps>(
-         (state: RootState) => ({
-             user: state.auth.user,
-             currentToken: state.auth.apiToken,
-             working: isSystemWorking(state.progressIndicator)
-         })
-     )(
-         class extends React.Component<WorkbenchProps, WorkbenchState> {
-             state = {
-                 searchText: "",
-             };
-             render() {
-                 const { classes } = this.props;
-                 return <>
-                     <MainAppBar
-                         searchText={this.state.searchText}
-                         user={this.props.user}
-                         onSearch={this.onSearch}
-                         buildInfo={this.props.buildInfo}>
-                         {this.props.working ? <LinearProgress color="secondary" /> : null}
-                     </MainAppBar>
-                     <Grid container direction="column" className={classes.root}>
-                         {this.props.user &&
-                             <Grid container item xs alignItems="stretch" wrap="nowrap">
-                                 <Grid container item className={classes.container}>
-                                     <SplitterLayout customClassName={classes.splitter} percentage={true}
-                                         primaryIndex={0} primaryMinSize={20} secondaryInitialSize={80} secondaryMinSize={40}>
-                                         <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
-                                             <SidePanel />
-                                         </Grid>
-                                         <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
-                                             <Grid item>
-                                                 <MainContentBar />
-                                             </Grid>
-                                             <Grid item xs className={classes.content}>
-                                                 <Switch>
-                                                     <Route path={Routes.PROJECTS} component={ProjectPanel} />
-                                                     <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
-                                                     <Route path={Routes.FAVORITES} component={FavoritePanel} />
-                                                     <Route path={Routes.PROCESSES} component={ProcessPanel} />
-                                                     <Route path={Routes.TRASH} component={TrashPanel} />
-                                                     <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
-                                                     <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
-                                                     <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
-                                                 </Switch>
-                                             </Grid>
-                                         </Grid>
-                                     </SplitterLayout>
-                                 </Grid>
-                                 <Grid item>
-                                     <DetailsPanel />
-                                 </Grid>
-                             </Grid>
-                         }
+ export const WorkbenchPanel = 
+     withStyles(styles)(({ classes }: WorkbenchPanelProps) => 
+         <Grid container item xs className={classes.root}>
+             <Grid container item xs className={classes.container}>
+                 <SplitterLayout customClassName={classes.splitter} percentage={true}
+                     primaryIndex={0} primaryMinSize={20} secondaryInitialSize={80} secondaryMinSize={40}>
+                     <Grid container item xs component='aside' direction='column' className={classes.asidePanel}>
+                         <SidePanel />
                      </Grid>
-                     <ContextMenu />
-                     <CopyCollectionDialog />
-                     <CopyProcessDialog />
-                     <CreateCollectionDialog />
-                     <CreateProjectDialog />
-                     <CurrentTokenDialog />
-                     <FileRemoveDialog />
-                     <FileRemoveDialog />
-                     <FilesUploadCollectionDialog />
-                     <MoveCollectionDialog />
-                     <MoveProcessDialog />
-                     <MoveProjectDialog />
-                     <MultipleFilesRemoveDialog />
-                     <PartialCopyCollectionDialog />
-                     <ProcessCommandDialog />
-                     <RenameFileDialog />
-                     <Snackbar />
-                     <UpdateCollectionDialog />
-                     <UpdateProcessDialog />
-                     <UpdateProjectDialog />
-                 </>;
-             }
-             onSearch = (searchText: string) => {
-                 this.setState({ searchText });
-                 this.props.dispatch(push(`/search?q=${searchText}`));
-             }
-             toggleDetailsPanel = () => {
-                 this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
-             }
-         }
-     )
- );
+                     <Grid container item xs component="main" direction="column" className={classes.contentWrapper}>
+                         <Grid item>
+                             <MainContentBar />
+                         </Grid>
+                         <Grid item xs className={classes.content}>
+                             <Switch>
+                                 <Route path={Routes.PROJECTS} component={ProjectPanel} />
+                                 <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
+                                 <Route path={Routes.FAVORITES} component={FavoritePanel} />
+                                 <Route path={Routes.PROCESSES} component={ProcessPanel} />
+                                 <Route path={Routes.TRASH} component={TrashPanel} />
+                                 <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
+                                 <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
++                                <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
+                             </Switch>
+                         </Grid>
+                     </Grid>
+                 </SplitterLayout>
+             </Grid>
+             <Grid item>
+                 <DetailsPanel />
+             </Grid>
+             <ContextMenu />
+             <CopyCollectionDialog />
+             <CopyProcessDialog />
+             <CreateCollectionDialog />
+             <CreateProjectDialog />
+             <CurrentTokenDialog />
+             <FileRemoveDialog />
+             <FileRemoveDialog />
+             <FilesUploadCollectionDialog />
+             <MoveCollectionDialog />
+             <MoveProcessDialog />
+             <MoveProjectDialog />
+             <MultipleFilesRemoveDialog />
+             <PartialCopyCollectionDialog />
+             <ProcessCommandDialog />
+             <RenameFileDialog />
+             <Snackbar />
+             <UpdateCollectionDialog />
+             <UpdateProcessDialog />
+             <UpdateProjectDialog />
+         </Grid>
+     );
index 7aa26e6a12efbf3bbbc1bdb16c46b928bebebcd4,0000000000000000000000000000000000000000..e2b0f295e8e4ba5cd99a29ded6d8dc48c2b39943
mode 100644,000000..100644
--- /dev/null
@@@ -1,35 -1,0 +1,37 @@@
- import { StyleRulesCallback, WithStyles, withStyles, Card, CardHeader, Typography } from '@material-ui/core';
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import * as React from 'react';
-             <DataTableDefaultView
-                 icon={WorkflowIcon}
-                 messages={['Please select a workflow to see its description.']} />
++import { StyleRulesCallback, WithStyles, withStyles, Card, CardHeader, Typography, CardContent } from '@material-ui/core';
 +import { ArvadosTheme } from '~/common/custom-theme';
 +import { WorkflowIcon } from '~/components/icon/icon';
 +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 +
 +export type CssRules = 'card';
 +
 +const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 +    card: {
 +        height: '100%'
 +    }
 +});
 +
 +interface WorkflowDescriptionCardDataProps {
 +}
 +
 +type WorkflowDescriptionCardProps = WorkflowDescriptionCardDataProps & WithStyles<CssRules>;
 +
 +export const WorkflowDescriptionCard = withStyles(styles)(
 +    ({ classes }: WorkflowDescriptionCardProps) => {
 +        return <Card className={classes.card}>
 +            <CardHeader
 +                title={<Typography noWrap variant="body2">
 +                    Workflow description:
 +                </Typography>} />
++            <CardContent>
++                <DataTableDefaultView
++                    icon={WorkflowIcon}
++                    messages={['Please select a workflow to see its description.']} />
++            </CardContent>
 +        </Card>;
 +    });
index ef0b5fbb6e4f2d1ef6ae1eaa335a953626dea30f,0000000000000000000000000000000000000000..92bbcad65a1159a088a58d03614e8e2979cc2d52
mode 100644,000000..100644
--- /dev/null
@@@ -1,142 -1,0 +1,143 @@@
-             return <Grid container>
-                 <Grid item xs={6} style={{ paddingRight: '24px', display: 'grid' }}>
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import * as React from 'react';
 +import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
 +import { connect, DispatchProp } from 'react-redux';
 +import { RootState } from '~/store/store';
 +import { WorkflowIcon } from '~/components/icon/icon';
 +import { ResourcesState, getResource } from '~/store/resources/resources';
 +import { navigateTo } from "~/store/navigation/navigation-action";
 +import { loadDetailsPanel } from "~/store/details-panel/details-panel-action";
 +import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 +import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions';
 +import { openContextMenu } from '~/store/context-menu/context-menu-actions';
 +import { GroupResource } from '~/models/group';
 +import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
 +import {
 +    ResourceLastModifiedDate,
 +    ResourceName,
 +} from "~/views-components/data-explorer/renderers";
 +import { SortDirection } from '~/components/data-table/data-column';
 +import { DataColumns } from '~/components/data-table/data-table';
 +import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 +import { Grid } from '@material-ui/core';
 +import { WorkflowDescriptionCard } from './workflow-description-card';
 +
 +export enum WorkflowPanelColumnNames {
 +    NAME = "Name",
 +    AUTHORISATION = "Authorisation",
 +    LAST_MODIFIED = "Last modified",
 +}
 +
 +interface WorkflowPanelDataProps {
 +    resources: ResourcesState;
 +}
 +
 +export enum ResourceStatus {
 +    PUBLIC = 'public',
 +    PRIVATE = 'private',
 +    SHARED = 'shared'
 +}
 +
 +const resourceStatus = (type: string) => {
 +    switch (type) {
 +        case ResourceStatus.PUBLIC:
 +            return "Public";
 +        case ResourceStatus.PRIVATE:
 +            return "Private";
 +        case ResourceStatus.SHARED:
 +            return "Shared";
 +        default:
 +            return "Unknown";
 +    }
 +};
 +
 +export const workflowPanelColumns: DataColumns<string, DataTableFilterItem> = [
 +    {
 +        name: WorkflowPanelColumnNames.NAME,
 +        selected: true,
 +        configurable: true,
 +        sortDirection: SortDirection.ASC,
 +        filters: [],
 +        render: (uuid: string) => <ResourceName uuid={uuid} />
 +    },
 +    {
 +        name: WorkflowPanelColumnNames.AUTHORISATION,
 +        selected: true,
 +        configurable: true,
 +        sortDirection: SortDirection.NONE,
 +        filters: [
 +            {
 +                name: resourceStatus(ResourceStatus.PUBLIC),
 +                selected: true,
 +            },
 +            {
 +                name: resourceStatus(ResourceStatus.PRIVATE),
 +                selected: true,
 +            },
 +            {
 +                name: resourceStatus(ResourceStatus.SHARED),
 +                selected: true,
 +            }
 +        ],
++        // do zmiany na ResourceAuthorisation
 +        render: (uuid: string) => <ResourceName uuid={uuid} />,
 +    },
 +    {
 +        name: WorkflowPanelColumnNames.LAST_MODIFIED,
 +        selected: true,
 +        configurable: true,
 +        sortDirection: SortDirection.NONE,
 +        filters: [],
 +        render: (uuid: string) => <ResourceLastModifiedDate uuid={uuid} />
 +    }
 +];
 +
 +type WorkflowPanelProps = WorkflowPanelDataProps & DispatchProp;
 +
 +export const WorkflowPanel = connect((state: RootState) => ({
 +    resources: state.resources
 +}))(
 +    class extends React.Component<WorkflowPanelProps> {
 +        render() {
++            return <Grid container spacing={16}>
++                <Grid item xs={6}>
 +                    <DataExplorer
 +                        id={WORKFLOW_PANEL_ID}
 +                        onRowClick={this.handleRowClick}
 +                        onRowDoubleClick={this.handleRowDoubleClick}
 +                        onContextMenu={this.handleContextMenu}
 +                        contextMenuColumn={false}
 +                        dataTableDefaultView={<DataTableDefaultView icon={WorkflowIcon} />} />
 +                </Grid>
 +                <Grid item xs={6}>
 +                    <WorkflowDescriptionCard />
 +                </Grid>
 +            </Grid>;
 +        }
 +
 +        handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
 +            const resource = getResource<GroupResource>(resourceUuid)(this.props.resources);
 +            if (resource) {
 +                this.props.dispatch<any>(openContextMenu(event, {
 +                    name: '',
 +                    uuid: resource.uuid,
 +                    ownerUuid: resource.ownerUuid,
 +                    isTrashed: resource.isTrashed,
 +                    kind: resource.kind,
 +                    menuKind: ContextMenuKind.PROJECT,
 +                }));
 +            }
 +        }
 +
 +        handleRowDoubleClick = (uuid: string) => {
 +            this.props.dispatch<any>(navigateTo(uuid));
 +        }
 +
 +        handleRowClick = (uuid: string) => {
 +            this.props.dispatch(loadDetailsPanel(uuid));
 +        }
 +    }
 +);