Merge branch '18123-group-edit-page-rebase1' into main. Closes #18123
authorStephen Smith <stephen@curii.com>
Thu, 16 Dec 2021 17:27:26 +0000 (12:27 -0500)
committerStephen Smith <stephen@curii.com>
Thu, 16 Dec 2021 17:27:26 +0000 (12:27 -0500)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

1  2 
src/components/data-explorer/data-explorer.tsx
src/components/icon/icon.tsx
src/views/group-details-panel/group-details-panel.tsx
src/views/groups-panel/groups-panel.tsx
src/views/workbench/workbench.tsx

index 05125f12c7311b8a4fe700a7dd61923ce6e682c8,928d3ed4867e68388ed1a416e25b51f4018a1c31..55840ae9fd52a752f8b90e73c1f3cc06370c19b9
@@@ -11,19 -11,17 +11,19 @@@ import { SearchInput } from 'components
  import { ArvadosTheme } from "common/custom-theme";
  import { createTree } from 'models/tree';
  import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
 -import { MoreOptionsIcon } from 'components/icon/icon';
 +import { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
  import { PaperProps } from '@material-ui/core/Paper';
 +import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
  
 -type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
 +type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title' | 'dataTable' | 'container';
  
  const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
      searchBox: {
          paddingBottom: theme.spacing.unit * 2
      },
      toolbar: {
 -        paddingTop: theme.spacing.unit * 2
 +        paddingTop: theme.spacing.unit,
 +        paddingRight: theme.spacing.unit * 2,
      },
      toolbarUnderTitle: {
          paddingTop: 0
@@@ -32,7 -30,7 +32,7 @@@
          overflow: 'auto'
      },
      root: {
 -        height: '100%'
 +        height: '100%',
      },
      moreOptionsButton: {
          padding: 0
          paddingLeft: theme.spacing.unit * 3,
          paddingTop: theme.spacing.unit * 3,
          fontSize: '18px'
 -    }
 +    },
 +    dataTable: {
 +        height: '100%',
 +        overflow: 'auto',
 +    },
 +    container: {
 +        height: '100%',
 +    },
  });
  
  interface DataExplorerDataProps<T> {
@@@ -88,55 -79,41 +88,56 @@@ interface DataExplorerActionProps<T> 
      extractKey?: (item: T) => React.Key;
  }
  
 -type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
 +type DataExplorerProps<T> = DataExplorerDataProps<T> &
 +    DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
  
  export const DataExplorer = withStyles(styles)(
      class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
 +
          componentDidMount() {
              if (this.props.onSetColumns) {
                  this.props.onSetColumns(this.props.columns);
              }
          }
 +
          render() {
              const {
                  columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
                  rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
                  items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
                  dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
 -                paperKey, fetchMode, currentItemUuid, title
 +                paperKey, fetchMode, currentItemUuid, title,
 +                doHidePanel, doMaximizePanel, panelName, panelMaximized
              } = this.props;
-             return <Paper className={classes.root} {...paperProps} key={paperKey}>
 +
 -                {title && <div className={classes.title}>{title}</div>}
 -                {(!hideColumnSelector || !hideSearchInput || !!actions) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
+             const dataCy = this.props["data-cy"];
+             return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={dataCy}>
-                 {(!hideColumnSelector || !hideSearchInput) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
 +                <Grid container direction="column" wrap="nowrap" className={classes.container}>
 +                {title && <Grid item xs className={classes.title}>{title}</Grid>}
++                {(!hideColumnSelector || !hideSearchInput || !!actions) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
                      <Grid container justify="space-between" wrap="nowrap" alignItems="center">
-                         <div className={classes.searchBox}>
+                         {!hideSearchInput && <div className={classes.searchBox}>
                              {!hideSearchInput && <SearchInput
                                  label={searchLabel}
                                  value={searchValue}
 +                                selfClearProp={currentItemUuid}
                                  onSearch={onSearch} />}
-                         </div>
+                         </div>}
                          {actions}
                          {!hideColumnSelector && <ColumnSelector
                              columns={columns}
                              onColumnToggle={onColumnToggle} />}
                      </Grid>
 -                </Toolbar>}
 -                <DataTable
 +                    { doMaximizePanel && !panelMaximized &&
 +                        <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
 +                            <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
 +                        </Tooltip> }
 +                    { doHidePanel &&
 +                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
 +                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
 +                        </Tooltip> }
 +                </Toolbar></Grid>}
 +                <Grid item xs="auto" className={classes.dataTable}><DataTable
                      columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
                      items={items}
                      onRowClick={(_, item: T) => onRowClick(item)}
                      working={working}
                      defaultView={dataTableDefaultView}
                      currentItemUuid={currentItemUuid}
 -                    currentRoute={paperKey} />
 -                <Toolbar className={classes.footer}>
 +                    currentRoute={paperKey} /></Grid>
 +                <Grid item xs><Toolbar className={classes.footer}>
                      <Grid container justify="flex-end">
                          {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
                              count={itemsAvailable}
                                  onClick={this.loadMore}
                              >Load more</Button>}
                      </Grid>
 -                </Toolbar>
 +                </Toolbar></Grid>
 +                </Grid>
              </Paper>;
          }
  
index 523eefbd10c7b75bb0e6904f59a1abf62555f563,4543d8d9a688cb148aff742d8c75e9736970a168..15a9f02d7339ab9c05411135d00d8190f06a0a92
@@@ -59,18 -59,18 +59,21 @@@ import SettingsEthernet from '@material
  import Star from '@material-ui/icons/Star';
  import StarBorder from '@material-ui/icons/StarBorder';
  import Warning from '@material-ui/icons/Warning';
 +import Visibility from '@material-ui/icons/Visibility';
 +import VisibilityOff from '@material-ui/icons/VisibilityOff';
  import VpnKey from '@material-ui/icons/VpnKey';
  import LinkOutlined from '@material-ui/icons/LinkOutlined';
+ import RemoveRedEye from '@material-ui/icons/RemoveRedEye';
+ import Computer from '@material-ui/icons/Computer';
  
  // Import FontAwesome icons
  import { library } from '@fortawesome/fontawesome-svg-core';
- import { faPencilAlt, faSlash } from '@fortawesome/free-solid-svg-icons';
+ import { faPencilAlt, faSlash, faUsers } from '@fortawesome/free-solid-svg-icons';
 +import { CropFreeSharp } from '@material-ui/icons';
  library.add(
      faPencilAlt,
      faSlash,
+     faUsers,
  );
  
  export const ReadOnlyIcon = (props: any) =>
          </div>
      </span>;
  
+ export const GroupsIcon = (props: any) =>
+     <span {...props}>
+         <span className="fas fa-users" />
+     </span>;
  export const CollectionOldVersionIcon = (props: any) =>
      <Tooltip title='Old version'>
          <Badge badgeContent={<History fontSize='small' />}>
@@@ -115,12 -120,10 +123,12 @@@ export const FileIcon: IconType = (prop
  export const HelpIcon: IconType = (props) => <Help {...props} />;
  export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
  export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
 +export const InfoIcon: IconType = (props) => <Info {...props} />;
  export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
  export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
  export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
  export const MailIcon: IconType = (props) => <Mail {...props} />;
 +export const MaximizeIcon: IconType = (props) => <CropFreeSharp {...props} />;
  export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
  export const MoveToIcon: IconType = (props) => <Input {...props} />;
  export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
@@@ -149,9 -152,10 +157,12 @@@ export const SidePanelRightArrowIcon: I
  export const TrashIcon: IconType = (props) => <Delete {...props} />;
  export const UserPanelIcon: IconType = (props) => <Person {...props} />;
  export const UsedByIcon: IconType = (props) => <Folder {...props} />;
 +export const VisibleIcon: IconType = (props) => <Visibility {...props} />;
 +export const InvisibleIcon: IconType = (props) => <VisibilityOff {...props} />;
  export const WorkflowIcon: IconType = (props) => <Code {...props} />;
  export const WarningIcon: IconType = (props) => <Warning style={{ color: '#fbc02d', height: '30px', width: '30px' }} {...props} />;
  export const Link: IconType = (props) => <LinkOutlined {...props} />;
  export const FolderSharedIcon: IconType = (props) => <FolderShared {...props} />;
+ export const CanReadIcon: IconType = (props) => <RemoveRedEye {...props} />;
+ export const CanWriteIcon: IconType = (props) => <Edit {...props} />;
+ export const CanManageIcon: IconType = (props) => <Computer {...props} />;
index d0f7973675acbd7e92d852215db35efe1b81d8a7,e47ff8c0cc114eef30147b75ffa434b366a12398..ce3f34c75348d39c4dbd1ee63df9b2e06a70d73c
@@@ -7,67 -7,120 +7,129 @@@ import { connect } from 'react-redux'
  
  import { DataExplorer } from "views-components/data-explorer/data-explorer";
  import { DataColumns } from 'components/data-table/data-table';
- import { ResourceUuid, ResourceFirstName, ResourceLastName, ResourceEmail, ResourceUsername } from 'views-components/data-explorer/renderers';
+ import { ResourceLinkHeadUuid, ResourceLinkTailUsername, ResourceLinkHeadPermissionLevel, ResourceLinkTailPermissionLevel, ResourceLinkHead, ResourceLinkTail, ResourceLinkDelete, ResourceLinkTailIsActive, ResourceLinkTailIsVisible } from 'views-components/data-explorer/renderers';
  import { createTree } from 'models/tree';
  import { noop } from 'lodash/fp';
  import { RootState } from 'store/store';
- import { GROUP_DETAILS_PANEL_ID, openAddGroupMembersDialog } from 'store/group-details-panel/group-details-panel-actions';
+ import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID, openAddGroupMembersDialog, getCurrentGroupDetailsPanelUuid } from 'store/group-details-panel/group-details-panel-actions';
  import { openContextMenu } from 'store/context-menu/context-menu-actions';
  import { ResourcesState, getResource } from 'store/resources/resources';
- import { ContextMenuKind } from 'views-components/context-menu/context-menu';
- import { PermissionResource } from 'models/permission';
- import { Grid, Button } from '@material-ui/core';
 -import { Grid, Button, Tabs, Tab, Paper } from '@material-ui/core';
++import { Grid, Button, Tabs, Tab, Paper, WithStyles, withStyles, StyleRulesCallback } from '@material-ui/core';
  import { AddIcon } from 'components/icon/icon';
- export enum GroupDetailsPanelColumnNames {
-     FIRST_NAME = "First name",
-     LAST_NAME = "Last name",
-     UUID = "UUID",
-     EMAIL = "Email",
+ import { getUserUuid } from 'common/getuser';
+ import { GroupResource, isBuiltinGroup } from 'models/group';
++import { ArvadosTheme } from 'common/custom-theme';
 +
++type CssRules = "root";
++
++const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
++    root: {
++        width: '100%',
++    }
++});
+ export enum GroupDetailsPanelMembersColumnNames {
+     FULL_NAME = "Name",
      USERNAME = "Username",
+     ACTIVE = "User Active",
+     VISIBLE = "Visible to other members",
+     PERMISSION = "Permission",
+     REMOVE = "Remove",
+ }
+ export enum GroupDetailsPanelPermissionsColumnNames {
+     NAME = "Name",
+     PERMISSION = "Permission",
+     UUID = "UUID",
+     REMOVE = "Remove",
  }
  
- export const groupDetailsPanelColumns: DataColumns<string> = [
+ export const groupDetailsMembersPanelColumns: DataColumns<string> = [
      {
-         name: GroupDetailsPanelColumnNames.FIRST_NAME,
+         name: GroupDetailsPanelMembersColumnNames.FULL_NAME,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceFirstName uuid={uuid} />
+         render: uuid => <ResourceLinkTail uuid={uuid} />
      },
      {
-         name: GroupDetailsPanelColumnNames.LAST_NAME,
+         name: GroupDetailsPanelMembersColumnNames.USERNAME,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceLastName uuid={uuid} />
+         render: uuid => <ResourceLinkTailUsername uuid={uuid} />
      },
      {
-         name: GroupDetailsPanelColumnNames.UUID,
+         name: GroupDetailsPanelMembersColumnNames.ACTIVE,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceUuid uuid={uuid} />
+         render: uuid => <ResourceLinkTailIsActive uuid={uuid} disabled={true} />
      },
      {
-         name: GroupDetailsPanelColumnNames.EMAIL,
+         name: GroupDetailsPanelMembersColumnNames.VISIBLE,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceEmail uuid={uuid} />
+         render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
      },
      {
-         name: GroupDetailsPanelColumnNames.USERNAME,
+         name: GroupDetailsPanelMembersColumnNames.PERMISSION,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceUsername uuid={uuid} />
+         render: uuid => <ResourceLinkTailPermissionLevel uuid={uuid} />
+     },
+     {
+         name: GroupDetailsPanelMembersColumnNames.REMOVE,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceLinkDelete uuid={uuid} />
+     },
+ ];
+ export const groupDetailsPermissionsPanelColumns: DataColumns<string> = [
+     {
+         name: GroupDetailsPanelPermissionsColumnNames.NAME,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceLinkHead uuid={uuid} />
+     },
+     {
+         name: GroupDetailsPanelPermissionsColumnNames.PERMISSION,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
+     },
+     {
+         name: GroupDetailsPanelPermissionsColumnNames.UUID,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
+     },
+     {
+         name: GroupDetailsPanelPermissionsColumnNames.REMOVE,
+         selected: true,
+         configurable: true,
+         filters: createTree(),
+         render: uuid => <ResourceLinkDelete uuid={uuid} />
      },
  ];
  
  const mapStateToProps = (state: RootState) => {
+     const groupUuid = getCurrentGroupDetailsPanelUuid(state.properties);
+     const group = getResource<GroupResource>(groupUuid || '')(state.resources);
+     const userUuid = getUserUuid(state);
      return {
-         resources: state.resources
+         resources: state.resources,
+         groupCanManage: userUuid && !isBuiltinGroup(group?.uuid || '')
+                             ? group?.writableBy?.includes(userUuid)
+                             : false,
      };
  };
  
@@@ -80,47 -133,74 +142,74 @@@ export interface GroupDetailsPanelProp
      onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
      onAddUser: () => void;
      resources: ResourcesState;
+     groupCanManage: boolean;
  }
  
--export const GroupDetailsPanel = connect(
++export const GroupDetailsPanel = withStyles(styles)(connect(
      mapStateToProps, mapDispatchToProps
  )(
--    class GroupDetailsPanel extends React.Component<GroupDetailsPanelProps> {
++    class GroupDetailsPanel extends React.Component<GroupDetailsPanelProps & WithStyles<CssRules>> {
+         state = {
+           value: 0,
+         };
+         componentDidMount() {
+             this.setState({ value: 0 });
+         }
  
          render() {
+             const { value } = this.state;
              return (
-                 <DataExplorer
-                     id={GROUP_DETAILS_PANEL_ID}
-                     onRowClick={noop}
-                     onRowDoubleClick={noop}
-                     onContextMenu={this.handleContextMenu}
-                     contextMenuColumn={true}
-                     hideColumnSelector
-                     hideSearchInput
-                     actions={
-                         <Grid container justify='flex-end'>
-                             <Button
-                                 variant="contained"
-                                 color="primary"
-                                 onClick={this.props.onAddUser}>
-                                 <AddIcon /> Add user
-                         </Button>
-                         </Grid>
-                     } />
 -                <Paper>
++                <Paper className={this.props.classes.root}>
+                   <Tabs value={value} onChange={this.handleChange} variant="fullWidth">
+                       <Tab data-cy="group-details-members-tab" label="MEMBERS" />
+                       <Tab data-cy="group-details-permissions-tab" label="PERMISSIONS" />
+                   </Tabs>
+                   {value === 0 &&
+                       <DataExplorer
+                           id={GROUP_DETAILS_MEMBERS_PANEL_ID}
+                           data-cy="group-members-data-explorer"
+                           onRowClick={noop}
+                           onRowDoubleClick={noop}
+                           onContextMenu={noop}
+                           contextMenuColumn={false}
+                           hideColumnSelector
+                           hideSearchInput
+                           actions={
+                                 this.props.groupCanManage &&
+                                 <Grid container justify='flex-end'>
+                                     <Button
+                                       data-cy="group-member-add"
+                                       variant="contained"
+                                       color="primary"
+                                       onClick={this.props.onAddUser}>
+                                       <AddIcon /> Add user
+                                     </Button>
+                                 </Grid>
+                           }
+                           paperProps={{
+                               elevation: 0,
+                           }} />
+                   }
+                   {value === 1 &&
+                       <DataExplorer
+                           id={GROUP_DETAILS_PERMISSIONS_PANEL_ID}
+                           data-cy="group-permissions-data-explorer"
+                           onRowClick={noop}
+                           onRowDoubleClick={noop}
+                           onContextMenu={noop}
+                           contextMenuColumn={false}
+                           hideColumnSelector
+                           hideSearchInput
+                           paperProps={{
+                               elevation: 0,
+                           }} />
+                   }
+                 </Paper>
              );
          }
  
-         handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
-             const resource = getResource<PermissionResource>(resourceUuid)(this.props.resources);
-             if (resource) {
-                 this.props.onContextMenu(event, {
-                     name: '',
-                     uuid: resource.uuid,
-                     ownerUuid: resource.ownerUuid,
-                     kind: resource.kind,
-                     menuKind: ContextMenuKind.GROUP_MEMBER
-                 });
-             }
+         handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+             this.setState({ value });
          }
--    });
++    }));
index faefab107de3b0365f37a917eaf1f4a3c41cb673,c96c06775376ae0eb47e35e464ce52b6cba54885..3251c729eee32d6df8d75a4c298d38d9bb0e8c4b
@@@ -4,11 -4,11 +4,11 @@@
  
  import React from 'react';
  import { connect } from 'react-redux';
 -import { Grid, Button, Typography } from "@material-ui/core";
 +import { Grid, Button, Typography, StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
  import { DataExplorer } from "views-components/data-explorer/data-explorer";
  import { DataColumns } from 'components/data-table/data-table';
  import { SortDirection } from 'components/data-table/data-column';
- import { ResourceOwner } from 'views-components/data-explorer/renderers';
+ import { ResourceUuid } from 'views-components/data-explorer/renderers';
  import { AddIcon } from 'components/icon/icon';
  import { ResourceName } from 'views-components/data-explorer/renderers';
  import { createTree } from 'models/tree';
@@@ -21,20 -21,10 +21,19 @@@ import { RootState } from 'store/store'
  import { openContextMenu } from 'store/context-menu/context-menu-actions';
  import { ResourceKind } from 'models/resource';
  import { LinkClass, LinkResource } from 'models/link';
- import { navigateToGroupDetails } from 'store/navigation/navigation-action';
 +import { ArvadosTheme } from 'common/custom-theme';
 +
 +type CssRules = "root";
 +
 +const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 +    root: {
 +        width: '100%',
 +    }
 +});
  
  export enum GroupsPanelColumnNames {
      GROUP = "Name",
-     OWNER = "Owner",
+     UUID = "UUID",
      MEMBERS = "Members",
  }
  
@@@ -48,11 -38,11 +47,11 @@@ export const groupsPanelColumns: DataCo
          render: uuid => <ResourceName uuid={uuid} />
      },
      {
-         name: GroupsPanelColumnNames.OWNER,
+         name: GroupsPanelColumnNames.UUID,
          selected: true,
          configurable: true,
          filters: createTree(),
-         render: uuid => <ResourceOwner uuid={uuid} />,
+         render: uuid => <ResourceUuid uuid={uuid} />,
      },
      {
          name: GroupsPanelColumnNames.MEMBERS,
@@@ -71,42 -61,41 +70,41 @@@ const mapStateToProps = (state: RootSta
  
  const mapDispatchToProps = {
      onContextMenu: openContextMenu,
-     onRowDoubleClick: (uuid: string) =>
-         navigateToGroupDetails(uuid),
      onNewGroup: openCreateGroupDialog,
  };
  
  export interface GroupsPanelProps {
      onNewGroup: () => void;
      onContextMenu: (event: React.MouseEvent<HTMLElement>, item: any) => void;
-     onRowDoubleClick: (item: string) => void;
      resources: ResourcesState;
  }
  
 -export const GroupsPanel = connect(
 +export const GroupsPanel = withStyles(styles)(connect(
      mapStateToProps, mapDispatchToProps
  )(
 -    class GroupsPanel extends React.Component<GroupsPanelProps> {
 +    class GroupsPanel extends React.Component<GroupsPanelProps & WithStyles<CssRules>> {
  
          render() {
              return (
 -                <DataExplorer
 +                <div className={this.props.classes.root}><DataExplorer
                      id={GROUPS_PANEL_ID}
+                     data-cy="groups-panel-data-explorer"
                      onRowClick={noop}
-                     onRowDoubleClick={this.props.onRowDoubleClick}
+                     onRowDoubleClick={noop}
                      onContextMenu={this.handleContextMenu}
                      contextMenuColumn={true}
                      hideColumnSelector
                      actions={
                          <Grid container justify='flex-end'>
                              <Button
+                                 data-cy="groups-panel-new-group"
                                  variant="contained"
                                  color="primary"
                                  onClick={this.props.onNewGroup}>
                                  <AddIcon /> New group
                          </Button>
                          </Grid>
 -                    } />
 +                    } /></div>
              );
          }
  
              const resource = getResource<GroupResource>(resourceUuid)(this.props.resources);
              if (resource) {
                  this.props.onContextMenu(event, {
-                     name: '',
+                     name: resource.name,
                      uuid: resource.uuid,
+                     description: resource.description,
                      ownerUuid: resource.ownerUuid,
                      kind: resource.kind,
                      menuKind: ContextMenuKind.GROUPS
                  });
              }
          }
 -    });
 +    }));
  
  
  const GroupMembersCount = connect(
          const permissions = filterResources((resource: LinkResource) =>
              resource.kind === ResourceKind.LINK &&
              resource.linkClass === LinkClass.PERMISSION &&
-             resource.tailUuid === props.uuid
+             resource.headUuid === props.uuid
          )(state.resources);
  
          return {
          };
  
      }
- )(Typography);
+ )((props: {children: number}) => (<Typography children={props.children} />));
index 1c6bf03fd9baf73dc2dd1b2675d219c70ea1379b,64caa6cac5d857024965d532102107e92f48a0c2..25d70776e2f9c97ee779cf7594d577bf23f3844f
@@@ -82,13 -82,11 +82,11 @@@ import { HelpApiClientAuthorizationDial
  import { UserManageDialog } from 'views-components/user-dialog/manage-dialog';
  import { SetupShellAccountDialog } from 'views-components/dialog-forms/setup-shell-account-dialog';
  import { GroupsPanel } from 'views/groups-panel/groups-panel';
- import { CreateGroupDialog } from 'views-components/dialog-forms/create-group-dialog';
  import { RemoveGroupDialog } from 'views-components/groups-dialog/remove-dialog';
  import { GroupAttributesDialog } from 'views-components/groups-dialog/attributes-dialog';
  import { GroupDetailsPanel } from 'views/group-details-panel/group-details-panel';
  import { RemoveGroupMemberDialog } from 'views-components/groups-dialog/member-remove-dialog';
  import { GroupMemberAttributesDialog } from 'views-components/groups-dialog/member-attributes-dialog';
- 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 { LinkAccountPanel } from 'views/link-account-panel/link-account-panel';
@@@ -129,9 -127,6 +127,9 @@@ const styles: StyleRulesCallback<CssRul
          minWidth: 0,
          paddingLeft: theme.spacing.unit * 3,
          paddingRight: theme.spacing.unit * 3,
 +        // Reserve vertical space for app bar + MainContentBar
 +        minHeight: `calc(100vh - ${theme.spacing.unit * 16}px)`,
 +        display: 'flex',
      }
  });
  
@@@ -215,7 -210,6 +213,6 @@@ export const WorkbenchPanel 
              <Grid item>
                  <DetailsPanel />
              </Grid>
-             <AddGroupMembersDialog />
              <AdvancedTabDialog />
              <AttributesApiClientAuthorizationDialog />
              <AttributesKeepServiceDialog />
              <CopyCollectionDialog />
              <CopyProcessDialog />
              <CreateCollectionDialog />
-             <CreateGroupDialog />
              <CreateProjectDialog />
              <CreateRepositoryDialog />
              <CreateSshKeyDialog />