Kaynağa Gözat

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

feat: adding events when adding and removing collaborators
GitOrigin-RevId: 5a78a2fc1fb3937ca2c5e08625c628c993545d97
Jimmy Domagala-Tang 3 yıl önce
ebeveyn
işleme
30d0c268ca

+ 10 - 0
services/web/frontend/js/features/share-project-modal/components/add-collaborators.js

@@ -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(

+ 9 - 2
services/web/frontend/js/features/share-project-modal/components/edit-member.js

@@ -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,
         })
       }
     )

+ 9 - 2
services/web/frontend/js/features/share-project-modal/components/invite.js

@@ -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,
       })
     })
   }

+ 12 - 3
services/web/frontend/js/features/share-project-modal/components/link-sharing.js

@@ -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 }) {

+ 2 - 1
services/web/frontend/js/features/share-project-modal/components/share-project-modal.js

@@ -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(() => {