Merge pull request #7859 from cli/pr-shared-prompts

switch to prompter in pr shared code
This commit is contained in:
Nate Smith 2023-08-18 00:48:25 -05:00 committed by GitHub
commit 6417e1c19f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 137 additions and 206 deletions

View file

@ -268,7 +268,7 @@ func createRun(opts *CreateOptions) (err error) {
Repo: baseRepo,
State: &tb,
}
err = prShared.MetadataSurvey(opts.IO, baseRepo, fetcher, &tb)
err = prShared.MetadataSurvey(opts.Prompter, opts.IO, baseRepo, fetcher, &tb)
if err != nil {
return
}

View file

@ -21,10 +21,11 @@ type EditOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
Prompter prShared.EditPrompter
DetermineEditor func() (string, error)
FieldsToEditSurvey func(*prShared.Editable) error
EditFieldsSurvey func(*prShared.Editable, string) error
FieldsToEditSurvey func(prShared.EditPrompter, *prShared.Editable) error
EditFieldsSurvey func(prShared.EditPrompter, *prShared.Editable, string) error
FetchOptions func(*api.Client, ghrepo.Interface, *prShared.Editable) error
SelectorArgs []string
@ -41,6 +42,7 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
FieldsToEditSurvey: prShared.FieldsToEditSurvey,
EditFieldsSurvey: prShared.EditFieldsSurvey,
FetchOptions: prShared.FetchOptions,
Prompter: f.Prompter,
}
var bodyFile string
@ -152,7 +154,7 @@ func editRun(opts *EditOptions) error {
// Prompt the user which fields they'd like to edit.
editable := opts.Editable
if opts.Interactive {
err = opts.FieldsToEditSurvey(&editable)
err = opts.FieldsToEditSurvey(opts.Prompter, &editable)
if err != nil {
return err
}
@ -222,7 +224,7 @@ func editRun(opts *EditOptions) error {
if err != nil {
return err
}
err = opts.EditFieldsSurvey(&editable, editorCommand)
err = opts.EditFieldsSurvey(opts.Prompter, &editable, editorCommand)
if err != nil {
return err
}

View file

@ -511,7 +511,7 @@ func Test_editRun(t *testing.T) {
input: &EditOptions{
SelectorArgs: []string{"123"},
Interactive: true,
FieldsToEditSurvey: func(eo *prShared.Editable) error {
FieldsToEditSurvey: func(p prShared.EditPrompter, eo *prShared.Editable) error {
eo.Title.Edited = true
eo.Body.Edited = true
eo.Assignees.Edited = true
@ -520,7 +520,7 @@ func Test_editRun(t *testing.T) {
eo.Milestone.Edited = true
return nil
},
EditFieldsSurvey: func(eo *prShared.Editable, _ string) error {
EditFieldsSurvey: func(p prShared.EditPrompter, eo *prShared.Editable, _ string) error {
eo.Title.Value = "new title"
eo.Body.Value = "new body"
eo.Assignees.Value = []string{"monalisa", "hubot"}

View file

@ -361,7 +361,7 @@ func createRun(opts *CreateOptions) (err error) {
Repo: ctx.BaseRepo,
State: state,
}
err = shared.MetadataSurvey(opts.IO, ctx.BaseRepo, fetcher, state)
err = shared.MetadataSurvey(opts.Prompter, opts.IO, ctx.BaseRepo, fetcher, state)
if err != nil {
return
}

View file

@ -24,6 +24,7 @@ type EditOptions struct {
Surveyor Surveyor
Fetcher EditableOptionsFetcher
EditorRetriever EditorRetriever
Prompter shared.EditPrompter
SelectorArg string
Interactive bool
@ -35,9 +36,10 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
opts := &EditOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
Surveyor: surveyor{},
Surveyor: surveyor{P: f.Prompter},
Fetcher: fetcher{},
EditorRetriever: editorRetriever{config: f.Config},
Prompter: f.Prompter,
}
var bodyFile string
@ -280,14 +282,16 @@ type Surveyor interface {
EditFields(*shared.Editable, string) error
}
type surveyor struct{}
type surveyor struct {
P shared.EditPrompter
}
func (s surveyor) FieldsToEdit(editable *shared.Editable) error {
return shared.FieldsToEditSurvey(editable)
return shared.FieldsToEditSurvey(s.P, editable)
}
func (s surveyor) EditFields(editable *shared.Editable, editorCmd string) error {
return shared.EditFieldsSurvey(editable, editorCmd)
return shared.EditFieldsSurvey(s.P, editable, editorCmd)
}
type EditableOptionsFetcher interface {

View file

@ -4,12 +4,9 @@ import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/set"
"github.com/cli/cli/v2/pkg/surveyext"
)
type Editable struct {
@ -255,34 +252,45 @@ func (ep *EditableProjects) clone() EditableProjects {
}
}
func EditFieldsSurvey(editable *Editable, editorCommand string) error {
type EditPrompter interface {
Select(string, string, []string) (int, error)
Input(string, string) (string, error)
MarkdownEditor(string, string, bool) (string, error)
MultiSelect(string, []string, []string) ([]int, error)
Confirm(string, bool) (bool, error)
}
func EditFieldsSurvey(p EditPrompter, editable *Editable, editorCommand string) error {
var err error
if editable.Title.Edited {
editable.Title.Value, err = titleSurvey(editable.Title.Default)
editable.Title.Value, err = p.Input("Title", editable.Title.Default)
if err != nil {
return err
}
}
if editable.Body.Edited {
editable.Body.Value, err = bodySurvey(editable.Body.Default, editorCommand)
editable.Body.Value, err = p.MarkdownEditor("Body", editable.Body.Default, false)
if err != nil {
return err
}
}
if editable.Reviewers.Edited {
editable.Reviewers.Value, err = multiSelectSurvey("Reviewers", editable.Reviewers.Default, editable.Reviewers.Options)
editable.Reviewers.Value, err = multiSelectSurvey(
p, "Reviewers", editable.Reviewers.Default, editable.Reviewers.Options)
if err != nil {
return err
}
}
if editable.Assignees.Edited {
editable.Assignees.Value, err = multiSelectSurvey("Assignees", editable.Assignees.Default, editable.Assignees.Options)
editable.Assignees.Value, err = multiSelectSurvey(
p, "Assignees", editable.Assignees.Default, editable.Assignees.Options)
if err != nil {
return err
}
}
if editable.Labels.Edited {
editable.Labels.Add, err = multiSelectSurvey("Labels", editable.Labels.Default, editable.Labels.Options)
editable.Labels.Add, err = multiSelectSurvey(
p, "Labels", editable.Labels.Default, editable.Labels.Options)
if err != nil {
return err
}
@ -300,18 +308,19 @@ func EditFieldsSurvey(editable *Editable, editorCommand string) error {
}
}
if editable.Projects.Edited {
editable.Projects.Value, err = multiSelectSurvey("Projects", editable.Projects.Default, editable.Projects.Options)
editable.Projects.Value, err = multiSelectSurvey(
p, "Projects", editable.Projects.Default, editable.Projects.Options)
if err != nil {
return err
}
}
if editable.Milestone.Edited {
editable.Milestone.Value, err = milestoneSurvey(editable.Milestone.Default, editable.Milestone.Options)
editable.Milestone.Value, err = milestoneSurvey(p, editable.Milestone.Default, editable.Milestone.Options)
if err != nil {
return err
}
}
confirm, err := confirmSurvey()
confirm, err := p.Confirm("Submit?", true)
if err != nil {
return err
}
@ -322,7 +331,7 @@ func EditFieldsSurvey(editable *Editable, editorCommand string) error {
return nil
}
func FieldsToEditSurvey(editable *Editable) error {
func FieldsToEditSurvey(p EditPrompter, editable *Editable) error {
contains := func(s []string, str string) bool {
for _, v := range s {
if v == str {
@ -337,7 +346,7 @@ func FieldsToEditSurvey(editable *Editable) error {
opts = append(opts, "Reviewers")
}
opts = append(opts, "Assignees", "Labels", "Projects", "Milestone")
results, err := multiSelectSurvey("What would you like to edit?", []string{}, opts)
results, err := multiSelectSurvey(p, "What would you like to edit?", []string{}, opts)
if err != nil {
return err
}
@ -414,67 +423,34 @@ func FetchOptions(client *api.Client, repo ghrepo.Interface, editable *Editable)
return nil
}
func titleSurvey(title string) (string, error) {
var result string
q := &survey.Input{
Message: "Title",
Default: title,
}
err := survey.AskOne(q, &result)
return result, err
}
func bodySurvey(body, editorCommand string) (string, error) {
var result string
q := &surveyext.GhEditor{
EditorCommand: editorCommand,
Editor: &survey.Editor{
Message: "Body",
FileName: "*.md",
Default: body,
HideDefault: true,
AppendDefault: true,
},
}
err := survey.AskOne(q, &result)
return result, err
}
func multiSelectSurvey(message string, defaults, options []string) ([]string, error) {
func multiSelectSurvey(p EditPrompter, message string, defaults, options []string) (results []string, err error) {
if len(options) == 0 {
return nil, nil
}
var results []string
q := &survey.MultiSelect{
Message: message,
Options: options,
Default: defaults,
Filter: prompter.LatinMatchingFilter,
var selected []int
selected, err = p.MultiSelect(message, defaults, options)
if err != nil {
return
}
err := survey.AskOne(q, &results)
for _, i := range selected {
results = append(results, options[i])
}
return results, err
}
func milestoneSurvey(title string, opts []string) (string, error) {
func milestoneSurvey(p EditPrompter, title string, opts []string) (result string, err error) {
if len(opts) == 0 {
return "", nil
}
var result string
q := &survey.Select{
Message: "Milestone",
Options: opts,
Default: title,
var selected int
selected, err = p.Select("Milestone", title, opts)
if err != nil {
return
}
err := survey.AskOne(q, &result)
return result, err
}
func confirmSurvey() (bool, error) {
var result bool
q := &survey.Confirm{
Message: "Submit?",
Default: true,
}
err := survey.AskOne(q, &result)
return result, err
result = opts[selected]
return
}

View file

@ -4,12 +4,9 @@ import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
)
type Action int
@ -37,6 +34,7 @@ type Prompt interface {
Select(string, string, []string) (int, error)
MarkdownEditor(string, string, bool) (string, error)
Confirm(string, bool) (bool, error)
MultiSelect(string, []string, []string) ([]int, error)
}
func ConfirmIssueSubmission(p Prompt, allowPreview bool, allowMetadata bool) (Action, error) {
@ -142,7 +140,7 @@ type RepoMetadataFetcher interface {
RepoMetadataFetch(api.RepoMetadataInput) (*api.RepoMetadataResult, error)
}
func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher RepoMetadataFetcher, state *IssueMetadataState) error {
func MetadataSurvey(p Prompt, io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher RepoMetadataFetcher, state *IssueMetadataState) error {
isChosen := func(m string) bool {
for _, c := range state.Metadata {
if m == c {
@ -160,18 +158,12 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
}
extraFieldsOptions = append(extraFieldsOptions, "Assignees", "Labels", "Projects", "Milestone")
//nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter
err := prompt.SurveyAsk([]*survey.Question{
{
Name: "metadata",
Prompt: &survey.MultiSelect{
Message: "What would you like to add?",
Options: extraFieldsOptions,
},
},
}, state)
selected, err := p.MultiSelect("What would you like to add?", nil, extraFieldsOptions)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
return err
}
for _, i := range selected {
state.Metadata = append(state.Metadata, extraFieldsOptions[i])
}
metadataInput := api.RepoMetadataInput{
@ -215,63 +207,62 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
milestones = append(milestones, m.Title)
}
var mqs []*survey.Question
values := struct {
Reviewers []string
Assignees []string
Labels []string
Projects []string
Milestone string
}{}
if isChosen("Reviewers") {
if len(reviewers) > 0 {
mqs = append(mqs, &survey.Question{
Name: "reviewers",
Prompt: &survey.MultiSelect{
Message: "Reviewers",
Options: reviewers,
Default: state.Reviewers,
Filter: prompter.LatinMatchingFilter,
},
})
selected, err := p.MultiSelect("Reviewers", state.Reviewers, reviewers)
if err != nil {
return err
}
for _, i := range selected {
values.Reviewers = append(values.Reviewers, reviewers[i])
}
} else {
fmt.Fprintln(io.ErrOut, "warning: no available reviewers")
}
}
if isChosen("Assignees") {
if len(assignees) > 0 {
mqs = append(mqs, &survey.Question{
Name: "assignees",
Prompt: &survey.MultiSelect{
Message: "Assignees",
Options: assignees,
Default: state.Assignees,
Filter: prompter.LatinMatchingFilter,
},
})
selected, err := p.MultiSelect("Assignees", state.Assignees, assignees)
if err != nil {
return err
}
for _, i := range selected {
values.Assignees = append(values.Assignees, assignees[i])
}
} else {
fmt.Fprintln(io.ErrOut, "warning: no assignable users")
}
}
if isChosen("Labels") {
if len(labels) > 0 {
mqs = append(mqs, &survey.Question{
Name: "labels",
Prompt: &survey.MultiSelect{
Message: "Labels",
Options: labels,
Default: state.Labels,
Filter: prompter.LatinMatchingFilter,
},
})
selected, err := p.MultiSelect("Labels", state.Labels, labels)
if err != nil {
return err
}
for _, i := range selected {
values.Labels = append(values.Labels, labels[i])
}
} else {
fmt.Fprintln(io.ErrOut, "warning: no labels in the repository")
}
}
if isChosen("Projects") {
if len(projects) > 0 {
mqs = append(mqs, &survey.Question{
Name: "projects",
Prompt: &survey.MultiSelect{
Message: "Projects",
Options: projects,
Default: state.Projects,
Filter: prompter.LatinMatchingFilter,
},
})
selected, err := p.MultiSelect("Projects", state.Projects, projects)
if err != nil {
return err
}
for _, i := range selected {
values.Projects = append(values.Projects, projects[i])
}
} else {
fmt.Fprintln(io.ErrOut, "warning: no projects to choose from")
}
@ -284,33 +275,16 @@ func MetadataSurvey(io *iostreams.IOStreams, baseRepo ghrepo.Interface, fetcher
} else {
milestoneDefault = milestones[1]
}
mqs = append(mqs, &survey.Question{
Name: "milestone",
Prompt: &survey.Select{
Message: "Milestone",
Options: milestones,
Default: milestoneDefault,
},
})
selected, err := p.Select("Milestone", milestoneDefault, milestones)
if err != nil {
return err
}
values.Milestone = milestones[selected]
} else {
fmt.Fprintln(io.ErrOut, "warning: no milestones in the repository")
}
}
values := struct {
Reviewers []string
Assignees []string
Labels []string
Projects []string
Milestone string
}{}
//nolint:staticcheck // SA1019: prompt.SurveyAsk is deprecated: use Prompter
err = prompt.SurveyAsk(mqs, &values)
if err != nil {
return fmt.Errorf("could not prompt: %w", err)
}
if isChosen("Reviewers") {
var logins []string
for _, r := range values.Reviewers {

View file

@ -5,8 +5,8 @@ import (
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/stretchr/testify/assert"
)
@ -43,45 +43,32 @@ func TestMetadataSurvey_selectAll(t *testing.T) {
},
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: []string{"Labels", "Projects", "Assignees", "Reviewers", "Milestone"},
},
pm := prompter.NewMockPrompter(t)
pm.RegisterMultiSelect("What would you like to add?",
[]string{}, []string{"Reviewers", "Assignees", "Labels", "Projects", "Milestone"}, func(_ string, _, _ []string) ([]int, error) {
return []int{0, 1, 2, 3, 4}, nil
})
pm.RegisterMultiSelect("Reviewers", []string{}, []string{"hubot", "monalisa"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1}, nil
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "reviewers",
Value: []string{"monalisa"},
},
{
Name: "assignees",
Value: []string{"hubot"},
},
{
Name: "labels",
Value: []string{"good first issue"},
},
{
Name: "projects",
Value: []string{"The road to 1.0"},
},
{
Name: "milestone",
Value: "(none)",
},
pm.RegisterMultiSelect("Assignees", []string{}, []string{"hubot", "monalisa"}, func(_ string, _, _ []string) ([]int, error) {
return []int{0}, nil
})
pm.RegisterMultiSelect("Labels", []string{}, []string{"help wanted", "good first issue"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1}, nil
})
pm.RegisterMultiSelect("Projects", []string{}, []string{"Huge Refactoring", "The road to 1.0"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1}, nil
})
pm.RegisterSelect("Milestone", []string{"(none)", "1.2 patch release"}, func(_, _ string, _ []string) (int, error) {
return 0, nil
})
state := &IssueMetadataState{
Assignees: []string{"hubot"},
Type: PRMetadata,
}
err := MetadataSurvey(ios, repo, fetcher, state)
err := MetadataSurvey(pm, ios, repo, fetcher, state)
assert.NoError(t, err)
assert.Equal(t, "", stdout.String())
@ -112,33 +99,21 @@ func TestMetadataSurvey_keepExisting(t *testing.T) {
},
}
//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
as, restoreAsk := prompt.InitAskStubber()
defer restoreAsk()
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "metadata",
Value: []string{"Labels", "Projects"},
},
pm := prompter.NewMockPrompter(t)
pm.RegisterMultiSelect("What would you like to add?", []string{}, []string{"Assignees", "Labels", "Projects", "Milestone"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1, 2}, nil
})
//nolint:staticcheck // SA1019: as.Stub is deprecated: use StubPrompt
as.Stub([]*prompt.QuestionStub{
{
Name: "labels",
Value: []string{"good first issue"},
},
{
Name: "projects",
Value: []string{"The road to 1.0"},
},
pm.RegisterMultiSelect("Labels", []string{}, []string{"help wanted", "good first issue"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1}, nil
})
pm.RegisterMultiSelect("Projects", []string{}, []string{"Huge Refactoring", "The road to 1.0"}, func(_ string, _, _ []string) ([]int, error) {
return []int{1}, nil
})
state := &IssueMetadataState{
Assignees: []string{"hubot"},
}
err := MetadataSurvey(ios, repo, fetcher, state)
err := MetadataSurvey(pm, ios, repo, fetcher, state)
assert.NoError(t, err)
assert.Equal(t, "", stdout.String())