18123: Use sharing dialog to add users to groups
authorStephen Smith <stephen@curii.com>
Mon, 13 Dec 2021 00:19:18 +0000 (19:19 -0500)
committerStephen Smith <stephen@curii.com>
Mon, 13 Dec 2021 00:19:18 +0000 (19:19 -0500)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

cypress/integration/group-manage.spec.js
src/store/group-details-panel/group-details-panel-actions.ts
src/store/sharing-dialog/sharing-dialog-actions.ts
src/views-components/dialog-forms/add-group-member-dialog.tsx [deleted file]
src/views/workbench/workbench.tsx

index d17cefe9436648104311590fead38e48325193c2..e638c6489bfa43e2487b8df13f84176f116c2709 100644 (file)
@@ -58,13 +58,13 @@ describe('Group manage tests', function() {
     it('adds users to the group', function() {
         // Add other user to the group
         cy.get('[data-cy=group-member-add]').click();
     it('adds users to the group', function() {
         // Add other user to the group
         cy.get('[data-cy=group-member-add]').click();
-        cy.get('[data-cy=form-dialog]')
-            .should('contain', 'Add users')
+        cy.get('.sharing-dialog')
+            .should('contain', 'Sharing settings')
             .within(() => {
             .within(() => {
-                cy.get('input').type("other");
+                cy.get('[data-cy=invite-people-field] input').type("other");
             });
             });
-        cy.contains('Other User').click();
-        cy.get('[data-cy=form-dialog] button[type=submit]').click();
+        cy.get('[role=tooltip]').click();
+        cy.get('.sharing-dialog').contains('Save').click();
 
         // Check that both users are present with appropriate permissions
         cy.get('[data-cy=group-members-data-explorer]')
 
         // Check that both users are present with appropriate permissions
         cy.get('[data-cy=group-members-data-explorer]')
index 916d68a74d904568c464782221fbd2f504a0f742..8130869fdef9d0f5620eaaca5420d8aca168e145 100644 (file)
@@ -6,24 +6,19 @@ import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-actio
 import { Dispatch } from 'redux';
 import { propertiesActions } from 'store/properties/properties-actions';
 import { getProperty } from 'store/properties/properties';
 import { Dispatch } from 'redux';
 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 { dialogActions } from 'store/dialog/dialog-actions';
-import { reset, startSubmit } from 'redux-form';
-import { addGroupMember, deleteGroupMember } from 'store/groups-panel/groups-panel-actions';
+import { deleteGroupMember } from 'store/groups-panel/groups-panel-actions';
 import { getResource } from 'store/resources/resources';
 import { getResource } from 'store/resources/resources';
-import { GroupResource } from 'models/group';
 import { RootState } from 'store/store';
 import { ServiceRepository } from 'services/services';
 import { PermissionResource, PermissionLevel } from 'models/permission';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { LinkResource } from 'models/link';
 import { deleteResources } from 'store/resources/resources-actions';
 import { RootState } from 'store/store';
 import { ServiceRepository } from 'services/services';
 import { PermissionResource, PermissionLevel } from 'models/permission';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { LinkResource } from 'models/link';
 import { deleteResources } from 'store/resources/resources-actions';
+import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
 
 export const GROUP_DETAILS_MEMBERS_PANEL_ID = 'groupDetailsMembersPanel';
 export const GROUP_DETAILS_PERMISSIONS_PANEL_ID = 'groupDetailsPermissionsPanel';
 
 export const GROUP_DETAILS_MEMBERS_PANEL_ID = 'groupDetailsMembersPanel';
 export const GROUP_DETAILS_PERMISSIONS_PANEL_ID = 'groupDetailsPermissionsPanel';
-export const ADD_GROUP_MEMBERS_DIALOG = 'addGroupMembers';
-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 MEMBER_ATTRIBUTES_DIALOG = 'memberAttributesDialog';
 export const MEMBER_REMOVE_DIALOG = 'memberRemoveDialog';
 
@@ -40,45 +35,13 @@ export const loadGroupDetailsPanel = (groupUuid: string) =>
 
 export const getCurrentGroupDetailsPanelUuid = getProperty<string>(GROUP_DETAILS_MEMBERS_PANEL_ID);
 
 
 export const getCurrentGroupDetailsPanelUuid = getProperty<string>(GROUP_DETAILS_MEMBERS_PANEL_ID);
 
-export interface AddGroupMembersFormData {
-    [ADD_GROUP_MEMBERS_USERS_FIELD_NAME]: Participant[];
-}
-
 export const openAddGroupMembersDialog = () =>
 export const openAddGroupMembersDialog = () =>
-    (dispatch: Dispatch) => {
-        dispatch(dialogActions.OPEN_DIALOG({ id: ADD_GROUP_MEMBERS_DIALOG, data: {} }));
-        dispatch(reset(ADD_GROUP_MEMBERS_FORM));
-    };
-
-export const addGroupMembers = ({ users }: AddGroupMembersFormData) =>
-
-    async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
-
+    (dispatch: Dispatch, getState: () => RootState) => {
         const groupUuid = getCurrentGroupDetailsPanelUuid(getState().properties);
         const groupUuid = getCurrentGroupDetailsPanelUuid(getState().properties);
-
         if (groupUuid) {
         if (groupUuid) {
-
-            dispatch(startSubmit(ADD_GROUP_MEMBERS_FORM));
-
-            const group = getResource<GroupResource>(groupUuid)(getState().resources);
-
-            for (const user of users) {
-
-                await addGroupMember({
-                    user,
-                    group: {
-                        uuid: groupUuid,
-                        name: group ? group.name : groupUuid,
-                    },
-                    dispatch,
-                    permissionService,
-                });
-
-            }
-
-            dispatch(dialogActions.CLOSE_DIALOG({ id: ADD_GROUP_MEMBERS_FORM }));
-            dispatch(GroupMembersPanelActions.REQUEST_ITEMS());
-
+            dispatch<any>(openSharingDialog(groupUuid, () => {
+                dispatch(GroupMembersPanelActions.REQUEST_ITEMS());
+            }));
         }
     };
 
         }
     };
 
index 54ad6791d6b69c5272d786464e553386221502e0..4c0b88250a676f16a24b5d330af457b1d757670b 100644 (file)
@@ -21,9 +21,9 @@ import { progressIndicatorActions } from 'store/progress-indicator/progress-indi
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
 import { extractUuidKind, ResourceKind } from "models/resource";
 
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
 import { extractUuidKind, ResourceKind } from "models/resource";
 
-export const openSharingDialog = (resourceUuid: string) =>
+export const openSharingDialog = (resourceUuid: string, refresh?: () => void) =>
     (dispatch: Dispatch) => {
     (dispatch: Dispatch) => {
-        dispatch(dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: resourceUuid }));
+        dispatch(dialogActions.OPEN_DIALOG({ id: SHARING_DIALOG_NAME, data: {resourceUuid, refresh} }));
         dispatch<any>(loadSharingDialog);
     };
 
         dispatch<any>(loadSharingDialog);
     };
 
@@ -34,16 +34,21 @@ export const connectSharingDialog = withDialog(SHARING_DIALOG_NAME);
 export const connectSharingDialogProgress = withProgress(SHARING_DIALOG_NAME);
 
 
 export const connectSharingDialogProgress = withProgress(SHARING_DIALOG_NAME);
 
 
-export const saveSharingDialogChanges = async (dispatch: Dispatch) => {
+export const saveSharingDialogChanges = async (dispatch: Dispatch, getState: () => RootState) => {
     dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
     await dispatch<any>(savePublicPermissionChanges);
     await dispatch<any>(saveManagementChanges);
     await dispatch<any>(sendInvitations);
     dispatch(reset(SHARING_INVITATION_FORM_NAME));
     await dispatch<any>(loadSharingDialog);
     dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
     await dispatch<any>(savePublicPermissionChanges);
     await dispatch<any>(saveManagementChanges);
     await dispatch<any>(sendInvitations);
     dispatch(reset(SHARING_INVITATION_FORM_NAME));
     await dispatch<any>(loadSharingDialog);
+
+    const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
+    if (dialog && dialog.data.refresh) {
+        dialog.data.refresh();
+    }
 };
 
 };
 
-export const sendSharingInvitations = async (dispatch: Dispatch) => {
+export const sendSharingInvitations = async (dispatch: Dispatch, getState: () => RootState) => {
     dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
     await dispatch<any>(sendInvitations);
     dispatch(closeSharingDialog());
     dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
     await dispatch<any>(sendInvitations);
     dispatch(closeSharingDialog());
@@ -52,15 +57,25 @@ export const sendSharingInvitations = async (dispatch: Dispatch) => {
         kind: SnackbarKind.SUCCESS,
     }));
     dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
         kind: SnackbarKind.SUCCESS,
     }));
     dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
+    
+    const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
+    if (dialog && dialog.data.refresh) {
+        dialog.data.refresh();
+    }
 };
 
 };
 
+interface SharingDialogData {
+    resourceUuid: string;
+    refresh: () => void;
+}
+
 const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
 
 const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
 
-    const dialog = getDialog<string>(getState().dialog, SHARING_DIALOG_NAME);
+    const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
     if (dialog) {
         dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
         try {
     if (dialog) {
         dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
         try {
-            const { items } = await permissionService.listResourcePermissions(dialog.data);
+            const { items } = await permissionService.listResourcePermissions(dialog.data.resourceUuid);
             dispatch<any>(initializePublicAccessForm(items));
             await dispatch<any>(initializeManagementForm(items));
             dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
             dispatch<any>(initializePublicAccessForm(items));
             await dispatch<any>(initializeManagementForm(items));
             dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
@@ -133,7 +148,7 @@ const initializePublicAccessForm = (permissionLinks: PermissionResource[]) =>
 const savePublicPermissionChanges = async (_: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
     const state = getState();
     const { user } = state.auth;
 const savePublicPermissionChanges = async (_: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
     const state = getState();
     const { user } = state.auth;
-    const dialog = getDialog<string>(state.dialog, SHARING_DIALOG_NAME);
+    const dialog = getDialog<SharingDialogData>(state.dialog, SHARING_DIALOG_NAME);
     if (dialog && user) {
         const { permissionUuid, visibility } = getSharingPublicAccessFormData(state);
 
     if (dialog && user) {
         const { permissionUuid, visibility } = getSharingPublicAccessFormData(state);
 
@@ -150,7 +165,7 @@ const savePublicPermissionChanges = async (_: Dispatch, getState: () => RootStat
 
             await permissionService.create({
                 ownerUuid: user.uuid,
 
             await permissionService.create({
                 ownerUuid: user.uuid,
-                headUuid: dialog.data,
+                headUuid: dialog.data.resourceUuid,
                 tailUuid: getPublicGroupUuid(state),
                 name: PermissionLevel.CAN_READ,
             });
                 tailUuid: getPublicGroupUuid(state),
                 name: PermissionLevel.CAN_READ,
             });
@@ -197,7 +212,7 @@ const saveManagementChanges = async (_: Dispatch, getState: () => RootState, { p
 const sendInvitations = async (_: Dispatch, getState: () => RootState, { permissionService, userService }: ServiceRepository) => {
     const state = getState();
     const { user } = state.auth;
 const sendInvitations = async (_: Dispatch, getState: () => RootState, { permissionService, userService }: ServiceRepository) => {
     const state = getState();
     const { user } = state.auth;
-    const dialog = getDialog<string>(state.dialog, SHARING_DIALOG_NAME);
+    const dialog = getDialog<SharingDialogData>(state.dialog, SHARING_DIALOG_NAME);
     if (dialog && user) {
         const invitations = getFormValues(SHARING_INVITATION_FORM_NAME)(state) as SharingInvitationFormData;
 
     if (dialog && user) {
         const invitations = getFormValues(SHARING_INVITATION_FORM_NAME)(state) as SharingInvitationFormData;
 
@@ -207,7 +222,7 @@ const sendInvitations = async (_: Dispatch, getState: () => RootState, { permiss
         const invitationDataUsers = getUsersFromForm
             .map(person => ({
                 ownerUuid: user.uuid,
         const invitationDataUsers = getUsersFromForm
             .map(person => ({
                 ownerUuid: user.uuid,
-                headUuid: dialog.data,
+                headUuid: dialog.data.resourceUuid,
                 tailUuid: person.uuid,
                 name: invitations.permissions
             }));
                 tailUuid: person.uuid,
                 name: invitations.permissions
             }));
@@ -215,7 +230,7 @@ const sendInvitations = async (_: Dispatch, getState: () => RootState, { permiss
         const invitationsDataGroups = getGroupsFromForm.map(
             group => ({
                 ownerUuid: user.uuid,
         const invitationsDataGroups = getGroupsFromForm.map(
             group => ({
                 ownerUuid: user.uuid,
-                headUuid: dialog.data,
+                headUuid: dialog.data.resourceUuid,
                 tailUuid: group.uuid,
                 name: invitations.permissions
             })
                 tailUuid: group.uuid,
                 name: invitations.permissions
             })
diff --git a/src/views-components/dialog-forms/add-group-member-dialog.tsx b/src/views-components/dialog-forms/add-group-member-dialog.tsx
deleted file mode 100644 (file)
index 443191f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-// 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, WrappedFieldArrayProps, FieldArray } from 'redux-form';
-import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
-import { FormDialog } from 'components/form-dialog/form-dialog';
-import { ParticipantSelect, Participant } from 'views-components/sharing-dialog/participant-select';
-import { ADD_GROUP_MEMBERS_DIALOG, ADD_GROUP_MEMBERS_FORM, AddGroupMembersFormData, ADD_GROUP_MEMBERS_USERS_FIELD_NAME, addGroupMembers } from 'store/group-details-panel/group-details-panel-actions';
-import { minLength } from 'validators/min-length';
-
-export const AddGroupMembersDialog = compose(
-    withDialog(ADD_GROUP_MEMBERS_DIALOG),
-    reduxForm<AddGroupMembersFormData>({
-        form: ADD_GROUP_MEMBERS_FORM,
-        onSubmit: (data, dispatch) => {
-            dispatch(addGroupMembers(data));
-        },
-    })
-)(
-    (props: AddGroupMembersDialogProps) =>
-        <FormDialog
-            dialogTitle='Add users'
-            formFields={UsersField}
-            submitLabel='Add'
-            {...props}
-        />
-);
-
-type AddGroupMembersDialogProps = WithDialogProps<{}> & InjectedFormProps<AddGroupMembersFormData>;
-
-const UsersField = () =>
-    <FieldArray
-        name={ADD_GROUP_MEMBERS_USERS_FIELD_NAME}
-        component={UsersSelect as any}
-        validate={UsersFieldValidation} />;
-
-const UsersFieldValidation = [minLength(1, () => 'Select at least one user')];
-
-const UsersSelect = ({ fields }: WrappedFieldArrayProps<Participant>) =>
-    <ParticipantSelect
-        onlyPeople
-        autofocus
-        label='Enter email adresses '
-        items={fields.getAll() || []}
-        onSelect={fields.push}
-        onDelete={fields.remove} />;
index 9ce93bf2ae6e38214186defb3bc87dc02b6455d8..c756a7d6190fe47a7047c47678683e208cb06543 100644 (file)
@@ -88,7 +88,6 @@ import { GroupAttributesDialog } from 'views-components/groups-dialog/attributes
 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 { 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';
 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';
@@ -212,7 +211,6 @@ export const WorkbenchPanel =
             <Grid item>
                 <DetailsPanel />
             </Grid>
             <Grid item>
                 <DetailsPanel />
             </Grid>
-            <AddGroupMembersDialog />
             <AdvancedTabDialog />
             <AttributesApiClientAuthorizationDialog />
             <AttributesKeepServiceDialog />
             <AdvancedTabDialog />
             <AttributesApiClientAuthorizationDialog />
             <AttributesKeepServiceDialog />