Skip to content
This repository has been archived by the owner on May 19, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1163 from 18F/lkb-space-remove
Browse files Browse the repository at this point in the history
Add error message when Space user exists on Org user removal.
  • Loading branch information
rememberlenny authored Jul 24, 2017
2 parents a6d281f + 607daf9 commit c447c73
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 133 deletions.
71 changes: 63 additions & 8 deletions static_src/actions/user_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import OrgStore from '../stores/org_store';
import SpaceStore from '../stores/space_store';

const ORG_NAME = OrgStore.cfName;
const MSG_USER_HAS_SPACE_ROLES = 'This user can\'t be removed because they still have a space ' +
'role within the organization. Please remove all space ' +
'associations before removing this user from the organization. ' +
'To review how, click the "Managing Teammates" link below.';

const userActions = {
fetchOrgUsers(orgGuid) {
Expand Down Expand Up @@ -60,12 +64,55 @@ const userActions = {
});
},

receivedOrgSpacesToExtractSpaceUsers(orgSpaces) {
const orgSpaceUsers = orgSpaces.map((orgSpace) => Promise.resolve(
cfApi.fetchSpaceUserRoles(orgSpace.guid)
));
return Promise.all(orgSpaceUsers);
},

fetchUserAssociationsToOrgSpaces(userGuid, orgGuid) {
return Promise.resolve(cfApi.fetchAllOrgSpaces(orgGuid))
.then((orgSpaces) => userActions.receivedOrgSpacesToExtractSpaceUsers(orgSpaces));
},

deleteUserOrDisplayNotice(spaceUsers, userGuid, orgGuid) {
const usersSpaces = spaceUsers.filter(spaceUser => spaceUser.guid === userGuid);
if (usersSpaces.length > 0) {
userActions.createUserSpaceAssociationNotification(MSG_USER_HAS_SPACE_ROLES);
} else {
userActions.deleteUser(userGuid, orgGuid);
}
},

deleteUserIfNoSpaceAssociation(userGuid, orgGuid) {
return Promise.resolve(userActions.fetchUserAssociationsToOrgSpaces(userGuid, orgGuid))
.then((spaceUsers) => userActions.deleteUserOrDisplayNotice(spaceUsers, userGuid, orgGuid));
},

deleteUser(userGuid, orgGuid) {
AppDispatcher.handleViewAction({
type: userActionTypes.USER_DELETE,
userGuid,
orgGuid
});

return cfApi.deleteUser(userGuid, orgGuid)
.then(() => userActions.deletedUser(userGuid, orgGuid))
.catch(error => {
// Check whether we got caught on user roles in spaces
const userHasSpaceRoles = (error &&
error.response &&
error.response.status === 400 &&
error.response.data.error_code === 'CF-AssociationNotEmpty'
);
if (userHasSpaceRoles) {
this.createUserSpaceAssociationNotification(MSG_USER_HAS_SPACE_ROLES);
} else {
// else use generic error
this.errorRemoveUser(userGuid, error.response.data);
}
});
},

deletedUser(userGuid, orgGuid) {
Expand Down Expand Up @@ -198,12 +245,24 @@ const userActions = {
inviting ${email}`));
},

clearInviteNotifications() {
clearUserListNotifications() {
AppDispatcher.handleViewAction({
type: userActionTypes.USER_INVITE_STATUS_DISMISSED
type: userActionTypes.USER_LIST_NOTICE_DISMISSED
});
},

createUserListNotification(noticeType, description) {
AppDispatcher.handleViewAction({
type: userActionTypes.USER_LIST_NOTICE_CREATED,
noticeType,
description
});
},

createUserSpaceAssociationNotification(notification) {
userActions.createUserListNotification('error', notification);
},

createInviteNotification(verified, email) {
let description;
const noticeType = 'finish';
Expand All @@ -223,14 +282,10 @@ const userActions = {
'be controlled below.';
}

AppDispatcher.handleViewAction({
type: userActionTypes.USER_INVITE_STATUS_DISPLAYED,
noticeType,
description
});
userActions.createUserListNotification(noticeType, description);
},

userInviteError(err, contextualMessage) {
userListNoticeError(err, contextualMessage) {
AppDispatcher.handleServerAction({
type: userActionTypes.USER_INVITE_ERROR,
err,
Expand Down
14 changes: 7 additions & 7 deletions static_src/components/users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ function stateSetter() {
loading: UserStore.loading,
empty: !UserStore.loading && !users.length,
users,
inviteNotices: UserStore._inviteNotification,
userInviteError: UserStore.getInviteError()
userListNotices: UserStore._userListNotification,
userListNoticeError: UserStore.getUserListNotificationError()
};
}

Expand All @@ -82,7 +82,7 @@ export default class Users extends React.Component {

onNotificationDismiss(ev) {
ev.preventDefault();
userActions.clearInviteNotifications();
userActions.clearUserListNotifications();
}

handleAddPermissions(roleKey, apiKey, userGuid) {
Expand Down Expand Up @@ -143,14 +143,14 @@ export default class Users extends React.Component {
let notification;
let userInvite;

if (this.state.inviteNotices.description) {
const notice = this.state.inviteNotices;
if (this.state.userListNotices.description) {
const notice = this.state.userListNotices;
notification = (
<Notification
message={ notice.description }
actions={ [] }
onDismiss={ this.onNotificationDismiss }
status="finish"
status={ notice.noticeType }
/>
);
} else {
Expand All @@ -164,7 +164,7 @@ export default class Users extends React.Component {
<UsersInvite
inviteDisabled={ this.state.inviteDisabled }
currentUserAccess={ this.state.currentUserAccess }
error={ this.state.userInviteError }
error={ this.state.userListNoticeError }
/>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions static_src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ const userActionTypes = keymirror({
USER_INVITE_TRIGGER: null,
// Action to trigger when invite status is triggered for front end.
USER_INVITE_STATUS_UPDATED: null,
// Action to trigger when user list notice is created.
USER_LIST_NOTICE_CREATED: null,
// Action to trigger email sent to user with cloud.gov invite url.
USER_ORG_ASSOCIATE: null,
// Action to associate user to organization on the server.
Expand All @@ -266,10 +268,8 @@ const userActionTypes = keymirror({
USER_ASSOCIATED_ORG_DISPLAYED: null,
// Action when something goes wrong in user invite and email process.
USER_INVITE_ERROR: null,
// Action to display an invite notification
USER_INVITE_STATUS_DISPLAYED: null,
// Action to dismiss an invite notification
USER_INVITE_STATUS_DISMISSED: null,
// Action to dismiss an user list notification.
USER_LIST_NOTICE_DISMISSED: null,
// Action to delete a user from an org.
USER_DELETE: null,
// Action when a user was deleted from an org on the server.
Expand Down
36 changes: 14 additions & 22 deletions static_src/stores/user_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class UserStore extends BaseStore {
this._error = null;
this._saving = false;
this._inviteDisabled = false;
this._inviteNotification = {};
this._userListNotification = {};
this._loading = {};
}

Expand Down Expand Up @@ -64,7 +64,7 @@ export class UserStore extends BaseStore {

case userActionTypes.USER_INVITE_TRIGGER: {
this._inviteDisabled = true;
this._inviteError = null;
this._userListNotificationError = null;
this.emitChange();
break;
}
Expand Down Expand Up @@ -153,15 +153,7 @@ export class UserStore extends BaseStore {
}

case userActionTypes.USER_DELETE: {
const orgPermissionsReq = cfApi.deleteOrgUserPermissions(
action.userGuid,
action.orgGuid,
'users');

orgPermissionsReq.then(() => {
cfApi.deleteUser(action.userGuid, action.orgGuid);
});

// Nothing should happen.
break;
}

Expand All @@ -179,7 +171,7 @@ export class UserStore extends BaseStore {
}

case userActionTypes.USER_INVITE_ERROR: {
this._inviteError = Object.assign({}, action.err, {
this._userListNotificationError = Object.assign({}, action.err, {
contextualMessage: action.contextualMessage
});
this._inviteDisabled = false;
Expand All @@ -197,18 +189,18 @@ export class UserStore extends BaseStore {
break;
}

case userActionTypes.USER_INVITE_STATUS_DISPLAYED: {
case userActionTypes.USER_LIST_NOTICE_CREATED: {
this._inviteDisabled = false;
const noticeType = action.noticeType;
const description = action.description;
const notice = Object.assign({}, { noticeType }, { description });
this._inviteNotification = notice;
this._userListNotification = notice;
this.emitChange();
break;
}

case userActionTypes.USER_INVITE_STATUS_DISMISSED: {
this._inviteNotification = {};
case userActionTypes.USER_LIST_NOTICE_DISMISSED: {
this._userListNotification = {};
this.emitChange();
break;
}
Expand Down Expand Up @@ -285,8 +277,8 @@ export class UserStore extends BaseStore {
case errorActionTypes.CLEAR: {
this._error = null;
this._saving = false;
this._inviteNotification = {};
this._inviteError = null;
this._userListNotification = {};
this._userListNotificationError = null;
this._loading = {};
this.emitChange();
break;
Expand Down Expand Up @@ -367,12 +359,12 @@ export class UserStore extends BaseStore {
return this._currentUserIsAdmin;
}

getInviteNotification() {
return this._inviteNotification;
getUserListNotification() {
return this._userListNotification;
}

getInviteError() {
return this._inviteError;
getUserListNotificationError() {
return this._userListNotificationError;
}

get isSaving() {
Expand Down
Loading

0 comments on commit c447c73

Please sign in to comment.