mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #26683 from overleaf/ab-update-survey-form
Update survey form and preview + support custom button CTA GitOrigin-RevId: 2b519ab1da1c8e7897b3135956f80619f4e901b4
This commit is contained in:
committed by
Copybot
parent
75d443934f
commit
36c4c65609
@@ -14,7 +14,7 @@ import UserGetter from '../User/UserGetter.js'
|
||||
* determines if there is a survey to show, given current surveys and rollout percentages
|
||||
* uses userId in computation, to ensure that rollout groups always contain same users
|
||||
* @param {string} userId
|
||||
* @returns {Promise<Survey | undefined>}
|
||||
* @returns {Promise<Pick<Survey, 'name' | 'title' | 'text' | 'cta' | 'url'> | undefined>}
|
||||
*/
|
||||
async function getSurvey(userId) {
|
||||
const survey = await SurveyCache.get(true)
|
||||
@@ -27,7 +27,7 @@ async function getSurvey(userId) {
|
||||
}
|
||||
}
|
||||
|
||||
const { name, preText, linkText, url, options } = survey?.toObject() || {}
|
||||
const { name, title, text, cta, url, options } = survey?.toObject() || {}
|
||||
// default to full rollout for backwards compatibility
|
||||
const rolloutPercentage = options?.rolloutPercentage || 100
|
||||
if (!_userInRolloutPercentile(userId, name, rolloutPercentage)) {
|
||||
@@ -53,7 +53,7 @@ async function getSurvey(userId) {
|
||||
}
|
||||
}
|
||||
|
||||
return { name, preText, linkText, url }
|
||||
return { name, title, text, cta, url }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,16 @@ async function getSurvey() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSurvey({ name, preText, linkText, url, options }) {
|
||||
async function updateSurvey({ name, title, text, cta, url, options }) {
|
||||
validateOptions(options)
|
||||
let survey = await getSurvey()
|
||||
if (!survey) {
|
||||
survey = new Survey()
|
||||
}
|
||||
survey.name = name
|
||||
survey.preText = preText
|
||||
survey.linkText = linkText
|
||||
survey.title = title
|
||||
survey.text = text
|
||||
survey.cta = cta
|
||||
survey.url = url
|
||||
survey.options = options
|
||||
await survey.save()
|
||||
|
||||
@@ -19,14 +19,18 @@ const SurveySchema = new Schema(
|
||||
message: `invalid, must match: ${NAME_REGEX}`,
|
||||
},
|
||||
},
|
||||
preText: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
linkText: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cta: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import NewProjectButton from '../new-project-button'
|
||||
import SidebarFilters from './sidebar-filters'
|
||||
import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
|
||||
import SurveyWidget from '../survey-widget'
|
||||
import { usePersistedResize } from '../../../../shared/hooks/use-resize'
|
||||
|
||||
function Sidebar() {
|
||||
const { show: showAddAffiliationWidget } = useAddAffiliation()
|
||||
const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({
|
||||
name: 'project-sidebar',
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className="project-list-sidebar-wrapper-react d-none d-md-block"
|
||||
{...getTargetProps({
|
||||
style: {
|
||||
...(mousePos?.x && { flexBasis: `${mousePos.x}px` }),
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div className="project-list-sidebar-subwrapper">
|
||||
<aside className="project-list-sidebar-react">
|
||||
<NewProjectButton id="new-project-button-sidebar" />
|
||||
<SidebarFilters />
|
||||
{showAddAffiliationWidget && <hr />}
|
||||
<AddAffiliation />
|
||||
</aside>
|
||||
<div className="project-list-sidebar-survey-wrapper">
|
||||
<SurveyWidget />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
{...getHandleProps({
|
||||
style: {
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
right: '-2px',
|
||||
height: '100%',
|
||||
width: '4px',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
@@ -26,8 +26,8 @@ export function SurveyWidgetDsNav() {
|
||||
<div className="notification-entry">
|
||||
<div role="alert" className="survey-notification">
|
||||
<div className="notification-body">
|
||||
<p className="fw-bold fs-6 pe-4">{survey.preText}</p>
|
||||
<p>{survey.linkText}</p>
|
||||
<p className="fw-bold fs-6 pe-4">{survey.title}</p>
|
||||
<p>{survey.text}</p>
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@@ -35,7 +35,7 @@ export function SurveyWidgetDsNav() {
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{t('take_survey')}
|
||||
{survey.cta || t('take_survey')}
|
||||
</OLButton>
|
||||
</div>
|
||||
<OLButton
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import usePersistedState from '../../../shared/hooks/use-persisted-state'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { useCallback } from 'react'
|
||||
import Close from '@/shared/components/close'
|
||||
|
||||
export default function SurveyWidget() {
|
||||
const survey = getMeta('ol-survey')
|
||||
const [dismissedSurvey, setDismissedSurvey] = usePersistedState(
|
||||
`dismissed-${survey?.name}`,
|
||||
false
|
||||
)
|
||||
|
||||
const dismissSurvey = useCallback(() => {
|
||||
setDismissedSurvey(true)
|
||||
}, [setDismissedSurvey])
|
||||
|
||||
if (!survey?.name || dismissedSurvey) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="user-notifications">
|
||||
<div className="notification-entry">
|
||||
<div role="alert" className="survey-notification">
|
||||
<div className="notification-body">
|
||||
{survey.preText}
|
||||
<a
|
||||
className="project-list-sidebar-survey-link"
|
||||
href={survey.url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{survey.linkText}
|
||||
</a>
|
||||
</div>
|
||||
<div className="notification-close notification-close-button-style">
|
||||
<Close variant="dark" onDismiss={() => dismissSurvey()} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +1,32 @@
|
||||
import SurveyWidget from '../../js/features/project-list/components/survey-widget'
|
||||
import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
|
||||
|
||||
export const Survey = (args: any) => {
|
||||
localStorage.clear()
|
||||
window.metaAttributesCache.set('ol-survey', {
|
||||
name: 'my-survey',
|
||||
preText: 'To help shape the future of Overleaf',
|
||||
linkText: 'Click here!',
|
||||
title: 'To help shape the future of Overleaf',
|
||||
text: 'Click here!',
|
||||
cta: 'Let’s go!',
|
||||
url: 'https://example.com/my-survey',
|
||||
})
|
||||
|
||||
return <SurveyWidget {...args} />
|
||||
return <SurveyWidgetDsNav {...args} />
|
||||
}
|
||||
|
||||
export const UndefinedSurvey = (args: any) => {
|
||||
localStorage.clear()
|
||||
|
||||
return <SurveyWidget {...args} />
|
||||
return <SurveyWidgetDsNav {...args} />
|
||||
}
|
||||
|
||||
export const EmptySurvey = (args: any) => {
|
||||
localStorage.clear()
|
||||
window.metaAttributesCache.set('ol-survey', {})
|
||||
|
||||
return <SurveyWidget {...args} />
|
||||
return <SurveyWidgetDsNav {...args} />
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Project List / Survey Widget',
|
||||
component: SurveyWidget,
|
||||
component: SurveyWidgetDsNav,
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { SurveyWidgetDsNav } from '../../../../../frontend/js/features/project-list/components/survey-widget-ds-nav'
|
||||
import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
|
||||
describe('<SurveyWidgetDsNav />', function () {
|
||||
beforeEach(function () {
|
||||
this.name = 'my-survey'
|
||||
this.preText = 'To help shape the future of Overleaf'
|
||||
this.linkText = 'Click here!'
|
||||
this.title = 'To help shape the future of Overleaf'
|
||||
this.text = 'Click here!'
|
||||
this.cta = 'Let’s go!'
|
||||
this.url = 'https://example.com/my-survey'
|
||||
|
||||
localStorage.clear()
|
||||
@@ -17,8 +18,8 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-survey', {
|
||||
name: this.name,
|
||||
preText: this.preText,
|
||||
linkText: this.linkText,
|
||||
title: this.title,
|
||||
text: this.text,
|
||||
url: this.url,
|
||||
})
|
||||
|
||||
@@ -33,8 +34,8 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
const dismissed = localStorage.getItem('dismissed-my-survey')
|
||||
expect(dismissed).to.equal(null)
|
||||
|
||||
screen.getByText(this.preText)
|
||||
screen.getByText(this.linkText)
|
||||
screen.getByText(this.title)
|
||||
screen.getByText(this.text)
|
||||
|
||||
const link = screen.getByRole('link', {
|
||||
name: 'Take survey',
|
||||
@@ -48,7 +49,7 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
})
|
||||
fireEvent.click(dismissButton)
|
||||
|
||||
const text = screen.queryByText(this.preText)
|
||||
const text = screen.queryByText(this.title)
|
||||
expect(text).to.be.null
|
||||
|
||||
const link = screen.queryByRole('button')
|
||||
@@ -59,12 +60,43 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('survey widget is visible with custom CTA', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-survey', {
|
||||
name: this.name,
|
||||
title: this.title,
|
||||
text: this.text,
|
||||
cta: this.cta,
|
||||
url: this.url,
|
||||
})
|
||||
|
||||
render(
|
||||
<SplitTestProvider>
|
||||
<SurveyWidgetDsNav />
|
||||
</SplitTestProvider>
|
||||
)
|
||||
})
|
||||
|
||||
it('shows text and link with custom CTA', function () {
|
||||
const dismissed = localStorage.getItem('dismissed-my-survey')
|
||||
expect(dismissed).to.equal(null)
|
||||
|
||||
screen.getByText(this.title)
|
||||
screen.getByText(this.text)
|
||||
|
||||
const link = screen.getByRole('link', {
|
||||
name: this.cta,
|
||||
}) as HTMLAnchorElement
|
||||
expect(link.href).to.equal(this.url)
|
||||
})
|
||||
})
|
||||
|
||||
describe('survey widget is not shown when already dismissed', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-survey', {
|
||||
name: this.name,
|
||||
preText: this.preText,
|
||||
linkText: this.linkText,
|
||||
title: this.title,
|
||||
text: this.text,
|
||||
url: this.url,
|
||||
})
|
||||
localStorage.setItem('dismissed-my-survey', 'true')
|
||||
@@ -77,7 +109,7 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
})
|
||||
|
||||
it('nothing is displayed', function () {
|
||||
const text = screen.queryByText(this.preText)
|
||||
const text = screen.queryByText(this.title)
|
||||
expect(text).to.be.null
|
||||
|
||||
const link = screen.queryByRole('button')
|
||||
@@ -95,7 +127,7 @@ describe('<SurveyWidgetDsNav />', function () {
|
||||
})
|
||||
|
||||
it('nothing is displayed', function () {
|
||||
const text = screen.queryByText(this.preText)
|
||||
const text = screen.queryByText(this.title)
|
||||
expect(text).to.be.null
|
||||
|
||||
const link = screen.queryByRole('button')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type Survey = {
|
||||
name: string
|
||||
preText: string
|
||||
linkText: string
|
||||
title: string
|
||||
text: string
|
||||
cta?: string
|
||||
url: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user