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
This commit is contained in:
Thomas 2024-07-01 10:28:46 +02:00 committed by Copybot
parent a419fdf1a8
commit 566466185b
7 changed files with 124 additions and 27 deletions

View file

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

View file

@ -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>
}
/>

View file

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

View file

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

View file

@ -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} />

View file

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

View file

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