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

Merge pull request #26683 from overleaf/ab-update-survey-form

Update survey form and preview + support custom button CTA

GitOrigin-RevId: 2b519ab1da1c8e7897b3135956f80619f4e901b4
Alexandre Bourdin 1 год назад
Родитель
Сommit
36c4c65609

+ 3 - 3
services/web/app/src/Features/Survey/SurveyHandler.mjs

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

+ 4 - 3
services/web/app/src/Features/Survey/SurveyManager.js

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

+ 6 - 2
services/web/app/src/models/Survey.js

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

+ 0 - 49
services/web/frontend/js/features/project-list/components/sidebar/sidebar.tsx

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

+ 3 - 3
services/web/frontend/js/features/project-list/components/survey-widget-ds-nav.tsx

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

+ 0 - 43
services/web/frontend/js/features/project-list/components/survey-widget.tsx

@@ -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}&nbsp;
-            <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>
-  )
-}

+ 8 - 7
services/web/frontend/stories/project-list/survey-widget.stories.tsx → services/web/frontend/stories/project-list/survey-widget-ds-nav.stories.tsx

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

+ 44 - 12
services/web/test/frontend/features/project-list/components/survey-widget.test.tsx

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

+ 3 - 2
services/web/types/project/dashboard/survey.d.ts

@@ -1,6 +1,7 @@
 export type Survey = {
   name: string
-  preText: string
-  linkText: string
+  title: string
+  text: string
+  cta?: string
   url: string
 }