mirror of https://gitlab.com/gitlab-org/cli.git
Merge branch 'add-incident-close-command' into 'main'
feat(incident): add `incident close` command Closes #1295 See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1219 Merged-by: Jay McCure <jmccure@gitlab.com> Approved-by: Halil Coban <hcoban@gitlab.com> Approved-by: Jay McCure <jmccure@gitlab.com> Reviewed-by: Jay McCure <jmccure@gitlab.com> Reviewed-by: Halil Coban <hcoban@gitlab.com> Co-authored-by: Vitali Tatarintev <vtatarintev@gitlab.com>
This commit is contained in:
commit
ae77206daf
|
@ -0,0 +1,13 @@
|
|||
package close
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
|
||||
issuableCloseCmd "gitlab.com/gitlab-org/cli/commands/issuable/close"
|
||||
)
|
||||
|
||||
func NewCmdClose(f *cmdutils.Factory) *cobra.Command {
|
||||
return issuableCloseCmd.NewCmdClose(f, issuable.TypeIncident)
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
|
||||
incidentCloseCmd "gitlab.com/gitlab-org/cli/commands/incident/close"
|
||||
incidentListCmd "gitlab.com/gitlab-org/cli/commands/incident/list"
|
||||
incidentViewCmd "gitlab.com/gitlab-org/cli/commands/incident/view"
|
||||
|
||||
|
@ -31,5 +32,6 @@ func NewCmdIncident(f *cmdutils.Factory) *cobra.Command {
|
|||
|
||||
incidentCmd.AddCommand(incidentListCmd.NewCmdList(f, nil))
|
||||
incidentCmd.AddCommand(incidentViewCmd.NewCmdView(f))
|
||||
incidentCmd.AddCommand(incidentCloseCmd.NewCmdClose(f))
|
||||
return incidentCmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package close
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
"gitlab.com/gitlab-org/cli/commands/issue/issueutils"
|
||||
)
|
||||
|
||||
var closingMessage = map[issuable.IssueType]string{
|
||||
issuable.TypeIssue: "Closing Issue",
|
||||
issuable.TypeIncident: "Resolving Incident",
|
||||
}
|
||||
|
||||
var closedMessage = map[issuable.IssueType]string{
|
||||
issuable.TypeIssue: "Closed Issue",
|
||||
issuable.TypeIncident: "Resolved Incident",
|
||||
}
|
||||
|
||||
func NewCmdClose(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Command {
|
||||
examplePath := "issues/123"
|
||||
aliases := []string{}
|
||||
|
||||
if issueType == issuable.TypeIncident {
|
||||
examplePath = "issues/incident/123"
|
||||
aliases = []string{"resolve"}
|
||||
}
|
||||
|
||||
issueCloseCmd := &cobra.Command{
|
||||
Use: "close [<id> | <url>] [flags]",
|
||||
Short: fmt.Sprintf(`Close an %s`, issueType),
|
||||
Long: ``,
|
||||
Aliases: aliases,
|
||||
Example: heredoc.Doc(fmt.Sprintf(`
|
||||
glab %[1]s close 123
|
||||
glab %[1]s close https://gitlab.com/NAMESPACE/REPO/-/%s
|
||||
`, issueType, examplePath)),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
apiClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issues, repo, err := issueutils.IssuesFromArgs(apiClient, f.BaseRepo, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l := &gitlab.UpdateIssueOptions{}
|
||||
l.StateEvent = gitlab.String("close")
|
||||
|
||||
c := f.IO.Color()
|
||||
|
||||
for _, issue := range issues {
|
||||
// Issues and incidents are the same kind, but with different issueType.
|
||||
// `issue close` can close issues of all types including incidents
|
||||
// `incident close` on the other hand, should close only incidents, and treat all other issue types as not found
|
||||
//
|
||||
// When using `incident close` with non incident's IDs, print an error.
|
||||
if issueType == issuable.TypeIncident && *issue.IssueType != string(issuable.TypeIncident) {
|
||||
fmt.Fprintln(f.IO.StdOut, "Incident not found, but an issue with the provided ID exists. Run `glab issue close <id>` to close it.")
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(f.IO.StdOut, "- %s...\n", closingMessage[issueType])
|
||||
issue, err := api.UpdateIssue(apiClient, repo.FullName(), issue.IID, l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(f.IO.StdOut, "%s %s #%d\n", c.RedCheck(), closedMessage[issueType], issue.IID)
|
||||
fmt.Fprintln(f.IO.StdOut, issueutils.DisplayIssue(c, issue, f.IO.IsaTTY))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return issueCloseCmd
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package close
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
"gitlab.com/gitlab-org/cli/internal/glrepo"
|
||||
"gitlab.com/gitlab-org/cli/pkg/httpmock"
|
||||
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
||||
"gitlab.com/gitlab-org/cli/test"
|
||||
)
|
||||
|
||||
func mockAllResponses(t *testing.T, fakeHTTP *httpmock.Mocker) {
|
||||
fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
|
||||
httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 1,
|
||||
"iid": 1,
|
||||
"title": "test issue",
|
||||
"state": "opened",
|
||||
"issue_type": "issue",
|
||||
"created_at": "2023-04-05T10:51:26.371Z"
|
||||
}`),
|
||||
)
|
||||
|
||||
fakeHTTP.RegisterResponder(http.MethodPut, "/projects/OWNER/REPO/issues/1",
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
rb, _ := io.ReadAll(req.Body)
|
||||
|
||||
assert.Contains(t, string(rb), `"state_event":"close"`)
|
||||
resp, _ := httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 1,
|
||||
"iid": 1,
|
||||
"state": "closed",
|
||||
"issue_type": "issue",
|
||||
"created_at": "2023-04-05T10:51:26.371Z"
|
||||
}`)(req)
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/2",
|
||||
httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 2,
|
||||
"iid": 2,
|
||||
"title": "test incident",
|
||||
"state": "opened",
|
||||
"issue_type": "incident",
|
||||
"created_at": "2023-04-05T10:51:26.371Z"
|
||||
}`),
|
||||
)
|
||||
|
||||
fakeHTTP.RegisterResponder(http.MethodPut, "/projects/OWNER/REPO/issues/2",
|
||||
func(req *http.Request) (*http.Response, error) {
|
||||
rb, _ := io.ReadAll(req.Body)
|
||||
|
||||
assert.Contains(t, string(rb), `"state_event":"close"`)
|
||||
resp, _ := httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 2,
|
||||
"iid": 2,
|
||||
"state": "closed",
|
||||
"issue_type": "incident",
|
||||
"created_at": "2023-04-05T10:51:26.371Z"
|
||||
}`)(req)
|
||||
|
||||
return resp, nil
|
||||
},
|
||||
)
|
||||
|
||||
fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/404",
|
||||
httpmock.NewStringResponse(http.StatusNotFound, `{"message": "404 not found"}`),
|
||||
)
|
||||
}
|
||||
|
||||
func runCommand(rt http.RoundTripper, issuableID string, issueType issuable.IssueType) (*test.CmdOut, error) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
factory := &cmdutils.Factory{
|
||||
IO: ios,
|
||||
HttpClient: func() (*gitlab.Client, error) {
|
||||
a, err := api.TestClient(&http.Client{Transport: rt}, "", "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.Lab(), err
|
||||
},
|
||||
BaseRepo: func() (glrepo.Interface, error) {
|
||||
return glrepo.New("OWNER", "REPO"), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = factory.HttpClient()
|
||||
|
||||
cmd := NewCmdClose(factory, issueType)
|
||||
|
||||
argv, err := shlex.Split(issuableID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.SetArgs(argv)
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
return &test.CmdOut{
|
||||
OutBuf: stdout,
|
||||
ErrBuf: stderr,
|
||||
}, err
|
||||
}
|
||||
|
||||
func TestIssuableClose(t *testing.T) {
|
||||
tests := []struct {
|
||||
iid int
|
||||
name string
|
||||
issueType issuable.IssueType
|
||||
wantOutput string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
iid: 1,
|
||||
name: "issue_close",
|
||||
issueType: issuable.TypeIssue,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Closing Issue...
|
||||
✓ Closed Issue #1
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 2,
|
||||
name: "incident_close",
|
||||
issueType: issuable.TypeIncident,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Resolving Incident...
|
||||
✓ Resolved Incident #2
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 2,
|
||||
name: "incident_close_using_issue_command",
|
||||
issueType: issuable.TypeIssue,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Closing Issue...
|
||||
✓ Closed Issue #2
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 1,
|
||||
name: "issue_close_using_incident_command",
|
||||
issueType: issuable.TypeIncident,
|
||||
wantOutput: "Incident not found, but an issue with the provided ID exists. Run `glab issue close <id>` to close it.\n",
|
||||
},
|
||||
{
|
||||
iid: 404,
|
||||
name: "issue_not_found",
|
||||
issueType: issuable.TypeIssue,
|
||||
wantOutput: "404 not found",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fakeHTTP := httpmock.New()
|
||||
|
||||
mockAllResponses(t, fakeHTTP)
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := runCommand(fakeHTTP, fmt.Sprint(tt.iid), tt.issueType)
|
||||
if tt.wantErr {
|
||||
assert.Contains(t, err.Error(), tt.wantOutput)
|
||||
} else {
|
||||
assert.NoErrorf(t, err, "error running command `issue close %d`", tt.iid)
|
||||
assert.Equal(t, tt.wantOutput, output.String())
|
||||
assert.Empty(t, output.Stderr())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,55 +1,13 @@
|
|||
package close
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issue/issueutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
|
||||
issuableCloseCmd "gitlab.com/gitlab-org/cli/commands/issuable/close"
|
||||
)
|
||||
|
||||
func NewCmdClose(f *cmdutils.Factory) *cobra.Command {
|
||||
issueCloseCmd := &cobra.Command{
|
||||
Use: "close <id>",
|
||||
Short: `Close an issue`,
|
||||
Long: ``,
|
||||
Example: heredoc.Doc(`
|
||||
glab issue close 123
|
||||
glab issue close https://gitlab.com/profclems/glab/-/issues/123
|
||||
`),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
c := f.IO.Color()
|
||||
|
||||
apiClient, err := f.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issues, repo, err := issueutils.IssuesFromArgs(apiClient, f.BaseRepo, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l := &gitlab.UpdateIssueOptions{}
|
||||
l.StateEvent = gitlab.String("close")
|
||||
|
||||
for _, issue := range issues {
|
||||
fmt.Fprintln(f.IO.StdOut, "- Closing Issue...")
|
||||
issue, err := api.UpdateIssue(apiClient, repo.FullName(), issue.IID, l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(f.IO.StdOut, "%s Closed Issue #%d\n", c.RedCheck(), issue.IID)
|
||||
fmt.Fprintln(f.IO.StdOut, issueutils.DisplayIssue(c, issue, f.IO.IsaTTY))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return issueCloseCmd
|
||||
return issuableCloseCmd.NewCmdClose(f, issuable.TypeIssue)
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
package close
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/test"
|
||||
|
||||
"gitlab.com/gitlab-org/cli/pkg/iostreams"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
"gitlab.com/gitlab-org/cli/api"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdtest"
|
||||
)
|
||||
|
||||
func Test_issueClose_Integration(t *testing.T) {
|
||||
glTestHost := test.GetHostOrSkip(t)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
oldUpdateIssue := api.UpdateIssue
|
||||
timer, _ := time.Parse(time.RFC3339, "2014-11-12T11:45:26.371Z")
|
||||
api.UpdateIssue = func(client *gitlab.Client, projectID interface{}, issueID int, opts *gitlab.UpdateIssueOptions) (*gitlab.Issue, error) {
|
||||
if projectID == "" || projectID == "WRONG_REPO" || projectID == "expected_err" || issueID == 0 {
|
||||
return nil, fmt.Errorf("error expected")
|
||||
}
|
||||
return &gitlab.Issue{
|
||||
ID: issueID,
|
||||
IID: issueID,
|
||||
State: "closed",
|
||||
Description: "Dummy description for issue " + string(rune(issueID)),
|
||||
Author: &gitlab.IssueAuthor{
|
||||
ID: 1,
|
||||
Name: "John Dev Wick",
|
||||
Username: "jdwick",
|
||||
},
|
||||
CreatedAt: &timer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Issue string
|
||||
ExpectedMsg []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
Name: "Issue Exists",
|
||||
Issue: "1",
|
||||
ExpectedMsg: []string{"Closing Issue...", "Closed Issue #1"},
|
||||
},
|
||||
{
|
||||
Name: "Issue Does Not Exist",
|
||||
Issue: "0",
|
||||
ExpectedMsg: []string{"Closing Issue", "404 Not found"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
f := cmdtest.StubFactory(glTestHost + "/cli-automated-testing/test")
|
||||
f.IO = io
|
||||
f.IO.IsaTTY = true
|
||||
f.IO.IsErrTTY = true
|
||||
cmd := NewCmdClose(f)
|
||||
|
||||
cmd.SetArgs([]string{tc.Issue})
|
||||
cmd.SetOut(stdout)
|
||||
cmd.SetErr(stderr)
|
||||
|
||||
_, err := cmd.ExecuteC()
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
out := stripansi.Strip(stdout.String())
|
||||
|
||||
for _, msg := range tc.ExpectedMsg {
|
||||
assert.Contains(t, out, msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
api.UpdateIssue = oldUpdateIssue
|
||||
}
|
|
@ -54,7 +54,7 @@ func runCommand(rt http.RoundTripper, cli string) (*test.CmdOut, error) {
|
|||
}, err
|
||||
}
|
||||
|
||||
func TestMrApprove(t *testing.T) {
|
||||
func TestMrClose(t *testing.T) {
|
||||
fakeHTTP := httpmock.New()
|
||||
defer fakeHTTP.Verify(t)
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
<!--
|
||||
This documentation is auto generated by a script.
|
||||
Please do not edit this file directly. Run `make gen-docs` instead.
|
||||
-->
|
||||
|
||||
# `glab incident close`
|
||||
|
||||
Close an incident
|
||||
|
||||
```plaintext
|
||||
glab incident close [<id> | <url>] [flags]
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
```plaintext
|
||||
resolve
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```plaintext
|
||||
glab incident close 123
|
||||
glab incident close https://gitlab.com/NAMESPACE/REPO/-/issues/incident/123
|
||||
|
||||
```
|
||||
|
||||
## Options inherited from parent commands
|
||||
|
||||
```plaintext
|
||||
--help Show help for command
|
||||
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
|
||||
```
|
|
@ -34,5 +34,6 @@ glab incident list
|
|||
|
||||
## Subcommands
|
||||
|
||||
- [close](close.md)
|
||||
- [list](list.md)
|
||||
- [view](view.md)
|
||||
|
|
|
@ -14,14 +14,14 @@ Please do not edit this file directly. Run `make gen-docs` instead.
|
|||
Close an issue
|
||||
|
||||
```plaintext
|
||||
glab issue close <id> [flags]
|
||||
glab issue close [<id> | <url>] [flags]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```plaintext
|
||||
glab issue close 123
|
||||
glab issue close https://gitlab.com/profclems/glab/-/issues/123
|
||||
glab issue close https://gitlab.com/NAMESPACE/REPO/-/issues/123
|
||||
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in New Issue