mirror of https://gitlab.com/gitlab-org/cli.git
parent
25c6ec70a3
commit
a5154f5343
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
incidentCloseCmd "gitlab.com/gitlab-org/cli/commands/incident/close"
|
||||
incidentListCmd "gitlab.com/gitlab-org/cli/commands/incident/list"
|
||||
incidentReopenCmd "gitlab.com/gitlab-org/cli/commands/incident/reopen"
|
||||
incidentViewCmd "gitlab.com/gitlab-org/cli/commands/incident/view"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -33,5 +34,6 @@ func NewCmdIncident(f *cmdutils.Factory) *cobra.Command {
|
|||
incidentCmd.AddCommand(incidentListCmd.NewCmdList(f, nil))
|
||||
incidentCmd.AddCommand(incidentViewCmd.NewCmdView(f))
|
||||
incidentCmd.AddCommand(incidentCloseCmd.NewCmdClose(f))
|
||||
incidentCmd.AddCommand(incidentReopenCmd.NewCmdReopen(f))
|
||||
return incidentCmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package reopen
|
||||
|
||||
import (
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
issuableReopenCmd "gitlab.com/gitlab-org/cli/commands/issuable/reopen"
|
||||
)
|
||||
|
||||
func NewCmdReopen(f *cmdutils.Factory) *cobra.Command {
|
||||
return issuableReopenCmd.NewCmdReopen(f, issuable.TypeIncident)
|
||||
}
|
|
@ -59,13 +59,9 @@ func NewCmdClose(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Comma
|
|||
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.")
|
||||
valid, msg := issuable.ValidateIncidentCmd(issueType, "close", issue)
|
||||
if !valid {
|
||||
fmt.Fprintln(f.IO.StdOut, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,32 @@
|
|||
package issuable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
type IssueType string
|
||||
|
||||
const (
|
||||
TypeIssue IssueType = "issue"
|
||||
TypeIncident IssueType = "incident"
|
||||
)
|
||||
|
||||
// ValidateIncidentCmd returns an error when incident command is used with non-incident's IDs.
|
||||
//
|
||||
// Issues and incidents are the same kind, but with different issueType.
|
||||
//
|
||||
// For example:
|
||||
// `issue view` can view issues of all types including incidents
|
||||
// `incident view` on the other hand, should view only incidents, and treat all other issue types as not found
|
||||
func ValidateIncidentCmd(cmd IssueType, subcmd string, issue *gitlab.Issue) (bool, string) {
|
||||
if cmd == TypeIncident && *issue.IssueType != string(TypeIncident) {
|
||||
return false, fmt.Sprintf(
|
||||
"Incident not found, but an issue with the provided ID exists. Run `glab issue %[1]s <id>` to %[1]s it.",
|
||||
subcmd,
|
||||
)
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package issuable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
func TestValidateIncidentCmd(t *testing.T) {
|
||||
issueTypeIssue := "issue"
|
||||
issueTypeIncident := "incident"
|
||||
tests := []struct {
|
||||
name string
|
||||
cmd IssueType
|
||||
subcmd string
|
||||
issue *gitlab.Issue
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "valid_incident_view_command",
|
||||
cmd: TypeIncident,
|
||||
subcmd: "view",
|
||||
issue: &gitlab.Issue{
|
||||
IssueType: &issueTypeIncident,
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "invalid_incident_view_command",
|
||||
cmd: TypeIncident,
|
||||
subcmd: "view",
|
||||
issue: &gitlab.Issue{
|
||||
IssueType: &issueTypeIssue,
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "valid_issue_view_command_for_issue",
|
||||
cmd: TypeIssue,
|
||||
subcmd: "view",
|
||||
issue: &gitlab.Issue{
|
||||
IssueType: &issueTypeIssue,
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "valid_issue_view_command_for_incident",
|
||||
cmd: TypeIssue,
|
||||
subcmd: "view",
|
||||
issue: &gitlab.Issue{
|
||||
IssueType: &issueTypeIncident,
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
valid, msg := ValidateIncidentCmd(tt.cmd, tt.subcmd, tt.issue)
|
||||
assert.Equal(t, tt.valid, valid)
|
||||
|
||||
if !valid {
|
||||
assert.Equal(
|
||||
t,
|
||||
fmt.Sprintf("Incident not found, but an issue with the provided ID exists. Run `glab issue %[1]s <id>` to %[1]s it.", tt.subcmd),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -6,23 +6,47 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"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/commands/issue/issueutils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
func NewCmdReopen(f *cmdutils.Factory) *cobra.Command {
|
||||
var (
|
||||
description = map[issuable.IssueType]string{
|
||||
issuable.TypeIssue: "Reopen a closed issue",
|
||||
issuable.TypeIncident: "Reopen a resolved incident",
|
||||
}
|
||||
|
||||
reopeningMessage = map[issuable.IssueType]string{
|
||||
issuable.TypeIssue: "Reopening Issue",
|
||||
issuable.TypeIncident: "Reopening Incident",
|
||||
}
|
||||
|
||||
reopenedMessage = map[issuable.IssueType]string{
|
||||
issuable.TypeIssue: "Reopened Issue",
|
||||
issuable.TypeIncident: "Reopened Incident",
|
||||
}
|
||||
)
|
||||
|
||||
func NewCmdReopen(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Command {
|
||||
examplePath := "issues/123"
|
||||
|
||||
if issueType == issuable.TypeIncident {
|
||||
examplePath = "issues/incident/123"
|
||||
}
|
||||
|
||||
issueReopenCmd := &cobra.Command{
|
||||
Use: "reopen <id>",
|
||||
Short: `Reopen a closed issue`,
|
||||
Use: "reopen [<id> | <url>] [flags]",
|
||||
Short: description[issueType],
|
||||
Long: ``,
|
||||
Aliases: []string{"open"},
|
||||
Example: heredoc.Doc(`
|
||||
glab issue reopen 123
|
||||
glab issue open 123
|
||||
glab issue reopen https://gitlab.com/profclems/glab/-/issues/123
|
||||
`),
|
||||
Example: heredoc.Doc(fmt.Sprintf(`
|
||||
glab %[1]s reopen 123
|
||||
glab %[1]s open 123
|
||||
glab %[1]s reopen https://gitlab.com/NAMESPACE/REPO/-/%s
|
||||
`, issueType, examplePath)),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
@ -43,16 +67,19 @@ func NewCmdReopen(f *cmdutils.Factory) *cobra.Command {
|
|||
l.StateEvent = gitlab.String("reopen")
|
||||
|
||||
for _, issue := range issues {
|
||||
if f.IO.IsaTTY && f.IO.IsErrTTY {
|
||||
fmt.Fprintln(out, "- Reopening Issue...")
|
||||
valid, msg := issuable.ValidateIncidentCmd(issueType, "reopen", issue)
|
||||
if !valid {
|
||||
fmt.Fprintln(f.IO.StdOut, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "- %s...\n", reopeningMessage[issueType])
|
||||
issue, err := api.UpdateIssue(apiClient, repo.FullName(), issue.IID, l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%s Reopened Issue #%d\n", c.GreenCheck(), issue.IID)
|
||||
fmt.Fprintf(out, "%s %s #%d\n", c.GreenCheck(), reopenedMessage[issueType], issue.IID)
|
||||
fmt.Fprintln(out, issueutils.DisplayIssue(c, issue, f.IO.IsaTTY))
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
package reopen
|
||||
|
||||
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": "closed",
|
||||
"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":"reopen"`)
|
||||
resp, _ := httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 1,
|
||||
"iid": 1,
|
||||
"state": "open",
|
||||
"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": "closed",
|
||||
"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":"reopen"`)
|
||||
resp, _ := httpmock.NewStringResponse(http.StatusOK, `{
|
||||
"id": 2,
|
||||
"iid": 2,
|
||||
"state": "opened",
|
||||
"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 := NewCmdReopen(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 TestIssuableReopen(t *testing.T) {
|
||||
tests := []struct {
|
||||
iid int
|
||||
name string
|
||||
issueType issuable.IssueType
|
||||
wantOutput string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
iid: 1,
|
||||
name: "issue_reopen",
|
||||
issueType: issuable.TypeIssue,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Reopening Issue...
|
||||
✓ Reopened Issue #1
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 2,
|
||||
name: "incident_reopen",
|
||||
issueType: issuable.TypeIncident,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Reopening Incident...
|
||||
✓ Reopened Incident #2
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 2,
|
||||
name: "incident_reopen_using_issue_command",
|
||||
issueType: issuable.TypeIssue,
|
||||
wantOutput: heredoc.Doc(`
|
||||
- Reopening Issue...
|
||||
✓ Reopened Issue #2
|
||||
|
||||
`),
|
||||
},
|
||||
{
|
||||
iid: 1,
|
||||
name: "issue_reopen_using_incident_command",
|
||||
issueType: issuable.TypeIncident,
|
||||
wantOutput: "Incident not found, but an issue with the provided ID exists. Run `glab issue reopen <id>` to reopen 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)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoErrorf(t, err, "error running command `%s reopen %d`", tt.issueType, tt.iid)
|
||||
assert.Equal(t, tt.wantOutput, output.String())
|
||||
assert.Empty(t, output.Stderr())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -70,13 +70,9 @@ func NewCmdView(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Comman
|
|||
|
||||
opts.Issue = issue
|
||||
|
||||
// Issues and incidents are the same kind, but with different issueType.
|
||||
// `issue view` can display issues of all types including incidents
|
||||
// `incident view` on the other hand, should display only incidents, and treat all other issue types as not found
|
||||
//
|
||||
// When using `incident view` with non incident's IDs, print an error.
|
||||
if issueType == issuable.TypeIncident && *opts.Issue.IssueType != string(issuable.TypeIncident) {
|
||||
fmt.Fprintln(opts.IO.StdErr, "Incident not found, but an issue with the provided ID exists. Run `glab issue view <id>` to view it.")
|
||||
valid, msg := issuable.ValidateIncidentCmd(issueType, "view", opts.Issue)
|
||||
if !valid {
|
||||
fmt.Fprintln(opts.IO.StdErr, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ package issue
|
|||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
issuableReopenCmd "gitlab.com/gitlab-org/cli/commands/issuable/reopen"
|
||||
issueBoardCmd "gitlab.com/gitlab-org/cli/commands/issue/board"
|
||||
issueCloseCmd "gitlab.com/gitlab-org/cli/commands/issue/close"
|
||||
issueCreateCmd "gitlab.com/gitlab-org/cli/commands/issue/create"
|
||||
issueDeleteCmd "gitlab.com/gitlab-org/cli/commands/issue/delete"
|
||||
issueListCmd "gitlab.com/gitlab-org/cli/commands/issue/list"
|
||||
issueNoteCmd "gitlab.com/gitlab-org/cli/commands/issue/note"
|
||||
issueReopenCmd "gitlab.com/gitlab-org/cli/commands/issue/reopen"
|
||||
issueSubscribeCmd "gitlab.com/gitlab-org/cli/commands/issue/subscribe"
|
||||
issueUnsubscribeCmd "gitlab.com/gitlab-org/cli/commands/issue/unsubscribe"
|
||||
issueUpdateCmd "gitlab.com/gitlab-org/cli/commands/issue/update"
|
||||
|
@ -46,7 +46,7 @@ func NewCmdIssue(f *cmdutils.Factory) *cobra.Command {
|
|||
issueCmd.AddCommand(issueDeleteCmd.NewCmdDelete(f))
|
||||
issueCmd.AddCommand(issueListCmd.NewCmdList(f, nil))
|
||||
issueCmd.AddCommand(issueNoteCmd.NewCmdNote(f))
|
||||
issueCmd.AddCommand(issuableReopenCmd.NewCmdReopen(f))
|
||||
issueCmd.AddCommand(issueReopenCmd.NewCmdReopen(f))
|
||||
issueCmd.AddCommand(issueViewCmd.NewCmdView(f))
|
||||
issueCmd.AddCommand(issueSubscribeCmd.NewCmdSubscribe(f))
|
||||
issueCmd.AddCommand(issueUnsubscribeCmd.NewCmdUnsubscribe(f))
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package reopen
|
||||
|
||||
import (
|
||||
"gitlab.com/gitlab-org/cli/commands/cmdutils"
|
||||
"gitlab.com/gitlab-org/cli/commands/issuable"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
issuableReopenCmd "gitlab.com/gitlab-org/cli/commands/issuable/reopen"
|
||||
)
|
||||
|
||||
func NewCmdReopen(f *cmdutils.Factory) *cobra.Command {
|
||||
return issuableReopenCmd.NewCmdReopen(f, issuable.TypeIssue)
|
||||
}
|
|
@ -36,4 +36,5 @@ glab incident list
|
|||
|
||||
- [close](close.md)
|
||||
- [list](list.md)
|
||||
- [reopen](reopen.md)
|
||||
- [view](view.md)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
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 reopen`
|
||||
|
||||
Reopen a resolved incident
|
||||
|
||||
```plaintext
|
||||
glab incident reopen [<id> | <url>] [flags]
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
```plaintext
|
||||
open
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```plaintext
|
||||
glab incident reopen 123
|
||||
glab incident open 123
|
||||
glab incident reopen 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
|
||||
```
|
|
@ -14,7 +14,7 @@ Please do not edit this file directly. Run `make gen-docs` instead.
|
|||
Reopen a closed issue
|
||||
|
||||
```plaintext
|
||||
glab issue reopen <id> [flags]
|
||||
glab issue reopen [<id> | <url>] [flags]
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
@ -28,7 +28,7 @@ open
|
|||
```plaintext
|
||||
glab issue reopen 123
|
||||
glab issue open 123
|
||||
glab issue reopen https://gitlab.com/profclems/glab/-/issues/123
|
||||
glab issue reopen https://gitlab.com/NAMESPACE/REPO/-/issues/123
|
||||
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in New Issue