Skip to content

Commit b5de67c

Browse files
committed
feat: Add remote links to issue view
Adds a list of remote links to the issue view, if they exist, right below the issue links.
1 parent adab79f commit b5de67c

File tree

5 files changed

+104
-3
lines changed

5 files changed

+104
-3
lines changed

api/client.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func ProxyGetIssueRaw(c *jira.Client, key string) (string, error) {
107107
// ProxyGetIssue uses either a v2 or v3 version of the Jira GET /issue/{key}
108108
// endpoint to fetch the issue details based on configured installation type.
109109
// Defaults to v3 if installation type is not defined in the config.
110+
// Also fetches remote links for the issue.
110111
func ProxyGetIssue(c *jira.Client, key string, opts ...filter.Filter) (*jira.Issue, error) {
111112
var (
112113
iss *jira.Issue
@@ -121,6 +122,19 @@ func ProxyGetIssue(c *jira.Client, key string, opts ...filter.Filter) (*jira.Iss
121122
iss, err = c.GetIssue(key, opts...)
122123
}
123124

125+
if err != nil {
126+
return iss, err
127+
}
128+
129+
// Fetch remote links for the issue
130+
remoteLinks, err := c.GetIssueRemoteLinks(key)
131+
if err != nil {
132+
// Don't fail the entire request if remote links can't be fetched
133+
// Just log and continue without remote links
134+
remoteLinks = []jira.RemoteLink{}
135+
}
136+
iss.Fields.RemoteLinks = remoteLinks
137+
124138
return iss, err
125139
}
126140

go.sum

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2
2222
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
2323
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
2424
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
25-
github.com/charmbracelet/glamour v0.9.1 h1:11dEfiGP8q1BEqvGoIjivuc2rBk+5qEXdPtaQ2WoiCM=
25+
github.com/charmbracelet/glamour v0.9.1 h1:Q7PdJLOx8EoepsXUvW6Puz5WQ3YUElIGQdYKrIpiGLA=
2626
github.com/charmbracelet/glamour v0.9.1/go.mod h1:+SHvIS8qnwhgTpVMiXwn7OfGomSqff1cHBCI8jLOetk=
2727
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
2828
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=

internal/view/issue.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ func (i Issue) fragments() []fragment {
160160
)
161161
}
162162

163+
if len(i.Data.Fields.RemoteLinks) > 0 {
164+
scraps = append(
165+
scraps,
166+
newBlankFragment(1),
167+
fragment{Body: i.separator("External Links")},
168+
newBlankFragment(2),
169+
fragment{Body: i.remoteLinks()},
170+
newBlankFragment(1),
171+
)
172+
}
173+
163174
if i.Data.Fields.Comment.Total > 0 && i.Options.NumComments > 0 {
164175
scraps = append(
165176
scraps,
@@ -378,6 +389,40 @@ func (i Issue) linkedIssues() string {
378389
return linked.String()
379390
}
380391

392+
func (i Issue) remoteLinks() string {
393+
if len(i.Data.Fields.RemoteLinks) == 0 {
394+
return ""
395+
}
396+
397+
var (
398+
remote strings.Builder
399+
maxTitleLen int
400+
summaryLen = defaultSummaryLength
401+
)
402+
403+
// Calculate max lengths for formatting
404+
for _, link := range i.Data.Fields.RemoteLinks {
405+
maxTitleLen = max(len(link.Object.Title), maxTitleLen)
406+
}
407+
408+
if maxTitleLen < summaryLen {
409+
summaryLen = maxTitleLen
410+
}
411+
412+
remote.WriteString("\n")
413+
for _, link := range i.Data.Fields.RemoteLinks {
414+
remote.WriteString(
415+
fmt.Sprintf(
416+
" %s\n %s\n\n",
417+
coloredOut(shortenAndPad(link.Object.Title, summaryLen), color.FgCyan, color.Bold),
418+
coloredOut(link.Object.URL, color.FgBlue, color.Underline),
419+
),
420+
)
421+
}
422+
423+
return remote.String()
424+
}
425+
381426
func (i Issue) comments() []issueComment {
382427
total := i.Data.Fields.Comment.Total
383428
comments := make([]issueComment, 0, total)

pkg/jira/issue.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,37 @@ func (c *Client) RemoteLinkIssue(issueID, title, url string) error {
460460
return nil
461461
}
462462

463+
// GetIssueRemoteLinks fetches remote links for an issue using GET /issue/{issueId}/remotelink endpoint.
464+
func (c *Client) GetIssueRemoteLinks(issueID string) ([]RemoteLink, error) {
465+
path := fmt.Sprintf("/issue/%s/remotelink", issueID)
466+
467+
res, err := c.GetV2(context.Background(), path, nil)
468+
if err != nil {
469+
return nil, err
470+
}
471+
if res == nil {
472+
return nil, ErrEmptyResponse
473+
}
474+
defer func() { _ = res.Body.Close() }()
475+
476+
if res.StatusCode != http.StatusOK {
477+
return nil, formatUnexpectedResponse(res)
478+
}
479+
480+
body, err := io.ReadAll(res.Body)
481+
if err != nil {
482+
return nil, err
483+
}
484+
485+
var remoteLinks []RemoteLink
486+
err = json.Unmarshal(body, &remoteLinks)
487+
if err != nil {
488+
return nil, err
489+
}
490+
491+
return remoteLinks, nil
492+
}
493+
463494
// WatchIssue adds user as a watcher using v2 version of the POST /issue/{key}/watchers endpoint.
464495
func (c *Client) WatchIssue(key, watcher string) error {
465496
return c.watchIssue(key, watcher, apiVersion3)

pkg/jira/types.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ type IssueFields struct {
122122
InwardIssue *Issue `json:"inwardIssue,omitempty"`
123123
OutwardIssue *Issue `json:"outwardIssue,omitempty"`
124124
} `json:"issueLinks"`
125-
Created string `json:"created"`
126-
Updated string `json:"updated"`
125+
RemoteLinks []RemoteLink `json:"remoteLinks,omitempty"`
126+
Created string `json:"created"`
127+
Updated string `json:"updated"`
127128
}
128129

129130
// Field holds field info.
@@ -165,6 +166,16 @@ type IssueLinkType struct {
165166
Outward string `json:"outward"`
166167
}
167168

169+
// RemoteLink holds remote link info.
170+
type RemoteLink struct {
171+
ID int `json:"id"`
172+
Self string `json:"self"`
173+
Object struct {
174+
URL string `json:"url"`
175+
Title string `json:"title"`
176+
} `json:"object"`
177+
}
178+
168179
// Sprint holds sprint info.
169180
type Sprint struct {
170181
ID int `json:"id"`

0 commit comments

Comments
 (0)