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

feat(cmdutils): Add project and group milestone prompt #824

Merged
merged 1 commit into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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