Repository: ankitpokhrel/jira-cli
Pull request #234 (2021-12-27 21:49:09)

fix: Language support for Jira cloud
Relates to #231



Patch:
diff --git a/README.md b/README.md
index 4066922..3be44b3 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,11 @@ See [releases page](https://github.com/ankitpokhrel/jira-cli/releases) for more
 2. Run `jira init`, select installation type as `Local`, and provide required details to generate a config file required
    for the tool.
 
+   **Note:** If your on-premise Jira installation is using a language other than `English`, then the issue/epic creation
+   may not work because the older version of Jira API doesn't return untranslated name for `issuetypes`. In that case,
+   you will have to fill in `epic.name`, `epic.link` and `issue.types.*.handle` fields manually in the generated config
+   to get the expected behavior.
+
 #### Shell completion
 Check `jira completion --help` for more info on setting up a bash/zsh shell completion.
 
diff --git a/internal/cmd/issue/create/create.go b/internal/cmd/issue/create/create.go
index 32d13c5..78c9052 100644
--- a/internal/cmd/issue/create/create.go
+++ b/internal/cmd/issue/create/create.go
@@ -167,12 +167,14 @@ func (cc *createCmd) setIssueTypes() error {
 	for _, at := range availableTypes {
 		tp := at.(map[interface{}]interface{})
 		name := tp["name"].(string)
-		if name == jira.IssueTypeEpic {
+		handle := tp["handle"].(string)
+		if handle == jira.IssueTypeEpic || name == jira.IssueTypeEpic {
 			continue
 		}
 		issueTypes = append(issueTypes, &jira.IssueType{
 			ID:      tp["id"].(string),
 			Name:    name,
+			Handle:  handle,
 			Subtask: tp["subtask"].(bool),
 		})
 	}
@@ -187,7 +189,11 @@ func (cc *createCmd) getIssueType() *survey.Question {
 	if cc.params.issueType == "" {
 		var options []string
 		for _, t := range cc.issueTypes {
-			options = append(options, t.Name)
+			if t.Handle != "" && t.Handle != t.Name {
+				options = append(options, fmt.Sprintf("%s (%s)", t.Name, t.Handle))
+			} else {
+				options = append(options, t.Name)
+			}
 		}
 
 		qs = &survey.Question{
@@ -213,7 +219,13 @@ func (cc *createCmd) askQuestions() error {
 		}
 
 		if cc.params.issueType == "" {
-			cc.params.issueType = ans.IssueType
+			for _, t := range cc.issueTypes {
+				if t.Handle != "" && fmt.Sprintf("%s (%s)", t.Name, t.Handle) == ans.IssueType {
+					cc.params.issueType = t.Handle
+				} else if t.Name == ans.IssueType {
+					cc.params.issueType = t.Name
+				}
+			}
 		}
 	}
 
@@ -251,7 +263,7 @@ func (cc *createCmd) getRemainingQuestions() []*survey.Question {
 
 	if cc.params.parentIssueKey == "" {
 		for _, t := range cc.issueTypes {
-			if t.Name == cc.params.issueType && t.Subtask {
+			if t.Subtask && (t.Name == cc.params.issueType || (t.Handle != "" && t.Handle == cc.params.issueType)) {
 				qs = append(qs, &survey.Question{
 					Name:     "parentIssueKey",
 					Prompt:   &survey.Input{Message: "Parent issue key"},
diff --git a/internal/config/generator.go b/internal/config/generator.go
index bf367f2..41a0d7b 100644
--- a/internal/config/generator.go
+++ b/internal/config/generator.go
@@ -288,12 +288,13 @@ func (c *JiraCLIConfig) configureMetadata() error {
 	)
 
 	for _, it := range meta.Projects[0].IssueTypes {
-		if it.Name == jira.IssueTypeEpic {
+		if it.Handle == jira.IssueTypeEpic || it.Name == jira.IssueTypeEpic {
 			epicMeta = it.Fields
 		}
 		issueTypes = append(issueTypes, &jira.IssueType{
 			ID:      it.ID,
 			Name:    it.Name,
+			Handle:  it.Handle,
 			Subtask: it.Subtask,
 		})
 	}
diff --git a/pkg/jira/types.go b/pkg/jira/types.go
index 2e7f588..26d1757 100644
--- a/pkg/jira/types.go
+++ b/pkg/jira/types.go
@@ -41,10 +41,8 @@ type IssueFields struct {
 	Resolution  struct {
 		Name string `json:"name"`
 	} `json:"resolution"`
-	IssueType struct {
-		Name string `json:"name"`
-	} `json:"issueType"`
-	Assignee struct {
+	IssueType IssueType `json:"issueType"`
+	Assignee  struct {
 		Name string `json:"displayName"`
 	} `json:"assignee"`
 	Priority struct {
@@ -89,6 +87,7 @@ type IssueFields struct {
 type IssueType struct {
 	ID      string `json:"id"`
 	Name    string `json:"name"`
+	Handle  string `json:"untranslatedName,omitempty"` // This field may not exist in older version of the API.
 	Subtask bool   `json:"subtask"`
 }
 


Test patch:
diff --git a/internal/view/epic_test.go b/internal/view/epic_test.go
index ef64d2b..8b65236 100644
--- a/internal/view/epic_test.go
+++ b/internal/view/epic_test.go
@@ -17,9 +17,7 @@ func TestEpicData(t *testing.T) {
 			Resolution: struct {
 				Name string `json:"name"`
 			}{Name: "Fixed"},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Epic"},
+			IssueType: jira.IssueType{Name: "Epic"},
 			Assignee: struct {
 				Name string `json:"displayName"`
 			}{Name: "Person A"},
@@ -39,10 +37,8 @@ func TestEpicData(t *testing.T) {
 	epic2 := jira.Issue{
 		Key: "TEST-2",
 		Fields: jira.IssueFields{
-			Summary: "[EPIC] This is another test",
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Epic"},
+			Summary:   "[EPIC] This is another test",
+			IssueType: jira.IssueType{Name: "Epic"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Normal"},
@@ -64,9 +60,7 @@ func TestEpicData(t *testing.T) {
 			Resolution: struct {
 				Name string `json:"name"`
 			}{Name: "Fixed"},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType: jira.IssueType{Name: "Bug"},
 			Assignee: struct {
 				Name string `json:"displayName"`
 			}{Name: "Person A"},
@@ -86,10 +80,8 @@ func TestEpicData(t *testing.T) {
 	issue2 := jira.Issue{
 		Key: "ISSUE-2",
 		Fields: jira.IssueFields{
-			Summary: "This is another issue",
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Story"},
+			Summary:   "This is another issue",
+			IssueType: jira.IssueType{Name: "Story"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Normal"},
diff --git a/internal/view/issue_test.go b/internal/view/issue_test.go
index eda16d3..c05a62b 100644
--- a/internal/view/issue_test.go
+++ b/internal/view/issue_test.go
@@ -35,9 +35,7 @@ func TestIssueDetailsRenderInPlainView(t *testing.T) {
 					},
 				},
 			},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType: jira.IssueType{Name: "Bug"},
 			Assignee: struct {
 				Name string `json:"displayName"`
 			}{Name: "Person A"},
@@ -102,9 +100,7 @@ func TestIssueDetailsWithV2Description(t *testing.T) {
 				Name string `json:"name"`
 			}{Name: "Fixed"},
 			Description: "h1. Title\nh2. Subtitle\n\nThis is a *bold* and _italic_ text with [a link|https://ankit.pl] in between.",
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType:   jira.IssueType{Name: "Bug"},
 			Assignee: struct {
 				Name string `json:"displayName"`
 			}{Name: "Person A"},
@@ -159,9 +155,9 @@ func TestIssueDetailsWithV2Description(t *testing.T) {
 					InwardIssue: &jira.Issue{
 						Key: "TEST-2",
 						Fields: jira.IssueFields{
-							Summary: "Something is broken", IssueType: struct {
-								Name string `json:"name"`
-							}{Name: "Bug"}, Priority: struct {
+							Summary:   "Something is broken",
+							IssueType: jira.IssueType{Name: "Bug"},
+							Priority: struct {
 								Name string `json:"name"`
 							}{Name: "High"}, Status: struct {
 								Name string `json:"name"`
@@ -178,9 +174,9 @@ func TestIssueDetailsWithV2Description(t *testing.T) {
 					OutwardIssue: &jira.Issue{
 						Key: "TEST-3",
 						Fields: jira.IssueFields{
-							Summary: "Everything is on fire", IssueType: struct {
-								Name string `json:"name"`
-							}{Name: "Bug"}, Priority: struct {
+							Summary:   "Everything is on fire",
+							IssueType: jira.IssueType{Name: "Bug"},
+							Priority: struct {
 								Name string `json:"name"`
 							}{Name: "Urgent"}, Status: struct {
 								Name string `json:"name"`
diff --git a/internal/view/issues_test.go b/internal/view/issues_test.go
index 6d58ca3..055bc9d 100644
--- a/internal/view/issues_test.go
+++ b/internal/view/issues_test.go
@@ -140,9 +140,7 @@ func getIssues() []*jira.Issue {
 				Resolution: struct {
 					Name string `json:"name"`
 				}{Name: "Fixed"},
-				IssueType: struct {
-					Name string `json:"name"`
-				}{Name: "Bug"},
+				IssueType: jira.IssueType{Name: "Bug"},
 				Assignee: struct {
 					Name string `json:"displayName"`
 				}{Name: "Person A"},
@@ -162,10 +160,8 @@ func getIssues() []*jira.Issue {
 		{
 			Key: "TEST-2",
 			Fields: jira.IssueFields{
-				Summary: "This is another test",
-				IssueType: struct {
-					Name string `json:"name"`
-				}{Name: "Story"},
+				Summary:   "This is another test",
+				IssueType: jira.IssueType{Name: "Story"},
 				Priority: struct {
 					Name string `json:"name"`
 				}{Name: "Normal"},
diff --git a/internal/view/sprint_test.go b/internal/view/sprint_test.go
index 48bc142..10c9649 100644
--- a/internal/view/sprint_test.go
+++ b/internal/view/sprint_test.go
@@ -35,9 +35,7 @@ func TestSprintPreviewLayoutData(t *testing.T) {
 			Resolution: struct {
 				Name string `json:"name"`
 			}{Name: "Fixed"},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType: jira.IssueType{Name: "Bug"},
 			Assignee: struct {
 				Name string `json:"displayName"`
 			}{Name: "Person A"},
@@ -57,10 +55,8 @@ func TestSprintPreviewLayoutData(t *testing.T) {
 	issue2 := jira.Issue{
 		Key: "ISSUE-2",
 		Fields: jira.IssueFields{
-			Summary: "This is another issue",
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Story"},
+			Summary:   "This is another issue",
+			IssueType: jira.IssueType{Name: "Story"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Normal"},
diff --git a/pkg/jira/epic_test.go b/pkg/jira/epic_test.go
index decfb42..405fb83 100644
--- a/pkg/jira/epic_test.go
+++ b/pkg/jira/epic_test.go
@@ -52,11 +52,9 @@ func TestEpicIssues(t *testing.T) {
 			{
 				Key: "TEST-1",
 				Fields: IssueFields{
-					Summary: "Bug summary",
-					Labels:  []string{},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Bug"},
+					Summary:   "Bug summary",
+					Labels:    []string{},
+					IssueType: IssueType{Name: "Bug"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "Medium"},
@@ -77,11 +75,9 @@ func TestEpicIssues(t *testing.T) {
 			{
 				Key: "TEST-2",
 				Fields: IssueFields{
-					Summary: "Story summary",
-					Labels:  []string{"critical", "feature"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Story"},
+					Summary:   "Story summary",
+					Labels:    []string{"critical", "feature"},
+					IssueType: IssueType{Name: "Story"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "High"},
@@ -107,9 +103,7 @@ func TestEpicIssues(t *testing.T) {
 					Resolution: struct {
 						Name string `json:"name"`
 					}{Name: "Done"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Task"},
+					IssueType: IssueType{Name: "Task"},
 					Assignee: struct {
 						Name string `json:"displayName"`
 					}{Name: "Person A"},
diff --git a/pkg/jira/issue_test.go b/pkg/jira/issue_test.go
index e9eee26..d6ebc2c 100644
--- a/pkg/jira/issue_test.go
+++ b/pkg/jira/issue_test.go
@@ -53,10 +53,8 @@ func TestGetIssue(t *testing.T) {
 					},
 				},
 			},
-			Labels: []string{},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			Labels:    []string{},
+			IssueType: IssueType{Name: "Bug"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Medium"},
@@ -107,9 +105,7 @@ func TestGetIssueWithoutDescription(t *testing.T) {
 			Summary:     "Bug summary",
 			Description: nilADF,
 			Labels:      []string{},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType:   IssueType{Name: "Bug"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Medium"},
@@ -160,9 +156,7 @@ func TestGetIssueV2(t *testing.T) {
 			Summary:     "Bug summary",
 			Description: "Test description",
 			Labels:      []string{},
-			IssueType: struct {
-				Name string `json:"name"`
-			}{Name: "Bug"},
+			IssueType:   IssueType{Name: "Bug"},
 			Priority: struct {
 				Name string `json:"name"`
 			}{Name: "Medium"},
diff --git a/pkg/jira/search_test.go b/pkg/jira/search_test.go
index 0cb44d9..579662b 100644
--- a/pkg/jira/search_test.go
+++ b/pkg/jira/search_test.go
@@ -57,11 +57,9 @@ func TestSearch(t *testing.T) {
 			{
 				Key: "TEST-1",
 				Fields: IssueFields{
-					Summary: "Bug summary",
-					Labels:  []string{},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Bug"},
+					Summary:   "Bug summary",
+					Labels:    []string{},
+					IssueType: IssueType{Name: "Bug"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "Medium"},
@@ -82,11 +80,9 @@ func TestSearch(t *testing.T) {
 			{
 				Key: "TEST-2",
 				Fields: IssueFields{
-					Summary: "Story summary",
-					Labels:  []string{"critical", "feature"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Story"},
+					Summary:   "Story summary",
+					Labels:    []string{"critical", "feature"},
+					IssueType: IssueType{Name: "Story"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "High"},
@@ -112,9 +108,7 @@ func TestSearch(t *testing.T) {
 					Resolution: struct {
 						Name string `json:"name"`
 					}{Name: "Done"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Task"},
+					IssueType: IssueType{Name: "Task"},
 					Assignee: struct {
 						Name string `json:"displayName"`
 					}{Name: "Person A"},
diff --git a/pkg/jira/sprint_test.go b/pkg/jira/sprint_test.go
index b41ecba..32ed196 100644
--- a/pkg/jira/sprint_test.go
+++ b/pkg/jira/sprint_test.go
@@ -224,11 +224,9 @@ func TestSprintIssues(t *testing.T) {
 			{
 				Key: "TEST-1",
 				Fields: IssueFields{
-					Summary: "Bug summary",
-					Labels:  []string{},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Bug"},
+					Summary:   "Bug summary",
+					Labels:    []string{},
+					IssueType: IssueType{Name: "Bug"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "Medium"},
@@ -249,11 +247,9 @@ func TestSprintIssues(t *testing.T) {
 			{
 				Key: "TEST-2",
 				Fields: IssueFields{
-					Summary: "Story summary",
-					Labels:  []string{"critical", "feature"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Story"},
+					Summary:   "Story summary",
+					Labels:    []string{"critical", "feature"},
+					IssueType: IssueType{Name: "Story"},
 					Priority: struct {
 						Name string `json:"name"`
 					}{Name: "High"},
@@ -279,9 +275,7 @@ func TestSprintIssues(t *testing.T) {
 					Resolution: struct {
 						Name string `json:"name"`
 					}{Name: "Done"},
-					IssueType: struct {
-						Name string `json:"name"`
-					}{Name: "Task"},
+					IssueType: IssueType{Name: "Task"},
 					Assignee: struct {
 						Name string `json:"displayName"`
 					}{Name: "Person A"},