From 1946cac0fe6ee91f784e3dda3c12f3f30f7115b8 Mon Sep 17 00:00:00 2001
From: Roman Volodin <slash@ozon.ru>
Date: Wed, 11 Dec 2019 16:50:22 +0300
Subject: [PATCH] feat: Implement get remote links method

---
 issue.go                | 56 +++++++++++++++++++++++++++++++++++++++++
 issue_test.go           | 36 ++++++++++++++++++++++++++
 mocks/remote_links.json | 56 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 148 insertions(+)
 create mode 100644 mocks/remote_links.json

diff --git a/issue.go b/issue.go
index 747c134..857373c 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 7d4a376..7305816 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 0000000..80e5001
--- /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