X Tutup
Skip to content

Commit 57d5470

Browse files
Add issue delete command
Similar to `issue close`, but for deleting an issue rather than just closing it. Resolves cli#2820.
1 parent 6e2c1b3 commit 57d5470

File tree

4 files changed

+224
-0
lines changed

4 files changed

+224
-0
lines changed

api/queries_issue.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,27 @@ func IssueReopen(client *Client, repo ghrepo.Interface, issue Issue) error {
465465
return err
466466
}
467467

468+
func IssueDelete(client *Client, repo ghrepo.Interface, issue Issue) error {
469+
var mutation struct {
470+
DeleteIssue struct {
471+
Repository struct {
472+
ID githubv4.ID
473+
}
474+
} `graphql:"deleteIssue(input: $input)"`
475+
}
476+
477+
variables := map[string]interface{}{
478+
"input": githubv4.DeleteIssueInput{
479+
IssueID: issue.ID,
480+
},
481+
}
482+
483+
gql := graphQLClient(client.http, repo.RepoHost())
484+
err := gql.MutateNamed(context.Background(), "IssueDelete", &mutation, variables)
485+
486+
return err
487+
}
488+
468489
// milestoneNodeIdToDatabaseId extracts the REST Database ID from the GraphQL Node ID
469490
// This conversion is necessary since the GraphQL API requires the use of the milestone's database ID
470491
// for querying the related issues.

pkg/cmd/issue/delete/delete.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package delete
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/cli/cli/api"
8+
"github.com/cli/cli/internal/config"
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/cmd/issue/shared"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type DeleteOptions struct {
17+
HttpClient func() (*http.Client, error)
18+
Config func() (config.Config, error)
19+
IO *iostreams.IOStreams
20+
BaseRepo func() (ghrepo.Interface, error)
21+
22+
SelectorArg string
23+
}
24+
25+
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
26+
opts := &DeleteOptions{
27+
IO: f.IOStreams,
28+
HttpClient: f.HttpClient,
29+
Config: f.Config,
30+
}
31+
32+
cmd := &cobra.Command{
33+
Use: "delete {<number> | <url>}",
34+
Short: "Delete issue",
35+
Args: cobra.ExactArgs(1),
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
// support `-R, --repo` override
38+
opts.BaseRepo = f.BaseRepo
39+
40+
if len(args) > 0 {
41+
opts.SelectorArg = args[0]
42+
}
43+
44+
if runF != nil {
45+
return runF(opts)
46+
}
47+
return deleteRun(opts)
48+
},
49+
}
50+
51+
return cmd
52+
}
53+
54+
func deleteRun(opts *DeleteOptions) error {
55+
cs := opts.IO.ColorScheme()
56+
57+
httpClient, err := opts.HttpClient()
58+
if err != nil {
59+
return err
60+
}
61+
apiClient := api.NewClientFromHTTP(httpClient)
62+
63+
issue, baseRepo, err := shared.IssueFromArg(apiClient, opts.BaseRepo, opts.SelectorArg)
64+
if err != nil {
65+
return err
66+
}
67+
68+
err = api.IssueDelete(apiClient, baseRepo, *issue)
69+
if err != nil {
70+
return err
71+
}
72+
73+
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted issue #%d (%s)\n", cs.Red("✔"), issue.Number, issue.Title)
74+
75+
return nil
76+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package delete
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"net/http"
7+
"regexp"
8+
"testing"
9+
10+
"github.com/cli/cli/internal/config"
11+
"github.com/cli/cli/internal/ghrepo"
12+
"github.com/cli/cli/pkg/cmdutil"
13+
"github.com/cli/cli/pkg/httpmock"
14+
"github.com/cli/cli/pkg/iostreams"
15+
"github.com/cli/cli/test"
16+
"github.com/google/shlex"
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
21+
io, _, stdout, stderr := iostreams.Test()
22+
io.SetStdoutTTY(isTTY)
23+
io.SetStdinTTY(isTTY)
24+
io.SetStderrTTY(isTTY)
25+
26+
factory := &cmdutil.Factory{
27+
IOStreams: io,
28+
HttpClient: func() (*http.Client, error) {
29+
return &http.Client{Transport: rt}, nil
30+
},
31+
Config: func() (config.Config, error) {
32+
return config.NewBlankConfig(), nil
33+
},
34+
BaseRepo: func() (ghrepo.Interface, error) {
35+
return ghrepo.New("OWNER", "REPO"), nil
36+
},
37+
}
38+
39+
cmd := NewCmdDelete(factory, nil)
40+
41+
argv, err := shlex.Split(cli)
42+
if err != nil {
43+
return nil, err
44+
}
45+
cmd.SetArgs(argv)
46+
47+
cmd.SetIn(&bytes.Buffer{})
48+
cmd.SetOut(ioutil.Discard)
49+
cmd.SetErr(ioutil.Discard)
50+
51+
_, err = cmd.ExecuteC()
52+
return &test.CmdOut{
53+
OutBuf: stdout,
54+
ErrBuf: stderr,
55+
}, err
56+
}
57+
58+
func TestIssueDelete(t *testing.T) {
59+
httpRegistry := &httpmock.Registry{}
60+
defer httpRegistry.Verify(t)
61+
62+
httpRegistry.Register(
63+
httpmock.GraphQL(`query IssueByNumber\b`),
64+
httpmock.StringResponse(`
65+
{ "data": { "repository": {
66+
"hasIssuesEnabled": true,
67+
"issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"}
68+
} } }`),
69+
)
70+
httpRegistry.Register(
71+
httpmock.GraphQL(`mutation IssueDelete\b`),
72+
httpmock.GraphQLMutation(`{"id": "THE-ID"}`,
73+
func(inputs map[string]interface{}) {
74+
assert.Equal(t, inputs["issueId"], "THE-ID")
75+
}),
76+
)
77+
78+
output, err := runCommand(httpRegistry, true, "13")
79+
if err != nil {
80+
t.Fatalf("error running command `issue delete`: %v", err)
81+
}
82+
83+
r := regexp.MustCompile(`Deleted issue #13 \(The title of the issue\)`)
84+
85+
if !r.MatchString(output.Stderr()) {
86+
t.Fatalf("output did not match regexp /%s/\n> output\n%q\n", r, output.Stderr())
87+
}
88+
}
89+
90+
func TestIssueDelete_doesNotExist(t *testing.T) {
91+
httpRegistry := &httpmock.Registry{}
92+
defer httpRegistry.Verify(t)
93+
94+
httpRegistry.Register(
95+
httpmock.GraphQL(`query IssueByNumber\b`),
96+
httpmock.StringResponse(`
97+
{ "errors": [
98+
{ "message": "Could not resolve to an Issue with the number of 13." }
99+
] }
100+
`),
101+
)
102+
103+
_, err := runCommand(httpRegistry, true, "13")
104+
if err == nil || err.Error() != "GraphQL error: Could not resolve to an Issue with the number of 13." {
105+
t.Errorf("error running command `issue delete`: %v", err)
106+
}
107+
}
108+
109+
func TestIssueDelete_issuesDisabled(t *testing.T) {
110+
httpRegistry := &httpmock.Registry{}
111+
defer httpRegistry.Verify(t)
112+
113+
httpRegistry.Register(
114+
httpmock.GraphQL(`query IssueByNumber\b`),
115+
httpmock.StringResponse(`
116+
{ "data": { "repository": {
117+
"hasIssuesEnabled": false
118+
} } }`),
119+
)
120+
121+
_, err := runCommand(httpRegistry, true, "13")
122+
if err == nil || err.Error() != "the 'OWNER/REPO' repository has disabled issues" {
123+
t.Fatalf("got error: %v", err)
124+
}
125+
}

pkg/cmd/issue/issue.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
cmdClose "github.com/cli/cli/pkg/cmd/issue/close"
66
cmdComment "github.com/cli/cli/pkg/cmd/issue/comment"
77
cmdCreate "github.com/cli/cli/pkg/cmd/issue/create"
8+
cmdDelete "github.com/cli/cli/pkg/cmd/issue/delete"
89
cmdList "github.com/cli/cli/pkg/cmd/issue/list"
910
cmdReopen "github.com/cli/cli/pkg/cmd/issue/reopen"
1011
cmdStatus "github.com/cli/cli/pkg/cmd/issue/status"
@@ -42,6 +43,7 @@ func NewCmdIssue(f *cmdutil.Factory) *cobra.Command {
4243
cmd.AddCommand(cmdStatus.NewCmdStatus(f, nil))
4344
cmd.AddCommand(cmdView.NewCmdView(f, nil))
4445
cmd.AddCommand(cmdComment.NewCmdComment(f, nil))
46+
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
4547

4648
return cmd
4749
}

0 commit comments

Comments
 (0)
X Tutup