Merge pull request #19249 from overleaf/ii-invite-token-response

[web] Fix share modal resend status code

GitOrigin-RevId: 303f7b6c49d9522df6317789bb7c3c69d774715f
This commit is contained in:
ilkin-overleaf 2024-07-12 12:00:26 +03:00 committed by Copybot
parent 309340f94a
commit b442a74f54
8 changed files with 122 additions and 59 deletions

View file

@ -240,6 +240,12 @@ const CollaboratorsInviteController = {
inviteId inviteId
) )
EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
{ invites: true }
)
if (invite != null) { if (invite != null) {
ProjectAuditLogHandler.addEntryInBackground( ProjectAuditLogHandler.addEntryInBackground(
projectId, projectId,
@ -251,9 +257,11 @@ const CollaboratorsInviteController = {
privileges: invite.privileges, privileges: invite.privileges,
} }
) )
}
res.status(201).json({ newInviteId: invite._id }) res.sendStatus(201)
} else {
res.sendStatus(404)
}
}, },
async viewInvite(req, res) { async viewInvite(req, res) {

View file

@ -657,8 +657,10 @@
"invalid_password_too_similar": "", "invalid_password_too_similar": "",
"invalid_request": "", "invalid_request": "",
"invite": "", "invite": "",
"invite_expired": "",
"invite_more_collabs": "", "invite_more_collabs": "",
"invite_not_accepted": "", "invite_not_accepted": "",
"invite_resend_limit_hit": "",
"invited_to_group": "", "invited_to_group": "",
"invited_to_group_have_individual_subcription": "", "invited_to_group_have_individual_subcription": "",
"invited_to_join": "", "invited_to_join": "",

View file

@ -44,25 +44,21 @@ Invite.propTypes = {
function ResendInvite({ invite }) { function ResendInvite({ invite }) {
const { t } = useTranslation() const { t } = useTranslation()
const { updateProject, monitorRequest } = useShareProjectContext() const { monitorRequest, setError, inFlight } = useShareProjectContext()
const { _id: projectId, invites } = useProjectContext() const { _id: projectId } = useProjectContext()
// const buttonRef = useRef(null) // const buttonRef = useRef(null)
// //
const handleClick = useCallback( const handleClick = useCallback(
() => () =>
monitorRequest(() => resendInvite(projectId, invite)) monitorRequest(() => resendInvite(projectId, invite))
.then(({ newInviteId }) => { .catch(error => {
const updatedInvites = invites.map(existing => { if (error?.response?.status === 404) {
if (existing === invite) { setError('invite_expired')
// Update the invitation id for the project }
existing._id = newInviteId if (error?.response?.status === 429) {
} setError('invite_resend_limit_hit')
return existing }
})
updateProject({
invites: updatedInvites,
})
}) })
.finally(() => { .finally(() => {
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button` // NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
@ -71,7 +67,7 @@ function ResendInvite({ invite }) {
// } // }
document.activeElement.blur() document.activeElement.blur()
}), }),
[invite, monitorRequest, projectId, invites, updateProject] [invite, monitorRequest, projectId, setError]
) )
return ( return (
@ -79,6 +75,7 @@ function ResendInvite({ invite }) {
bsStyle="link" bsStyle="link"
className="btn-inline-link" className="btn-inline-link"
onClick={handleClick} onClick={handleClick}
disabled={inFlight}
// ref={buttonRef} // ref={buttonRef}
> >
{t('resend')} {t('resend')}

View file

@ -43,21 +43,30 @@ Invite.propTypes = {
function ResendInvite({ invite }) { function ResendInvite({ invite }) {
const { t } = useTranslation() const { t } = useTranslation()
const { monitorRequest } = useShareProjectContext() const { monitorRequest, setError, inFlight } = useShareProjectContext()
const { _id: projectId } = useProjectContext() const { _id: projectId } = useProjectContext()
// const buttonRef = useRef(null) // const buttonRef = useRef(null)
// //
const handleClick = useCallback( const handleClick = useCallback(
() => () =>
monitorRequest(() => resendInvite(projectId, invite)).finally(() => { monitorRequest(() => resendInvite(projectId, invite))
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button` .catch(error => {
// if (buttonRef.current) { if (error?.response?.status === 404) {
// buttonRef.current.blur() setError('invite_expired')
// } }
document.activeElement.blur() if (error?.response?.status === 429) {
}), setError('invite_resend_limit_hit')
[invite, monitorRequest, projectId] }
})
.finally(() => {
// NOTE: disabled as react-bootstrap v0.33.1 isn't forwarding the ref to the `button`
// if (buttonRef.current) {
// buttonRef.current.blur()
// }
document.activeElement.blur()
}),
[invite, monitorRequest, projectId, setError]
) )
return ( return (
@ -65,6 +74,7 @@ function ResendInvite({ invite }) {
bsStyle="link" bsStyle="link"
className="btn-inline-link" className="btn-inline-link"
onClick={handleClick} onClick={handleClick}
disabled={inFlight}
// ref={buttonRef} // ref={buttonRef}
> >
{t('resend')} {t('resend')}

View file

@ -98,6 +98,12 @@ function ErrorMessage({ error }: Pick<ShareProjectModalContentProps, 'error'>) {
case 'too_many_requests': case 'too_many_requests':
return <>{t('too_many_requests')}</> return <>{t('too_many_requests')}</>
case 'invite_expired':
return <>{t('invite_expired')}</>
case 'invite_resend_limit_hit':
return <>{t('invite_resend_limit_hit')}</>
default: default:
return <>{t('generic_something_went_wrong')}</> return <>{t('generic_something_went_wrong')}</>
} }

View file

@ -98,6 +98,12 @@ function ErrorMessage({ error }: Pick<ShareProjectModalContentProps, 'error'>) {
case 'too_many_requests': case 'too_many_requests':
return <>{t('too_many_requests')}</> return <>{t('too_many_requests')}</>
case 'invite_expired':
return <>{t('invite_expired')}</>
case 'invite_resend_limit_hit':
return <>{t('invite_resend_limit_hit')}</>
default: default:
return <>{t('generic_something_went_wrong')}</> return <>{t('generic_something_went_wrong')}</>
} }

View file

@ -951,10 +951,12 @@
"invalid_request": "Invalid Request. Please correct the data and try again.", "invalid_request": "Invalid Request. Please correct the data and try again.",
"invalid_zip_file": "Invalid zip file", "invalid_zip_file": "Invalid zip file",
"invite": "Invite", "invite": "Invite",
"invite_expired": "The invite may have expired",
"invite_more_collabs": "Invite more collaborators", "invite_more_collabs": "Invite more collaborators",
"invite_not_accepted": "Invite not yet accepted", "invite_not_accepted": "Invite not yet accepted",
"invite_not_valid": "This is not a valid project invite", "invite_not_valid": "This is not a valid project invite",
"invite_not_valid_description": "The invite may have expired. Please contact the project owner", "invite_not_valid_description": "The invite may have expired. Please contact the project owner",
"invite_resend_limit_hit": "The invite resend limit hit",
"invited_to_group": "<0>__inviterName__</0> has invited you to join a group subscription on __appName__", "invited_to_group": "<0>__inviterName__</0> has invited you to join a group subscription on __appName__",
"invited_to_group_have_individual_subcription": "__inviterName__ has invited you to join a group __appName__ subscription. If you join this group, you may not need your individual subscription. Would you like to cancel it?", "invited_to_group_have_individual_subcription": "__inviterName__ has invited you to join a group __appName__ subscription. If you join this group, you may not need your individual subscription. Would you like to cancel it?",
"invited_to_group_login": "To accept this invitation you need to log in as __emailAddress__.", "invited_to_group_login": "To accept this invitation you need to log in as __emailAddress__.",

View file

@ -1269,46 +1269,78 @@ describe('CollaboratorsInviteController', function () {
}) })
describe('when generateNewInvite does not produce an error', function () { describe('when generateNewInvite does not produce an error', function () {
beforeEach(function (done) { describe('and returns an invite object', function () {
this.res.callback = () => done() beforeEach(function (done) {
this.CollaboratorsInviteController.generateNewInvite( this.res.callback = () => done()
this.req, this.CollaboratorsInviteController.generateNewInvite(
this.res, this.req,
this.next this.res,
) this.next
}) )
})
it('should produce a 201 response with new invitation id', function () { it('should produce a 201 response', function () {
this.res.status.callCount.should.equal(1) this.res.sendStatus.callCount.should.equal(1)
this.res.status.calledWith(201).should.equal(true) this.res.sendStatus.calledWith(201).should.equal(true)
expect(this.res.json.firstCall.args[0]).to.deep.equal({ })
newInviteId: this.invite._id,
it('should have called generateNewInvite', function () {
this.CollaboratorsInviteHandler.promises.generateNewInvite.callCount.should.equal(
1
)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('should check the rate limit', function () {
this.CollaboratorsInviteController.promises._checkRateLimit.callCount.should.equal(
1
)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'resend-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
}) })
}) })
it('should have called generateNewInvite', function () { describe('and returns a null invite', function () {
this.CollaboratorsInviteHandler.promises.generateNewInvite.callCount.should.equal( beforeEach(function (done) {
1 this.CollaboratorsInviteHandler.promises.generateNewInvite.resolves(
) null
}) )
this.res.callback = () => done()
this.CollaboratorsInviteController.generateNewInvite(
this.req,
this.res,
this.next
)
})
it('should check the rate limit', function () { it('should have called emitToRoom', function () {
this.CollaboratorsInviteController.promises._checkRateLimit.callCount.should.equal( this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
1 this.EditorRealTimeController.emitToRoom
) .calledWith(this.projectId, 'project:membership:changed')
}) .should.equal(true)
})
it('should add a project audit log entry', function () { it('should produce a 404 response when invite is null', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith( this.res.sendStatus.callCount.should.equal(1)
this.projectId, this.res.sendStatus.should.have.been.calledWith(404)
'resend-invite', })
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
}) })
}) })