-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
feat(surveys): Add open-ended choices for multiple and single choice surveys #18258
Conversation
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, post a comment or remove the |
This PR was closed due to lack of activity. Feel free to reopen if it's still relevant. |
Updated the survey appearance in posthog-js: |
hi @ssoonmi this is great! :) One thing I'd mention is we don't need to keep the open ended choices as a custom string. We can instead add another parameter, called On the product side, I'm not sure when multiple open ended choices make sense - will let @liyiy answer this. Right now, it seems one is sufficient, in which case we can refine the data model to be even simpler. |
@neilkakkar Great! So if it is confirmed that y'all only need one open-ended choice per question, would the field be singular instead of plural ( |
+1 on the restriction for only 1 open ended choice per question and having it always be at the bottom of the choices list |
questions: [ | ||
{ | ||
type: SurveyQuestionType.SingleChoice, | ||
choices: ['Yes', 'No', 'OPENlabel=Maybe (explain)'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might start requiring more information to be added to our choices than an array of strings can handle. This is on us so I'm happy to update that part myself 😄 unless you really want to do it which is also awesome.
What I'm proposing is to add another property to the question object that's something like:
choicesProperties: [{ choiceId: 2, openTextOptional: true }]
where it provides more details about the choice instead of having to rely on parsing the string for the OPENlabel
prefix.
This could also be useful when we also implement logic branching survey questions that depend on specific choices, but basically it'd be nice to have some way of adding properties to answers.
cc @neilkakkar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense! Probably easier to always index by the id than having an array with key choiceId, so:
choiceProperties: {
<id: number 0 to len(choices)-1>: { attributes }
}
.. although, it's a bit janky to keep these two different things in sync, that closely depend on each other, so it's worth deprecating this in the future and move to a choices array of objects, which has the label value, and all the other attributes.
For example, if I delete a choice in the middle, the indexes of all choices after that choice need to change.
So, I'd say go down this route when we do have the other attributes & deprecate the current format in one go; and in the meanwhile since the open choice is always going to be last + not common to all choice options, keep it as a separate thing that we can incorporate into the deprecation in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The additional attributes can always be added later on, so if we know we want to add choiceProperties
and deprecate choices
later on anyway, why not just add it now, skip the additional open_ended_choices
field for the question, and only have the smallest addition of isOpenTextOption
attribute?
I think this could be okay, since we'll only need to keep the "other" choice in sync with choiceProperties
for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you meant to refactor choices
into an array of objects, which in that case, then we can hold off on choiceProperties
and the string prefix format should be a fine placeholder for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I also thought adding choiceProperties
would be fine was that not all choices actually need additional attributes onto it 😁 So we would only need to store properties for certain choices, at the expense of just keeping track the ID index value when answer choices get deleted/added, seems like an okay tradeoff to me to reduce bloat on the survey itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, since there choices
field is just a JSON string in the database, it can just be converted to an array of objects instead of an array of strings by the API backend when retrieving, creating, or updating a survey.
When creating or updating a survey:
choices = raw_question.get("choices")
if choices and not isinstance(choices, list):
raise serializers.ValidationError("Question choices must be a list of strings")
# TODO: if each choice is an instance of a string, convert it to an object before saving to database
When retrieving surveys:
surveys = SurveyAPISerializer(
Survey.objects.filter(team_id=team.id).exclude(archived=True).select_related("linked_flag", "targeting_flag"),
many=True,
).data
# TODO: convert each survey's choices to an array of objects instead of an array of strings
If you set up the backend to migrate the legacy survey data like this, the frontend will not have to have logic to handle both cases where choices can be an array of objects or an array of strings. The frontend will always get an array of objects. So you can isolate the location where you may introduce potential bugs from legacy choices to the backend safely while being able to add more potential properties to a choice in the future (or even having multiple open-ended choices in a question). It's also easier to test the backend API in isolation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem is that posthog-js consumes this as well, and every API client that creates their own surveys, so changing structure will be a breaking change. It isn't limited to our backend-frontend communication, but agree that when we do go this route, the backend can do the backwards-compatible conversion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, so because the consumers of the API isn't just limited to the PostHog frontend, the API must keep the original data structure of the choices field to whatever was set by the original consumer. So the logic for converting choices to an array of objects would have to happen on the consumer side. Should it convert the choices to be an array of objects for any survey updated in the frontend even if it was originally as an array of strings? Or should only new survey save choices as an array of objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neilkakkar I think the breaking change probably makes sense to do now, along with this new feature so it's easier in one go. I don't think there'll be too many more, if any, changes to add to the MCQ choices array object after this open text "other" field. And it'll also be nice to make this change earlier on rather than later when even more people are using it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Easiest to just implement the has_open_choice
field right now then and then we'll worry about choices array deprecation as it comes later on 😅
I updated this PR and the other one (PostHog/posthog-js#901) to use |
0b0c708
to
e205c70
Compare
I think we don't need the border for when the user has text input in the "Other" option, it looks good otherwise! |
I would recommend feature flagging this so we can get it merged ASAP without it breaking much. I can't figure out how to push to this branch lol otherwise I would help you feature flag it myself, but it should be a simple add. In your
And then in
And that should be it! To continue testing it locally, all you'll have to do is create a feature flag with |
Just put the 'Add open-ended choice' button behind the feature flag! |
Done! |
I'm breaking one of the Cypress CI tests that I made, but I can't run the Cypress tests locally to test anymore for some reason. I'll try to fix it now though. |
@liyiy would you know if the Cypress tests have all feature flags enabled? |
Oh yes sorry, the flag will need to be added to |
I tried to fix the code to pass as much of the CI tests as I could: 440e31f |
It looks like the backend tests are failing unrelated, can you try merging master again? It should fix it |
You mean merging master into this branch? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome stuff!!!
Problem
Addresses this issue: #17471
Changes
surveys.
choices
key on questions as an array of strings, I createdlogic to store and distinguish an open-ended choice from a regular choice
with a string prefix of
"OPENlabel="
.choices = ['Yes', 'No', 'OPENlabel=Maybe']
would produce two regularchoices (Yes and No) and one open-ended choice (Maybe)
"Other".
"OPENlabel=Other"
outlined, so I took creative liberty in the design and the user experience.
Would love a review and feedback on the design and UX.
form when editing a multiple or single-choice survey question. Design of the
button is identical to the existing "Add choice" button and is on the same
row, but will flex to a new row for smaller screen sizes.
multiple-choice surveys.
results to zero-fill choices only if they aren't open-ended choices.
with open-ended questions.
Survey Appearance:
Single-Choice Survey Form and Appearance:
Multiple-Choice Survey Form and Appearance:
Wrap buttons for small screen sizes:
👉 Stay up-to-date with PostHog coding conventions for a smoother review.
How did you test this code?
cypress/e2e/surveys.cy.ts
test file in Cypressand it passed in both Chrome and Microsoft Edge.
frontend/src/scenes/surveys/surveyLogic.test.ts
test file using the command
pnpm jest --testPathPattern=frontend/src/scenes/surveys
and it passed.posthog/api/test/test_survey.py
test file usingthe command
pytest posthog/api/test/test_survey.py
and it passed.it's been created and launched. Because of this, I couldn't test the view for
results of the survey. I would appreciate advice on how I could test this!
testing:
single-choice-survey-testing.mp4
multiple-choice-survey-testing.1.mov