Skip to content

Commit

Permalink
feat(surveys branching): add UI to define response-based branching (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored Jun 14, 2024
1 parent ec59e6f commit 756b7f8
Show file tree
Hide file tree
Showing 7 changed files with 683 additions and 37 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
132 changes: 121 additions & 11 deletions frontend/src/scenes/surveys/QuestionBranchingInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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) => ({
Expand All @@ -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 (
<>
Expand All @@ -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
? [
Expand All @@ -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}`,
})),
]}
/>
</LemonField>
{branchingDropdownValue === SurveyQuestionBranchingType.ResponseBased && (
<div>
<em>TODO: dropdowns for the response-based branching</em>
</div>
<QuestionResponseBasedBranchingInput question={question} questionIndex={questionIndex} />
)}
</>
)
}

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 (
<div className="mt-2 space-y-2">
{config.map(({ value, label }, i) => (
<div key={i} className="flex">
<div className="w-2/3 flex items-center">
<div>
If the answer is<span className="font-bold">&nbsp;{label}</span>, go to:
</div>
</div>
<div className="w-1/3 flex justify-end">
<LemonSelect
className="w-full whitespace-nowrap"
value={getResponseBasedBranchingDropdownValue(questionIndex, question, value)}
data-attr={`branching-question-${questionIndex}`}
onSelect={(nextStep) => {
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}`,
})),
]}
/>
</div>
</div>
))}
</div>
)
}
19 changes: 14 additions & 5 deletions frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -134,6 +132,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu
editingDescription,
editingThankYouMessage
)
resetBranchingForQuestion(index)
}}
options={[
{
Expand Down Expand Up @@ -201,6 +200,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu
const newQuestions = [...survey.questions]
newQuestions[index] = newQuestion
setSurveyValue('questions', newQuestions)
resetBranchingForQuestion(index)
}}
/>
</LemonField>
Expand All @@ -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)
}}
/>
</LemonField>
</div>
Expand Down
Loading

0 comments on commit 756b7f8

Please sign in to comment.