Skip to content

Commit

Permalink
feat: add service desk customer and request endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
iamjem committed Jul 30, 2021
1 parent cd21164 commit a2daf69
Show file tree
Hide file tree
Showing 7 changed files with 761 additions and 0 deletions.
77 changes: 77 additions & 0 deletions customer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package jira

import (
"context"
"encoding/json"
"fmt"
)

// CustomerService handles ServiceDesk customers for the Jira instance / API.
type CustomerService struct {
client *Client
}

// Customer represents a ServiceDesk customer.
type Customer struct {
AccountID string `json:"accountId,omitempty" structs:"accountId,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"`
Active *bool `json:"active,omitempty" structs:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
}

// CustomerListOptions is the query options for listing customers.
type CustomerListOptions struct {
Query string `url:"query,omitempty"`
Start int `url:"start,omitempty"`
Limit int `url:"limit,omitempty"`
}

// CustomerList is a page of customers.
type CustomerList struct {
Values []Customer `json:"values,omitempty" structs:"values,omitempty"`
Start int `json:"start,omitempty" structs:"start,omitempty"`
Limit int `json:"limit,omitempty" structs:"limit,omitempty"`
IsLast bool `json:"isLastPage,omitempty" structs:"isLastPage,omitempty"`
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
}

// CreateWithContext creates a ServiceDesk customer.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-customer/#api-rest-servicedeskapi-customer-post
func (c *CustomerService) CreateWithContext(ctx context.Context, email, displayName string) (*Customer, *Response, error) {
apiEndpoint := "rest/servicedeskapi/customer"

payload := struct {
Email string `json:"email"`
DisplayName string `json:"displayName"`
}{
Email: email,
DisplayName: displayName,
}

req, err := c.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
if err != nil {
return nil, nil, err
}
resp, err := c.client.Do(req, nil)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}
defer resp.Body.Close()

responseCustomer := new(Customer)
if err := json.NewDecoder(resp.Body).Decode(responseCustomer); err != nil {
return nil, resp, fmt.Errorf("could not unmarshall the data into struct")
}

return responseCustomer, resp, nil
}

// Create wraps CreateWithContext using the background context.
func (c *CustomerService) Create(email, displayName string) (*Customer, *Response, error) {
return c.CreateWithContext(context.Background(), email, displayName)
}
56 changes: 56 additions & 0 deletions customer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package jira

import (
"fmt"
"net/http"
"testing"
)

func TestCustomerService_Create(t *testing.T) {
setup()
defer teardown()

const (
wantDisplayName = "Fred F. User"
wantEmailAddress = "fred@example.com"
)

testMux.HandleFunc("/rest/servicedeskapi/customer", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
testRequestURL(t, r, "/rest/servicedeskapi/customer")

w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{
"accountId": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b",
"name": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b",
"key": "qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b",
"emailAddress": "%s",
"displayName": "%s",
"active": true,
"timeZone": "Australia/Sydney",
"_links": {
"jiraRest": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b",
"avatarUrls": {
"48x48": "https://avatar-cdn.atlassian.com/image",
"24x24": "https://avatar-cdn.atlassian.com/image",
"16x16": "https://avatar-cdn.atlassian.com/image",
"32x32": "https://avatar-cdn.atlassian.com/image"
},
"self": "https://your-domain.atlassian.net/rest/api/2/user?username=qm:a713c8ea-1075-4e30-9d96-891a7d181739:5ad6d3581db05e2a66fa80b"
}
}`, wantEmailAddress, wantDisplayName)
})

gotCustomer, _, err := testClient.Customer.Create(wantEmailAddress, wantDisplayName)
if err != nil {
t.Fatal(err)
}

if want, got := wantDisplayName, gotCustomer.DisplayName; want != got {
t.Fatalf("want display name: %q, got %q", want, got)
}

if want, got := wantEmailAddress, gotCustomer.EmailAddress; want != got {
t.Fatalf("want email address: %q, got %q", want, got)
}
}
4 changes: 4 additions & 0 deletions jira.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type Client struct {
IssueLinkType *IssueLinkTypeService
Organization *OrganizationService
ServiceDesk *ServiceDeskService
Customer *CustomerService
Request *RequestService
}

// NewClient returns a new Jira API client.
Expand Down Expand Up @@ -106,6 +108,8 @@ func NewClient(httpClient httpClient, baseURL string) (*Client, error) {
c.IssueLinkType = &IssueLinkTypeService{client: c}
c.Organization = &OrganizationService{client: c}
c.ServiceDesk = &ServiceDeskService{client: c}
c.Customer = &CustomerService{client: c}
c.Request = &RequestService{client: c}

return c, nil
}
Expand Down
136 changes: 136 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package jira

import (
"context"
"encoding/json"
"fmt"
)

// RequestService handles ServiceDesk customer requests for the Jira instance / API.
type RequestService struct {
client *Client
}

// Request represents a ServiceDesk customer request.
type Request struct {
IssueID string `json:"issueId,omitempty" structs:"issueId,omitempty"`
IssueKey string `json:"issueKey,omitempty" structs:"issueKey,omitempty"`
TypeID string `json:"requestTypeId,omitempty" structs:"requestTypeId,omitempty"`
ServiceDeskID string `json:"serviceDeskId,omitempty" structs:"serviceDeskId,omitempty"`
Reporter *Customer `json:"reporter,omitempty" structs:"reporter,omitempty"`
FieldValues []RequestFieldValue `json:"requestFieldValues,omitempty" structs:"requestFieldValues,omitempty"`
Status *RequestStatus `json:"currentStatus,omitempty" structs:"currentStatus,omitempty"`
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
}

// RequestFieldValue is a request field.
type RequestFieldValue struct {
FieldID string `json:"fieldId,omitempty" structs:"fieldId,omitempty"`
Label string `json:"label,omitempty" structs:"label,omitempty"`
Value string `json:"value,omitempty" structs:"value,omitempty"`
}

// RequestDate is the date format used in requests.
type RequestDate struct {
ISO8601 string `json:"iso8601,omitempty" structs:"iso8601,omitempty"`
Jira string `json:"jira,omitempty" structs:"jira,omitempty"`
Friendly string `json:"friendly,omitempty" structs:"friendly,omitempty"`
Epoch int64 `json:"epoch,omitempty" structs:"epoch,omitempty"`
}

// RequestStatus is the status for a request.
type RequestStatus struct {
Status string
Category string
Date RequestDate
}

// RequestComment is a comment for a request.
type RequestComment struct {
ID string `json:"id,omitempty" structs:"id,omitempty"`
Body string `json:"body,omitempty" structs:"body,omitempty"`
Public bool `json:"public" structs:"public"`
Author *Customer `json:"author,omitempty" structs:"author,omitempty"`
Created *RequestDate `json:"created,omitempty" structs:"created,omitempty"`
Links *SelfLink `json:"_links,omitempty" structs:"_links,omitempty"`
Expands []string `json:"_expands,omitempty" structs:"_expands,omitempty"`
}

// CreateWithContext creates a new request.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-post
func (r *RequestService) CreateWithContext(ctx context.Context, requester string, participants []string, request *Request) (*Request, *Response, error) {
apiEndpoint := "rest/servicedeskapi/request"

payload := struct {
*Request
FieldValues map[string]string `json:"requestFieldValues,omitempty"`
Requester string `json:"raiseOnBehalfOf,omitempty"`
Participants []string `json:"requestParticipants,omitempty"`
}{
Request: request,
FieldValues: make(map[string]string),
Requester: requester,
Participants: participants,
}

for _, field := range request.FieldValues {
payload.FieldValues[field.FieldID] = field.Value
}

req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, payload)
if err != nil {
return nil, nil, err
}

resp, err := r.client.Do(req, nil)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}

defer resp.Body.Close()

responseRequest := new(Request)
if err := json.NewDecoder(resp.Body).Decode(responseRequest); err != nil {
return nil, resp, fmt.Errorf("could not unmarshall the data into struct")
}

return responseRequest, resp, nil
}

// Create wraps CreateWithContext using the background context.
func (r *RequestService) Create(requester string, participants []string, request *Request) (*Request, *Response, error) {
return r.CreateWithContext(context.Background(), requester, participants, request)
}

// CreateCommentWithContext creates a comment on a request.
//
// https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-comment-post
func (r *RequestService) CreateCommentWithContext(ctx context.Context, issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) {
apiEndpoint := fmt.Sprintf("rest/servicedeskapi/request/%v/comment", issueIDOrKey)

req, err := r.client.NewRequestWithContext(ctx, "POST", apiEndpoint, comment)
if err != nil {
return nil, nil, err
}

resp, err := r.client.Do(req, nil)
if err != nil {
return nil, resp, NewJiraError(resp, err)
}

defer resp.Body.Close()

responseComment := new(RequestComment)
if err := json.NewDecoder(resp.Body).Decode(responseComment); err != nil {
return nil, resp, fmt.Errorf("could not unmarshall the data into struct")
}

return responseComment, resp, nil
}

// CreateComment wraps CreateCommentWithContext using the background context.
func (r *RequestService) CreateComment(issueIDOrKey string, comment *RequestComment) (*RequestComment, *Response, error) {
return r.CreateCommentWithContext(context.Background(), issueIDOrKey, comment)
}

0 comments on commit a2daf69

Please sign in to comment.