Skip to content

Commit

Permalink
Create Jira issue using the service desk api (#613)
Browse files Browse the repository at this point in the history
  • Loading branch information
sandromello authored Dec 20, 2024
1 parent d268e4a commit ec91c0f
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 5 deletions.
12 changes: 7 additions & 5 deletions gateway/api/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,21 @@ func Post(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}
resp, err := jira.CreateIssue(issueTemplate, jiraConfig, jiraFields)
resp, err := jira.CreateCustomerRequest(issueTemplate, jiraConfig, jiraFields)
// resp, err := jira.CreateIssue(issueTemplate, jiraConfig, jiraFields)
if err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}
err = models.UpdateSessionIntegrationMetadata(ctx.OrgID, sessionID, map[string]any{
"jira_issue_key": resp.Key,
"jira_issue_url": fmt.Sprintf("%s/browse/%s", jiraConfig.URL, resp.Key),
"jira_issue_key": resp.IssueKey,
"jira_issue_url": resp.Links.Agent,
})
if err != nil {
log.Errorf("failed updating session with jira issue (%s), reason=%v", resp.Key, err)
c.JSON(http.StatusInternalServerError, gin.H{"message": fmt.Sprintf("failed updating session with jira issue %s", resp.Key)})
log.Errorf("failed updating session with jira issue (%s), reason=%v", resp.IssueKey, err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("failed updating session with jira issue %s", resp.IssueKey)})
return
}
}
Expand Down
97 changes: 97 additions & 0 deletions gateway/jira/requestsapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package jira

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"

"github.com/hoophq/hoop/common/log"
"github.com/hoophq/hoop/gateway/models"
)

func CreateCustomerRequest(tmpl *models.JiraIssueTemplate, config *models.JiraIntegration, fields CustomFields) (*RequestResponse, error) {
serviceDeskID, err := fetchServiceDeskID(config, tmpl.ProjectKey)
if err != nil {
return nil, err
}

if _, hasSummary := fields["summary"]; !hasSummary {
fields["summary"] = "Hoop Session"
}
issue := IssueFieldsV2[CustomFields]{
ServiceDeskID: serviceDeskID,
RequestTypeID: tmpl.IssueTypeName,
IsAdfRequest: false,
IssueFieldValues: IssueFieldValues[CustomFields]{fields},
}
issuePayload, err := json.Marshal(issue)
if err != nil {
return nil, fmt.Errorf("failed encoding issue payload, reason=%v", err)
}
log.Infof("creating jira issue request with payload: %v", string(issuePayload))
apiURL := fmt.Sprintf("%s/rest/servicedeskapi/request", config.URL)
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(issuePayload))
if err != nil {
return nil, fmt.Errorf("failed creating request, reason=%v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.SetBasicAuth(config.User, config.APIToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed creating jira issue, reason=%v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 201 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unable to create jira issue, status=%v, body=%v",
resp.StatusCode, string(body))
}
var response RequestResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed decoding jira issue response, reason=%v", err)
}
return &response, nil
}

func fetchServiceDeskID(config *models.JiraIntegration, projectKey string) (string, error) {
// temporary to avoid having to paginate
if val := os.Getenv("JIRA_SERVICE_DESK_ID"); val != "" {
return val, nil
}
apiURL := fmt.Sprintf("%s/rest/servicedeskapi/servicedesk?limit=100", config.URL)
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return "", fmt.Errorf("failed creating service desk http request, reason=%v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.SetBasicAuth(config.User, config.APIToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("failed performing service desk http request for %s, reason=%v", projectKey, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("unable to list service desk resources, api-url=%v, status=%v, body=%v",
apiURL, resp.StatusCode, string(body))
}
var obj ServiceDesk
if err := json.NewDecoder(resp.Body).Decode(&obj); err != nil {
return "", fmt.Errorf("failed decoding service desk payload, reason=%v", err)
}
for _, val := range obj.Values {
if val.ProjectKey == projectKey {
return val.ID, nil
}
}
if !obj.IsLastPage {
return "", fmt.Errorf("unable to find service desk id for %v, pagination not implemented", projectKey)
}
log.Warnf("unable to find project key %v, values=%v", projectKey, obj.Values)
return "", fmt.Errorf("unable to find project key %v", projectKey)
}
63 changes: 63 additions & 0 deletions gateway/jira/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ type IssueResponse struct {
Self string `json:"self"`
}

type RequestLinks struct {
JiraRest string `json:"jiraRest"`
Web string `json:"web"`
Agent string `json:"agent"`
Self string `json:"self"`
}

type RequestResponse struct {
IssueID string `json:"issueId"`
IssueKey string `json:"issueKey"`
Links RequestLinks `json:"_links"`
}

type ServiceDeskValue struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
ProjectName string `json:"projectName"`
ProjectKey string `json:"projectKey"`
}

type ServiceDesk struct {
Start int `json:"start"`
Size int `json:"size"`
Limit int `json:"limit"`
IsLastPage bool `json:"isLastPage"`
Values []ServiceDeskValue `json:"values"`
}

type Project struct {
Key string `json:"key"`
}
Expand Down Expand Up @@ -69,6 +97,41 @@ func (A IssueFields[T]) MarshalJSON() ([]byte, error) {
return resp, nil
}

type IssueFieldValues[T any] struct {
CustomFields T `json:"requestFieldValues"`
}

type IssueFieldsV2[T any] struct {
ServiceDeskID string `json:"serviceDeskId"`
RequestTypeID string `json:"requestTypeId"`
IsAdfRequest bool `json:"isAdfRequest"`

IssueFieldValues IssueFieldValues[T] `json:"-"`
}

func (A IssueFieldsV2[T]) MarshalJSON() ([]byte, error) {
type ResponseAlias IssueFieldsV2[types.Nil]
resp, err := json.Marshal(ResponseAlias{
ServiceDeskID: A.ServiceDeskID,
RequestTypeID: A.RequestTypeID,
IsAdfRequest: A.IsAdfRequest,
})
if err != nil {
return nil, err
}

data, err := json.Marshal(A.IssueFieldValues)
if err != nil {
return nil, err
}
if bytes.Equal(data, []byte(`{}`)) {
return resp, nil
}
v := append(resp[1:len(resp)-1], byte(','))
resp = slices.Insert(data, 1, v...)
return resp, nil
}

func loadDefaultPresetFields(s storagev2types.Session) map[string]string {
return map[string]string{
"session.id": s.ID,
Expand Down

0 comments on commit ec91c0f

Please sign in to comment.