Merge branch 'master' into 13854-tags-card
authorJanicki Artur <artur.janicki@contractors.roche.com>
Tue, 7 Aug 2018 09:33:53 +0000 (11:33 +0200)
committerJanicki Artur <artur.janicki@contractors.roche.com>
Tue, 7 Aug 2018 09:33:53 +0000 (11:33 +0200)
refs #13854

Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki@contractors.roche.com>

1  2 
src/services/services.ts
src/store/collection-panel/collection-panel-action.ts
src/views/collection-panel/collection-panel.tsx
src/views/workbench/workbench.tsx

diff --combined src/services/services.ts
index f56a30f7183245f13f493701a1440d30129c8597,9e1adbf6e4e20d8a1f59637ae4a02f356905dc18..87c668f2ae4ca3ff3e9b36e0f6bb16d3c51a96e8
@@@ -8,11 -8,9 +8,10 @@@ import { ProjectService } from "./proje
  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 { Resource } from "../models/resource";
  import { CollectionService } from "./collection-service/collection-service";
 +import { TagService } from "./tag-service/tag-service";
  import Axios from "axios";
+ import { CollectionFilesService } from "./collection-files-service/collection-files-service";
  
  export interface ServiceRepository {
      apiClient: AxiosInstance;
@@@ -22,8 -20,8 +21,9 @@@
      projectService: ProjectService;
      linkService: LinkService;
      favoriteService: FavoriteService;
-     collectionService: CommonResourceService<Resource>;
 +    tagService: TagService;
+     collectionService: CollectionService;
+     collectionFilesService: CollectionFilesService;
  }
  
  export const createServices = (baseUrl: string): ServiceRepository => {
@@@ -36,8 -34,8 +36,9 @@@
      const linkService = new LinkService(apiClient);
      const favoriteService = new FavoriteService(linkService, groupsService);
      const collectionService = new CollectionService(apiClient);
-     
 +    const tagService = new TagService(linkService);
+     const collectionFilesService = new CollectionFilesService(collectionService);
      return {
          apiClient,
          authService,
@@@ -46,6 -44,6 +47,7 @@@
          linkService,
          favoriteService,
          collectionService,
-         tagService
++        tagService,
+         collectionFilesService
      };
  };
index f9994d734e375da7e7c2ee185ceba4787e8a7d50,ee95590c064446d3d614c2bc2b9640f293bf8aba..f2774f6fb384f2d72256e57a93bd9cda92d71493
@@@ -6,73 -6,32 +6,80 @@@ import { unionize, ofType, UnionOf } fr
  import { Dispatch } from "redux";
  import { ResourceKind } from "../../models/resource";
  import { CollectionResource } from "../../models/collection";
+ import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
+ import { createTree } from "../../models/tree";
  import { RootState } from "../store";
  import { ServiceRepository } from "../../services/services";
 +import { TagResource, TagProperty } from "../../models/tag";
 +import { snackbarActions } from "../snackbar/snackbar-actions";
  
  export const collectionPanelActions = unionize({
      LOAD_COLLECTION: ofType<{ uuid: string, kind: ResourceKind }>(),
 -    LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>()
 +    LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
 +    LOAD_COLLECTION_TAGS: ofType<{ uuid: string }>(),
 +    LOAD_COLLECTION_TAGS_SUCCESS: ofType<{ tags: TagResource[] }>(),
 +    CREATE_COLLECTION_TAG: ofType<{ data: any }>(),
 +    CREATE_COLLECTION_TAG_SUCCESS: ofType<{ tag: TagResource }>(),
 +    DELETE_COLLECTION_TAG: ofType<{ uuid: string }>(),
 +    DELETE_COLLECTION_TAG_SUCCESS: ofType<{ uuid: string }>()
  }, { tag: 'type', value: 'payload' });
  
  export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
  
 +export const COLLECTION_TAG_FORM_NAME = 'collectionTagForm';
 +
  export const loadCollection = (uuid: string, kind: ResourceKind) =>
      (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
          dispatch(collectionPanelActions.LOAD_COLLECTION({ uuid, kind }));
+         dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }));
          return services.collectionService
              .get(uuid)
              .then(item => {
-                 dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: item as CollectionResource }));
+                 dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item }));
+                 return services.collectionFilesService.getFiles(item.uuid);
+             })
+             .then(files => {
+                 dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files));
              });
      };
  
 +export const loadCollectionTags = (uuid: string) => 
 +    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +        dispatch(collectionPanelActions.LOAD_COLLECTION_TAGS({ uuid }));
 +        return services.tagService
 +            .list(uuid)
 +            .then(tags => {
 +                dispatch(collectionPanelActions.LOAD_COLLECTION_TAGS_SUCCESS({ tags }));
 +            });
 +    };
 +
  
 +export const createCollectionTag = (data: TagProperty) => 
 +    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +        dispatch(collectionPanelActions.CREATE_COLLECTION_TAG({ data }));
 +        const item = getState().collectionPanel.item;
 +        const uuid = item ? item.uuid : '';
 +        return services.tagService
 +            .create(uuid, data)
 +            .then(tag => {
 +                dispatch(collectionPanelActions.CREATE_COLLECTION_TAG_SUCCESS({ tag }));
 +                dispatch(snackbarActions.OPEN_SNACKBAR({
 +                    message: "Tag has been successfully added.",
 +                    hideDuration: 2000
 +                }));
 +            });
 +    };
  
 +export const deleteCollectionTag = (uuid: string) => 
 +    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +        dispatch(collectionPanelActions.DELETE_COLLECTION_TAG({ uuid }));
 +        return services.linkService
 +            .delete(uuid)
 +            .then(tag => {
 +                dispatch(collectionPanelActions.DELETE_COLLECTION_TAG_SUCCESS({ uuid: tag.uuid }));
 +                dispatch(snackbarActions.OPEN_SNACKBAR({
 +                    message: "Tag has been successfully deleted.",
 +                    hideDuration: 2000
 +                }));
 +            });
 +    };
index aac5661e14fd3687c3909ee261d48bad0fd89a00,43423a62f2c66a56d759cf304ad122e98d2adcf8..489d28473b0803fe3887380c850186f05c707680
@@@ -3,50 -3,42 +3,51 @@@
  // SPDX-License-Identifier: AGPL-3.0
  
  import * as React from 'react';
- import { 
-     StyleRulesCallback, WithStyles, withStyles, Card, 
-     CardHeader, IconButton, CardContent, Grid, Chip, TextField, Button
+ import {
+     StyleRulesCallback, WithStyles, withStyles, Card,
+     CardHeader, IconButton, CardContent, Grid, Chip
  } from '@material-ui/core';
 -import { connect } from 'react-redux';
 +import { connect, DispatchProp } from "react-redux";
  import { RouteComponentProps } from 'react-router';
  import { ArvadosTheme } from '../../common/custom-theme';
  import { RootState } from '../../store/store';
  import { MoreOptionsIcon, CollectionIcon, CopyIcon } from '../../components/icon/icon';
  import { DetailsAttribute } from '../../components/details-attribute/details-attribute';
  import { CollectionResource } from '../../models/collection';
+ import { CollectionPanelFiles } from '../../views-components/collection-panel-files/collection-panel-files';
  import * as CopyToClipboard from 'react-copy-to-clipboard';
 +import { TagResource } from '../../models/tag';
 +import { CollectionTagForm } from './collection-tag-form';
 +import { deleteCollectionTag } from '../../store/collection-panel/collection-panel-action';
  
 -type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon';
 +type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'value';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      card: {
 -        marginBottom: '20px'
 +        marginBottom: theme.spacing.unit * 2
      },
      iconHeader: {
          fontSize: '1.875rem',
          color: theme.customs.colors.yellow700
      },
      tag: {
 -        marginRight: theme.spacing.unit
 +        marginRight: theme.spacing.unit,
 +        marginBottom: theme.spacing.unit
      },
      copyIcon: {
          marginLeft: theme.spacing.unit,
          fontSize: '1.125rem',
 +        color: theme.palette.grey["500"],
          cursor: 'pointer'
 +    },
 +    value: {
 +        textTransform: 'none'
      }
  });
  
  interface CollectionPanelDataProps {
      item: CollectionResource;
 +    tags: TagResource[];
  }
  
  interface CollectionPanelActionProps {
      onContextMenu: (event: React.MouseEvent<HTMLElement>, item: CollectionResource) => void;
  }
  
 -type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps
 +type CollectionPanelProps = CollectionPanelDataProps & CollectionPanelActionProps & DispatchProp
                              & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
  
  
  export const CollectionPanel = withStyles(styles)(
 -    connect((state: RootState) => ({ item: state.collectionPanel.item! }))(
 -        class extends React.Component<CollectionPanelProps> {
 +    connect((state: RootState) => ({ 
 +        item: state.collectionPanel.item, 
 +        tags: state.collectionPanel.tags 
 +    }))(
 +        class extends React.Component<CollectionPanelProps> { 
  
              render() {
 -                const { classes, item, onContextMenu } = this.props;
 +                const { classes, item, tags, onContextMenu } = this.props;
                  return <div>
                          <Card className={classes.card}>
-                             <CardHeader 
+                             <CardHeader
                                  avatar={ <CollectionIcon className={classes.iconHeader} /> }
-                                 action={ 
+                                 action={
                                      <IconButton
                                          aria-label="More options"
                                          onClick={event => onContextMenu(event, item)}>
                                          <MoreOptionsIcon />
-                                     </IconButton> 
+                                     </IconButton>
                                  }
-                                 title={item && item.name } 
+                                 title={item && item.name }
                                  subheader={item && item.description} />
                              <CardContent>
                                  <Grid container direction="column">
                                      <Grid item xs={6}>
 -                                    <DetailsAttribute label='Collection UUID' value={item && item.uuid}>
 +                                    <DetailsAttribute classValue={classes.value} 
 +                                            label='Collection UUID' 
 +                                            value={item && item.uuid}>
                                          <CopyToClipboard text={item && item.uuid}>
                                              <CopyIcon className={classes.copyIcon} />
                                          </CopyToClipboard>
                                      </DetailsAttribute>
 +                                    <DetailsAttribute label='Number of files' value='14' />
                                      <DetailsAttribute label='Content size' value='54 MB' />
 -                                    <DetailsAttribute label='Owner' value={item && item.ownerUuid} />
 +                                    <DetailsAttribute classValue={classes.value} label='Owner' value={item && item.ownerUuid} />
                                      </Grid>
                                  </Grid>
                              </CardContent>
                              <CardHeader title="Tags" />
                              <CardContent>
                                  <Grid container direction="column">
 -                                    <Grid item xs={4}>
 -                                        <Chip label="Tag 1" className={classes.tag}/>
 -                                        <Chip label="Tag 2" className={classes.tag}/>
 -                                        <Chip label="Tag 3" className={classes.tag}/>
 +                                    <Grid item xs={12}><CollectionTagForm /></Grid>
 +                                    <Grid item xs={12}>
 +                                        {
 +                                            tags.map(tag => {
 +                                                return <Chip key={tag.etag} className={classes.tag}
 +                                                    onDelete={this.handleDelete(tag.uuid)}
 +                                                    label={renderTagLabel(tag)}  />;
 +                                            })
 +                                        }
                                      </Grid>
                                  </Grid>
                              </CardContent>
                          </Card>
-                         <Card className={classes.card}>
-                             <CardHeader title="Files" />
-                             <CardContent>
-                                 <Grid container direction="column">
-                                     <Grid item xs={4}>
-                                         Files
-                                     </Grid>
-                                 </Grid>
-                             </CardContent>
-                         </Card>
+                         <div className={classes.card}>
+                             <CollectionPanelFiles/>
+                         </div>
                      </div>;
              }
  
 +            handleDelete = (uuid: string) => () => {
 +                this.props.dispatch<any>(deleteCollectionTag(uuid));
 +            }
 +
              componentWillReceiveProps({ match, item, onItemRouteChange }: CollectionPanelProps) {
                  if (!item || match.params.id !== item.uuid) {
                      onItemRouteChange(match.params.id);
          }
      )
  );
- };
 +
 +const renderTagLabel = (tag: TagResource) => {
 +    const { properties } = tag;
 +    return `${properties.key}: ${properties.value}`;
++};
index 8491d172249a657b8c7957ea3e8024a0e65a5907,3611d7b10fb12210f9573f9ebf2df41476d62693..69d809869995d3c3344a043b0901076ec9633add
@@@ -34,13 -34,15 +34,14 @@@ import { ResourceKind } from '../../mod
  import { ContextMenu, ContextMenuKind } 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 { 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 { loadCollection, loadCollectionTags } from '../../store/collection-panel/collection-panel-action';
  import { getCollectionUrl } from '../../models/collection';
+ import { RemoveDialog } from '../../views-components/remove-dialog/remove-dialog';
+ import { RenameDialog } from '../../views-components/rename-dialog/rename-dialog';
  import { UpdateCollectionDialog } from '../../views-components/update-collection-dialog/update-collection-dialog.';
  import { AuthService } from "../../services/auth-service/auth-service";
  
@@@ -232,6 -234,8 +233,8 @@@ export const Workbench = withStyles(sty
                          <Snackbar />
                          <CreateProjectDialog />
                          <CreateCollectionDialog />
+                         <RemoveDialog />
+                         <RenameDialog />
                          <UpdateCollectionDialog />
                          <CurrentTokenDialog
                              currentToken={this.props.currentToken}
                  );
              }
  
 -            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel
 -                onItemRouteChange={(collectionId) => this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION))}
 +            renderCollectionPanel = (props: RouteComponentProps<{ id: string }>) => <CollectionPanel 
 +                onItemRouteChange={(collectionId) => {
 +                    this.props.dispatch<any>(loadCollection(collectionId, ResourceKind.COLLECTION));
 +                    this.props.dispatch<any>(loadCollectionTags(collectionId));
 +                }}
                  onContextMenu={(event, item) => {
                      this.openContextMenu(event, {
                          uuid: item.uuid,