From 52085417e68bd7c578f99850de6b3a8a1b7b1982 Mon Sep 17 00:00:00 2001 From: Cory Bennett Date: Sun, 26 Feb 2017 22:38:47 -0800 Subject: [PATCH] [#69] add subtask command --- commands.go | 114 ++++++++++++++++++++++++++++++++++++++++++--------- main/main.go | 14 +++++-- templates.go | 25 +++++++++++ 3 files changed, 129 insertions(+), 24 deletions(-) diff --git a/commands.go b/commands.go index 285573f..f57afaf 100644 --- a/commands.go +++ b/commands.go @@ -376,31 +376,15 @@ func (c *Cli) CmdCreate() error { issuetype = c.defaultIssueType() } - uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, url.QueryEscape(issuetype)) - data, err := responseToJSON(c.get(uri)) - if err != nil { - return err - } - issueData := make(map[string]interface{}) issueData["overrides"] = c.opts issueData["overrides"].(map[string]interface{})["issuetype"] = issuetype - if val, ok := data.(map[string]interface{})["projects"]; ok { - if len(val.([]interface{})) == 0 { - err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype) - log.Errorf("%s", err) - return err - } - if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok { - if len(val.([]interface{})) == 0 { - err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype) - log.Errorf("%s", err) - return err - } - issueData["meta"] = val.([]interface{})[0] - } + meta, err := c.createIssueMetaData(project, issuetype) + if err != nil { + return err } + issueData["meta"] = meta sanitizedType := strings.ToLower(strings.Replace(issuetype, " ", "", -1)) return c.editTemplate( @@ -446,6 +430,96 @@ func (c *Cli) CmdCreate() error { ) } +func (c *Cli) createIssueMetaData(project, issuetype string) (interface{}, error) { + uri := fmt.Sprintf("%s/rest/api/2/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=projects.issuetypes.fields", c.endpoint, project, url.QueryEscape(issuetype)) + metaData, err := responseToJSON(c.get(uri)) + if err != nil { + return nil, err + } + + if val, ok := metaData.(map[string]interface{})["projects"]; ok { + if len(val.([]interface{})) == 0 { + err = fmt.Errorf("Project '%s' or issuetype '%s' unknown. Unable to create issue.", project, issuetype) + log.Errorf("%s", err) + return nil, err + } + if val, ok = val.([]interface{})[0].(map[string]interface{})["issuetypes"]; ok { + if len(val.([]interface{})) == 0 { + err = fmt.Errorf("Project '%s' does not support issuetype '%s'. Unable to create issue.", project, issuetype) + log.Errorf("%s", err) + return nil, err + } + return val.([]interface{})[0], nil + } + } + return nil, nil +} + +// CmdSubtask sends the create-metadata to the "subtask" template for editing, then +// will parse the edited document as YAML and submit the document to jira. +func (c *Cli) CmdSubtask(issue string) error { + log.Debugf("subtask called") + + uri := fmt.Sprintf("%s/rest/api/2/issue/%s", c.endpoint, issue) + parentData, err := responseToJSON(c.get(uri)) + if err != nil { + return err + } + + subtaskData := make(map[string]interface{}) + subtaskData["parent"] = parentData + subtaskData["overrides"] = c.opts + + project := parentData.(map[string]interface{})["fields"].(map[string]interface{})["project"].(map[string]interface{})["key"].(string) + meta, err := c.createIssueMetaData(project, "Sub-task") + if err != nil { + return err + } + subtaskData["meta"] = meta + + return c.editTemplate( + c.getTemplate("subtask"), + "subtask-", + subtaskData, + func(json string) error { + uri := fmt.Sprintf("%s/rest/api/2/issue", c.endpoint) + if c.getOptBool("dryrun", false) { + log.Debugf("POST: %s", json) + log.Debugf("Dryrun mode, skipping POST") + return nil + } + resp, err := c.post(uri, json) + if err != nil { + return err + } + + if resp.StatusCode == 201 { + // response: {"id":"410836","key":"PROJ-238","self":"https://jira/rest/api/2/issue/410836"} + json, err := responseToJSON(resp, nil) + if err != nil { + return err + } + key := json.(map[string]interface{})["key"].(string) + link := fmt.Sprintf("%s/browse/%s", c.endpoint, key) + c.Browse(key) + c.SaveData(map[string]string{ + "issue": key, + "link": link, + }) + if !c.opts["quiet"].(bool) { + fmt.Printf("OK %s %s\n", key, link) + } + return nil + } + logBuffer := bytes.NewBuffer(make([]byte, 0)) + resp.Write(logBuffer) + err = fmt.Errorf("Unexpected Response From POST") + log.Errorf("%s:\n%s", err, logBuffer) + return err + }, + ) +} + // CmdIssueLinkTypes will send the issue link type data to the "issuelinktypes" template. func (c *Cli) CmdIssueLinkTypes() error { log.Debugf("Transitions called") diff --git a/main/main.go b/main/main.go index bc9ba5d..e9e472a 100644 --- a/main/main.go +++ b/main/main.go @@ -3,14 +3,15 @@ package main import ( "bytes" "fmt" - "gopkg.in/Netflix-Skunkworks/go-jira.v0" - "github.com/coryb/optigo" - "gopkg.in/coryb/yaml.v2" - "gopkg.in/op/go-logging.v1" "io/ioutil" "os" "os/exec" "strings" + + "github.com/coryb/optigo" + "gopkg.in/Netflix-Skunkworks/go-jira.v0" + "gopkg.in/coryb/yaml.v2" + "gopkg.in/op/go-logging.v1" ) var ( @@ -60,6 +61,7 @@ Usage: jira add worklog ISSUE jira edit [--noedit] [ISSUE | ] jira create [--noedit] [-p PROJECT] + jira subtask ISSUE [--noedit] jira DUPLICATE dups ISSUE jira BLOCKER blocks ISSUE jira vote ISSUE [--down] @@ -145,6 +147,7 @@ Command Options: "view": "view", "edit": "edit", "create": "create", + "subtask": "subtask", "dups": "dups", "blocks": "blocks", "watch": "watch", @@ -376,6 +379,9 @@ Command Options: case "create": setEditing(true) err = c.CmdCreate() + case "subtask": + setEditing(true) + err = c.CmdSubtask(args[0]) case "transitions": requireArgs(1) err = c.CmdTransitions(args[0]) diff --git a/templates.go b/templates.go index b14ec92..a16696f 100644 --- a/templates.go +++ b/templates.go @@ -15,6 +15,7 @@ var allTemplates = map[string]string{ "components": defaultComponentsTemplate, "issuetypes": defaultIssuetypesTemplate, "create": defaultCreateTemplate, + "subtask": defaultSubtaskTemplate, "comment": defaultCommentTemplate, "transition": defaultTransitionTemplate, "request": defaultDebugTemplate, @@ -140,6 +141,30 @@ fields: - name: {{.}}{{end}} - name:{{end}}` +const defaultSubtaskTemplate = `{{/* create subtask template */ -}} +fields: + project: + key: {{ .parent.fields.project.key }} + summary: {{ or .overrides.summary "" }}{{if .meta.fields.priority.allowedValues}} + priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}} + name: {{ or .overrides.priority ""}}{{end}}{{if .meta.fields.components.allowedValues}} + components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}} + - name: {{ . }}{{end}}{{end}} + description: |~ + {{ or .overrides.description "" | indent 4 }}{{if .meta.fields.assignee}} + assignee: + name: {{ or .overrides.assignee "" }}{{end}}{{if .meta.fields.reporter}} + reporter: + name: {{ or .overrides.reporter .overrides.user }}{{end}}{{if .meta.fields.customfield_10110}} + # watchers + customfield_10110: {{ range split "," (or .overrides.watchers "")}} + - name: {{.}}{{end}} + - name:{{end}} + issuetype: + name: Sub-task + parent: + key: {{ .parent.key }}` + const defaultCommentTemplate = `body: |~ {{ or .overrides.comment "" | indent 2 }} `