Skip to content

feat(surveys): add slider question type#62158

Draft
TJLSmith0831 wants to merge 5 commits into
PostHog:masterfrom
TJLSmith0831:feat/survey-slider-question
Draft

feat(surveys): add slider question type#62158
TJLSmith0831 wants to merge 5 commits into
PostHog:masterfrom
TJLSmith0831:feat/survey-slider-question

Conversation

@TJLSmith0831

@TJLSmith0831 TJLSmith0831 commented Jun 8, 2026

Copy link
Copy Markdown

Problem

Survey creators want to ask users for a numeric value across a configurable range — e.g. "what price would you pay?" — without the endpoint bias that rating scales and multiple choice introduce. The existing question types push respondents toward the lowest or highest option, which doesn't reflect what they actually think.

Closes #60079

Changes

Adds a new slider question type with min, max, step, and optional prefix/suffix (e.g. $, %) and lower/upper bound labels.

  • Backend: new SurveySliderQuestionSchemaSerializer and validation in validate_questions enforcing min < max, positive step, and step <= range.
  • Frontend editor: type appears in the question-type dropdown with IconTuning. Config UI lets creators set min/max/step/prefix/suffix and bound labels.
  • Results: new SliderQuestionViz shows average/median/min/max stat cards plus a value histogram. Responses are bucketed via floor(value / step) * step at query time and cast through toFloat64OrNull.
  • Kea logic: type-switching defaults and translation cleanup preserve bound labels for slider just like rating.

The widget rendering itself depends on the companion PostHog/posthog-js#3774. Until that ships and the dependency is bumped, the in-editor live preview is empty for slider questions — the editor and results views work end-to-end on their own.

How did you test this code?

I'm an agent (Claude Opus 4.7) — only listing automated tests I actually ran.

  • products/surveys/backend/api/test/test_survey.py::TestSurveyQuestionValidation — slider acceptance + validation rejection cases (parameterized for min/max/step edge cases)
  • frontend/src/scenes/surveys/surveyLogic.test.tssetDefaultForQuestionType applies slider defaults
  • frontend/src/scenes/surveys/utils.test.tsbuildAggregateQuery emits the expected slider branch (Float64 cast, floor-bucket grouping)

No manual UI testing was performed — the live preview is blocked on posthog-js anyway.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

🤖 Agent context

Built with Claude Code (Opus 4.7) using a TDD workflow — failing test, minimal implementation, repeat — across backend validation, kea logic, editor UI, results query, and visualization phases.

Two notable decisions: (1) slider responses are cast to Float64 at query time rather than introducing a typed column, avoiding a ClickHouse migration; (2) results visualization is its own component rather than reusing rating's, since slider has continuous numeric data with summary stats rather than fixed buckets with branching/NPS.

Branching, validation rules, and SDK widget rendering are intentionally out of scope — branching can be added later if needed, and the widget rendering ships in the companion PostHog/posthog-js#3774.

Adds a slider question type with configurable min, max, step, and
optional prefix/suffix. Widget rendering requires a companion
posthog-js update before the live preview works.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. products/surveys/backend/api/test/test_survey.py, line 922-940 (link)

    P2 Duplicate test case

    test_slider_rejects_min_greater_than_max is already covered by the ("min_greater_than_max", {"min": 20, "max": 10, "step": 1}) case in the parameterized test_slider_validation_rejects_invalid_config test directly above it. The standalone test is redundant and adds noise without extra coverage.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: products/surveys/backend/api/test/test_survey.py
    Line: 922-940
    
    Comment:
    **Duplicate test case**
    
    `test_slider_rejects_min_greater_than_max` is already covered by the `("min_greater_than_max", {"min": 20, "max": 10, "step": 1})` case in the parameterized `test_slider_validation_rejects_invalid_config` test directly above it. The standalone test is redundant and adds noise without extra coverage.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
products/surveys/backend/api/survey.py:494-500
**Bound labels silently dropped on save**

`lowerBoundLabel` and `upperBoundLabel` are not declared in `SurveySliderQuestionSchemaSerializer`. The validation path calls `question_serializer.data` to build `cleaned_question`, which only returns declared fields. Any bound label values the user sets in the editor UI will be stripped before being persisted, so they will never be stored or returned by the API. The generated API schema (`SurveySliderQuestionSchemaApi`) confirms neither field is included. Contrast with `SurveyRatingQuestionSchemaSerializer`, which explicitly declares both fields.

### Issue 2 of 4
products/surveys/backend/api/survey.py:498
`step` is declared `required=False` here and therefore appears as optional in every generated API schema (`step?: number`). However, the validation code in `validate_questions` immediately raises `"Slider questions require 'step'."` when it is absent. Callers who follow the schema and omit `step` will receive a 400 error — the API contract is broken.

```suggestion
    step = serializers.FloatField(required=True, help_text="Step size for the slider.")
```

### Issue 3 of 4
products/surveys/backend/api/test/test_survey.py:922-940
**Duplicate test case**

`test_slider_rejects_min_greater_than_max` is already covered by the `("min_greater_than_max", {"min": 20, "max": 10, "step": 1})` case in the parameterized `test_slider_validation_rejects_invalid_config` test directly above it. The standalone test is redundant and adds noise without extra coverage.

### Issue 4 of 4
frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx:53-54
**Duplicate type guard**

`isSliderQuestion` is also exported from `questionTypeGuards.ts` (added in the same PR). The local definition here is identical. `SurveyEditQuestionRow.tsx` could import `isSliderQuestion` from `questionTypeGuards.ts` instead, keeping the guard in one place — consistent with the existing `isRatingQuestion` and `isLinkQuestion` guards that exist in both files as pre-existing debt.

Reviews (1): Last reviewed commit: "feat(surveys): add slider question type" | Re-trigger Greptile

Comment on lines +494 to +500
class SurveySliderQuestionSchemaSerializer(SurveyBaseQuestionSchemaSerializer):
type = serializers.ChoiceField(choices=["slider"], required=True)
min = serializers.FloatField(required=True, help_text="Minimum value of the slider.")
max = serializers.FloatField(required=True, help_text="Maximum value of the slider.")
step = serializers.FloatField(required=False, help_text="Step size for the slider.")
prefix = serializers.CharField(required=False, help_text="Prefix for the slider value (e.g., '$').")
suffix = serializers.CharField(required=False, help_text="Suffix for the slider value (e.g., '%').")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Bound labels silently dropped on save

lowerBoundLabel and upperBoundLabel are not declared in SurveySliderQuestionSchemaSerializer. The validation path calls question_serializer.data to build cleaned_question, which only returns declared fields. Any bound label values the user sets in the editor UI will be stripped before being persisted, so they will never be stored or returned by the API. The generated API schema (SurveySliderQuestionSchemaApi) confirms neither field is included. Contrast with SurveyRatingQuestionSchemaSerializer, which explicitly declares both fields.

Prompt To Fix With AI
This is a comment left during a code review.
Path: products/surveys/backend/api/survey.py
Line: 494-500

Comment:
**Bound labels silently dropped on save**

`lowerBoundLabel` and `upperBoundLabel` are not declared in `SurveySliderQuestionSchemaSerializer`. The validation path calls `question_serializer.data` to build `cleaned_question`, which only returns declared fields. Any bound label values the user sets in the editor UI will be stripped before being persisted, so they will never be stored or returned by the API. The generated API schema (`SurveySliderQuestionSchemaApi`) confirms neither field is included. Contrast with `SurveyRatingQuestionSchemaSerializer`, which explicitly declares both fields.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread products/surveys/backend/api/survey.py Outdated
type = serializers.ChoiceField(choices=["slider"], required=True)
min = serializers.FloatField(required=True, help_text="Minimum value of the slider.")
max = serializers.FloatField(required=True, help_text="Maximum value of the slider.")
step = serializers.FloatField(required=False, help_text="Step size for the slider.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 step is declared required=False here and therefore appears as optional in every generated API schema (step?: number). However, the validation code in validate_questions immediately raises "Slider questions require 'step'." when it is absent. Callers who follow the schema and omit step will receive a 400 error — the API contract is broken.

Suggested change
step = serializers.FloatField(required=False, help_text="Step size for the slider.")
step = serializers.FloatField(required=True, help_text="Step size for the slider.")
Prompt To Fix With AI
This is a comment left during a code review.
Path: products/surveys/backend/api/survey.py
Line: 498

Comment:
`step` is declared `required=False` here and therefore appears as optional in every generated API schema (`step?: number`). However, the validation code in `validate_questions` immediately raises `"Slider questions require 'step'."` when it is absent. Callers who follow the schema and omit `step` will receive a 400 error — the API contract is broken.

```suggestion
    step = serializers.FloatField(required=True, help_text="Step size for the slider.")
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +53 to +54

const isSliderQuestion = (question: SurveyQuestion): question is SliderSurveyQuestion =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicate type guard

isSliderQuestion is also exported from questionTypeGuards.ts (added in the same PR). The local definition here is identical. SurveyEditQuestionRow.tsx could import isSliderQuestion from questionTypeGuards.ts instead, keeping the guard in one place — consistent with the existing isRatingQuestion and isLinkQuestion guards that exist in both files as pre-existing debt.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx
Line: 53-54

Comment:
**Duplicate type guard**

`isSliderQuestion` is also exported from `questionTypeGuards.ts` (added in the same PR). The local definition here is identical. `SurveyEditQuestionRow.tsx` could import `isSliderQuestion` from `questionTypeGuards.ts` instead, keeping the guard in one place — consistent with the existing `isRatingQuestion` and `isLinkQuestion` guards that exist in both files as pre-existing debt.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@TJLSmith0831 TJLSmith0831 marked this pull request as ready for review June 15, 2026 11:22
@assign-reviewers-posthog assign-reviewers-posthog Bot requested a review from a team June 15, 2026 11:22
@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "Merge branch 'master' into feat/survey-s..." | Re-trigger Greptile

@TJLSmith0831 TJLSmith0831 reopened this Jun 16, 2026
@TJLSmith0831 TJLSmith0831 marked this pull request as draft June 16, 2026 10:15
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (3): Last reviewed commit: "Merge branch 'master' into feat/survey-s..." | Re-trigger Greptile

@scheduled-actions-posthog

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the stale label – otherwise this will be closed in another week. If you want to permanently keep it open, use the waiting label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slider question type for surveys

1 participant