Files
LazyPM/internal/commands/issues/sprint.go
Robin Olsen 1cfd23f1b0 Enhance command structure and user feedback in task management
- Added SprintCmd to issues command group
- taskLoop hide survey related commands when in repl
- Updated coding task instructions for clarity
- Removed redundant SprintCmd registration
- Improved REPL command handling for better user experience avoiding a chash if users type pm
- Enhanced task UI to display additional information
2026-04-03 11:19:57 +02:00

321 lines
7.7 KiB
Go

package issues
import (
"fmt"
"strconv"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/spf13/cobra"
)
// sprintFlags holds the flag values for sprint commands
type sprintFlags struct {
sprintNum int
issueID string
}
var sprintCmdFlags sprintFlags
// SprintCmd represents the sprint management command
var SprintCmd = &cobra.Command{
Use: "sprint",
Short: "Manage sprints",
Long: `Manage sprints - create, list, add/remove issues, and view sprint contents.`,
}
// SprintListCmd lists all sprints
var SprintListCmd = &cobra.Command{
Use: "list",
Short: "List all sprints",
Long: `List all sprints in the project.`,
Aliases: []string{"ls"},
RunE: runSprintListCmd,
}
// SprintCreateCmd creates a new sprint
var SprintCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new sprint",
Long: `Create a new sprint for organizing issues.`,
Aliases: []string{"new"},
RunE: runSprintCreateCmd,
}
// SprintIssuesCmd lists issues in a sprint
var SprintIssuesCmd = &cobra.Command{
Use: "issues [sprint-num]",
Short: "List issues in a sprint",
Long: `List all issues assigned to a specific sprint. Use 'backlog' or omit to view the backlog.`,
Aliases: []string{"show", "view"},
Args: cobra.MaximumNArgs(1),
RunE: runSprintIssuesCmd,
}
// SprintAddCmd adds an issue to a sprint
var SprintAddCmd = &cobra.Command{
Use: "add [issue-id] [sprint-num]",
Short: "Add an issue to a sprint",
Long: `Add an issue to a sprint. If sprint-num is omitted, adds to backlog.`,
Args: cobra.RangeArgs(1, 2),
RunE: runSprintAddCmd,
ValidArgsFunction: completeIssues,
}
// SprintRemoveCmd removes an issue from a sprint
var SprintRemoveCmd = &cobra.Command{
Use: "remove [issue-id] [sprint-num]",
Short: "Remove an issue from a sprint",
Long: `Remove an issue from a sprint. If sprint-num is omitted, removes from backlog.`,
Aliases: []string{"rm"},
Args: cobra.RangeArgs(1, 2),
RunE: runSprintRemoveCmd,
ValidArgsFunction: completeIssues,
}
// SprintBacklogCmd shows the backlog sprint
var SprintBacklogCmd = &cobra.Command{
Use: "backlog",
Short: "Show backlog issues",
Long: `Show all issues in the backlog sprint.`,
RunE: runSprintBacklogCmd,
}
// SprintDeleteCmd deletes a sprint
var SprintDeleteCmd = &cobra.Command{
Use: "delete [sprint-num]",
Short: "Delete a sprint",
Long: `Delete a sprint by its number. Issues in the sprint will not be deleted.`,
Aliases: []string{"del"},
Args: cobra.MaximumNArgs(1),
RunE: runSprintDeleteCmd,
}
func runSprintListCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
sprints, err := app.Issues.GetSprints(cmd.Context())
if err != nil {
return fmt.Errorf("failed to get sprints: %w", err)
}
if len(sprints) == 0 {
cmd.Println("No sprints found.")
return nil
}
backlogNum, _ := app.Issues.GetBacklogSprint(cmd.Context())
cmd.Println("Sprints:")
for _, sprintNum := range sprints {
label := ""
if sprintNum == backlogNum {
label = " (backlog)"
}
cmd.Printf(" Sprint %d%s\n", sprintNum, label)
}
return nil
}
func runSprintCreateCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
sprintNum, err := app.Issues.AddSprint(cmd.Context())
if err != nil {
return fmt.Errorf("failed to create sprint: %w", err)
}
cmd.Printf("Created sprint %d\n", sprintNum)
return nil
}
func runSprintIssuesCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
ctx := cmd.Context()
var sprintNum int
var isBacklog bool
var err error
if len(args) == 0 || args[0] == "backlog" {
sprintNum, err = app.Issues.GetBacklogSprint(ctx)
if err != nil {
return fmt.Errorf("failed to get backlog sprint: %w", err)
}
isBacklog = true
} else {
sprintNum, err = strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid sprint number: %s", args[0])
}
isBacklog = false
}
var issues []*models.Issue
if isBacklog {
issues, err = app.Issues.GetIssuesNotInAnySprint(ctx)
} else {
issues, err = app.Issues.GetIssuesBySprint(ctx, sprintNum)
}
if err != nil {
return fmt.Errorf("failed to get issues in sprint %d: %w", sprintNum, err)
}
if isBacklog {
cmd.Printf("Backlog (%d issues):\n", len(issues))
} else {
cmd.Printf("Sprint %d (%d issues):\n", sprintNum, len(issues))
}
if len(issues) == 0 {
cmd.Println(" No issues in this sprint.")
return nil
}
issuesList := models.IssuesPtrToIssues(issues)
models.PrintIssues(issuesList)
return nil
}
func runSprintAddCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
ctx := cmd.Context()
issueID := args[0]
var sprintNum int
var err error
if len(args) == 1 {
sprintNum, err = app.Issues.GetBacklogSprint(ctx)
if err != nil {
return fmt.Errorf("failed to get backlog sprint: %w", err)
}
} else {
sprintNum, err = strconv.Atoi(args[1])
if err != nil {
return fmt.Errorf("invalid sprint number: %s", args[1])
}
}
err = app.Issues.AddIssueToSprint(ctx, issueID, sprintNum)
if err != nil {
return fmt.Errorf("failed to add issue to sprint: %w", err)
}
backlogNum, _ := app.Issues.GetBacklogSprint(ctx)
if sprintNum == backlogNum {
cmd.Printf("Added issue %s to backlog\n", issueID)
} else {
cmd.Printf("Added issue %s to sprint %d\n", issueID, sprintNum)
}
return nil
}
func runSprintRemoveCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
ctx := cmd.Context()
issueID := args[0]
var sprintNum int
var err error
if len(args) == 1 {
sprintNum, err = app.Issues.GetBacklogSprint(ctx)
if err != nil {
return fmt.Errorf("failed to get backlog sprint: %w", err)
}
} else {
sprintNum, err = strconv.Atoi(args[1])
if err != nil {
return fmt.Errorf("invalid sprint number: %s", args[1])
}
}
err = app.Issues.RemoveIssueFromSprint(ctx, issueID, sprintNum)
if err != nil {
return fmt.Errorf("failed to remove issue from sprint: %w", err)
}
backlogNum, _ := app.Issues.GetBacklogSprint(ctx)
if sprintNum == backlogNum {
cmd.Printf("Removed issue %s from backlog\n", issueID)
} else {
cmd.Printf("Removed issue %s from sprint %d\n", issueID, sprintNum)
}
return nil
}
func runSprintBacklogCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
ctx := cmd.Context()
issues, err := app.Issues.GetIssuesNotInAnySprint(ctx)
if err != nil {
return fmt.Errorf("failed to get backlog issues: %w", err)
}
cmd.Printf("Backlog (%d issues):\n", len(issues))
if len(issues) == 0 {
cmd.Println(" No issues in backlog.")
return nil
}
issuesList := models.IssuesPtrToIssues(issues)
models.PrintIssues(issuesList)
return nil
}
func runSprintDeleteCmd(cmd *cobra.Command, args []string) error {
app := AppFromContext(cmd.Context())
ctx := cmd.Context()
var sprintNum int
var err error
if len(args) == 0 {
sprintNum, err = app.Issues.GetBacklogSprint(ctx)
if err != nil {
return fmt.Errorf("failed to get backlog sprint: %w", err)
}
} else {
sprintNum, err = strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid sprint number: %s", args[0])
}
}
backlogNum, _ := app.Issues.GetBacklogSprint(ctx)
if sprintNum == backlogNum {
return fmt.Errorf("cannot delete the backlog sprint")
}
err = app.Issues.RemoveSprint(ctx, sprintNum)
if err != nil {
return fmt.Errorf("failed to delete sprint: %w", err)
}
cmd.Printf("Deleted sprint %d\n", sprintNum)
return nil
}
func init() {
SprintCmd.AddCommand(SprintListCmd)
SprintCmd.AddCommand(SprintBacklogCmd)
SprintCmd.AddCommand(SprintCreateCmd)
SprintCmd.AddCommand(SprintIssuesCmd)
SprintCmd.AddCommand(SprintAddCmd)
SprintCmd.AddCommand(SprintRemoveCmd)
SprintCmd.AddCommand(SprintDeleteCmd)
}