Repository: ankitpokhrel/jira-cli
Pull request #226 (2021-12-12 14:33:45)

feat: Add an option to load N comments when viewing issue
We can now use `--comments <limit>` filter to load `N` comments when viewing the issue.

```sh
// This will load 5 comments
$ jira issue view ISSUE-1 --comments 5
```
![Screen Shot 2021-12-12 at 3 26 17 PM](https://user-images.githubusercontent.com/2364546/145716400-5553c638-0bf4-4f32-a14e-d2bf70407a90.png)




Patch:
diff --git a/README.md b/README.md
index 5d526c9..4066922 100644
--- a/README.md
+++ b/README.md
@@ -341,6 +341,11 @@ $ jira issue view ISSUE-1
 The view screen will display linked issues and the latest comment after description. Note that the displayed comment may
 not be the latest one if you for some reason have more than 5k comments in a ticket.
 
+```sh
+# Show 5 recent comments when viewing the issue
+$ jira issue view ISSUE-1 --comments 5
+```
+
 #### Link
 The `link` command lets you link two issues.
 
diff --git a/api/client.go b/api/client.go
index b66d5b0..4ff725e 100644
--- a/api/client.go
+++ b/api/client.go
@@ -6,6 +6,7 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/ankitpokhrel/jira-cli/pkg/jira"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter"
 )
 
 const clientTimeout = 15 * time.Second
@@ -56,21 +57,21 @@ func ProxyCreate(c *jira.Client, cr *jira.CreateRequest) (*jira.CreateResponse,
 // ProxyGetIssue uses either a v2 or v3 version of the Jira GET /issue/{key}
 // endpoint to fetch the issue details based on configured installation type.
 // Defaults to v3 if installation type is not defined in the config.
-func ProxyGetIssue(c *jira.Client, key string) (*jira.Issue, error) {
+func ProxyGetIssue(c *jira.Client, key string, opts ...filter.Filter) (*jira.Issue, error) {
 	var (
-		issue *jira.Issue
-		err   error
+		iss *jira.Issue
+		err error
 	)
 
 	it := viper.GetString("installation")
 
 	if it == jira.InstallationTypeLocal {
-		issue, err = c.GetIssueV2(key)
+		iss, err = c.GetIssueV2(key, opts...)
 	} else {
-		issue, err = c.GetIssue(key)
+		iss, err = c.GetIssue(key, opts...)
 	}
 
-	return issue, err
+	return iss, err
 }
 
 // ProxySearch uses either a v2 or v3 version of the Jira GET /search endpoint
diff --git a/internal/cmd/issue/view/view.go b/internal/cmd/issue/view/view.go
index e3321ea..9c60819 100644
--- a/internal/cmd/issue/view/view.go
+++ b/internal/cmd/issue/view/view.go
@@ -8,11 +8,15 @@ import (
 	"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
 	tuiView "github.com/ankitpokhrel/jira-cli/internal/view"
 	"github.com/ankitpokhrel/jira-cli/pkg/jira"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
 )
 
 const (
 	helpText = `View displays contents of an issue.`
-	examples = `$ jira issue view ISSUE-1`
+	examples = `$ jira issue view ISSUE-1
+
+# Show 5 recent comments when viewing the issue
+$ jira issue view ISSUE-1 --comments 5`
 )
 
 // NewCmdView is a view command.
@@ -30,6 +34,7 @@ func NewCmdView() *cobra.Command {
 		Run:  view,
 	}
 
+	cmd.Flags().Uint("comments", 1, "Show N comments")
 	cmd.Flags().Bool("plain", false, "Display output in plain mode")
 
 	return &cmd
@@ -39,13 +44,16 @@ func view(cmd *cobra.Command, args []string) {
 	debug, err := cmd.Flags().GetBool("debug")
 	cmdutil.ExitIfError(err)
 
+	comments, err := cmd.Flags().GetUint("comments")
+	cmdutil.ExitIfError(err)
+
 	key := cmdutil.GetJiraIssueKey(viper.GetString("project.key"), args[0])
-	issue, err := func() (*jira.Issue, error) {
+	iss, err := func() (*jira.Issue, error) {
 		s := cmdutil.Info("Fetching issue details...")
 		defer s.Stop()
 
 		client := api.Client(jira.Config{Debug: debug})
-		return api.ProxyGetIssue(client, key)
+		return api.ProxyGetIssue(client, key, issue.NewNumCommentsFilter(comments))
 	}()
 	cmdutil.ExitIfError(err)
 
@@ -54,8 +62,9 @@ func view(cmd *cobra.Command, args []string) {
 
 	v := tuiView.Issue{
 		Server:  viper.GetString("server"),
-		Data:    issue,
+		Data:    iss,
 		Display: tuiView.DisplayFormat{Plain: plain},
+		Options: tuiView.IssueOption{NumComments: comments},
 	}
 	cmdutil.ExitIfError(v.Render())
 }
diff --git a/internal/view/epic.go b/internal/view/epic.go
index 62eda4c..ea89f7d 100644
--- a/internal/view/epic.go
+++ b/internal/view/epic.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/ankitpokhrel/jira-cli/api"
 	"github.com/ankitpokhrel/jira-cli/pkg/jira"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
 	"github.com/ankitpokhrel/jira-cli/pkg/tui"
 )
 
@@ -39,13 +40,14 @@ func (el EpicList) Render() error {
 				dataFn := func() interface{} {
 					data := d.(tui.TableData)
 					ci := getKeyColumnIndex(data[0])
-					issue, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci])
-					return issue
+					iss, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci], issue.NewNumCommentsFilter(1))
+					return iss
 				}
 				renderFn := func(i interface{}) (string, error) {
 					iss := Issue{
-						Server: el.Server,
-						Data:   i.(*jira.Issue),
+						Server:  el.Server,
+						Data:    i.(*jira.Issue),
+						Options: IssueOption{NumComments: 1},
 					}
 					return iss.RenderedOut(renderer)
 				}
diff --git a/internal/view/issue.go b/internal/view/issue.go
index 5907816..14b37f5 100644
--- a/internal/view/issue.go
+++ b/internal/view/issue.go
@@ -33,11 +33,22 @@ func newBlankFragment(n int) fragment {
 	}
 }
 
+type issueComment struct {
+	meta string
+	body string
+}
+
+// IssueOption is filtering options for an issue.
+type IssueOption struct {
+	NumComments uint
+}
+
 // Issue is a list view for issues.
 type Issue struct {
 	Server  string
 	Data    *jira.Issue
 	Display DisplayFormat
+	Options IssueOption
 }
 
 // Render renders the view.
@@ -87,10 +98,13 @@ func (i Issue) String() string {
 	if len(i.Data.Fields.IssueLinks) > 0 {
 		s.WriteString(fmt.Sprintf("\n\n%s\n\n%s\n", i.separator("Linked Issues"), i.linkedIssues()))
 	}
-	if i.Data.Fields.Comment.Total > 0 {
-		author, body := i.comment()
-		sep := fmt.Sprintf("%d comments", i.Data.Fields.Comment.Total)
-		s.WriteString(fmt.Sprintf("\n\n%s\n\n%s\n\n%s\n", i.separator(sep), author, body))
+	total := i.Data.Fields.Comment.Total
+	if total > 0 && i.Options.NumComments > 0 {
+		sep := fmt.Sprintf("%d Comments", total)
+		s.WriteString(fmt.Sprintf("\n\n%s", i.separator(sep)))
+		for _, comment := range i.comments() {
+			s.WriteString(fmt.Sprintf("\n\n%s\n\n%s\n", comment.meta, comment.body))
+		}
 	}
 	s.WriteString(i.footer())
 
@@ -124,17 +138,21 @@ func (i Issue) fragments() []fragment {
 		)
 	}
 
-	if i.Data.Fields.Comment.Total > 0 {
-		author, body := i.comment()
+	if i.Data.Fields.Comment.Total > 0 && i.Options.NumComments > 0 {
 		scraps = append(
 			scraps,
 			newBlankFragment(1),
-			fragment{Body: i.separator(fmt.Sprintf("%d comments", i.Data.Fields.Comment.Total))},
+			fragment{Body: i.separator(fmt.Sprintf("%d Comments", i.Data.Fields.Comment.Total))},
 			newBlankFragment(2),
-			fragment{Body: author},
-			newBlankFragment(1),
-			fragment{Body: body, Parse: true},
 		)
+		for _, comment := range i.comments() {
+			scraps = append(
+				scraps,
+				fragment{Body: comment.meta},
+				newBlankFragment(1),
+				fragment{Body: comment.body, Parse: true},
+			)
+		}
 	}
 
 	return append(scraps, newBlankFragment(1), fragment{Body: i.footer()}, newBlankFragment(2))
@@ -191,7 +209,7 @@ func (i Issue) header() string {
 		wch = fmt.Sprintf("You + %d watchers", i.Data.Fields.Watches.WatchCount-1)
 	}
 	return fmt.Sprintf(
-		"%s %s  %s %s  ⌛ %s  👷 %s  🔑️ %s  💭 %d comments  \U0001F9F5 %d linked issues\n# %s\n⏱️  %s  🔎 %s  🚀 %s  📦 %s  🏷️  %s  👀 %s",
+		"%s %s  %s %s  ⌛ %s  👷 %s  🔑️ %s  💭 %d comments  \U0001F9F5 %d linked\n# %s\n⏱️  %s  🔎 %s  🚀 %s  📦 %s  🏷️  %s  👀 %s",
 		iti, it, sti, st, cmdutil.FormatDateTimeHuman(i.Data.Fields.Updated, jira.RFC3339), as, i.Data.Key,
 		i.Data.Fields.Comment.Total, len(i.Data.Fields.IssueLinks),
 		i.Data.Fields.Summary,
@@ -273,12 +291,12 @@ func (i Issue) linkedIssues() string {
 
 	for _, k := range keys {
 		linked.WriteString(
-			fmt.Sprintf("\n  %s\n\n", coloredOut(strings.ToUpper(k), color.FgWhite, color.Bold)),
+			fmt.Sprintf("\n %s\n\n", coloredOut(strings.ToUpper(k), color.FgWhite, color.Bold)),
 		)
 		for _, iss := range linkMap[k] {
 			linked.WriteString(
 				fmt.Sprintf(
-					"    %s %s • %s • %s • %s\n",
+					"  %s %s • %s • %s • %s\n",
 					coloredOut(pad(iss.Key, maxKeyLen), color.FgGreen, color.Bold),
 					shortenAndPad(iss.Fields.Summary, summaryLen),
 					pad(iss.Fields.IssueType.Name, maxTypeLen),
@@ -292,28 +310,61 @@ func (i Issue) linkedIssues() string {
 	return linked.String()
 }
 
-func (i Issue) comment() (string, string) {
-	if i.Data.Fields.Comment.Total == 0 {
-		return "", ""
+func (i Issue) comments() []issueComment {
+	comments := make([]issueComment, 0, i.Options.NumComments)
+
+	total := i.Data.Fields.Comment.Total
+	if total == 0 {
+		return comments
 	}
-	latestComment := i.Data.Fields.Comment.Comments[i.Data.Fields.Comment.Total-1]
-	var body string
-	if adfNode, ok := latestComment.Body.(*adf.ADF); ok {
-		body = adf.NewTranslator(adfNode, adf.NewMarkdownTranslator()).Translate()
-	} else {
-		body = latestComment.Body.(string)
-		body = md.FromJiraMD(body)
+
+	limit := int(i.Options.NumComments)
+	if limit > total {
+		limit = total
 	}
-	return fmt.Sprintf(
-		"\n  %s • %s • %s",
-		coloredOut(latestComment.Author.Name, color.FgWhite, color.Bold),
-		coloredOut(cmdutil.FormatDateTimeHuman(latestComment.Created, jira.RFC3339), color.FgWhite, color.Bold),
-		coloredOut("Latest comment", color.FgCyan, color.Bold),
-	), body
+
+	for idx := total - 1; idx >= total-limit; idx-- {
+		c := i.Data.Fields.Comment.Comments[idx]
+		var body string
+		if adfNode, ok := c.Body.(*adf.ADF); ok {
+			body = adf.NewTranslator(adfNode, adf.NewMarkdownTranslator()).Translate()
+		} else {
+			body = c.Body.(string)
+			body = md.FromJiraMD(body)
+		}
+		meta := fmt.Sprintf(
+			"\n %s • %s",
+			coloredOut(c.Author.Name, color.FgWhite, color.Bold),
+			coloredOut(cmdutil.FormatDateTimeHuman(c.Created, jira.RFC3339), color.FgWhite, color.Bold),
+		)
+		if idx == total-1 {
+			meta += fmt.Sprintf(" • %s", coloredOut("Latest comment", color.FgCyan, color.Bold))
+		}
+		comments = append(comments, issueComment{
+			meta: meta,
+			body: body,
+		})
+	}
+
+	return comments
 }
 
 func (i Issue) footer() string {
-	return gray(fmt.Sprintf("View this issue on Jira: %s/browse/%s", i.Server, i.Data.Key))
+	var out strings.Builder
+
+	nc := int(i.Options.NumComments)
+	if i.Data.Fields.Comment.Total > 0 && nc > 0 && nc < i.Data.Fields.Comment.Total {
+		if i.Display.Plain {
+			out.WriteString("\n")
+		}
+		out.WriteString(fmt.Sprintf("%s\n", gray("Use --comments <limit> with `jira issue view` to load more comments")))
+	}
+	if i.Display.Plain {
+		out.WriteString("\n")
+	}
+	out.WriteString(gray(fmt.Sprintf("View this issue on Jira: %s/browse/%s", i.Server, i.Data.Key)))
+
+	return out.String()
 }
 
 // renderPlain renders the issue in plain view.
diff --git a/internal/view/issues.go b/internal/view/issues.go
index 3356121..b25d091 100644
--- a/internal/view/issues.go
+++ b/internal/view/issues.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/ankitpokhrel/jira-cli/api"
 	"github.com/ankitpokhrel/jira-cli/pkg/jira"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
 	"github.com/ankitpokhrel/jira-cli/pkg/tui"
 )
 
@@ -60,13 +61,14 @@ func (l IssueList) Render() error {
 		tui.WithViewModeFunc(func(r, c int, _ interface{}) (func() interface{}, func(interface{}) (string, error)) {
 			dataFn := func() interface{} {
 				ci := getKeyColumnIndex(data[0])
-				issue, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci])
-				return issue
+				iss, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci], issue.NewNumCommentsFilter(1))
+				return iss
 			}
 			renderFn := func(i interface{}) (string, error) {
 				iss := Issue{
-					Server: l.Server,
-					Data:   i.(*jira.Issue),
+					Server:  l.Server,
+					Data:    i.(*jira.Issue),
+					Options: IssueOption{NumComments: 1},
 				}
 				return iss.RenderedOut(renderer)
 			}
diff --git a/internal/view/sprint.go b/internal/view/sprint.go
index 0930784..c9da8a7 100644
--- a/internal/view/sprint.go
+++ b/internal/view/sprint.go
@@ -11,6 +11,7 @@ import (
 	"github.com/ankitpokhrel/jira-cli/api"
 	"github.com/ankitpokhrel/jira-cli/internal/cmdutil"
 	"github.com/ankitpokhrel/jira-cli/pkg/jira"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
 	"github.com/ankitpokhrel/jira-cli/pkg/tui"
 )
 
@@ -50,13 +51,14 @@ func (sl SprintList) Render() error {
 				dataFn := func() interface{} {
 					data := d.(tui.TableData)
 					ci := getKeyColumnIndex(data[0])
-					issue, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci])
-					return issue
+					iss, _ := api.ProxyGetIssue(api.Client(jira.Config{}), data[r][ci], issue.NewNumCommentsFilter(1))
+					return iss
 				}
 				renderFn := func(i interface{}) (string, error) {
 					iss := Issue{
-						Server: sl.Server,
-						Data:   i.(*jira.Issue),
+						Server:  sl.Server,
+						Data:    i.(*jira.Issue),
+						Options: IssueOption{NumComments: 1},
 					}
 					return iss.RenderedOut(renderer)
 				}
diff --git a/pkg/jira/filter/doc.go b/pkg/jira/filter/doc.go
new file mode 100644
index 0000000..4509082
--- /dev/null
+++ b/pkg/jira/filter/doc.go
@@ -0,0 +1,3 @@
+// Package filter provides a way to pass optional parameters to client methods.
+// To create a new filter, you will need to satisfy `Filter` interface.
+package filter
diff --git a/pkg/jira/filter/filter.go b/pkg/jira/filter/filter.go
new file mode 100644
index 0000000..fade181
--- /dev/null
+++ b/pkg/jira/filter/filter.go
@@ -0,0 +1,36 @@
+package filter
+
+// Filter groups filterable config.
+type Filter interface {
+	Key() Key
+	Val() interface{}
+}
+
+// Key represents filter key for issue.
+type Key string
+
+// Collection is a group of unique filters.
+type Collection []Filter
+
+// Get returns filter value as it is passed.
+func (flt Collection) Get(key Key) interface{} {
+	for _, f := range flt {
+		if f.Key() == key {
+			return f.Val()
+		}
+	}
+	return nil
+}
+
+// GetInt returns filter value as an integer.
+func (flt Collection) GetInt(key Key) int {
+	for _, f := range flt {
+		if f.Key() != key {
+			continue
+		}
+		if v, ok := f.Val().(uint); ok {
+			return int(v)
+		}
+	}
+	return 0
+}
diff --git a/pkg/jira/filter/issue/num_comments.go b/pkg/jira/filter/issue/num_comments.go
new file mode 100644
index 0000000..046cbab
--- /dev/null
+++ b/pkg/jira/filter/issue/num_comments.go
@@ -0,0 +1,32 @@
+package issue
+
+import (
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter"
+)
+
+// KeyIssueNumComments is a filter key for issue comments.
+const KeyIssueNumComments = filter.Key("issue-num-comments")
+
+// NumCommentsFilter is a filter for issue comments.
+type NumCommentsFilter struct {
+	key   filter.Key
+	value uint
+}
+
+// NewNumCommentsFilter constructs a filter to limit number of comments.
+func NewNumCommentsFilter(value uint) NumCommentsFilter {
+	return NumCommentsFilter{
+		key:   KeyIssueNumComments,
+		value: value,
+	}
+}
+
+// Key returns key of this filter.
+func (ncf NumCommentsFilter) Key() filter.Key {
+	return ncf.key
+}
+
+// Val returns value of this filter.
+func (ncf NumCommentsFilter) Val() interface{} {
+	return ncf.value
+}
diff --git a/pkg/jira/issue.go b/pkg/jira/issue.go
index 461f5a2..668e6f5 100644
--- a/pkg/jira/issue.go
+++ b/pkg/jira/issue.go
@@ -6,7 +6,10 @@ import (
 	"fmt"
 	"net/http"
 
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
+
 	"github.com/ankitpokhrel/jira-cli/pkg/adf"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter"
 	"github.com/ankitpokhrel/jira-cli/pkg/md"
 )
 
@@ -22,16 +25,16 @@ const (
 )
 
 // GetIssue fetches issue details using GET /issue/{key} endpoint.
-func (c *Client) GetIssue(key string) (*Issue, error) {
-	return c.getIssue(key, apiVersion3)
+func (c *Client) GetIssue(key string, opts ...filter.Filter) (*Issue, error) {
+	return c.getIssue(key, apiVersion3, opts)
 }
 
 // GetIssueV2 fetches issue details using v2 version of Jira GET /issue/{key} endpoint.
-func (c *Client) GetIssueV2(key string) (*Issue, error) {
-	return c.getIssue(key, apiVersion2)
+func (c *Client) GetIssueV2(key string, opts ...filter.Filter) (*Issue, error) {
+	return c.getIssue(key, apiVersion2, opts)
 }
 
-func (c *Client) getIssue(key, ver string) (*Issue, error) {
+func (c *Client) getIssue(key, ver string, opts filter.Collection) (*Issue, error) {
 	path := fmt.Sprintf("/issue/%s", key)
 
 	var (
@@ -66,11 +69,14 @@ func (c *Client) getIssue(key, ver string) (*Issue, error) {
 	if ver == apiVersion3 {
 		out.Fields.Description = ifaceToADF(out.Fields.Description)
 
-		if out.Fields.Comment.Total > 0 {
-			// We are only interested in top most comment at the moment so let's just unmarshal that one.
-			out.Fields.Comment.Comments[out.Fields.Comment.Total-1].Body = ifaceToADF(
-				out.Fields.Comment.Comments[out.Fields.Comment.Total-1].Body,
-			)
+		total := out.Fields.Comment.Total
+		limit := opts.GetInt(issue.KeyIssueNumComments)
+		if limit > total {
+			limit = total
+		}
+		for i := total - 1; i >= total-limit; i-- {
+			body := out.Fields.Comment.Comments[i].Body
+			out.Fields.Comment.Comments[i].Body = ifaceToADF(body)
 		}
 	}
 


Test patch:
diff --git a/internal/view/issue_test.go b/internal/view/issue_test.go
index 5c3985f..eda16d3 100644
--- a/internal/view/issue_test.go
+++ b/internal/view/issue_test.go
@@ -77,7 +77,7 @@ func TestIssueDetailsRenderInPlainView(t *testing.T) {
 		Display: DisplayFormat{Plain: true},
 	}
 
-	expected := "🐞 Bug  ✅ Done  ⌛ Sun, 13 Dec 20  👷 Person A  🔑️ TEST-1  💭 0 comments  \U0001F9F5 0 linked issues\n# This is a test\n⏱️  Sun, 13 Dec 20  🔎 Person Z  🚀 High  📦 BE, FE  🏷️  None  👀 You + 3 watchers\n\n------------------------ Description ------------------------\n\nTest description\n\n"
+	expected := "🐞 Bug  ✅ Done  ⌛ Sun, 13 Dec 20  👷 Person A  🔑️ TEST-1  💭 0 comments  \U0001F9F5 0 linked\n# This is a test\n⏱️  Sun, 13 Dec 20  🔎 Person Z  🚀 High  📦 BE, FE  🏷️  None  👀 You + 3 watchers\n\n------------------------ Description ------------------------\n\nTest description\n\n\n"
 	if xterm256() {
 		expected += "\x1b[38;5;242mView this issue on Jira: https://test.local/browse/TEST-1\x1b[m"
 	} else {
@@ -198,13 +198,16 @@ func TestIssueDetailsWithV2Description(t *testing.T) {
 		Server:  "https://test.local",
 		Data:    data,
 		Display: DisplayFormat{Plain: true},
+		Options: IssueOption{NumComments: 2},
 	}
 	assert.NoError(t, issue.renderPlain(&b))
 
-	expected := "🐞 Bug  ✅ Done  ⌛ Sun, 13 Dec 20  👷 Person A  🔑️ TEST-1  💭 3 comments  \U0001F9F5 2 linked issues\n# This is a test\n⏱️  Sun, 13 Dec 20  🔎 Person Z  🚀 High  📦 BE, FE  🏷️  None  👀 0 watchers\n\n------------------------ Description ------------------------\n\n# Title\n## Subtitle\nThis is a **bold** and _italic_ text with [a link](https://ankit.pl) in between.\n\n\n------------------------ Linked Issues ------------------------\n\n\n  BLOCKS\n\n    TEST-2 Something is broken   • Bug • High   • TO DO\n\n  RELATES TO\n\n    TEST-3 Everything is on fire • Bug • Urgent • Done \n\n\n\n------------------------ 3 comments ------------------------\n\n\n  Person C • Wed, 24 Nov 21 • Latest comment\n\nTest comment C\n"
+	expected := "🐞 Bug  ✅ Done  ⌛ Sun, 13 Dec 20  👷 Person A  🔑️ TEST-1  💭 3 comments  \U0001F9F5 2 linked\n# This is a test\n⏱️  Sun, 13 Dec 20  🔎 Person Z  🚀 High  📦 BE, FE  🏷️  None  👀 0 watchers\n\n------------------------ Description ------------------------\n\n# Title\n## Subtitle\nThis is a **bold** and _italic_ text with [a link](https://ankit.pl) in between.\n\n\n------------------------ Linked Issues ------------------------\n\n\n BLOCKS\n\n  TEST-2 Something is broken   • Bug • High   • TO DO\n\n RELATES TO\n\n  TEST-3 Everything is on fire • Bug • Urgent • Done \n\n\n\n------------------------ 3 Comments ------------------------\n\n\n Person C • Wed, 24 Nov 21 • Latest comment\n\nTest comment C\n\n\n\n Person B • Tue, 23 Nov 21\n\nTest comment B\n\n"
 	if xterm256() {
+		expected += "\x1b[38;5;242mUse --comments <limit> with `jira issue view` to load more comments\x1b[m\n\n"
 		expected += "\x1b[38;5;242mView this issue on Jira: https://test.local/browse/TEST-1\x1b[m"
 	} else {
+		expected += "\x1b[0;90mUse --comments <limit> with `jira issue view` to load more comments\x1b[0m\n\n"
 		expected += "\x1b[0;90mView this issue on Jira: https://test.local/browse/TEST-1\x1b[0m"
 	}
 	actual := issue.String()
diff --git a/pkg/jira/filter/filter_test.go b/pkg/jira/filter/filter_test.go
new file mode 100644
index 0000000..f6af6ee
--- /dev/null
+++ b/pkg/jira/filter/filter_test.go
@@ -0,0 +1,22 @@
+package filter_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter"
+	"github.com/ankitpokhrel/jira-cli/pkg/jira/filter/issue"
+)
+
+func TestCollectionGet(t *testing.T) {
+	cltn := filter.Collection{issue.NewNumCommentsFilter(5)}
+	assert.Equal(t, uint(5), cltn.Get(cltn[0].Key()))
+	assert.Nil(t, cltn.Get("unknown"))
+}
+
+func TestCollectionGetInt(t *testing.T) {
+	cltn := filter.Collection{issue.NewNumCommentsFilter(5)}
+	assert.Equal(t, 5, cltn.GetInt(cltn[0].Key()))
+	assert.Equal(t, 0, cltn.GetInt("unknown"))
+}