Merge pull request #33805 from overleaf/mg-ai-paywall-analytics
Add paywall-prompt and paywall-click events to ai paywalls GitOrigin-RevId: aa7de15a990ad1833e3dda65d5fb50f60bb7c9e3
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { useEffect } from 'react'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import UpgradeButton from '@/features/ide-react/components/toolbar/upgrade-button'
|
||||
import PaywallUpgradeButton from '@/shared/components/paywall-upgrade-button'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { useUserFeaturesContext } from '@/shared/context/user-features-context'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { formatSecondsToHoursAndMinutes } from '@/shared/utils/time'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
@@ -9,14 +11,20 @@ import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
const hasUnlimitedAi = getMeta('ol-hasUnlimitedAi')
|
||||
|
||||
type aiFeatureLocations = 'errorAssist' | 'workbench'
|
||||
type AiFeatureLocations = 'errorAssist' | 'workbench'
|
||||
type PaywallType = 'assistant' | 'workbench'
|
||||
|
||||
const paywallTypeByLocation: Record<AiFeatureLocations, PaywallType> = {
|
||||
workbench: 'workbench',
|
||||
errorAssist: 'assistant',
|
||||
}
|
||||
|
||||
function AiPaywallNotification({
|
||||
isActionBelowContent = false,
|
||||
featureLocation,
|
||||
}: {
|
||||
isActionBelowContent?: boolean
|
||||
featureLocation: aiFeatureLocations
|
||||
featureLocation: AiFeatureLocations
|
||||
}) {
|
||||
const {
|
||||
hasSuggestionsLeft,
|
||||
@@ -123,29 +131,12 @@ function AiPaywallNotification({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const message = t('upgrade_for_unlimited_access_to_ai', {
|
||||
time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
|
||||
})
|
||||
return (
|
||||
<>
|
||||
<Notification
|
||||
type="info"
|
||||
title={t('youve_hit_your_daily_ai_limit')}
|
||||
content={message}
|
||||
isDismissible={false}
|
||||
customIcon={null}
|
||||
isActionBelowContent={isActionBelowContent}
|
||||
action={
|
||||
<UpgradeButton
|
||||
className="px-2.5 py-2"
|
||||
referrer="ai"
|
||||
source={featureLocation}
|
||||
/>
|
||||
}
|
||||
className="ai-upgrade-paywall-btn ai-paywall-notification"
|
||||
/>
|
||||
</>
|
||||
<UpgradePaywall
|
||||
secondsTillReset={secondsTillReset}
|
||||
isActionBelowContent={isActionBelowContent}
|
||||
featureLocation={featureLocation}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,7 +145,7 @@ function GroupsPaywall({
|
||||
featureLocation,
|
||||
}: {
|
||||
secondsTillReset: number
|
||||
featureLocation: aiFeatureLocations
|
||||
featureLocation: AiFeatureLocations
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -186,7 +177,7 @@ function CommonsPaywall({
|
||||
featureLocation,
|
||||
}: {
|
||||
secondsTillReset: number
|
||||
featureLocation: aiFeatureLocations
|
||||
featureLocation: AiFeatureLocations
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -218,7 +209,7 @@ function FairUseLimit({
|
||||
featureLocation,
|
||||
}: {
|
||||
secondsTillReset: number
|
||||
featureLocation: aiFeatureLocations
|
||||
featureLocation: AiFeatureLocations
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -254,4 +245,45 @@ function FairUseLimit({
|
||||
)
|
||||
}
|
||||
|
||||
function UpgradePaywall({
|
||||
secondsTillReset,
|
||||
isActionBelowContent,
|
||||
featureLocation,
|
||||
}: {
|
||||
secondsTillReset: number
|
||||
isActionBelowContent: boolean
|
||||
featureLocation: AiFeatureLocations
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
const paywallType = paywallTypeByLocation[featureLocation]
|
||||
|
||||
useEffect(() => {
|
||||
sendEvent('paywall-prompt', {
|
||||
'paywall-type': paywallType,
|
||||
})
|
||||
}, [sendEvent, paywallType])
|
||||
|
||||
return (
|
||||
<Notification
|
||||
type="info"
|
||||
title={t('youve_hit_your_daily_ai_limit')}
|
||||
content={t('upgrade_for_unlimited_access_to_ai', {
|
||||
time: formatSecondsToHoursAndMinutes(t, secondsTillReset),
|
||||
})}
|
||||
isDismissible={false}
|
||||
customIcon={null}
|
||||
isActionBelowContent={isActionBelowContent}
|
||||
action={
|
||||
<PaywallUpgradeButton
|
||||
referrer="ai"
|
||||
paywallType={paywallType}
|
||||
className="px-2.5 py-2"
|
||||
/>
|
||||
}
|
||||
className="ai-upgrade-paywall-btn ai-paywall-notification"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default AiPaywallNotification
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function PaywallUpgradeButton({
|
||||
referrer,
|
||||
paywallType,
|
||||
className,
|
||||
}: {
|
||||
referrer: string
|
||||
paywallType: string
|
||||
className?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
const user = getMeta('ol-user')
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
sendEvent('paywall-click', {
|
||||
upgradeType: user.hasPaidSubscription ? 'add-on' : 'standalone',
|
||||
'paywall-type': paywallType,
|
||||
})
|
||||
}, [sendEvent, user.hasPaidSubscription, paywallType])
|
||||
|
||||
return (
|
||||
<OLButton
|
||||
variant="premium"
|
||||
size="sm"
|
||||
href={`/user/subscription/choose-your-plan?itm_referrer=${referrer}&itm_campaign=${paywallType}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={className}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t('upgrade')}
|
||||
</OLButton>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import PaywallUpgradeButton from '../../../../frontend/js/shared/components/paywall-upgrade-button'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
|
||||
describe('<PaywallUpgradeButton />', function () {
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
||||
beforeEach(function () {
|
||||
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
|
||||
window.metaAttributesCache.set('ol-user', { hasPaidSubscription: false })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sendMBSpy.restore()
|
||||
})
|
||||
|
||||
it('upgrade link has required attributes', function () {
|
||||
render(<PaywallUpgradeButton referrer="ai" paywallType="assistant" />)
|
||||
|
||||
const upgradeLink = screen.getByRole('link', { name: 'Upgrade' })
|
||||
expect(upgradeLink.getAttribute('href')).to.equal(
|
||||
'/user/subscription/choose-your-plan?itm_referrer=ai&itm_campaign=assistant'
|
||||
)
|
||||
expect(upgradeLink.getAttribute('target')).to.equal('_blank')
|
||||
expect(upgradeLink.getAttribute('rel')).to.equal('noreferrer')
|
||||
})
|
||||
|
||||
it('sends paywall-click with standalone upgradeType for users without a paid subscription', async function () {
|
||||
render(<PaywallUpgradeButton referrer="ai" paywallType="assistant" />)
|
||||
|
||||
await userEvent.click(screen.getByRole('link', { name: 'Upgrade' }))
|
||||
|
||||
expect(sendMBSpy).to.have.been.calledOnceWith(
|
||||
'paywall-click',
|
||||
sinon.match({
|
||||
upgradeType: 'standalone',
|
||||
'paywall-type': 'assistant',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('sends paywall-click with add-on upgradeType for users with a paid subscription', async function () {
|
||||
window.metaAttributesCache.set('ol-user', { hasPaidSubscription: true })
|
||||
|
||||
render(<PaywallUpgradeButton referrer="ai" paywallType="workbench" />)
|
||||
|
||||
await userEvent.click(screen.getByRole('link', { name: 'Upgrade' }))
|
||||
|
||||
expect(sendMBSpy).to.have.been.calledOnceWith(
|
||||
'paywall-click',
|
||||
sinon.match({
|
||||
upgradeType: 'add-on',
|
||||
'paywall-type': 'workbench',
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user