Comment on issues from editor
This commit is contained in:
parent
86eb264277
commit
8ef2bb4d14
4 changed files with 340 additions and 0 deletions
|
|
@ -123,6 +123,25 @@ func IssueCreate(client *Client, repo *Repository, params map[string]interface{}
|
|||
return &result.CreateIssue.Issue, nil
|
||||
}
|
||||
|
||||
func CommentCreate(client *Client, repoHost string, params map[string]interface{}) error {
|
||||
query := `
|
||||
mutation CommentCreate($input: AddCommentInput!) {
|
||||
addComment(input: $input) { clientMutationId }
|
||||
}`
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"input": params,
|
||||
}
|
||||
|
||||
err := client.GraphQL(repoHost, query, variables, nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IssueStatus(client *Client, repo ghrepo.Interface, currentUsername string) (*IssuesPayload, error) {
|
||||
type response struct {
|
||||
Repository struct {
|
||||
|
|
|
|||
202
pkg/cmd/issue/comment/comment.go
Normal file
202
pkg/cmd/issue/comment/comment.go
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package comment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmd/issue/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/cli/cli/pkg/surveyext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CommentOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Body string
|
||||
SelectorArg string
|
||||
Interactive bool
|
||||
Action Action
|
||||
}
|
||||
|
||||
func NewCmdComment(f *cmdutil.Factory, runF func(*CommentOptions) error) *cobra.Command {
|
||||
opts := &CommentOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "comment {<number> | <url>}",
|
||||
Short: "Create comments for the issue",
|
||||
Example: heredoc.Doc(`
|
||||
$ gh issue comment --body "I found a bug. Nothing works"
|
||||
`),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// support `-R, --repo` override
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
|
||||
bodyProvided := cmd.Flags().Changed("body")
|
||||
|
||||
opts.Interactive = !(bodyProvided)
|
||||
|
||||
if len(args) > 0 {
|
||||
opts.SelectorArg = args[0]
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
return commentRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.Body, "body", "b", "", "Supply a body.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type Action int
|
||||
|
||||
const (
|
||||
SubmitAction Action = iota
|
||||
CancelAction
|
||||
)
|
||||
|
||||
func titleBodySurvey(editorCommand string, issueState *CommentOptions, apiClient *api.Client, repo ghrepo.Interface, providedBody string) error {
|
||||
bodyQuestion := &survey.Question{
|
||||
Name: "body",
|
||||
Prompt: &surveyext.GhEditor{
|
||||
BlankAllowed: false,
|
||||
EditorCommand: editorCommand,
|
||||
Editor: &survey.Editor{
|
||||
Message: "Body",
|
||||
FileName: "*.md",
|
||||
Default: issueState.Body,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var qs []*survey.Question
|
||||
|
||||
if providedBody == "" {
|
||||
qs = append(qs, bodyQuestion)
|
||||
}
|
||||
|
||||
err := prompt.SurveyAsk(qs, issueState)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not prompt: %w", err))
|
||||
}
|
||||
|
||||
confirmA, err := confirmSubmission()
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to confirm: %w", err))
|
||||
}
|
||||
|
||||
issueState.Action = confirmA
|
||||
return nil
|
||||
}
|
||||
|
||||
func confirmSubmission() (Action, error) {
|
||||
const (
|
||||
submitLabel = "Submit"
|
||||
cancelLabel = "Cancel"
|
||||
)
|
||||
|
||||
options := []string{submitLabel, cancelLabel}
|
||||
|
||||
confirmAnswers := struct {
|
||||
Confirmation int
|
||||
}{}
|
||||
confirmQs := []*survey.Question{
|
||||
{
|
||||
Name: "confirmation",
|
||||
Prompt: &survey.Select{
|
||||
Message: "What's next?",
|
||||
Options: options,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := prompt.SurveyAsk(confirmQs, &confirmAnswers)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
|
||||
switch options[confirmAnswers.Confirmation] {
|
||||
case submitLabel:
|
||||
return SubmitAction, nil
|
||||
case cancelLabel:
|
||||
return CancelAction, nil
|
||||
default:
|
||||
return -1, fmt.Errorf("invalid index: %d", confirmAnswers.Confirmation)
|
||||
}
|
||||
}
|
||||
|
||||
func commentRun(opts *CommentOptions) error {
|
||||
httpClient, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiClient := api.NewClientFromHTTP(httpClient)
|
||||
|
||||
issue, baseRepo, err := shared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isTerminal := opts.IO.IsStdoutTTY()
|
||||
|
||||
if isTerminal {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "\nMake a comment for %s in %s\n\n", issue.Title, ghrepo.FullName(baseRepo))
|
||||
}
|
||||
|
||||
action := SubmitAction
|
||||
body := opts.Body
|
||||
|
||||
if opts.Interactive {
|
||||
editorCommand, err := cmdutil.DetermineEditor(opts.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = titleBodySurvey(editorCommand, opts, apiClient, baseRepo, body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
action = opts.Action
|
||||
}
|
||||
|
||||
if action == CancelAction {
|
||||
return nil
|
||||
} else if action == SubmitAction {
|
||||
params := map[string]interface{}{
|
||||
"subjectId": issue.ID,
|
||||
"body": opts.Body,
|
||||
}
|
||||
|
||||
err = api.CommentCreate(apiClient, baseRepo.RepoHost(), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(opts.IO.Out, issue.URL)
|
||||
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unexpected action state: %v", action)
|
||||
}
|
||||
117
pkg/cmd/issue/comment/comment_test.go
Normal file
117
pkg/cmd/issue/comment/comment_test.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package comment
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/test"
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
func eq(t *testing.T, got interface{}, expected interface{}) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("expected: %v, got: %v", expected, got)
|
||||
}
|
||||
}
|
||||
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io.SetStdoutTTY(isTTY)
|
||||
io.SetStdinTTY(isTTY)
|
||||
io.SetStderrTTY(isTTY)
|
||||
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: io,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: rt}, nil
|
||||
},
|
||||
Config: func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd := NewCmdComment(factory, func(opts *CommentOptions) error {
|
||||
return commentRun(opts)
|
||||
})
|
||||
argv, err := shlex.Split(cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.SetArgs(argv)
|
||||
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(ioutil.Discard)
|
||||
cmd.SetErr(ioutil.Discard)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
return &test.CmdOut{
|
||||
OutBuf: stdout,
|
||||
ErrBuf: stderr,
|
||||
}, err
|
||||
}
|
||||
|
||||
func TestCommentCreate(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"hasIssuesEnabled": true,
|
||||
"issue": { "number": 96, "title": "The title of the issue"}
|
||||
} } }
|
||||
`))
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`{"mutationId": "THE-ID"}`))
|
||||
|
||||
output, err := runCommand(http, true, `13 -b "cash rules everything around me"`)
|
||||
if err != nil {
|
||||
t.Errorf("error running command `issue comment`: %v", err)
|
||||
}
|
||||
|
||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[1].Body)
|
||||
reqBody := struct {
|
||||
Variables struct {
|
||||
Input struct {
|
||||
Body string
|
||||
}
|
||||
}
|
||||
}{}
|
||||
_ = json.Unmarshal(bodyBytes, &reqBody)
|
||||
|
||||
eq(t, reqBody.Variables.Input.Body, "cash rules everything around me")
|
||||
|
||||
r := regexp.MustCompile(`Make a comment for The title of the issue`)
|
||||
|
||||
if !r.MatchString(output.Stderr()) {
|
||||
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue_Disabled(t *testing.T) {
|
||||
http := &httpmock.Registry{}
|
||||
defer http.Verify(t)
|
||||
|
||||
http.StubResponse(200, bytes.NewBufferString(`
|
||||
{ "data": { "repository": {
|
||||
"hasIssuesEnabled": false
|
||||
} } }
|
||||
`))
|
||||
|
||||
_, err := runCommand(http, true, "13")
|
||||
if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
|
||||
t.Fatalf("got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package issue
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
cmdClose "github.com/cli/cli/pkg/cmd/issue/close"
|
||||
cmdComment "github.com/cli/cli/pkg/cmd/issue/comment"
|
||||
cmdCreate "github.com/cli/cli/pkg/cmd/issue/create"
|
||||
cmdList "github.com/cli/cli/pkg/cmd/issue/list"
|
||||
cmdReopen "github.com/cli/cli/pkg/cmd/issue/reopen"
|
||||
|
|
@ -40,6 +41,7 @@ func NewCmdIssue(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(cmdReopen.NewCmdReopen(f, nil))
|
||||
cmd.AddCommand(cmdStatus.NewCmdStatus(f, nil))
|
||||
cmd.AddCommand(cmdView.NewCmdView(f, nil))
|
||||
cmd.AddCommand(cmdComment.NewCmdComment(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue