diff --git a/internal/cmd/config/category.go b/internal/cmd/config/category.go new file mode 100644 index 0000000..8a42e33 --- /dev/null +++ b/internal/cmd/config/category.go @@ -0,0 +1,85 @@ +package config + +import ( + "strconv" + + "github.com/MrJeffLarry/redmine-cli/internal/config" + "github.com/MrJeffLarry/redmine-cli/internal/print" + "github.com/spf13/cobra" +) + +func getCategoryID(r *config.Red_t, cmd *cobra.Command) (int, error) { + var id int + var err error + + if id, err = strconv.Atoi(cmd.Flags().Arg(0)); err != nil { + print.Error("ID is not an valid number, please use `category set 1` for category id 1") + return 0, err + } + return id, nil +} + +func setLocalCategory(r *config.Red_t) *cobra.Command { + cmd := &cobra.Command{ + Use: "set [id]", + Short: "set local category", + Long: "set a local category under .red/config.json", + Run: func(cmd *cobra.Command, args []string) { + var id int + var err error + if id, err = getCategoryID(r, cmd); err != nil { + return + } + + if err := r.SaveLocalCategory(id); err != nil { + print.Error("%s [%s]", "Could not save category, please verify permissions", err) + return + } + print.OK("Category locally set to ID #%d", id) + }, + } + return cmd +} + +func setGlobalCategory(r *config.Red_t) *cobra.Command { + cmd := &cobra.Command{ + Use: "set [id]", + Short: "set global category", + Long: "set a global category under ~/.red/config.json", + Run: func(cmd *cobra.Command, args []string) { + var id int + var err error + if id, err = getCategoryID(r, cmd); err != nil { + return + } + + r.SetCategoryID(id) + if err := r.Save(); err != nil { + print.Error("%s [%s]", "Could not save category, please verify permissions", err) + return + } + print.OK("Category globally set to ID #%d", id) + }, + } + return cmd +} + +func localCategory(r *config.Red_t) *cobra.Command { + cmd := &cobra.Command{ + Use: "category", + Short: "set or get local category", + Long: "set or get a local category under ~/.red/config.json", + } + cmd.AddCommand(setLocalCategory(r)) + return cmd +} + +func globalCategory(r *config.Red_t) *cobra.Command { + cmd := &cobra.Command{ + Use: "category", + Short: "set or get global category", + Long: "set or get a global category under ~/.red/config.json", + } + cmd.AddCommand(setGlobalCategory(r)) + return cmd +} diff --git a/internal/cmd/global/helpers.go b/internal/cmd/global/helpers.go index 2149595..818bcb4 100644 --- a/internal/cmd/global/helpers.go +++ b/internal/cmd/global/helpers.go @@ -3,6 +3,7 @@ package global import ( "encoding/json" "errors" + "strconv" "github.com/MrJeffLarry/redmine-cli/internal/api" "github.com/MrJeffLarry/redmine-cli/internal/config" @@ -10,6 +11,33 @@ import ( "github.com/MrJeffLarry/redmine-cli/internal/util" ) +func GetCategories(r *config.Red_t, projectID int) ([]util.IdName, error) { + var payload issueCategories + var idNames []util.IdName + + body, status, err := api.ClientGET(r, "/projects/"+strconv.Itoa(projectID)+"/issue_categories.json") + + print.Debug(r, "%d %s", status, string(body)) + + if err != nil { + return idNames, err + } + + if err := json.Unmarshal(body, &payload); err != nil { + print.Debug(r, err.Error()) + return idNames, errors.New("Could not parse and read response from server") + } + + for _, v := range payload.IssueCategories { + idNames = append(idNames, util.IdName{ + ID: v.ID, + Name: v.Name, + }) + } + + return idNames, nil +} + func GetPriorities(r *config.Red_t) ([]util.IdName, error) { var payload issuePriorities var idNames []util.IdName diff --git a/internal/cmd/global/structs.go b/internal/cmd/global/structs.go index 7de7838..a66b7e9 100644 --- a/internal/cmd/global/structs.go +++ b/internal/cmd/global/structs.go @@ -1,5 +1,20 @@ package global +import ( + "github.com/MrJeffLarry/redmine-cli/internal/util" +) + +type issueCategory struct { + ID int `json:"id"` + Name string `json:"name"` + Project util.IdName `json:"project"` + AssignedTo util.IdName `json:"assignedTo"` +} + +type issueCategories struct { + IssueCategories []issueCategory `json:"issue_categories"` +} + type issuePriorities struct { IssuePriorities []issuePriority `json:"issue_priorities"` } diff --git a/internal/cmd/issue/create.go b/internal/cmd/issue/create.go index 994593e..590eb13 100644 --- a/internal/cmd/issue/create.go +++ b/internal/cmd/issue/create.go @@ -59,6 +59,7 @@ func displayCreateIssue(r *config.Red_t, cmd *cobra.Command, path string) { chooses := []string{ FIELD_SAVE, FIELD_PRIORITY, + FIELD_CATEGORY, FIELD_TARGET_VERSION, FIELD_PARENT_ID, FIELD_ASSIGN, @@ -119,6 +120,16 @@ func displayCreateIssue(r *config.Red_t, cmd *cobra.Command, path string) { if id >= 0 { issue.Issue.PriorityID = id } + case FIELD_CATEGORY: + if idNames, err = global.GetCategories(r, projectID); err != nil { + print.Error(err.Error()) + } + + id, _ := r.Term.Choose("Category", idNames) + + if id >= 0 { + issue.Issue.CategoryID = id + } case FIELD_TARGET_VERSION: if idNames, err = project.GetVersions(r, projectID); err != nil { print.Error(err.Error()) diff --git a/internal/cmd/issue/edit.go b/internal/cmd/issue/edit.go index 61a5cf4..b8c0dd3 100644 --- a/internal/cmd/issue/edit.go +++ b/internal/cmd/issue/edit.go @@ -46,7 +46,7 @@ func cmdIssueEditIssueAssign(r *config.Red_t, projectID int) (util.IdName, error idName.ID, idName.Name = r.Term.Choose("Assign", idNames) if idName.ID < 0 { - return idName, errors.New("assigne ID not valid") + return idName, errors.New("assignee ID not valid") } return idName, nil @@ -82,6 +82,19 @@ func cmdIssueEditIssueStatus(r *config.Red_t, allowedStatus []global.IssueStatus return issueStatus, nil } +func cmdIssueEditIssueCategory(r *config.Red_t, projectID int) (util.IdName, error) { + var err error + var idName util.IdName + var idNames []util.IdName + + if idNames, err = global.GetCategories(r, projectID); err != nil { + return idName, err + } + + idName.ID, idName.Name = r.Term.Choose("Choose Category", idNames) + return idName, nil +} + func cmdIssueEditIssuePriority(r *config.Red_t) (util.IdName, error) { var err error var idName util.IdName @@ -148,6 +161,7 @@ func cmdIssueEditIssue(r *config.Red_t, cmd *cobra.Command, id, path string) { FIELD_SAVE, FIELD_SUBJECT, FIELD_DESCRIPTION, + FIELD_CATEGORY, FIELD_STATUS, FIELD_PRIORITY, FIELD_TRACKER, @@ -231,6 +245,15 @@ func cmdIssueEditIssue(r *config.Red_t, cmd *cobra.Command, id, path string) { case FIELD_DESCRIPTION: issue.Issue.Description = editor.StartEdit(r.Config.Editor, viewIssue.Issue.Description) viewIssue.Issue.Description = issue.Issue.Description + case FIELD_CATEGORY: + idName, err := cmdIssueEditIssueCategory(r, viewIssue.Issue.Project.ID) + + if err != nil { + print.Error(err.Error()) + } else { + issue.Issue.CategoryID = idName.ID + viewIssue.Issue.Category = idName + } case FIELD_NOTE: if err = cmdIssueEditIssueNote(r, &issue); err != nil { print.Error(err.Error()) diff --git a/internal/cmd/issue/structs.go b/internal/cmd/issue/structs.go index 08f972f..710a027 100644 --- a/internal/cmd/issue/structs.go +++ b/internal/cmd/issue/structs.go @@ -8,6 +8,7 @@ import ( const ( FIELD_SUBJECT = "Subject" FIELD_DESCRIPTION = "Description" + FIELD_CATEGORY = "Category" FIELD_STATUS = "Status" FIELD_PRIORITY = "Priority" FIELD_TRACKER = "Tracker" @@ -55,6 +56,7 @@ type newIssue struct { type issue struct { ID int `json:"id,omitempty"` Project util.IdName `json:"project,omitempty"` + Category util.IdName `json:"category,omitempty"` Tracker util.IdName `json:"tracker,omitempty"` Status global.IssueStatus `json:"status,omitempty"` Priority util.IdName `json:"priority,omitempty"` diff --git a/internal/cmd/issue/view.go b/internal/cmd/issue/view.go index d0cdac5..fce4863 100644 --- a/internal/cmd/issue/view.go +++ b/internal/cmd/issue/view.go @@ -50,6 +50,7 @@ func displayIssue(r *config.Red_t, i issue, journalFlag bool) { text.FgGreen.Sprint("Version")+" %s\n"+ text.FgGreen.Sprint("Status")+" %s\n"+ text.FgGreen.Sprint("Priority")+" %s\n"+ + text.FgGreen.Sprint("Category")+" %s\n"+ text.FgGreen.Sprint("Description")+"\n"+ "\n%s\n\n", text.FgYellow.Sprint(i.Tracker.Name), @@ -67,6 +68,7 @@ func displayIssue(r *config.Red_t, i issue, journalFlag bool) { i.FixedVersion.Name, i.Status.Name, i.Priority.Name, + i.Category.Name, i.Description, ) diff --git a/internal/config/config.go b/internal/config/config.go index dcacce7..b983643 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,8 @@ const ( RED_CONFIG_REDMINE_API_KEY = "RED_CONFIG_REDMINE_API_KEY" RED_CONFIG_REDMINE_PROJECT = "RED_CONFIG_REDMINE_PROJECT" RED_CONFIG_REDMINE_PROJECT_ID = "RED_CONFIG_REDMINE_PROJECT_ID" + RED_CONFIG_REDMINE_CATEGORY = "RED_CONFIG_REDMINE_CATEGORY" + RED_CONFIG_REDMINE_CATEGORY_ID = "RED_CONFIG_REDMINE_CATEGORY_ID" RED_CONFIG_REDMINE_USER_ID = "RED_CONFIG_REDMINE_USER_ID" RED_CONFIG_EDITOR = "RED_CONFIG_EDITOR" RED_CONFIG_PAGER = "RED_CONFIG_PAGER" @@ -29,6 +31,8 @@ const ( CONFIG_REDMINE_API_KEY = "api-key" CONFIG_REDMINE_PROJECT = "project" CONFIG_REDMINE_PROJECT_ID = "project-id" + CONFIG_REDMINE_CATEGORY = "category" + CONFIG_REDMINE_CATEGORY_ID = "category-id" CONFIG_REDMINE_USER_ID = "user-id" CONFIG_EDITOR = "editor" CONFIG_PAGER = "pager" @@ -52,6 +56,8 @@ type Config_t struct { ApiKey string `mapstructure:"api-key"` Project string `json:"project"` ProjectID int `mapstructure:"project-id"` + Category string `json:"category"` + CategoryID int `mapstructure:"category-id"` UserID int `mapstructure:"user-id"` Editor string `json:"editor"` Pager string `json:"pager"` @@ -103,6 +109,14 @@ func (r *Red_t) SetProjectID(id int) { r.Config.ProjectID = id } +func (r *Red_t) SetCategory(id string) { + r.Config.Category = id +} + +func (r *Red_t) SetCategoryID(id int) { + r.Config.CategoryID = id +} + func (r *Red_t) SetUserID(id int) { r.Config.UserID = id } @@ -121,6 +135,8 @@ func (r *Red_t) ClearAll() { r.Config.UserID = 0 r.Config.Project = "" r.Config.ProjectID = 0 + r.Config.Category = "" + r.Config.CategoryID = 0 r.Config.Editor = "" r.Config.Pager = "" } @@ -238,6 +254,8 @@ func (r *Red_t) Save() error { viper.Set(CONFIG_REDMINE_API_KEY, r.Config.ApiKey) viper.Set(CONFIG_REDMINE_PROJECT, r.Config.Project) viper.Set(CONFIG_REDMINE_PROJECT_ID, r.Config.ProjectID) + viper.Set(CONFIG_REDMINE_CATEGORY, r.Config.Category) + viper.Set(CONFIG_REDMINE_CATEGORY_ID, r.Config.CategoryID) viper.Set(CONFIG_REDMINE_USER_ID, r.Config.UserID) viper.Set(CONFIG_EDITOR, r.Config.Editor) viper.Set(CONFIG_PAGER, r.Config.Pager) @@ -258,6 +276,10 @@ func (r *Red_t) SaveLocalProject(projectID int) error { return saveLocal(r, CONFIG_REDMINE_PROJECT_ID, projectID) } +func (r *Red_t) SaveLocalCategory(categoryID int) error { + return saveLocal(r, CONFIG_REDMINE_CATEGORY_ID, categoryID) +} + func (r *Red_t) LoadConfig() error { sep := string(os.PathSeparator) @@ -327,6 +349,12 @@ func (r *Red_t) localConfig() error { if c.ProjectID > 0 { r.Config.ProjectID = c.ProjectID } + if len(c.Category) > 0 { + r.Config.Category = c.Category + } + if c.CategoryID > 0 { + r.Config.CategoryID = c.CategoryID + } if c.UserID > 0 { r.Config.UserID = c.UserID } @@ -343,6 +371,8 @@ func InitConfig() *Red_t { red.Config.ApiKey = exEnv(RED_CONFIG_REDMINE_API_KEY, "") red.Config.Project = exEnv(RED_CONFIG_REDMINE_PROJECT, "") red.Config.ProjectID, _ = strconv.Atoi(exEnv(RED_CONFIG_REDMINE_PROJECT_ID, "")) + red.Config.Category = exEnv(RED_CONFIG_REDMINE_CATEGORY, "") + red.Config.CategoryID, _ = strconv.Atoi(exEnv(RED_CONFIG_REDMINE_CATEGORY_ID, "")) red.Config.UserID, _ = strconv.Atoi(exEnv(RED_CONFIG_REDMINE_USER_ID, "")) red.Config.Editor = exEnv(RED_CONFIG_EDITOR, "") red.Config.Pager = exEnv(RED_CONFIG_PAGER, "")