Merge branch 'master' into 15064-wb2-fed-login
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Wed, 15 May 2019 14:30:38 +0000 (10:30 -0400)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Wed, 15 May 2019 14:30:38 +0000 (10:30 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

1  2 
src/components/data-explorer/data-explorer.tsx
src/routes/routes.ts
src/views/collection-content-address-panel/collection-content-address-panel.tsx
src/views/search-results-panel/search-results-panel-view.tsx
src/views/workbench/workbench.tsx

index 9f727049b1eda3d7d7f0faf9560c9a1e70d2553b,5f01957753d0fc5252f5a31b3a63b83b221bd770..7107bd70823526226e45aa16687bdbcb5609b38a
@@@ -14,7 -14,7 +14,7 @@@ import { DataTableFilters } from '~/com
  import { MoreOptionsIcon } from '~/components/icon/icon';
  import { PaperProps } from '@material-ui/core/Paper';
  
- type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton';
 -type CssRules = 'searchBox' | "toolbar" | "footer" | "root" | 'moreOptionsButton' | 'title';
++type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      searchBox: {
@@@ -23,6 -23,6 +23,9 @@@
      toolbar: {
          paddingTop: theme.spacing.unit * 2
      },
++    toolbarUnderTitle: {
++        paddingTop: 0
++    },
      footer: {
          overflow: 'auto'
      },
      },
      moreOptionsButton: {
          padding: 0
+     },
+     title: {
+         paddingLeft: theme.spacing.unit * 3,
+         paddingTop: theme.spacing.unit * 3,
+         fontSize: '18px'
      }
  });
  
@@@ -50,9 -55,9 +58,9 @@@ interface DataExplorerDataProps<T> 
      paperProps?: PaperProps;
      actions?: React.ReactNode;
      hideSearchInput?: boolean;
-     header?: React.ReactNode;
++    title?: React.ReactNode;
      paperKey?: string;
      currentItemUuid: string;
 -    title?: string;
  }
  
  interface DataExplorerActionProps<T> {
@@@ -85,12 -90,12 +93,12 @@@ export const DataExplorer = withStyles(
                  rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
                  items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                  dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
-                 paperKey, fetchMode, currentItemUuid, header
+                 paperKey, fetchMode, currentItemUuid, title
              } = this.props;
              return <Paper className={classes.root} {...paperProps} key={paperKey}>
 -                {title ? <div className={classes.title}>Content Address: {title}</div> : null}
--                {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={classes.toolbar}>
++                {title && <div className={classes.title}>{title}</div>}
++                {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
                      <Grid container justify="space-between" wrap="nowrap" alignItems="center">
-                         {header && <div>{header}</div>}
                          <div className={classes.searchBox}>
                              {!hideSearchInput && <SearchInput
                                  value={searchValue}
diff --combined src/routes/routes.ts
index bd47ca2fd38eb752646a03b27365928b7637b22f,dd09d8ece55545546c482bf0b5eb5d5398ce51ae..831c69e58cc62fcd15b09f135b2de66896cf0089
@@@ -10,7 -10,6 +10,7 @@@ import { getCollectionUrl } from '~/mod
  export const Routes = {
      ROOT: '/',
      TOKEN: '/token',
 +    FED_LOGIN: '/fedtoken',
      PROJECTS: `/projects/:id(${RESOURCE_UUID_PATTERN})`,
      COLLECTIONS: `/collections/:id(${RESOURCE_UUID_PATTERN})`,
      PROCESSES: `/processes/:id(${RESOURCE_UUID_PATTERN})`,
@@@ -35,7 -34,8 +35,8 @@@
      GROUPS: '/groups',
      GROUP_DETAILS: `/group/:id(${RESOURCE_UUID_PATTERN})`,
      LINKS: '/links',
-     PUBLIC_FAVORITES: '/public-favorites'
+     PUBLIC_FAVORITES: '/public-favorites',
+     COLLECTIONS_CONTENT_ADDRESS: '/collections/:id',
  };
  
  export const getResourceUrl = (uuid: string) => {
@@@ -137,5 -137,8 +138,8 @@@ export const matchGroupDetailsRoute = (
  export const matchLinksRoute = (route: string) =>
      matchPath(route, { path: Routes.LINKS });
  
- export const matchPublicFavorites = (route: string) =>
+ export const matchPublicFavoritesRoute = (route: string) =>
      matchPath(route, { path: Routes.PUBLIC_FAVORITES });
+ export const matchCollectionsContentAddressRoute = (route: string) =>
+     matchPath(route, { path: Routes.COLLECTIONS_CONTENT_ADDRESS });
index 0000000000000000000000000000000000000000,89b23f7c2fe4b8f2874057e883866ca772b6e33c..b652b502886653bcf43695cf80eeaefd8d9e86bf
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,154 +1,154 @@@
 -                        title={this.props.match.params.id}
+ // Copyright (C) The Arvados Authors. All rights reserved.
+ //
+ // SPDX-License-Identifier: AGPL-3.0
+ import * as React from 'react';
+ import { StyleRulesCallback, WithStyles, withStyles, Grid, Button } from '@material-ui/core';
+ import { CollectionIcon } from '~/components/icon/icon';
+ import { ArvadosTheme } from '~/common/custom-theme';
+ import { BackIcon } from '~/components/icon/icon';
+ import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
+ import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from '~/store/collections-content-address-panel/collections-content-address-panel-actions';
+ import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
+ import { Dispatch } from 'redux';
+ import { getIsAdmin } from '~/store/public-favorites/public-favorites-actions';
+ import { resourceKindToContextMenuKind, openContextMenu } from '~/store/context-menu/context-menu-actions';
+ import { ResourceKind } from '~/models/resource';
+ import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
+ import { connect } from 'react-redux';
+ import { navigateTo } from '~/store/navigation/navigation-action';
+ import { DataColumns } from '~/components/data-table/data-table';
+ import { SortDirection } from '~/components/data-table/data-column';
+ import { createTree } from '~/models/tree';
+ import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate } from '~/views-components/data-explorer/renderers';
+ type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
+ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+     backLink: {
+         fontSize: '14px',
+         fontWeight: 600,
+         display: 'flex',
+         alignItems: 'center',
+         padding: theme.spacing.unit,
+         marginBottom: theme.spacing.unit,
+         color: theme.palette.grey["700"],
+     },
+     backIcon: {
+         marginRight: theme.spacing.unit
+     },
+     card: {
+         width: '100%'
+     },
+     title: {
+         color: theme.palette.grey["700"]
+     },
+     iconHeader: {
+         fontSize: '1.875rem',
+         color: theme.customs.colors.green700
+     },
+     link: {
+         fontSize: '0.875rem',
+         color: theme.palette.primary.main,
+         textAlign: 'right',
+         '&:hover': {
+             cursor: 'pointer'
+         }
+     }
+ });
+ enum CollectionContentAddressPanelColumnNames {
+     COLLECTION_WITH_THIS_ADDRESS = "Collection with this address",
+     LOCATION = "Location",
+     LAST_MODIFIED = "Last modified"
+ }
+ export const collectionContentAddressPanelColumns: DataColumns<string> = [
+     {
+         name: CollectionContentAddressPanelColumnNames.COLLECTION_WITH_THIS_ADDRESS,
+         selected: true,
+         configurable: true,
+         sortDirection: SortDirection.NONE,
+         filters: createTree(),
+         render: uuid => <ResourceName uuid={uuid} />
+     },
+     {
+         name: CollectionContentAddressPanelColumnNames.LOCATION,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceOwnerName uuid={uuid} />
+     },
+     {
+         name: CollectionContentAddressPanelColumnNames.LAST_MODIFIED,
+         selected: true,
+         configurable: true,
+         sortDirection: SortDirection.DESC,
+         filters: createTree(),
+         render: uuid => <ResourceLastModifiedDate uuid={uuid} />
+     }
+ ];
+ export interface CollectionContentAddressPanelActionProps {
+     onContextMenu: (event: React.MouseEvent<any>, uuid: string) => void;
+     onItemClick: (item: string) => void;
+     onItemDoubleClick: (item: string) => void;
+ }
+ const mapDispatchToProps = (dispatch: Dispatch): CollectionContentAddressPanelActionProps => ({
+     onContextMenu: (event, resourceUuid) => {
+         const isAdmin = dispatch<any>(getIsAdmin());
+         const kind = resourceKindToContextMenuKind(resourceUuid, isAdmin);
+         if (kind) {
+             dispatch<any>(openContextMenu(event, {
+                 name: '',
+                 uuid: resourceUuid,
+                 ownerUuid: '',
+                 kind: ResourceKind.NONE,
+                 menuKind: kind
+             }));
+         }
+         dispatch<any>(loadDetailsPanel(resourceUuid));
+     },
+     onItemClick: (uuid: string) => {
+         dispatch<any>(loadDetailsPanel(uuid));
+     },
+     onItemDoubleClick: uuid => {
+         dispatch<any>(navigateTo(uuid));
+     }
+ });
+ interface CollectionContentAddressDataProps {
+     match: {
+         params: { id: string }
+     };
+ }
+ export const CollectionsContentAddressPanel = withStyles(styles)(
+     connect(null, mapDispatchToProps)(
+         class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
+             render() {
+                 return <Grid item xs={12}>
+                     <Button
+                         onClick={() => history.back()}
+                         className={this.props.classes.backLink}>
+                         <BackIcon className={this.props.classes.backIcon} />
+                         Back
+                     </Button>
+                     <DataExplorer
+                         id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
+                         onRowClick={this.props.onItemClick}
+                         onRowDoubleClick={this.props.onItemDoubleClick}
+                         onContextMenu={this.props.onContextMenu}
+                         contextMenuColumn={true}
 -);
++                        title={`Content address: ${this.props.match.params.id}`}
+                         dataTableDefaultView={
+                             <DataTableDefaultView
+                                 icon={CollectionIcon}
+                                 messages={['Collections with this content address not found.']} />
+                         } />;
+                     </Grid >;
+             }
+         }
+     )
++);
index df6a7e8c1ef0b2957715e2c40c4ab53203c2714c,368a0d6449171c5a7efdc592ae5ad365ad83514f..72a9b282824888e1142c5b85edea3b586ef36890
@@@ -21,9 -21,6 +21,9 @@@ import 
  } from '~/views-components/data-explorer/renderers';
  import { createTree } from '~/models/tree';
  import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
 +import { User } from "~/models/user";
 +import { Config } from '~/common/config';
 +import { Session } from "~/models/session";
  
  export enum SearchResultsPanelColumnNames {
      CLUSTER = "Cluster",
  
  export interface SearchResultsPanelDataProps {
      data: SearchBarAdvanceFormData;
 +    user: User;
 +    sessions: Session[];
 +    remoteHostsConfig: { [key: string]: Config };
 +    localCluster: string;
  }
  
  export interface SearchResultsPanelActionProps {
@@@ -119,18 -112,11 +119,18 @@@ export const searchResultsPanelColumns
  ];
  
  export const SearchResultsPanelView = (props: SearchResultsPanelProps) => {
 +    const homeCluster = props.user.uuid.substr(0, 5);
      return <DataExplorer
          id={SEARCH_RESULTS_PANEL_ID}
          onRowClick={props.onItemClick}
          onRowDoubleClick={props.onItemDoubleClick}
          onContextMenu={props.onContextMenu}
          contextMenuColumn={true}
 -        hideSearchInput />;
 +        hideSearchInput
-         header={
++        title={
 +            props.localCluster === homeCluster ?
-                 <p>Searching clusters: {props.sessions.filter((ss) => ss.loggedIn).map((ss) => <span key={ss.clusterId}> {ss.clusterId}</span>)}</p> :
-                 <p>Searching local cluster {props.localCluster} only.  To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></p>
++                <div>Searching clusters: {props.sessions.filter((ss) => ss.loggedIn).map((ss) => <span key={ss.clusterId}> {ss.clusterId}</span>)}</div> :
++                <div>Searching local cluster {props.localCluster} only.  To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></div>
 +        }
 +    />;
  };
index e0997ea16eb1b78725266a039049021cac489860,e3668373ff754ce93bb1dbf978ad346461ff894d..20cbbdea0c9a0b262f976889d5e097243b777c6f
@@@ -92,7 -92,7 +92,8 @@@ import { GroupMemberAttributesDialog } 
  import { AddGroupMembersDialog } from '~/views-components/dialog-forms/add-group-member-dialog';
  import { PartialCopyToCollectionDialog } from '~/views-components/dialog-forms/partial-copy-to-collection-dialog';
  import { PublicFavoritePanel } from '~/views/public-favorites-panel/public-favorites-panel';
 +import { FedLogin } from './fed-login';
+ import { CollectionsContentAddressPanel } from '~/views/collection-content-address-panel/collection-content-address-panel';
  
  type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
  
@@@ -177,6 -177,7 +178,7 @@@ export const WorkbenchPanel 
                                  <Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
                                  <Route path={Routes.LINKS} component={LinkPanel} />
                                  <Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
 -                                <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel}/>
++                                <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel} />
                              </Switch>
                          </Grid>
                      </Grid>
              <UserAttributesDialog />
              <UserManageDialog />
              <VirtualMachineAttributesDialog />
 +            <FedLogin />
          </Grid>
      );