X Tutup
package view import ( "fmt" "io" "net/http" "strings" "time" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/api" "github.com/cli/cli/internal/ghrepo" "github.com/cli/cli/pkg/cmd/issue/shared" issueShared "github.com/cli/cli/pkg/cmd/issue/shared" prShared "github.com/cli/cli/pkg/cmd/pr/shared" "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" "github.com/cli/cli/pkg/markdown" "github.com/cli/cli/utils" "github.com/spf13/cobra" ) type ViewOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams BaseRepo func() (ghrepo.Interface, error) SelectorArg string WebMode bool Comments bool Now func() time.Time } func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command { opts := &ViewOptions{ IO: f.IOStreams, HttpClient: f.HttpClient, Now: time.Now, } cmd := &cobra.Command{ Use: "view { | }", Short: "View an issue", Long: heredoc.Doc(` Display the title, body, and other information about an issue. With '--web', open the issue in a web browser instead. `), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { // support `-R, --repo` override opts.BaseRepo = f.BaseRepo if len(args) > 0 { opts.SelectorArg = args[0] } if runF != nil { return runF(opts) } return viewRun(opts) }, } cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open an issue in the browser") cmd.Flags().BoolVarP(&opts.Comments, "comments", "c", false, "View issue comments") return cmd } func viewRun(opts *ViewOptions) error { httpClient, err := opts.HttpClient() if err != nil { return err } apiClient := api.NewClientFromHTTP(httpClient) issue, repo, err := issueShared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg) if err != nil { return err } if opts.WebMode { openURL := issue.URL if opts.IO.IsStdoutTTY() { fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL)) } return utils.OpenInBrowser(openURL) } if opts.Comments { opts.IO.StartProgressIndicator() comments, err := api.CommentsForIssue(apiClient, repo, issue) opts.IO.StopProgressIndicator() if err != nil { return err } issue.Comments = *comments } opts.IO.DetectTerminalTheme() err = opts.IO.StartPager() if err != nil { return err } defer opts.IO.StopPager() if opts.IO.IsStdoutTTY() { return printHumanIssuePreview(opts, issue) } if opts.Comments { fmt.Fprint(opts.IO.Out, prShared.RawCommentList(issue.Comments, api.PullRequestReviews{})) return nil } return printRawIssuePreview(opts.IO.Out, issue) } func printRawIssuePreview(out io.Writer, issue *api.Issue) error { assignees := issueAssigneeList(*issue) labels := shared.IssueLabelList(*issue) projects := issueProjectList(*issue) // Print empty strings for empty values so the number of metadata lines is consistent when // processing many issues with head and grep. fmt.Fprintf(out, "title:\t%s\n", issue.Title) fmt.Fprintf(out, "state:\t%s\n", issue.State) fmt.Fprintf(out, "author:\t%s\n", issue.Author.Login) fmt.Fprintf(out, "labels:\t%s\n", labels) fmt.Fprintf(out, "comments:\t%d\n", issue.Comments.TotalCount) fmt.Fprintf(out, "assignees:\t%s\n", assignees) fmt.Fprintf(out, "projects:\t%s\n", projects) fmt.Fprintf(out, "milestone:\t%s\n", issue.Milestone.Title) fmt.Fprintln(out, "--") fmt.Fprintln(out, issue.Body) return nil } func printHumanIssuePreview(opts *ViewOptions, issue *api.Issue) error { out := opts.IO.Out now := opts.Now() ago := now.Sub(issue.CreatedAt) cs := opts.IO.ColorScheme() // Header (Title and State) fmt.Fprintln(out, cs.Bold(issue.Title)) fmt.Fprintf(out, "%s • %s opened %s • %s\n", issueStateTitleWithColor(cs, issue.State), issue.Author.Login, utils.FuzzyAgo(ago), utils.Pluralize(issue.Comments.TotalCount, "comment"), ) // Reactions if reactions := prShared.ReactionGroupList(issue.ReactionGroups); reactions != "" { fmt.Fprint(out, reactions) fmt.Fprintln(out) } // Metadata if assignees := issueAssigneeList(*issue); assignees != "" { fmt.Fprint(out, cs.Bold("Assignees: ")) fmt.Fprintln(out, assignees) } if labels := shared.IssueLabelList(*issue); labels != "" { fmt.Fprint(out, cs.Bold("Labels: ")) fmt.Fprintln(out, labels) } if projects := issueProjectList(*issue); projects != "" { fmt.Fprint(out, cs.Bold("Projects: ")) fmt.Fprintln(out, projects) } if issue.Milestone.Title != "" { fmt.Fprint(out, cs.Bold("Milestone: ")) fmt.Fprintln(out, issue.Milestone.Title) } // Body var md string var err error if issue.Body == "" { md = fmt.Sprintf("\n %s\n\n", cs.Gray("No description provided")) } else { style := markdown.GetStyle(opts.IO.TerminalTheme()) md, err = markdown.Render(issue.Body, style, "") if err != nil { return err } } fmt.Fprintf(out, "\n%s\n", md) // Comments if issue.Comments.TotalCount > 0 { preview := !opts.Comments comments, err := prShared.CommentList(opts.IO, issue.Comments, api.PullRequestReviews{}, preview) if err != nil { return err } fmt.Fprint(out, comments) } // Footer fmt.Fprintf(out, cs.Gray("View this issue on GitHub: %s\n"), issue.URL) return nil } func issueStateTitleWithColor(cs *iostreams.ColorScheme, state string) string { colorFunc := cs.ColorFromString(prShared.ColorForState(state)) return colorFunc(strings.Title(strings.ToLower(state))) } func issueAssigneeList(issue api.Issue) string { if len(issue.Assignees.Nodes) == 0 { return "" } AssigneeNames := make([]string, 0, len(issue.Assignees.Nodes)) for _, assignee := range issue.Assignees.Nodes { AssigneeNames = append(AssigneeNames, assignee.Login) } list := strings.Join(AssigneeNames, ", ") if issue.Assignees.TotalCount > len(issue.Assignees.Nodes) { list += ", …" } return list } func issueProjectList(issue api.Issue) string { if len(issue.ProjectCards.Nodes) == 0 { return "" } projectNames := make([]string, 0, len(issue.ProjectCards.Nodes)) for _, project := range issue.ProjectCards.Nodes { colName := project.Column.Name if colName == "" { colName = "Awaiting triage" } projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, colName)) } list := strings.Join(projectNames, ", ") if issue.ProjectCards.TotalCount > len(issue.ProjectCards.Nodes) { list += ", …" } return list }
X Tutup