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

Commit 947a231

Browse files
authored
Merge pull request #1124 from 18F/lkb-existing_user_frontend_notification
Create the frontend notification when a user is invited/associated to an org
2 parents 3f90002 + 5e6ed1c commit 947a231

10 files changed

Lines changed: 316 additions & 21 deletions

File tree

static_src/actions/user_actions.js

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,64 @@ const userActions = {
163163
});
164164

165165
return uaaApi.inviteUaaUser(email)
166-
.then(invite => cfApi.fetchUser(invite.userGuid))
166+
.then(invite => userActions.receivedInviteStatus(invite, email))
167+
.catch(err => userActions.userInviteCreateError(err, `There was a problem
168+
inviting ${email}`));
169+
},
170+
171+
receivedInviteStatus(invite, email) {
172+
const verified = invite.verified;
173+
AppDispatcher.handleViewAction({
174+
type: userActionTypes.USER_INVITE_STATUS_UPDATED,
175+
email,
176+
verified
177+
});
178+
179+
return cfApi.fetchUser(invite.userGuid)
167180
.then(user => userActions.createUserAndAssociate(user))
181+
.then(() => userActions.createInviteNotification(verified, email))
168182
.catch(err => userActions.userInviteCreateError(err, `There was a problem
169183
inviting ${email}`));
170184
},
171185

186+
clearInviteNotifications() {
187+
AppDispatcher.handleViewAction({
188+
type: userActionTypes.USER_INVITE_STATUS_DISMISSED
189+
});
190+
},
191+
192+
createInviteNotification(verified, email) {
193+
let description;
194+
const noticeType = 'finish';
195+
const currentViewedType = UserStore.currentlyViewedType;
196+
const viewTypeNouns = {
197+
org_users: {
198+
singular: 'organization'
199+
},
200+
space_users: {
201+
singular: 'space'
202+
}
203+
};
204+
205+
if (verified) {
206+
description = `The account for ${email} is now associated to this ` +
207+
`${viewTypeNouns[currentViewedType].singular}. Control their ` +
208+
`${viewTypeNouns[currentViewedType].singular} roles below.`;
209+
} else {
210+
description = `There was no cloud.gov account found for ${email} ` +
211+
'or the user has not verified their account by logging in.' +
212+
'They have been sent an email cloud.gov invitation. Their account ' +
213+
`has been associated to this ${viewTypeNouns[currentViewedType].singular}` +
214+
` and their ${viewTypeNouns[currentViewedType].singular}` +
215+
' roles can be controlled below.';
216+
}
217+
AppDispatcher.handleViewAction({
218+
type: userActionTypes.USER_INVITE_STATUS_DISPLAYED,
219+
noticeType,
220+
description
221+
});
222+
},
223+
172224
userInviteError(err, contextualMessage) {
173225
AppDispatcher.handleServerAction({
174226
type: userActionTypes.USER_INVITE_ERROR,
@@ -311,7 +363,4 @@ const userActions = {
311363
}
312364
};
313365

314-
const _userActions = userActions;
315-
window.useraction = _userActions;
316-
317366
export default userActions;

static_src/components/users.jsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import OrgStore from '../stores/org_store.js';
1111
import SpaceStore from '../stores/space_store.js';
1212
import UserList from './user_list.jsx';
1313
import UsersInvite from './users_invite.jsx';
14+
import Notification from './notification.jsx';
1415
import UserStore from '../stores/user_store.js';
1516

1617
const SPACE_NAME = 'space_users';
@@ -50,6 +51,7 @@ function stateSetter() {
5051
loading: UserStore.loading,
5152
empty: !UserStore.loading && !users.length,
5253
users,
54+
inviteNotices: UserStore._inviteNotification,
5355
userInviteError: UserStore.getInviteError()
5456
};
5557
}
@@ -74,13 +76,9 @@ export default class Users extends React.Component {
7476
UserStore.removeChangeListener(this._onChange);
7577
}
7678

77-
_onChange() {
78-
this.setState(stateSetter());
79-
}
80-
81-
handleRemove(userGuid, ev) {
79+
onNotificationDismiss(ev) {
8280
ev.preventDefault();
83-
userActions.deleteUser(userGuid, this.state.currentOrgGuid);
81+
userActions.clearInviteNotifications();
8482
}
8583

8684
handleAddPermissions(roleKey, apiKey, userGuid) {
@@ -99,6 +97,11 @@ export default class Users extends React.Component {
9997
this.entityType);
10098
}
10199

100+
handleRemove(userGuid, ev) {
101+
ev.preventDefault();
102+
userActions.deleteUser(userGuid, this.state.currentOrgGuid);
103+
}
104+
102105
get entityType() {
103106
return this.state.currentType === ORG_NAME ? 'org' : 'space';
104107
}
@@ -109,6 +112,10 @@ export default class Users extends React.Component {
109112
return entityGuid;
110113
}
111114

115+
_onChange() {
116+
this.setState(stateSetter());
117+
}
118+
112119
render() {
113120
let removeHandler;
114121
let errorMessage;
@@ -137,6 +144,20 @@ export default class Users extends React.Component {
137144
);
138145
}
139146

147+
let notification;
148+
149+
if (this.state.inviteNotices.description) {
150+
const notice = this.state.inviteNotices;
151+
notification = (
152+
<Notification
153+
message={ notice.description }
154+
actions={ [] }
155+
onDismiss={ this.onNotificationDismiss }
156+
status="finish"
157+
/>
158+
);
159+
}
160+
140161
return (
141162
<div className="test-users">
142163
{ errorMessage }
@@ -145,6 +166,7 @@ export default class Users extends React.Component {
145166
currentUserAccess={ this.state.currentUserAccess }
146167
error={ this.state.userInviteError }
147168
/>
169+
{ notification }
148170
<div>
149171
<div>
150172
{ content }
@@ -153,9 +175,8 @@ export default class Users extends React.Component {
153175
</div>
154176
);
155177
}
156-
157178
}
158179

159-
Users.propTypes = { };
180+
Users.propTypes = {};
160181

161-
Users.defaultProps = { };
182+
Users.defaultProps = {};

static_src/components/users_invite.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ import FormStore from '../stores/form_store';
1111
import { Form, FormText } from './form';
1212
import PanelDocumentation from './panel_documentation.jsx';
1313
import userActions from '../actions/user_actions';
14+
1415
import { validateString } from '../util/validators';
1516

17+
import createStyler from '../util/create_styler';
18+
import style from 'cloudgov-style/css/cloudgov-style.css';
19+
1620
const USERS_INVITE_FORM_GUID = 'users-invite-form';
1721

1822
const propTypes = {
@@ -39,6 +43,8 @@ export default class UsersInvite extends React.Component {
3943
this.props = props;
4044
this.state = stateSetter(props);
4145

46+
this.styler = createStyler(style);
47+
4248
this.validateString = validateString().bind(this);
4349
this._onValidForm = this._onValidForm.bind(this);
4450
}
@@ -59,6 +65,7 @@ export default class UsersInvite extends React.Component {
5965

6066
render() {
6167
let content;
68+
6269
if (this.props.currentUserAccess) {
6370
content = (
6471
<div>

static_src/constants.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ const entityHealth = keymirror({
2222
warning: null,
2323
// Something is definitely not right and requires some attention
2424
error: null,
25+
// Entity process was successful
26+
finish: null,
2527
// Entity is inactive
2628
inactive: null,
2729
// This warrants a bug, so we can figure out what the correct health
@@ -252,6 +254,8 @@ const userActionTypes = keymirror({
252254
USER_ROLES_DELETED: null,
253255
// Action to request an invite link from UAA, with user GUID.
254256
USER_INVITE_TRIGGER: null,
257+
// Action to trigger when invite status is triggered for front end.
258+
USER_INVITE_STATUS_UPDATED: null,
255259
// Action to trigger email sent to user with cloud.gov invite url.
256260
USER_ORG_ASSOCIATE: null,
257261
// Action to associate user to organization on the server.
@@ -260,6 +264,10 @@ const userActionTypes = keymirror({
260264
USER_ASSOCIATED_ORG_DISPLAYED: null,
261265
// Action when something goes wrong in user invite and email process.
262266
USER_INVITE_ERROR: null,
267+
// Action to display an invite notification
268+
USER_INVITE_STATUS_DISPLAYED: null,
269+
// Action to dismiss an invite notification
270+
USER_INVITE_STATUS_DISMISSED: null,
263271
// Action to delete a user from an org.
264272
USER_DELETE: null,
265273
// Action when a user was deleted from an org on the server.

static_src/stores/user_store.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class UserStore extends BaseStore {
2121
this._error = null;
2222
this._saving = false;
2323
this._inviteDisabled = false;
24+
this._inviteNotification = {};
2425
this._loading = {};
2526
}
2627

@@ -78,7 +79,6 @@ export class UserStore extends BaseStore {
7879
} else {
7980
this.merge('guid', user, () => {});
8081
}
81-
this._inviteDisabled = false;
8282
this.emitChange();
8383
break;
8484
}
@@ -177,6 +177,23 @@ export class UserStore extends BaseStore {
177177
this._inviteError = Object.assign({}, action.err, {
178178
contextualMessage: action.contextualMessage
179179
});
180+
this._inviteDisabled = false;
181+
this.emitChange();
182+
break;
183+
}
184+
185+
case userActionTypes.USER_INVITE_STATUS_DISPLAYED: {
186+
this._inviteDisabled = false;
187+
const noticeType = action.noticeType;
188+
const description = action.description;
189+
const notice = Object.assign({}, { noticeType }, { description });
190+
this._inviteNotification = notice;
191+
this.emitChange();
192+
break;
193+
}
194+
195+
case userActionTypes.USER_INVITE_STATUS_DISMISSED: {
196+
this._inviteNotification = {};
180197
this.emitChange();
181198
break;
182199
}
@@ -276,10 +293,6 @@ export class UserStore extends BaseStore {
276293
return this._error;
277294
}
278295

279-
get currentlyViewedType() {
280-
return this._currentViewedType;
281-
}
282-
283296
get isLoadingCurrentUser() {
284297
return this._loading.currentUser === true;
285298
}
@@ -329,6 +342,10 @@ export class UserStore extends BaseStore {
329342
return this._currentUserIsAdmin;
330343
}
331344

345+
getInviteNotification() {
346+
return this._inviteNotification;
347+
}
348+
332349
getInviteError() {
333350
return this._inviteError;
334351
}
@@ -337,10 +354,12 @@ export class UserStore extends BaseStore {
337354
return this.get(this._currentUserGuid);
338355
}
339356

357+
get currentlyViewedType() {
358+
return this._currentViewedType;
359+
}
360+
340361
}
341362

342363
const _UserStore = new UserStore();
343364

344-
window.userstore = _UserStore;
345-
346365
export default _UserStore;

static_src/test/functional/user_role.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ describe('User roles', function () {
123123
// sets cookie to org X manager
124124
cookieValue = cookieManagerOrgX;
125125
});
126+
126127
describe('shouldn\'t have permission to edit fields on org Y pages', function () {
127128
it('should set url to org Y', function () {
128129
browser.url(urlOrgY);
@@ -285,6 +286,7 @@ describe('User roles', function () {
285286
// sets cookie to space manager YY
286287
cookieValue = cookieManagerOrgXSpaceYY;
287288
});
289+
288290
describe('shouldn\'t have permission to edit fields on org X space XX pages', function () {
289291
it('should set url to org Y', function () {
290292
browser.url(urlOrgXSpaceXX);

0 commit comments

Comments
 (0)