From 612ff864276e848c4cfdef187b5f16f5c52f3c25 Mon Sep 17 00:00:00 2001 From: zemzale <14844365+zemzale@users.noreply.github.com> Date: Mon, 10 Jan 2022 15:08:39 +0200 Subject: [PATCH] feat: Add group flag to mr list command (#931) * feat: Add group flag to mr list command This adds a flag for listing groups mrs. This works only for groups in authenticated repos. This is acceptable since mr list only works on authenticated repos too. Issue #909 * feat: Add group flag for issue listing Issue #909 --- api/issue.go | 44 +++++++++++++++++++++++++ api/merge_request.go | 45 ++++++++++++++++++++++++++ commands/issue/list/issue_list.go | 21 +++++++++--- commands/issue/list/issue_list_test.go | 40 +++++++++++++++++------ commands/mr/list/mr_list.go | 9 ++++-- commands/mr/list/mr_list_test.go | 43 +++++++++++++++++------- 6 files changed, 173 insertions(+), 29 deletions(-) diff --git a/api/issue.go b/api/issue.go index c61b3953..39640911 100644 --- a/api/issue.go +++ b/api/issue.go @@ -45,6 +45,50 @@ var GetIssue = func(client *gitlab.Client, projectID interface{}, issueID int) ( return issue, nil } +var ProjectListIssueOptionsToGroup = func(l *gitlab.ListProjectIssuesOptions) *gitlab.ListGroupIssuesOptions { + return &gitlab.ListGroupIssuesOptions{ + ListOptions: l.ListOptions, + State: l.State, + Labels: l.Labels, + NotLabels: l.NotLabels, + WithLabelDetails: l.WithLabelDetails, + IIDs: l.IIDs, + Milestone: l.Milestone, + Scope: l.Scope, + AuthorID: l.AuthorID, + NotAuthorID: l.NotAuthorID, + AssigneeID: l.AssigneeID, + NotAssigneeID: l.NotAssigneeID, + AssigneeUsername: l.AssigneeUsername, + MyReactionEmoji: l.MyReactionEmoji, + NotMyReactionEmoji: l.NotMyReactionEmoji, + OrderBy: l.OrderBy, + Sort: l.Sort, + Search: l.Search, + In: l.In, + CreatedAfter: l.CreatedAfter, + CreatedBefore: l.CreatedBefore, + UpdatedAfter: l.UpdatedAfter, + UpdatedBefore: l.UpdatedBefore, + IssueType: l.IssueType, + } +} + +var ListGroupIssues = func(client *gitlab.Client, groupID interface{}, opts *gitlab.ListGroupIssuesOptions) ([]*gitlab.Issue, error) { + if client == nil { + client = apiClient.Lab() + } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + issues, _, err := client.Issues.ListGroupIssues(groupID, opts) + if err != nil { + return nil, err + } + + return issues, nil +} + var ListIssues = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectIssuesOptions) ([]*gitlab.Issue, error) { if client == nil { client = apiClient.Lab() diff --git a/api/merge_request.go b/api/merge_request.go index ded65095..d711aa36 100644 --- a/api/merge_request.go +++ b/api/merge_request.go @@ -40,6 +40,51 @@ var GetMR = func(client *gitlab.Client, projectID interface{}, mrID int, opts *g return mr, nil } +var ListGroupMRs = func(client *gitlab.Client, groupID interface{}, opts *gitlab.ListGroupMergeRequestsOptions) ([]*gitlab.MergeRequest, error) { + if client == nil { + client = apiClient.Lab() + } + if opts.PerPage == 0 { + opts.PerPage = DefaultListLimit + } + + mrs, _, err := client.MergeRequests.ListGroupMergeRequests(groupID, opts) + if err != nil { + return nil, err + } + + return mrs, nil +} + +var ProjectListMROptionsToGroup = func(l *gitlab.ListProjectMergeRequestsOptions) *gitlab.ListGroupMergeRequestsOptions { + return &gitlab.ListGroupMergeRequestsOptions{ + ListOptions: l.ListOptions, + State: l.State, + OrderBy: l.OrderBy, + Sort: l.Sort, + Milestone: l.Milestone, + View: l.View, + Labels: l.Labels, + NotLabels: l.NotLabels, + WithLabelsDetails: l.WithLabelsDetails, + WithMergeStatusRecheck: l.WithMergeStatusRecheck, + CreatedAfter: l.CreatedAfter, + CreatedBefore: l.CreatedBefore, + UpdatedAfter: l.UpdatedAfter, + UpdatedBefore: l.UpdatedBefore, + Scope: l.Scope, + AuthorID: l.AuthorID, + AssigneeID: l.AssigneeID, + ReviewerID: l.ReviewerID, + ReviewerUsername: l.ReviewerUsername, + MyReactionEmoji: l.MyReactionEmoji, + SourceBranch: l.SourceBranch, + TargetBranch: l.TargetBranch, + Search: l.Search, + WIP: l.WIP, + } +} + var ListMRs = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListProjectMergeRequestsOptions) ([]*gitlab.MergeRequest, error) { if client == nil { client = apiClient.Lab() diff --git a/commands/issue/list/issue_list.go b/commands/issue/list/issue_list.go index f3d380db..655d25bc 100644 --- a/commands/issue/list/issue_list.go +++ b/commands/issue/list/issue_list.go @@ -29,6 +29,7 @@ type ListOptions struct { Milestone string Mine bool Search string + Group string // issue states State string @@ -124,6 +125,7 @@ func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error) *cobra. issueListCmd.Flags().BoolVarP(&opts.Confidential, "confidential", "C", false, "Filter by confidential issues") issueListCmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number") issueListCmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 30, "Number of items to list per page. (default 30)") + issueListCmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Get issues from group and it's subgroups") issueListCmd.Flags().BoolP("opened", "o", false, "Get only opened issues") _ = issueListCmd.Flags().MarkHidden("opened") @@ -214,13 +216,22 @@ func listRun(opts *ListOptions) error { opts.ListType = "search" } - issues, err := api.ListIssues(apiClient, repo.FullName(), listOpts) - if err != nil { - return err - } - + var issues []*gitlab.Issue title := utils.NewListTitle(opts.TitleQualifier + " issue") title.RepoName = repo.FullName() + if opts.Group != "" { + issues, err = api.ListGroupIssues(apiClient, opts.Group, api.ProjectListIssueOptionsToGroup(listOpts)) + if err != nil { + return err + } + title.RepoName = opts.Group + } else { + issues, err = api.ListIssues(apiClient, repo.FullName(), listOpts) + if err != nil { + return err + } + } + title.Page = listOpts.Page title.ListActionType = opts.ListType title.CurrentPageTotal = len(issues) diff --git a/commands/issue/list/issue_list_test.go b/commands/issue/list/issue_list_test.go index 424aaa14..c0b8e59b 100644 --- a/commands/issue/list/issue_list_test.go +++ b/commands/issue/list/issue_list_test.go @@ -143,22 +143,42 @@ func TestIssueList_tty(t *testing.T) { } func TestIssueList_tty_withFlags(t *testing.T) { - fakeHTTP := httpmock.New() - defer fakeHTTP.Verify(t) + t.Run("project", func(t *testing.T) { + fakeHTTP := httpmock.New() + defer fakeHTTP.Verify(t) - fakeHTTP.RegisterResponder("GET", "/projects/OWNER/REPO/issues", - httpmock.NewStringResponse(200, `[]`)) + fakeHTTP.RegisterResponder("GET", "/projects/OWNER/REPO/issues", + httpmock.NewStringResponse(200, `[]`)) - output, err := runCommand(fakeHTTP, true, "--opened -P1 -p100 --confidential -a someuser -l bug -m1", nil, "") - if err != nil { - t.Errorf("error running command `issue list`: %v", err) - } + output, err := runCommand(fakeHTTP, true, "--opened -P1 -p100 --confidential -a someuser -l bug -m1", nil, "") + if err != nil { + t.Errorf("error running command `issue list`: %v", err) + } - cmdtest.Eq(t, output.Stderr(), "") - cmdtest.Eq(t, output.String(), `No open issues match your search in OWNER/REPO + cmdtest.Eq(t, output.Stderr(), "") + cmdtest.Eq(t, output.String(), `No open issues match your search in OWNER/REPO `) + }) + t.Run("group", func(t *testing.T) { + fakeHTTP := httpmock.New() + defer fakeHTTP.Verify(t) + + fakeHTTP.RegisterResponder("GET", "/groups/GROUP/issues", + httpmock.NewStringResponse(200, `[]`)) + + output, err := runCommand(fakeHTTP, true, "--group GROUP", nil, "") + if err != nil { + t.Errorf("error running command `issue list`: %v", err) + } + + cmdtest.Eq(t, output.Stderr(), "") + cmdtest.Eq(t, output.String(), `No open issues match your search in GROUP + + +`) + }) } func TestIssueList_tty_mine(t *testing.T) { diff --git a/commands/mr/list/mr_list.go b/commands/mr/list/mr_list.go index 24d2517e..655aaa8c 100644 --- a/commands/mr/list/mr_list.go +++ b/commands/mr/list/mr_list.go @@ -30,6 +30,7 @@ type ListOptions struct { TargetBranch string Search string Mine bool + Group string // issue states State string @@ -135,6 +136,7 @@ func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error) *cobra. mrListCmd.Flags().BoolVarP(&opts.Mine, "mine", "", false, "Get only merge requests assigned to me") _ = mrListCmd.Flags().MarkHidden("mine") _ = mrListCmd.Flags().MarkDeprecated("mine", "use --assignee=@me") + mrListCmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Get MRs from group and it's subgroups") return mrListCmd } @@ -223,10 +225,15 @@ func listRun(opts *ListOptions) error { reviewerIds = append(reviewerIds, user.ID) } } + title := utils.NewListTitle(opts.TitleQualifier + " merge request") + title.RepoName = repo.FullName() if len(assigneeIds) > 0 || len(reviewerIds) > 0 { mergeRequests, err = api.ListMRsWithAssigneesOrReviewers(apiClient, repo.FullName(), l, assigneeIds, reviewerIds) + } else if opts.Group != "" { + mergeRequests, err = api.ListGroupMRs(apiClient, opts.Group, api.ProjectListMROptionsToGroup(l)) + title.RepoName = opts.Group } else { mergeRequests, err = api.ListMRs(apiClient, repo.FullName(), l) } @@ -234,8 +241,6 @@ func listRun(opts *ListOptions) error { return err } - title := utils.NewListTitle(opts.TitleQualifier + " merge request") - title.RepoName = repo.FullName() title.Page = l.Page title.ListActionType = opts.ListType title.CurrentPageTotal = len(mergeRequests) diff --git a/commands/mr/list/mr_list_test.go b/commands/mr/list/mr_list_test.go index 0fa12585..794d97b6 100644 --- a/commands/mr/list/mr_list_test.go +++ b/commands/mr/list/mr_list_test.go @@ -174,25 +174,44 @@ func TestMergeRequestList_tty(t *testing.T) { } func TestMergeRequestList_tty_withFlags(t *testing.T) { - fakeHTTP := httpmock.New() - defer fakeHTTP.Verify(t) + t.Run("repo", func(t *testing.T) { + fakeHTTP := httpmock.New() + defer fakeHTTP.Verify(t) - fakeHTTP.RegisterResponder("GET", "/projects/OWNER/REPO/merge_requests", - httpmock.NewStringResponse(200, `[]`)) + fakeHTTP.RegisterResponder("GET", "/projects/OWNER/REPO/merge_requests", + httpmock.NewStringResponse(200, `[]`)) - fakeHTTP.RegisterResponder("GET", "/users", - httpmock.NewStringResponse(200, `[{"id" : 1, "iid" : 1, "username": "john_smith"}]`)) + fakeHTTP.RegisterResponder("GET", "/users", + httpmock.NewStringResponse(200, `[{"id" : 1, "iid" : 1, "username": "john_smith"}]`)) - output, err := runCommand(fakeHTTP, true, "--opened -P1 -p100 -a someuser -l bug -m1", nil, "") - if err != nil { - t.Errorf("error running command `issue list`: %v", err) - } + output, err := runCommand(fakeHTTP, true, "--opened -P1 -p100 -a someuser -l bug -m1", nil, "") + if err != nil { + t.Errorf("error running command `issue list`: %v", err) + } - cmdtest.Eq(t, output.Stderr(), "") - cmdtest.Eq(t, output.String(), `No open merge requests match your search in OWNER/REPO + cmdtest.Eq(t, output.Stderr(), "") + cmdtest.Eq(t, output.String(), `No open merge requests match your search in OWNER/REPO `) + }) + t.Run("group", func(t *testing.T) { + fakeHTTP := httpmock.New() + defer fakeHTTP.Verify(t) + + fakeHTTP.RegisterResponder("GET", "/groups/GROUP/merge_requests", + httpmock.NewStringResponse(200, `[]`)) + + output, err := runCommand(fakeHTTP, true, "--group GROUP", nil, "") + if err != nil { + t.Errorf("error running command `mr list`: %v", err) + } + + cmdtest.Eq(t, output.Stderr(), "") + cmdtest.Eq(t, output.String(), `No open merge requests available on GROUP + +`) + }) } func makeHyperlink(linkText, targetURL string) string {