merge master
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Thu, 27 Sep 2018 14:12:37 +0000 (16:12 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Thu, 27 Sep 2018 14:12:37 +0000 (16:12 +0200)
Feature #13857

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

1  2 
src/index.tsx
src/store/navigation/navigation-action.ts
src/store/side-panel-tree/side-panel-tree-actions.ts
src/store/workbench/workbench-actions.ts
src/views/workbench/workbench.tsx

diff --combined src/index.tsx
index f81e9da9b832dff89cabb4b5f9fbae74ab776c77,52852847355b79bce73b8307481166176991470e..d0154b663e0a9150419edbf437ac1b1eb729405d
@@@ -18,7 -18,7 +18,7 @@@ import { createServices } from "./servi
  import { MuiThemeProvider } from '@material-ui/core/styles';
  import { CustomTheme } from './common/custom-theme';
  import { fetchConfig } from './common/config';
- import { addMenuActionSet, ContextMenuKind } from "./views-components/context-menu/context-menu";
+ import { addMenuActionSet, ContextMenuKind } from './views-components/context-menu/context-menu';
  import { rootProjectActionSet } from "./views-components/context-menu/action-sets/root-project-action-set";
  import { projectActionSet } from "./views-components/context-menu/action-sets/project-action-set";
  import { resourceActionSet } from './views-components/context-menu/action-sets/resource-action-set';
@@@ -36,9 -36,9 +36,10 @@@ import { initWebSocket } from '~/websoc
  import { Config } from '~/common/config';
  import { addRouteChangeHandlers } from './routes/route-change-handlers';
  import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
- import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set';
+ import { processResourceActionSet } from '~/views-components/context-menu/action-sets/process-resource-action-set';
  import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
 +import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
+ import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set';
  
  const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
  const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
@@@ -56,6 -56,7 +57,7 @@@ addMenuActionSet(ContextMenuKind.COLLEC
  addMenuActionSet(ContextMenuKind.COLLECTION_FILES_ITEM, collectionFilesItemActionSet);
  addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet);
  addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSet);
+ addMenuActionSet(ContextMenuKind.TRASHED_COLLECTION, trashedCollectionActionSet);
  addMenuActionSet(ContextMenuKind.PROCESS, processActionSet);
  addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
  addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
@@@ -77,7 -78,6 +79,7 @@@ fetchConfig(
          store.subscribe(initListener(history, store, services, config));
          store.dispatch(initAuth());
          store.dispatch(setCurrentTokenDialogApiHost(apiHost));
 +        store.dispatch(setUuidPrefix(config.uuidPrefix));
  
          const TokenComponent = (props: any) => <ApiToken authService={services.authService} {...props} />;
          const MainPanelComponent = (props: any) => <MainPanel buildInfo={buildInfo} {...props} />;
index d332e0fca600dc03c6c3f9c6ebc944ac0edb7cbf,943f38ce98b4eb633b6d369c2bcc2313d85d932f..c8a554c7651ea5d5f955ab11bb91051ee96626b2
@@@ -26,8 -26,8 +26,10 @@@ export const navigateTo = (uuid: string
              dispatch<any>(navigateToFavorites);
          } else if (uuid === SidePanelTreeCategory.SHARED_WITH_ME) {
              dispatch(navigateToSharedWithMe);
 +        } else if (uuid === SidePanelTreeCategory.WORKFLOWS) {
 +            dispatch(navigateToWorkflows);
+         } else if (uuid === SidePanelTreeCategory.TRASH) {
+             dispatch(navigateToTrash);
          }
      };
  
@@@ -35,8 -35,6 +37,8 @@@ export const navigateToFavorites = push
  
  export const navigateToTrash = push(Routes.TRASH);
  
 +export const navigateToWorkflows = push(Routes.WORKFLOWS);
 +
  export const navigateToProject = compose(push, getProjectUrl);
  
  export const navigateToCollection = compose(push, getCollectionUrl);
index 073de22c4ea4b9fa30aae1b15fc2311dfc706fe0,3fd2d68af6930946c933657a8d59aec8dfdcddc6..22a83dda8c9f71b130e5b8251b3e8d8c321bb714
@@@ -13,6 -13,8 +13,7 @@@ import { getTreePicker, TreePicker } fr
  import { TreeItemStatus } from "~/components/tree/tree";
  import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree';
  import { ProjectResource } from '~/models/project';
 -import { progressIndicatorActions } from '../progress-indicator/progress-indicator-actions';
+ import { OrderBuilder } from '../../services/api/order-builder';
  
  export enum SidePanelTreeCategory {
      PROJECTS = 'Projects',
@@@ -78,7 -80,10 +79,10 @@@ export const loadSidePanelTreeProjects 
              const params = {
                  filters: new FilterBuilder()
                      .addEqual('ownerUuid', projectUuid)
-                     .getFilters()
+                     .getFilters(),
+                 order: new OrderBuilder<ProjectResource>()
+                     .addAsc('name')
+                     .getOrder()
              };
              const { items } = await services.projectService.list(params);
              dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
index 01b5b92c7b365515e70d276dddc1d307efebd25b,94b4b4f5bb7602f811b257edc08ab48289522618..8f034ec0383b4f61cb4e8b5b98ebe688e9da0e4f
@@@ -2,20 -2,19 +2,19 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
- import { Dispatch, AnyAction } from 'redux';
+ import { Dispatch } from 'redux';
  import { RootState } from "../store";
  import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
- import { loadCollectionPanel } from '~/store/collection-panel/collection-panel-action';
  import { snackbarActions } from '../snackbar/snackbar-actions';
  import { loadFavoritePanel } from '../favorite-panel/favorite-panel-action';
  import { openProjectPanel, projectPanelActions } from '~/store/project-panel/project-panel-action';
- import { activateSidePanelTreeItem, initSidePanelTree, SidePanelTreeCategory, loadSidePanelTreeProjects, getSidePanelTreeNodeAncestorsIds } from '../side-panel-tree/side-panel-tree-actions';
+ import { activateSidePanelTreeItem, initSidePanelTree, SidePanelTreeCategory, loadSidePanelTreeProjects } from '../side-panel-tree/side-panel-tree-actions';
  import { loadResource, updateResources } from '../resources/resources-actions';
  import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
  import { projectPanelColumns } from '~/views/project-panel/project-panel';
  import { favoritePanelColumns } from '~/views/favorite-panel/favorite-panel';
  import { matchRootRoute } from '~/routes/routes';
- import { setCollectionBreadcrumbs, setProjectBreadcrumbs, setSidePanelBreadcrumbs, setProcessBreadcrumbs, setSharedWithMeBreadcrumbs } from '../breadcrumbs/breadcrumbs-actions';
+ import { setSidePanelBreadcrumbs, setProcessBreadcrumbs, setSharedWithMeBreadcrumbs, setTrashBreadcrumbs } from '../breadcrumbs/breadcrumbs-actions';
  import { navigateToProject } from '../navigation/navigation-action';
  import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
  import { ServiceRepository } from '~/services/services';
@@@ -39,10 -38,12 +38,14 @@@ 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-view';
  import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
  import { getProgressIndicator } from '../progress-indicator/progress-indicator-reducer';
+ import { ResourceKind, extractUuidKind } from '~/models/resource';
+ import { FilterBuilder } from '~/services/api/filter-builder';
+ import { GroupContentsResource } from '~/services/groups-service/groups-service';
+ import { unionize, ofType, UnionOf, MatchCases } from '~/common/unionize';
  
  export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
  
@@@ -75,7 -76,6 +78,7 @@@ export const loadWorkbench = () =
                  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<any>(initSidePanelTree());
                  if (router.location) {
                      const match = matchRootRoute(router.location.pathname);
@@@ -110,10 -110,33 +113,33 @@@ export const loadTrash = () =
  export const loadProject = (uuid: string) =>
      handleFirstTimeLoad(
          async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
-             dispatch(openProjectPanel(uuid));
-             await dispatch(activateSidePanelTreeItem(uuid));
-             dispatch(setProjectBreadcrumbs(uuid));
-             dispatch(loadDetailsPanel(uuid));
+             const userUuid = services.authService.getUuid();
+             if (userUuid) {
+                 if (userUuid !== uuid) {
+                     const match = await loadGroupContentsResource({ uuid, userUuid, services });
+                     match({
+                         OWNED: async project => {
+                             await dispatch(activateSidePanelTreeItem(uuid));
+                             dispatch<any>(setSidePanelBreadcrumbs(uuid));
+                             dispatch(finishLoadingProject(project));
+                         },
+                         SHARED: project => {
+                             dispatch<any>(setSharedWithMeBreadcrumbs(uuid));
+                             dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+                             dispatch(finishLoadingProject(project));
+                         },
+                         TRASHED: project => {
+                             dispatch<any>(setTrashBreadcrumbs(uuid));
+                             dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+                             dispatch(finishLoadingProject(project));
+                         }
+                     });
+                 } else {
+                     await dispatch(activateSidePanelTreeItem(userUuid));
+                     dispatch<any>(setSidePanelBreadcrumbs(userUuid));
+                     dispatch(finishLoadingProject(userUuid));
+                 }
+             }
          });
  
  export const createProject = (data: projectCreateActions.ProjectCreateFormDialogData) =>
@@@ -148,7 -171,7 +174,7 @@@ export const moveProject = (data: MoveT
      };
  
  export const updateProject = (data: projectUpdateActions.ProjectUpdateFormDialogData) =>
-     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+     async (dispatch: Dispatch) => {
          const updatedProject = await dispatch<any>(projectUpdateActions.updateProject(data));
          if (updatedProject) {
              dispatch(snackbarActions.OPEN_SNACKBAR({
  
  export const loadCollection = (uuid: string) =>
      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));
+         async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+             const userUuid = services.authService.getUuid();
+             if (userUuid) {
+                 const match = await loadGroupContentsResource({ uuid, userUuid, services });
+                 match({
+                     OWNED: async collection => {
+                         dispatch(updateResources([collection]));
+                         await dispatch(activateSidePanelTreeItem(collection.ownerUuid));
+                         dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
+                     },
+                     SHARED: collection => {
+                         dispatch(updateResources([collection]));
+                         dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
+                         dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+                     },
+                     TRASHED: collection => {
+                         dispatch(updateResources([collection]));
+                         dispatch(setTrashBreadcrumbs(''));
+                         dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+                     },
+                 });
+             }
          });
  
  export const createCollection = (data: collectionCreateActions.CollectionCreateFormDialogData) =>
@@@ -306,8 -347,58 +350,63 @@@ export const loadSharedWithMe = handleF
      await dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
  });
  
 +export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
 +    dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS));
 +    await dispatch(loadWorkflowPanel());
 +    dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
 +});
+ 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
+ }) => {
+     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 {
+             resource = await params.services.containerRequestService.get(params.uuid);
+         }
+         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>(),
+ };
+ const groupContentsHandlers = unionize(groupContentsHandlersRecord);
+ type GroupContentsHandler = UnionOf<typeof groupContentsHandlers>;
index 692c40b819b8b68cea59fa8f59b9f34b9b5a30aa,b0d14f09072bf0be2c139bb7eee28525588c9bfd..776850ceb8950ffe89b2fa7f1e7fc58414dce685
@@@ -11,6 -11,7 +11,7 @@@ import { ArvadosTheme } from '~/common/
  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 { RichTextEditorDialog } from '~/views-components/rich-text-editor-dialog/rich-text-editor-dialog';
  import { Snackbar } from '~/views-components/snackbar/snackbar';
  import { CollectionPanel } from '../collection-panel/collection-panel';
  import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
@@@ -38,7 -39,6 +39,7 @@@ 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 { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
  
  type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
  
@@@ -94,7 -94,6 +95,7 @@@ export const WorkbenchPanel 
                                  <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>
              <PartialCopyCollectionDialog />
              <ProcessCommandDialog />
              <RenameFileDialog />
+             <RichTextEditorDialog />
              <Snackbar />
              <UpdateCollectionDialog />
              <UpdateProcessDialog />