Просмотр исходного кода

Merge pull request #19206 from overleaf/tm-link-sharing-changes-events

Implement link sharing warning events by reusing the StartFreeTrial paywall button component from the original modal

GitOrigin-RevId: 9c16407ad8a7b5afc9b5b13be1491ef903ae74a3
Thomas 2 лет назад
Родитель
Сommit
566466185b

+ 19 - 10
services/web/app/src/Features/Project/ProjectController.js

@@ -44,6 +44,7 @@ const TutorialHandler = require('../Tutorial/TutorialHandler')
 const UserUpdater = require('../User/UserUpdater')
 const { checkUserPermissions } =
   require('../Authorization/PermissionsManager').promises
+const UserGetter = require('../User/UserGetter')
 
 /**
  * @typedef {import("./types").GetProjectsRequest} GetProjectsRequest
@@ -400,6 +401,8 @@ const _ProjectController = {
           brandVariationId: 1,
           overleaf: 1,
           tokens: 1,
+          tokenAccessReadAndWrite_refs: 1, // used for link sharing analytics
+          collaberator_refs: 1, // used for link sharing analytics
         }),
         userIsMemberOfGroupSubscription: sessionUser
           ? LimitationsManager.promises.userIsMemberOfGroupSubscription(
@@ -546,12 +549,24 @@ const _ProjectController = {
           logger.error({ err }, 'failed to update split test info in session')
         )
       if (userId) {
+        const ownerFeatures = await UserGetter.promises.getUserFeatures(
+          project.owner_ref
+        )
+        const projectOpenedSegmentation = {
+          projectId: project._id,
+          // temporary link sharing segmentation:
+          linkSharingWarning: linkSharingChanges?.variant,
+          namedEditors: project.collaberator_refs?.length || 0,
+          tokenEditors: project.tokenAccessReadAndWrite_refs?.length || 0,
+          planLimit: ownerFeatures?.collaborators || 0,
+        }
+        projectOpenedSegmentation.exceedAtLimit =
+          projectOpenedSegmentation.namedEditors >=
+          projectOpenedSegmentation.planLimit
         AnalyticsManager.recordEventForUserInBackground(
           userId,
           'project-opened',
-          {
-            projectId: project._id,
-          }
+          projectOpenedSegmentation
         )
         User.updateOne(
           { _id: new ObjectId(userId) },
@@ -619,12 +634,6 @@ const _ProjectController = {
           ? 'project/ide-react-detached'
           : 'project/ide-react'
 
-      const assignLink = await SplitTestHandler.promises.getAssignmentForUser(
-        project.owner_ref,
-        'link-sharing-warning'
-      )
-      const linkSharingWarning = assignLink.variant === 'active'
-
       res.render(template, {
         title: project.name,
         priority_title: true,
@@ -698,7 +707,7 @@ const _ProjectController = {
         optionalPersonalAccessToken,
         hasTrackChangesFeature: Features.hasFeature('track-changes'),
         projectTags,
-        linkSharingWarning,
+        linkSharingWarning: linkSharingChanges.variant === 'active',
       })
       timer.done()
     } catch (err) {

+ 34 - 8
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx

@@ -4,14 +4,19 @@ import Notification from '@/shared/components/notification'
 import { upgradePlan } from '../../../../main/account-upgrade'
 import { linkSharingEnforcementDate } from '../../utils/link-sharing'
 import { useProjectContext } from '@/shared/context/project-context'
+import { useUserContext } from '@/shared/context/user-context'
+import { sendMB } from '@/infrastructure/event-tracking'
+import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
 
 export default function AddCollaboratorsUpgrade() {
   const { t } = useTranslation()
   const { features } = useProjectContext()
+  const user = useUserContext()
 
   return (
     <div className="add-collaborators-upgrade">
       <Notification
+        isActionBelowContent
         type="warning"
         title={t('editor_limit_exceeded_in_this_project')}
         content={
@@ -26,20 +31,41 @@ export default function AddCollaboratorsUpgrade() {
         }
         action={
           <div className="upgrade-actions">
+            {user.allowedFreeTrial ? (
+              <StartFreeTrialButton
+                buttonProps={{ variant: 'secondary', size: 'small' }}
+                source="project-sharing"
+                variant="exceeds"
+              >
+                {t('upgrade')}
+              </StartFreeTrialButton>
+            ) : (
+              <Button
+                bsSize="sm"
+                className="btn-secondary"
+                onClick={() => {
+                  upgradePlan('project-sharing')
+                }}
+              >
+                {t('upgrade')}
+              </Button>
+            )}
             <Button
-              bsSize="sm"
-              className="btn-secondary"
-              onClick={() => upgradePlan('project-sharing')}
-            >
-              {t('upgrade')}
-            </Button>
-            <a
               href="https://www.overleaf.com/blog/changes-to-project-sharing"
+              bsSize="sm"
+              className="btn-link"
               target="_blank"
               rel="noreferrer"
+              onClick={() => {
+                sendMB('paywall-info-click', {
+                  'paywall-type': 'project-sharing',
+                  content: 'blog',
+                  variant: 'exceeds',
+                })
+              }}
             >
               {t('read_more')}
-            </a>
+            </Button>
           </div>
         }
       />

+ 22 - 7
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx

@@ -3,10 +3,13 @@ import { useTranslation } from 'react-i18next'
 import Notification from '@/shared/components/notification'
 import { upgradePlan } from '@/main/account-upgrade'
 import { useProjectContext } from '@/shared/context/project-context'
+import { useUserContext } from '@/shared/context/user-context'
+import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
 
 export default function CollaboratorsLimitUpgrade() {
   const { t } = useTranslation()
   const { features } = useProjectContext()
+  const user = useUserContext()
 
   return (
     <div className="invite-warning">
@@ -22,13 +25,25 @@ export default function CollaboratorsLimitUpgrade() {
           </p>
         }
         action={
-          <Button
-            bsSize="sm"
-            className="btn-secondary"
-            onClick={() => upgradePlan('project-sharing')}
-          >
-            {t('upgrade')}
-          </Button>
+          user.allowedFreeTrial ? (
+            <StartFreeTrialButton
+              buttonProps={{ variant: 'secondary', size: 'small' }}
+              source="project-sharing"
+              variant="limit"
+            >
+              {t('upgrade')}
+            </StartFreeTrialButton>
+          ) : (
+            <Button
+              bsSize="sm"
+              className="btn-secondary"
+              onClick={() => {
+                upgradePlan('project-sharing')
+              }}
+            >
+              {t('upgrade')}
+            </Button>
+          )
         }
       />
     </div>

+ 17 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx

@@ -1,6 +1,7 @@
 import { Button, Modal } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import { linkSharingEnforcementDate } from '../../utils/link-sharing'
+import { sendMB } from '@/infrastructure/event-tracking'
 
 type EditorOverLimitModalContentProps = {
   handleHide: () => void
@@ -32,10 +33,25 @@ export default function EditorOverLimitModalContent({
           href="/blog/changes-to-project-sharing"
           target="_blank"
           rel="noreferrer"
+          onClick={() => {
+            sendMB('notification-click', {
+              name: 'link-sharing-collaborator-limit',
+              button: 'learn',
+            })
+          }}
         >
           {t('learn_more')}
         </Button>
-        <Button className="btn-primary" onClick={handleHide}>
+        <Button
+          className="btn-primary"
+          onClick={() => {
+            sendMB('notification-click', {
+              name: 'link-sharing-collaborator-limit',
+              button: 'ok',
+            })
+            handleHide()
+          }}
+        >
           {t('ok')}
         </Button>
       </Modal.Footer>

+ 10 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx

@@ -4,6 +4,7 @@ import EditorOverLimitModalContent from './editor-over-limit-modal-content'
 import customLocalStorage from '@/infrastructure/local-storage'
 import { useProjectContext } from '@/shared/context/project-context'
 import { useEditorContext } from '@/shared/context/editor-context'
+import { sendMB } from '@/infrastructure/event-tracking'
 
 const EditorOverLimitModal = () => {
   const [show, setShow] = useState(false)
@@ -46,6 +47,9 @@ const EditorOverLimitModal = () => {
       ) {
         setShow(true)
         customLocalStorage.setItem(localStorageKey, Date.now())
+        sendMB('notification-prompt', {
+          name: 'link-sharing-collaborator-limit',
+        })
       }
     }
   }, [features, isProjectOwner, members, permissionsLevel, projectId])
@@ -54,7 +58,12 @@ const EditorOverLimitModal = () => {
     <AccessibleModal
       animation
       show={show}
-      onHide={handleHide}
+      onHide={() => {
+        sendMB('notification-dismiss', {
+          name: 'link-sharing-collaborator-limit',
+        })
+        handleHide()
+      }}
       id="editor-over-limit-modal"
     >
       <EditorOverLimitModalContent handleHide={handleHide} />

+ 19 - 0
services/web/frontend/js/features/token-access/components/sharing-updates-root.tsx

@@ -8,6 +8,7 @@ import { postJSON } from '@/infrastructure/fetch-json'
 import { debugConsole } from '@/utils/debugging'
 import useAsync from '@/shared/hooks/use-async'
 import Notification from '@/shared/components/notification'
+import { sendMB } from '@/infrastructure/event-tracking'
 
 function SharingUpdatesRoot() {
   const { isReady } = useWaitForI18n()
@@ -16,6 +17,10 @@ function SharingUpdatesRoot() {
   const projectId = getMeta('ol-project_id')
 
   const joinProject = useCallback(() => {
+    sendMB('notification-click', {
+      name: 'link-sharing-collaborator',
+      button: 'ok',
+    })
     runAsync(postJSON(`/project/${projectId}/sharing-updates/join`))
       .then(() => {
         location.assign(`/project/${projectId}`)
@@ -24,6 +29,10 @@ function SharingUpdatesRoot() {
   }, [runAsync, projectId])
 
   const viewProject = useCallback(() => {
+    sendMB('notification-click', {
+      name: 'link-sharing-collaborator',
+      button: 'anonymous',
+    })
     runAsync(postJSON(`/project/${projectId}/sharing-updates/view`))
       .then(() => {
         location.assign(`/project/${projectId}`)
@@ -32,6 +41,10 @@ function SharingUpdatesRoot() {
   }, [runAsync, projectId])
 
   const leaveProject = useCallback(() => {
+    sendMB('notification-click', {
+      name: 'link-sharing-collaborator',
+      button: 'leave',
+    })
     runAsync(postJSON(`/project/${projectId}/leave`))
       .then(() => {
         location.assign('/project')
@@ -67,6 +80,12 @@ function SharingUpdatesRoot() {
                         href="/blog/changes-to-project-sharing"
                         rel="noopener noreferrer"
                         target="_blank"
+                        onClick={() => {
+                          sendMB('notification-click', {
+                            name: 'link-sharing-collaborator',
+                            button: 'learn',
+                          })
+                        }}
                       />,
                     ]}
                   />

+ 3 - 0
services/web/test/unit/src/Project/ProjectControllerTests.js

@@ -135,6 +135,9 @@ describe('ProjectController', function () {
     this.UserGetter = {
       getUserFullEmails: sinon.stub().yields(null, []),
       getUser: sinon.stub().resolves({ lastLoginIp: '192.170.18.2' }),
+      promises: {
+        getUserFeatures: sinon.stub().resolves(null, { collaborators: 1 }),
+      },
     }
     this.Features = {
       hasFeature: sinon.stub(),