diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js index 0414a2d42b..1e525cbcae 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js @@ -55,7 +55,7 @@ const StudyPermissionsStore = BaseStore.named('StudyPermissionsStore') superCleanup(); }, - update: async selectedUserIds => { + update: async (selectedUserIds, staleUserIds) => { const updateRequest = { usersToAdd: [], usersToRemove: [] }; const parent = getParent(self, 1); @@ -63,7 +63,10 @@ const StudyPermissionsStore = BaseStore.named('StudyPermissionsStore') const userToRequestFormat = uid => ({ uid, permissionLevel: type }); // Set selected users as "usersToAdd" (API is idempotent) - updateRequest.usersToAdd.push(..._.map(selectedUserIds[type], userToRequestFormat)); + // And remove staleUserIds from usersToAdd list + updateRequest.usersToAdd.push( + ..._.differenceWith(selectedUserIds[type], staleUserIds[type], _.isEqual).map(userToRequestFormat), + ); // Set removed users as "usersToRemove" updateRequest.usersToRemove.push( @@ -71,6 +74,8 @@ const StudyPermissionsStore = BaseStore.named('StudyPermissionsStore') userToRequestFormat, ), ); + // Add staleUserIds to usersToRemove + updateRequest.usersToRemove.push(..._.map(staleUserIds[type], userToRequestFormat)); }); // Perform update and reload store diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/users/UsersStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/users/UsersStore.js index 248a2d72e5..13f5ed5a07 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/users/UsersStore.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/users/UsersStore.js @@ -158,7 +158,17 @@ const UsersStore = BaseStore.named('UsersStore') if (user) { result.push(user); } else { - result.push(User.create(getSnapshot(userIdentifier))); + let userSnapshot; + try { + userSnapshot = getSnapshot(userIdentifier); + } catch (error) { + // Note that user might be already deleted. In order to prevent UI from crashing log the error instead + // and not add it to User list + console.log(`User ${userIdentifier.uid} doesn't exist`, error); + } + if (userSnapshot) { + result.push(User.create(userSnapshot)); + } } // this could happen in the employee is no longer active or with the company } }); diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/StudyPermissionsTable.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/StudyPermissionsTable.js index 340c269683..1e91be1866 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/StudyPermissionsTable.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/StudyPermissionsTable.js @@ -59,8 +59,10 @@ class StudyPermissionsTable extends React.Component { // Set users who currently have permission to the study as the selected users this.study.userTypes.forEach(userType => { this.selectedUserIds[userType] = this.permissionsStore.studyPermissions[`${userType}Users`]; - }); + // determine staleUserIds + this.staleUserIds[userType] = getStaleUsers(this.selectedUserIds[userType], this.usersStore); + }); // Show edit dropdowns via observable this.editModeOn = true; }; @@ -69,6 +71,7 @@ class StudyPermissionsTable extends React.Component { this.editModeOn = false; this.isProcessing = false; this.selectedUserIds = {}; + this.staleUserIds = {}; }; submitUpdate = async () => { @@ -78,7 +81,7 @@ class StudyPermissionsTable extends React.Component { // Perform update try { - await this.permissionsStore.update(this.selectedUserIds); + await this.permissionsStore.update(this.selectedUserIds, this.staleUserIds); displaySuccess('Update Succeeded'); runInAction(() => { this.resetForm(); @@ -195,13 +198,28 @@ class StudyPermissionsTable extends React.Component { } } +function getStaleUsers(selectedUserIds, usersStore) { + const userIdentifiers = _.map(selectedUserIds, uid => ({ uid })); + const users = usersStore.asUserObjects(userIdentifiers); + const userIdDict = Object.assign({}, ...users.map(user => ({ [user.id]: user }))); + const invalidUserIds = []; + selectedUserIds.forEach(userId => { + if (!(userId in userIdDict)) { + invalidUserIds.push(userId); + } + }); + return invalidUserIds; +} + decorate(StudyPermissionsTable, { editModeOn: observable, isProcessing: observable, selectedUserIds: observable, + staleUserIds: observable, enableEditMode: action, resetForm: action, submitUpdate: action, }); export default inject('userStore', 'usersStore')(observer(StudyPermissionsTable)); +export { getStaleUsers }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/__tests__/StudyPermissionsTable.test.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/__tests__/StudyPermissionsTable.test.js new file mode 100644 index 0000000000..23d41d6c94 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/studies/__tests__/StudyPermissionsTable.test.js @@ -0,0 +1,35 @@ +import { getStaleUsers } from '../StudyPermissionsTable'; + +describe('StudyPermissionsTable tests', () => { + describe('getStaleUsers', () => { + it('Test stale users when no users are stale', async () => { + const usersStore = {}; + usersStore.asUserObjects = jest.fn().mockImplementationOnce(() => { + return [{ id: 1 }, { id: 2 }]; + }); + const staleUsers = getStaleUsers([1, 2], usersStore); + expect(staleUsers).toEqual([]); + expect(usersStore.asUserObjects).toHaveBeenCalledTimes(1); + }); + + it('Test stale users when all users are stale', async () => { + const usersStore = {}; + usersStore.asUserObjects = jest.fn().mockImplementationOnce(() => { + return []; + }); + const staleUsers = getStaleUsers([1, 2], usersStore); + expect(staleUsers).toEqual([1, 2]); + expect(usersStore.asUserObjects).toHaveBeenCalledTimes(1); + }); + + it('Test stale users when one user is stale', async () => { + const usersStore = {}; + usersStore.asUserObjects = jest.fn().mockImplementationOnce(() => { + return [{ id: 1 }, { id: 4 }, { id: 3 }]; + }); + const staleUsers = getStaleUsers([1, 2, 4, 3], usersStore); + expect(staleUsers).toEqual([2]); + expect(usersStore.asUserObjects).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/users/UpdateUser.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/users/UpdateUser.js index 6d5ba0176d..abd8533786 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/users/UpdateUser.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/users/UpdateUser.js @@ -149,18 +149,6 @@ class UpdateUser extends React.Component { disabled: this.processing, }); - const deleteButton = - // TODO: deletion actions should be confirmed by user first - this.view === 'detail' - ? makeButton({ - label: 'Delete', - floated: 'right', - color: 'red', - onClick: this.handleDeleteClick, - disabled: currentUser.isRootUser || this.processing, - }) - : ''; - const activeButton = this.props.user.status === 'pending' || this.props.user.status === 'inactive' ? makeButton({ @@ -191,7 +179,6 @@ class UpdateUser extends React.Component {