274 lines
6.2 KiB
Go
274 lines
6.2 KiB
Go
package move
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"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 = `Move transitions an issue from one state to another.`
|
|
examples = `$ jira issue move ISSUE-1 "In Progress"
|
|
$ jira issue move ISSUE-1 Done`
|
|
|
|
optionCancel = "Cancel"
|
|
)
|
|
|
|
// NewCmdMove is a move command.
|
|
func NewCmdMove() *cobra.Command {
|
|
cmd := cobra.Command{
|
|
Use: "move ISSUE-KEY STATE",
|
|
Short: "Transition an issue to a given state",
|
|
Long: helpText,
|
|
Example: examples,
|
|
Aliases: []string{"transition", "mv"},
|
|
Annotations: map[string]string{
|
|
"help:args": `ISSUE-KEY Issue key, eg: ISSUE-1
|
|
STATE State you want to transition the issue to`,
|
|
},
|
|
Run: move,
|
|
}
|
|
|
|
cmd.Flags().SortFlags = false
|
|
|
|
cmd.Flags().String("comment", "", "Add comment to the issue")
|
|
cmd.Flags().StringP("assignee", "a", "", "Assign issue to a user")
|
|
cmd.Flags().StringP("resolution", "R", "", "Set resolution")
|
|
cmd.Flags().Bool("web", false, "Open issue in web browser after successful transition")
|
|
|
|
return &cmd
|
|
}
|
|
|
|
func move(cmd *cobra.Command, args []string) {
|
|
project := viper.GetString("project.key")
|
|
installation := viper.GetString("installation")
|
|
params := parseArgsAndFlags(cmd.Flags(), args, project)
|
|
client := api.DefaultClient(params.debug)
|
|
mc := moveCmd{
|
|
client: client,
|
|
transitions: nil,
|
|
params: params,
|
|
}
|
|
|
|
cmdutil.ExitIfError(mc.setIssueKey(project))
|
|
cmdutil.ExitIfError(mc.setAvailableTransitions())
|
|
cmdutil.ExitIfError(mc.setDesiredState(installation))
|
|
|
|
if mc.params.state == optionCancel {
|
|
cmdutil.Fail("Action aborted")
|
|
os.Exit(0)
|
|
}
|
|
|
|
tr, err := mc.verifyTransition(installation)
|
|
if err != nil {
|
|
fmt.Println()
|
|
cmdutil.Failed("Error: %s", err.Error())
|
|
return
|
|
}
|
|
|
|
err = func() error {
|
|
s := cmdutil.Info(fmt.Sprintf("Transitioning issue to %q...", tr.Name))
|
|
defer s.Stop()
|
|
|
|
trFieldsReq := jira.TransitionRequestFields{}
|
|
trUpdateReq := jira.TransitionRequestUpdate{}
|
|
|
|
if mc.params.assignee != "" {
|
|
trFieldsReq.Assignee = &struct {
|
|
Name string `json:"name"`
|
|
}{Name: mc.params.assignee}
|
|
}
|
|
if mc.params.resolution != "" {
|
|
trFieldsReq.Resolution = &struct {
|
|
Name string `json:"name"`
|
|
}{Name: mc.params.resolution}
|
|
}
|
|
if mc.params.comment != "" {
|
|
trUpdateReq.Comment = []struct {
|
|
Add struct {
|
|
Body string `json:"body"`
|
|
} `json:"add"`
|
|
}{
|
|
{Add: struct {
|
|
Body string `json:"body"`
|
|
}{Body: mc.params.comment}},
|
|
}
|
|
}
|
|
|
|
_, err := client.Transition(mc.params.key, &jira.TransitionRequest{
|
|
Fields: &trFieldsReq,
|
|
Update: &trUpdateReq,
|
|
Transition: &jira.TransitionRequestData{
|
|
ID: tr.ID.String(),
|
|
Name: tr.Name,
|
|
},
|
|
})
|
|
return err
|
|
}()
|
|
cmdutil.ExitIfError(err)
|
|
|
|
server := viper.GetString("server")
|
|
|
|
cmdutil.Success("Issue transitioned to state %q", tr.Name)
|
|
fmt.Printf("%s\n", cmdutil.GenerateServerBrowseURL(server, mc.params.key))
|
|
|
|
if web, _ := cmd.Flags().GetBool("web"); web {
|
|
err := cmdutil.Navigate(server, mc.params.key)
|
|
cmdutil.ExitIfError(err)
|
|
}
|
|
}
|
|
|
|
type moveParams struct {
|
|
key string
|
|
state string
|
|
comment string
|
|
assignee string
|
|
resolution string
|
|
debug bool
|
|
}
|
|
|
|
func parseArgsAndFlags(flags query.FlagParser, args []string, project string) *moveParams {
|
|
var key, state string
|
|
|
|
nargs := len(args)
|
|
if nargs >= 1 {
|
|
key = cmdutil.GetJiraIssueKey(project, args[0])
|
|
}
|
|
if nargs >= 2 {
|
|
state = args[1]
|
|
}
|
|
|
|
comment, err := flags.GetString("comment")
|
|
cmdutil.ExitIfError(err)
|
|
|
|
assignee, err := flags.GetString("assignee")
|
|
cmdutil.ExitIfError(err)
|
|
|
|
resolution, err := flags.GetString("resolution")
|
|
cmdutil.ExitIfError(err)
|
|
|
|
debug, err := flags.GetBool("debug")
|
|
cmdutil.ExitIfError(err)
|
|
|
|
return &moveParams{
|
|
key: key,
|
|
state: state,
|
|
comment: comment,
|
|
assignee: assignee,
|
|
resolution: resolution,
|
|
debug: debug,
|
|
}
|
|
}
|
|
|
|
type moveCmd struct {
|
|
client *jira.Client
|
|
transitions []*jira.Transition
|
|
params *moveParams
|
|
}
|
|
|
|
func (mc *moveCmd) setIssueKey(project string) error {
|
|
if mc.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
|
|
}
|
|
mc.params.key = cmdutil.GetJiraIssueKey(project, ans)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mc *moveCmd) setDesiredState(it string) error {
|
|
if mc.params.state != "" {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
options = make([]string, 0, len(mc.transitions))
|
|
ans string
|
|
)
|
|
|
|
for _, t := range mc.transitions {
|
|
if it == jira.InstallationTypeCloud && !t.IsAvailable {
|
|
continue
|
|
}
|
|
options = append(options, t.Name)
|
|
}
|
|
options = append(options, optionCancel)
|
|
|
|
qs := &survey.Question{
|
|
Name: "state",
|
|
Prompt: &survey.Select{
|
|
Message: "Desired state:",
|
|
Options: options,
|
|
},
|
|
Validate: survey.Required,
|
|
}
|
|
if err := survey.Ask([]*survey.Question{qs}, &ans); err != nil {
|
|
return err
|
|
}
|
|
mc.params.state = ans
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mc *moveCmd) setAvailableTransitions() error {
|
|
s := cmdutil.Info("Fetching available transitions. Please wait...")
|
|
defer s.Stop()
|
|
|
|
t, err := api.ProxyTransitions(mc.client, mc.params.key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mc.transitions = t
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mc *moveCmd) verifyTransition(it string) (*jira.Transition, error) {
|
|
var tr *jira.Transition
|
|
|
|
st := strings.ToLower(mc.params.state)
|
|
all := make([]string, 0, len(mc.transitions))
|
|
for _, t := range mc.transitions {
|
|
if strings.ToLower(t.Name) == st {
|
|
tr = t
|
|
}
|
|
all = append(all, fmt.Sprintf("'%s'", t.Name))
|
|
}
|
|
|
|
if tr == nil {
|
|
return nil, fmt.Errorf(
|
|
"invalid transition state %q\nAvailable states for issue %s: %s",
|
|
mc.params.state, mc.params.key, strings.Join(all, ", "),
|
|
)
|
|
}
|
|
|
|
// Jira API v2 doesn't seem to return "isAvailable" field even if the documentation says it does.
|
|
// So, we will only verify if the transition is available for the cloud installation.
|
|
if it == jira.InstallationTypeCloud && !tr.IsAvailable {
|
|
return nil, fmt.Errorf(
|
|
"transition state %q for issue %q is not available",
|
|
mc.params.state, mc.params.key,
|
|
)
|
|
}
|
|
return tr, nil
|
|
}
|