diff --git a/cmd/jkl/create.go b/cmd/jkl/create.go index 46c0287..69c37ec 100644 --- a/cmd/jkl/create.go +++ b/cmd/jkl/create.go @@ -6,6 +6,8 @@ import ( "flag" "io" "os" + "fmt" + "text/template" "otremblay.com/jkl" ) @@ -14,6 +16,7 @@ type CreateCmd struct { args []string project string file string + issuetype string } func NewCreateCmd(args []string) (*CreateCmd, error) { @@ -22,6 +25,7 @@ func NewCreateCmd(args []string) (*CreateCmd, error) { f.StringVar(&ccmd.project, "p", "", "Jira project key") f.StringVar(&ccmd.file, "f", "", "File to get issue description from") f.Parse(args) + ccmd.args = f.Args() return ccmd, nil } @@ -39,18 +43,32 @@ func (ccmd *CreateCmd) Create() error { } } + + if ccmd.project == "" { + return ErrCcmdJiraProjectRequired + } + isstype := "" + if len(ccmd.args) > 0 { + isstype = ccmd.args[0] + } + cm, err := jkl.GetCreateMeta(ccmd.project, isstype) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Sprintf("Error getting the CreateMeta for project [%s] and issue types [%s]", ccmd.project, isstype), err) + } + if !readfile { - b.WriteString(CREATE_TEMPLATE) + createTemplate.Execute(b, cm) } var iss *jkl.JiraIssue - var err error + // TODO: Evil badbad don't do this. + em := &jkl.EditMeta{Fields: cm.Projects[0].IssueTypes[0].Fields} if ccmd.file != "" { - iss, err = GetIssueFromFile(ccmd.file, b) + iss, err = GetIssueFromFile(ccmd.file, b, em) if err != nil { return err } } else { - iss, err = GetIssueFromTmpFile(b) + iss, err = GetIssueFromTmpFile(b, em) if err != nil { return err } @@ -60,13 +78,24 @@ func (ccmd *CreateCmd) Create() error { (iss.Fields.Project == nil || iss.Fields.Project.Key == "") { iss.Fields.Project = &jkl.Project{Key: ccmd.project} } - return jkl.Create(iss) + iss, err = jkl.Create(iss) + if err != nil { + return err + } + fmt.Println(iss.Key) + return nil } func (ccmd *CreateCmd) Run() error { return ccmd.Create() } -const CREATE_TEMPLATE = `Issue Type: +var createTemplate = template.Must(template.New("createissue").Parse(`{{range .Projects -}} +Project: {{.Key}} +{{range .IssueTypes -}} +Issue Type: {{.Name}} Summary: -Description:` +Description: +{{.RangeFieldSpecs}} +{{end}} +{{end}}`)) diff --git a/cmd/jkl/edit.go b/cmd/jkl/edit.go index 01a3a99..e06ec37 100644 --- a/cmd/jkl/edit.go +++ b/cmd/jkl/edit.go @@ -39,13 +39,13 @@ func (ecmd *EditCmd) Edit() error { } if ecmd.file != "" { - iss, err = GetIssueFromFile(ecmd.file, b) + iss, err = GetIssueFromFile(ecmd.file, b, iss.EditMeta) if err != nil { return err } } else { - iss, err = GetIssueFromTmpFile(b) + iss, err = GetIssueFromTmpFile(b, iss.EditMeta) if err != nil { return err } diff --git a/cmd/jkl/editcomment.go b/cmd/jkl/editcomment.go index 0ed1dc8..0dcddc9 100644 --- a/cmd/jkl/editcomment.go +++ b/cmd/jkl/editcomment.go @@ -1,28 +1,72 @@ package main import ( - "errors" + "bytes" "flag" + "io" + "io/ioutil" + "strings" + + "otremblay.com/jkl" ) type EditCommentCmd struct { - args []string - file string - issueKey string + args []string + file string + issueKey string + commentId string } func NewEditCommentCmd(args []string) (*EditCommentCmd, error) { ccmd := &EditCommentCmd{} - f := flag.NewFlagSet("comments", flag.ExitOnError) + f := flag.NewFlagSet("editcomments", flag.ExitOnError) f.StringVar(&ccmd.file, "f", "", "File to get issue comment from") f.Parse(args) if len(f.Args()) < 1 { return nil, ErrNotEnoughArgs } - ccmd.issueKey = f.Arg(0) + ids := strings.Split(f.Arg(0), jkl.CommentIdSeparator) + ccmd.issueKey = ids[0] + if len(ids) < 2 { + if len(f.Args()) == 2 { + ccmd.commentId = f.Args()[1] + } else { + return nil, ErrNotEnoughArgs + } + } else { + ccmd.commentId = ids[1] + } return ccmd, nil } func (e *EditCommentCmd) Run() error { - return errors.New("Not implemented") -} + + + // Get Comment + comm, err := jkl.GetComment(e.issueKey, e.commentId) + if err != nil { + return err + } + b := bytes.NewBufferString(comm.Body) + var rdr io.Reader + if e.file != "" { + rdr, err = GetTextFromSpecifiedFile(e.file, b) + + if err != nil { + return err + } + } else { + rdr, err = GetTextFromTmpFile(b) + if err != nil { + return err + } + } + btxt, err := ioutil.ReadAll(rdr) + txt := strings.TrimSpace(string(btxt)) + if txt == "" || txt == strings.TrimSpace(comm.Body) { + // Nothing to do + return nil + } + comm.Body = txt + return jkl.EditComment(e.issueKey, e.commentId, comm) +} \ No newline at end of file diff --git a/cmd/jkl/editor.go b/cmd/jkl/editor.go index 6a3ead3..fedfc6e 100644 --- a/cmd/jkl/editor.go +++ b/cmd/jkl/editor.go @@ -17,7 +17,6 @@ import ( "otremblay.com/jkl" ) - // def get_editor do // [System.get_env("EDITOR"), "nano", "vim", "vi"] // |> Enum.find(nil, fn (ed) -> System.find_executable(ed) != nil end) @@ -40,7 +39,7 @@ func copyInitial(dst io.WriteSeeker, initial io.Reader) { dst.Seek(0, 0) } -func GetIssueFromTmpFile(initial io.Reader) (*jkl.JiraIssue, error) { +func GetIssueFromTmpFile(initial io.Reader, editMeta *jkl.EditMeta) (*jkl.JiraIssue, error) { f, err := ioutil.TempFile(os.TempDir(), "jkl") if err != nil { return nil, err @@ -50,7 +49,7 @@ func GetIssueFromTmpFile(initial io.Reader) (*jkl.JiraIssue, error) { if err != nil { return nil, err } - return IssueFromReader(f2), nil + return IssueFromReader(f2, editMeta), nil } func GetTextFromTmpFile(initial io.Reader) (io.Reader, error) { @@ -87,7 +86,7 @@ func GetTextFromFile(file *os.File) (io.Reader, error) { return file, err } -func GetIssueFromFile(filename string, initial io.Reader) (*jkl.JiraIssue, error) { +func GetIssueFromFile(filename string, initial io.Reader, editMeta *jkl.EditMeta) (*jkl.JiraIssue, error) { f, err := os.Open(filename) if err != nil { return nil, err @@ -99,12 +98,12 @@ func GetIssueFromFile(filename string, initial io.Reader) (*jkl.JiraIssue, error if err != nil { return nil, err } - return IssueFromReader(f2), nil + return IssueFromReader(f2, editMeta), nil } var spacex = regexp.MustCompile(`\s`) -func IssueFromReader(f io.Reader) *jkl.JiraIssue { +func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue { iss := &jkl.JiraIssue{Fields: &jkl.Fields{}} riss := reflect.ValueOf(iss).Elem() fieldsField := riss.FieldByName("Fields").Elem() @@ -155,6 +154,9 @@ func IssueFromReader(f io.Reader) *jkl.JiraIssue { } else { currentField = newfield } + } else if editMeta != nil { + // If it's not valid, throw it at the createmeta. It will probably end up in ExtraFields. + } if currentField.IsValid() { currentField.SetString(strings.TrimSpace(currentField.String() + "\n" + strings.Join(parts, ":"))) @@ -163,6 +165,6 @@ func IssueFromReader(f io.Reader) *jkl.JiraIssue { return iss } -func IssueFromList(list []string) *jkl.JiraIssue { - return IssueFromReader(bytes.NewBufferString(strings.Join(list, "\n"))) +func IssueFromList(list []string, editMeta *jkl.EditMeta) *jkl.JiraIssue { + return IssueFromReader(bytes.NewBufferString(strings.Join(list, "\n")), editMeta) } diff --git a/cmd/jkl/jkl.go b/cmd/jkl/jkl.go index 5136e6a..45da524 100644 --- a/cmd/jkl/jkl.go +++ b/cmd/jkl/jkl.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "sort" "strings" @@ -54,6 +55,9 @@ func getCmd(args []string, depth int) (Runner, error) { case "edit": return NewEditCmd(args[1:]) case "comment": + if strings.Contains(strings.Join(args,""),jkl.CommentIdSeparator){ + return NewEditCommentCmd(args[1:]) + } return NewCommentCmd(args[1:]) case "edit-comment": return NewEditCommentCmd(args[1:]) @@ -67,6 +71,13 @@ func getCmd(args []string, depth int) (Runner, error) { if depth == 0 { // Assume args[0] is a task key + if len(args) == 1 { + // Default to task info + args = append(args, "task") + } + if verbs[sort.SearchStrings(verbs, args[1])] != args[1] { + return &TaskCmd{args}, nil + } args[0], args[1] = args[1], args[0] return getCmd(args, depth+1) } else { @@ -81,6 +92,11 @@ func getCmd(args []string, depth int) (Runner, error) { return nil, ErrTaskSubCommandNotFound } +var verbs = []string{"list", "create", "task", "edit", "comment","edit-comment"} +func init(){ +sort.Strings(verbs) +} + const usage = `Usage: jkl [options] [args] diff --git a/cmd/jkl/task.go b/cmd/jkl/task.go index cfe90d6..b9279a3 100644 --- a/cmd/jkl/task.go +++ b/cmd/jkl/task.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "strings" "otremblay.com/jkl" ) @@ -18,7 +19,12 @@ func (t *TaskCmd) Handle() error { return t.Get(t.args[0]) } if c == 2 { - return t.Transition(t.args[0], t.args[1]) + fmt.Println(t.args) + err := t.Transition(t.args[0], t.args[1]) + if err != nil { + fmt.Println(err) + return t.Log(t.args[0], strings.Join(t.args[1:]," ")) + } } return ErrTaskSubCommandNotFound } @@ -35,7 +41,11 @@ func (t *TaskCmd) Get(taskKey string) error { } func (t *TaskCmd) Transition(taskKey, transition string) error { - return nil + return jkl.DoTransition(taskKey, transition) +} + +func (t *TaskCmd) Log(taskKey, time string) error { +return jkl.LogWork(taskKey, time) } func (t *TaskCmd) Run() error { diff --git a/issue.go b/issue.go index da8d9ab..e014354 100644 --- a/issue.go +++ b/issue.go @@ -2,10 +2,16 @@ package jkl import ( "bytes" + "errors" "fmt" "log" "os" + "reflect" + "strings" + "encoding/json" "text/template" + "sort" + "regexp" ) type Search struct { @@ -14,7 +20,30 @@ type Search struct { type IssueType struct { Name string `json:"name"` - Fields map[string]FieldSpec + Fields map[string]*FieldSpec +} + +func (it *IssueType) RangeFieldSpecs() string { + output := bytes.NewBuffer(nil) + keys := make([]string, 0, len(it.Fields)) + for k, _ := range it.Fields { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintln(output, fmt.Sprintf("%s: %v", it.Fields[k].Name, it.Fields[k].AllowedValues)) + } + return output.String() +} + +type AllowedValue struct { + Id string + Self string + Value string +} + +func (a *AllowedValue) String() string{ + return a.Value } type FieldSpec struct { @@ -22,23 +51,39 @@ type FieldSpec struct { Required bool Schema struct { Type string + Custom string + CustomId int + Items string } + Operations []string + AllowedValues []*AllowedValue + +} + + +type CreateMeta struct { + Projects []*Project } type Project struct { Key string `json:"key,omitempty"` - IssueTypes []IssueType + Name string + IssueTypes []*IssueType } type Author struct { - Name string - DisplayName string + Name string `json:"name"` + DisplayName string `json:"displayName"` +} + +func (a *Author) String() string { + return a.DisplayName } type Comment struct { - Id string - Author *Author - Body string + Id string `json:"id"` + Author *Author `json:"author"` + Body string `json:"body"` } type CommentColl struct { @@ -54,6 +99,86 @@ type TimeTracking struct { RemainingEstimateSeconds int } +func (a *Attachment) String() string { + return fmt.Sprintf("Filename: [%s]\tAuthor:[%s]\tURL:[%s]", a.Filename, a.Author.Name, a.Content) +} + +type Attachment struct { + Filename string + Author *Author + Content string +} + +type LinkType struct { + Id string + Name string + Inward string + Outward string +} + +func (i *IssueLink) String() string { + if i.InwardIssue != nil { + return fmt.Sprintf("%s --> %s: %s", i.LinkType.Inward, i.InwardIssue.Key, i.InwardIssue.Fields.Summary) + } + if i.OutwardIssue != nil { + return fmt.Sprintf("%s --> %s: %s", i.LinkType.Outward, i.OutwardIssue.Key, i.OutwardIssue.Fields.Summary) + } + return "Issue Link Got Weird" +} + +type IssueLink struct { + LinkType *LinkType `json:"type"` + InwardIssue *JiraIssue + OutwardIssue *JiraIssue +} + +func (w *Worklog) String() string { + return fmt.Sprintf("%s worked %s on %s", w.Author.Name, w.TimeSpent, w.Started) +} + +type Worklog struct { + Author *Author + Comment string + TimeSpent string + TimeSpentSeconds int + Started string +} + +type Worklogs struct { + Worklogs []*Worklog `json:",omitempty"` +} + +func (f *Fields) UnmarshalJSON(b []byte) error{ + err := json.Unmarshal(b, &f.rawFields) + if err != nil { + fmt.Println("splosion") + return err + } + f.rawExtraFields = map[string]json.RawMessage{} + vf := reflect.ValueOf(f).Elem() + for key, mess := range f.rawFields { + field := vf.FieldByNameFunc(func(s string)bool{return strings.ToLower(key) == strings.ToLower(s)}) + if field.IsValid() { + objType := field.Type() + obj := reflect.New(objType).Interface() + err := json.Unmarshal(mess, &obj) + if err != nil { + fmt.Fprintln(os.Stderr, objType, obj, string(mess)) + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s [%s]: %s","Error allocating field",key, err))) + } + field.Set(reflect.ValueOf(obj).Elem()) + } else { + f.rawExtraFields[key] = mess + } + } + + return nil +} + +type Priority struct { + Name string +} + type Fields struct { *IssueType `json:"issuetype,omitempty"` Assignee *Author `json:",omitempty"` @@ -64,6 +189,13 @@ type Fields struct { Parent *JiraIssue `json:",omitempty"` Status *Status `json:",omitempty"` TimeTracking *TimeTracking `json:"timetracking,omitempty"` + Attachment []*Attachment `json:"attachment,omitempty"` + IssueLinks []*IssueLink `json:"issueLinks,omitempty"` + Priority *Priority `json:",omitempty"` + Worklog *Worklogs `json:"worklog,omitempty"` + rawFields map[string]json.RawMessage + rawExtraFields map[string]json.RawMessage + ExtraFields map[string]interface{} `json:"-"` } func (f *Fields) PrettyRemaining() string { @@ -84,9 +216,99 @@ func PrettySeconds(seconds int) string { return fmt.Sprintf("%dd %2dh %2dm %2ds", days, hours, minutes, seconds) } +type Transition struct{ + Id string `json:"id"` + Name string `json:"name"` +} + + +type Schema struct { + System string + Custom string + CustomId int +} + + +type EditMeta struct { + Fields map[string]*FieldSpec +} + + + type JiraIssue struct { Key string `json:"key,omitempty"` - Fields *Fields `json:"fields"` + Fields *Fields `json:"fields,omitempty"` + Transitions []*Transition `json:"transitions,omitempty"` + EditMeta *EditMeta `json:"editmeta,omitempty"` +} + + +var sprintRegexp = regexp.MustCompile(`name=([^,]+),`) +func (i *JiraIssue) UnmarshalJSON(b []byte) error { + tmp := map[string]json.RawMessage{} + if len(b) == 0 { + return nil + } + i.Fields = &Fields{} + i.EditMeta = &EditMeta{Fields:map[string]*FieldSpec{}} + err := json.Unmarshal(b, &tmp) + if err != nil && *Verbose { + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking raw json", err))) + } + err = json.Unmarshal(tmp["fields"], &i.Fields) + if err != nil && *Verbose { + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking fields", err))) + } + err = json.Unmarshal(tmp["transitions"], &i.Transitions) + if err != nil && *Verbose { + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking transitions", err))) + } + err = json.Unmarshal(tmp["editmeta"], &i.EditMeta) + if err != nil && *Verbose{ + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking EditMeta", err))) + } + err = json.Unmarshal(tmp["key"], &i.Key) + if err != nil && *Verbose { + fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking key", err))) + } + + i.Fields.ExtraFields = map[string]interface{}{} + for k, v := range i.Fields.rawExtraFields { + if f, ok := i.EditMeta.Fields[k]; ok { + if f.Schema.Custom == "com.pyxis.greenhopper.jira:gh-sprint" { + results := sprintRegexp.FindStringSubmatch(string(v)) + if len(results) == 2 { + i.Fields.ExtraFields[k] = results[1] + } + } else {switch f.Schema.Type { + case "user": + a := &Author{} + json.Unmarshal(v, &a) + i.Fields.ExtraFields[k] = a + case "option": + val := &AllowedValue{} + err = json.Unmarshal(v, &val) + if err != nil {panic(err)} + i.Fields.ExtraFields[k] = val + case "array": + if f.Schema.Items == "option" { + val := []*AllowedValue{} + err = json.Unmarshal(v, &val) + if err != nil {panic(err)} + i.Fields.ExtraFields[k] = val + continue + } + fallthrough + default: + if string(v) != "null" { + i.Fields.ExtraFields[k] = string(v) + } + } + } + } + } + + return nil } func (i *JiraIssue) URL() string { @@ -107,7 +329,26 @@ func (i *JiraIssue) String() string { return b.String() } -var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comment.Comments}}{{.Author.DisplayName}} ({{$k}}#{{.Id}}): +func (i *JiraIssue) PrintExtraFields() string{ + sorter := map[string]string{} + b := bytes.NewBuffer(nil) + for k, v := range i.Fields.ExtraFields { + if f, ok := i.EditMeta.Fields[k]; ok && v != nil { + sorter[f.Name] = fmt.Sprintf("%s: %s", f.Name, v) + } + } + keys := make([]string, 0, len(sorter)) + for k, _ := range sorter { + keys = append(keys, k) + } + sort.Strings(keys) + for _, v := range keys { + fmt.Fprintln(b, sorter[v]) + } + return b.String() +} + +var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comment.Comments}}{{.Author.DisplayName}} [~{{.Author.Name}}] ({{$k}}`+CommentIdSeparator+`{{.Id}}): ----------------- {{.Body}} ----------------- @@ -117,18 +358,28 @@ var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comm var issueTmplTxt = "\x1b[1m{{.Key}}\x1b[0m\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" + "\x1b[1mURL\x1b[0m: {{.URL}}\n\n" + "{{if .Fields.Status}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Status.Name}}\n{{end}}" + + "{{if .Fields.Priority}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Priority.Name}}\n{{end}}" + + "\x1b[1mTransitions\x1b[0m: {{range .Transitions}}[{{.Name}}] {{end}}\n"+ "{{if .Fields.Assignee}}\x1b[1mAssignee:\x1b[0m\t{{.Fields.Assignee.Name}}\n{{end}}\n" + "\x1b[1mTime Remaining/Original Estimate:\x1b[0m\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" + + "{{$i := .}}{{range $k, $v := .Fields.ExtraFields}}{{with index $i.EditMeta.Fields $k}}\x1b[1m{{.Name}}\x1b[0m{{end}}: {{$v}}\n{{end}}\n\n"+ "\x1b[1mDescription:\x1b[0m {{.Fields.Description}} \n\n" + - "\x1b[1mComments:\x1b[0m\n\n" + commentTemplate + "\x1b[1mIssue Links\x1b[0m: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" + + "\x1b[1mComments:\x1b[0m\n\n" + commentTemplate + + "Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}" var issueTmplNoColorTxt = "{{.Key}}\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" + "URL: {{.URL}}\n\n" + "{{if .Fields.Status}}Status:\t {{.Fields.Status.Name}}\n{{end}}" + + "Transitions: {{range .Transitions}}[{{.Name}}] {{end}}\n"+ "{{if .Fields.Assignee}}Assignee:\t{{.Fields.Assignee.Name}}\n{{end}}\n" + "Time Remaining/Original Estimate:\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" + + "{{.PrintExtraFields}}\n\n"+ "Description: {{.Fields.Description}} \n\n" + - "Comments:\n\n" + commentTemplate + "Issue Links: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" + + "Comments:\n\n" + commentTemplate+ + "Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}" +var CommentIdSeparator = "~" var issueTmpl = template.Must(template.New("issueTmpl").Parse(issueTmplTxt)) var issueTmplNoColor = template.Must(template.New("issueTmplNoColor").Parse(issueTmplNoColorTxt)) \ No newline at end of file diff --git a/jkl.go b/jkl.go index 05b88af..04715ce 100644 --- a/jkl.go +++ b/jkl.go @@ -24,22 +24,63 @@ func bootHttpClient() { } } -func Create(issue *JiraIssue) error { +func Create(issue *JiraIssue) (*JiraIssue, error) { bootHttpClient() payload, err := formatPayload(issue) if err != nil { - return err + return nil, err } // fmt.Println(issue) resp, err := httpClient.Post("api/2/issue", payload) if err != nil { fmt.Println(resp.StatusCode) - return err + return nil, err } if resp.StatusCode >= 400 { io.Copy(os.Stderr, resp.Body) + return nil, errors.New(fmt.Sprintf("HTTP error, %v", resp.StatusCode)) } - return nil + dec := json.NewDecoder(resp.Body) + issue = &JiraIssue{} + err = dec.Decode(issue) + if err != nil { + b, _ := ioutil.ReadAll(resp.Body) + fmt.Println(string(b)) + return nil, err + } + return issue, nil +} + +func GetCreateMeta(projectKey, issueType string) (*CreateMeta, error) { + bootHttpClient() + path := fmt.Sprintf("api/2/issue/createmeta?expand=projects.issuetypes.fields&issuetypeNames=%s&projectKeys=%s", strings.Title(strings.ToLower(issueType)), projectKey) + fmt.Println(path) + resp, err := httpClient.Get(path) + if err != nil { + fmt.Println(err) + return nil, err + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + fmt.Println("Status code:", resp.StatusCode) + fmt.Println("Response:") + fmt.Println(string(b)) + return nil, errors.New("Some http error happened.") + } + fmt.Println(string(b)) + dec := json.NewDecoder(bytes.NewBuffer(b)) + var createmeta = &CreateMeta{} + err = dec.Decode(createmeta) + if err != nil { + b, _ := ioutil.ReadAll(resp.Body) + fmt.Println(string(b)) + return nil, err + } + + return createmeta, nil } func Edit(issue *JiraIssue) error { @@ -93,10 +134,9 @@ func List(jql string) ([]*JiraIssue, error) { func GetIssue(taskKey string) (*JiraIssue, error) { bootHttpClient() - path := "api/2/issue/" + taskKey + path := "api/2/issue/" + taskKey+"?expand=transitions,operations,editmeta" resp, err := httpClient.Get(path) if err != nil { - fmt.Println(resp.StatusCode) return nil, err } dec := json.NewDecoder(resp.Body) @@ -127,16 +167,97 @@ func AddComment(taskKey string, comment string) error { return nil } +func GetComment(taskKey string, commentId string) (*Comment, error) { + bootHttpClient() + path := "api/2/issue/" + taskKey + "/comment/" + commentId + resp, err := httpClient.Get(path) + if err != nil { + fmt.Println(resp.StatusCode) + return nil, err + } + dec := json.NewDecoder(resp.Body) + var comment = &Comment{} + err = dec.Decode(comment) + if err != nil { + b, _ := ioutil.ReadAll(resp.Body) + fmt.Println(string(b)) + return nil, err + } + return comment, nil +} + +func EditComment(taskKey string, commentId string, comment *Comment) error { + bootHttpClient() + payload, err := serializePayload(comment) + if err != nil { + return err + } + resp, err := httpClient.Put("api/2/issue/"+taskKey+"/comment/"+commentId, payload) + if err != nil { + fmt.Println(resp.StatusCode) + return err + } + if resp.StatusCode >= 400 { + io.Copy(os.Stderr, resp.Body) + } + return nil +} + +func DoTransition(taskKey string, transitionName string) error { + iss, err := GetIssue(taskKey) + if err != nil { + return err + } + var t *Transition + fmt.Println(iss.Transitions) + for _, transition := range iss.Transitions { + if strings.ToLower(transition.Name) == strings.ToLower(transitionName) { + t = transition + break + } + } + if t == nil { + return errors.New("Transition not found") + } + payload, err := serializePayload(map[string]interface{}{"transition": t}) + resp, err := httpClient.Post("api/2/issue/"+taskKey+"/transitions/", payload) + if err != nil { + fmt.Println(resp.StatusCode) + return err + } + if resp.StatusCode >= 400 { + io.Copy(os.Stderr, resp.Body) + } + return nil +} + +func LogWork(taskKey string, workAmount string) error { + payload, err := serializePayload(map[string]interface{}{"timeSpent": workAmount}) + resp, err := httpClient.Post("api/2/issue/"+taskKey+"/worklog", payload) + if err != nil { + fmt.Println(resp.StatusCode) + return err + } + if resp.StatusCode >= 400 { + io.Copy(os.Stderr, resp.Body) + } + return nil +} + func formatPayload(issue *JiraIssue) (io.Reader, error) { if issue.Fields != nil && issue.Fields.Project != nil && issue.Fields.Project.Key == "" { issue.Fields.Project.Key = os.Getenv("JIRA_PROJECT") } + return serializePayload(issue) +} + +func serializePayload(i interface{}) (io.Reader, error) { var b []byte payload := bytes.NewBuffer(b) enc := json.NewEncoder(payload) - err := enc.Encode(issue) + err := enc.Encode(i) fmt.Println(payload.String()) if err != nil { fmt.Println(err)