Merge pull request #13569 from overleaf/jdt-collab-events

feat: adding events when adding and removing collaborators
GitOrigin-RevId: 5a78a2fc1fb3937ca2c5e08625c628c993545d97
This commit is contained in:
Jimmy Domagala-Tang 2023-07-31 11:27:12 -04:00 committed by Copybot
parent 53c613906f
commit 30d0c268ca
5 changed files with 42 additions and 8 deletions

View file

@ -8,6 +8,7 @@ import { resendInvite, sendInvite } from '../utils/api'
import { useUserContacts } from '../hooks/use-user-contacts'
import useIsMounted from '../../../shared/hooks/use-is-mounted'
import { useProjectContext } from '../../../shared/context/project-context'
import { sendMB } from '../../../infrastructure/event-tracking'
export default function AddCollaborators() {
const [privileges, setPrivileges] = useState('readAndWrite')
@ -82,6 +83,15 @@ export default function AddCollaborators() {
} else {
data = await sendInvite(projectId, email, privileges)
}
sendMB('collaborator-invited', {
project_id: projectId,
// invitation is only populated on successful invite, meaning that for paywall and other cases this will be null
successful_invite: !!data.invite,
users_updated: !!(data.users || data.user),
current_collaborators_amount: members.length,
current_invites_amount: invites.length,
})
} catch (error) {
setInFlight(false)
setError(

View file

@ -8,6 +8,7 @@ import { Button, Col, Form, FormControl, FormGroup } from 'react-bootstrap'
import Tooltip from '../../../shared/components/tooltip'
import Icon from '../../../shared/components/icon'
import { useProjectContext } from '../../../shared/context/project-context'
import { sendMB } from '../../../infrastructure/event-tracking'
export default function EditMember({ member }) {
const [privileges, setPrivileges] = useState(member.privileges)
@ -112,15 +113,21 @@ SelectPrivilege.propTypes = {
function RemoveMemberAction({ member }) {
const { t } = useTranslation()
const { updateProject, monitorRequest } = useShareProjectContext()
const { _id: projectId, members } = useProjectContext()
const { _id: projectId, members, invites } = useProjectContext()
function handleClick(event) {
event.preventDefault()
monitorRequest(() => removeMemberFromProject(projectId, member)).then(
() => {
const updatedMembers = members.filter(existing => existing !== member)
updateProject({
members: members.filter(existing => existing !== member),
members: updatedMembers,
})
sendMB('collaborator-removed', {
project_id: projectId,
current_collaborators_amount: updatedMembers.length,
current_invites_amount: invites.length,
})
}
)

View file

@ -8,6 +8,7 @@ import { Trans, useTranslation } from 'react-i18next'
import MemberPrivileges from './member-privileges'
import { resendInvite, revokeInvite } from '../utils/api'
import { useProjectContext } from '../../../shared/context/project-context'
import { sendMB } from '../../../infrastructure/event-tracking'
export default function Invite({ invite, isProjectOwner }) {
return (
@ -77,14 +78,20 @@ ResendInvite.propTypes = {
function RevokeInvite({ invite }) {
const { t } = useTranslation()
const { updateProject, monitorRequest } = useShareProjectContext()
const { _id: projectId, invites } = useProjectContext()
const { _id: projectId, invites, members } = useProjectContext()
function handleClick(event) {
event.preventDefault()
monitorRequest(() => revokeInvite(projectId, invite)).then(() => {
const updatedInvites = invites.filter(existing => existing !== invite)
updateProject({
invites: invites.filter(existing => existing !== invite),
invites: updatedInvites,
})
sendMB('collaborator-invite-revoked', {
project_id: projectId,
current_invites_amount: updatedInvites.length,
current_collaborators_amount: members.length,
})
})
}

View file

@ -10,6 +10,7 @@ import CopyLink from '../../../shared/components/copy-link'
import { useProjectContext } from '../../../shared/context/project-context'
import * as eventTracking from '../../../infrastructure/event-tracking'
import { useUserContext } from '../../../shared/context/user-context'
import { sendMB } from '../../../infrastructure/event-tracking'
import { getJSON } from '../../../infrastructure/fetch-json'
import useAbortController from '../../../shared/hooks/use-abort-controller'
@ -24,6 +25,9 @@ export default function LinkSharing({ canAddCollaborators }) {
const setAccessLevel = useCallback(
newPublicAccessLevel => {
setInflight(true)
sendMB('link-sharing-click-off', {
project_id: projectId,
})
monitorRequest(() =>
setProjectAccessLevel(projectId, newPublicAccessLevel)
)
@ -43,7 +47,11 @@ export default function LinkSharing({ canAddCollaborators }) {
// Private (with token-access available)
case 'private':
return (
<PrivateSharing setAccessLevel={setAccessLevel} inflight={inflight} />
<PrivateSharing
setAccessLevel={setAccessLevel}
inflight={inflight}
projectId={projectId}
/>
)
// Token-based access
@ -76,7 +84,7 @@ LinkSharing.propTypes = {
canAddCollaborators: PropTypes.bool,
}
function PrivateSharing({ setAccessLevel, inflight }) {
function PrivateSharing({ setAccessLevel, inflight, projectId }) {
return (
<Row className="public-access-level">
<Col xs={12} className="text-center">
@ -88,7 +96,7 @@ function PrivateSharing({ setAccessLevel, inflight }) {
className="btn-inline-link"
onClick={() => {
setAccessLevel('tokenBased')
eventTracking.sendMB('link-sharing-click')
eventTracking.sendMB('link-sharing-click', { projectId })
}}
disabled={inflight}
>
@ -104,6 +112,7 @@ function PrivateSharing({ setAccessLevel, inflight }) {
PrivateSharing.propTypes = {
setAccessLevel: PropTypes.func.isRequired,
inflight: PropTypes.bool,
projectId: PropTypes.string,
}
function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {

View file

@ -58,9 +58,10 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
if (show) {
sendMB('share-modal-opened', {
splitTestVariant: splitTestVariants['null-test-share-modal'],
project_id: project._id,
})
}
}, [splitTestVariants, show])
}, [splitTestVariants, project._id, show])
// reset error when the modal is opened
useEffect(() => {