Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
Merge pull request #546 from profclems/feat-issue-url-arg
Browse files Browse the repository at this point in the history
feat(command/issue): allow issue URL as argument
  • Loading branch information
profclems authored Jan 10, 2021
2 parents 9138515 + c177751 commit 2ff41de
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 174 deletions.
12 changes: 5 additions & 7 deletions commands/issue/close/issue_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package close

import (
"fmt"
"strings"

"github.com/profclems/glab/internal/utils"
"github.com/profclems/glab/pkg/api"
Expand All @@ -22,28 +21,27 @@ func NewCmdClose(f *cmdutils.Factory) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
issueID := strings.TrimSpace(args[0])

apiClient, err := f.HttpClient()
if err != nil {
return err
}

repo, err := f.BaseRepo()
issues, repo, err := issueutils.IssuesFromArgs(apiClient, f.BaseRepo, args)
if err != nil {
return err
}

l := &gitlab.UpdateIssueOptions{}
l.StateEvent = gitlab.String("close")
arrIds := strings.Split(strings.Trim(issueID, "[] "), ",")
for _, i2 := range arrIds {

for _, issue := range issues {
fmt.Fprintln(f.IO.StdOut, "- Closing Issue...")
issue, err := api.UpdateIssue(apiClient, repo.FullName(), utils.StringToInt(i2), l)
issue, err := api.UpdateIssue(apiClient, repo.FullName(), issue.IID, l)
if err != nil {
return err
}
fmt.Fprintf(f.IO.StdOut, "%s Closed Issue #%s\n", utils.RedCheck(), i2)
fmt.Fprintf(f.IO.StdOut, "%s Closed Issue #%d\n", utils.RedCheck(), issue.IID)
fmt.Fprintln(f.IO.StdOut, issueutils.DisplayIssue(issue))
}
return nil
Expand Down
15 changes: 6 additions & 9 deletions commands/issue/delete/issue_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package delete

import (
"fmt"
"strings"

"github.com/profclems/glab/commands/cmdutils"
"github.com/profclems/glab/commands/issue/issueutils"
"github.com/profclems/glab/internal/utils"
"github.com/profclems/glab/pkg/api"

Expand All @@ -19,32 +19,29 @@ func NewCmdDelete(f *cmdutils.Factory) *cobra.Command {
Aliases: []string{"del"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {

issueID := strings.TrimSpace(args[0])
var err error

apiClient, err := f.HttpClient()
if err != nil {
return err
}

repo, err := f.BaseRepo()
issues, repo, err := issueutils.IssuesFromArgs(apiClient, f.BaseRepo, args)
if err != nil {
return err
}

arrIds := strings.Split(strings.Trim(issueID, "[] "), ",")
for _, i2 := range arrIds {
for _, issue := range issues {
if f.IO.IsErrTTY && f.IO.IsaTTY {
fmt.Fprintln(f.IO.StdOut, "- Deleting Issue #"+i2)
fmt.Fprintf(f.IO.StdErr, "- Deleting Issue #%d\n", issue.IID)
}

err := api.DeleteIssue(apiClient, repo.FullName(), utils.StringToInt(i2))
err := api.DeleteIssue(apiClient, repo.FullName(), issue.IID)
if err != nil {
return err
}

fmt.Fprintln(f.IO.StdOut, utils.GreenCheck(), "Issue Deleted")
fmt.Fprintln(f.IO.StdErr, utils.GreenCheck(), "Issue Deleted")
}
return nil
},
Expand Down
50 changes: 22 additions & 28 deletions commands/issue/delete/issue_delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ import (

"github.com/profclems/glab/internal/utils"

"github.com/acarl005/stripansi"
"github.com/profclems/glab/commands/cmdtest"
"github.com/profclems/glab/pkg/api"
"github.com/stretchr/testify/assert"
"github.com/xanzy/go-gitlab"
)

// TODO: test by mocking the appropriate api function
func TestMain(m *testing.M) {
cmdtest.InitTest(m, "mr_delete_test")
cmdtest.InitTest(m, "issue_delete_test")
}

func TestNewCmdDelete(t *testing.T) {
Expand All @@ -29,6 +27,14 @@ func TestNewCmdDelete(t *testing.T) {
}
return nil
}
api.GetIssue = func(client *gitlab.Client, projectID interface{}, issueID int) (*gitlab.Issue, error) {
if projectID == "" || projectID == "WRONG_REPO" || projectID == "expected_err" {
return nil, fmt.Errorf("error expected")
}
return &gitlab.Issue{
IID: issueID,
}, nil
}

tests := []struct {
name string
Expand All @@ -41,59 +47,47 @@ func TestNewCmdDelete(t *testing.T) {
name: "delete",
args: []string{"0", "-R", "NAMESPACE/WRONG_REPO"},
wantErr: true,

assertFunc: func(t *testing.T, out string, err string) {
assert.Contains(t, err, "error expected")
},
},
{
name: "id exists",
args: []string{"1"},
wantErr: false,
assertFunc: func(t *testing.T, out string, err string) {
assert.Contains(t, out, "✓ Issue Deleted\n")
assert.Contains(t, err, "✓ Issue Deleted\n")
},
},
{
name: "delete on different repo",
args: []string{"12", "-R", "profclems/glab"},
wantErr: false,
assertFunc: func(t *testing.T, out string, err string) {
assert.Contains(t, out, "✓ Issue Deleted\n")
assertFunc: func(t *testing.T, out string, stderr string) {
assert.Contains(t, stderr, "✓ Issue Deleted\n")
},
},
}

io, _, stdout, stderr := utils.IOTest()
f := cmdtest.StubFactory("")
f.IO = io
f.IO.IsaTTY = true
f.IO.IsErrTTY = true
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

cmd := NewCmdDelete(f)
io, _, stdout, stderr := utils.IOTest()
f := cmdtest.StubFactory("")
f.IO = io
f.IO.IsaTTY = true
f.IO.IsErrTTY = true

cmd.Flags().StringP("repo", "R", "", "")
cmd := NewCmdDelete(f)

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd.Flags().StringP("repo", "R", "", "")

cli := strings.Join(tt.args, " ")
t.Log(cli)
_, err := cmdtest.RunCommand(cmd, cli)
if !tt.wantErr {
assert.Nil(t, err)
tt.assertFunc(t, stdout.String(), stderr.String())
} else {
assert.NotNil(t, err)
stderr.WriteString(err.Error()) // write err to stderr
}

out := stripansi.Strip(stdout.String())
outErr := stripansi.Strip(stderr.String())

tt.assertFunc(t, out, outErr)
assert.Contains(t, outErr, tt.errMsg)
stderr.Reset()
stdout.Reset()
})
}

Expand Down
124 changes: 124 additions & 0 deletions commands/issue/issueutils/utils.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package issueutils

import (
"context"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"

"github.com/profclems/glab/internal/config"
"github.com/profclems/glab/internal/glrepo"
"github.com/profclems/glab/pkg/api"
"golang.org/x/sync/errgroup"

"github.com/profclems/glab/internal/utils"
"github.com/profclems/glab/pkg/tableprinter"

Expand Down Expand Up @@ -45,3 +54,118 @@ func IssueState(i *gitlab.Issue) (issueID string) {
}
return
}

func IssuesFromArgs(apiClient *gitlab.Client, baseRepoFn func() (glrepo.Interface, error), args []string) ([]*gitlab.Issue, glrepo.Interface, error) {
baseRepo, err := baseRepoFn()
if err != nil {
return nil, nil, err
}
if len(args) <= 1 {
if len(args) == 1 {
args = strings.Split(args[0], ",")
}
if len(args) <= 1 {
issue, repo, err := IssueFromArg(apiClient, baseRepoFn, args[0])
if err != nil {
return nil, nil, err
}
baseRepo = repo
return []*gitlab.Issue{issue}, baseRepo, err
}
}

errGroup, _ := errgroup.WithContext(context.Background())
issues := make([]*gitlab.Issue, len(args))
for i, arg := range args {
i, arg := i, arg
errGroup.Go(func() error {
issue, repo, err := IssueFromArg(apiClient, baseRepoFn, arg)
if err != nil {
return err
}
baseRepo = repo
issues[i] = issue
return nil
})
}
if err := errGroup.Wait(); err != nil {
return nil, nil, err
}
return issues, baseRepo, nil

}

func IssueFromArg(apiClient *gitlab.Client, baseRepoFn func() (glrepo.Interface, error), arg string) (*gitlab.Issue, glrepo.Interface, error) {
issueIID, baseRepo := issueMetadataFromURL(arg)
if issueIID == 0 {
var err error
issueIID, err = strconv.Atoi(strings.TrimPrefix(arg, "#"))
if err != nil {
return nil, nil, fmt.Errorf("invalid issue format: %q", arg)
}
}

if baseRepo == nil {
var err error
baseRepo, err = baseRepoFn()
if err != nil {
return nil, nil, fmt.Errorf("could not determine base repo: %w", err)
}
} else {
// initialize a new HTTP Client with the new host
// TODO: avoid reinitializing the config, get the config as a parameter

cfg, _ := config.Init()
a, err := api.NewClientWithCfg(baseRepo.RepoHost(), cfg, false)
if err != nil {
return nil, nil, err
}
apiClient = a.Lab()
}

issue, err := issueFromIID(apiClient, baseRepo, issueIID)
return issue, baseRepo, err
}

// FIXME: have a single regex to match either of the following
// OWNER/REPO/issues/id
// GROUP/NAMESPACE/REPO/issues/id
var issueURLPersonalRE = regexp.MustCompile(`^/([^/]+)/([^/]+)/issues/(\d+)`)
var issueURLGroupRE = regexp.MustCompile(`^/([^/]+)/([^/]+)/([^/]+)/issues/(\d+)`)

func issueMetadataFromURL(s string) (int, glrepo.Interface) {
u, err := url.Parse(s)
if err != nil {
return 0, nil
}

if u.Scheme != "https" && u.Scheme != "http" {
return 0, nil
}

u.Path = strings.Replace(u.Path, "/-/issues", "/issues", 1)

m := issueURLPersonalRE.FindStringSubmatch(u.Path)
if m == nil {
m = issueURLGroupRE.FindStringSubmatch(u.Path)
if m == nil {
return 0, nil
}
}
var issueIID int
if len(m) > 0 {
issueIID, _ = strconv.Atoi(m[len(m)-1])
}

u.Path = strings.Replace(u.Path, fmt.Sprintf("/issues/%d", issueIID), "", 1)

repo, err := glrepo.FromURL(u)
if err != nil {
return 0, nil
}
return issueIID, repo
}

func issueFromIID(apiClient *gitlab.Client, repo glrepo.Interface, issueIID int) (*gitlab.Issue, error) {
return api.GetIssue(apiClient, repo.FullName(), issueIID)
}
13 changes: 4 additions & 9 deletions commands/issue/note/issue_note_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"fmt"

"github.com/profclems/glab/commands/issue/issueutils"

"github.com/profclems/glab/commands/cmdutils"
"github.com/profclems/glab/internal/utils"
"github.com/profclems/glab/pkg/api"
Expand All @@ -28,20 +30,13 @@ func NewCmdNote(f *cmdutils.Factory) *cobra.Command {
return err
}

repo, err := f.BaseRepo()
issue, repo, err := issueutils.IssueFromArg(apiClient, f.BaseRepo, args[0])
if err != nil {
return err
}

issueID := args[0]

body, _ := cmd.Flags().GetString("message")

issue, err := api.GetIssue(apiClient, repo.FullName(), utils.StringToInt(issueID))
if err != nil {
return err
}

if body == "" {
body = utils.Editor(utils.EditorOptions{
Label: "Note Message:",
Expand All @@ -54,7 +49,7 @@ func NewCmdNote(f *cmdutils.Factory) *cobra.Command {
return errors.New("aborted... Note is empty")
}

noteInfo, err := api.CreateIssueNote(apiClient, repo.FullName(), utils.StringToInt(issueID), &gitlab.CreateIssueNoteOptions{
noteInfo, err := api.CreateIssueNote(apiClient, repo.FullName(), issue.IID, &gitlab.CreateIssueNoteOptions{
Body: &body,
})
if err != nil {
Expand Down
Loading

0 comments on commit 2ff41de

Please sign in to comment.