Skip to content

Commit b0ae09e

Browse files
authored
Merge pull request cli#2535 from cli/create-comments
Create issue comments
2 parents 3985577 + 723e9e3 commit b0ae09e

File tree

4 files changed

+523
-0
lines changed

4 files changed

+523
-0
lines changed

api/queries_comments.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/cli/cli/internal/ghrepo"
88
"github.com/shurcooL/githubv4"
9+
"github.com/shurcooL/graphql"
910
)
1011

1112
type Comments struct {
@@ -99,3 +100,35 @@ func CommentsForPullRequest(client *Client, repo ghrepo.Interface, pr *PullReque
99100

100101
return &Comments{Nodes: comments, TotalCount: len(comments)}, nil
101102
}
103+
104+
type CommentCreateInput struct {
105+
Body string
106+
SubjectId string
107+
}
108+
109+
func CommentCreate(client *Client, repoHost string, params CommentCreateInput) (string, error) {
110+
var mutation struct {
111+
AddComment struct {
112+
CommentEdge struct {
113+
Node struct {
114+
URL string
115+
}
116+
}
117+
} `graphql:"addComment(input: $input)"`
118+
}
119+
120+
variables := map[string]interface{}{
121+
"input": githubv4.AddCommentInput{
122+
Body: githubv4.String(params.Body),
123+
SubjectID: graphql.ID(params.SubjectId),
124+
},
125+
}
126+
127+
gql := graphQLClient(client.http, repoHost)
128+
err := gql.MutateNamed(context.Background(), "CommentCreate", &mutation, variables)
129+
if err != nil {
130+
return "", err
131+
}
132+
133+
return mutation.AddComment.CommentEdge.Node.URL, nil
134+
}

pkg/cmd/issue/comment/comment.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package comment
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/AlecAivazis/survey/v2"
9+
"github.com/MakeNowJust/heredoc"
10+
"github.com/cli/cli/api"
11+
"github.com/cli/cli/internal/config"
12+
"github.com/cli/cli/internal/ghrepo"
13+
"github.com/cli/cli/pkg/cmd/issue/shared"
14+
"github.com/cli/cli/pkg/cmdutil"
15+
"github.com/cli/cli/pkg/iostreams"
16+
"github.com/cli/cli/pkg/surveyext"
17+
"github.com/cli/cli/utils"
18+
"github.com/spf13/cobra"
19+
)
20+
21+
type CommentOptions struct {
22+
HttpClient func() (*http.Client, error)
23+
IO *iostreams.IOStreams
24+
BaseRepo func() (ghrepo.Interface, error)
25+
EditSurvey func() (string, error)
26+
InputTypeSurvey func() (inputType, error)
27+
ConfirmSubmitSurvey func() (bool, error)
28+
OpenInBrowser func(string) error
29+
30+
SelectorArg string
31+
Interactive bool
32+
InputType inputType
33+
Body string
34+
}
35+
36+
type inputType int
37+
38+
const (
39+
inputTypeEditor inputType = iota
40+
inputTypeInline
41+
inputTypeWeb
42+
)
43+
44+
func NewCmdComment(f *cmdutil.Factory, runF func(*CommentOptions) error) *cobra.Command {
45+
opts := &CommentOptions{
46+
IO: f.IOStreams,
47+
HttpClient: f.HttpClient,
48+
EditSurvey: editSurvey(f.Config, f.IOStreams),
49+
InputTypeSurvey: inputTypeSurvey,
50+
ConfirmSubmitSurvey: confirmSubmitSurvey,
51+
OpenInBrowser: utils.OpenInBrowser,
52+
}
53+
54+
var webMode bool
55+
var editorMode bool
56+
57+
cmd := &cobra.Command{
58+
Use: "comment {<number> | <url>}",
59+
Short: "Create a new issue comment",
60+
Example: heredoc.Doc(`
61+
$ gh issue comment 22 --body "I was able to reproduce this issue, lets fix it."
62+
`),
63+
Args: cobra.ExactArgs(1),
64+
RunE: func(cmd *cobra.Command, args []string) error {
65+
// support `-R, --repo` override
66+
opts.BaseRepo = f.BaseRepo
67+
opts.SelectorArg = args[0]
68+
69+
inputFlags := 0
70+
if cmd.Flags().Changed("body") {
71+
opts.InputType = inputTypeInline
72+
inputFlags++
73+
}
74+
if webMode {
75+
opts.InputType = inputTypeWeb
76+
inputFlags++
77+
}
78+
if editorMode {
79+
opts.InputType = inputTypeEditor
80+
inputFlags++
81+
}
82+
83+
if inputFlags == 0 {
84+
if !opts.IO.CanPrompt() {
85+
return &cmdutil.FlagError{Err: errors.New("--body or --web required when not running interactively")}
86+
}
87+
opts.Interactive = true
88+
} else if inputFlags == 1 {
89+
if !opts.IO.CanPrompt() && opts.InputType == inputTypeEditor {
90+
return &cmdutil.FlagError{Err: errors.New("--body or --web required when not running interactively")}
91+
}
92+
} else if inputFlags > 1 {
93+
return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of --body, --editor, or --web")}
94+
}
95+
96+
if runF != nil {
97+
return runF(opts)
98+
}
99+
return commentRun(opts)
100+
},
101+
}
102+
103+
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.")
104+
cmd.Flags().BoolVarP(&editorMode, "editor", "e", false, "Add body using editor")
105+
cmd.Flags().BoolVarP(&webMode, "web", "w", false, "Add body in browser")
106+
107+
return cmd
108+
}
109+
110+
func commentRun(opts *CommentOptions) error {
111+
httpClient, err := opts.HttpClient()
112+
if err != nil {
113+
return err
114+
}
115+
apiClient := api.NewClientFromHTTP(httpClient)
116+
117+
issue, baseRepo, err := shared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg)
118+
if err != nil {
119+
return err
120+
}
121+
122+
if opts.Interactive {
123+
inputType, err := opts.InputTypeSurvey()
124+
if err != nil {
125+
return err
126+
}
127+
opts.InputType = inputType
128+
}
129+
130+
switch opts.InputType {
131+
case inputTypeWeb:
132+
openURL := issue.URL + "#issuecomment-new"
133+
if opts.IO.IsStdoutTTY() {
134+
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
135+
}
136+
return opts.OpenInBrowser(openURL)
137+
case inputTypeEditor:
138+
body, err := opts.EditSurvey()
139+
if err != nil {
140+
return err
141+
}
142+
opts.Body = body
143+
}
144+
145+
if opts.Interactive {
146+
cont, err := opts.ConfirmSubmitSurvey()
147+
if err != nil {
148+
return err
149+
}
150+
if !cont {
151+
return fmt.Errorf("Discarding...")
152+
}
153+
}
154+
155+
params := api.CommentCreateInput{Body: opts.Body, SubjectId: issue.ID}
156+
url, err := api.CommentCreate(apiClient, baseRepo.RepoHost(), params)
157+
if err != nil {
158+
return err
159+
}
160+
fmt.Fprintln(opts.IO.Out, url)
161+
return nil
162+
}
163+
164+
var inputTypeSurvey = func() (inputType, error) {
165+
var result int
166+
inputTypeQuestion := &survey.Select{
167+
Message: "Where do you want to draft your comment?",
168+
Options: []string{"Editor", "Web"},
169+
}
170+
err := survey.AskOne(inputTypeQuestion, &result)
171+
if err != nil {
172+
return 0, err
173+
}
174+
175+
if result == 0 {
176+
return inputTypeEditor, nil
177+
} else {
178+
return inputTypeWeb, nil
179+
}
180+
}
181+
182+
var confirmSubmitSurvey = func() (bool, error) {
183+
var confirm bool
184+
submit := &survey.Confirm{
185+
Message: "Submit?",
186+
Default: true,
187+
}
188+
err := survey.AskOne(submit, &confirm)
189+
return confirm, err
190+
}
191+
192+
var editSurvey = func(cf func() (config.Config, error), io *iostreams.IOStreams) func() (string, error) {
193+
return func() (string, error) {
194+
editorCommand, err := cmdutil.DetermineEditor(cf)
195+
if err != nil {
196+
return "", err
197+
}
198+
return surveyext.Edit(editorCommand, "*.md", "", io.In, io.Out, io.ErrOut, nil)
199+
}
200+
}

0 commit comments

Comments
 (0)