add intro questoinare to gather participant details

This commit is contained in:
Robin Olsen
2026-03-12 14:56:07 +01:00
parent 08ce45c99a
commit a51fd848d6
6 changed files with 166 additions and 27 deletions

View File

@@ -38,6 +38,14 @@ type keyMap struct {
Quit key.Binding
}
type introModel struct {
stage int
width, height int
userQuit bool
keys keyMap
}
func newIntroModel() introModel {
var keys = keyMap{
Start: key.NewBinding(
key.WithKeys("enter"),
@@ -56,16 +64,9 @@ var keys = keyMap{
key.WithHelp("esc", "quit"),
),
}
type introModel struct {
stage int
width, height int
userQuit bool
}
func newIntroModel() introModel {
return introModel{
stage: 1,
keys: keys,
}
}
@@ -90,18 +91,18 @@ func (m introModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.SetSize(msg.Width, msg.Height)
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Start) && m.stage == stages:
case key.Matches(msg, m.keys.Start) && m.stage == stages:
return m, tea.Quit
case key.Matches(msg, keys.Continue):
case key.Matches(msg, m.keys.Continue):
if m.stage < stages {
m.stage++
}
case key.Matches(msg, keys.Back):
case key.Matches(msg, m.keys.Back):
if m.stage > 1 {
m.stage--
}
return m, nil
case key.Matches(msg, keys.Quit):
case key.Matches(msg, m.keys.Quit):
m.userQuit = true
return m, tea.Quit
}
@@ -139,11 +140,11 @@ func (m introModel) View() string {
b.WriteString(boxStyle.Render(style.TextStyle.Render(content)))
b.WriteString("\n")
helpText := "Press " + keys.Continue.Help().Key + " to continue • " +
keys.Back.Help().Key + " to go back • " + keys.Quit.Help().Key + " to quit"
helpText := "Press " + m.keys.Continue.Help().Key + " to continue • " +
m.keys.Back.Help().Key + " to go back • " + m.keys.Quit.Help().Key + " to quit"
if m.stage == stages {
helpText += "\nPress " + keys.Start.Help().Key + " to start the survey"
helpText += "\nPress " + m.keys.Start.Help().Key + " to start the survey"
}
b.WriteString(style.HelpStyle.Render(helpText))

View File

@@ -0,0 +1,96 @@
package main
import (
"github.com/LazyBachelor/LazyPM/pkg/task"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
)
type IntroQuestionnaire struct{}
func newIntroQuestionnaire() *IntroQuestionnaire {
return &IntroQuestionnaire{}
}
func (iq *IntroQuestionnaire) Run() (map[string]any, error) {
model := task.NewQuestionnaireModel(iq.Questions(), iq.Keys())
app := tea.NewProgram(model, tea.WithAltScreen())
if _, err := app.Run(); err != nil {
return nil, err
}
return model.GetAnswers(), nil
}
func (iq *IntroQuestionnaire) Questions() task.Questions {
return task.Questions{
huh.NewGroup(
huh.NewSelect[string]().
Title("Which age group do you belong to?").
Description("This helps us understand the background of participants.").
Options(
huh.NewOption("Under 18", "under_18"),
huh.NewOption("1824", "18_24"),
huh.NewOption("2534", "25_34"),
huh.NewOption("3544", "35_44"),
huh.NewOption("45+", "45_plus"),
).
Key("age_group"),
),
huh.NewGroup(
huh.NewSelect[string]().
Title("Are you a student, employed, or both?").
Description("This helps us understand your current situation.").
Options(
huh.NewOption("Student", "student"),
huh.NewOption("Employed", "employed"),
huh.NewOption("Both student and employed", "both"),
huh.NewOption("Neither", "neither"),
).
Key("occupation_status"),
),
huh.NewGroup(
huh.NewSelect[string]().
Title("How far along are you in your education?").
Description("Select the option that best matches your current level.").
Options(
huh.NewOption("Primary school", "primary"),
huh.NewOption("Secondary school", "secondary"),
huh.NewOption("Bachelor's degree", "bachelor"),
huh.NewOption("Master's degree", "master"),
huh.NewOption("PhD / Doctorate", "phd"),
huh.NewOption("Other", "other"),
).
Key("education_level"),
),
huh.NewGroup(
huh.NewSelect[string]().
Title("How would you describe your experience with the command line?").
Description("This helps us tailor the questions to your experience level.").
Options(
huh.NewOption("No experience", "none"),
huh.NewOption("Some experience", "some"),
huh.NewOption("Extensive experience", "extensive"),
).
Key("cli_experience"),
),
huh.NewGroup(
huh.NewSelect[string]().
Title("How often do you use the command line?").
Description("Select the option that best describes your usage.").
Options(
huh.NewOption("Never", "never"),
huh.NewOption("Rarely", "rarely"),
huh.NewOption("Weekly", "weekly"),
huh.NewOption("Several times a week", "multiple_weekly"),
huh.NewOption("Daily", "daily"),
).
Key("cli_frequency"),
),
}
}
func (iq *IntroQuestionnaire) Keys() []string {
return []string{"age_group", "occupation_status", "education_level", "cli_experience", "cli_frequency"}
}

View File

@@ -130,6 +130,18 @@ func runStartCmd(cmd *cobra.Command, args []string) error {
return returnIfUserQuit(err, "failed to run intro")
}
}
introAnswers, err := newIntroQuestionnaire().Run()
if err != nil {
return returnIfUserQuit(err, "failed to run intro questionnaire")
}
if app != nil && app.Stats != nil && introAnswers != nil {
if err := app.Stats.RecordIntroQuestionnaireAnswers(introAnswers); err != nil {
cmd.Printf("Failed to record intro questionnaire answers: %v\n", err)
}
}
if err := taskLoop(cmd.Context(), app, surveyTasks, interfaces); err != nil {
return returnIfUserQuit(err, "task loop failed")
}

View File

@@ -131,3 +131,29 @@ func (s *StatisticsService) RecordTaskRun(ctx context.Context, run models.TaskRu
return nil
}
func (s *StatisticsService) RecordIntroQuestionnaireAnswers(answers map[string]any) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.storage.Data == nil {
return fmt.Errorf("statistics data not initialized")
}
s.storage.Data.IntroQuestionnaireAnswers = answers
if err := s.storage.Save(); err != nil {
if s.logger != nil {
s.logger.Error("failed to save intro questionnaire answers", "error", err)
}
return err
}
if s.logger != nil {
s.logger.Info("intro questionnaire answers saved",
"answers_count", len(answers),
)
}
return nil
}

View File

@@ -61,4 +61,5 @@ type StatsService interface {
GetStatistics() (Statistics, error)
GetParticipantID() primitive.ObjectID
RecordTaskRun(ctx context.Context, run TaskRunMetrics) error
RecordIntroQuestionnaireAnswers(answers map[string]any) error
}

View File

@@ -21,6 +21,9 @@ type Statistics struct {
AverageDurationMs int64 `bson:"average_duration_ms" json:"average_duration_ms"`
TotalUserActions int `bson:"total_user_actions" json:"total_user_actions"`
IntroQuestionnaireAnswers map[string]any `bson:"intro_questionnaire_answers" json:"intro_questionnaire_answers"`
QuestionnairesCompleted int `bson:"questionnaires_completed" json:"questionnaires_completed"`
QuestionnairesAbandoned int `bson:"questionnaires_abandoned" json:"questionnaires_abandoned"`