diff --git a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png
index f427261f0c801..a820c399ecbf2 100644
Binary files a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png and b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png differ
diff --git a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png
index fa79b1b313e1d..1276a2564f958 100644
Binary files a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png and b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png differ
diff --git a/frontend/src/scenes/surveys/QuestionBranchingInput.tsx b/frontend/src/scenes/surveys/QuestionBranchingInput.tsx
index 96c6ea55912d6..be078d80f050f 100644
--- a/frontend/src/scenes/surveys/QuestionBranchingInput.tsx
+++ b/frontend/src/scenes/surveys/QuestionBranchingInput.tsx
@@ -3,8 +3,9 @@ import './EditSurvey.scss'
import { LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { LemonField } from 'lib/lemon-ui/LemonField'
+import { truncate } from 'lib/utils'
-import { MultipleSurveyQuestion, RatingSurveyQuestion, SurveyQuestionBranchingType } from '~/types'
+import { MultipleSurveyQuestion, RatingSurveyQuestion, SurveyQuestionBranchingType, SurveyQuestionType } from '~/types'
import { surveyLogic } from './surveyLogic'
@@ -16,7 +17,7 @@ export function QuestionBranchingInput({
question: RatingSurveyQuestion | MultipleSurveyQuestion
}): JSX.Element {
const { survey, getBranchingDropdownValue } = useValues(surveyLogic)
- const { setQuestionBranching } = useActions(surveyLogic)
+ const { setQuestionBranchingType } = useActions(surveyLogic)
const availableNextQuestions = survey.questions
.map((question, questionIndex) => ({
@@ -25,6 +26,8 @@ export function QuestionBranchingInput({
}))
.filter((_, idx) => questionIndex !== idx)
const branchingDropdownValue = getBranchingDropdownValue(questionIndex, question)
+ const hasResponseBasedBranching =
+ question.type === SurveyQuestionType.Rating || question.type === SurveyQuestionType.SingleChoice
return (
<>
@@ -33,7 +36,14 @@ export function QuestionBranchingInput({
className="max-w-80 whitespace-nowrap"
value={branchingDropdownValue}
data-attr={`branching-question-${questionIndex}`}
- onSelect={(value) => setQuestionBranching(questionIndex, value)}
+ onSelect={(type) => {
+ let specificQuestionIndex
+ if (type.startsWith(SurveyQuestionBranchingType.SpecificQuestion)) {
+ specificQuestionIndex = parseInt(type.split(':')[1])
+ type = SurveyQuestionBranchingType.SpecificQuestion
+ }
+ setQuestionBranchingType(questionIndex, type, specificQuestionIndex)
+ }}
options={[
...(questionIndex < survey.questions.length - 1
? [
@@ -47,22 +57,122 @@ export function QuestionBranchingInput({
label: 'Confirmation message',
value: SurveyQuestionBranchingType.ConfirmationMessage,
},
- {
- label: 'Specific question based on answer',
- value: SurveyQuestionBranchingType.ResponseBased,
- },
+ ...(hasResponseBasedBranching
+ ? [
+ {
+ label: 'Specific question based on answer',
+ value: SurveyQuestionBranchingType.ResponseBased,
+ },
+ ]
+ : []),
...availableNextQuestions.map((question) => ({
- label: `${question.questionIndex + 1}. ${question.question}`,
+ label: truncate(`${question.questionIndex + 1}. ${question.question}`, 40),
value: `${SurveyQuestionBranchingType.SpecificQuestion}:${question.questionIndex}`,
})),
]}
/>
{branchingDropdownValue === SurveyQuestionBranchingType.ResponseBased && (
-
- TODO: dropdowns for the response-based branching
-
+
)}
>
)
}
+
+function QuestionResponseBasedBranchingInput({
+ questionIndex,
+ question,
+}: {
+ questionIndex: number
+ question: RatingSurveyQuestion | MultipleSurveyQuestion
+}): JSX.Element {
+ const { survey, getResponseBasedBranchingDropdownValue } = useValues(surveyLogic)
+ const { setResponseBasedBranchingForQuestion } = useActions(surveyLogic)
+
+ const availableNextQuestions = survey.questions
+ .map((question, questionIndex) => ({
+ ...question,
+ questionIndex,
+ }))
+ .filter((_, idx) => questionIndex !== idx)
+
+ let config: { value: string | number; label: string }[] = []
+
+ if (question.type === SurveyQuestionType.Rating && question.scale === 3) {
+ config = [
+ { value: 'negative', label: '1 (Negative)' },
+ { value: 'neutral', label: '2 (Neutral)' },
+ { value: 'positive', label: '3 (Positive)' },
+ ]
+ } else if (question.type === SurveyQuestionType.Rating && question.scale === 5) {
+ config = [
+ { value: 'negative', label: '1 to 2 (Negative)' },
+ { value: 'neutral', label: '3 (Neutral)' },
+ { value: 'positive', label: '4 to 5 (Positive)' },
+ ]
+ } else if (question.type === SurveyQuestionType.Rating && question.scale === 10) {
+ config = [
+ // NPS categories
+ { value: 'detractors', label: '0 to 6 (Detractors)' },
+ { value: 'passives', label: '7 to 8 (Passives)' },
+ { value: 'promoters', label: '9 to 10 (Promoters)' },
+ ]
+ } else if (question.type === SurveyQuestionType.SingleChoice) {
+ config = question.choices.map((choice, choiceIndex) => ({
+ value: choiceIndex,
+ label: `Option ${choiceIndex + 1} ("${truncate(choice, 15)}")`,
+ }))
+ }
+
+ return (
+
+ {config.map(({ value, label }, i) => (
+
+
+
+ If the answer is {label}, go to:
+
+
+
+ {
+ let specificQuestionIndex
+ if (nextStep.startsWith(SurveyQuestionBranchingType.SpecificQuestion)) {
+ specificQuestionIndex = parseInt(nextStep.split(':')[1])
+ nextStep = SurveyQuestionBranchingType.SpecificQuestion
+ }
+ setResponseBasedBranchingForQuestion(
+ questionIndex,
+ value,
+ nextStep,
+ specificQuestionIndex
+ )
+ }}
+ options={[
+ ...(questionIndex < survey.questions.length - 1
+ ? [
+ {
+ label: 'Next question',
+ value: SurveyQuestionBranchingType.NextQuestion,
+ },
+ ]
+ : []),
+ {
+ label: 'Confirmation message',
+ value: SurveyQuestionBranchingType.ConfirmationMessage,
+ },
+ ...availableNextQuestions.map((question) => ({
+ label: truncate(`${question.questionIndex + 1}. ${question.question}`, 20),
+ value: `${SurveyQuestionBranchingType.SpecificQuestion}:${question.questionIndex}`,
+ })),
+ ]}
+ />
+
+
+ ))}
+
+ )
+}
diff --git a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx
index ec3b03d54b41d..e1b24b1bea182 100644
--- a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx
+++ b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx
@@ -87,11 +87,9 @@ export function SurveyEditQuestionHeader({
export function SurveyEditQuestionGroup({ index, question }: { index: number; question: any }): JSX.Element {
const { survey, descriptionContentType } = useValues(surveyLogic)
- const { setDefaultForQuestionType, setSurveyValue } = useActions(surveyLogic)
+ const { setDefaultForQuestionType, setSurveyValue, resetBranchingForQuestion } = useActions(surveyLogic)
const { featureFlags } = useValues(enabledFeaturesLogic)
- const hasBranching =
- featureFlags[FEATURE_FLAGS.SURVEYS_BRANCHING_LOGIC] &&
- (question.type === SurveyQuestionType.Rating || question.type === SurveyQuestionType.SingleChoice)
+ const hasBranching = featureFlags[FEATURE_FLAGS.SURVEYS_BRANCHING_LOGIC]
const initialDescriptionContentType = descriptionContentType(index) ?? 'text'
@@ -134,6 +132,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu
editingDescription,
editingThankYouMessage
)
+ resetBranchingForQuestion(index)
}}
options={[
{
@@ -201,6 +200,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu
const newQuestions = [...survey.questions]
newQuestions[index] = newQuestion
setSurveyValue('questions', newQuestions)
+ resetBranchingForQuestion(index)
}}
/>
@@ -212,8 +212,17 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu
label: '1 - 5',
value: 5,
},
- ...(question.display === 'number' ? [{ label: '0 - 10', value: 10 }] : []),
+ ...(question.display === 'number'
+ ? [{ label: '0 - 10 (Net Promoter Score)', value: 10 }]
+ : []),
]}
+ onChange={(val) => {
+ const newQuestion = { ...survey.questions[index], scale: val }
+ const newQuestions = [...survey.questions]
+ newQuestions[index] = newQuestion
+ setSurveyValue('questions', newQuestions)
+ resetBranchingForQuestion(index)
+ }}
/>
diff --git a/frontend/src/scenes/surveys/surveyLogic.test.ts b/frontend/src/scenes/surveys/surveyLogic.test.ts
index 56e6615beb36f..4b12c4b1459f1 100644
--- a/frontend/src/scenes/surveys/surveyLogic.test.ts
+++ b/frontend/src/scenes/surveys/surveyLogic.test.ts
@@ -1,9 +1,9 @@
-import { expectLogic } from 'kea-test-utils'
+import { expectLogic, partial } from 'kea-test-utils'
import { surveyLogic } from 'scenes/surveys/surveyLogic'
import { useMocks } from '~/mocks/jest'
import { initKeaTests } from '~/test/init'
-import { Survey, SurveyQuestionType, SurveyType } from '~/types'
+import { Survey, SurveyQuestionBranchingType, SurveyQuestionType, SurveyType } from '~/types'
const MULTIPLE_CHOICE_SURVEY: Survey = {
id: '018b22a3-09b1-0000-2f5b-1bd8352ceec9',
@@ -398,3 +398,439 @@ describe('single choice survey with open choice logic', () => {
})
})
})
+
+describe('set response-based survey branching', () => {
+ let logic: ReturnType
+
+ beforeEach(() => {
+ initKeaTests()
+ logic = surveyLogic({ id: 'new' })
+ logic.mount()
+ })
+
+ const SURVEY: Survey = {
+ id: '118b22a3-09b1-0000-2f5b-1bd8352ceec9',
+ name: 'My survey',
+ description: '',
+ type: SurveyType.Popover,
+ linked_flag: null,
+ linked_flag_id: null,
+ targeting_flag: null,
+ questions: [],
+ 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,
+ responses_limit: null,
+ }
+
+ describe('main', () => {
+ // Single-choice question
+ it('set response-based branching for a single-choice question', async () => {
+ SURVEY.questions = [
+ {
+ type: SurveyQuestionType.SingleChoice,
+ choices: ['Yes', 'No'],
+ question: 'Are you happy with our service?',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Glad to hear that. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Sorry to hear that. Tell us more!',
+ description: '',
+ },
+ ]
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SURVEY)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ const questionIndex = 0
+
+ await expectLogic(logic, () => {
+ logic.actions.setQuestionBranchingType(
+ questionIndex,
+ SurveyQuestionBranchingType.ResponseBased,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'Yes',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 1
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'No',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 2
+ )
+ })
+ .toDispatchActions([
+ 'setQuestionBranchingType',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ ])
+ .toMatchValues({
+ survey: partial({
+ questions: [
+ {
+ ...SURVEY.questions[0],
+ branching: {
+ type: SurveyQuestionBranchingType.ResponseBased,
+ responseValues: { Yes: 1, No: 2 },
+ },
+ },
+ { ...SURVEY.questions[1] },
+ { ...SURVEY.questions[2] },
+ ],
+ }),
+ })
+ })
+
+ // Rating question, scale 1-3
+ it('set response-based branching for a rating question with scale 3', async () => {
+ SURVEY.questions = [
+ {
+ type: SurveyQuestionType.Rating,
+ question: 'How happy are you?',
+ description: '',
+ display: 'number',
+ scale: 3,
+ lowerBoundLabel: 'Unhappy',
+ upperBoundLabel: 'Happy',
+ buttonText: 'Submit',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Sorry to hear that. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Seems you are not completely happy. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Glad to hear that. Tell us more!',
+ description: '',
+ },
+ ]
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SURVEY)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ const questionIndex = 0
+
+ await expectLogic(logic, () => {
+ logic.actions.setQuestionBranchingType(
+ questionIndex,
+ SurveyQuestionBranchingType.ResponseBased,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'negative',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 1
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'neutral',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 2
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'positive',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 3
+ )
+ })
+ .toDispatchActions([
+ 'setQuestionBranchingType',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ ])
+ .toMatchValues({
+ survey: partial({
+ questions: [
+ {
+ ...SURVEY.questions[0],
+ branching: {
+ type: SurveyQuestionBranchingType.ResponseBased,
+ responseValues: { negative: 1, neutral: 2, positive: 3 },
+ },
+ },
+ { ...SURVEY.questions[1] },
+ { ...SURVEY.questions[2] },
+ { ...SURVEY.questions[3] },
+ ],
+ }),
+ })
+ })
+
+ // Rating question, scale 1-5
+ it('set response-based branching for a rating question with scale 5', async () => {
+ SURVEY.questions = [
+ {
+ type: SurveyQuestionType.Rating,
+ question: 'How happy are you?',
+ description: '',
+ display: 'number',
+ scale: 5,
+ lowerBoundLabel: 'Unhappy',
+ upperBoundLabel: 'Happy',
+ buttonText: 'Submit',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Sorry to hear that. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Seems you are not completely happy. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Glad to hear that. Tell us more!',
+ description: '',
+ },
+ ]
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SURVEY)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ const questionIndex = 0
+
+ await expectLogic(logic, () => {
+ logic.actions.setQuestionBranchingType(
+ questionIndex,
+ SurveyQuestionBranchingType.ResponseBased,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'negative',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 1
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'neutral',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 2
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'positive',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 3
+ )
+ })
+ .toDispatchActions([
+ 'setQuestionBranchingType',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ ])
+ .toMatchValues({
+ survey: partial({
+ questions: [
+ {
+ ...SURVEY.questions[0],
+ branching: {
+ type: SurveyQuestionBranchingType.ResponseBased,
+ responseValues: { negative: 1, neutral: 2, positive: 3 },
+ },
+ },
+ { ...SURVEY.questions[1] },
+ { ...SURVEY.questions[2] },
+ { ...SURVEY.questions[3] },
+ ],
+ }),
+ })
+ })
+
+ // Rating question, scale 0-10 (NPS)
+ it('set response-based branching for a rating question with scale 10', async () => {
+ SURVEY.questions = [
+ {
+ type: SurveyQuestionType.Rating,
+ question: 'How happy are you?',
+ description: '',
+ display: 'number',
+ scale: 10,
+ lowerBoundLabel: 'Unhappy',
+ upperBoundLabel: 'Happy',
+ buttonText: 'Submit',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Sorry to hear that. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Seems you are not completely happy. Tell us more!',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Glad to hear that. Tell us more!',
+ description: '',
+ },
+ ]
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SURVEY)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ const questionIndex = 0
+
+ await expectLogic(logic, () => {
+ logic.actions.setQuestionBranchingType(
+ questionIndex,
+ SurveyQuestionBranchingType.ResponseBased,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'detractors',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 1
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'passives',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 2
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 'promoters',
+ SurveyQuestionBranchingType.SpecificQuestion,
+ 3
+ )
+ })
+ .toDispatchActions([
+ 'setQuestionBranchingType',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ ])
+ .toMatchValues({
+ survey: partial({
+ questions: [
+ {
+ ...SURVEY.questions[0],
+ branching: {
+ type: SurveyQuestionBranchingType.ResponseBased,
+ responseValues: { detractors: 1, passives: 2, promoters: 3 },
+ },
+ },
+ { ...SURVEY.questions[1] },
+ { ...SURVEY.questions[2] },
+ { ...SURVEY.questions[3] },
+ ],
+ }),
+ })
+ })
+
+ // Branch out to Next question / Confirmation message
+ it('branch out to next question or confirmation message', async () => {
+ SURVEY.questions = [
+ {
+ type: SurveyQuestionType.SingleChoice,
+ choices: ['Yes', 'No'],
+ question: 'Are you happy with our service?',
+ description: '',
+ },
+ {
+ type: SurveyQuestionType.Open,
+ question: 'Sorry to hear that. Tell us more!',
+ description: '',
+ },
+ ]
+
+ await expectLogic(logic, () => {
+ logic.actions.loadSurveySuccess(SURVEY)
+ }).toDispatchActions(['loadSurveySuccess'])
+
+ const questionIndex = 0
+
+ await expectLogic(logic, () => {
+ logic.actions.setQuestionBranchingType(
+ questionIndex,
+ SurveyQuestionBranchingType.ResponseBased,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 0,
+ SurveyQuestionBranchingType.ConfirmationMessage,
+ undefined
+ )
+ logic.actions.setResponseBasedBranchingForQuestion(
+ questionIndex,
+ 1,
+ SurveyQuestionBranchingType.NextQuestion,
+ undefined
+ )
+ })
+ .toDispatchActions([
+ 'setQuestionBranchingType',
+ 'setResponseBasedBranchingForQuestion',
+ 'setResponseBasedBranchingForQuestion',
+ ])
+ .toMatchValues({
+ survey: partial({
+ questions: [
+ {
+ ...SURVEY.questions[0],
+ branching: {
+ type: SurveyQuestionBranchingType.ResponseBased,
+ responseValues: { 0: SurveyQuestionBranchingType.ConfirmationMessage }, // Branching out to "Next question" is implicit
+ },
+ },
+ { ...SURVEY.questions[1] },
+ ],
+ }),
+ })
+ })
+ })
+})
diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx
index 9b3964b64fdf3..b66a5326228a3 100644
--- a/frontend/src/scenes/surveys/surveyLogic.tsx
+++ b/frontend/src/scenes/surveys/surveyLogic.tsx
@@ -157,7 +157,18 @@ export const surveyLogic = kea([
isEditingDescription,
isEditingThankYouMessage,
}),
- setQuestionBranching: (questionIndex, value) => ({ questionIndex, value }),
+ setQuestionBranchingType: (questionIndex, type, specificQuestionIndex) => ({
+ questionIndex,
+ type,
+ specificQuestionIndex,
+ }),
+ setResponseBasedBranchingForQuestion: (questionIndex, responseValue, nextStep, specificQuestionIndex) => ({
+ questionIndex,
+ responseValue,
+ nextStep,
+ specificQuestionIndex,
+ }),
+ resetBranchingForQuestion: (questionIndex) => ({ questionIndex }),
archiveSurvey: true,
setWritingHTMLDescription: (writingHTML: boolean) => ({ writingHTML }),
setSurveyTemplateValues: (template: any) => ({ template }),
@@ -661,38 +672,87 @@ export const surveyLogic = kea([
const newTemplateSurvey = { ...NEW_SURVEY, ...template }
return newTemplateSurvey
},
- setQuestionBranching: (state, { questionIndex, value }) => {
+ setQuestionBranchingType: (state, { questionIndex, type, specificQuestionIndex }) => {
const newQuestions = [...state.questions]
const question = newQuestions[questionIndex]
- if (
- question.type !== SurveyQuestionType.Rating &&
- question.type !== SurveyQuestionType.SingleChoice
- ) {
- throw new Error(
- `Survey question type must be ${SurveyQuestionType.Rating} or ${SurveyQuestionType.SingleChoice}`
- )
- }
-
- if (value === SurveyQuestionBranchingType.NextQuestion) {
+ if (type === SurveyQuestionBranchingType.NextQuestion) {
delete question.branching
- } else if (value === SurveyQuestionBranchingType.ConfirmationMessage) {
+ } else if (type === SurveyQuestionBranchingType.ConfirmationMessage) {
question.branching = {
type: SurveyQuestionBranchingType.ConfirmationMessage,
}
- } else if (value === SurveyQuestionBranchingType.ResponseBased) {
+ } else if (type === SurveyQuestionBranchingType.ResponseBased) {
+ if (
+ question.type !== SurveyQuestionType.Rating &&
+ question.type !== SurveyQuestionType.SingleChoice
+ ) {
+ throw new Error(
+ `Survey question type must be ${SurveyQuestionType.Rating} or ${SurveyQuestionType.SingleChoice}`
+ )
+ }
+
question.branching = {
type: SurveyQuestionBranchingType.ResponseBased,
- responseValue: {},
+ responseValues: {},
}
- } else if (value.startsWith(SurveyQuestionBranchingType.SpecificQuestion)) {
- const nextQuestionIndex = parseInt(value.split(':')[1])
+ } else if (type === SurveyQuestionBranchingType.SpecificQuestion) {
question.branching = {
type: SurveyQuestionBranchingType.SpecificQuestion,
- index: nextQuestionIndex,
+ index: specificQuestionIndex,
+ }
+ }
+
+ newQuestions[questionIndex] = question
+ return {
+ ...state,
+ questions: newQuestions,
+ }
+ },
+ setResponseBasedBranchingForQuestion: (
+ state,
+ { questionIndex, responseValue, nextStep, specificQuestionIndex }
+ ) => {
+ const newQuestions = [...state.questions]
+ const question = newQuestions[questionIndex]
+
+ if (
+ question.type !== SurveyQuestionType.Rating &&
+ question.type !== SurveyQuestionType.SingleChoice
+ ) {
+ throw new Error(
+ `Survey question type must be ${SurveyQuestionType.Rating} or ${SurveyQuestionType.SingleChoice}`
+ )
+ }
+
+ if (question.branching?.type !== SurveyQuestionBranchingType.ResponseBased) {
+ throw new Error(
+ `Survey question branching type must be ${SurveyQuestionBranchingType.ResponseBased}`
+ )
+ }
+
+ if ('responseValues' in question.branching) {
+ if (nextStep === SurveyQuestionBranchingType.NextQuestion) {
+ delete question.branching.responseValues[responseValue]
+ } else if (nextStep === SurveyQuestionBranchingType.ConfirmationMessage) {
+ question.branching.responseValues[responseValue] =
+ SurveyQuestionBranchingType.ConfirmationMessage
+ } else if (nextStep === SurveyQuestionBranchingType.SpecificQuestion) {
+ question.branching.responseValues[responseValue] = specificQuestionIndex
}
}
+ newQuestions[questionIndex] = question
+ return {
+ ...state,
+ questions: newQuestions,
+ }
+ },
+ resetBranchingForQuestion: (state, { questionIndex }) => {
+ const newQuestions = [...state.questions]
+ const question = newQuestions[questionIndex]
+ delete question.branching
+
newQuestions[questionIndex] = question
return {
...state,
@@ -943,6 +1003,32 @@ export const surveyLogic = kea([
return SurveyQuestionBranchingType.NextQuestion
}
+ return SurveyQuestionBranchingType.ConfirmationMessage
+ },
+ ],
+ getResponseBasedBranchingDropdownValue: [
+ (s) => [s.survey],
+ (survey) => (questionIndex: number, question: RatingSurveyQuestion | MultipleSurveyQuestion, response) => {
+ if (!question.branching || !('responseValues' in question.branching)) {
+ return SurveyQuestionBranchingType.NextQuestion
+ }
+
+ // If a value is mapped onto an integer, we're redirecting to a specific question
+ if (Number.isInteger(question.branching.responseValues[response])) {
+ const nextQuestionIndex = question.branching.responseValues[response]
+ return `${SurveyQuestionBranchingType.SpecificQuestion}:${nextQuestionIndex}`
+ }
+
+ // If any other value is present (practically only Confirmation message), return that value
+ if (question.branching?.responseValues?.[response]) {
+ return question.branching.responseValues[response]
+ }
+
+ // No branching specified, default to Next question / Confirmation message
+ if (questionIndex < survey.questions.length - 1) {
+ return SurveyQuestionBranchingType.NextQuestion
+ }
+
return SurveyQuestionBranchingType.ConfirmationMessage
},
],
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index b91060b2e20be..e568b42145ce6 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -2660,6 +2660,11 @@ export interface SurveyQuestionBase {
descriptionContentType?: SurveyQuestionDescriptionContentType
optional?: boolean
buttonText?: string
+ branching?:
+ | NextQuestionBranching
+ | ConfirmationMessageBranching
+ | ResponseBasedBranching
+ | SpecificQuestionBranching
}
export interface BasicSurveyQuestion extends SurveyQuestionBase {
@@ -2723,7 +2728,7 @@ interface ConfirmationMessageBranching {
interface ResponseBasedBranching {
type: SurveyQuestionBranchingType.ResponseBased
- responseValue: Record
+ responseValues: Record
}
interface SpecificQuestionBranching {