diff --git a/cmd/jira/main.go b/cmd/jira/main.go index 92f29ac3..98e6f7f6 100644 --- a/cmd/jira/main.go +++ b/cmd/jira/main.go @@ -191,6 +191,10 @@ func main() { Command: "vote", Entry: cli.CmdVoteRegistry(), }, + jiracli.CommandRegistry{ + Command: "rank", + Entry: cli.CmdRankRegistry(), + }, } cli.Register(app, registry) @@ -229,7 +233,6 @@ func main() { // } // output := fmt.Sprintf(` // Usage: - // jira rank ISSUE (after|before) ISSUE // jira watch ISSUE [-w WATCHER] [--remove] // jira comment ISSUE [--noedit] // jira (set,add,remove) labels ISSUE [LABEL] ... @@ -303,7 +306,6 @@ func main() { // "browse": "browse", // "req": "request", // "request": "request", - // "rank": "rank", // "unassign": "unassign", // } @@ -504,13 +506,6 @@ func main() { // case "unassign": // requireArgs(1) // err = c.CmdUnassign(args[0]) - // case "rank": - // requireArgs(3) - // if args[1] == "after" { - // err = c.CmdRankAfter(args[0], args[2]) - // } else { - // err = c.CmdRankBefore(args[0], args[2]) - // } // case "request": // requireArgs(1) // data := "" diff --git a/issue.go b/issue.go index 6b153dca..154a6663 100644 --- a/issue.go +++ b/issue.go @@ -345,3 +345,27 @@ func (j *Jira) IssueRemoveVote(issue string) error { } return responseError(resp) } + +type RankRequestProvider interface { + ProvideRankRequest() *jiradata.RankRequest +} + +// https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/issue-rankIssues +func (j *Jira) RankIssues(rrp RankRequestProvider) error { + req := rrp.ProvideRankRequest() + encoded, err := json.Marshal(req) + if err != nil { + return err + } + uri := fmt.Sprintf("%s/rest/agile/1.0/issue/rank", j.Endpoint) + resp, err := j.UA.Put(uri, "application/json", bytes.NewBuffer(encoded)) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == 204 { + return nil + } + return responseError(resp) +} diff --git a/jiracli/cli.go b/jiracli/cli.go index 1e33982e..0b12d6f4 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -553,55 +553,6 @@ func (jc *JiraCli) editLoop(opts *GlobalOptions, input interface{}, output inter // return data, nil // } -// // RankOrder type used to specify before/after ranking arguments to RankIssue -// type RankOrder int - -// const ( -// // RANKBEFORE should be used to rank issue before the target issue -// RANKBEFORE RankOrder = iota -// // RANKAFTER should be used to rank issue after the target issue -// RANKAFTER RankOrder = iota -// ) - -// // RankIssue will modify issue to have rank before or after the target issue -// func (c *Cli) RankIssue(issue, target string, order RankOrder) error { -// type RankRequest struct { -// Issues []string `json:"issues"` -// Before string `json:"rankBeforeIssue,omitempty"` -// After string `json:"rankAfterIssue,omitempty"` -// } -// req := &RankRequest{ -// Issues: []string{ -// issue, -// }, -// } -// if order == RANKBEFORE { -// req.Before = target -// } else { -// req.After = target -// } - -// json, err := jsonEncode(req) -// if err != nil { -// return err -// } - -// uri := fmt.Sprintf("%s/rest/agile/1.0/issue/rank", c.endpoint) -// if c.getOptBool("dryrun", false) { -// log.Debugf("PUT: %s", json) -// log.Debugf("Dryrun mode, skipping PUT") -// return nil -// } -// resp, err := c.put(uri, json) -// if err != nil { -// return err -// } -// if resp.StatusCode != 204 { -// return fmt.Errorf("failed to modify issue rank: %s", resp.Status) -// } -// return nil -// } - // // GetOptString will extract the string from the Cli object options // // otherwise return the provided default // func (c *Cli) GetOptString(optName string, dflt string) string { diff --git a/jiracli/commands.go b/jiracli/commands.go index 89739e5f..7fc96fdb 100644 --- a/jiracli/commands.go +++ b/jiracli/commands.go @@ -168,30 +168,6 @@ func (jc *JiraCli) Register(app *kingpin.Application, reg []CommandRegistry) { // return nil // } -// // CmdRankAfter rank issue after target issue -// func (c *Cli) CmdRankAfter(issue, after string) error { -// err := c.RankIssue(issue, after, RANKAFTER) -// if err != nil { -// return nil -// } -// if !c.GetOptBool("quiet", false) { -// fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue) -// } -// return nil -// } - -// // CmdRankBefore rank issue before target issue -// func (c *Cli) CmdRankBefore(issue, before string) error { -// err := c.RankIssue(issue, before, RANKBEFORE) -// if err != nil { -// return nil -// } -// if !c.GetOptBool("quiet", false) { -// fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue) -// } -// return nil -// } - // // CmdComment will open up editor with "comment" template and submit // // YAML output to jira // func (c *Cli) CmdComment(issue string) error { diff --git a/jiracli/list.go b/jiracli/list.go index 9efb4831..f9072d02 100644 --- a/jiracli/list.go +++ b/jiracli/list.go @@ -37,13 +37,16 @@ func (jc *JiraCli) CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions) error cmd.Flag("assignee", "User assigned the issue").Short('a').StringVar(&opts.Assignee) cmd.Flag("component", "Component to search for").Short('c').StringVar(&opts.Component) cmd.Flag("issuetype", "Issue type to search for").Short('i').StringVar(&opts.IssueType) + // FIXME Default cmd.Flag("limit", "Maximum number of results to return in search").Short('l').Default("500").IntVar(&opts.MaxResults) cmd.Flag("project", "Project to search for").Short('p').StringVar(&opts.Project) cmd.Flag("query", "Jira Query Language (JQL) expression for the search").Short('q').StringVar(&opts.Query) + // FIXME Default cmd.Flag("queryfields", "Fields that are used in \"list\" template").Short('f').Default( "assignee,created,priority,reporter,status,summary,updated", ).StringVar(&opts.QueryFields) cmd.Flag("reporter", "Reporter to search for").Short('r').StringVar(&opts.Reporter) + // FIXME Default cmd.Flag("sort", "Sort order to return").Short('s').Default("priority asc, key").StringVar(&opts.Sort) cmd.Flag("watcher", "Watcher to search for").Short('w').StringVar(&opts.Watcher) return nil diff --git a/jiracli/rank.go b/jiracli/rank.go new file mode 100644 index 00000000..d204244b --- /dev/null +++ b/jiracli/rank.go @@ -0,0 +1,65 @@ +package jiracli + +import ( + "fmt" + + "gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata" + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +type RankOptions struct { + GlobalOptions + First string + Second string + Order string +} + +func (jc *JiraCli) CmdRankRegistry() *CommandRegistryEntry { + opts := RankOptions{ + GlobalOptions: GlobalOptions{}, + } + + return &CommandRegistryEntry{ + "Mark issues as blocker", + func() error { + return jc.CmdRank(&opts) + }, + func(cmd *kingpin.CmdClause) error { + return jc.CmdRankUsage(cmd, &opts) + }, + } +} + +func (jc *JiraCli) CmdRankUsage(cmd *kingpin.CmdClause, opts *RankOptions) error { + if err := jc.GlobalUsage(cmd, &opts.GlobalOptions); err != nil { + return err + } + cmd.Arg("FIRST-ISSUE", "first issue").Required().StringVar(&opts.First) + cmd.Arg("after|before", "rank ordering").Required().HintOptions("after", "before").EnumVar(&opts.Order, "after", "before") + cmd.Arg("SECOND-ISSUE", "second issue").Required().StringVar(&opts.Second) + return nil +} + +// CmdRank order two issue +func (jc *JiraCli) CmdRank(opts *RankOptions) error { + req := &jiradata.RankRequest{ + Issues: []string{opts.First}, + } + + if opts.Order == "after" { + req.RankAfterIssue = opts.Second + } else { + req.RankBeforeIssue = opts.Second + } + + if err := jc.RankIssues(req); err != nil { + return err + } + + fmt.Printf("OK %s %s/browse/%s\n", opts.First, jc.Endpoint, opts.First) + fmt.Printf("OK %s %s/browse/%s\n", opts.Second, jc.Endpoint, opts.Second) + + // FIXME implement browse + + return nil +} diff --git a/jiradata/RankRequest.go b/jiradata/RankRequest.go new file mode 100644 index 00000000..826516da --- /dev/null +++ b/jiradata/RankRequest.go @@ -0,0 +1,7 @@ +package jiradata + +type RankRequest struct { + Issues []string `json:"issues,omitempty" yaml:"issues,omitempty"` + RankBeforeIssue string `json:"rankBeforeIssue,omitempty" yaml:"rankBeforeIssue,omitempty"` + RankAfterIssue string `json:"rankAfterIssue,omitempty" yaml:"rankAfterIssue,omitempty"` +} diff --git a/jiradata/providers.go b/jiradata/providers.go index 085fe5ba..1d36ddaa 100644 --- a/jiradata/providers.go +++ b/jiradata/providers.go @@ -13,3 +13,7 @@ func (w *Worklog) ProvideWorklog() *Worklog { func (l *LinkIssueRequest) ProvideLinkIssueRequest() *LinkIssueRequest { return l } + +func (r *RankRequest) ProvideRankRequest() *RankRequest { + return r +}