diff --git a/jira/cli/cli.go b/jira/cli/cli.go index 0e9034d..ece1eda 100644 --- a/jira/cli/cli.go +++ b/jira/cli/cli.go @@ -235,29 +235,37 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m } } } + + editing := true + if val, ok := c.opts["edit"]; ok && val == "false" { + editing = false + } + for true { - log.Debug("Running: %s %s", editor, tmpFileName) - cmd := exec.Command(editor, tmpFileName) - cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin - if err := cmd.Run(); err != nil { - log.Error("Failed to edit template with %s: %s", editor, err) - if promptYN("edit again?", true) { - continue + if editing { + log.Debug("Running: %s %s", editor, tmpFileName) + cmd := exec.Command(editor, tmpFileName) + cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin + if err := cmd.Run(); err != nil { + log.Error("Failed to edit template with %s: %s", editor, err) + if promptYN("edit again?", true) { + continue + } + return err } - return err } edited := make(map[string]interface{}) if fh, err := ioutil.ReadFile(tmpFileName); err != nil { log.Error("Failed to read tmpfile %s: %s", tmpFileName, err) - if promptYN("edit again?", true) { + if editing && promptYN("edit again?", true) { continue } return err } else { if err := yaml.Unmarshal(fh, &edited); err != nil { log.Error("Failed to parse YAML: %s", err) - if promptYN("edit again?", true) { + if editing && promptYN("edit again?", true) { continue } return err @@ -272,15 +280,16 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m if _, ok := templateData["meta"]; ok { mf := templateData["meta"].(map[string]interface{})["fields"] - f := edited["fields"].(map[string]interface{}) - for k, _ := range f { - if _, ok := mf.(map[string]interface{})[k]; !ok { - err := fmt.Errorf("Field %s is not editable", k) - log.Error("%s", err) - if promptYN("edit again?", true) { - continue + if f, ok := edited["fields"].(map[string]interface{}); ok { + for k, _ := range f { + if _, ok := mf.(map[string]interface{})[k]; !ok { + err := fmt.Errorf("Field %s is not editable", k) + log.Error("%s", err) + if editing && promptYN("edit again?", true) { + continue + } + return err } - return err } } } @@ -292,7 +301,7 @@ func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData m if err := templateProcessor(json); err != nil { log.Error("%s", err) - if promptYN("edit again?", true) { + if editing && promptYN("edit again?", true) { continue } } diff --git a/jira/cli/commands.go b/jira/cli/commands.go index e7163d8..6aaa3c9 100644 --- a/jira/cli/commands.go +++ b/jira/cli/commands.go @@ -403,21 +403,24 @@ func (c *Cli) CmdWatch(issue string, watcher string) error { func (c *Cli) CmdTransition(issue string, trans string) error { log.Debug("transition called") - uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue) + uri := fmt.Sprintf("%s/rest/api/2/issue/%s/transitions?expand=transitions.fields", c.endpoint, issue) data, err := responseToJson(c.get(uri)) if err != nil { return err } transitions := data.(map[string]interface{})["transitions"].([]interface{}) - var transId string + var transId, transName string + var transMeta map[string]interface{} found := make([]string, 0, len(transitions)) for _, transition := range transitions { name := transition.(map[string]interface{})["name"].(string) id := transition.(map[string]interface{})["id"].(string) found = append(found, name) if strings.Contains(strings.ToLower(name), trans) { + transName = name transId = id + transMeta = transition.(map[string]interface{}) } } if transId == "" { @@ -426,45 +429,47 @@ func (c *Cli) CmdTransition(issue string, trans string) error { return err } - payload := map[string]interface{}{ - "transition": map[string]interface{}{ - "id": transId, - }, - } - - if comment, ok := c.opts["comment"]; ok { - payload["update"] = map[string]interface{}{ - "comment": []interface{}{ - map[string]interface{}{ - "add": map[string]interface{}{ - "body": comment, - }, - }, - }, + handlePost := func(json string) error { + log.Debug("POST: %s", json) + // os.Exit(0) + uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue) + resp, err := c.post(uri, json) + if err != nil { + return err } + if resp.StatusCode == 204 { + c.Browse(issue) + fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue) + } else { + logBuffer := bytes.NewBuffer(make([]byte, 0)) + resp.Write(logBuffer) + err := fmt.Errorf("Unexpected Response From POST") + log.Error("%s:\n%s", err, logBuffer) + return err + } + return nil } - - json, err := jsonEncode(payload) - if err != nil { + + uri = fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue) + var issueData map[string]interface{} + if data, err := responseToJson(c.get(uri)); err != nil { return err - } - - uri = fmt.Sprintf("%s/rest/api/2/issue/%s/transitions", c.endpoint, issue) - resp, err := c.post(uri, json) - if err != nil { - return err - } - if resp.StatusCode == 204 { - c.Browse(issue) - fmt.Printf("OK %s %s/browse/%s\n", issue, c.endpoint, issue) } else { - logBuffer := bytes.NewBuffer(make([]byte, 0)) - resp.Write(logBuffer) - err := fmt.Errorf("Unexpected Response From POST") - log.Error("%s:\n%s", err, logBuffer) - return err + issueData = data.(map[string]interface{}) } - return nil + issueData["meta"] = transMeta + issueData["overrides"] = c.opts + issueData["transition"] = map[string]interface{}{ + "name": transName, + "id": transId, + }; + + return c.editTemplate( + c.getTemplate("transition"), + fmt.Sprintf("%s-trans-%s-", issue, trans), + issueData, + handlePost, + ) } func (c *Cli) CmdComment(issue string) error { diff --git a/jira/cli/templates.go b/jira/cli/templates.go index f786244..4a9f970 100644 --- a/jira/cli/templates.go +++ b/jira/cli/templates.go @@ -14,6 +14,7 @@ var all_templates = map[string]string{ "issuetypes": default_issuetypes_template, "create": default_create_template, "comment": default_comment_template, + "transition": default_transition_template, } const default_debug_template = "{{ . | toJson}}\n" @@ -76,7 +77,7 @@ const default_create_template = `fields: name: {{ .overrides.issuetype }} summary: {{ or .overrides.summary "" }} priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}} - name: {{ or .overrides.priority "" }} + name: {{ or .overrides.priority "unassigned" }} components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}} - name: {{ . }}{{end}} description: | @@ -93,3 +94,38 @@ const default_create_template = `fields: const default_comment_template = `body: | {{ or .overrides.comment | indent 2 }} ` + +const default_transition_template = `update: + comment: + - add: + body: | + {{ or .overrides.comment "" | indent 10 }} +fields:{{if .meta.fields.assignee}} + assignee: + name: {{if .overrides.assignee}}{{.overrides.assignee}}{{else}}{{if .fields.assignee}}{{.fields.assignee.name}}{{end}}{{end}}{{end}}{{if .meta.fields.components}} + components: # {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{if .overrides.components }}{{ range (split "," .overrides.components)}} + - name: {{.}}{{end}}{{else}}{{ range .fields.components }} + - name: {{ .name }}{{end}}{{end}}{{end}}{{if .meta.fields.description}} + description: {{or .overrides.description .fields.description }}{{end}}{{if .meta.fields.fixVersions}}{{if .meta.fields.fixVersions.allowedValues}} + fixVersions: # {{ range .meta.fields.fixVersions.allowedValues }}{{.name}}, {{end}}{{if .overrides.fixVersions}}{{ range (split "," .overrides.fixVersions)}} + - name: {{.}}{{end}}{{else}}{{range .fields.fixVersions}} + - name: {{.}}{{end}}{{end}}{{end}}{{end}}{{if .meta.fields.issuetype}} + issuetype: # {{ range .meta.fields.issuetype.allowedValues }}{{.name}}, {{end}} + name: {{if .overrides.issuetype}}{{.overrides.issuetype}}{{else}}{{if .fields.issuetype}}{{.fields.issuetype.name}}{{end}}{{end}}{{end}}{{if .meta.fields.labels}} + labels: {{range .fields.labels}} + - {{.}}{{end}}{{if .overrides.labels}}{{range (split "," .overrides.labels)}} + - {{.}}{{end}}{{end}}{{end}}{{if .meta.fields.priority}} + priority: # {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}} + name: {{ or .overrides.priority "unassigned" }}{{end}}{{if .meta.fields.reporter}} + reporter: + name: {{if .overrides.reporter}}{{.overrides.reporter}}{{else}}{{if .fields.reporter}}{{.fields.reporter.name}}{{end}}{{end}}{{end}}{{if .meta.fields.resolution}} + resolution: # {{ range .meta.fields.resolution.allowedValues }}{{.name}}, {{end}} + name: {{if .overrides.resolution}}{{.overrides.resolution}}{{else if .fields.resolution}}{{.fields.resolution.name}}{{else}}Fixed{{end}}{{end}}{{if .meta.fields.summary}} + summary: {{or .overrides.summary .fields.summary}}{{end}}{{if .meta.fields.versions.allowedValues}} + versions: # {{ range .meta.fields.versions.allowedValues }}{{.name}}, {{end}}{{if .overrides.versions}}{{ range (split "," .overrides.versions)}} + - name: {{.}}{{end}}{{else}}{{range .fields.versions}} + - name: {{.}}{{end}}{{end}}{{end}} +transition: + id: {{ .transition.id }} + name: {{ .transition.name }} +` diff --git a/jira/cli/util.go b/jira/cli/util.go index 0bd117f..7d8f656 100644 --- a/jira/cli/util.go +++ b/jira/cli/util.go @@ -184,7 +184,7 @@ func yamlFixup(data interface{}) (interface{}, error) { case string: if fixed, err := yamlFixup(val); err != nil { return nil, err - } else { + } else if fixed != nil { copy[k] = fixed } default: @@ -198,7 +198,7 @@ func yamlFixup(data interface{}) (interface{}, error) { for k, v := range d { if fixed, err := yamlFixup(v); err != nil { return nil, err - } else { + } else if fixed != nil { d[k] = fixed } } @@ -207,11 +207,16 @@ func yamlFixup(data interface{}) (interface{}, error) { for i, val := range d { if fixed, err := yamlFixup(val); err != nil { return nil, err - } else { + } else if fixed != nil { d[i] = fixed } } return data, nil + case string: + if d == "" { + return nil, nil + } + return d, nil default: return d, nil } diff --git a/jira/main.go b/jira/main.go index 65de7cb..d8736c6 100644 --- a/jira/main.go +++ b/jira/main.go @@ -21,18 +21,18 @@ func main() { Usage: jira [-v ...] [-u USER] [-e URI] [-t FILE] (ls|list) ( [-q JQL] | [-p PROJECT] [-c COMPONENT] [-a ASSIGNEE] [-i ISSUETYPE] [-w WATCHER] [-r REPORTER]) jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] view ISSUE - jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [-m COMMENT] [-o KEY=VAL]... - jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]... + jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] edit ISSUE [--noedit] [-m COMMENT] [-o KEY=VAL]... + jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] create [--noedit] [-p PROJECT] [-i ISSUETYPE] [-o KEY=VAL]... jira [-v ...] [-u USER] [-e URI] [-b] DUPLICATE dups ISSUE jira [-v ...] [-u USER] [-e URI] [-b] BLOCKER blocks ISSUE jira [-v ...] [-u USER] [-e URI] [-b] watch ISSUE [-w WATCHER] - jira [-v ...] [-u USER] [-e URI] [-b] (trans|transition) TRANSITION ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] ack ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] close ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] resolve ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] reopen ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] start ISSUE [-m COMMENT] - jira [-v ...] [-u USER] [-e URI] [-b] stop ISSUE [-m COMMENT] + jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] (trans|transition) TRANSITION ISSUE [-m COMMENT] [--noedit] + jira [-v ...] [-u USER] [-e URI] [-b] ack ISSUE [-m COMMENT] [--edit] + jira [-v ...] [-u USER] [-e URI] [-b] close ISSUE [-m COMMENT] [--edit] + jira [-v ...] [-u USER] [-e URI] [-b] resolve ISSUE [-m COMMENT] [--edit] + jira [-v ...] [-u USER] [-e URI] [-b] reopen ISSUE [-m COMMENT] [--edit] + jira [-v ...] [-u USER] [-e URI] [-b] start ISSUE [-m COMMENT] [--edit] + jira [-v ...] [-u USER] [-e URI] [-b] stop ISSUE [-m COMMENT] [--edit] jira [-v ...] [-u USER] [-e URI] [-b] [-t FILE] comment ISSUE [-m COMMENT] jira [-v ...] [-u USER] [-e URI] [-b] take ISSUE jira [-v ...] [-u USER] [-e URI] [-b] (assign|give) ISSUE ASSIGNEE @@ -142,7 +142,7 @@ Command Options: log.Error("endpoint option required. Either use --endpoint or set a enpoint option in your ~/.jira.d/config.yml file") os.Exit(1) } - + c := cli.New(opts) log.Debug("opts: %s", opts) @@ -165,6 +165,22 @@ Command Options: return dflt } + setEditing := func(dflt bool) { + if dflt { + if val, ok := opts["noedit"]; ok && val == "true" { + opts["edit"] = "false" + } else { + opts["edit"] = "true" + } + } else { + if val, ok := opts["edit"]; ok && val == "true" { + opts["edit"] = "true" + } else { + opts["edit"] = "false" + } + } + } + if validCommand("login") { err = c.CmdLogin() } else if validCommand("fields") { @@ -172,6 +188,7 @@ Command Options: } else if validCommand("ls") || validCommand("list") { err = c.CmdList() } else if validCommand("edit") { + setEditing(true) err = c.CmdEdit(args["ISSUE"].(string)) } else if validCommand("editmeta") { err = c.CmdEditMeta(args["ISSUE"].(string)) @@ -187,6 +204,7 @@ Command Options: validOpt("issuetype", "Bug").(string), ) } else if validCommand("create") { + setEditing(true) err = c.CmdCreate( validOpt("project", nil).(string), validOpt("issuetype", "Bug").(string), @@ -209,21 +227,28 @@ Command Options: validOpt("watcher", user).(string), ) } else if validCommand("trans") || validCommand("transition") { + setEditing(true) err = c.CmdTransition( args["ISSUE"].(string), args["TRANSITION"].(string), ) } else if validCommand("close") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "close") } else if validCommand("ack") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "acknowledge") } else if validCommand("reopen") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "reopen") } else if validCommand("resolve") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "resolve") } else if validCommand("start") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "start") } else if validCommand("stop") { + setEditing(false) err = c.CmdTransition(args["ISSUE"].(string), "stop") } else if validCommand("comment") { err = c.CmdComment(args["ISSUE"].(string))