diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 4682372fb5..191033e3bd 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/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) { diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx index 2ea44180cc..ace42c327a 100644 --- a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx +++ b/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 (
+ {user.allowedFreeTrial ? ( + + {t('upgrade')} + + ) : ( + + )} - { + sendMB('paywall-info-click', { + 'paywall-type': 'project-sharing', + content: 'blog', + variant: 'exceeds', + }) + }} > {t('read_more')} - +
} /> diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx index 6cf9c37f3c..a088fe3ce2 100644 --- a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx +++ b/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 (
@@ -22,13 +25,25 @@ export default function CollaboratorsLimitUpgrade() {

} action={ - + user.allowedFreeTrial ? ( + + {t('upgrade')} + + ) : ( + + ) } />
diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx index 6b36f8538e..a67d8ea906 100644 --- a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx +++ b/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')} - diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx index c76f2ed290..b8455b11a9 100644 --- a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx +++ b/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 = () => { { + sendMB('notification-dismiss', { + name: 'link-sharing-collaborator-limit', + }) + handleHide() + }} id="editor-over-limit-modal" > diff --git a/services/web/frontend/js/features/token-access/components/sharing-updates-root.tsx b/services/web/frontend/js/features/token-access/components/sharing-updates-root.tsx index a765972a5e..df9a6e5430 100644 --- a/services/web/frontend/js/features/token-access/components/sharing-updates-root.tsx +++ b/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', + }) + }} />, ]} /> diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index 6f404e9422..798e3446cb 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/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(),