From 3b7fbe2f202d8f76ba1a517a6f5df3310d50048b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 10 Apr 2025 22:36:27 +0200 Subject: [PATCH 1/6] feat: copilot invitation with email --- config/constants/development.js | 1 + config/constants/production.js | 1 + src/components/Users/index.js | 1 + src/components/Users/invite-user.modal.js | 5 +++- src/components/Users/user-add.modal.js | 28 ++++++++++++++++++----- src/config/constants.js | 1 + src/containers/Users/index.js | 12 +++++++--- src/services/projects.js | 23 ++++++++++++++----- 8 files changed, 56 insertions(+), 16 deletions(-) diff --git a/config/constants/development.js b/config/constants/development.js index 311336ed..58e34497 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -22,6 +22,7 @@ module.exports = { PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${DEV_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${DEV_API_HOSTNAME}/v5/terms`, + MEMBERS_API_URL: `${DEV_API_HOSTNAME}/v5/members`, RESOURCES_API_URL: `${DEV_API_HOSTNAME}/v5/resources`, RESOURCE_ROLES_API_URL: `${DEV_API_HOSTNAME}/v5/resource-roles`, SUBMISSIONS_API_URL: `${DEV_API_HOSTNAME}/v5/submissions`, diff --git a/config/constants/production.js b/config/constants/production.js index ff837ee2..10a13349 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -21,6 +21,7 @@ module.exports = { PROJECT_API_URL: `${PROD_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${PROD_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${PROD_API_HOSTNAME}/v5/terms`, + MEMBERS_API_URL: `${PROD_API_HOSTNAME}/v5/members`, RESOURCES_API_URL: `${PROD_API_HOSTNAME}/v5/resources`, RESOURCE_ROLES_API_URL: `${PROD_API_HOSTNAME}/v5/resource-roles`, SUBMISSIONS_API_URL: `${PROD_API_HOSTNAME}/v5/submissions`, diff --git a/src/components/Users/index.js b/src/components/Users/index.js index e328e230..3d00edc0 100644 --- a/src/components/Users/index.js +++ b/src/components/Users/index.js @@ -221,6 +221,7 @@ class Users extends Component { ) diff --git a/src/components/Users/invite-user.modal.js b/src/components/Users/invite-user.modal.js index 41b1dff4..37caa3b7 100644 --- a/src/components/Users/invite-user.modal.js +++ b/src/components/Users/invite-user.modal.js @@ -55,7 +55,10 @@ const InviteUserModalContent = ({ projectId, onClose, onMemberInvited, projectMe try { // api restriction: ONLY "customer" role can be invited via email - const { success: invitations = [], failed } = await inviteUserToProject(projectId, emailToInvite, PROJECT_ROLES.CUSTOMER) + const { success: invitations = [], failed } = await inviteUserToProject(projectId, { + emails: [emailToInvite], + role: PROJECT_ROLES.CUSTOMER + }) if (failed) { const error = get(failed, '0.message', 'Unable to invite user') diff --git a/src/components/Users/user-add.modal.js b/src/components/Users/user-add.modal.js index 79f08f93..dd910dc8 100644 --- a/src/components/Users/user-add.modal.js +++ b/src/components/Users/user-add.modal.js @@ -6,7 +6,7 @@ import Modal from '../Modal' import SelectUserAutocomplete from '../SelectUserAutocomplete' import { PROJECT_ROLES } from '../../config/constants' import PrimaryButton from '../Buttons/PrimaryButton' -import { addUserToProject } from '../../services/projects' +import { addUserToProject, inviteUserToProject } from '../../services/projects' import styles from './Users.module.scss' @@ -14,7 +14,7 @@ const theme = { container: styles.modalContainer } -const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { +const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, onClose }) => { const [userToAdd, setUserToAdd] = useState(null) const [userPermissionToAdd, setUserPermissionToAdd] = useState(PROJECT_ROLES.READ) const [showSelectUserError, setShowSelectUserError] = useState(false) @@ -45,10 +45,25 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { setAddUserError(null) try { - const newUserInfo = await addUserToProject(projectId, userToAdd.userId, userPermissionToAdd) - newUserInfo.handle = userToAdd.handle - addNewProjectMember(newUserInfo) - onClose() + if (userPermissionToAdd === PROJECT_ROLES.COPILOT) { + const { success: invitations = [], failed } = await inviteUserToProject(projectId, { + handles: [userToAdd.handle], + role: userPermissionToAdd + }) + if (failed) { + const error = get(failed, '0.message', 'Unable to invite user') + setAddUserError(error) + setIsAdding(false) + } else { + onMemberInvited(invitations[0] || {}) + onClose() + } + } else { + const newUserInfo = await addUserToProject(projectId, userToAdd.userId, userPermissionToAdd) + newUserInfo.handle = userToAdd.handle + addNewProjectMember(newUserInfo) + onClose() + } } catch (e) { const error = get(e, 'response.data.message', 'Unable to add user') setAddUserError(error) @@ -169,6 +184,7 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { UserAddModalContent.propTypes = { projectId: PropTypes.number.isRequired, addNewProjectMember: PropTypes.func.isRequired, + onMemberInvited: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } diff --git a/src/config/constants.js b/src/config/constants.js index 8729c02c..d40ddcbb 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -59,6 +59,7 @@ export const FILE_PICKER_PROGRESS_INTERVAL = 100 export const FILE_PICKER_UPLOAD_RETRY = 2 export const FILE_PICKER_UPLOAD_TIMEOUT = 30 * 60 * 1000 // 30 minutes export const SPECIFICATION_ATTACHMENTS_FOLDER = 'SPECIFICATION_ATTACHMENTS' +export const MEMBERS_API_URL = process.env.MEMBERS_API_URL export const getAWSContainerFileURL = (key) => `https://${FILE_PICKER_CONTAINER_NAME}.s3.amazonaws.com/${key}` diff --git a/src/containers/Users/index.js b/src/containers/Users/index.js index dabaa0b5..4cfb85f6 100644 --- a/src/containers/Users/index.js +++ b/src/containers/Users/index.js @@ -4,7 +4,7 @@ import _ from 'lodash' import PT from 'prop-types' import UsersComponent from '../../components/Users' import { PROJECT_ROLES } from '../../config/constants' -import { fetchProjectById } from '../../services/projects' +import { fetchInviteMembers, fetchProjectById } from '../../services/projects' import { checkAdmin, checkManager } from '../../util/tc' import { @@ -80,12 +80,18 @@ class Users extends Component { } loadProject (projectId) { - fetchProjectById(projectId).then((project) => { + fetchProjectById(projectId).then(async (project) => { const projectMembers = _.get(project, 'members') const invitedMembers = _.get(project, 'invites') + const invitedUserIds = _.filter(_.map(invitedMembers, 'userId')) + const invitedUsers = await fetchInviteMembers(invitedUserIds) + this.setState({ projectMembers, - invitedMembers + invitedMembers: invitedMembers.map(m => ({ + ...m, + email: m.email || invitedUsers[m.userId].email + })) }) const { loggedInUser } = this.props this.updateLoginUserRoleInProject(projectMembers, loggedInUser) diff --git a/src/services/projects.js b/src/services/projects.js index e586a144..a3cf43c0 100644 --- a/src/services/projects.js +++ b/src/services/projects.js @@ -8,7 +8,8 @@ import { GENERIC_PROJECT_MILESTONE_PRODUCT_TYPE, PHASE_PRODUCT_CHALLENGE_ID_FIELD, PHASE_PRODUCT_TEMPLATE_ID, - PROJECTS_API_URL + PROJECTS_API_URL, + MEMBERS_API_URL } from '../config/constants' import { paginationHeaders } from '../util/pagination' import { createProjectMemberInvite } from './projectMemberInvites' @@ -68,6 +69,19 @@ export async function fetchProjectById (id) { return _.get(response, 'data') } +/** + * This fetches the user corresponding to the given userIds + * @param {*} userIds + */ +export async function fetchInviteMembers (userIds) { + const url = `${MEMBERS_API_URL}?${userIds.map(id => `userIds[]=${id}`).join('&')}` + const { data = [] } = await axiosInstance.get(url) + return data.reduce((acc, member) => { + acc[member.userId] = member + return acc + }, {}) +} + /** * Api request for fetching project phases * @param id Project id @@ -118,11 +132,8 @@ export async function addUserToProject (projectId, userId, role) { * @param role * @returns {Promise<*>} */ -export async function inviteUserToProject (projectId, email, role) { - return createProjectMemberInvite(projectId, { - emails: [email], - role: role - }) +export async function inviteUserToProject (projectId, params) { + return createProjectMemberInvite(projectId, params) } /** From 997d8f065da0305d52a10655feda49e606556351 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 16 Apr 2025 10:13:49 +0200 Subject: [PATCH 2/6] fix: show update billing address for project manager who belongs to org --- .../ChallengesComponent/ChallengeList/index.js | 7 ++++++- src/components/UpdateBillingAccount/index.js | 12 ++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/ChallengesComponent/ChallengeList/index.js b/src/components/ChallengesComponent/ChallengeList/index.js index 71079d7d..6d0bc806 100644 --- a/src/components/ChallengesComponent/ChallengeList/index.js +++ b/src/components/ChallengesComponent/ChallengeList/index.js @@ -25,7 +25,7 @@ import Loader from '../../Loader' import UpdateBillingAccount from '../../UpdateBillingAccount' import { CHALLENGE_STATUS, PAGE_SIZE, PAGINATION_PER_PAGE_OPTIONS, PROJECT_ROLES } from '../../../config/constants' -import { checkAdmin, checkReadOnlyRoles } from '../../../util/tc' +import { checkAdmin, checkManager, checkReadOnlyRoles } from '../../../util/tc' require('bootstrap/scss/bootstrap.scss') @@ -406,6 +406,9 @@ class ChallengeList extends Component { } = this.props const isReadOnly = checkReadOnlyRoles(this.props.auth.token) || loginUserRoleInProject === PROJECT_ROLES.READ const isAdmin = checkAdmin(this.props.auth.token) + const isManager = checkManager(this.props.auth.token) + const loginUserId = this.props.auth.user.userId + const isMemberOfActiveProject = activeProject && activeProject.members && activeProject.members.some(member => member.userId === loginUserId) if (warnMessage) { return @@ -496,6 +499,8 @@ class ChallengeList extends Component { currentBillingAccount={currentBillingAccount} updateProject={updateProject} projectId={activeProject.id} + isMemberOfActiveProject={isMemberOfActiveProject} + isManager={isManager} /> ) : ( diff --git a/src/components/UpdateBillingAccount/index.js b/src/components/UpdateBillingAccount/index.js index b20cd9b4..d45de764 100644 --- a/src/components/UpdateBillingAccount/index.js +++ b/src/components/UpdateBillingAccount/index.js @@ -17,7 +17,9 @@ const UpdateBillingAccount = ({ isAdmin, currentBillingAccount, projectId, - updateProject + updateProject, + isMemberOfActiveProject, + isManager }) => { const [isEditing, setIsEditing] = useState(false) const [selectedBillingAccount, setSelectedBillingAccount] = useState(null) @@ -129,7 +131,7 @@ const UpdateBillingAccount = ({ !currentBillingAccount && ( No Billing Account set - {isAdmin && ( + {(isAdmin || (isManager && isMemberOfActiveProject)) && ( {' '} ({' '} @@ -153,7 +155,7 @@ const UpdateBillingAccount = ({ > {isBillingAccountExpired ? 'INACTIVE' : 'ACTIVE'} {' '} - {isAdmin && ( + {(isAdmin || (isManager && isMemberOfActiveProject)) && ( {' '} ({' '} @@ -187,7 +189,9 @@ UpdateBillingAccount.propTypes = { isBillingAccountExpired: PropTypes.bool, isAdmin: PropTypes.bool, projectId: PropTypes.number, - updateProject: PropTypes.func.isRequired + updateProject: PropTypes.func.isRequired, + isMemberOfActiveProject: PropTypes.bool.isRequired, + isManager: PropTypes.bool.isRequired } export default UpdateBillingAccount From c8c10758154955dd133220066afd9afbb23b5851 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 16 Apr 2025 18:33:02 +0200 Subject: [PATCH 3/6] fix: handled errors for certain use cases --- src/components/Users/user-add.modal.js | 7 +++++-- src/containers/Users/index.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/Users/user-add.modal.js b/src/components/Users/user-add.modal.js index dd910dc8..58b68d20 100644 --- a/src/components/Users/user-add.modal.js +++ b/src/components/Users/user-add.modal.js @@ -46,14 +46,17 @@ const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, try { if (userPermissionToAdd === PROJECT_ROLES.COPILOT) { - const { success: invitations = [], failed } = await inviteUserToProject(projectId, { + const { success: invitations = [], failed, ...rest } = await inviteUserToProject(projectId, { handles: [userToAdd.handle], role: userPermissionToAdd }) if (failed) { - const error = get(failed, '0.message', 'Unable to invite user') + const error = get(failed, '0.message', 'User cannot be invited') setAddUserError(error) setIsAdding(false) + } else if (rest.message) { + setAddUserError(rest.message) + setIsAdding(false) } else { onMemberInvited(invitations[0] || {}) onClose() diff --git a/src/containers/Users/index.js b/src/containers/Users/index.js index 4cfb85f6..08ee1f01 100644 --- a/src/containers/Users/index.js +++ b/src/containers/Users/index.js @@ -90,7 +90,7 @@ class Users extends Component { projectMembers, invitedMembers: invitedMembers.map(m => ({ ...m, - email: m.email || invitedUsers[m.userId].email + email: m.email || invitedUsers[m.userId].handle })) }) const { loggedInUser } = this.props From e04fae185233f1073596bd98bad54e478e9cf087 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 22 Apr 2025 23:22:23 +0200 Subject: [PATCH 4/6] fix: show handle for invit --- src/components/UserCard/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/UserCard/index.js b/src/components/UserCard/index.js index 67f1cc08..ef79ce82 100644 --- a/src/components/UserCard/index.js +++ b/src/components/UserCard/index.js @@ -91,7 +91,7 @@ class UserCard extends Component { )}
- {isInvite ? user.email : user.handle} + {isInvite ? (user.email || user.handle) : user.handle}
{!isInvite && ( <> From 50d6f0af540d254e8501a1c6f4350b9f83226094 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 22 Apr 2025 23:22:29 +0200 Subject: [PATCH 5/6] fix: show handle for invit --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 43fa7b2e..6c6566f2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,7 +152,7 @@ workflows: context: org-global filters: &filters-dev branches: - only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page"] + only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", 'pm-1077'] # Production builds are exectuted only on tagged commits to the # master branch. From b0d0f48ab89ae26be30d9a00bb2a55c4a0aabf4a Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 23 Apr 2025 00:28:03 +0200 Subject: [PATCH 6/6] removed circle ci changes --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6c6566f2..43fa7b2e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,7 +152,7 @@ workflows: context: org-global filters: &filters-dev branches: - only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page", 'pm-1077'] + only: ["develop", "PM-803_wm-regression-fixes", "PM-902_show-all-projects-on-challenge-page"] # Production builds are exectuted only on tagged commits to the # master branch.