Repository: ankitpokhrel/jira-cli
Pull request #64 (2021-01-16 20:17:10)

feat(cmd): Show available issue types in dropdown during creation
Updates config. Re-run `jira init`.


Patch:
diff --git a/internal/cmd/issue/create/create.go b/internal/cmd/issue/create/create.go
index d836257..55e0e60 100644
--- a/internal/cmd/issue/create/create.go
+++ b/internal/cmd/issue/create/create.go
@@ -40,9 +40,16 @@ func create(cmd *cobra.Command, _ []string) {
 	server := viper.GetString("server")
 	project := viper.GetString("project")
 
-	flags := parseFlags(cmd.Flags())
-	qs := getQuestions(flags)
+	params := parseFlags(cmd.Flags())
+	client := api.Client(jira.Config{Debug: params.debug})
+	cc := createCmd{
+		client: client,
+		params: params,
+	}
+
+	cmdutil.ExitIfError(cc.setIssueTypes())
 
+	qs := cc.getQuestions()
 	if len(qs) > 0 {
 		ans := struct {
 			IssueType string
@@ -53,14 +60,14 @@ func create(cmd *cobra.Command, _ []string) {
 		err := survey.Ask(qs, &ans)
 		cmdutil.ExitIfError(err)
 
-		if flags.issueType == "" {
-			flags.issueType = ans.IssueType
+		if params.issueType == "" {
+			params.issueType = ans.IssueType
 		}
-		if flags.summary == "" {
-			flags.summary = ans.Summary
+		if params.summary == "" {
+			params.summary = ans.Summary
 		}
-		if flags.body == "" {
-			flags.body = ans.Body
+		if params.body == "" {
+			params.body = ans.Body
 		}
 	}
 
@@ -68,13 +75,13 @@ func create(cmd *cobra.Command, _ []string) {
 		s := cmdutil.Info("Creating an issue...")
 		defer s.Stop()
 
-		resp, err := api.Client(jira.Config{Debug: flags.debug}).Create(&jira.CreateRequest{
+		resp, err := client.Create(&jira.CreateRequest{
 			Project:   project,
-			IssueType: flags.issueType,
-			Summary:   flags.summary,
-			Body:      flags.body,
-			Priority:  flags.priority,
-			Labels:    flags.labels,
+			IssueType: params.issueType,
+			Summary:   params.summary,
+			Body:      params.body,
+			Priority:  params.priority,
+			Labels:    params.labels,
 		})
 		cmdutil.ExitIfError(err)
 
@@ -102,24 +109,61 @@ func SetFlags(cmd *cobra.Command) {
 	cmd.Flags().Bool("no-input", false, "Disable prompt for non-required fields")
 }
 
-func getQuestions(params *createParams) []*survey.Question {
+type createCmd struct {
+	client     *jira.Client
+	issueTypes []*jira.IssueType
+	params     *createParams
+}
+
+func (cc *createCmd) setIssueTypes() error {
+	issueTypes := make([]*jira.IssueType, 0)
+	availableTypes, ok := viper.Get("issue.types").([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid issue types in config")
+	}
+	for _, at := range availableTypes {
+		tp := at.(map[interface{}]interface{})
+		st := tp["subtask"].(bool)
+		if st {
+			continue
+		}
+		issueTypes = append(issueTypes, &jira.IssueType{
+			ID:      tp["id"].(string),
+			Name:    tp["name"].(string),
+			Subtask: st,
+		})
+	}
+	cc.issueTypes = issueTypes
+
+	return nil
+}
+
+func (cc *createCmd) getQuestions() []*survey.Question {
 	var qs []*survey.Question
 
-	if params.issueType == "" {
+	if cc.params.issueType == "" {
+		var options []string
+		for _, t := range cc.issueTypes {
+			options = append(options, t.Name)
+		}
+
 		qs = append(qs, &survey.Question{
-			Name:     "issueType",
-			Prompt:   &survey.Input{Message: "Issue type"},
+			Name: "issueType",
+			Prompt: &survey.Select{
+				Message: "Issue type:",
+				Options: options,
+			},
 			Validate: survey.Required,
 		})
 	}
-	if params.summary == "" {
+	if cc.params.summary == "" {
 		qs = append(qs, &survey.Question{
 			Name:     "summary",
 			Prompt:   &survey.Input{Message: "Summary"},
 			Validate: survey.Required,
 		})
 	}
-	if !params.noInput && params.body == "" {
+	if !cc.params.noInput && cc.params.body == "" {
 		qs = append(qs, &survey.Question{
 			Name: "body",
 			Prompt: &surveyext.JiraEditor{
diff --git a/internal/cmd/issue/move/move.go b/internal/cmd/issue/move/move.go
index e38a235..818e643 100644
--- a/internal/cmd/issue/move/move.go
+++ b/internal/cmd/issue/move/move.go
@@ -35,7 +35,7 @@ STATE		State you want to transition the issue to`,
 		Run: move,
 	}
 
-	cmd.Flags().Bool("web", false, "Open issue in web browser after successful transistion")
+	cmd.Flags().Bool("web", false, "Open issue in web browser after successful transition")
 
 	return &cmd
 }
diff --git a/internal/config/generator.go b/internal/config/generator.go
index 147ea5b..93f72d5 100644
--- a/internal/config/generator.go
+++ b/internal/config/generator.go
@@ -31,11 +31,12 @@ var (
 // JiraCLIConfig is a Jira CLI config.
 type JiraCLIConfig struct {
 	value struct {
-		server  string
-		login   string
-		project string
-		board   *jira.Board
-		epic    *jira.Epic
+		server     string
+		login      string
+		project    string
+		board      *jira.Board
+		epic       *jira.Epic
+		issueTypes []*jira.IssueType
 	}
 	jiraClient         *jira.Client
 	projectSuggestions []string
@@ -209,13 +210,12 @@ func (c *JiraCLIConfig) configureProjectAndBoardDetails() error {
 }
 
 func (c *JiraCLIConfig) configureMetadata() error {
-	s := cmdutil.Info("Fetching metadata...")
+	s := cmdutil.Info("Configuring metadata. Please wait...")
 	defer s.Stop()
 
 	meta, err := c.jiraClient.GetCreateMeta(&jira.CreateMetaRequest{
-		Projects:       c.value.project,
-		IssueTypeNames: jira.IssueTypeEpic,
-		Expand:         "projects.issuetypes.fields",
+		Projects: c.value.project,
+		Expand:   "projects.issuetypes.fields",
 	})
 	if err != nil {
 		return err
@@ -224,19 +224,34 @@ func (c *JiraCLIConfig) configureMetadata() error {
 		return ErrUnexpectedResponseFormat
 	}
 
-	var key string
-	fields := meta.Projects[0].IssueTypes[0].Fields
-	for field, value := range fields {
+	var (
+		epicMeta map[string]interface{}
+		epicKey  string
+	)
+	issueTypes := make([]*jira.IssueType, 0, len(meta.Projects[0].IssueTypes))
+	for _, it := range meta.Projects[0].IssueTypes {
+		if it.Name == jira.IssueTypeEpic {
+			epicMeta = it.Fields
+		}
+		issueTypes = append(issueTypes, &jira.IssueType{
+			ID:      it.ID,
+			Name:    it.Name,
+			Subtask: it.Subtask,
+		})
+	}
+	c.value.issueTypes = issueTypes
+
+	for field, value := range epicMeta {
 		if !strings.Contains(field, "customfield") {
 			continue
 		}
 		v := value.(map[string]interface{})
 		if v["name"].(string) == "Epic Name" {
-			key = v["key"].(string)
+			epicKey = v["key"].(string)
 			break
 		}
 	}
-	c.value.epic = &jira.Epic{Field: key}
+	c.value.epic = &jira.Epic{Field: epicKey}
 
 	return nil
 }
@@ -245,10 +260,9 @@ func (c *JiraCLIConfig) write() error {
 	viper.Set("server", c.value.server)
 	viper.Set("login", c.value.login)
 	viper.Set("project", c.value.project)
-	viper.Set("board.id", c.value.board.ID)
-	viper.Set("board.name", c.value.board.Name)
-	viper.Set("board.type", c.value.board.Type)
-	viper.Set("epic.field", c.value.epic.Field)
+	viper.Set("board", c.value.board)
+	viper.Set("epic", c.value.epic)
+	viper.Set("issue.types", c.value.issueTypes)
 
 	return viper.WriteConfig()
 }
diff --git a/pkg/jira/createmeta.go b/pkg/jira/createmeta.go
index a475858..1f4bb96 100644
--- a/pkg/jira/createmeta.go
+++ b/pkg/jira/createmeta.go
@@ -14,24 +14,30 @@ type CreateMetaRequest struct {
 	Expand         string
 }
 
-// CreateMetaResponse struct holds response from POST /issue/createmeta endpoint.
+// CreateMetaResponse struct holds response from GET /issue/createmeta endpoint.
 type CreateMetaResponse struct {
 	Projects []struct {
-		Key        string `json:"key"`
-		Name       string `json:"name"`
-		IssueTypes []struct {
-			Name   string                 `json:"name"`
-			Fields map[string]interface{} `json:"fields"`
-		} `json:"issuetypes"`
+		Key        string                 `json:"key"`
+		Name       string                 `json:"name"`
+		IssueTypes []*CreateMetaIssueType `json:"issuetypes"`
 	} `json:"projects"`
 }
 
+// CreateMetaIssueType struct holds issue types from GET /issue/createmeta endpoint.
+type CreateMetaIssueType struct {
+	IssueType
+	Fields map[string]interface{} `json:"fields"`
+}
+
 // GetCreateMeta gets create metadata using GET /issue/createmeta endpoint.
 func (c *Client) GetCreateMeta(req *CreateMetaRequest) (*CreateMetaResponse, error) {
 	path := fmt.Sprintf(
-		"/issue/createmeta?projectKeys=%s&issuetypeNames=%s&expand=%s",
-		req.Projects, req.IssueTypeNames, req.Expand,
+		"/issue/createmeta?projectKeys=%s&expand=%s",
+		req.Projects, req.Expand,
 	)
+	if req.IssueTypeNames != "" {
+		path += fmt.Sprintf("&issuetypeNames=%s", req.IssueTypeNames)
+	}
 
 	res, err := c.Get(context.Background(), path, nil)
 	if err != nil {
diff --git a/pkg/jira/types.go b/pkg/jira/types.go
index 014d4e9..7006be7 100644
--- a/pkg/jira/types.go
+++ b/pkg/jira/types.go
@@ -65,6 +65,13 @@ type IssueFields struct {
 	Updated string `json:"updated"`
 }
 
+// IssueType holds issue type info.
+type IssueType struct {
+	ID      string `json:"id"`
+	Name    string `json:"name"`
+	Subtask bool   `json:"subtask"`
+}
+
 // Sprint holds sprint info.
 type Sprint struct {
 	ID           int    `json:"id"`


Test patch:
diff --git a/pkg/jira/createmeta_test.go b/pkg/jira/createmeta_test.go
index 032b444..20116e5 100644
--- a/pkg/jira/createmeta_test.go
+++ b/pkg/jira/createmeta_test.go
@@ -47,22 +47,20 @@ func TestGetCreateMeta(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := &CreateMetaResponse{[]struct {
-		Key        string `json:"key"`
-		Name       string `json:"name"`
-		IssueTypes []struct {
-			Name   string                 `json:"name"`
-			Fields map[string]interface{} `json:"fields"`
-		} `json:"issuetypes"`
+		Key        string                 `json:"key"`
+		Name       string                 `json:"name"`
+		IssueTypes []*CreateMetaIssueType `json:"issuetypes"`
 	}{
 		{
 			Key:  "TEST",
 			Name: "Test Project",
-			IssueTypes: []struct {
-				Name   string                 `json:"name"`
-				Fields map[string]interface{} `json:"fields"`
-			}{
+			IssueTypes: []*CreateMetaIssueType{
 				{
-					Name: "Epic",
+					IssueType: IssueType{
+						ID:      "10001",
+						Name:    "Epic",
+						Subtask: false,
+					},
 					Fields: map[string]interface{}{
 						"customfield_10011": map[string]interface{}{
 							"name": "Epic Name",
diff --git a/pkg/jira/testdata/createmeta.json b/pkg/jira/testdata/createmeta.json
index 9bd27d6..daeb1f0 100644
--- a/pkg/jira/testdata/createmeta.json
+++ b/pkg/jira/testdata/createmeta.json
@@ -7,7 +7,9 @@
       "name": "Test Project",
       "issuetypes": [
         {
+          "id": "10001",
           "name": "Epic",
+          "subtask": false,
           "fields": {
             "customfield_10011": {
               "name": "Epic Name",