mirror of https://gitlab.com/gitlab-org/cli.git
233 lines
6.8 KiB
Go
233 lines
6.8 KiB
Go
package view
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
|
|
|
"gitlab.com/gitlab-org/cli/commands/issue/issueutils"
|
|
|
|
"gitlab.com/gitlab-org/cli/api"
|
|
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
|
"gitlab.com/gitlab-org/cli/pkg/utils"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/spf13/cobra"
|
|
"github.com/xanzy/go-gitlab"
|
|
)
|
|
|
|
type ViewOpts struct {
|
|
ShowComments bool
|
|
ShowSystemLogs bool
|
|
OpenInBrowser bool
|
|
Web bool
|
|
|
|
CommentPageNumber int
|
|
CommentLimit int
|
|
|
|
Notes []*gitlab.Note
|
|
Issue *gitlab.Issue
|
|
|
|
IO *iostreams.IOStreams
|
|
}
|
|
|
|
func NewCmdView(f *cmdutils.Factory) *cobra.Command {
|
|
opts := &ViewOpts{
|
|
IO: f.IO,
|
|
}
|
|
var issueViewCmd = &cobra.Command{
|
|
Use: "view <id>",
|
|
Short: `Display the title, body, and other information about an issue.`,
|
|
Long: ``,
|
|
Aliases: []string{"show"},
|
|
Example: heredoc.Doc(`
|
|
glab issue view 123
|
|
glab issue show 123
|
|
glab issue view --web 123
|
|
glab issue view --comments 123
|
|
glab issue view https://gitlab.com/profclems/glab/-/issues/123
|
|
`),
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
apiClient, err := f.HttpClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg, _ := f.Config()
|
|
|
|
issue, baseRepo, err := issueutils.IssueFromArg(apiClient, f.BaseRepo, args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
opts.Issue = issue
|
|
|
|
//open in browser if --web flag is specified
|
|
if opts.Web {
|
|
if f.IO.IsaTTY && f.IO.IsErrTTY {
|
|
fmt.Fprintf(opts.IO.StdErr, "Opening %s in your browser.\n", utils.DisplayURL(opts.Issue.WebURL))
|
|
}
|
|
|
|
browser, _ := cfg.Get(baseRepo.RepoHost(), "browser")
|
|
return utils.OpenInBrowser(opts.Issue.WebURL, browser)
|
|
}
|
|
|
|
if opts.ShowComments {
|
|
l := &gitlab.ListIssueNotesOptions{
|
|
Sort: gitlab.String("asc"),
|
|
}
|
|
if opts.CommentPageNumber != 0 {
|
|
l.Page = opts.CommentPageNumber
|
|
}
|
|
if opts.CommentLimit != 0 {
|
|
l.PerPage = opts.CommentLimit
|
|
}
|
|
opts.Notes, err = api.ListIssueNotes(apiClient, baseRepo.FullName(), opts.Issue.IID, l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
glamourStyle, _ := cfg.Get(baseRepo.RepoHost(), "glamour_style")
|
|
f.IO.ResolveBackgroundColor(glamourStyle)
|
|
err = f.IO.StartPager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.IO.StopPager()
|
|
if f.IO.IsErrTTY && f.IO.IsaTTY {
|
|
return printTTYIssuePreview(opts)
|
|
}
|
|
return printRawIssuePreview(opts)
|
|
},
|
|
}
|
|
|
|
issueViewCmd.Flags().BoolVarP(&opts.ShowComments, "comments", "c", false, "Show issue comments and activities")
|
|
issueViewCmd.Flags().BoolVarP(&opts.ShowSystemLogs, "system-logs", "s", false, "Show system activities / logs")
|
|
issueViewCmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open issue in a browser. Uses default browser or browser specified in BROWSER variable")
|
|
issueViewCmd.Flags().IntVarP(&opts.CommentPageNumber, "page", "p", 1, "Page number")
|
|
issueViewCmd.Flags().IntVarP(&opts.CommentLimit, "per-page", "P", 20, "Number of items to list per page")
|
|
|
|
return issueViewCmd
|
|
}
|
|
|
|
func labelsList(opts *ViewOpts) string {
|
|
var labels string
|
|
for _, l := range opts.Issue.Labels {
|
|
labels += " " + l + ","
|
|
}
|
|
return strings.Trim(labels, ", ")
|
|
}
|
|
|
|
func assigneesList(opts *ViewOpts) string {
|
|
var assignees string
|
|
for _, a := range opts.Issue.Assignees {
|
|
assignees += " " + a.Username + ","
|
|
}
|
|
return strings.Trim(assignees, ", ")
|
|
}
|
|
|
|
func issueState(opts *ViewOpts, c *iostreams.ColorPalette) (state string) {
|
|
if opts.Issue.State == "opened" {
|
|
state = c.Green("open")
|
|
} else if opts.Issue.State == "locked" {
|
|
state = c.Blue(opts.Issue.State)
|
|
} else {
|
|
state = c.Red(opts.Issue.State)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func printTTYIssuePreview(opts *ViewOpts) error {
|
|
c := opts.IO.Color()
|
|
issueTimeAgo := utils.TimeToPrettyTimeAgo(*opts.Issue.CreatedAt)
|
|
// Header
|
|
fmt.Fprint(opts.IO.StdOut, issueState(opts, c))
|
|
fmt.Fprintf(opts.IO.StdOut, c.Gray(" • opened by %s %s\n"), opts.Issue.Author.Username, issueTimeAgo)
|
|
fmt.Fprint(opts.IO.StdOut, c.Bold(opts.Issue.Title))
|
|
fmt.Fprintf(opts.IO.StdOut, c.Gray(" #%d"), opts.Issue.IID)
|
|
fmt.Fprintln(opts.IO.StdOut)
|
|
|
|
// Description
|
|
if opts.Issue.Description != "" {
|
|
opts.Issue.Description, _ = utils.RenderMarkdown(opts.Issue.Description, opts.IO.BackgroundColor())
|
|
fmt.Fprintln(opts.IO.StdOut, opts.Issue.Description)
|
|
}
|
|
|
|
fmt.Fprintf(opts.IO.StdOut, c.Gray("\n%d upvotes • %d downvotes • %d comments\n"), opts.Issue.Upvotes, opts.Issue.Downvotes, opts.Issue.UserNotesCount)
|
|
|
|
// Meta information
|
|
if labels := labelsList(opts); labels != "" {
|
|
fmt.Fprint(opts.IO.StdOut, c.Bold("Labels: "))
|
|
fmt.Fprintln(opts.IO.StdOut, labels)
|
|
}
|
|
if assignees := assigneesList(opts); assignees != "" {
|
|
fmt.Fprint(opts.IO.StdOut, c.Bold("Assignees: "))
|
|
fmt.Fprintln(opts.IO.StdOut, assignees)
|
|
}
|
|
if opts.Issue.Milestone != nil {
|
|
fmt.Fprint(opts.IO.StdOut, c.Bold("Milestone: "))
|
|
fmt.Fprintln(opts.IO.StdOut, opts.Issue.Milestone.Title)
|
|
}
|
|
if opts.Issue.State == "closed" {
|
|
fmt.Fprintf(opts.IO.StdOut, "Closed By: %s %s\n", opts.Issue.ClosedBy.Username, issueTimeAgo)
|
|
}
|
|
|
|
// Comments
|
|
if opts.ShowComments {
|
|
fmt.Fprintln(opts.IO.StdOut, heredoc.Doc(`
|
|
--------------------------------------------
|
|
Comments / Notes
|
|
--------------------------------------------
|
|
`))
|
|
if len(opts.Notes) > 0 {
|
|
for _, note := range opts.Notes {
|
|
if note.System && !opts.ShowSystemLogs {
|
|
continue
|
|
}
|
|
createdAt := utils.TimeToPrettyTimeAgo(*note.CreatedAt)
|
|
fmt.Fprint(opts.IO.StdOut, note.Author.Username)
|
|
if note.System {
|
|
fmt.Fprintf(opts.IO.StdOut, " %s ", note.Body)
|
|
fmt.Fprintln(opts.IO.StdOut, c.Gray(createdAt))
|
|
} else {
|
|
body, _ := utils.RenderMarkdown(note.Body, opts.IO.BackgroundColor())
|
|
fmt.Fprint(opts.IO.StdOut, " commented ")
|
|
fmt.Fprintf(opts.IO.StdOut, c.Gray("%s\n"), createdAt)
|
|
fmt.Fprintln(opts.IO.StdOut, utils.Indent(body, " "))
|
|
}
|
|
fmt.Fprintln(opts.IO.StdOut)
|
|
}
|
|
} else {
|
|
fmt.Fprintln(opts.IO.StdOut, "There are no comments on this issue")
|
|
}
|
|
}
|
|
|
|
fmt.Fprintln(opts.IO.StdOut)
|
|
fmt.Fprintf(opts.IO.StdOut, c.Gray("View this issue on GitLab: %s\n"), opts.Issue.WebURL)
|
|
|
|
return nil
|
|
}
|
|
|
|
func printRawIssuePreview(opts *ViewOpts) error {
|
|
assignees := assigneesList(opts)
|
|
labels := labelsList(opts)
|
|
|
|
fmt.Fprintf(opts.IO.StdOut, "title:\t%s\n", opts.Issue.Title)
|
|
fmt.Fprintf(opts.IO.StdOut, "state:\t%s\n", issueState(opts, opts.IO.Color()))
|
|
fmt.Fprintf(opts.IO.StdOut, "author:\t%s\n", opts.Issue.Author.Username)
|
|
fmt.Fprintf(opts.IO.StdOut, "labels:\t%s\n", labels)
|
|
fmt.Fprintf(opts.IO.StdOut, "comments:\t%d\n", opts.Issue.UserNotesCount)
|
|
fmt.Fprintf(opts.IO.StdOut, "assignees:\t%s\n", assignees)
|
|
if opts.Issue.Milestone != nil {
|
|
fmt.Fprintf(opts.IO.StdOut, "milestone:\t%s\n", opts.Issue.Milestone.Title)
|
|
}
|
|
|
|
fmt.Fprintln(opts.IO.StdOut, "--")
|
|
fmt.Fprintln(opts.IO.StdOut, opts.Issue.Description)
|
|
return nil
|
|
}
|