From: Stephen Smith Date: Thu, 16 Dec 2021 17:27:26 +0000 (-0500) Subject: Merge branch '18123-group-edit-page-rebase1' into main. Closes #18123 X-Git-Tag: 2.4.0~22 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/58db72fee358d5987139a1b8526c0ca873e07dbf?hp=-c Merge branch '18123-group-edit-page-rebase1' into main. Closes #18123 Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- 58db72fee358d5987139a1b8526c0ca873e07dbf diff --combined src/components/data-explorer/data-explorer.tsx index 05125f12,928d3ed4..55840ae9 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@@ -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 = (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 @@@ -41,14 -39,7 +41,14 @@@ paddingLeft: theme.spacing.unit * 3, paddingTop: theme.spacing.unit * 3, fontSize: '18px' - } + }, + dataTable: { + height: '100%', + overflow: 'auto', + }, + container: { + height: '100%', + }, }); interface DataExplorerDataProps { @@@ -88,55 -79,41 +88,56 @@@ interface DataExplorerActionProps extractKey?: (item: T) => React.Key; } -type DataExplorerProps = DataExplorerDataProps & DataExplorerActionProps & WithStyles; +type DataExplorerProps = DataExplorerDataProps & + DataExplorerActionProps & WithStyles & MPVPanelProps; export const DataExplorer = withStyles(styles)( class DataExplorerGeneric extends React.Component> { + 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 + const dataCy = this.props["data-cy"]; + return - {title &&
{title}
} - {(!hideColumnSelector || !hideSearchInput || !!actions) && + + {title && {title}} - {(!hideColumnSelector || !hideSearchInput) && ++ {(!hideColumnSelector || !hideSearchInput || !!actions) && -
+ {!hideSearchInput &&
{!hideSearchInput && } -
+
} {actions} {!hideColumnSelector && }
-
} - + + } + { doHidePanel && + + + } +
} + onRowClick(item)} @@@ -148,8 -125,8 +149,8 @@@ working={working} defaultView={dataTableDefaultView} currentItemUuid={currentItemUuid} - currentRoute={paperKey} /> - + currentRoute={paperKey} /> + {fetchMode === DataTableFetchMode.PAGINATED ? Load more} - +
+
; } diff --combined src/components/icon/icon.tsx index 523eefbd,4543d8d9..15a9f02d --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@@ -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) => @@@ -82,6 -82,11 +85,11 @@@ ; + export const GroupsIcon = (props: any) => + + + ; + export const CollectionOldVersionIcon = (props: any) => }> @@@ -115,12 -120,10 +123,12 @@@ export const FileIcon: IconType = (prop export const HelpIcon: IconType = (props) => ; export const HelpOutlineIcon: IconType = (props) => ; export const ImportContactsIcon: IconType = (props) => ; +export const InfoIcon: IconType = (props) => ; export const InputIcon: IconType = (props) => ; export const KeyIcon: IconType = (props) => ; export const LogIcon: IconType = (props) => ; export const MailIcon: IconType = (props) => ; +export const MaximizeIcon: IconType = (props) => ; export const MoreOptionsIcon: IconType = (props) => ; export const MoveToIcon: IconType = (props) => ; export const NewProjectIcon: IconType = (props) => ; @@@ -149,9 -152,10 +157,12 @@@ export const SidePanelRightArrowIcon: I export const TrashIcon: IconType = (props) => ; export const UserPanelIcon: IconType = (props) => ; export const UsedByIcon: IconType = (props) => ; +export const VisibleIcon: IconType = (props) => ; +export const InvisibleIcon: IconType = (props) => ; export const WorkflowIcon: IconType = (props) => ; export const WarningIcon: IconType = (props) => ; export const Link: IconType = (props) => ; export const FolderSharedIcon: IconType = (props) => ; + export const CanReadIcon: IconType = (props) => ; + export const CanWriteIcon: IconType = (props) => ; + export const CanManageIcon: IconType = (props) => ; diff --combined src/views/group-details-panel/group-details-panel.tsx index d0f79736,e47ff8c0..ce3f34c7 --- a/src/views/group-details-panel/group-details-panel.tsx +++ b/src/views/group-details-panel/group-details-panel.tsx @@@ -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'; + import { getUserUuid } from 'common/getuser'; + import { GroupResource, isBuiltinGroup } from 'models/group'; ++import { ArvadosTheme } from 'common/custom-theme'; + - export enum GroupDetailsPanelColumnNames { - FIRST_NAME = "First name", - LAST_NAME = "Last name", - UUID = "UUID", - EMAIL = "Email", ++type CssRules = "root"; ++ ++const styles: StyleRulesCallback = (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 = [ + export const groupDetailsMembersPanelColumns: DataColumns = [ { - name: GroupDetailsPanelColumnNames.FIRST_NAME, + name: GroupDetailsPanelMembersColumnNames.FULL_NAME, selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { - name: GroupDetailsPanelColumnNames.LAST_NAME, + name: GroupDetailsPanelMembersColumnNames.USERNAME, selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { - name: GroupDetailsPanelColumnNames.UUID, + name: GroupDetailsPanelMembersColumnNames.ACTIVE, selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { - name: GroupDetailsPanelColumnNames.EMAIL, + name: GroupDetailsPanelMembersColumnNames.VISIBLE, selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => }, { - name: GroupDetailsPanelColumnNames.USERNAME, + name: GroupDetailsPanelMembersColumnNames.PERMISSION, selected: true, configurable: true, filters: createTree(), - render: uuid => + render: uuid => + }, + { + name: GroupDetailsPanelMembersColumnNames.REMOVE, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + ]; + + export const groupDetailsPermissionsPanelColumns: DataColumns = [ + { + name: GroupDetailsPanelPermissionsColumnNames.NAME, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + { + name: GroupDetailsPanelPermissionsColumnNames.PERMISSION, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + { + name: GroupDetailsPanelPermissionsColumnNames.UUID, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => + }, + { + name: GroupDetailsPanelPermissionsColumnNames.REMOVE, + selected: true, + configurable: true, + filters: createTree(), + render: uuid => }, ]; const mapStateToProps = (state: RootState) => { + const groupUuid = getCurrentGroupDetailsPanelUuid(state.properties); + const group = getResource(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, 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 { ++ class GroupDetailsPanel extends React.Component> { + state = { + value: 0, + }; + + componentDidMount() { + this.setState({ value: 0 }); + } render() { + const { value } = this.state; return ( - - - - } /> - ++ + + + + + {value === 0 && + + + + } + paperProps={{ + elevation: 0, + }} /> + } + {value === 1 && + + } + ); } - handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { - const resource = getResource(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, value: number) => { + this.setState({ value }); } -- }); - ++ })); diff --combined src/views/groups-panel/groups-panel.tsx index faefab10,c96c0677..3251c729 --- a/src/views/groups-panel/groups-panel.tsx +++ b/src/views/groups-panel/groups-panel.tsx @@@ -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 = (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 => }, { - name: GroupsPanelColumnNames.OWNER, + name: GroupsPanelColumnNames.UUID, selected: true, configurable: true, filters: createTree(), - render: uuid => , + render: 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, 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 { + class GroupsPanel extends React.Component> { render() { return ( - - } /> + } /> ); } @@@ -114,15 -103,16 +112,16 @@@ const resource = getResource(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( @@@ -131,7 -121,7 +130,7 @@@ const permissions = filterResources((resource: LinkResource) => resource.kind === ResourceKind.LINK && resource.linkClass === LinkClass.PERMISSION && - resource.tailUuid === props.uuid + resource.headUuid === props.uuid )(state.resources); return { @@@ -139,4 -129,4 +138,4 @@@ }; } - )(Typography); + )((props: {children: number}) => ()); diff --combined src/views/workbench/workbench.tsx index 1c6bf03f,64caa6ca..25d70776 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -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 - @@@ -226,7 -220,6 +223,6 @@@ -