refs #master Merge branch 'origin/master' into 13901-services-repo
authorDaniel Kos <daniel.kos@contractors.roche.com>
Fri, 3 Aug 2018 05:48:51 +0000 (07:48 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Fri, 3 Aug 2018 05:48:51 +0000 (07:48 +0200)
# Conflicts:
# src/services/services.ts
# src/store/favorite-panel/favorite-panel-middleware.ts
# src/store/project-panel/project-panel-middleware.ts
# src/store/store.ts
# src/views/workbench/workbench.tsx

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

1  2 
src/index.tsx
src/services/services.ts
src/store/collection-panel/collection-panel-action.ts
src/store/collections/creator/collection-creator-action.ts
src/store/favorite-panel/favorite-panel-middleware-service.ts
src/store/project-panel/project-panel-middleware-service.ts
src/store/store.ts
src/views/workbench/workbench.tsx

diff --cc src/index.tsx
Simple merge
index e137176030bd8a029508d1f0703a35d890f0cd58,69157f8e8f4414e9d01b8ca797f2445b6c876fd6..1f0e23a3c4cad010c505fa24e693d5682ba72cf0
@@@ -7,41 -7,12 +7,42 @@@ import { GroupsService } from "./groups
  import { ProjectService } from "./project-service/project-service";
  import { LinkService } from "./link-service/link-service";
  import { FavoriteService } from "./favorite-service/favorite-service";
 +import { AxiosInstance } from "axios";
 +import { CommonResourceService } from "../common/api/common-resource-service";
 +import { CollectionResource } from "../models/collection";
 +import { Resource } from "../models/resource";
+ import { CollectionService } from "./collection-service/collection-service";
 +import Axios from "axios";
  
 -export const authService = new AuthService(authClient, apiClient);
 -export const groupsService = new GroupsService(apiClient);
 -export const projectService = new ProjectService(apiClient);
 -export const collectionService = new CollectionService(apiClient);
 -export const linkService = new LinkService(apiClient);
 -export const favoriteService = new FavoriteService(linkService, groupsService);
 +export interface ServiceRepository {
 +    apiClient: AxiosInstance;
 +
 +    authService: AuthService;
 +    groupsService: GroupsService;
 +    projectService: ProjectService;
 +    linkService: LinkService;
 +    favoriteService: FavoriteService;
 +    collectionService: CommonResourceService<Resource>;
 +}
 +
 +export const createServices = (baseUrl: string): ServiceRepository => {
 +    const apiClient = Axios.create();
 +    apiClient.defaults.baseURL = `${baseUrl}/arvados/v1`;
 +
 +    const authService = new AuthService(apiClient, baseUrl);
 +    const groupsService = new GroupsService(apiClient);
 +    const projectService = new ProjectService(apiClient);
 +    const linkService = new LinkService(apiClient);
 +    const favoriteService = new FavoriteService(linkService, groupsService);
-     const collectionService = new CommonResourceService<CollectionResource>(apiClient, "collections");
++    const collectionService = new CollectionService(apiClient);
 +
 +    return {
 +        apiClient,
 +        authService,
 +        groupsService,
 +        projectService,
 +        linkService,
 +        favoriteService,
 +        collectionService
 +    };
 +};
index 0000000000000000000000000000000000000000,3c6601657083296a056f40f711ad331f579a516c..1a23fbe2253ace597f922e6d1c0c02ad988298cd
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,30 +1,31 @@@
 -import { apiClient } from "../../common/api/server-api";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { unionize, ofType, UnionOf } from "unionize";
+ import { CommonResourceService } from "../../common/api/common-resource-service";
 -    (dispatch: Dispatch) => {
+ import { Dispatch } from "redux";
+ import { ResourceKind } from "../../models/resource";
+ import { CollectionResource } from "../../models/collection";
++import { RootState } from "../store";
++import { ServiceRepository } from "../../services/services";
+ export const collectionPanelActions = unionize({
+     LOAD_COLLECTION: ofType<{ uuid: string, kind: ResourceKind }>(),
+     LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
+ }, { tag: 'type', value: 'payload' });
+ export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
+ export const loadCollection = (uuid: string, kind: ResourceKind) =>
 -        return new CommonResourceService(apiClient, "collections")
++    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid, kind }));
++        return new CommonResourceService(services.apiClient, "collections")
+             .get(uuid)
+             .then(item => {
+                 dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: item as CollectionResource }));
+             });
+     };
index 0000000000000000000000000000000000000000,b30f8b80876ef60db1fea4bd84ff1a2863db291e..1dc82365130459ae2a01a4829ad55ecab5c43398
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,32 +1,32 @@@
 -import { collectionService } from '../../../services/services';
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { default as unionize, ofType, UnionOf } from "unionize";
+ import { Dispatch } from "redux";
+ import { RootState } from "../../store";
 -    (dispatch: Dispatch, getState: () => RootState) => {
+ import { CollectionResource } from '../../../models/collection';
++import { ServiceRepository } from "../../../services/services";
+ export const collectionCreateActions = unionize({
+     OPEN_COLLECTION_CREATOR: ofType<{ ownerUuid: string }>(),
+     CLOSE_COLLECTION_CREATOR: ofType<{}>(),
+     CREATE_COLLECTION: ofType<{}>(),
+     CREATE_COLLECTION_SUCCESS: ofType<{}>(),
+ }, {
+         tag: 'type',
+         value: 'payload'
+     });
+ export const createCollection = (collection: Partial<CollectionResource>) =>
 -        return collectionService
++    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         const { ownerUuid } = getState().collectionCreation.creator;
+         const collectiontData = { ownerUuid, ...collection };
+         dispatch(collectionCreateActions.CREATE_COLLECTION(collectiontData));
 -export type CollectionCreateAction = UnionOf<typeof collectionCreateActions>;
++        return services.collectionService
+             .create(collectiontData)
+             .then(collection => dispatch(collectionCreateActions.CREATE_COLLECTION_SUCCESS(collection)));
+     };
++export type CollectionCreateAction = UnionOf<typeof collectionCreateActions>;
index 0000000000000000000000000000000000000000,8908fff7083edc1d261239d057128a3bb60b2010..62d9ae2ab6330632f7a063f50989793eb3149a3c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,70 +1,70 @@@
 -import { favoriteService, authService } from "../../services/services";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
+ import { FavoritePanelFilter, FavoritePanelColumnNames } from "../../views/favorite-panel/favorite-panel";
+ import { RootState } from "../store";
+ import { DataColumns } from "../../components/data-table/data-table";
+ import { FavoritePanelItem, resourceToDataItem } from "../../views/favorite-panel/favorite-panel-item";
+ import { FavoriteOrderBuilder } from "../../services/favorite-service/favorite-order-builder";
 -    constructor(id: string) {
++import { ServiceRepository } from "../../services/services";
+ import { SortDirection } from "../../components/data-table/data-column";
+ import { FilterBuilder } from "../../common/api/filter-builder";
+ import { LinkResource } from "../../models/link";
+ import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+ import { favoritePanelActions } from "./favorite-panel-action";
+ import { Dispatch, MiddlewareAPI } from "redux";
+ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
 -            favoriteService
 -                .list(authService.getUuid()!, {
++    constructor(private services: ServiceRepository, id: string) {
+         super(id);
+     }
+     requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+         const dataExplorer = api.getState().dataExplorer[this.getId()];
+         const columns = dataExplorer.columns as DataColumns<FavoritePanelItem, FavoritePanelFilter>;
+         const sortColumn = dataExplorer.columns.find(
+             ({ sortDirection }) => sortDirection !== undefined && sortDirection !== "none"
+         );
+         const typeFilters = getColumnFilters(columns, FavoritePanelColumnNames.TYPE);
+         const order = FavoriteOrderBuilder.create();
+         if (typeFilters.length > 0) {
++            this.services.favoriteService
++                .list(this.services.authService.getUuid()!, {
+                     limit: dataExplorer.rowsPerPage,
+                     offset: dataExplorer.page * dataExplorer.rowsPerPage,
+                     order: sortColumn!.name === FavoritePanelColumnNames.NAME
+                         ? sortColumn!.sortDirection === SortDirection.ASC
+                             ? order.addDesc("name")
+                             : order.addAsc("name")
+                         : order,
+                     filters: FilterBuilder
+                         .create<LinkResource>()
+                         .addIsA("headUuid", typeFilters.map(filter => filter.type))
+                         .addILike("name", dataExplorer.searchValue)
+                 })
+                 .then(response => {
+                     api.dispatch(favoritePanelActions.SET_ITEMS({
+                         items: response.items.map(resourceToDataItem),
+                         itemsAvailable: response.itemsAvailable,
+                         page: Math.floor(response.offset / response.limit),
+                         rowsPerPage: response.limit
+                     }));
+                     api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+                 });
+         } else {
+             api.dispatch(favoritePanelActions.SET_ITEMS({
+                 items: [],
+                 itemsAvailable: 0,
+                 page: 0,
+                 rowsPerPage: dataExplorer.rowsPerPage
+             }));
+         }
+     }
+ }
+ const getColumnFilters = (columns: DataColumns<FavoritePanelItem, FavoritePanelFilter>, columnName: string) => {
+     const column = columns.find(c => c.name === columnName);
+     return column && column.filters ? column.filters.filter(f => f.selected) : [];
+ };
index 0000000000000000000000000000000000000000,761ec188bde282019e7421b34654ef732b33806b..8d3f06a59b3732637a9c2c73997ecbb8f8956151
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,96 +1,96 @@@
 -import { groupsService } from "../../services/services";
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import { DataExplorerMiddlewareService } from "../data-explorer/data-explorer-middleware-service";
+ import { ProjectPanelColumnNames, ProjectPanelFilter } from "../../views/project-panel/project-panel";
+ import { RootState } from "../store";
+ import { DataColumns } from "../../components/data-table/data-table";
 -    constructor(id: string) {
++import { ServiceRepository } from "../../services/services";
+ import { ProjectPanelItem, resourceToDataItem } from "../../views/project-panel/project-panel-item";
+ import { SortDirection } from "../../components/data-table/data-column";
+ import { OrderBuilder } from "../../common/api/order-builder";
+ import { FilterBuilder } from "../../common/api/filter-builder";
+ import { ProcessResource } from "../../models/process";
+ import { GroupContentsResourcePrefix, GroupContentsResource } from "../../services/groups-service/groups-service";
+ import { checkPresenceInFavorites } from "../favorites/favorites-actions";
+ import { projectPanelActions } from "./project-panel-action";
+ import { Dispatch, MiddlewareAPI } from "redux";
+ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService {
 -            groupsService
++    constructor(private services: ServiceRepository, id: string) {
+         super(id);
+     }
+     requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+         const state = api.getState();
+         const dataExplorer = state.dataExplorer[this.getId()];
+         const columns = dataExplorer.columns as DataColumns<ProjectPanelItem, ProjectPanelFilter>;
+         const typeFilters = getColumnFilters(columns, ProjectPanelColumnNames.TYPE);
+         const statusFilters = getColumnFilters(columns, ProjectPanelColumnNames.STATUS);
+         const sortColumn = dataExplorer.columns.find(({ sortDirection }) => Boolean(sortDirection && sortDirection !== "none"));
+         const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC ? SortDirection.ASC : SortDirection.DESC;
+         if (typeFilters.length > 0) {
++            this.services.groupsService
+                 .contents(state.projects.currentItemId, {
+                     limit: dataExplorer.rowsPerPage,
+                     offset: dataExplorer.page * dataExplorer.rowsPerPage,
+                     order: sortColumn
+                         ? sortColumn.name === ProjectPanelColumnNames.NAME
+                             ? getOrder("name", sortDirection)
+                             : getOrder("createdAt", sortDirection)
+                         : OrderBuilder.create(),
+                     filters: FilterBuilder
+                         .create()
+                         .concat(FilterBuilder
+                             .create()
+                             .addIsA("uuid", typeFilters.map(f => f.type)))
+                         .concat(FilterBuilder
+                             .create<ProcessResource>(GroupContentsResourcePrefix.PROCESS)
+                             .addIn("state", statusFilters.map(f => f.type)))
+                         .concat(getSearchFilter(dataExplorer.searchValue))
+                 })
+                 .then(response => {
+                     api.dispatch(projectPanelActions.SET_ITEMS({
+                         items: response.items.map(resourceToDataItem),
+                         itemsAvailable: response.itemsAvailable,
+                         page: Math.floor(response.offset / response.limit),
+                         rowsPerPage: response.limit
+                     }));
+                     api.dispatch<any>(checkPresenceInFavorites(response.items.map(item => item.uuid)));
+                 });
+         } else {
+             api.dispatch(projectPanelActions.SET_ITEMS({
+                 items: [],
+                 itemsAvailable: 0,
+                 page: 0,
+                 rowsPerPage: dataExplorer.rowsPerPage
+             }));
+         }
+     }
+ }
+ const getColumnFilters = (columns: DataColumns<ProjectPanelItem, ProjectPanelFilter>, columnName: string) => {
+     const column = columns.find(c => c.name === columnName);
+     return column && column.filters ? column.filters.filter(f => f.selected) : [];
+ };
+ const getOrder = (attribute: "name" | "createdAt", direction: SortDirection) =>
+     [
+         OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
+         OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
+         OrderBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)
+     ].reduce((acc, b) =>
+         acc.concat(direction === SortDirection.ASC
+             ? b.addAsc(attribute)
+             : b.addDesc(attribute)), OrderBuilder.create());
+ const getSearchFilter = (searchValue: string) =>
+     searchValue
+         ? [
+             FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.COLLECTION),
+             FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROCESS),
+             FilterBuilder.create<GroupContentsResource>(GroupContentsResourcePrefix.PROJECT)]
+             .reduce((acc, b) =>
+                 acc.concat(b.addILike("name", searchValue)), FilterBuilder.create())
+         : FilterBuilder.create();
index 2154b3b0d327c3b21d9db300494e53674ab45860,53a01e2a3a335bdad525a3dc56adefdd062f50f1..37343619fe2c4319288574a615bffe01f1e3e485
@@@ -17,8 -16,13 +16,14 @@@ import { contextMenuReducer, ContextMen
  import { reducer as formReducer } from 'redux-form';
  import { FavoritesState, favoritesReducer } from './favorites/favorites-reducer';
  import { snackbarReducer, SnackbarState } from './snackbar/snackbar-reducer';
+ import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware";
+ import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action";
+ import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action";
+ import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service";
+ import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
+ import { CollectionCreatorState, collectionCreationReducer } from './collections/creator/collection-creator-reducer';
+ import { CollectionPanelState, collectionPanelReducer } from './collection-panel/collection-panel-reducer';
 +import { ServiceRepository } from "../services/services";
  
  const composeEnhancers =
      (process.env.NODE_ENV === 'development' &&
@@@ -37,27 -43,34 +44,36 @@@ export interface RootState 
      snackbar: SnackbarState;
  }
  
 -const rootReducer = combineReducers({
 -    auth: authReducer,
 -    projects: projectsReducer,
 -    collectionCreation: collectionCreationReducer,
 -    router: routerReducer,
 -    dataExplorer: dataExplorerReducer,
 -    sidePanel: sidePanelReducer,
 -    collectionPanel: collectionPanelReducer,
 -    detailsPanel: detailsPanelReducer,
 -    contextMenu: contextMenuReducer,
 -    form: formReducer,
 -    favorites: favoritesReducer,
 -    snackbar: snackbarReducer,
 -});
 +export type RootStore = Store<RootState, Action> & { dispatch: Dispatch<any> };
 +
 +export function configureStore(history: History, services: ServiceRepository): RootStore {
-     const rootReducer = combineReducers({
-         auth: authReducer(services),
-         projects: projectsReducer,
-         router: routerReducer,
-         dataExplorer: dataExplorerReducer,
-         sidePanel: sidePanelReducer,
-         detailsPanel: detailsPanelReducer,
-         contextMenu: contextMenuReducer,
-         form: formReducer,
-         favorites: favoritesReducer,
-         snackbar: snackbarReducer,
-     });
++      const rootReducer = combineReducers({
++          auth: authReducer(services),
++          projects: projectsReducer,
++          collectionCreation: collectionCreationReducer,
++          router: routerReducer,
++          dataExplorer: dataExplorerReducer,
++          sidePanel: sidePanelReducer,
++          collectionPanel: collectionPanelReducer,
++          detailsPanel: detailsPanelReducer,
++          contextMenu: contextMenuReducer,
++          form: formReducer,
++          favorites: favoritesReducer,
++          snackbar: snackbarReducer,
++      });
 -export function configureStore(history: History) {
+     const projectPanelMiddleware = dataExplorerMiddleware(
 -        new ProjectPanelMiddlewareService(PROJECT_PANEL_ID)
++        new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID)
+     );
+     const favoritePanelMiddleware = dataExplorerMiddleware(
 -        new FavoritePanelMiddlewareService(FAVORITE_PANEL_ID)
++        new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)
+     );
  
      const middlewares: Middleware[] = [
          routerMiddleware(history),
 -        thunkMiddleware,
 +        thunkMiddleware.withExtraArgument(services),
-         projectPanelMiddleware(services),
-         favoritePanelMiddleware(services)
+         projectPanelMiddleware,
+         favoritePanelMiddleware
      ];
      const enhancer = composeEnhancers(applyMiddleware(...middlewares));
      return createStore(rootReducer, enhancer);
index a7d8cba7594b6ea68781ed4be9768e1df2dc5518,96398c736c8d4206854cbe57ba5aa4d7def0f1e2..f0067aa4e6f34e4e97534513302855e31b3d4e29
@@@ -35,10 -37,14 +36,15 @@@ import { FavoritePanel } from "../favor
  import { CurrentTokenDialog } from '../../views-components/current-token-dialog/current-token-dialog';
  import { dataExplorerActions } from '../../store/data-explorer/data-explorer-action';
  import { Snackbar } from '../../views-components/snackbar/snackbar';
+ import { favoritePanelActions } from '../../store/favorite-panel/favorite-panel-action';
+ import { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
+ import { CollectionPanel } from '../collection-panel/collection-panel';
+ import { loadCollection } from '../../store/collection-panel/collection-panel-action';
+ import { getCollectionUrl } from '../../models/collection';
 +import { AuthService } from "../../services/auth-service/auth-service";
  
 -const drawerWidth = 240;
 -const appBarHeight = 100;
 +const DRAWER_WITDH = 240;
 +const APP_BAR_HEIGHT = 100;
  
  type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar';
  
@@@ -231,9 -235,21 +239,21 @@@ export const Workbench = withStyles(sty
                  );
              }
  
+             renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel 
+                 onItemRouteChange={(collectionId) => this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION))}
+                 onContextMenu={(event, item) => {
+                     this.openContextMenu(event, {
+                         uuid: item.uuid,
+                         name: item.name,
+                         kind: ContextMenuKind.COLLECTION
+                     });
+                 }}
+                 {...props} />
              renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
 -                onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
 +                onItemRouteChange={itemId => this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE))}
                  onContextMenu={(event, item) => {
                      const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
                      this.openContextMenu(event, {
                          uuid: item.uuid,
                          kind
                      });
                  }}
-                 onDialogOpen={this.handleCreationDialogOpen}
+                 onProjectCreationDialogOpen={this.handleProjectCreationDialogOpen}
+                 onCollectionCreationDialogOpen={this.handleCollectionCreationDialogOpen}
                  onItemClick={item => {
 -                    this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
 +                    this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
                  }}
                  onItemDoubleClick={item => {
-                     this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
-                     this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
+                     switch (item.kind) {
+                         case ResourceKind.COLLECTION:
 -                            this.props.dispatch<any>(loadCollection(item.uuid, item.kind as ResourceKind));
++                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
+                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
+                         default: 
 -                            this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
 -                            this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
++                            this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
++                            this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
+                     }
                  }}
                  {...props} />
  
              renderFavoritePanel = (props: RouteComponentProps<{ id: string }>) => <FavoritePanel
-                 onItemRouteChange={() => this.props.dispatch(dataExplorerActions.REQUEST_ITEMS({ id: FAVORITE_PANEL_ID }))}
 -                onItemRouteChange={() => this.props.dispatch<any>(favoritePanelActions.REQUEST_ITEMS())}
++                onItemRouteChange={() => this.props.dispatch(favoritePanelActions.REQUEST_ITEMS())}
                  onContextMenu={(event, item) => {
                      const kind = item.kind === ResourceKind.PROJECT ? ContextMenuKind.PROJECT : ContextMenuKind.RESOURCE;
                      this.openContextMenu(event, {
                          kind,
                      });
                  }}
-                 onDialogOpen={this.handleCreationDialogOpen}
+                 onDialogOpen={this.handleProjectCreationDialogOpen}
                  onItemClick={item => {
 -                    this.props.dispatch<any>(loadDetails(item.uuid, item.kind as ResourceKind));
 +                    this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind));
                  }}
                  onItemDoubleClick={item => {
-                     this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
-                     this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
-                     this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.PROJECTS));
+                     switch (item.kind) {
+                         case ResourceKind.COLLECTION:
 -                            this.props.dispatch<any>(loadCollection(item.uuid, item.kind as ResourceKind));
++                            this.props.dispatch(loadCollection(item.uuid, item.kind as ResourceKind));
+                             this.props.dispatch(push(getCollectionUrl(item.uuid)));
+                         default:
 -                            this.props.dispatch<any>(loadDetails(item.uuid, ResourceKind.PROJECT));
 -                            this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE));
 -                            this.props.dispatch<any>(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.PROJECTS));
++                            this.props.dispatch(loadDetails(item.uuid, ResourceKind.PROJECT));
++                            this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE));
++                            this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.PROJECTS));
+                     }
                  }}
                  {...props} />