Skip to content

Commit

Permalink
[GH-912]:Fixed issue "Handle error case where issuetypes are not avai…
Browse files Browse the repository at this point in the history
…lable" of Jira plugin (#915)
  • Loading branch information
Kshitij-Katiyar authored Mar 3, 2023
1 parent e277ede commit a7f0e41
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 59 deletions.
5 changes: 3 additions & 2 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ type UserService interface {
// ProjectService is the interface for project-related APIs.
type ProjectService interface {
GetProject(key string) (*jira.Project, error)
ListProjects(query string, limit int) (jira.ProjectList, error)
ListProjects(query string, limit int, expandIssueTypes bool) (jira.ProjectList, error)
GetIssueTypes(projectID string) ([]jira.IssueType, error)
}

// SearchService is the interface for search-related APIs.
Expand All @@ -74,7 +75,7 @@ type IssueService interface {
AddAttachment(api plugin.API, issueKey, fileID string, maxSize utils.ByteSize) (mattermostName, jiraName, mime string, err error)
AddComment(issueKey string, comment *jira.Comment) (*jira.Comment, error)
DoTransition(issueKey, transitionID string) error
GetCreateMetaInfo(*jira.GetQueryOptions) (*jira.CreateMetaInfo, error)
GetCreateMetaInfo(api plugin.API, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, error)
GetTransitions(issueKey string) ([]jira.Transition, error)
UpdateAssignee(issueKey string, user *jira.User) error
UpdateComment(issueKey string, comment *jira.Comment) (*jira.Comment, error)
Expand Down
24 changes: 21 additions & 3 deletions server/client_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"

jira "github.com/andygrunwald/go-jira"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/pkg/errors"
)

Expand All @@ -25,7 +26,7 @@ func newCloudClient(jiraClient *jira.Client) Client {

// GetCreateMetaInfo returns the metadata needed to implement the UI and validation of
// creating new Jira issues.
func (client jiraCloudClient) GetCreateMetaInfo(options *jira.GetQueryOptions) (*jira.CreateMetaInfo, error) {
func (client jiraCloudClient) GetCreateMetaInfo(api plugin.API, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, error) {
cimd, resp, err := client.Jira.Issue.GetCreateMetaWithOptions(options)
if err != nil {
if resp == nil {
Expand Down Expand Up @@ -68,7 +69,7 @@ func (client jiraCloudClient) GetUserGroups(connection *Connection) ([]*jira.Use
return groups, nil
}

func (client jiraCloudClient) ListProjects(query string, limit int) (jira.ProjectList, error) {
func (client jiraCloudClient) ListProjects(query string, limit int, expandIssueTypes bool) (jira.ProjectList, error) {
type searchResult struct {
Values jira.ProjectList `json:"values"`
StartAt int `json:"startAt"`
Expand All @@ -91,8 +92,12 @@ func (client jiraCloudClient) ListProjects(query string, limit int) (jira.Projec
opts := map[string]string{
"startAt": strconv.Itoa(len(out)),
"maxResults": strconv.Itoa(remaining),
"expand": "issueTypes",
}

if expandIssueTypes {
opts["expand"] = QueryParamIssueTypes
}

var result searchResult
err := client.RESTGet("/3/project/search", opts, &result)
if err != nil {
Expand All @@ -114,3 +119,16 @@ func (client jiraCloudClient) ListProjects(query string, limit int) (jira.Projec
}
}
}

func (client jiraCloudClient) GetIssueTypes(projectID string) ([]jira.IssueType, error) {
var result []jira.IssueType
opts := map[string]string{
"projectId": projectID,
}

if err := client.RESTGet("3/issuetype/project", opts, &result); err != nil {
return nil, err
}

return result, nil
}
114 changes: 67 additions & 47 deletions server/client_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ import (

jira "github.com/andygrunwald/go-jira"
"github.com/blang/semver/v4"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/pkg/errors"
)

const (
APIEndpointGetServerInfo = "rest/api/2/serverInfo"
APIEndpointCreateIssueMeta = "rest/api/2/issue/createmeta/"
JiraVersionWithOldIssueAPIBreaking = "9.0.0"
QueryParamIssueTypes = "issueTypes"
)

type jiraServerClient struct {
JiraClient
}

type searchResult struct {
IssueTypes []jira.IssueType `json:"issueTypes"`
}

func newServerClient(jiraClient *jira.Client) Client {
return &jiraServerClient{
JiraClient: JiraClient{
Expand Down Expand Up @@ -55,67 +61,63 @@ func (client jiraServerClient) GetIssueInfo(projectID string) (*ProjectIssueInfo
return &issues, response, err
}

func (client jiraServerClient) GetProjectInfoForPivotJiraVersion(options *jira.GetQueryOptions) (*jira.CreateMetaInfo, *jira.Response, error) {
var issueInfo *ProjectIssueInfo
var req *http.Request

projectList, resp, err := client.Jira.Project.ListWithOptions(options)
meta := new(jira.CreateMetaInfo)
func (client jiraServerClient) GetCreateMetaInfoForPivotJiraVersion(api plugin.API, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, *jira.Response, error) {
projectID := options.ProjectKeys
proj, resp, err := client.Jira.Project.Get(projectID)
if err != nil {
return nil, resp, errors.Wrap(err, "failed to get project for CreateMetaInfo")
}

issueInfo, resp, err := client.GetIssueInfo(proj.ID)
if err != nil {
return nil, resp, errors.Wrap(err, "failed to list projects")
return nil, resp, errors.Wrap(err, "failed to get create meta info")
}

for _, proj := range *projectList {
meta.Expand = proj.Expand
issueInfo, resp, err = client.GetIssueInfo(proj.ID)
if err != nil {
break
for _, issueType := range issueInfo.Values {
apiEndpoint := fmt.Sprintf("%s%s/issuetypes/%s", APIEndpointCreateIssueMeta, proj.ID, issueType.Id)
req, rErr := client.Jira.NewRequest(http.MethodGet, apiEndpoint, nil)
if rErr != nil {
api.LogDebug("Failed to get the issue type.", "IssueType", issueType, "Error", rErr.Error())
continue
}

for _, issueType := range issueInfo.Values {
apiEndpoint := fmt.Sprintf("%s%s/issuetypes/%s", APIEndpointCreateIssueMeta, proj.ID, issueType.Id)
req, err = client.Jira.NewRequest(http.MethodGet, apiEndpoint, nil)
if err != nil {
break
}

fieldInfo := FieldInfo{}
resp, err = client.Jira.Do(req, &fieldInfo)
if err != nil {
break
}

fieldMap := make(map[string]interface{})
for _, fieldValue := range fieldInfo.Values {
fieldMap[fmt.Sprintf("%v", fieldValue["fieldId"])] = fieldValue
}
issueType.Fields = fieldMap
}
project := &jira.MetaProject{
Expand: proj.Expand,
Self: proj.Self,
Id: proj.ID,
Key: proj.Key,
Name: proj.Name,
IssueTypes: issueInfo.Values,
fieldInfo := FieldInfo{}
resp, err = client.Jira.Do(req, &fieldInfo)
if err != nil {
api.LogDebug("Failed to get the response for field info.", "Error", err.Error())
continue
}

meta.Projects = append(meta.Projects, project)
fieldMap := make(map[string]interface{})
for _, fieldValue := range fieldInfo.Values {
fieldMap[fmt.Sprintf("%v", fieldValue["fieldId"])] = fieldValue
}
issueType.Fields = fieldMap
}
project := &jira.MetaProject{
Expand: proj.Expand,
Self: proj.Self,
Id: proj.ID,
Key: proj.Key,
Name: proj.Name,
IssueTypes: issueInfo.Values,
}

meta := new(jira.CreateMetaInfo)
meta.Projects = append(meta.Projects, project)
return meta, resp, err
}

func (client jiraServerClient) GetProjectInfo(currentVersion, pivotVersion semver.Version, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, *jira.Response, error) {
func (client jiraServerClient) GetCreateMetaInfoForSpecificJiraVersion(api plugin.API, currentVersion, pivotVersion semver.Version, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, *jira.Response, error) {
if currentVersion.LT(pivotVersion) {
return client.Jira.Issue.GetCreateMetaWithOptions(options)
}
return client.GetProjectInfoForPivotJiraVersion(options)
return client.GetCreateMetaInfoForPivotJiraVersion(api, options)
}

// GetCreateMetaInfo returns the metadata needed to implement the UI and validation of
// creating new Jira issues.
func (client jiraServerClient) GetCreateMetaInfo(options *jira.GetQueryOptions) (*jira.CreateMetaInfo, error) {
func (client jiraServerClient) GetCreateMetaInfo(api plugin.API, options *jira.GetQueryOptions) (*jira.CreateMetaInfo, error) {
v := new(JiraServerVersionInfo)
req, err := client.Jira.NewRequest(http.MethodGet, APIEndpointGetServerInfo, nil)
if err != nil {
Expand All @@ -136,7 +138,7 @@ func (client jiraServerClient) GetCreateMetaInfo(options *jira.GetQueryOptions)
return nil, errors.Wrap(err, "error while parsing version")
}

info, resp, err := client.GetProjectInfo(currentVersion, pivotVersion, options)
info, resp, err := client.GetCreateMetaInfoForSpecificJiraVersion(api, currentVersion, pivotVersion, options)
if err != nil {
if resp == nil {
return nil, err
Expand Down Expand Up @@ -174,17 +176,35 @@ func (client jiraServerClient) GetUserGroups(connection *Connection) ([]*jira.Us
return result.Groups.Items, nil
}

func (client jiraServerClient) ListProjects(query string, limit int) (jira.ProjectList, error) {
plist, resp, err := client.Jira.Project.GetList()
func (client jiraServerClient) ListProjects(query string, limit int, expandIssueTypes bool) (jira.ProjectList, error) {
queryOptions := &jira.GetQueryOptions{}
if expandIssueTypes {
queryOptions.Expand = QueryParamIssueTypes
}

pList, resp, err := client.Jira.Project.ListWithOptions(queryOptions)
if err != nil {
return nil, userFriendlyJiraError(resp, err)
}
if plist == nil {
if pList == nil {
return jira.ProjectList{}, nil
}
result := *plist
result := *pList
if limit > 0 && len(result) > limit {
result = result[:limit]
}
return result, nil
}

func (client jiraServerClient) GetIssueTypes(projectID string) ([]jira.IssueType, error) {
var result searchResult
opts := map[string]string{
"expand": "issueTypes",
}

if err := client.RESTGet(fmt.Sprintf("2/project/%s", projectID), opts, &result); err != nil {
return nil, err
}

return result.IssueTypes, nil
}
47 changes: 41 additions & 6 deletions server/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ func (p *Plugin) GetCreateIssueMetadataForProjects(instanceID, mattermostUserID
return nil, err
}

return client.GetCreateMetaInfo(&jira.GetQueryOptions{
return client.GetCreateMetaInfo(p.API, &jira.GetQueryOptions{
Expand: "projects.issuetypes.fields",
ProjectKeys: projectKeys,
})
Expand Down Expand Up @@ -502,10 +502,31 @@ func (p *Plugin) httpGetJiraProjectMetadata(w http.ResponseWriter, r *http.Reque

instanceID := r.FormValue("instance_id")

plist, connection, err := p.ListJiraProjects(types.ID(instanceID), types.ID(mattermostUserID))
plist, connection, err := p.ListJiraProjects(types.ID(instanceID), types.ID(mattermostUserID), true)
if err != nil {
return respondErr(w, http.StatusInternalServerError,
errors.WithMessage(err, "failed to GetProjectMetadata"))
// Getting the issue Types separately only when the status code returned is 400
if !strings.Contains(err.Error(), "400") {
return respondErr(w, http.StatusInternalServerError,
errors.WithMessage(err, "failed to GetProjectMetadata"))
}

plist, connection, err = p.ListJiraProjects(types.ID(instanceID), types.ID(mattermostUserID), false)
if err != nil {
return respondErr(w, http.StatusInternalServerError,
errors.WithMessage(err, "failed to get the list of Jira Projects"))
}

var projectList jira.ProjectList
for _, prj := range plist {
issueTypeList, iErr := p.GetIssueTypes(types.ID(instanceID), types.ID(mattermostUserID), prj.ID)
if iErr != nil {
p.API.LogDebug("Failed to get issue types for project.", "ProjectKey", prj.Key, "Error", iErr.Error())
continue
}
prj.IssueTypes = issueTypeList
projectList = append(projectList, prj)
}
plist = projectList
}

if len(plist) == 0 {
Expand Down Expand Up @@ -545,18 +566,32 @@ func (p *Plugin) httpGetJiraProjectMetadata(w http.ResponseWriter, r *http.Reque
})
}

func (p *Plugin) ListJiraProjects(instanceID, mattermostUserID types.ID) (jira.ProjectList, *Connection, error) {
func (p *Plugin) ListJiraProjects(instanceID, mattermostUserID types.ID, expandIssueTypes bool) (jira.ProjectList, *Connection, error) {
client, _, connection, err := p.getClient(instanceID, mattermostUserID)
if err != nil {
return nil, nil, err
}
plist, err := client.ListProjects("", -1)
plist, err := client.ListProjects("", -1, expandIssueTypes)
if err != nil {
return nil, nil, err
}
return plist, connection, nil
}

func (p *Plugin) GetIssueTypes(instanceID, mattermostUserID types.ID, projectID string) ([]jira.IssueType, error) {
client, _, _, err := p.getClient(instanceID, mattermostUserID)
if err != nil {
return nil, err
}

issueTypes, err := client.GetIssueTypes(projectID)
if err != nil {
return nil, err
}

return issueTypes, nil
}

var reJiraIssueKey = regexp.MustCompile(`^([[:alnum:]]+)-([[:digit:]]+)$`)

func (p *Plugin) httpAttachCommentToIssue(w http.ResponseWriter, r *http.Request) (int, error) {
Expand Down
2 changes: 1 addition & 1 deletion server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ func (p *Plugin) AddAutolinksForCloudInstance(ci *cloudInstance) error {
return fmt.Errorf("unable to get jira client for server: %w", err)
}

plist, err := jiraCloudClient{JiraClient{Jira: client}}.ListProjects("", -1)
plist, err := jiraCloudClient{JiraClient{Jira: client}}.ListProjects("", -1, false)
if err != nil {
return fmt.Errorf("unable to get project keys: %w", err)
}
Expand Down

0 comments on commit a7f0e41

Please sign in to comment.