Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(surveys): Add open-ended choices for multiple and single choice surveys #18258

Merged
merged 11 commits into from
Nov 22, 2023
54 changes: 54 additions & 0 deletions cypress/e2e/surveys.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,58 @@ describe('Surveys', () => {
cy.get('[data-attr=delete-survey]').click()
cy.get('.Toastify__toast-body').contains('Survey deleted').should('be.visible')
})

it('creates a new multiple choice survey with an open-ended choice', () => {
cy.get('h1').should('contain', 'Surveys')
cy.get('[data-attr=new-survey]').click()
cy.get('[data-attr=new-blank-survey]').click()

// add a multiple choice question with an open-ended question
cy.get('[data-attr=survey-name]').focus().type(name).should('have.value', name)
cy.get('[data-attr="survey-question-type-0"]').click()
cy.contains('Multiple choice select').click()
cy.get('button').contains('Add open-ended choice').click()

// check default open-ended choice form input and appearance after
// open-ended choice was added
cy.get('.LemonInput__input[value="Other"]')
cy.get('.choice-option').eq(3).contains('Other:')
cy.get('.choice-option').eq(3).find('input[type="text"]').should('have.value', '')

// typing in open-ended question's appearance automatically checks the
// checkbox
cy.get('.choice-option').eq(3).find('input[type="checkbox"]').should('not.be.checked')
cy.get('.choice-option').eq(3).find('input[type="text"]').type('Outreach')
cy.get('.choice-option').eq(3).find('input[type="checkbox"]').should('be.checked')

// clicking on open-ended question's appearance label unchecks or checks
// the checkbox
cy.get('.choice-option').eq(3).click()
cy.get('.choice-option').eq(3).find('input[type="checkbox"]').should('not.be.checked')
cy.get('.choice-option').eq(3).click()
cy.get('.choice-option').eq(3).find('input[type="checkbox"]').should('be.checked')

// removing text in open-ended question's appearance automatically
// unchecks the checkbox
cy.get('.choice-option').eq(3).find('input[type="text"]').clear()

// open-ended question label doesn't change even if appearance input
// changes
cy.get('.LemonInput__input[value="Other"]')

// change open-ended choice after the label was added
cy.contains('Choices').parent().find('input[type="text"]').eq(3).clear()
cy.contains('Choices').parent().find('input[type="text"]').eq(3).type('First Choice')
cy.get('.choice-option').eq(3).contains('First Choice:')

// attempt to create and save survey
cy.get('[data-attr=save-survey]').first().click()

// after save there should be a launch button
cy.get('button[data-attr="launch-survey"]').should('have.text', 'Launch')

cy.clickNavMenu('surveys')
cy.get('[data-attr=surveys-table]').should('contain', name)
cy.get(`[data-row-key="${name}"]`).contains(name).click()
})
})
1 change: 1 addition & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ beforeEach(() => {
'surveys-new-creation-flow': true,
'surveys-results-visualizations': true,
'auto-redirect': true,
'surveys-open-choice': true,
notebooks: true,
})
)
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const FEATURE_FLAGS = {
NETWORK_PAYLOAD_CAPTURE: 'network-payload-capture', // owner: #team-monitoring
FEATURE_FLAG_COHORT_CREATION: 'feature-flag-cohort-creation', // owner: @neilkakkar #team-feature-success
INSIGHT_HORIZONTAL_CONTROLS: 'insight-horizontal-controls', // owner: @benjackwhite
SURVEYS_OPEN_CHOICE: 'surveys-open-choice', // owner: @ssoonmi, #team-feature-success
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
9 changes: 9 additions & 0 deletions frontend/src/scenes/surveys/EditSurvey.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,12 @@
background: var(--border-light);
}
}

.question-choice-open-ended-footer {
position: absolute;
bottom: -5px;
left: 6px;
font-size: 10px;
background-color: var(--bg-3000);
padding: 0 5px;
}
27 changes: 24 additions & 3 deletions frontend/src/scenes/surveys/SurveyAppearance.scss
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,13 @@
opacity: 1 !important;
}

.multiple-choice-options input[type='checkbox']:checked + label {
.multiple-choice-options input:checked + label {
font-weight: bold;
border: 1.5px solid rgb(0 0 0 / 100%);
}

.multiple-choice-options input:checked + label {
border: 1.5px solid rgb(0 0 0);
.multiple-choice-options input:checked + label input {
font-weight: bold;
}

.multiple-choice-options label {
Expand All @@ -243,6 +244,26 @@
background: white;
}

.multiple-choice-options .choice-option-open label {
padding-right: 30px;
display: flex;
flex-wrap: wrap;
gap: 8px;
max-width: 100%;
}

.multiple-choice-options .choice-option-open input:disabled + label {
opacity: 0.6;
}

.multiple-choice-options .choice-option-open label input {
position: relative;
opacity: 1;
flex-grow: 1;
border: 0;
outline: 0;
}

.thank-you-message {
position: relative;
bottom: 0;
Expand Down
91 changes: 80 additions & 11 deletions frontend/src/scenes/surveys/SurveyAppearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,64 @@ export function SurveyRatingAppearance({
)
}

const OpenEndedChoice = ({
label,
initialChecked,
inputType,
index,
}: {
label: string
initialChecked: boolean
inputType: string
textColor: string
index: number
}): JSX.Element => {
const textRef = useRef<HTMLInputElement | null>(null)
const checkRef = useRef<HTMLInputElement | null>(null)

return (
<div
className="choice-option choice-option-open"
onClick={() => {
if (checkRef.current?.checked || checkRef.current?.disabled) {
textRef.current?.focus()
}
}}
>
<input
id={`${label}-${index}`}
ref={checkRef}
type={inputType}
disabled={!initialChecked || !checkRef.current?.value}
defaultChecked={initialChecked}
name="choice"
/>
<label htmlFor={`${label}-${index}`}>
<span>{label}:</span>
<input
ref={textRef}
type="text"
maxLength={100}
onClick={(e) => e.stopPropagation()}
onChange={(e) => {
if (checkRef.current) {
checkRef.current.value = e.target.value
if (e.target.value) {
checkRef.current.disabled = false
checkRef.current.checked = true
} else {
checkRef.current.disabled = true
checkRef.current.checked = false
}
}
}}
/>
</label>
<span className="choice-check">{check}</span>
</div>
)
}

export function SurveyMultipleChoiceAppearance({
multipleChoiceQuestion,
appearance,
Expand Down Expand Up @@ -587,18 +645,29 @@ export function SurveyMultipleChoiceAppearance({
/>
)}
<div className="multiple-choice-options">
{(multipleChoiceQuestion.choices || []).map((choice, idx) => (
<div className="choice-option" key={idx}>
<input
{...(initialChecked ? { checked: initialChecked.includes(idx) } : null)}
type={inputType}
name="choice"
value={choice}
{(multipleChoiceQuestion.choices || []).map((choice, idx) =>
multipleChoiceQuestion?.hasOpenChoice && idx === multipleChoiceQuestion.choices?.length - 1 ? (
<OpenEndedChoice
key={idx}
index={idx}
initialChecked={!!initialChecked?.includes(idx)}
inputType={inputType}
label={choice}
textColor={textColor}
/>
<label>{choice}</label>
<span className="choice-check">{check}</span>
</div>
))}
) : (
<div className="choice-option" key={idx}>
<input
{...(initialChecked ? { defaultChecked: initialChecked.includes(idx) } : null)}
type={inputType}
name="choice"
value={choice}
/>
<label>{choice}</label>
<span className="choice-check">{check}</span>
</div>
)
)}
</div>
<div className="bottom-section">
<div className="buttons">
Expand Down
Loading
Loading