[ARVADOS-WORKBENCH2] created: 2.2.1-121-gf3266a6f
Git user
git at public.arvados.org
Mon Nov 8 23:41:46 UTC 2021
at f3266a6f4b6892a8e817c0d04f693ee8911ab49c (commit)
commit f3266a6f4b6892a8e817c0d04f693ee8911ab49c
Author: Stephen Smith <stephen at curii.com>
Date: Mon Nov 8 10:09:33 2021 -0500
18123: Add edit permission level dialog for group members and outgoing permissions.
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/store/group-details-panel/group-details-panel-actions.ts b/src/store/group-details-panel/group-details-panel-actions.ts
index 22247a8f..26ba537d 100644
--- a/src/store/group-details-panel/group-details-panel-actions.ts
+++ b/src/store/group-details-panel/group-details-panel-actions.ts
@@ -8,14 +8,16 @@ import { propertiesActions } from 'store/properties/properties-actions';
import { getProperty } from 'store/properties/properties';
import { Participant } from 'views-components/sharing-dialog/participant-select';
import { dialogActions } from 'store/dialog/dialog-actions';
-import { reset, startSubmit } from 'redux-form';
+import { initialize, reset, startSubmit } from 'redux-form';
import { addGroupMember, deleteGroupMember } from 'store/groups-panel/groups-panel-actions';
import { getResource } from 'store/resources/resources';
import { GroupResource } from 'models/group';
+import { Resource } from 'models/resource';
import { RootState } from 'store/store';
import { ServiceRepository } from 'services/services';
-import { PermissionResource } from 'models/permission';
+import { PermissionResource, PermissionLevel } from 'models/permission';
import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
+import { PermissionSelectValue, parsePermissionLevel, formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
export const GROUP_DETAILS_MEMBERS_PANEL_ID = 'groupDetailsMembersPanel';
export const GROUP_DETAILS_PERMISSIONS_PANEL_ID = 'groupDetailsPermissionsPanel';
@@ -24,6 +26,10 @@ export const ADD_GROUP_MEMBERS_FORM = 'addGroupMembers';
export const ADD_GROUP_MEMBERS_USERS_FIELD_NAME = 'users';
export const MEMBER_ATTRIBUTES_DIALOG = 'memberAttributesDialog';
export const MEMBER_REMOVE_DIALOG = 'memberRemoveDialog';
+export const EDIT_PERMISSION_LEVEL_DIALOG = 'editPermissionLevel';
+export const EDIT_PERMISSION_LEVEL_FORM = 'editPermissionLevel';
+export const EDIT_PERMISSION_LEVEL_FIELD_NAME = 'name';
+export const EDIT_PERMISSION_LEVEL_UUID_FIELD_NAME = 'uuid';
export const GroupMembersPanelActions = bindDataExplorerActions(GROUP_DETAILS_MEMBERS_PANEL_ID);
export const GroupPermissionsPanelActions = bindDataExplorerActions(GROUP_DETAILS_PERMISSIONS_PANEL_ID);
@@ -42,6 +48,11 @@ export interface AddGroupMembersFormData {
[ADD_GROUP_MEMBERS_USERS_FIELD_NAME]: Participant[];
}
+export interface EditPermissionLevelFormData {
+ [EDIT_PERMISSION_LEVEL_UUID_FIELD_NAME]: string;
+ [EDIT_PERMISSION_LEVEL_FIELD_NAME]: PermissionSelectValue;
+}
+
export const openAddGroupMembersDialog = () =>
(dispatch: Dispatch) => {
dispatch(dialogActions.OPEN_DIALOG({ id: ADD_GROUP_MEMBERS_DIALOG, data: {} }));
@@ -80,6 +91,32 @@ export const addGroupMembers = ({ users }: AddGroupMembersFormData) =>
}
};
+export const openEditPermissionLevelDialog = (linkUuid: string, resourceUuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState) => {
+ const link = getResource<PermissionResource>(linkUuid)(getState().resources);
+ const resource = getResource<Resource>(resourceUuid)(getState().resources);
+
+ if (link) {
+ dispatch(reset(EDIT_PERMISSION_LEVEL_FORM));
+ dispatch<any>(initialize(EDIT_PERMISSION_LEVEL_FORM, {[EDIT_PERMISSION_LEVEL_UUID_FIELD_NAME]: link.uuid, [EDIT_PERMISSION_LEVEL_FIELD_NAME]: formatPermissionLevel(link.name as PermissionLevel)}));
+ dispatch(dialogActions.OPEN_DIALOG({ id: EDIT_PERMISSION_LEVEL_DIALOG, data: resource }));
+ }
+ };
+
+export const editPermissionLevel = (data: EditPermissionLevelFormData) =>
+ async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
+ try {
+ await permissionService.update(data[EDIT_PERMISSION_LEVEL_UUID_FIELD_NAME], {name: parsePermissionLevel(data[EDIT_PERMISSION_LEVEL_FIELD_NAME])});
+ dispatch(dialogActions.CLOSE_DIALOG({ id: EDIT_PERMISSION_LEVEL_DIALOG }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Permission level changed.', hideDuration: 2000 }));
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: 'Failed to update permission',
+ kind: SnackbarKind.ERROR,
+ }));
+ }
+ };
+
export const openGroupMemberAttributes = (uuid: string) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const { resources } = getState();
diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx
index 71b82b6f..73ef32b0 100644
--- a/src/views-components/data-explorer/renderers.tsx
+++ b/src/views-components/data-explorer/renderers.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
import { Resource, ResourceKind, TrashableResource } from 'models/resource';
-import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon, RemoveIcon } from 'components/icon/icon';
+import { ProjectIcon, FilterGroupIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon, RemoveIcon, RenameIcon } from 'components/icon/icon';
import { formatDate, formatFileSize, formatTime } from 'common/formatters';
import { resourceLabel } from 'common/labels';
import { connect, DispatchProp } from 'react-redux';
@@ -23,21 +23,25 @@ import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
import { toggleIsActive, toggleIsAdmin } from 'store/users/users-actions';
import { LinkResource } from 'models/link';
-import { navigateTo } from 'store/navigation/navigation-action';
+import { navigateTo, navigateToGroupDetails } from 'store/navigation/navigation-action';
import { withResourceData } from 'views-components/data-explorer/with-resources';
import { CollectionResource } from 'models/collection';
import { IllegalNamingWarning } from 'components/warning/warning';
import { loadResource } from 'store/resources/resources-actions';
import { GroupClass } from 'models/group';
-import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
+import { openRemoveGroupMemberDialog, openEditPermissionLevelDialog } from 'store/group-details-panel/group-details-panel-actions';
+import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
+import { PermissionLevel } from 'models/permission';
-const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
- <Grid container alignItems="center" wrap="nowrap" spacing={16}>
+const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
+
+ const navFunc = ("groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo);
+ return <Grid container alignItems="center" wrap="nowrap" spacing={16}>
<Grid item>
{renderIcon(item)}
</Grid>
<Grid item>
- <Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
+ <Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
{item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION
? <IllegalNamingWarning name={item.name} />
: null}
@@ -51,6 +55,7 @@ const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
</Typography>
</Grid>
</Grid>;
+};
export const ResourceName = connect(
(state: RootState, props: { uuid: string }) => {
@@ -287,21 +292,31 @@ export const ResourceLinkClass = connect(
return resource || { linkClass: '' };
})(renderLinkClass);
-const renderLink = (dispatch: Dispatch, item: Resource) => {
- var displayName = '';
-
- if ((item as UserResource).kind === ResourceKind.USER
- && typeof (item as UserResource).firstName !== 'undefined') {
+const getResourceDisplayName = (resource: Resource): string => {
+ if ((resource as UserResource).kind === ResourceKind.USER
+ && typeof (resource as UserResource).firstName !== 'undefined') {
// We can be sure the resource is UserResource
- displayName = getUserDisplayName(item as UserResource);
+ return getUserDisplayName(resource as UserResource);
} else {
- displayName = (item as GroupContentsResource).name;
+ return (resource as GroupContentsResource).name;
}
+}
+
+const renderResourceLink = (dispatch: Dispatch, item: Resource) => {
+ var displayName = getResourceDisplayName(item);
return <Typography noWrap color="primary" style={{ 'cursor': 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
{resourceLabel(item.kind)}: {displayName || item.uuid}
</Typography>;
-}
+};
+
+const renderResource = (dispatch: Dispatch, item: Resource) => {
+ var displayName = getResourceDisplayName(item);
+
+ return <Typography variant='body2'>
+ {resourceLabel(item.kind)}: {displayName || item.uuid}
+ </Typography>;
+};
export const ResourceLinkTail = connect(
(state: RootState, props: { uuid: string }) => {
@@ -312,7 +327,7 @@ export const ResourceLinkTail = connect(
item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE }
};
})((props: { item: Resource } & DispatchProp<any>) =>
- renderLink(props.dispatch, props.item));
+ renderResourceLink(props.dispatch, props.item));
export const ResourceLinkHead = connect(
(state: RootState, props: { uuid: string }) => {
@@ -323,7 +338,7 @@ export const ResourceLinkHead = connect(
item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE }
};
})((props: { item: Resource } & DispatchProp<any>) =>
- renderLink(props.dispatch, props.item));
+ renderResourceLink(props.dispatch, props.item));
export const ResourceLinkUuid = connect(
(state: RootState, props: { uuid: string }) => {
@@ -384,6 +399,49 @@ export const ResourceLinkTailUsername = connect(
return resource || { username: '' };
})(renderUsername);
+const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, resource: Resource) => {
+ return <Typography noWrap>
+ {formatPermissionLevel(link.name as PermissionLevel)}
+ <IconButton onClick={() => dispatch<any>(openEditPermissionLevelDialog(link.uuid, resource.uuid))}>
+ <RenameIcon />
+ </IconButton>
+ </Typography>;
+}
+
+export const ResourceLinkHeadPermissionLevel = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const link = getResource<LinkResource>(props.uuid)(state.resources);
+ const resource = getResource<Resource>(link?.headUuid || '')(state.resources);
+
+ return {
+ link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
+ resource: resource || { uuid: '', kind: ResourceKind.NONE }
+ };
+ })((props: { link: LinkResource, resource: Resource } & DispatchProp<any>) =>
+ renderPermissionLevel(props.dispatch, props.link, props.resource));
+
+export const ResourceLinkTailPermissionLevel = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const link = getResource<LinkResource>(props.uuid)(state.resources);
+ const resource = getResource<Resource>(link?.tailUuid || '')(state.resources);
+
+ return {
+ link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
+ resource: resource || { uuid: '', kind: ResourceKind.NONE }
+ };
+ })((props: { link: LinkResource, resource: Resource } & DispatchProp<any>) =>
+ renderPermissionLevel(props.dispatch, props.link, props.resource));
+
+// Displays resource type and display name without link
+export const ResourceLabel = connect(
+ (state: RootState, props: { uuid: string }) => {
+ const resource = getResource<Resource>(props.uuid)(state.resources);
+ return {
+ item: resource || { uuid: '', kind: ResourceKind.NONE }
+ };
+ })((props: { item: Resource } & DispatchProp<any>) =>
+ renderResource(props.dispatch, props.item));
+
// Process Resources
const resourceRunProcess = (dispatch: Dispatch, uuid: string) => {
return (
diff --git a/src/views-components/dialog-forms/edit-permission-level-dialog.tsx b/src/views-components/dialog-forms/edit-permission-level-dialog.tsx
new file mode 100644
index 00000000..5479a0c6
--- /dev/null
+++ b/src/views-components/dialog-forms/edit-permission-level-dialog.tsx
@@ -0,0 +1,55 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { compose } from "redux";
+import { reduxForm, InjectedFormProps, WrappedFieldProps, Field } from 'redux-form';
+import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
+import { FormDialog } from 'components/form-dialog/form-dialog';
+import { EDIT_PERMISSION_LEVEL_DIALOG, EDIT_PERMISSION_LEVEL_FORM, EditPermissionLevelFormData, EDIT_PERMISSION_LEVEL_FIELD_NAME, editPermissionLevel } from 'store/group-details-panel/group-details-panel-actions';
+import { require } from 'validators/require';
+import { PermissionSelect } from 'views-components/sharing-dialog/permission-select';
+import { Grid } from '@material-ui/core';
+import { Resource } from 'models/resource';
+import { ResourceLabel } from 'views-components/data-explorer/renderers';
+
+export const EditPermissionLevelDialog = compose(
+ withDialog(EDIT_PERMISSION_LEVEL_DIALOG),
+ reduxForm<EditPermissionLevelFormData>({
+ form: EDIT_PERMISSION_LEVEL_FORM,
+ onSubmit: (data, dispatch) => {
+ dispatch(editPermissionLevel(data));
+ },
+ })
+)(
+ (props: EditPermissionLevelDialogProps) =>
+ <FormDialog
+ dialogTitle='Edit permission'
+ formFields={PermissionField}
+ submitLabel='Update'
+ {...props}
+ />
+);
+
+interface EditPermissionLevelDataProps {
+ data: Resource;
+}
+
+type EditPermissionLevelDialogProps = EditPermissionLevelDataProps & WithDialogProps<{}> & InjectedFormProps<EditPermissionLevelFormData>;
+
+const PermissionField = (props: EditPermissionLevelDialogProps) =>
+ <Grid container spacing={8}>
+ <Grid item xs={8}>
+ <ResourceLabel uuid={props.data.uuid} />
+ </Grid>
+ <Grid item xs={4} container wrap='nowrap'>
+ <Field
+ name={EDIT_PERMISSION_LEVEL_FIELD_NAME}
+ component={PermissionSelectComponent as any}
+ validate={require} />
+ </Grid>
+ </Grid>;
+
+const PermissionSelectComponent = ({ input }: WrappedFieldProps) =>
+ <PermissionSelect fullWidth disableUnderline {...input} />;
diff --git a/src/views/group-details-panel/group-details-panel.tsx b/src/views/group-details-panel/group-details-panel.tsx
index c402ebb6..50838f7d 100644
--- a/src/views/group-details-panel/group-details-panel.tsx
+++ b/src/views/group-details-panel/group-details-panel.tsx
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { DataExplorer } from "views-components/data-explorer/data-explorer";
import { DataColumns } from 'components/data-table/data-table';
-import { ResourceLinkHeadUuid, ResourceLinkTailUuid, ResourceLinkTailEmail, ResourceLinkTailUsername, ResourceLinkName, ResourceLinkHead, ResourceLinkTail, ResourceLinkDelete } from 'views-components/data-explorer/renderers';
+import { ResourceLinkHeadUuid, ResourceLinkTailUuid, ResourceLinkTailEmail, ResourceLinkTailUsername, ResourceLinkHeadPermissionLevel, ResourceLinkTailPermissionLevel, ResourceLinkHead, ResourceLinkTail, ResourceLinkDelete } from 'views-components/data-explorer/renderers';
import { createTree } from 'models/tree';
import { noop } from 'lodash/fp';
import { RootState } from 'store/store';
@@ -62,7 +62,7 @@ export const groupDetailsMembersPanelColumns: DataColumns<string> = [
selected: true,
configurable: true,
filters: createTree(),
- render: uuid => <ResourceLinkName uuid={uuid} />
+ render: uuid => <ResourceLinkTailPermissionLevel uuid={uuid} />
},
{
name: GroupDetailsPanelMembersColumnNames.UUID,
@@ -93,7 +93,7 @@ export const groupDetailsPermissionsPanelColumns: DataColumns<string> = [
selected: true,
configurable: true,
filters: createTree(),
- render: uuid => <ResourceLinkName uuid={uuid} />
+ render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
},
{
name: GroupDetailsPanelPermissionsColumnNames.UUID,
diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx
index 04f2a273..9bfad524 100644
--- a/src/views/groups-panel/groups-panel.tsx
+++ b/src/views/groups-panel/groups-panel.tsx
@@ -8,7 +8,7 @@ import { Grid, Button, Typography } 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';
@@ -25,7 +25,7 @@ import { navigateToGroupDetails } from 'store/navigation/navigation-action';
export enum GroupsPanelColumnNames {
GROUP = "Name",
- OWNER = "Owner",
+ UUID = "UUID",
MEMBERS = "Members",
}
@@ -39,11 +39,11 @@ export const groupsPanelColumns: DataColumns<string> = [
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,
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 9ce93bf2..50194f9e 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -89,6 +89,7 @@ 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 { EditPermissionLevelDialog } from 'views-components/dialog-forms/edit-permission-level-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';
@@ -213,6 +214,7 @@ export const WorkbenchPanel =
<DetailsPanel />
</Grid>
<AddGroupMembersDialog />
+ <EditPermissionLevelDialog />
<AdvancedTabDialog />
<AttributesApiClientAuthorizationDialog />
<AttributesKeepServiceDialog />
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list