diff --git a/frontend/src/scenes/surveys/SurveyAppearance.tsx b/frontend/src/scenes/surveys/SurveyAppearance.tsx
index 8a1e97ddc87b9f..1d515a62d2df92 100644
--- a/frontend/src/scenes/surveys/SurveyAppearance.tsx
+++ b/frontend/src/scenes/surveys/SurveyAppearance.tsx
@@ -8,7 +8,7 @@ import {
MultipleSurveyQuestion,
AvailableFeature,
} from '~/types'
-import { defaultSurveyAppearance, QUESTION_CHOICE_OPEN_ENDED_PREFIX } from './constants'
+import { defaultSurveyAppearance } from './constants'
import {
cancel,
check,
@@ -656,12 +656,13 @@ export function SurveyMultipleChoiceAppearance({
)}
{(multipleChoiceQuestion.choices || []).map((choice, idx) =>
- choice.startsWith(QUESTION_CHOICE_OPEN_ENDED_PREFIX) ? (
+ multipleChoiceQuestion?.has_open_choice &&
+ idx === multipleChoiceQuestion.choices?.length - 1 ? (
) : (
diff --git a/frontend/src/scenes/surveys/SurveyEdit.tsx b/frontend/src/scenes/surveys/SurveyEdit.tsx
index 7f733adc7e06b3..03e5f1759379dc 100644
--- a/frontend/src/scenes/surveys/SurveyEdit.tsx
+++ b/frontend/src/scenes/surveys/SurveyEdit.tsx
@@ -39,7 +39,6 @@ import {
defaultSurveyAppearance,
SurveyQuestionLabel,
SurveyUrlMatchTypeLabels,
- QUESTION_CHOICE_OPEN_ENDED_PREFIX,
} from './constants'
import { FeatureFlagReleaseConditions } from 'scenes/feature-flags/FeatureFlagReleaseConditions'
import React from 'react'
@@ -440,133 +439,182 @@ export default function SurveyEdit(): JSX.Element {
question.type ===
SurveyQuestionType.MultipleChoice) && (
-
- {({ value, onChange }) => (
-
- {(value || []).map(
- (
- choice: string,
- index: number
- ) => {
- const isOpenEnded =
- choice.startsWith(
- QUESTION_CHOICE_OPEN_ENDED_PREFIX
- )
- return (
-
-
{
- const newChoices =
- [
- ...value,
- ]
- newChoices[
- index
- ] =
- (isOpenEnded
- ? QUESTION_CHOICE_OPEN_ENDED_PREFIX
- : '') +
- val
- onChange(
- newChoices
- )
- }}
- />
- {isOpenEnded && (
-
- open-ended
-
- )}
-
- }
- size="small"
- status="muted"
- noPadding
- onClick={() => {
- const newChoices =
- [
- ...value,
- ]
- newChoices.splice(
- index,
+
+ {({
+ value: hasOpenChoice,
+ onChange: toggleHasOpenChoice,
+ }) => (
+
+ {({ value, onChange }) => (
+
+ {(value || []).map(
+ (
+ choice: string,
+ index: number
+ ) => {
+ const isOpenChoice =
+ hasOpenChoice &&
+ index ===
+ value?.length -
1
- )
- onChange(
- newChoices
- )
- }}
- />
-
- )
- }
+ return (
+
+ {
+ const newChoices =
+ [
+ ...value,
+ ]
+ newChoices[
+ index
+ ] =
+ val
+ onChange(
+ newChoices
+ )
+ }}
+ />
+ {isOpenChoice && (
+
+ open-ended
+
+ )}
+
+ }
+ size="small"
+ status="muted"
+ noPadding
+ onClick={() => {
+ const newChoices =
+ [
+ ...value,
+ ]
+ newChoices.splice(
+ index,
+ 1
+ )
+ onChange(
+ newChoices
+ )
+ if (
+ isOpenChoice
+ ) {
+ toggleHasOpenChoice(
+ false
+ )
+ }
+ }}
+ />
+
+ )
+ }
+ )}
+
+ {(value || []).length <
+ 6 && (
+ <>
+
+ }
+ type="secondary"
+ fullWidth={
+ false
+ }
+ onClick={() => {
+ if (
+ !value
+ ) {
+ onChange(
+ [
+ '',
+ ]
+ )
+ } else if (
+ hasOpenChoice
+ ) {
+ const newChoices =
+ value.slice(
+ 0,
+ -1
+ )
+ newChoices.push(
+ ''
+ )
+ newChoices.push(
+ value[
+ value.length -
+ 1
+ ]
+ )
+ onChange(
+ newChoices
+ )
+ } else {
+ onChange(
+ [
+ ...value,
+ '',
+ ]
+ )
+ }
+ }}
+ >
+ Add choice
+
+ {!hasOpenChoice && (
+
+ }
+ type="secondary"
+ fullWidth={
+ false
+ }
+ onClick={() => {
+ if (
+ !value
+ ) {
+ onChange(
+ [
+ 'Other',
+ ]
+ )
+ } else {
+ onChange(
+ [
+ ...value,
+ 'Other',
+ ]
+ )
+ }
+ toggleHasOpenChoice(
+ true
+ )
+ }}
+ >
+ Add
+ open-ended
+ choice
+
+ )}
+ >
+ )}
+
+
)}
-
- {(value || []).length < 6 && (
- <>
-
- }
- type="secondary"
- fullWidth={false}
- onClick={() => {
- if (!value) {
- onChange([
- '',
- ])
- } else {
- onChange([
- ...value,
- '',
- ])
- }
- }}
- >
- Add choice
-
-
- }
- type="secondary"
- fullWidth={false}
- onClick={() => {
- if (!value) {
- onChange([
- QUESTION_CHOICE_OPEN_ENDED_PREFIX +
- 'Other',
- ])
- } else {
- onChange([
- ...value,
- QUESTION_CHOICE_OPEN_ENDED_PREFIX +
- 'Other',
- ])
- }
- }}
- >
- Add open-ended
- choice
-
- >
- )}
-
-
+
)}
diff --git a/frontend/src/scenes/surveys/constants.tsx b/frontend/src/scenes/surveys/constants.tsx
index bebec318869bb9..6339def81aaa37 100644
--- a/frontend/src/scenes/surveys/constants.tsx
+++ b/frontend/src/scenes/surveys/constants.tsx
@@ -2,7 +2,6 @@ import { FeatureFlagFilters, Survey, SurveyQuestionType, SurveyType, SurveyUrlMa
export const SURVEY_EVENT_NAME = 'survey sent'
export const SURVEY_RESPONSE_PROPERTY = '$survey_response'
-export const QUESTION_CHOICE_OPEN_ENDED_PREFIX = 'OPENlabel='
export const SurveyQuestionLabel = {
[SurveyQuestionType.Open]: 'Freeform text',
diff --git a/frontend/src/scenes/surveys/surveyLogic.test.ts b/frontend/src/scenes/surveys/surveyLogic.test.ts
index f2d39471cd3925..a7a3a5c529e79e 100644
--- a/frontend/src/scenes/surveys/surveyLogic.test.ts
+++ b/frontend/src/scenes/surveys/surveyLogic.test.ts
@@ -15,7 +15,7 @@ const MULTIPLE_CHOICE_SURVEY: Survey = {
questions: [
{
type: SurveyQuestionType.MultipleChoice,
- choices: ['Tutorials', 'Customer case studies', 'Product announcements', 'OPENlabel=Other'],
+ choices: ['Tutorials', 'Customer case studies', 'Product announcements', 'Other'],
question: 'Which types of content would you like to see more of?',
description: '',
},
@@ -60,7 +60,7 @@ const SINGLE_CHOICE_SURVEY: Survey = {
questions: [
{
type: SurveyQuestionType.SingleChoice,
- choices: ['Yes', 'No', 'OPENlabel=Maybe (explain)'],
+ choices: ['Yes', 'No'],
question: 'Would you like us to continue this feature?',
description: '',
},
@@ -94,6 +94,98 @@ const SINGLE_CHOICE_SURVEY: Survey = {
targeting_flag_filters: undefined,
}
+const MULTIPLE_CHOICE_SURVEY_WITH_OPEN_CHOICE: Survey = {
+ id: '018b22a3-09b1-0000-2f5b-1bd8352ceec9',
+ name: 'Multiple Choice survey',
+ description: '',
+ type: SurveyType.Popover,
+ linked_flag: null,
+ linked_flag_id: null,
+ targeting_flag: null,
+ questions: [
+ {
+ type: SurveyQuestionType.MultipleChoice,
+ choices: ['Tutorials', 'Customer case studies', 'Product announcements', 'Other'],
+ question: 'Which types of content would you like to see more of?',
+ description: '',
+ has_open_choice: true,
+ },
+ ],
+ conditions: null,
+ appearance: {
+ position: 'right',
+ whiteLabel: false,
+ borderColor: '#c9c6c6',
+ placeholder: '',
+ backgroundColor: '#eeeded',
+ submitButtonText: 'Submit',
+ ratingButtonColor: 'white',
+ submitButtonColor: 'black',
+ thankYouMessageHeader: 'Thank you for your feedback!',
+ displayThankYouMessage: true,
+ ratingButtonActiveColor: 'black',
+ },
+ created_at: '2023-10-12T06:46:32.113745Z',
+ created_by: {
+ id: 1,
+ uuid: '018aa8a6-10e8-0000-dba2-0e956f7bae38',
+ distinct_id: 'TGqg9Cn4jLkj9X87oXni9ZPBD6VbOxMtGV1GfJeB5LO',
+ first_name: 'test',
+ email: 'test@posthog.com',
+ is_email_verified: false,
+ },
+ start_date: '2023-10-12T06:46:34.482000Z',
+ end_date: null,
+ archived: false,
+ targeting_flag_filters: undefined,
+}
+
+const SINGLE_CHOICE_SURVEY_WITH_OPEN_CHOICE: Survey = {
+ id: '118b22a3-09b1-0000-2f5b-1bd8352ceec9',
+ name: 'Single Choice survey',
+ description: '',
+ type: SurveyType.Popover,
+ linked_flag: null,
+ linked_flag_id: null,
+ targeting_flag: null,
+ questions: [
+ {
+ type: SurveyQuestionType.SingleChoice,
+ choices: ['Yes', 'No', 'Maybe (explain)'],
+ question: 'Would you like us to continue this feature?',
+ description: '',
+ has_open_choice: true,
+ },
+ ],
+ conditions: null,
+ appearance: {
+ position: 'right',
+ whiteLabel: false,
+ borderColor: '#c9c6c6',
+ placeholder: '',
+ backgroundColor: '#eeeded',
+ submitButtonText: 'Submit',
+ ratingButtonColor: 'white',
+ submitButtonColor: 'black',
+ thankYouMessageHeader: 'Thank you for your feedback!',
+ displayThankYouMessage: true,
+ ratingButtonActiveColor: 'black',
+ },
+ created_at: '2023-10-12T06:46:32.113745Z',
+ created_by: {
+ id: 1,
+ uuid: '018aa8a6-10e8-0000-dba2-0e956f7bae38',
+ distinct_id: 'TGqg9Cn4jLkj9X87oXni9ZPBD6VbOxMtGV1GfJeB5LO',
+ first_name: 'test',
+ email: 'test@posthog.com',
+ is_email_verified: false,
+ },
+ start_date: '2023-10-12T06:46:34.482000Z',
+ end_date: null,
+ archived: false,
+ targeting_flag_filters: undefined,
+}
+
describe('multiple choice survey logic', () => {
let logic: ReturnType
@@ -114,7 +206,7 @@ describe('multiple choice survey logic', () => {
results: [
[336, '"Tutorials"'],
[312, '"Customer case studies"'],
- [20, '"Other choice"'],
+ [20, '"Other"'],
],
},
],
@@ -135,7 +227,7 @@ describe('multiple choice survey logic', () => {
.toMatchValues({
surveyMultipleChoiceResults: {
0: {
- labels: ['Tutorials', 'Customer case studies', 'Other choice', 'Product announcements'],
+ labels: ['Tutorials', 'Customer case studies', 'Other', 'Product announcements'],
data: [336, 312, 20, 0],
},
},
@@ -163,7 +255,7 @@ describe('single choice survey logic', () => {
{
results: [
['"Yes"', 101],
- ['"Only if I could customize my choices"', 1],
+ ['"No"', 1],
],
},
],
@@ -177,6 +269,106 @@ describe('single choice survey logic', () => {
logic.actions.loadSurveySuccess(SINGLE_CHOICE_SURVEY)
}).toDispatchActions(['loadSurveySuccess'])
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySingleChoiceResults({ questionIndex: 1 })
+ })
+ .toFinishAllListeners()
+ .toMatchValues({
+ surveySingleChoiceResults: {
+ 1: {
+ labels: ['"Yes"', '"No"'],
+ data: [101, 1],
+ total: 102,
+ },
+ },
+ })
+ })
+ })
+})
+
+describe('multiple choice survey with open choice logic', () => {
+ let logic: ReturnType
+
+ beforeEach(() => {
+ initKeaTests()
+ logic = surveyLogic({ id: 'new' })
+ logic.mount()
+
+ useMocks({
+ get: {
+ '/api/projects/:team/surveys/': () => [200, { results: [] }],
+ '/api/projects/:team/surveys/responses_count': () => [200, {}],
+ },
+ post: {
+ '/api/projects/:team/query/': () => [
+ 200,
+ {
+ results: [
+ [336, '"Tutorials"'],
+ [312, '"Customer case studies"'],
+ [20, '"Other choice"'],
+ ],
+ },
+ ],
+ },
+ })
+ })
+
+ describe('main', () => {
+ it('post processes return values', async () => {
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(MULTIPLE_CHOICE_SURVEY_WITH_OPEN_CHOICE)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveyMultipleChoiceResults({ questionIndex: 0 })
+ })
+ .toFinishAllListeners()
+ .toMatchValues({
+ surveyMultipleChoiceResults: {
+ 0: {
+ labels: ['Tutorials', 'Customer case studies', 'Other choice', 'Product announcements'],
+ data: [336, 312, 20, 0],
+ },
+ },
+ })
+ })
+ })
+})
+
+describe('single choice survey with open choice logic', () => {
+ let logic: ReturnType
+
+ beforeEach(() => {
+ initKeaTests()
+ logic = surveyLogic({ id: 'new' })
+ logic.mount()
+
+ useMocks({
+ get: {
+ '/api/projects/:team/surveys/': () => [200, { results: [] }],
+ '/api/projects/:team/surveys/responses_count': () => [200, {}],
+ },
+ post: {
+ '/api/projects/:team/query/': () => [
+ 200,
+ {
+ results: [
+ ['"Yes"', 101],
+ ['"Only if I could customize my choices"', 1],
+ ],
+ },
+ ],
+ },
+ })
+ })
+
+ describe('main', () => {
+ it('post processes return values', async () => {
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SINGLE_CHOICE_SURVEY_WITH_OPEN_CHOICE)
+ }).toDispatchActions(['loadSurveySuccess'])
+
await expectLogic(logic, () => {
logic.actions.loadSurveySingleChoiceResults({ questionIndex: 1 })
})
diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx
index e8dccda2673f6d..e80e865c7ec816 100644
--- a/frontend/src/scenes/surveys/surveyLogic.tsx
+++ b/frontend/src/scenes/surveys/surveyLogic.tsx
@@ -22,7 +22,7 @@ import { dayjs } from 'lib/dayjs'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic'
import { featureFlagLogic as enabledFlagLogic } from 'lib/logic/featureFlagLogic'
-import { defaultSurveyFieldValues, QUESTION_CHOICE_OPEN_ENDED_PREFIX, NEW_SURVEY, NewSurvey } from './constants'
+import { defaultSurveyFieldValues, NEW_SURVEY, NewSurvey } from './constants'
import { sanitize } from 'dompurify'
export enum SurveyEditSection {
@@ -330,10 +330,11 @@ export const surveyLogic = kea([
})
// Zero-fill choices that are not open-ended
- question.choices.forEach((choice) => {
+ question.choices.forEach((choice, idx) => {
if (
results?.length &&
- !choice.startsWith(QUESTION_CHOICE_OPEN_ENDED_PREFIX) &&
+ idx === question.choices.length - 1 &&
+ question?.has_open_choice &&
!results.some((r) => r[1] === choice)
) {
results.push([0, choice])
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 00a73ed0b8cf30..b33a8bf0bd00aa 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -2177,6 +2177,7 @@ export interface RatingSurveyQuestion extends SurveyQuestionBase {
export interface MultipleSurveyQuestion extends SurveyQuestionBase {
type: SurveyQuestionType.SingleChoice | SurveyQuestionType.MultipleChoice
choices: string[]
+ has_open_choice?: boolean
}
export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion