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

Commit

Permalink
feat(cmdutils): Add project and group milestone promt
Browse files Browse the repository at this point in the history
When creating issues we prompted only project milestones. This would
break workflows where group milestones were used.

Issue #698
  • Loading branch information
zemzale committed Aug 14, 2021
1 parent 0456108 commit 4af30b2
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 18 deletions.
122 changes: 120 additions & 2 deletions api/milestone.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,87 @@ import (
"fmt"

"github.com/xanzy/go-gitlab"
"golang.org/x/sync/errgroup"
)

var ListMilestones = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListMilestonesOptions) ([]*gitlab.Milestone, error) {
// Describe namespace kinds which is either group or user
// See docs: https://docs.gitlab.com/ee/api/namespaces.html
const (
NamespaceKindUser = "user"
NamespaceKindGroup = "group"
)

type Milestone struct {
ID int
Title string
}

func NewProjectMilestone(m *gitlab.Milestone) *Milestone {
return &Milestone{
ID: m.ID,
Title: m.Title,
}
}

func NewGroupMilestone(m *gitlab.GroupMilestone) *Milestone {
return &Milestone{
ID: m.ID,
Title: m.Title,
}
}

type ListMilestonesOptions struct {
IIDs []int
State *string
Title *string
Search *string
IncludeParentMilestones *bool
PerPage int
Page int
}

func (opts *ListMilestonesOptions) ListProjectMilestonesOptions() *gitlab.ListMilestonesOptions {
projectOpts := &gitlab.ListMilestonesOptions{
IIDs: opts.IIDs,
State: opts.State,
Title: opts.Title,
Search: opts.Search,
}
projectOpts.PerPage = opts.PerPage
projectOpts.Page = opts.Page
return projectOpts
}

func (opts *ListMilestonesOptions) ListGroupMilestonesOptions() *gitlab.ListGroupMilestonesOptions {
groupOpts := &gitlab.ListGroupMilestonesOptions{
IIDs: opts.IIDs,
State: opts.State,
Title: opts.Title,
Search: opts.Search,
IncludeParentMilestones: opts.IncludeParentMilestones,
}
groupOpts.PerPage = opts.PerPage
groupOpts.Page = opts.Page
return groupOpts
}

var ListGroupMilestones = func(client *gitlab.Client, groupID interface{}, opts *gitlab.ListGroupMilestonesOptions) ([]*gitlab.GroupMilestone, error) {
if client == nil {
client = apiClient.Lab()
}

if opts.PerPage == 0 {
opts.PerPage = DefaultListLimit
}

milestone, _, err := client.GroupMilestones.ListGroupMilestones(groupID, opts)
if err != nil {
return nil, err
}
return milestone, nil
}

var ListProjectMilestones = func(client *gitlab.Client, projectID interface{}, opts *gitlab.ListMilestonesOptions) ([]*gitlab.Milestone, error) {
if client == nil {
client = apiClient.Lab()
}
Expand All @@ -22,7 +100,7 @@ var ListMilestones = func(client *gitlab.Client, projectID interface{}, opts *gi
return milestone, nil
}

var MilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
var ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
opts := &gitlab.ListMilestonesOptions{Title: gitlab.String(name)}

if client == nil {
Expand All @@ -44,3 +122,43 @@ var MilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name s

return milestones[0], nil
}

var ListAllMilestones = func(client *gitlab.Client, projectID interface{}, opts *ListMilestonesOptions) ([]*Milestone, error) {
project, err := GetProject(client, projectID)
if err != nil {
return nil, err
}

errGroup := &errgroup.Group{}
projectMilestones := []*gitlab.Milestone{}
groupMilestones := []*gitlab.GroupMilestone{}

errGroup.Go(func() error {
var err error
projectMilestones, err = ListProjectMilestones(client, projectID, opts.ListProjectMilestonesOptions())
return err
})

if project.Namespace.Kind == NamespaceKindGroup {
errGroup.Go(func() error {
var err error
groupMilestones, err = ListGroupMilestones(client, project.Namespace.ID, opts.ListGroupMilestonesOptions())
return err
})
}

if err := errGroup.Wait(); err != nil {
return nil, fmt.Errorf("failed to get all project related milestones. %w", err)
}

milestones := make([]*Milestone, 0, len(projectMilestones)+len(groupMilestones))
for _, v := range projectMilestones {
milestones = append(milestones, NewProjectMilestone(v))
}

for _, v := range groupMilestones {
milestones = append(milestones, NewGroupMilestone(v))
}

return milestones, nil
}
17 changes: 9 additions & 8 deletions commands/cmdutils/cmdutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,14 @@ func LabelsPrompt(response *[]string, apiClient *gitlab.Client, repoRemote *glre

func MilestonesPrompt(response *int, apiClient *gitlab.Client, repoRemote *glrepo.Remote, io *iostreams.IOStreams) (err error) {
var milestoneOptions []string
milestoneMap := map[string]*gitlab.Milestone{}
milestoneMap := map[string]int{}

lOpts := &gitlab.ListMilestonesOptions{
State: gitlab.String("active"),
lOpts := &api.ListMilestonesOptions{
IncludeParentMilestones: gitlab.Bool(true),
State: gitlab.String("active"),
PerPage: 100,
}
lOpts.PerPage = 100
milestones, err := api.ListMilestones(apiClient, repoRemote.FullName(), lOpts)
milestones, err := api.ListAllMilestones(apiClient, repoRemote.FullName(), lOpts)
if err != nil {
return err
}
Expand All @@ -191,15 +192,15 @@ func MilestonesPrompt(response *int, apiClient *gitlab.Client, repoRemote *glrep

for i := range milestones {
milestoneOptions = append(milestoneOptions, milestones[i].Title)
milestoneMap[milestones[i].Title] = milestones[i]
milestoneMap[milestones[i].Title] = milestones[i].ID
}

var selectedMilestone string
err = prompt.Select(&selectedMilestone, "milestone", "Select Milestone", milestoneOptions)
if err != nil {
return err
}
*response = milestoneMap[selectedMilestone].ID
*response = milestoneMap[selectedMilestone]

return nil
}
Expand Down Expand Up @@ -359,7 +360,7 @@ func ParseMilestone(apiClient *gitlab.Client, repo glrepo.Interface, milestoneTi
return milestoneID, nil
}

milestone, err := api.MilestoneByTitle(apiClient, repo.FullName(), milestoneTitle)
milestone, err := api.ProjectMilestoneByTitle(apiClient, repo.FullName(), milestoneTitle)
if err != nil {
return 0, err
}
Expand Down
16 changes: 8 additions & 8 deletions commands/cmdutils/cmdutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ func Test_ParseMilestoneTitleIsID(t *testing.T) {
expectedMilestoneID := 1

// Override function to return an error, it should never reach this
api.MilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return nil, fmt.Errorf("We shouldn't have reached here")
}

Expand All @@ -497,7 +497,7 @@ func Test_ParseMilestoneAPIFail(t *testing.T) {
want := "api call failed in api.MilestoneByTitle()"

// Override function to return an error simulating an API call failure
api.MilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return nil, fmt.Errorf("api call failed in api.MilestoneByTitle()")
}

Expand All @@ -515,7 +515,7 @@ func Test_ParseMilestoneTitleToID(t *testing.T) {
expectedID := 3

// Override function so it returns the correct milestone
api.MilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
api.ProjectMilestoneByTitle = func(client *gitlab.Client, projectID interface{}, name string) (*gitlab.Milestone, error) {
return &gitlab.Milestone{
Title: "kind: testing",
ID: 3,
Expand Down Expand Up @@ -817,7 +817,7 @@ func Test_AssigneesPrompt(t *testing.T) {
}

func Test_MilestonesPrompt(t *testing.T) {
mockMilestones := []*gitlab.Milestone{
mockMilestones := []*api.Milestone{
{
Title: "New Release",
ID: 5,
Expand All @@ -833,7 +833,7 @@ func Test_MilestonesPrompt(t *testing.T) {
}

// Override API.ListMilestones so it doesn't make any network calls
api.ListMilestones = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListMilestonesOptions) ([]*gitlab.Milestone, error) {
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return mockMilestones, nil
}

Expand Down Expand Up @@ -908,8 +908,8 @@ func Test_MilestonesPromptNoPrompts(t *testing.T) {
// Override api.ListMilestones so it returns an empty slice, we are testing if MilestonesPrompt()
// will print the correct message to `stderr` when it tries to get the list of Milestones in a
// project but the project has no milestones
api.ListMilestones = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListMilestonesOptions) ([]*gitlab.Milestone, error) {
return []*gitlab.Milestone{}, nil
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return []*api.Milestone{}, nil
}

// mock glrepo.Remote object
Expand All @@ -936,7 +936,7 @@ func Test_MilestonesPromptNoPrompts(t *testing.T) {
func TestMilestonesPromptFailures(t *testing.T) {
// Override api.ListMilestones so it returns an error, we are testing to see if error
// handling from the usage of api.ListMilestones is correct
api.ListMilestones = func(_ *gitlab.Client, _ interface{}, _ *gitlab.ListMilestonesOptions) ([]*gitlab.Milestone, error) {
api.ListAllMilestones = func(_ *gitlab.Client, _ interface{}, _ *api.ListMilestonesOptions) ([]*api.Milestone, error) {
return nil, errors.New("api.ListMilestones() failed")
}

Expand Down

0 comments on commit 4af30b2

Please sign in to comment.