Skip to content

Commit

Permalink
feat: new selector
Browse files Browse the repository at this point in the history
  • Loading branch information
domenicsim1 authored Oct 31, 2022
1 parent 18b36f6 commit 564dcff
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 33 deletions.
18 changes: 6 additions & 12 deletions pkg/cmd/project/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package create

import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/MakeNowJust/heredoc/v2"
"github.com/OctopusDeploy/cli/pkg/cmd"
Expand Down Expand Up @@ -83,7 +84,7 @@ func NewCmdCreate(f factory.Factory) *cobra.Command {
Long: "Creates a new project in Octopus Deploy.",
Example: fmt.Sprintf(heredoc.Doc(`
$ %s project create
$ %s project create --process-vcs
$ %s project create --process-vcs
$ %s project create --name 'Deploy web app' --lifecycle 'Default Lifecycle' --group 'Default Project Group'
`), constants.ExecutableName, constants.ExecutableName, constants.ExecutableName),
RunE: func(c *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -176,22 +177,15 @@ func AskProjectGroups(ask question.Asker, value string, getAllGroupsCallback Get
if value != "" {
return value, nil, nil
}
var shouldCreateNewProjectGroup bool
ask(&survey.Confirm{
Message: "Would you like to create a new Project Group?",
Default: false,
}, &shouldCreateNewProjectGroup)

if shouldCreateNewProjectGroup {
return createProjectGroupCallback()
}

g, err := selectors.Select(ask, "You have not specified a Project group for this project. Please select one:", getAllGroupsCallback, func(pg *projectgroups.ProjectGroup) string {
g, shouldCreateNew, err := selectors.SelectOrNew(ask, "You have not specified a Project group for this project. Please select one:", getAllGroupsCallback, func(pg *projectgroups.ProjectGroup) string {
return pg.Name
})
if err != nil {
return "", nil, err
}
if shouldCreateNew {
return createProjectGroupCallback()
}
return g.Name, nil, nil

}
Expand Down
34 changes: 23 additions & 11 deletions pkg/cmd/project/create/create_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package create_test

import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/credentials"
"net/url"
"testing"

"github.com/AlecAivazis/survey/v2"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/credentials"

"github.com/OctopusDeploy/cli/pkg/cmd"
projectCreate "github.com/OctopusDeploy/cli/pkg/cmd/project/create"
projectGroupCreate "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/create"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/surveyext"
"github.com/OctopusDeploy/cli/test/testutil"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectgroups"
"github.com/stretchr/testify/assert"
Expand All @@ -28,13 +30,7 @@ func TestAskProjectGroup_WithProvidedName(t *testing.T) {
func TestAskProjectGroup_WithExistingProjectGroup(t *testing.T) {
pa := []*testutil.PA{
{
Prompt: &survey.Confirm{
Message: "Would you like to create a new Project Group?",
},
Answer: false,
},
{
Prompt: &survey.Select{
Prompt: &surveyext.Select{
Message: "You have not specified a Project group for this project. Please select one:",
Options: []string{
"foo",
Expand All @@ -61,16 +57,32 @@ func TestAskProjectGroup_WithExistingProjectGroup(t *testing.T) {

func TestAskProjectGroup_WithNewProjectGroup(t *testing.T) {
pa := []*testutil.PA{
testutil.NewConfirmPrompt("Would you like to create a new Project Group?", "", true),
{
Prompt: &surveyext.Select{
Message: "You have not specified a Project group for this project. Please select one:",
Options: []string{
"foo",
"bar",
},
},
Answer: constants.PromptCreateNew,
},
}
asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa)

getFakeProjectGroups := func() ([]*projectgroups.ProjectGroup, error) {
return []*projectgroups.ProjectGroup{
projectgroups.NewProjectGroup("foo"),
projectgroups.NewProjectGroup("bar"),
}, nil
}

projectGroupCreateOpts := projectGroupCreate.NewCreateOptions(nil, nil)
createProjectGroup := func() (string, cmd.Dependable, error) {
return "foo", projectGroupCreateOpts, nil
}

value, pgOpts, err := projectCreate.AskProjectGroups(asker, "", nil, createProjectGroup)
value, pgOpts, err := projectCreate.AskProjectGroups(asker, "", getFakeProjectGroups, createProjectGroup)
checkRemainingPrompts()
assert.NoError(t, err)
assert.Equal(t, "foo", value)
Expand Down
4 changes: 4 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const (
NoDescription = "No description provided"
)

const (
PromptCreateNew = "<Create New>"
)

// IsProgrammaticOutputFormat tells you if it is acceptable for your command to
// print miscellaneous output to stdout, such as progress messages.
// If your command is capable of printing such things, you should check the output format
Expand Down
23 changes: 23 additions & 0 deletions pkg/question/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package question

import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/OctopusDeploy/cli/pkg/constants"
"github.com/OctopusDeploy/cli/pkg/surveyext"
)

func MultiSelectMap[T any](ask Asker, message string, items []T, getKey func(item T) string, required bool) ([]T, error) {
Expand Down Expand Up @@ -42,6 +45,26 @@ func SelectMap[T any](ask Asker, message string, items []T, getKey func(item T)
return selectedValue, nil
}

func SelectMapWithNew[T any](ask Asker, message string, items []T, getKey func(item T) string) (T, bool, error) {
optionMap, options := MakeItemMapAndOptions(items, getKey)
var selectedValue T
var selectedKey string
if err := ask(&surveyext.Select{
Message: message,
Options: options,
}, &selectedKey); err != nil {
return selectedValue, false, err
}
if selectedKey == constants.PromptCreateNew {
return *new(T), true, nil
}
selectedValue, ok := optionMap[selectedKey]
if !ok { // without this explict check SelectMap can return nil, nil which people don't expect
return *new(T), false, fmt.Errorf("SelectMap did not get valid answer (selectedKey=%s)", selectedKey)
}
return selectedValue, false, nil
}

func MakeItemMapAndOptions[T any](items []T, getKey func(item T) string) (map[string]T, []string) {
optionMap := make(map[string]T, len(items))
options := make([]string, 0, len(items))
Expand Down
10 changes: 10 additions & 0 deletions pkg/question/selectors/selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,13 @@ func Select[T any](ask question.Asker, questionText string, itemsCallback func()

return question.SelectMap(ask, questionText, items, getKey)
}

// SelectOrNew is the same as Select but show a create new option at the bottom of the list
// When create new is selected the returned bool will be true
func SelectOrNew[T any](ask question.Asker, questionText string, itemsCallback func() ([]*T, error), getKey func(item *T) string) (*T, bool, error) {
items, err := itemsCallback()
if err != nil {
return nil, false, err
}
return question.SelectMapWithNew(ask, questionText, items, getKey)
}
41 changes: 41 additions & 0 deletions pkg/surveyext/paginate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package surveyext

import "github.com/AlecAivazis/survey/v2/core"

func paginate(pageSize int, choices []core.OptionAnswer, sel int) (choiceList []core.OptionAnswer, cursor int) {
var start, end int

if len(choices) < pageSize {
// if we dont have enough options to fill a page
start = 0
end = len(choices)
cursor = sel
choiceList = choices[start:end]

} else if sel < pageSize/2 {
// if we are in the first half page
start = 0
end = pageSize - 1
cursor = sel
choiceList = append(choices[start:end], choices[len(choices)-1])

} else if len(choices)-sel-1 < pageSize/2 {
// if we are in the last half page
start = len(choices) - pageSize
end = len(choices)
cursor = sel - start
choiceList = choices[start:end]
} else {
// somewhere in the middle
above := pageSize / 2
below := pageSize - above

cursor = pageSize / 2
start = sel - above
end = (sel + below) - 1
choiceList = append(choices[start:end], choices[len(choices)-1])
}

// return the subset we care about and the index
return choiceList, cursor
}
Loading

0 comments on commit 564dcff

Please sign in to comment.