refactor for better reusability and maintainability

This commit is contained in:
Robin Olsen
2026-02-17 15:17:49 +01:00
parent e2f3aa63da
commit 86c8bc3143
6 changed files with 140 additions and 48 deletions

View File

@@ -23,7 +23,7 @@ func initializeServices(ctx context.Context) (*service.Services, func(), error)
func initTasks(svc *service.Services) []*task.Task { func initTasks(svc *service.Services) []*task.Task {
return []*task.Task{ return []*task.Task{
tasks.NewCreateIssueTask(svc), tasks.NewCreateIssueTask(svc).Init(),
} }
} }

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"github.com/LazyBachelor/LazyPM/cmd/survey/tasks"
"github.com/LazyBachelor/LazyPM/pkg/task" "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 { for _, t := range surveyTasks {
t.SetInterface(interfaces[interfaceIndex]) t.SetInterface(interfaces[interfaceIndex])
t.SetInterfaceType(tasks.InterfaceToType(interfaces[interfaceIndex]))
doneChan := make(chan bool, 1) doneChan := make(chan bool, 1)
quitChan := make(chan bool, 1) quitChan := make(chan bool, 1)

View File

@@ -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. 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.` Make sure to fill out all the necessary details, including the title, description, and assignee.`
func NewCreateIssueTask(svc *service.Services) *task.Task { type CreateIssueTask struct {
aboutScreen := ui.NewTaskModel(createIssueDetails()) *task.Task
questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) svc *service.Services
}
task := task.NewTask(svc, aboutScreen, questionnaire) func NewCreateIssueTask(svc *service.Services) *CreateIssueTask {
task.SetConfigFunc(createIssueConfig) return &CreateIssueTask{
task.SetDbStateFunc(createIssueDbState) svc: svc,
task.SetValidateFunc(createIssueValidate) }
}
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 return task
} }
func createIssueConfig() task.TaskConfig { func (t *CreateIssueTask) Config() task.TaskConfig {
return task.TaskConfig{ return task.TaskConfig{
IssuePrefix: "pm", IssuePrefix: "pm",
BeadsDBPath: "./.pm/db.db", 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{ return ui.TaskDetails{
Title: "Create Issue Task", Title: "Create Issue Task",
Description: "Create a new issue in the project management system to test the issue creation workflow.", 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 { func (t *CreateIssueTask) QuestionsFunc() task.QuestionsFunc {
return ui.Questions{ return func(interfaceType task.InterfaceType) ui.Questions {
huh.NewGroup( questions := ui.Questions{}
huh.NewConfirm().Title("Was this good"),
), // Add an extra question for TUI
huh.NewGroup( if interfaceType == task.InterfaceTUI {
huh.NewSelect[int]().Options( questions = append(questions,
huh.NewOption("Very good", 1), huh.NewGroup(
huh.NewOption("Very Bad", 2), huh.NewConfirm().Title("Did you complete?"),
).Title("How good was it?"), ),
), )
}
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 // 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 return err
} }
@@ -72,16 +95,16 @@ func createIssueDbState(ctx context.Context, svc *service.Services) error {
IssueType: models.TypeTask, Status: models.StatusOpen, 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 err
} }
return nil 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 // 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 { if err != nil {
return false, err return false, err
} }

27
cmd/survey/tasks/types.go Normal file
View File

@@ -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"
}
}

View File

@@ -6,38 +6,57 @@ import (
"time" "time"
"github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/internal/service"
taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui"
tea "github.com/charmbracelet/bubbletea" 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 { type Task struct {
Config TaskConfig svc *service.Services
interfaceType Interface Config TaskConfig
aboutScreen tea.Model
questionnaire tea.Model Interface Interface
InterfaceType InterfaceType
details taskui.TaskDetails
questionsFunc QuestionsFunc
validateFunc ValidateFunc validateFunc ValidateFunc
dbStateFunc DbStateFunc dbStateFunc DbStateFunc
svc *service.Services
feedbackChan chan ValidationFeedback feedbackChan chan ValidationFeedback
doneChan chan bool doneChan chan bool
quitChan 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{ return &Task{
aboutScreen: aboutScreen, details: details,
questionnaire: questionnaire, questionsFunc: questionsFunc,
svc: svc, svc: svc,
} }
} }
func (t *Task) IntroduceTask() error { func (t *Task) IntroduceTask() error {
if t.aboutScreen == nil { if t.details == (taskui.TaskDetails{}) {
return fmt.Errorf("aboutScreen is not set") 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 { if err != nil {
return err return err
} }
@@ -48,32 +67,39 @@ func (t *Task) IntroduceTask() error {
} }
func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) 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 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 { func (t *Task) Initialize(ctx context.Context) error {
if t.dbStateFunc == nil { if t.dbStateFunc == nil {
return fmt.Errorf("dbStateFunc is not set") 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) { func (t *Task) Validate(ctx context.Context) (bool, error) {
if t.validateFunc == nil { if t.validateFunc == nil {
return false, fmt.Errorf("validateFunc is not set") return false, fmt.Errorf("validateFunc is not set")
} }
return t.validateFunc(ctx, t.svc) return t.validateFunc(ctx)
} }
func (t *Task) StartQuestionnaire() error { func (t *Task) StartQuestionnaire() error {
if t.questionnaire == nil { if t.questionsFunc == nil {
return fmt.Errorf("questionnaire is not set") 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 { if err != nil {
return err return err
} }
@@ -88,7 +114,11 @@ func (t *Task) SetConfigFunc(fn ConfigFunc) {
} }
func (t *Task) SetInterface(interfaceType Interface) { 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) { func (t *Task) SetDbStateFunc(fn DbStateFunc) {

View File

@@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/internal/service"
taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui"
) )
var ErrUserQuit = errors.New("user quit") var ErrUserQuit = errors.New("user quit")
@@ -16,9 +17,18 @@ type Interface interface {
Run(context.Context, TaskConfig) error Run(context.Context, TaskConfig) error
} }
type InterfaceType string
const (
InterfaceTUI InterfaceType = "tui"
InterfaceCLI InterfaceType = "repl"
InterfaceWeb InterfaceType = "web"
)
type ConfigFunc func() TaskConfig type ConfigFunc func() TaskConfig
type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) type ValidateFunc func(context.Context) (ok bool, err error)
type DbStateFunc func(context.Context, *service.Services) error type DbStateFunc func(context.Context) error
type QuestionsFunc func(InterfaceType) taskui.Questions
type ValidationFeedback struct { type ValidationFeedback struct {
Success bool Success bool