diff --git a/issue.go b/issue.go index 747c1348..857373cc 100644 --- a/issue.go +++ b/issue.go @@ -547,6 +547,44 @@ type AddWorklogQueryOptions struct { // This can heavily differ between JIRA instances type CustomFields map[string]string +// RemoteLink represents remote links which linked to issues +type RemoteLink struct { + ID int `json:"id,omitempty" structs:"id,omitempty"` + Self string `json:"self,omitempty" structs:"self,omitempty"` + GlobalID string `json:"globalId,omitempty" structs:"globalId,omitempty"` + Application *RemoteLinkApplication `json:"application,omitempty" structs:"application,omitempty"` + Relationship string `json:"relationship,omitempty" structs:"relationship,omitempty"` + Object *RemoteLinkObject `json:"object,omitempty" structs:"object,omitempty"` +} + +// RemoteLinkApplication represents remote links application +type RemoteLinkApplication struct { + Type string `json:"type,omitempty" structs:"type,omitempty"` + Name string `json:"name,omitempty" structs:"name,omitempty"` +} + +// RemoteLinkObject represents remote link object itself +type RemoteLinkObject struct { + URL string `json:"url,omitempty" structs:"url,omitempty"` + Title string `json:"title,omitempty" structs:"title,omitempty"` + Summary string `json:"summary,omitempty" structs:"summary,omitempty"` + Icon *RemoteLinkIcon `json:"icon,omitempty" structs:"icon,omitempty"` + Status *RemoteLinkStatus `json:"status,omitempty" structs:"status,omitempty"` +} + +// RemoteLinkIcon represents icon displayed next to link +type RemoteLinkIcon struct { + Url16x16 string `json:"url16x16,omitempty" structs:"url16x16,omitempty"` + Title string `json:"title,omitempty" structs:"title,omitempty"` + Link string `json:"link,omitempty" structs:"link,omitempty"` +} + +// RemoteLinkStatus if the link is a resolvable object (issue, epic) - the structure represent its status +type RemoteLinkStatus struct { + Resolved bool + Icon *RemoteLinkIcon +} + // Get returns a full representation of the issue for the given issue key. // JIRA will attempt to identify the issue by the issueIdOrKey path parameter. // This can be an issue id, or an issue key. @@ -1274,3 +1312,21 @@ func (c ChangelogHistory) CreatedTime() (time.Time, error) { t, err := time.Parse("2006-01-02T15:04:05.999-0700", c.Created) return t, err } + +// GetRemoteLinks gets remote issue links on the issue. +// +// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getRemoteIssueLinks +func (s *IssueService) GetRemoteLinks(id string) (*[]RemoteLink, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/remotelink", id) + req, err := s.client.NewRequest("GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + result := new([]RemoteLink) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + return result, resp, err +} diff --git a/issue_test.go b/issue_test.go index 7d4a3767..7305816c 100644 --- a/issue_test.go +++ b/issue_test.go @@ -1649,3 +1649,39 @@ func TestIssueService_Get_Fields_AffectsVersions(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestIssueService_GetRemoteLinks(t *testing.T) { + setup() + defer teardown() + + testAPIEndpoint := "/rest/api/2/issue/123/remotelink" + + raw, err := ioutil.ReadFile("./mocks/remote_links.json") + if err != nil { + t.Error(err.Error()) + } + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, string(raw)) + }) + + remoteLinks, _, err := testClient.Issue.GetRemoteLinks("123") + + if err != nil { + t.Errorf("Got error: %v", err) + } + + if remoteLinks == nil { + t.Error("Expected remote links list. Got nil.") + } + + if len(*remoteLinks) != 2 { + t.Errorf("Expected 2 remote links. Got %d", len(*remoteLinks)) + } + + if !(*remoteLinks)[0].Object.Status.Resolved { + t.Errorf("First remote link object status should be resolved") + } +} diff --git a/mocks/remote_links.json b/mocks/remote_links.json new file mode 100644 index 00000000..80e50013 --- /dev/null +++ b/mocks/remote_links.json @@ -0,0 +1,56 @@ +[ + { + "id": 10000, + "self": "http://www.example.com/jira/rest/api/issue/MKY-1/remotelink/10000", + "globalId": "system=http://www.mycompany.com/support&id=1", + "application": { + "type": "com.acme.tracker", + "name": "My Acme Tracker" + }, + "relationship": "causes", + "object": { + "url": "http://www.mycompany.com/support?id=1", + "title": "TSTSUP-111", + "summary": "Crazy customer support issue", + "icon": { + "url16x16": "http://www.mycompany.com/support/ticket.png", + "title": "Support Ticket" + }, + "status": { + "resolved": true, + "icon": { + "url16x16": "http://www.mycompany.com/support/resolved.png", + "title": "Case Closed", + "link": "http://www.mycompany.com/support?id=1&details=closed" + } + } + } + }, + { + "id": 10001, + "self": "http://www.example.com/jira/rest/api/issue/MKY-1/remotelink/10001", + "globalId": "system=http://www.anothercompany.com/tester&id=1234", + "application": { + "type": "com.acme.tester", + "name": "My Acme Tester" + }, + "relationship": "is tested by", + "object": { + "url": "http://www.anothercompany.com/tester/testcase/1234", + "title": "Test Case #1234", + "summary": "Test that the submit button saves the thing", + "icon": { + "url16x16": "http://www.anothercompany.com/tester/images/testcase.gif", + "title": "Test Case" + }, + "status": { + "resolved": false, + "icon": { + "url16x16": "http://www.anothercompany.com/tester/images/person/fred.gif", + "title": "Tested by Fred Jones", + "link": "http://www.anothercompany.com/tester/person?username=fred" + } + } + } + } +] \ No newline at end of file