github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/shared/comments.go (about) 1 package shared 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/ungtb10d/cli/v2/api" 10 "github.com/ungtb10d/cli/v2/internal/text" 11 "github.com/ungtb10d/cli/v2/pkg/iostreams" 12 "github.com/ungtb10d/cli/v2/pkg/markdown" 13 ) 14 15 type Comment interface { 16 Identifier() string 17 AuthorLogin() string 18 Association() string 19 Content() string 20 Created() time.Time 21 HiddenReason() string 22 IsEdited() bool 23 IsHidden() bool 24 Link() string 25 Reactions() api.ReactionGroups 26 Status() string 27 } 28 29 func RawCommentList(comments api.Comments, reviews api.PullRequestReviews) string { 30 sortedComments := sortComments(comments, reviews) 31 var b strings.Builder 32 for _, comment := range sortedComments { 33 fmt.Fprint(&b, formatRawComment(comment)) 34 } 35 return b.String() 36 } 37 38 func formatRawComment(comment Comment) string { 39 if comment.IsHidden() { 40 return "" 41 } 42 var b strings.Builder 43 fmt.Fprintf(&b, "author:\t%s\n", comment.AuthorLogin()) 44 fmt.Fprintf(&b, "association:\t%s\n", strings.ToLower(comment.Association())) 45 fmt.Fprintf(&b, "edited:\t%t\n", comment.IsEdited()) 46 fmt.Fprintf(&b, "status:\t%s\n", formatRawCommentStatus(comment.Status())) 47 fmt.Fprintln(&b, "--") 48 fmt.Fprintln(&b, comment.Content()) 49 fmt.Fprintln(&b, "--") 50 return b.String() 51 } 52 53 func CommentList(io *iostreams.IOStreams, comments api.Comments, reviews api.PullRequestReviews, preview bool) (string, error) { 54 sortedComments := sortComments(comments, reviews) 55 if preview && len(sortedComments) > 0 { 56 sortedComments = sortedComments[len(sortedComments)-1:] 57 } 58 var b strings.Builder 59 cs := io.ColorScheme() 60 totalCount := comments.TotalCount + reviews.TotalCount 61 retrievedCount := len(sortedComments) 62 hiddenCount := totalCount - retrievedCount 63 64 if preview && hiddenCount > 0 { 65 fmt.Fprint(&b, cs.Gray(fmt.Sprintf("———————— Not showing %s ————————", text.Pluralize(hiddenCount, "comment")))) 66 fmt.Fprintf(&b, "\n\n\n") 67 } 68 69 for i, comment := range sortedComments { 70 last := i+1 == retrievedCount 71 cmt, err := formatComment(io, comment, last) 72 if err != nil { 73 return "", err 74 } 75 fmt.Fprint(&b, cmt) 76 if last { 77 fmt.Fprintln(&b) 78 } 79 } 80 81 if preview && hiddenCount > 0 { 82 fmt.Fprint(&b, cs.Gray("Use --comments to view the full conversation")) 83 fmt.Fprintln(&b) 84 } 85 86 return b.String(), nil 87 } 88 89 func formatComment(io *iostreams.IOStreams, comment Comment, newest bool) (string, error) { 90 var b strings.Builder 91 cs := io.ColorScheme() 92 93 if comment.IsHidden() { 94 return cs.Bold(formatHiddenComment(comment)), nil 95 } 96 97 // Header 98 fmt.Fprint(&b, cs.Bold(comment.AuthorLogin())) 99 if comment.Status() != "" { 100 fmt.Fprint(&b, formatCommentStatus(cs, comment.Status())) 101 } 102 if comment.Association() != "NONE" { 103 fmt.Fprint(&b, cs.Boldf(" (%s)", text.Title(comment.Association()))) 104 } 105 fmt.Fprint(&b, cs.Boldf(" • %s", text.FuzzyAgoAbbr(time.Now(), comment.Created()))) 106 if comment.IsEdited() { 107 fmt.Fprint(&b, cs.Bold(" • Edited")) 108 } 109 if newest { 110 fmt.Fprint(&b, cs.Bold(" • ")) 111 fmt.Fprint(&b, cs.CyanBold("Newest comment")) 112 } 113 fmt.Fprintln(&b) 114 115 // Reactions 116 if reactions := ReactionGroupList(comment.Reactions()); reactions != "" { 117 fmt.Fprint(&b, reactions) 118 fmt.Fprintln(&b) 119 } 120 121 // Body 122 var md string 123 var err error 124 if comment.Content() == "" { 125 md = fmt.Sprintf("\n %s\n\n", cs.Gray("No body provided")) 126 } else { 127 md, err = markdown.Render(comment.Content(), 128 markdown.WithTheme(io.TerminalTheme()), 129 markdown.WithWrap(io.TerminalWidth())) 130 if err != nil { 131 return "", err 132 } 133 } 134 fmt.Fprint(&b, md) 135 136 // Footer 137 if comment.Link() != "" { 138 fmt.Fprintf(&b, cs.Gray("View the full review: %s\n\n"), comment.Link()) 139 } 140 141 return b.String(), nil 142 } 143 144 func sortComments(cs api.Comments, rs api.PullRequestReviews) []Comment { 145 comments := cs.Nodes 146 reviews := rs.Nodes 147 var sorted []Comment = make([]Comment, len(comments)+len(reviews)) 148 149 var i int 150 for _, c := range comments { 151 sorted[i] = c 152 i++ 153 } 154 for _, r := range reviews { 155 sorted[i] = r 156 i++ 157 } 158 159 sort.Slice(sorted, func(i, j int) bool { 160 return sorted[i].Created().Before(sorted[j].Created()) 161 }) 162 163 return sorted 164 } 165 166 const ( 167 approvedStatus = "APPROVED" 168 changesRequestedStatus = "CHANGES_REQUESTED" 169 commentedStatus = "COMMENTED" 170 dismissedStatus = "DISMISSED" 171 ) 172 173 func formatCommentStatus(cs *iostreams.ColorScheme, status string) string { 174 switch status { 175 case approvedStatus: 176 return fmt.Sprintf(" %s", cs.Green("approved")) 177 case changesRequestedStatus: 178 return fmt.Sprintf(" %s", cs.Red("requested changes")) 179 case commentedStatus, dismissedStatus: 180 return fmt.Sprintf(" %s", strings.ToLower(status)) 181 } 182 183 return "" 184 } 185 186 func formatRawCommentStatus(status string) string { 187 if status == approvedStatus || 188 status == changesRequestedStatus || 189 status == commentedStatus || 190 status == dismissedStatus { 191 return strings.ReplaceAll(strings.ToLower(status), "_", " ") 192 } 193 194 return "none" 195 } 196 197 func formatHiddenComment(comment Comment) string { 198 var b strings.Builder 199 fmt.Fprint(&b, comment.AuthorLogin()) 200 if comment.Association() != "NONE" { 201 fmt.Fprintf(&b, " (%s)", text.Title(comment.Association())) 202 } 203 fmt.Fprintf(&b, " • This comment has been marked as %s\n\n", comment.HiddenReason()) 204 return b.String() 205 }