Skip to content

Commit

Permalink
feat(surveys): templates (#17904)
Browse files Browse the repository at this point in the history
* routes for survey templates

* basic on click setup logic

* Update UI snapshots for `chromium` (2)

* import type

* Apply suggestions from code review

Co-authored-by: Joe Martin <[email protected]>

* address feedback from cory

* fix logic

* remove unused logic file

* Update frontend/src/scenes/surveys/constants.tsx

Co-authored-by: Joe Martin <[email protected]>

* Update frontend/src/scenes/surveys/constants.tsx

Co-authored-by: Neil Kakkar <[email protected]>

* address comments

* Update UI snapshots for `chromium` (2)

* more fixes

* Update UI snapshots for `webkit` (2)

* Update UI snapshots for `webkit` (2)

* Update UI snapshots for `chromium` (2)

* move description and title together

* fix e2e

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Joe Martin <[email protected]>
Co-authored-by: Neil Kakkar <[email protected]>
  • Loading branch information
4 people authored Oct 16, 2023
1 parent 938e391 commit 93e8ccc
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 7 deletions.
3 changes: 3 additions & 0 deletions cypress/e2e/surveys.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('Surveys', () => {

// go to create a new survey
cy.get('[data-attr="create-survey"]').click()
cy.get('[data-attr="new-blank-survey"]').click()

cy.get('[data-attr="survey-name"]').type(name)

Expand Down Expand Up @@ -76,6 +77,7 @@ describe('Surveys', () => {

// click via top right button
cy.get('[data-attr="new-survey"]').click()
cy.get('[data-attr="new-blank-survey"]').click()

// select "add filter" and "property"
cy.get('[data-attr="survey-name"]').type(name)
Expand Down Expand Up @@ -180,6 +182,7 @@ describe('Surveys', () => {
it('Delete survey', () => {
cy.get('h1').should('contain', 'Surveys')
cy.get('[data-attr=new-survey]').click()
cy.get('[data-attr=new-blank-survey]').click()
cy.get('[data-attr=survey-name]').focus().type(name).should('have.value', name)
cy.get('[data-attr=save-survey]').first().click()

Expand Down
Binary file modified frontend/__snapshots__/scenes-app-surveys--surveys-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/scenes/appScenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const appScenes: Record<Scene, () => any> = {
[Scene.EarlyAccessFeature]: () => import('./early-access-features/EarlyAccessFeature'),
[Scene.Surveys]: () => import('./surveys/Surveys'),
[Scene.Survey]: () => import('./surveys/Survey'),
[Scene.SurveyTemplates]: () => import('./surveys/SurveyTemplates'),
[Scene.DataWarehouse]: () => import('./data-warehouse/external/DataWarehouseExternalScene'),
[Scene.DataWarehousePosthog]: () => import('./data-warehouse/posthog/DataWarehousePosthogScene'),
[Scene.DataWarehouseExternal]: () => import('./data-warehouse/external/DataWarehouseExternalScene'),
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/sceneLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const sceneNavAlias: Partial<Record<Scene, Scene>> = {
[Scene.FeatureFlag]: Scene.FeatureFlags,
[Scene.EarlyAccessFeature]: Scene.EarlyAccessFeatures,
[Scene.Survey]: Scene.Surveys,
[Scene.SurveyTemplates]: Scene.Surveys,
[Scene.DataWarehouseTable]: Scene.DataWarehouse,
[Scene.DataWarehousePosthog]: Scene.DataWarehouse,
[Scene.DataWarehouseExternal]: Scene.DataWarehouse,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/sceneTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum Scene {
EarlyAccessFeature = 'EarlyAccessFeature',
Surveys = 'Surveys',
Survey = 'Survey',
SurveyTemplates = 'SurveyTemplates',
DataWarehouse = 'DataWarehouse',
DataWarehousePosthog = 'DataWarehousePosthog',
DataWarehouseExternal = 'DataWarehouseExternal',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/scenes/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ export const sceneConfigurations: Partial<Record<Scene, SceneConfig>> = {
projectBased: true,
name: 'Survey',
},
[Scene.SurveyTemplates]: {
projectBased: true,
name: 'New survey',
},
[Scene.DataWarehouse]: {
projectBased: true,
name: 'Data Warehouse',
Expand Down Expand Up @@ -433,6 +437,7 @@ export const routes: Record<string, Scene> = {
[urls.earlyAccessFeature(':id')]: Scene.EarlyAccessFeature,
[urls.surveys()]: Scene.Surveys,
[urls.survey(':id')]: Scene.Survey,
[urls.surveyTemplates()]: Scene.SurveyTemplates,
[urls.dataWarehouse()]: Scene.DataWarehouse,
[urls.dataWarehouseTable(':id')]: Scene.DataWarehouseTable,
[urls.dataWarehousePosthog()]: Scene.DataWarehousePosthog,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/surveys/SurveyAppearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface SurveyAppearanceProps {
type: SurveyQuestionType
question: string
appearance: SurveyAppearanceType
surveyQuestionItem: RatingSurveyQuestion | SurveyQuestion | MultipleSurveyQuestion
surveyQuestionItem: SurveyQuestion
description?: string | null
link?: string | null
preview?: boolean
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/scenes/surveys/SurveyTemplates.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '../../styles/mixins';

.SurveyTemplateContainer {
display: flex;
align-items: center;
border: 1px solid var(--border);
border-radius: 6px;
min-height: 300px;
margin-top: 2px;

&:hover {
cursor: pointer;
border-color: var(--primary-light);
}

.SurveyTemplate {
-ms-transform: scale(0.8, 0.8); /* IE 9 */
-webkit-transform: scale(0.8, 0.8); /* Safari */
transform: scale(0.8, 0.8);
}
}
62 changes: 62 additions & 0 deletions frontend/src/scenes/surveys/SurveyTemplates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { SceneExport } from 'scenes/sceneTypes'
import { SurveyAppearance } from './SurveyAppearance'
import { defaultSurveyTemplates } from './constants'
import { SurveyQuestion } from '~/types'
import './SurveyTemplates.scss'
import { useActions } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { LemonButton } from '@posthog/lemon-ui'
import { urls } from 'scenes/urls'
import { surveyLogic } from './surveyLogic'

export const scene: SceneExport = {
component: SurveyTemplates,
}

export function SurveyTemplates(): JSX.Element {
const { setSurveyTemplateValues } = useActions(surveyLogic({ id: 'new' }))

return (
<>
<PageHeader
title={'New survey'}
buttons={
<LemonButton type="primary" to={urls.survey('new')} data-attr="new-blank-survey">
Create blank survey
</LemonButton>
}
/>
<div className="flex flex-row flex-wrap gap-8 ml-8 mt-8">
{defaultSurveyTemplates.map((template, idx) => {
return (
<div
className="flex flex-col items-center"
key={idx}
onClick={() =>
setSurveyTemplateValues({ name: template.type, questions: template.questions })
}
>
<span className="mb-2 text-md">
<b>{template.type}</b>
</span>
<span className="flex flex-wrap text-xs text-muted max-w-80 font-medium mb-2">
{template.description}
</span>
<div className="SurveyTemplateContainer">
<div className="SurveyTemplate">
<SurveyAppearance
key={idx}
type={template.questions[0].type}
question={template.questions[0].question}
appearance={{ whiteLabel: true, ...template.appearance }}
surveyQuestionItem={template.questions[0] as SurveyQuestion}
/>
</div>
</div>
</div>
)
})}
</div>
</>
)
}
23 changes: 20 additions & 3 deletions frontend/src/scenes/surveys/Surveys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
LemonTag,
LemonTagType,
Spinner,
LemonButtonWithSideAction,
} from '@posthog/lemon-ui'
import { PageHeader } from 'lib/components/PageHeader'
import { More } from 'lib/lemon-ui/LemonButton/More'
Expand Down Expand Up @@ -78,9 +79,25 @@ export function Surveys(): JSX.Element {
}
buttons={
<>
<LemonButton type="primary" to={urls.survey('new')} data-attr="new-survey">
<LemonButtonWithSideAction
to={urls.surveyTemplates()}
type="primary"
data-attr="new-survey"
sideAction={{
dropdown: {
placement: 'bottom-start',
actionable: true,
overlay: (
<LemonButton size="small" to={urls.survey('new')}>
New from blank
</LemonButton>
),
},
'data-attr': 'saved-insights-new-insight-dropdown',
}}
>
New survey
</LemonButton>
</LemonButtonWithSideAction>
<LemonButton
type="secondary"
icon={<IconSettings />}
Expand Down Expand Up @@ -145,7 +162,7 @@ export function Surveys(): JSX.Element {
description={
'Use surveys to gather qualitative feedback from your users on new or existing features.'
}
action={() => router.actions.push(urls.survey('new'))}
action={() => router.actions.push(urls.surveyTemplates())}
isEmpty={surveys.length === 0}
productKey={ProductKey.SURVEYS}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,100 @@ export const NEW_SURVEY: NewSurvey = {
archived: false,
appearance: defaultSurveyAppearance,
}

export enum SurveyTemplateType {
Interview = 'User interview',
NPS = 'Net promoter score (NPS)',
CSAT = 'Customer satisfaction score (CSAT)',
CES = 'Customer effort score (CES)',
CCR = 'Customer churn rate (CCR)',
PMF = 'Product-market fit (PMF)',
}

export const defaultSurveyTemplates = [
{
type: SurveyTemplateType.Interview,
questions: [
{
type: SurveyQuestionType.Link,
question: 'Would you be interested in participating in a customer interview?',
description: 'We are looking for feedback on our product and would love to hear from you!',
link: 'https://calendly.com/',
},
],
appearance: { submitButtonText: 'Schedule' },
description: <>Send users straight to your calendar.</>,
},
{
type: SurveyTemplateType.NPS,
questions: [
{
type: SurveyQuestionType.Rating,
question: 'How likely are you to recommend us to a friend?',
description: '',
display: 'number',
scale: 10,
lowerBoundLabel: 'Unlikely',
upperBoundLabel: 'Very likely',
},
],
description: 'Get an industry-recognized benchmark.',
},
{
type: SurveyTemplateType.PMF,
questions: [
{
type: SurveyQuestionType.SingleChoice,
question: 'How would you feel if you could no longer use PostHog?',
choices: ['Not disappointed', 'Somewhat disappointed', 'Very disappointed'],
},
],
description: "40% 'very disappointed' signals product-market fit.",
},
{
type: SurveyTemplateType.CSAT,
questions: [
{
type: SurveyQuestionType.Rating,
question: 'How satisfied are you with PostHog surveys?',
description: '',
display: 'emoji',
scale: 5,
lowerBoundLabel: 'Very dissatisfied',
upperBoundLabel: 'Very satisfied',
},
],
description: 'Works best after a checkout or support flow.',
},
{
type: SurveyTemplateType.CES,
questions: [
{
type: SurveyQuestionType.Rating,
question: 'How easy was it to use our product?',
description: '',
display: 'emoji',
scale: 5,
lowerBoundLabel: 'Very difficult',
upperBoundLabel: 'Very easy',
},
],
description: 'Works well with churn surveys.',
},
{
type: SurveyTemplateType.CCR,
questions: [
{
type: SurveyQuestionType.MultipleChoice,
question: "We're sorry to see you go. What's your reason for unsubscribing?",
choices: [
'I no longer need the product',
'I found a better product',
'I found the product too difficult to use',
'Other',
],
},
],
description: 'Find out if it was something you said.',
},
]
21 changes: 19 additions & 2 deletions frontend/src/scenes/surveys/surveyLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { lemonToast } from '@posthog/lemon-ui'
import { kea, path, props, key, listeners, afterMount, reducers, actions, selectors, connect } from 'kea'
import { forms } from 'kea-forms'
import { loaders } from 'kea-loaders'
import { router, urlToAction } from 'kea-router'
import { actionToUrl, router, urlToAction } from 'kea-router'
import api from 'lib/api'
import { urls } from 'scenes/urls'
import {
Expand Down Expand Up @@ -113,6 +113,7 @@ export const surveyLogic = kea<surveyLogicType>([
archiveSurvey: true,
setCurrentQuestionIndexAndType: (idx: number, type: SurveyQuestionType) => ({ idx, type }),
setWritingHTMLDescription: (writingHTML: boolean) => ({ writingHTML }),
setSurveyTemplateValues: (template: any) => ({ template }),
resetTargeting: true,
}),
loaders(({ props, actions, values }) => ({
Expand All @@ -128,7 +129,11 @@ export const surveyLogic = kea<surveyLogicType>([
throw error
}
}
return { ...NEW_SURVEY }
if (props.id === 'new' && router.values.hashParams.fromTemplate) {
return values.survey
} else {
return { ...NEW_SURVEY }
}
},
createSurvey: async (surveyPayload: Partial<Survey>) => {
return await api.surveys.create(sanitizeQuestions(surveyPayload))
Expand Down Expand Up @@ -414,6 +419,10 @@ export const surveyLogic = kea<surveyLogicType>([
},
}
},
setSurveyTemplateValues: (_, { template }) => {
const newTemplateSurvey = { ...NEW_SURVEY, ...template }
return newTemplateSurvey
},
},
],
currentQuestionIndexAndType: [
Expand Down Expand Up @@ -699,6 +708,14 @@ export const surveyLogic = kea<surveyLogicType>([
}
},
})),
actionToUrl(({ values }) => ({
setSurveyTemplateValues: () => {
const hashParams = router.values.hashParams
hashParams['fromTemplate'] = true

return [urls.survey(values.survey.id), router.values.searchParams, hashParams]
},
})),
afterMount(async ({ props, actions }) => {
if (props.id !== 'new') {
await actions.loadSurvey()
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/scenes/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ export const urls = {
earlyAccessFeatures: (): string => '/early_access_features',
earlyAccessFeature: (id: ':id' | 'new' | string): string => `/early_access_features/${id}`,
surveys: (): string => '/surveys',
survey: (id: ':id' | 'new' | string): string => `/surveys/${id}`,
surveyTemplates: (): string => '/survey_templates',
dataWarehouse: (): string => '/warehouse',
dataWarehouseTable: (id: ':id' | 'new' | string): string => `/warehouse/${id}`,
dataWarehousePosthog: (): string => '/data-warehouse/posthog',
dataWarehouseExternal: (): string => '/data-warehouse/external',
dataWarehouseSavedQueries: (): string => '/data-warehouse/views',
survey: (id: ':id' | 'new' | string): string => `/surveys/${id}`,
annotations: (): string => '/annotations',
annotation: (id: AnnotationType['id'] | ':id'): string => `/annotations/${id}`,
projectApps: (tab?: PluginTab): string => `/project/apps${tab ? `?tab=${tab}` : ''}`,
Expand Down

0 comments on commit 93e8ccc

Please sign in to comment.