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 #1172 from 18F/lkb-dropdown-space-org-users
Browse files Browse the repository at this point in the history
Create space user's dropdown form for adding other org users
  • Loading branch information
rememberlenny authored Aug 1, 2017
2 parents 30cd9db + f655f6e commit 4317913
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 13 deletions.
14 changes: 8 additions & 6 deletions static_src/actions/user_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import UserStore from '../stores/user_store';
import OrgStore from '../stores/org_store';
import SpaceStore from '../stores/space_store';

const ORG_ENTITY = 'organization';
const SPACE_ENTITY = 'space';
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 ' +
Expand Down Expand Up @@ -146,7 +148,7 @@ const userActions = {
});
},

addUserRoles(roles, apiKey, userGuid, entityGuid, entityType) {
addUserRoles(role, resource, userGuid, entityGuid, entityType) {
const apiMethodMap = {
organization: cfApi.putOrgUserPermissions,
space: cfApi.putSpaceUserPermissions
Expand All @@ -155,7 +157,7 @@ const userActions = {

AppDispatcher.handleViewAction({
type: userActionTypes.USER_ROLES_ADD,
roles,
role,
userGuid,
entityGuid,
entityType
Expand All @@ -164,10 +166,10 @@ const userActions = {
return api(
userGuid,
entityGuid,
apiKey
resource
).then(() => {
userActions.addedUserRoles(
roles,
role,
userGuid,
entityGuid,
entityType);
Expand Down Expand Up @@ -291,8 +293,8 @@ const userActions = {
const noticeType = 'finish';
const currentViewedType = UserStore.currentlyViewedType;
const viewTypeNouns = Object.assign({},
{ space_users: { singular: 'space' } },
{ org_users: { singular: 'organization' } }
{ space_users: { singular: SPACE_ENTITY } },
{ org_users: { singular: ORG_ENTITY } }
);
const entity = viewTypeNouns[currentViewedType].singular;

Expand Down
41 changes: 41 additions & 0 deletions static_src/components/users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import OrgStore from '../stores/org_store.js';
import SpaceStore from '../stores/space_store.js';
import UserList from './user_list.jsx';
import UsersInvite from './users_invite.jsx';
import UsersSelector from './users_selector.jsx';
import Notification from './notification.jsx';
import UserStore from '../stores/user_store.js';
import ErrorMessage from './error_message.jsx';
Expand All @@ -33,12 +34,15 @@ function stateSetter() {
const isSaving = UserStore.isSaving;

let users = [];
let parentEntityUsers;
let currentUserAccess = false;
const inviteDisabled = UserStore.inviteDisabled();
const usersSelectorDisabled = UserStore.usersSelectorDisabled();
let entityGuid;

if (currentType === SPACE_NAME) {
users = UserStore.getAllInSpace(currentSpaceGuid);
parentEntityUsers = UserStore.getAllInOrgAndNotSpace(currentSpaceGuid);
entityGuid = currentSpaceGuid;
currentUserAccess = UserStore.hasRole(currentUser.guid, currentSpaceGuid,
SPACE_MANAGER);
Expand All @@ -53,6 +57,7 @@ function stateSetter() {
currentUser,
error: UserStore.getError(),
inviteDisabled,
usersSelectorDisabled,
currentUserAccess,
currentOrgGuid,
currentSpaceGuid,
Expand All @@ -62,6 +67,7 @@ function stateSetter() {
loading: UserStore.loading,
empty: !UserStore.loading && !users.length,
users,
parentEntityUsers,
userListNotices: UserStore._userListNotification,
userListNoticeError: UserStore.getUserListNotificationError()
};
Expand Down Expand Up @@ -140,6 +146,13 @@ export default class Users extends React.Component {
return entityGuid;
}

get currentUserIsSpaceManager() {
const { currentUser } = this.state;
const { currentSpaceGuid } = SpaceStore;

return UserStore.hasRole(currentUser.guid, currentSpaceGuid, SPACE_MANAGER);
}

get currentUserIsOrgManager() {
const { currentUser } = this.state;
const { currentOrgGuid } = OrgStore;
Expand Down Expand Up @@ -186,6 +199,33 @@ export default class Users extends React.Component {
);
}

get userParentEntityUserSelector() {
if (!this.isSpace) {
return null;
}

if (!this.currentUserIsSpaceManager) {
return (
<PanelDocumentation>
Org Managers and Space Managers can add current organization users into this space.
</PanelDocumentation>
);
}

return (
<UsersSelector
usersSelectorDisabled={ this.state.usersSelectorDisabled }
parentEntity={ ORG_ENTITY }
currentEntityGuid={ this.entityGuid }
currentEntity={ this.entityType }
parentEntityUsers={ this.state.parentEntityUsers }
inviteEntityType={ this.entityType }
currentUserAccess={ this.state.currentUserAccess }
error={ this.state.userListNoticeError }
/>
);
}

_onChange() {
this.setState(stateSetter());
}
Expand All @@ -203,6 +243,7 @@ export default class Users extends React.Component {
<div className="test-users">
<ErrorMessage error={this.state.error} />
{ this.userInvite }
{ this.userParentEntityUserSelector }
{ this.notification }
<UserList
users={ this.state.users }
Expand Down
123 changes: 123 additions & 0 deletions static_src/components/users_selector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* Renders a form that allows org users to invite new users
* to cloud.gov
*/

import PropTypes from 'prop-types';
import React from 'react';
import Action from './action.jsx';
import FormStore from '../stores/form_store';
import { Form, FormSelect } from './form';
import PanelDocumentation from './panel_documentation.jsx';
import userActions from '../actions/user_actions';
import { validateString } from '../util/validators';

const AUDITOR_NAME = 'auditors';
const SPACE_AUDITOR_NAME = 'space_auditor';
const USERS_SELECTOR_GUID = 'users-selector';

const propTypes = {
usersSelectorDisabled: PropTypes.bool,
currentUserAccess: PropTypes.bool,
parentEntityUsers: PropTypes.array,
error: PropTypes.object,
parentEntity: PropTypes.string,
currentEntityGuid: PropTypes.string,
currentEntity: PropTypes.string
};
const defaultProps = {
usersSelectorDisabled: false,
currentUserAccess: false,
error: {}
};

export default class UsersSelector extends React.Component {
constructor(props) {
super(props);

this.validateString = validateString().bind(this);
this._onSubmitForm = this._onSubmitForm.bind(this);
}

componentDidMount() {
FormStore.create(USERS_SELECTOR_GUID);
}

_onSubmitForm(errs, values) {
const { currentEntity } = this.props;
const { currentEntityGuid } = this.props;
const userRole = AUDITOR_NAME;
const role = SPACE_AUDITOR_NAME;
if (values.userGuid) {
const userGuid = values.userGuid.value;
userActions.addUserRoles(role, userRole, userGuid, currentEntityGuid, currentEntity);
}
}

get invitationMessage() {
const { parentEntity } = this.props;
const { currentEntity } = this.props;

return `Invite an existing user in this ${parentEntity}` +
` to this ${currentEntity}.`;
}

get userSelector() {
const { parentEntityUsers } = this.props;
const orgUsers = parentEntityUsers.map((user) =>
({ value: user.guid, label: user.username })
);

if (!orgUsers) {
return null;
}

return (
<FormSelect
formGuid={ USERS_SELECTOR_GUID }
classes={ ['test-users-selector'] }
label="Username"
name="userGuid"
options={ orgUsers }
validator={ this.validateString }
/>
);
}

render() {
const { usersSelectorDisabled } = this.props;
const { currentEntity } = this.props;

if (!this.props.currentUserAccess) {
return null;
}

return (
<div className="test-users-selector">
<PanelDocumentation description>
<p>{ this.invitationMessage }</p>
</PanelDocumentation>
<Form
guid={ USERS_SELECTOR_GUID }
classes={ ['users_selector'] }
ref="form"
onSubmit={ this._onSubmitForm }
>
{ this.userSelector }
<Action
label="submit"
type="submit"
disabled={ usersSelectorDisabled }
>
Add user to this { currentEntity }
</Action>
</Form>
</div>
);
}

}

UsersSelector.propTypes = propTypes;

UsersSelector.defaultProps = defaultProps;
15 changes: 14 additions & 1 deletion static_src/stores/user_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class UserStore extends BaseStore {
this._error = null;
this._saving = false;
this._inviteDisabled = false;
this._usersSelectorDisabled = false;
this._userListNotification = {};
this._loading = {};
}
Expand Down Expand Up @@ -189,7 +190,7 @@ export class UserStore extends BaseStore {
this._userListNotificationError = Object.assign({}, action.err, {
contextualMessage: action.contextualMessage
});
this._inviteDisabled = false;
this._usersSelectorDisabled = false;
this.emitChange();
break;
}
Expand Down Expand Up @@ -322,6 +323,14 @@ export class UserStore extends BaseStore {
return usersInOrg.toJS();
}

getAllInOrgAndNotSpace() {
const usersInOrg = this._data.toJS().filter((user) =>
!user.space_roles
);

return usersInOrg;
}

getError() {
return this._error;
}
Expand Down Expand Up @@ -367,6 +376,10 @@ export class UserStore extends BaseStore {
return !!roles.find((role) => wrappedRoles.includes(role));
}

usersSelectorDisabled() {
return this._usersSelectorDisabled;
}

inviteDisabled() {
return this._inviteDisabled;
}
Expand Down
6 changes: 6 additions & 0 deletions static_src/test/functional/pageobjects/user_invite.element.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export default class UserInviteElement extends BaseElement {
return browser.elements('.test-users .complex_list-item').value.length;
}

// TODO move this to user list element.
countNumberOfUserSelectors() {
browser.waitForExist('.test-users-selector');
return browser.elements('.test-users-selector').value.length;
}

// TODO move this to user list element.
getUserByIndex(idx) {
const sel = `.test-users .complex_list-item:nth-child(${idx})`;
Expand Down
8 changes: 3 additions & 5 deletions static_src/test/functional/user_invite.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,13 @@ describe('User roles', function () {
browser.waitForExist('.test-users');
});

it('should not have the user invite panel', function () {
it('should have the user selector panel', function () {
userRoleElement.setAndGetUserRole(cookieManagerOrgXSpaceXX);
browser.url(urlOrgXSpaceXX);

browser.waitForExist('.test-users');
browser.waitForExist('.test-users-invite', 500, true);
const ells = browser.elements('.test-users-invite');
const userSelectorCount = userInviteElement.countNumberOfUserSelectors();

expect(ells.length).toBe(undefined);
expect(userSelectorCount).toBe(1);
});
});
});
2 changes: 1 addition & 1 deletion static_src/test/unit/actions/user_actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ describe('userActions', function() {
// expectedParams for the params dispatched with userActionTypes.USER_ROLES_ADD.
// the apiKey is not sent with it.
let expectedParams = {
roles: expectedRole,
role: expectedRole,
userGuid: expectedUserGuid,
entityGuid: expectedGuid,
entityType: expectedType
Expand Down
Loading

0 comments on commit 4317913

Please sign in to comment.