diff --git a/ui/src/__tests__/pages/workflow/__snapshots__/admin.test.js.snap b/ui/src/__tests__/pages/workflow/__snapshots__/admin.test.js.snap
index dc4910d9d97..c65654ee84a 100644
--- a/ui/src/__tests__/pages/workflow/__snapshots__/admin.test.js.snap
+++ b/ui/src/__tests__/pages/workflow/__snapshots__/admin.test.js.snap
@@ -981,210 +981,6 @@ exports[`PendingApprovalPage should render 1`] = `
}
.emotion-115 {
- background-color: #3570f40D;
-}
-
-.emotion-117 {
- text-align: left;
- vertical-align: middle;
- word-break: break-all;
- padding: 5px 0px 5px 15px;
- white-space: nowrap;
-}
-
-.emotion-120 {
- text-align: left;
- vertical-align: middle;
- word-break: break-all;
- padding: 5px 0px 5px 0px;
-}
-
-.emotion-128 {
- text-align: left;
- vertical-align: middle;
- padding: 5px 0px 5px 15px;
-}
-
-.emotion-138 {
- font-family: Helvetica,Arial,sans-serif;
- font-size: 14px;
- font-weight: 300;
- box-sizing: border-box;
- display: inline-flex;
- -webkit-box-flex-flow: column nowrap;
- -webkit-flex-flow: column nowrap;
- -ms-flex-flow: column nowrap;
- flex-flow: column nowrap;
- position: relative;
- width: 100%;
-}
-
-.emotion-138>textarea {
- -webkit-appearance: none;
- -moz-appearance: none;
- -ms-appearance: none;
- appearance: none;
- background: rgba(53,112,244,0.05);
- border-top: 2px solid transparent;
- border-bottom: 2px solid transparent;
- border-right: transparent;
- border-left: transparent;
- border-radius: 2px;
- box-shadow: none;
- box-sizing: border-box;
- color: #303030;
- -webkit-flex: 1 0 auto;
- -ms-flex: 1 0 auto;
- flex: 1 0 auto;
- font: inherit;
- height: auto;
- line-height: 1.5;
- margin: 0;
- outline: none;
- padding: 0.25em 0.75rem;
- text-align: left;
- -webkit-transition: background-color 0.2s ease-in-out,color 0.2s ease-in-out,border 0.2s ease-in-out;
- transition: background-color 0.2s ease-in-out,color 0.2s ease-in-out,border 0.2s ease-in-out;
- width: auto;
-}
-
-.emotion-138>textarea::-webkit-input-placeholder {
- color: rgba(48,48,48,0.6);
-}
-
-.emotion-138>textarea::-moz-placeholder {
- color: rgba(48,48,48,0.6);
-}
-
-.emotion-138>textarea:-ms-input-placeholder {
- color: rgba(48,48,48,0.6);
-}
-
-.emotion-138>textarea::placeholder {
- color: rgba(48,48,48,0.6);
-}
-
-.emotion-138>textarea:active,
-.emotion-138>textarea:focus {
- background: rgba(53,112,244,0.05);
- border-bottom: 2px solid #3570f4;
- color: #303030;
-}
-
-.emotion-138>textarea:invalid {
- background: rgba(53,112,244,0.05);
- border-bottom: 2px solid #d01111;
- color: #303030;
-}
-
-.emotion-138>textarea[disabled] {
- background: rgba(48,48,48,0.05);
- border-bottom: 2px solid rgba(48,48,48,0.05);
- color: rgba(48,48,48,0.25);
- cursor: not-allowed;
-}
-
-.emotion-138 .message {
- font-size: 12px;
- color: #303030;
- left: 0;
- line-height: 1.8;
- position: absolute;
- top: 2.571rem;
-}
-
-.emotion-143>div input {
- position: relative;
- font: 300 14px HelveticaNeue-Reg,Helvetica,Arial,sans-serif;
- background-color: rgba(53, 112, 244, 0.05);
- box-shadow: none;
- color: rgb(48, 48, 48);
- height: 16px;
- min-width: 50px;
- text-align: left;
- border-width: 2px;
- border-style: solid;
- border-color: transparent;
- border-image: initial;
- border-radius: 2px;
- -webkit-flex: 1 0 auto;
- -ms-flex: 1 0 auto;
- flex: 1 0 auto;
- margin: 5px 7px 0px 0px;
- outline: none;
- padding: 0.6em 12px;
- -webkit-transition: background-color 0.2s ease-in-out 0s,color 0.2s ease-in-out 0s,border 0.2s ease-in-out 0s;
- transition: background-color 0.2s ease-in-out 0s,color 0.2s ease-in-out 0s,border 0.2s ease-in-out 0s;
- width: 10em;
-}
-
-.emotion-159 {
- vertical-align: middle;
- word-break: break-all;
- text-align: center;
- position: absolute;
- width: 6em;
- right: 6em;
- height: 96px;
- background-color: rgba(244, 248, 254, 1);
- padding: 5px 0px 5px 14px;
-}
-
-.emotion-161 {
- margin-top: 45px;
-}
-
-.emotion-163 {
- fill: #1dad51;
- cursor: pointer;
- vertical-align: text-bottom;
-}
-
-.emotion-164 {
- vertical-align: middle;
- word-break: break-all;
- text-align: center;
- border-right: none;
- position: absolute;
- width: 6em;
- right: 0em;
- height: 96px;
- background-color: rgba(244, 248, 254, 1);
- padding: 5px 0px 5px 0px;
-}
-
-.emotion-168 {
- fill: #ea0000;
- cursor: pointer;
- vertical-align: text-bottom;
-}
-
-.emotion-213 {
- vertical-align: middle;
- word-break: break-all;
- text-align: center;
- position: absolute;
- width: 6em;
- right: 6em;
- height: 96px;
- background-color: rgba(255, 255, 255, 1);
- padding: 5px 0px 5px 14px;
-}
-
-.emotion-218 {
- vertical-align: middle;
- word-break: break-all;
- text-align: center;
- border-right: none;
- position: absolute;
- width: 6em;
- right: 0em;
- height: 96px;
- background-color: rgba(255, 255, 255, 1);
- padding: 5px 0px 5px 0px;
-}
-
-.emotion-223 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
@@ -1207,7 +1003,7 @@ exports[`PendingApprovalPage should render 1`] = `
width: 20px;
}
-.emotion-225 {
+.emotion-117 {
fill: #303030;
cursor: inherit;
vertical-align: text-bottom;
@@ -1300,10 +1096,10 @@ exports[`PendingApprovalPage should render 1`] = `
width="25px"
>
- notification-solid
+ notification
(
- Admin View) *
+ Admin View)
-
-
-
-
-
-
-
- |
-
- home.domain1
- |
-
- role
- |
- |
-
- testrole1
- |
-
-
- Test
-
-
- (
- user.test1
- )
-
- |
-
- testing1
- |
-
-
- Test
-
-
- (
- user.craman
- )
-
- |
-
- 2022-02-15 18:14 UTC
- |
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
- |
-
-
- |
-
-
-
-
-
-
-
- |
-
- home.domain2
- |
-
- role
- |
- |
-
- add-test
- |
-
-
- Test
-
-
- (
- user.test2
- )
-
- |
-
- test
- |
-
-
- Test
-
-
- (
- user.craman
- )
-
- |
-
- 2022-02-16 16:02 UTC
- |
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
- |
-
-
- |
-
-
+
@@ -2174,11 +1487,11 @@ exports[`PendingApprovalPage should render 1`] = `
data-testid="user-domains"
>
(
- Admin View) *
+ Admin View)
);
});
diff --git a/ui/src/components/pending-approval/PendingApprovalTableRow.js b/ui/src/components/pending-approval/PendingApprovalTableRow.js
index a0e377d4544..fc20aa3bc7b 100644
--- a/ui/src/components/pending-approval/PendingApprovalTableRow.js
+++ b/ui/src/components/pending-approval/PendingApprovalTableRow.js
@@ -215,7 +215,10 @@ export default class PendingApprovalTableRow extends React.Component {
onChange={(event) => {
this.props.auditRefChange(key, event);
}}
- />
+ {...(this.props.selfServe && {
+ defaultValue: this.props.auditRef,
+ })}
+ >
{this.props.auditRefMissing ? (
Justification is Required
) : null}
diff --git a/ui/src/redux/actions/user.js b/ui/src/redux/actions/user.js
index 48c3755e5dc..508d107d575 100644
--- a/ui/src/redux/actions/user.js
+++ b/ui/src/redux/actions/user.js
@@ -43,3 +43,30 @@ export const addUsersToStore = (userList) => ({
userList: userList,
},
});
+
+export const STORE_PENDING_ROLE = 'STORE_PENDING_ROLE';
+export const storePendingRole = (role, domainName, roleName) => {
+ console.log('ROLE: ', {
+ type: STORE_PENDING_ROLE,
+ payload: {
+ role,
+ domainName,
+ roleName,
+ },
+ });
+
+ return {
+ type: STORE_PENDING_ROLE,
+ payload: {
+ role,
+ domainName,
+ roleName,
+ },
+ };
+};
+
+export const STORE_PENDING_GROUP = 'STORE_PENDING_GROUP';
+export const storePendingGroup = (group, domainName, groupName) => ({
+ type: STORE_PENDING_GROUP,
+ payload: { group, domainName, groupName },
+});
diff --git a/ui/src/redux/reducers/user.js b/ui/src/redux/reducers/user.js
index ff5de0aca81..2d69b2c9c5c 100644
--- a/ui/src/redux/reducers/user.js
+++ b/ui/src/redux/reducers/user.js
@@ -18,6 +18,8 @@ import {
ADD_ALL_USERS,
LOAD_PENDING_MEMBERS,
LOAD_RESOURCE_ACCESS_LIST,
+ STORE_PENDING_GROUP,
+ STORE_PENDING_ROLE,
} from '../actions/user';
import {
PROCESS_GROUP_PENDING_MEMBERS_TO_STORE,
@@ -72,6 +74,26 @@ export const user = (state = {}, action) => {
draft.userList = userList;
});
}
+ case STORE_PENDING_ROLE: {
+ return produce(state, (draft) => {
+ if (!draft.pendingMemberRoles) {
+ draft.pendingMemberRoles = {};
+ }
+ draft.pendingMemberRoles[
+ `${payload.domainName}:${payload.roleName}`
+ ] = payload.role;
+ });
+ }
+ case STORE_PENDING_GROUP: {
+ return produce(state, (draft) => {
+ if (!draft.pendingMemberGroups) {
+ draft.pendingMemberGroups = {};
+ }
+ draft.pendingMemberGroups[
+ `${payload.domainName}:${payload.groupName}`
+ ] = payload.group;
+ });
+ }
default:
return state;
}
diff --git a/ui/src/redux/selectors/groups.js b/ui/src/redux/selectors/groups.js
index 396b0e7467b..2345c47f26e 100644
--- a/ui/src/redux/selectors/groups.js
+++ b/ui/src/redux/selectors/groups.js
@@ -30,7 +30,8 @@ export const selectGroups = (state) => {
};
export const selectGroup = (state, domainName, groupName) => {
- return state.groups.groups &&
+ return state.groups &&
+ state.groups.groups &&
state.groups.groups[getFullName(domainName, groupDelimiter, groupName)]
? state.groups.groups[
getFullName(domainName, groupDelimiter, groupName)
@@ -43,7 +44,8 @@ export const thunkSelectGroupMembers = (state, domainName, groupName) => {
};
export const selectGroupMembers = (state, domainName, groupName) => {
- return state.groups.groups &&
+ return state.groups &&
+ state.groups.groups &&
state.groups.groups[getFullName(domainName, groupDelimiter, groupName)]
? membersMapsToList(
state.groups.groups[
diff --git a/ui/src/redux/selectors/roles.js b/ui/src/redux/selectors/roles.js
index f84fd57be13..9c49dfa499e 100644
--- a/ui/src/redux/selectors/roles.js
+++ b/ui/src/redux/selectors/roles.js
@@ -81,14 +81,16 @@ export const selectRoleUsers = (state) => {
};
export const selectRole = (state, domainName, roleName) => {
- return state.roles.roles &&
+ return state.roles &&
+ state.roles.roles &&
state.roles.roles[getFullName(domainName, roleDelimiter, roleName)]
? state.roles.roles[getFullName(domainName, roleDelimiter, roleName)]
: {};
};
export const selectRoleMembers = (state, domainName, roleName) => {
- return state.roles.roles &&
+ return state.roles &&
+ state.roles.roles &&
state.roles.roles[getFullName(domainName, roleDelimiter, roleName)] &&
state.roles.roles[getFullName(domainName, roleDelimiter, roleName)]
.roleMembers
diff --git a/ui/src/redux/selectors/user.js b/ui/src/redux/selectors/user.js
index b0693d9c731..9526fc04c42 100644
--- a/ui/src/redux/selectors/user.js
+++ b/ui/src/redux/selectors/user.js
@@ -22,3 +22,8 @@ export const selectUserResourceAccessList = (state) =>
export const selectAllUsers = (state) => {
return state.user.userList;
};
+
+export const selectPendingMemberRole = (state, domainName, roleName) =>
+ state?.user?.pendingMemberRoles?.[`${domainName}:${roleName}`] ?? null;
+export const selectPendingMemberGroup = (state, domainName, groupName) =>
+ state?.user?.pendingMemberGroups?.[`${domainName}:${groupName}`] ?? null;
diff --git a/ui/src/redux/thunks/user.js b/ui/src/redux/thunks/user.js
index 76b5b025c8e..4b28b9b20d1 100644
--- a/ui/src/redux/thunks/user.js
+++ b/ui/src/redux/thunks/user.js
@@ -17,6 +17,8 @@
import { getExpiryTime, isExpired } from '../utils';
import {
selectAllUsers,
+ selectPendingMemberGroup,
+ selectPendingMemberRole,
selectUserPendingMembers,
selectUserResourceAccessList,
} from '../selectors/user';
@@ -26,6 +28,8 @@ import {
loadUserPendingMembers,
loadUserResourceAccessList,
returnUserResourceAccessList,
+ storePendingGroup,
+ storePendingRole,
} from '../actions/user';
import {
@@ -34,6 +38,93 @@ import {
loadingSuccess,
} from '../actions/loading';
+const ROLE = 'role';
+const GROUP = 'group';
+
+const getPendingMemberRole = async (dispatch, state, domainName, roleName) => {
+ let role = selectPendingMemberRole(state, domainName, roleName);
+ if (!role || isExpired(role.expiry)) {
+ role = await API().getRole(domainName, roleName, false, false, true);
+ role.expiry = getExpiryTime();
+ dispatch(storePendingRole(role, domainName, roleName));
+ }
+ return role;
+};
+const getPendingMemberGroup = async (
+ dispatch,
+ state,
+ domainName,
+ groupName
+) => {
+ let group = selectPendingMemberGroup(state, domainName, groupName);
+ if (!group || isExpired(group.expiry)) {
+ group = await API().getGroup(domainName, groupName, false, true);
+ group.expiry = getExpiryTime();
+ dispatch(storePendingGroup(group, domainName, groupName));
+ }
+ return group;
+};
+
+const prepareSelfServePendingMembers = async (
+ pendingMembers,
+ category,
+ dispatch,
+ state
+) => {
+ // SET MEMBER COMMENT AS AUDITREF FOR SELF-SERVE ROLES/GROUPS
+
+ // set of domain:role/group to search for
+ let roleOrGroupSet = new Set();
+ Object.keys(pendingMembers).forEach((member) => {
+ const memberData = pendingMembers[member];
+ if (category === memberData.category) {
+ roleOrGroupSet.add(
+ `${memberData.domainName}:${memberData.roleName}`
+ );
+ }
+ });
+
+ // setup promises to get roles/groups
+ let promises = [];
+ roleOrGroupSet.forEach((entity) => {
+ let [domain, role] = entity.split(':');
+ if (category === ROLE) {
+ promises.push(getPendingMemberRole(dispatch, state, domain, role));
+ } else if (category === GROUP) {
+ promises.push(getPendingMemberGroup(dispatch, state, domain, role));
+ }
+ });
+
+ let data = await Promise.all(promises);
+
+ // find which roles/groups have selfServe and assign comment as auditRef
+ Object.keys(pendingMembers).forEach((memberName) => {
+ const member = pendingMembers[memberName];
+ if (member.category !== category) {
+ return; // category is different (can be role or group)
+ }
+ // category matches
+ for (let i = 0; i < data.length; i++) {
+ const roleGroup = data[i];
+ if (!roleGroup.selfServe) {
+ continue; // we only look for self-serve roles/groups
+ }
+ const [domain, rest] = roleGroup.name.split(':');
+ if (domain !== member.domainName) {
+ break; // go to next "i" list containing roles/groups for diff domain
+ }
+ // domain matches
+ const [princType, roleGroupName] = rest.split('.');
+ if (roleGroupName === member.roleName) {
+ // role/group name matches pending member's role/group and is self serve
+ // set audetRef as provided comment
+ member.auditRef = member.userComment;
+ member.selfServe = true;
+ }
+ }
+ });
+};
+
export const getUserPendingMembers = () => async (dispatch, getState) => {
const expiry = getExpiryTime();
try {
@@ -44,10 +135,31 @@ export const getUserPendingMembers = () => async (dispatch, getState) => {
) {
let userPendingMembersList =
await API().getPendingDomainMembersList();
+
+ let promises = [];
+ promises.push(
+ prepareSelfServePendingMembers(
+ userPendingMembersList,
+ ROLE,
+ dispatch,
+ getState()
+ )
+ );
+ promises.push(
+ prepareSelfServePendingMembers(
+ userPendingMembersList,
+ GROUP,
+ dispatch,
+ getState()
+ )
+ );
+ await Promise.all(promises);
+
dispatch(loadUserPendingMembers(userPendingMembersList, expiry));
}
} catch (error) {
// if error, set userPendingMembers to empty array
+ console.error(error);
dispatch(loadUserPendingMembers([], expiry));
}
};