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

Create space user's dropdown form for adding other org users #1172

Merged
merged 29 commits into from
Aug 1, 2017
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e56bc8c
Add new constants to check for user role by username
rememberlenny Jul 28, 2017
4ac8ee1
Create ability to get all users in org that aren't in space
rememberlenny Jul 28, 2017
503e7f4
Create functions used to check space org role status
rememberlenny Jul 28, 2017
0bba2f7
Create the component to manage the org parent user selector
rememberlenny Jul 28, 2017
931eef7
Add the selector to the user component
rememberlenny Jul 28, 2017
9fa5cbc
Get the selector based user adding working
rememberlenny Jul 28, 2017
2461e0c
Add a component test
rememberlenny Jul 28, 2017
651e522
Remove unused store cases
rememberlenny Jul 28, 2017
9c2815e
Create check again space users
rememberlenny Jul 28, 2017
fa73119
Take care of linting
rememberlenny Jul 28, 2017
04a8972
Lint tests
rememberlenny Jul 28, 2017
1d3f87f
Fix linting
rememberlenny Jul 28, 2017
55defcd
Remove unused comment
rememberlenny Jul 28, 2017
33a9778
Rename component to be easily grokable
rememberlenny Jul 28, 2017
4947863
Pull out constants
rememberlenny Jul 28, 2017
42cb434
Rework file names and component reference
rememberlenny Jul 28, 2017
a94ab91
Get the error working on the component
rememberlenny Jul 28, 2017
06ad595
Get the working selector check
rememberlenny Jul 28, 2017
02106fb
Merge branch 'master' of github.com:18F/cg-dashboard into lkb-dropdow…
rememberlenny Jul 28, 2017
b98bf11
Save the changes on the user store check
rememberlenny Jul 28, 2017
a7813cf
Fix linting
rememberlenny Jul 28, 2017
80ca7d6
Add the selector detection
rememberlenny Aug 1, 2017
fe47005
Rename the component
rememberlenny Aug 1, 2017
38ed848
Change the function argument names
rememberlenny Aug 1, 2017
ff12ede
Rename usersSelectorDisabled
rememberlenny Aug 1, 2017
5d8b81d
Linting
rememberlenny Aug 1, 2017
57ac1d0
Re-update user action names
rememberlenny Aug 1, 2017
339846c
Fix new var name on params around addUserRoles
rememberlenny Aug 1, 2017
f655f6e
Rename classes for specificity
rememberlenny Aug 1, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 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 @@ -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 OrgUsersSelector from './users_org_user_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 orgUsersSelectorDisabled = UserStore.orgUsersSelectorDisabled();
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,
orgUsersSelectorDisabled,
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 (
<OrgUsersSelector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this component has a lot of attributes. for a lot of attributes, you can make this generic like UserSelector.
For OrgUserSelector, you probably wouldn't need all this. I'm leaning to keeping all of these and just rename to UserSelector and we can refactor for more generic cases later on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it!

orgUsersSelectorDisabled={ this.state.orgUsersSelectorDisabled }
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_org_user_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_PARENT_ENTITY_USER_FORM_GUID = 'users-parent-entity-users-form';

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

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

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

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

_onSubmitForm(errs, values) {
const { currentEntity } = this.props;
const { currentEntityGuid } = this.props;
const apiKey = AUDITOR_NAME;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be renamed to endpoint? When I read api key, I think of an access key, or a secret. I know you didn't introduce this pattern, but maybe we can start renaming now!

Copy link
Contributor

@jcscottiii jcscottiii Jul 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the idea of renaming it! let's move it to resource (i think of endpoint as being the full path).

(coming from me, the person who introduced the pattern lol)

Copy link
Contributor

@jcscottiii jcscottiii Jul 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's another refactor while we are talking about it, (but outside the scope of this PR)
addUserRoles currently only takes one role but it and the argument are plural roles.

This needs to be made singular. Also, apiKey / resource/ endpoint are the actual role in the uri. it could be confusing between whichever variable name is used vs the roles which is a map to the roles used in the frontend for the check boxes.

would like to rename roles but not sure to what.

const roles = SPACE_AUDITOR_NAME;
if (values.userGuid) {
const userGuid = values.userGuid.value;
userActions.addUserRoles(roles, apiKey, 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_PARENT_ENTITY_USER_FORM_GUID }
classes={ ['test-users'] }
label="Username"
name="userGuid"
options={ orgUsers }
validator={ this.validateString }
/>
);
}

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

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

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

}

OrgUsersSelector.propTypes = propTypes;

OrgUsersSelector.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._orgUsersSelectorDisabled = 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._orgUsersSelectorDisabled = 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));
}

orgUsersSelectorDisabled() {
return this._orgUsersSelectorDisabled;
}

inviteDisabled() {
return this._inviteDisabled;
}
Expand Down
64 changes: 64 additions & 0 deletions static_src/test/unit/components/users_org_user_selector.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import '../../global_setup.js';

import React from 'react';
import { shallow } from 'enzyme';
import { Form, FormSelect } from '../../../components/form';
import OrgUsersSelector from '../../../components/users_org_user_selector.jsx';
import PanelDocumentation from '../../../components/panel_documentation.jsx';

describe('<OrgUsersSelector />', function () {
const parentEntityType = 'organization';
const entityType = 'space';
const props = {
currentUserAccess: true,
orgUsersSelectorDisabled: false,
parentEntityUsers: [],
error: {},
parentEntity: parentEntityType,
currentEntityGuid: 'a-space-guid',
currentEntity: entityType
};
let wrapper;


describe('when the working description is displayed as text panel', () => {
beforeEach(() => {
wrapper = shallow(<OrgUsersSelector { ...props } />);
});

it('displays proper message', () => {
const doc = 'Invite an existing user in this organization to this space.';
expect(wrapper.find(PanelDocumentation).find('p').text()).toBe(doc);
});
});

describe('when user selector', () => {
it('renders users', () => {
const username = 'username';
const guid = 'a-guid';
const user = { guid, username };
const parentEntityUsers = [user, user, user];
const usersProps = Object.assign({}, props, { parentEntityUsers });
wrapper = shallow(<OrgUsersSelector { ...usersProps } />);
const formSelect = wrapper.find(Form).find(FormSelect);
expect(formSelect.length).toEqual(1);
expect(formSelect.props().options.length).toEqual(3);
});
it('renders without users', () => {
const usersProps = Object.assign({}, props, { parentEntityUsers: [] });
wrapper = shallow(<OrgUsersSelector { ...usersProps } />);
const formSelect = wrapper.find(Form).find(FormSelect);
expect(formSelect.length).toEqual(1);
expect(formSelect.props().options.length).toEqual(0);
});
});

describe('when user does not have ability to invite other users', () => {
it('does not render <Form /> component', () => {
const noAccessProps = Object.assign({}, props, { currentUserAccess: false });
wrapper = shallow(<OrgUsersSelector { ...noAccessProps } />);

expect(wrapper.find(Form).length).toEqual(0);
});
});
});
15 changes: 15 additions & 0 deletions static_src/test/unit/stores/user_store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,20 @@ describe('UserStore', function () {
});
});

describe('getAllInOrgAndNotSpace()', function() {
it('should find all users that are in an org, without the space users', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am probably misreading this spec, but from the description, shouldn't the testUser be excluded here? They don't have a space_roles key on their object, they do have a space role in their roles.

Should the corresponding method in UserStore also check for the existence of space roles within the roles object?

var spaceGuid = 'sdfadf';
var orgGuid = 'asdfa';
var testUser = { guid: 'adfzxcv', roles: { [orgGuid]: [ 'org_user'] } };

UserStore.push(testUser);

let actual = UserStore.getAllInOrgAndNotSpace(spaceGuid);

expect(actual[0]).toEqual(testUser);
});
});

describe('USER_FETCH', function () {
let user;
beforeEach(function () {
Expand Down Expand Up @@ -909,6 +923,7 @@ describe('UserStore', function () {
});
});
});

describe('isAdmin()', function () {
describe('user with _currentUserIsAdmin', function () {
let user, space, org, actual;
Expand Down