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