package main import ( "context" "errors" "fmt" jira "github.com/andygrunwald/go-jira/v2/cloud" "github.com/charmbracelet/huh/spinner" ) var errorAborted = errors.New("aborted by user") func CreateSpinner(title string) *spinner.Spinner { return spinner.New(). Title(" " + title). Type(spinner.Globe) } func RunSpinnerRaw(title string, callback func(ctx context.Context) error) error { var err error finished := false ctx, cancel := context.WithCancel(context.Background()) err2 := CreateSpinner(title). Action(func() { err = callback(ctx) finished = true }).Run() if err2 != nil { return err2 } if !finished { cancel() return errorAborted } return err } func RunSpinner[T any](title string, callback func(ctx context.Context) (*T, any, error)) (*T, error) { var result *T err := RunSpinnerRaw(title, func(ctx context.Context) error { var err error result, _, err = callback(ctx) return err }) return result, err } func GetAllStatuses(projectId string) (*jira.IssueTypesWithStatus, error) { return RunSpinner("Fetching statuses...", func(ctx context.Context) (*jira.IssueTypesWithStatus, any, error) { return jiraClient.Project.GetAllStatuses(ctx, projectId) }) } func GetUserData() (*jira.User, error) { return RunSpinner("Fetching user info...", func(ctx context.Context) (*jira.User, any, error) { return jiraClient.User.GetCurrentUser(ctx) }) } func GetAllProjects() (*jira.ProjectList, error) { return RunSpinner("Fetching all projects...", func(ctx context.Context) (*jira.ProjectList, any, error) { return jiraClient.Project.GetAll(ctx, nil) }) } func GetAllBoardsForProject(projectIdOrKey string) (*jira.BoardsList, error) { return RunSpinner(fmt.Sprintf("Fetching boards for project '%s'...", projectIdOrKey), func(ctx context.Context) (*jira.BoardsList, any, error) { return jiraClient.Board.GetAllBoards(ctx, &jira.BoardListOptions{ProjectKeyOrID: projectIdOrKey}) }) } func GetBoardConfiguration(boardId int) (*jira.BoardConfiguration, error) { return RunSpinner("Fetching board...", func(ctx context.Context) (*jira.BoardConfiguration, any, error) { return jiraClient.Board.GetBoardConfiguration(ctx, boardId) }) } func GetCurrentSprint(boardId int) (*jira.Sprint, error) { list, err := RunSpinner("Fetching sprint...", func(ctx context.Context) (*jira.SprintsList, any, error) { return jiraClient.Board.GetAllSprints(ctx, int64(boardId), &jira.GetAllSprintsOptions{ State: "active", }) }) if err != nil { return nil, err } if len(list.Values) == 0 { return nil, errors.New("No active sprint found") } if len(list.Values) > 1 { return nil, errors.New("Multiple active sprints found") } return &list.Values[0], nil } func GetIssuesForSprint(sprintId int) ([]jira.Issue, error) { issues, err := RunSpinner("Fetching issues...", func(ctx context.Context) (*[]jira.Issue, any, error) { list, t, err := jiraClient.Sprint.GetIssuesForSprint(ctx, sprintId) return &list, t, err }) if issues == nil { return nil, err } else { return *issues, err } } func GetIssue(issueKey string) (*jira.Issue, error) { return RunSpinner("Fetching issue...", func(ctx context.Context) (*jira.Issue, any, error) { return jiraClient.Issue.Get(ctx, issueKey, nil) }) } func UpdateIssueTitle(issue string, title string) error { return RunSpinnerRaw("Updating issue title...", func(ctx context.Context) error { resp, err := jiraClient.Issue.UpdateIssue(ctx, issue, map[string]any{ "fields": map[string]any{ "summary": title, }, }) if err != nil { return err } return resp.Body.Close() }) } func GetIssueTransitions(issue string) ([]jira.Transition, error) { r, err := RunSpinner("Getting issue transitions...", func(ctx context.Context) (*[]jira.Transition, any, error) { transitions, response, err := jiraClient.Issue.GetTransitions(ctx, issue) return &transitions, response, err }) if r == nil { return nil, err } else { return *r, err } } func UpdateIssueTransition(issue string, transition string) error { return RunSpinnerRaw("Transitioning issue...", func(ctx context.Context) error { resp, err := jiraClient.Issue.DoTransition(ctx, issue, transition) if err != nil { return err } return resp.Body.Close() }) }