From 86c8bc3143a327d26f3a28356d417cc947ac1668 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Tue, 17 Feb 2026 15:17:49 +0100 Subject: [PATCH] refactor for better reusability and maintainability --- cmd/survey/init.go | 2 +- cmd/survey/runner.go | 2 + cmd/survey/tasks/createIssue.go | 73 ++++++++++++++++++++++----------- cmd/survey/tasks/types.go | 27 ++++++++++++ pkg/task/task.go | 70 ++++++++++++++++++++++--------- pkg/task/types.go | 14 ++++++- 6 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 cmd/survey/tasks/types.go diff --git a/cmd/survey/init.go b/cmd/survey/init.go index 4f563ea..d4ca196 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -23,7 +23,7 @@ func initializeServices(ctx context.Context) (*service.Services, func(), error) func initTasks(svc *service.Services) []*task.Task { return []*task.Task{ - tasks.NewCreateIssueTask(svc), + tasks.NewCreateIssueTask(svc).Init(), } } diff --git a/cmd/survey/runner.go b/cmd/survey/runner.go index c41e870..68eb6bf 100644 --- a/cmd/survey/runner.go +++ b/cmd/survey/runner.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/pkg/task" ) @@ -15,6 +16,7 @@ func taskLoop(ctx context.Context, surveyTasks []*task.Task, interfaces []task.I for _, t := range surveyTasks { t.SetInterface(interfaces[interfaceIndex]) + t.SetInterfaceType(tasks.InterfaceToType(interfaces[interfaceIndex])) doneChan := make(chan bool, 1) quitChan := make(chan bool, 1) diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go index cdbd4ac..5dbc3bc 100644 --- a/cmd/survey/tasks/createIssue.go +++ b/cmd/survey/tasks/createIssue.go @@ -17,18 +17,26 @@ This task will test your ability to use the issue creation workflow effectively. Assign this task to yourself and start creating the issue. Make sure to fill out all the necessary details, including the title, description, and assignee.` -func NewCreateIssueTask(svc *service.Services) *task.Task { - aboutScreen := ui.NewTaskModel(createIssueDetails()) - questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) +type CreateIssueTask struct { + *task.Task + svc *service.Services +} - task := task.NewTask(svc, aboutScreen, questionnaire) - task.SetConfigFunc(createIssueConfig) - task.SetDbStateFunc(createIssueDbState) - task.SetValidateFunc(createIssueValidate) +func NewCreateIssueTask(svc *service.Services) *CreateIssueTask { + return &CreateIssueTask{ + svc: svc, + } +} + +func (t *CreateIssueTask) Init() *task.Task { + task := task.NewTask(t.svc, t.Details(), t.QuestionsFunc()) + task.SetConfigFunc(t.Config) + task.SetDbStateFunc(t.DbStateFunc) + task.SetValidateFunc(t.ValidateFunc) return task } -func createIssueConfig() task.TaskConfig { +func (t *CreateIssueTask) Config() task.TaskConfig { return task.TaskConfig{ IssuePrefix: "pm", BeadsDBPath: "./.pm/db.db", @@ -37,7 +45,7 @@ func createIssueConfig() task.TaskConfig { } } -func createIssueDetails() ui.TaskDetails { +func (t *CreateIssueTask) Details() ui.TaskDetails { return ui.TaskDetails{ Title: "Create Issue Task", Description: "Create a new issue in the project management system to test the issue creation workflow.", @@ -46,23 +54,38 @@ func createIssueDetails() ui.TaskDetails { } } -func createIssueQuestionnaire() ui.Questions { - return ui.Questions{ - huh.NewGroup( - huh.NewConfirm().Title("Was this good"), - ), - huh.NewGroup( - huh.NewSelect[int]().Options( - huh.NewOption("Very good", 1), - huh.NewOption("Very Bad", 2), - ).Title("How good was it?"), - ), +func (t *CreateIssueTask) QuestionsFunc() task.QuestionsFunc { + return func(interfaceType task.InterfaceType) ui.Questions { + questions := ui.Questions{} + + // Add an extra question for TUI + if interfaceType == task.InterfaceTUI { + questions = append(questions, + huh.NewGroup( + huh.NewConfirm().Title("Did you complete?"), + ), + ) + } + + questions = append(questions, + huh.NewGroup( + huh.NewConfirm().Title("Was this good"), + ), + huh.NewGroup( + huh.NewSelect[int]().Options( + huh.NewOption("Very good", 1), + huh.NewOption("Very Bad", 2), + ).Title("How good was it?"), + ), + ) + + return questions } } -func createIssueDbState(ctx context.Context, svc *service.Services) error { +func (t *CreateIssueTask) DbStateFunc(ctx context.Context) error { // Clear existing issues to ensure a clean state for the task - if err := svc.DeleteIssues(); err != nil { + if err := t.svc.DeleteIssues(); err != nil { return err } @@ -72,16 +95,16 @@ func createIssueDbState(ctx context.Context, svc *service.Services) error { IssueType: models.TypeTask, Status: models.StatusOpen, } - if err := svc.Beads.CreateIssue(ctx, &issue, ""); err != nil { + if err := t.svc.Beads.CreateIssue(ctx, &issue, ""); err != nil { return err } return nil } -func createIssueValidate(ctx context.Context, svc *service.Services) (ok bool, errorMsg error) { +func (t *CreateIssueTask) ValidateFunc(ctx context.Context) (ok bool, errorMsg error) { // Fetches issues, indexed with latest first - issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + issues, err := t.svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) if err != nil { return false, err } diff --git a/cmd/survey/tasks/types.go b/cmd/survey/tasks/types.go new file mode 100644 index 0000000..c8c805b --- /dev/null +++ b/cmd/survey/tasks/types.go @@ -0,0 +1,27 @@ +package tasks + +import ( + "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" +) + +const ( + InterfaceTUI task.InterfaceType = "tui" + InterfaceCLI task.InterfaceType = "repl" + InterfaceWeb task.InterfaceType = "web" +) + +func InterfaceToType(it task.Interface) task.InterfaceType { + switch it.(type) { + case *repl.REPL: + return InterfaceCLI + case *tui.Tui: + return InterfaceTUI + case *web.Web: + return InterfaceWeb + default: + return "unknown" + } +} diff --git a/pkg/task/task.go b/pkg/task/task.go index d71198f..8a82d56 100644 --- a/pkg/task/task.go +++ b/pkg/task/task.go @@ -6,38 +6,57 @@ import ( "time" "github.com/LazyBachelor/LazyPM/internal/service" + taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui" tea "github.com/charmbracelet/bubbletea" ) +type Tasker interface { + Init() *Task + Config() TaskConfig + + Details() taskui.TaskDetails + QuestionsFunc() QuestionsFunc + + InterfaceType() Interface + + ValidateFunc(context.Context) (ok bool, errorMsg error) + DbStateFunc(context.Context) error +} + type Task struct { - Config TaskConfig - interfaceType Interface - aboutScreen tea.Model - questionnaire tea.Model + svc *service.Services + Config TaskConfig + + Interface Interface + InterfaceType InterfaceType + + details taskui.TaskDetails + questionsFunc QuestionsFunc validateFunc ValidateFunc dbStateFunc DbStateFunc - svc *service.Services - feedbackChan chan ValidationFeedback doneChan chan bool quitChan chan bool } -func NewTask(svc *service.Services, aboutScreen tea.Model, questionnaire tea.Model) *Task { +func NewTask(svc *service.Services, details taskui.TaskDetails, questionsFunc QuestionsFunc) *Task { return &Task{ - aboutScreen: aboutScreen, - questionnaire: questionnaire, + details: details, + questionsFunc: questionsFunc, svc: svc, } } func (t *Task) IntroduceTask() error { - if t.aboutScreen == nil { - return fmt.Errorf("aboutScreen is not set") + if t.details == (taskui.TaskDetails{}) { + return fmt.Errorf("details is not set") } - model, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() + + detailsScreen := taskui.NewTaskModel(t.details) + + model, err := tea.NewProgram(detailsScreen, tea.WithAltScreen()).Run() if err != nil { return err } @@ -48,32 +67,39 @@ func (t *Task) IntroduceTask() error { } func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error { - if t.interfaceType == nil { + if t.Interface == nil { return fmt.Errorf("interfaceType is not set") } - return t.interfaceType.Run(ctx, cfg) + return t.Interface.Run(ctx, cfg) } func (t *Task) Initialize(ctx context.Context) error { if t.dbStateFunc == nil { return fmt.Errorf("dbStateFunc is not set") } - return t.dbStateFunc(ctx, t.svc) + return t.dbStateFunc(ctx) } func (t *Task) Validate(ctx context.Context) (bool, error) { if t.validateFunc == nil { return false, fmt.Errorf("validateFunc is not set") } - return t.validateFunc(ctx, t.svc) + return t.validateFunc(ctx) } func (t *Task) StartQuestionnaire() error { - if t.questionnaire == nil { - return fmt.Errorf("questionnaire is not set") + if t.questionsFunc == nil { + return fmt.Errorf("questionsFunc is not set") } - model, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() + + questions := t.questionsFunc(t.InterfaceType) + if questions == nil { + return fmt.Errorf("questions is nil") + } + + questionare := taskui.NewQuestionnaireModel(questions) + model, err := tea.NewProgram(questionare, tea.WithAltScreen()).Run() if err != nil { return err } @@ -88,7 +114,11 @@ func (t *Task) SetConfigFunc(fn ConfigFunc) { } func (t *Task) SetInterface(interfaceType Interface) { - t.interfaceType = interfaceType + t.Interface = interfaceType +} + +func (t *Task) SetInterfaceType(interfaceType InterfaceType) { + t.InterfaceType = interfaceType } func (t *Task) SetDbStateFunc(fn DbStateFunc) { diff --git a/pkg/task/types.go b/pkg/task/types.go index d3de92e..a08051f 100644 --- a/pkg/task/types.go +++ b/pkg/task/types.go @@ -6,6 +6,7 @@ import ( "time" "github.com/LazyBachelor/LazyPM/internal/service" + taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui" ) var ErrUserQuit = errors.New("user quit") @@ -16,9 +17,18 @@ type Interface interface { Run(context.Context, TaskConfig) error } +type InterfaceType string + +const ( + InterfaceTUI InterfaceType = "tui" + InterfaceCLI InterfaceType = "repl" + InterfaceWeb InterfaceType = "web" +) + type ConfigFunc func() TaskConfig -type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) -type DbStateFunc func(context.Context, *service.Services) error +type ValidateFunc func(context.Context) (ok bool, err error) +type DbStateFunc func(context.Context) error +type QuestionsFunc func(InterfaceType) taskui.Questions type ValidationFeedback struct { Success bool