347 lines
7.7 KiB
Go
347 lines
7.7 KiB
Go
package assign
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/AlecAivazis/survey/v2/core"
|
|
"github.com/briandowns/spinner"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/ankitpokhrel/jira-cli/api"
|
|
"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
|
|
"github.com/ankitpokhrel/jira-cli/internal/query"
|
|
"github.com/ankitpokhrel/jira-cli/pkg/jira"
|
|
)
|
|
|
|
const (
|
|
helpText = `Assign issue to a user.`
|
|
examples = `$ jira issue assign ISSUE-1 jon@domain.tld
|
|
|
|
# Assignee name or email needs to be an exact match
|
|
$ jira issue assign ISSUE-1 "Jon Doe"
|
|
|
|
# Assign to self
|
|
$ jira issue assign ISSUE-1 $(jira me)
|
|
|
|
# Assign to default assignee
|
|
$ jira issue assign ISSUE-1 default
|
|
|
|
# Unassign
|
|
$ jira issue assign ISSUE-1 x`
|
|
|
|
maxResults = 100
|
|
lineBreak = "----------"
|
|
|
|
optionSearch = "[Search...]"
|
|
optionDefault = "Default"
|
|
optionNone = "No-one (Unassign)"
|
|
optionCancel = "Cancel"
|
|
)
|
|
|
|
// NewCmdAssign is an assign command.
|
|
func NewCmdAssign() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "assign ISSUE-KEY ASSIGNEE",
|
|
Short: "Assign issue to a user",
|
|
Long: helpText,
|
|
Example: examples,
|
|
Aliases: []string{"asg"},
|
|
Annotations: map[string]string{
|
|
"help:args": `ISSUE-KEY Issue key, eg: ISSUE-1
|
|
ASSIGNEE Email or display name of the user to assign the issue to`,
|
|
},
|
|
Run: assign,
|
|
}
|
|
}
|
|
|
|
func assign(cmd *cobra.Command, args []string) {
|
|
project := viper.GetString("project.key")
|
|
params := parseArgsAndFlags(cmd.Flags(), args, project)
|
|
client := api.DefaultClient(params.debug)
|
|
ac := assignCmd{
|
|
client: client,
|
|
users: nil,
|
|
params: params,
|
|
}
|
|
lu := strings.ToLower(ac.params.user)
|
|
|
|
cmdutil.ExitIfError(ac.setIssueKey(project))
|
|
|
|
if lu != strings.ToLower(optionNone) && lu != "x" && lu != jira.AssigneeDefault {
|
|
cmdutil.ExitIfError(ac.setAvailableUsers(project))
|
|
cmdutil.ExitIfError(ac.setAssignee(project))
|
|
|
|
lu = strings.ToLower(ac.params.user)
|
|
}
|
|
|
|
u, err := ac.verifyAssignee()
|
|
if err != nil {
|
|
cmdutil.Failed("Error: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
var assignee, uname string
|
|
|
|
switch {
|
|
case u != nil:
|
|
uname = getQueryableName(u.DisplayName, u.Name)
|
|
case lu == strings.ToLower(optionNone) || lu == "x":
|
|
assignee = jira.AssigneeNone
|
|
uname = "unassigned"
|
|
case lu == strings.ToLower(optionDefault):
|
|
assignee = jira.AssigneeDefault
|
|
uname = assignee
|
|
}
|
|
|
|
err = func() error {
|
|
var s *spinner.Spinner
|
|
if uname == "unassigned" {
|
|
s = cmdutil.Info(fmt.Sprintf("Unassigning user from issue %q...", ac.params.key))
|
|
} else {
|
|
s = cmdutil.Info(fmt.Sprintf("Assigning issue %q to user %q...", ac.params.key, uname))
|
|
}
|
|
defer s.Stop()
|
|
|
|
return api.ProxyAssignIssue(client, ac.params.key, u, assignee)
|
|
}()
|
|
cmdutil.ExitIfError(err)
|
|
|
|
if uname == "unassigned" {
|
|
cmdutil.Success("User unassigned from the issue %q", ac.params.key)
|
|
} else {
|
|
cmdutil.Success("User %q assigned to issue %q", uname, ac.params.key)
|
|
}
|
|
fmt.Printf("%s\n", cmdutil.GenerateServerBrowseURL(viper.GetString("server"), ac.params.key))
|
|
}
|
|
|
|
type assignParams struct {
|
|
key string
|
|
user string
|
|
debug bool
|
|
}
|
|
|
|
func parseArgsAndFlags(flags query.FlagParser, args []string, project string) *assignParams {
|
|
var key, user string
|
|
|
|
nargs := len(args)
|
|
if nargs >= 1 {
|
|
key = cmdutil.GetJiraIssueKey(project, args[0])
|
|
}
|
|
if nargs >= 2 {
|
|
user = args[1]
|
|
}
|
|
|
|
debug, err := flags.GetBool("debug")
|
|
cmdutil.ExitIfError(err)
|
|
|
|
return &assignParams{
|
|
key: key,
|
|
user: user,
|
|
debug: debug,
|
|
}
|
|
}
|
|
|
|
type assignCmd struct {
|
|
client *jira.Client
|
|
users []*jira.User
|
|
params *assignParams
|
|
}
|
|
|
|
func (ac *assignCmd) setIssueKey(project string) error {
|
|
if ac.params.key != "" {
|
|
return nil
|
|
}
|
|
|
|
var ans string
|
|
|
|
qs := &survey.Question{
|
|
Name: "key",
|
|
Prompt: &survey.Input{Message: "Issue key"},
|
|
Validate: survey.Required,
|
|
}
|
|
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
|
|
return err
|
|
}
|
|
ac.params.key = cmdutil.GetJiraIssueKey(project, ans)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ac *assignCmd) setAssignee(project string) error {
|
|
if ac.params.user != "" && len(ac.users) == 1 {
|
|
ac.params.user = getQueryableName(ac.users[0].Name, ac.users[0].DisplayName)
|
|
return nil
|
|
}
|
|
|
|
lu := strings.ToLower(ac.params.user)
|
|
if lu == strings.ToLower(optionNone) || lu == strings.ToLower(optionDefault) || lu == "x" {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
ans string
|
|
last bool
|
|
)
|
|
if ac.params.user != "" && len(ac.users) > 0 {
|
|
last = true
|
|
}
|
|
|
|
for {
|
|
qs := &survey.Question{
|
|
Name: "user",
|
|
Prompt: &survey.Select{
|
|
Message: "Assign to user:",
|
|
Help: "Can't find the user? Select search and look for a keyword or cancel to abort",
|
|
Options: ac.getOptions(last),
|
|
},
|
|
Validate: func(val interface{}) error {
|
|
errInvalidSelection := fmt.Errorf("invalid selection")
|
|
|
|
ans, ok := val.(core.OptionAnswer)
|
|
if !ok {
|
|
return errInvalidSelection
|
|
}
|
|
if ans.Value == "" || ans.Value == lineBreak {
|
|
return errInvalidSelection
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
|
|
return err
|
|
}
|
|
if ans == optionCancel {
|
|
cmdutil.Fail("Action aborted")
|
|
os.Exit(0)
|
|
}
|
|
if ans != optionSearch {
|
|
break
|
|
}
|
|
if err := ac.getSearchKeyword(); err != nil {
|
|
return err
|
|
}
|
|
if err := ac.searchAndAssignUser(project); err != nil {
|
|
return err
|
|
}
|
|
last = true
|
|
}
|
|
ac.params.user = ans
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ac *assignCmd) getOptions(last bool) []string {
|
|
var validUsers []string
|
|
|
|
for _, t := range ac.users {
|
|
if t.Active {
|
|
name := t.DisplayName
|
|
if t.Name != "" {
|
|
name += fmt.Sprintf(" (%s)", t.Name)
|
|
}
|
|
validUsers = append(validUsers, name)
|
|
}
|
|
}
|
|
always := []string{optionDefault, optionNone, optionCancel}
|
|
options := []string{optionSearch}
|
|
|
|
if last {
|
|
options = append(options, validUsers...)
|
|
options = append(options, lineBreak)
|
|
options = append(options, always...)
|
|
} else {
|
|
options = append(options, always...)
|
|
options = append(options, lineBreak)
|
|
options = append(options, validUsers...)
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
func (ac *assignCmd) getSearchKeyword() error {
|
|
qs := &survey.Question{
|
|
Name: "user",
|
|
Prompt: &survey.Input{
|
|
Message: "Search user:",
|
|
Help: "Type user email or display name to search for a user",
|
|
},
|
|
Validate: func(val interface{}) error {
|
|
errInvalidKeyword := fmt.Errorf("enter atleast 3 characters to search")
|
|
|
|
str, ok := val.(string)
|
|
if !ok {
|
|
return errInvalidKeyword
|
|
}
|
|
if len(str) < 3 {
|
|
return errInvalidKeyword
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
return survey.Ask([]*survey.Question{qs}, &ac.params.user)
|
|
}
|
|
|
|
func (ac *assignCmd) searchAndAssignUser(project string) error {
|
|
u, err := api.ProxyUserSearch(ac.client, &jira.UserSearchOptions{
|
|
Query: ac.params.user,
|
|
Project: project,
|
|
MaxResults: maxResults,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ac.users = u
|
|
return nil
|
|
}
|
|
|
|
func (ac *assignCmd) setAvailableUsers(project string) error {
|
|
s := cmdutil.Info("Fetching available users. Please wait...")
|
|
defer s.Stop()
|
|
|
|
return ac.searchAndAssignUser(project)
|
|
}
|
|
|
|
func (ac *assignCmd) verifyAssignee() (*jira.User, error) {
|
|
assignee := strings.ToLower(ac.params.user)
|
|
if assignee == strings.ToLower(optionDefault) || assignee == strings.ToLower(optionNone) || assignee == "x" {
|
|
return nil, nil
|
|
}
|
|
|
|
var user *jira.User
|
|
|
|
for _, u := range ac.users {
|
|
name := strings.ToLower(getQueryableName(u.Name, u.DisplayName))
|
|
if name == assignee || strings.ToLower(u.Email) == assignee {
|
|
user = u
|
|
}
|
|
if strings.ToLower(fmt.Sprintf("%s (%s)", u.DisplayName, u.Name)) == assignee {
|
|
user = u
|
|
}
|
|
if user != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if user == nil {
|
|
return nil, fmt.Errorf("invalid assignee %q", ac.params.user)
|
|
}
|
|
if !user.Active {
|
|
return nil, fmt.Errorf("user %q is not active", getQueryableName(user.Name, user.DisplayName))
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func getQueryableName(name, displayName string) string {
|
|
if name != "" {
|
|
return name
|
|
}
|
|
return displayName
|
|
}
|