-
Notifications
You must be signed in to change notification settings - Fork 51
feat(PM-972): copilot invitation with email #1638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider updating the function |
||
emails: [emailToInvite], | ||
role: PROJECT_ROLES.CUSTOMER | ||
}) | ||
|
||
if (failed) { | ||
const error = get(failed, '0.message', 'Unable to invite user') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,15 +6,15 @@ 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' | ||
|
||
const theme = { | ||
container: styles.modalContainer | ||
} | ||
|
||
const UserAddModalContent = ({ projectId, addNewProjectMember, onClose }) => { | ||
const UserAddModalContent = ({ projectId, addNewProjectMember, onMemberInvited, onClose }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider checking if |
||
setAddUserError(error) | ||
setIsAdding(false) | ||
} else { | ||
onMemberInvited(invitations[0] || {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that |
||
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 | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -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 | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hentrymartin can we reuse There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kkartunov afaik, we don't have to set the MEMBERS_API_URL, we are loading the env variables into process.env( work-manager/config/webpack.config.js Line 493 in 7d0fcd9
|
||||
|
||||
export const getAWSContainerFileURL = (key) => `https://${FILE_PICKER_CONTAINER_NAME}.s3.amazonaws.com/${key}` | ||||
|
||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider checking if |
||
const invitedUsers = await fetchInviteMembers(invitedUserIds) | ||
|
||
this.setState({ | ||
projectMembers, | ||
invitedMembers | ||
invitedMembers: invitedMembers.map(m => ({ | ||
...m, | ||
email: m.email || invitedUsers[m.userId].email | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that |
||
})) | ||
}) | ||
const { loggedInUser } = this.props | ||
this.updateLoginUserRoleInProject(projectMembers, loggedInUser) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider specifying the type of |
||
*/ | ||
export async function fetchInviteMembers (userIds) { | ||
const url = `${MEMBERS_API_URL}?${userIds.map(id => `userIds[]=${id}`).join('&')}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that |
||
const { data = [] } = await axiosInstance.get(url) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding error handling for the API request. This will help manage scenarios where the request fails or returns an unexpected response. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function |
||
return createProjectMemberInvite(projectId, params) | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming the
onMemberInvited
prop to something more descriptive, such asonInviteMember
orhandleMemberInvitation
, to clearly convey its purpose and maintain consistency with other handler props.