Merge branch 'trunk' into git-tag-release-notes
This commit is contained in:
commit
02034af28d
12 changed files with 151 additions and 451 deletions
|
|
@ -1159,6 +1159,6 @@ func (a *API) withRetry(f func() (*http.Response, error)) (*http.Response, error
|
|||
if resp.StatusCode < 500 {
|
||||
return resp, nil
|
||||
}
|
||||
return nil, errors.New("retry")
|
||||
return nil, fmt.Errorf("received response with status code %d", resp.StatusCode)
|
||||
}, backoff.WithMaxRetries(bo, 3))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,14 @@ type logger interface {
|
|||
Printf(f string, v ...interface{})
|
||||
}
|
||||
|
||||
type TimeoutError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *TimeoutError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
// ConnectToLiveshare waits for a Codespace to become running,
|
||||
// and connects to it using a Live Share session.
|
||||
func ConnectToLiveshare(ctx context.Context, progress progressIndicator, sessionLogger logger, apiClient apiClient, codespace *api.Codespace) (*liveshare.Session, error) {
|
||||
|
|
@ -63,15 +71,15 @@ func ConnectToLiveshare(ctx context.Context, progress progressIndicator, session
|
|||
return nil
|
||||
}
|
||||
|
||||
return errors.New("codespace not ready yet")
|
||||
return &TimeoutError{message: "codespace not ready yet"}
|
||||
}, backoff.WithContext(expBackoff, ctx))
|
||||
if err != nil {
|
||||
var permErr *backoff.PermanentError
|
||||
if errors.As(err, &permErr) {
|
||||
return nil, err
|
||||
var timeoutErr *TimeoutError
|
||||
if errors.As(err, &timeoutErr) {
|
||||
return nil, errors.New("timed out while waiting for the codespace to start")
|
||||
}
|
||||
|
||||
return nil, errors.New("timed out while waiting for the codespace to start")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package prompt
|
||||
|
||||
import "github.com/AlecAivazis/survey/v2"
|
||||
|
||||
// Deprecated: use PrompterMock
|
||||
func StubConfirm(result bool) func() {
|
||||
orig := Confirm
|
||||
Confirm = func(_ string, r *bool) error {
|
||||
*r = result
|
||||
return nil
|
||||
}
|
||||
return func() {
|
||||
Confirm = orig
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: use Prompter
|
||||
var Confirm = func(prompt string, result *bool) error {
|
||||
p := &survey.Confirm{
|
||||
Message: prompt,
|
||||
Default: true,
|
||||
}
|
||||
return SurveyAskOne(p, result)
|
||||
}
|
||||
|
||||
// Deprecated: use Prompter
|
||||
var SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
|
||||
return survey.AskOne(p, response, opts...)
|
||||
}
|
||||
|
||||
// Deprecated: use Prompter
|
||||
var SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error {
|
||||
return survey.Ask(qs, response, opts...)
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/cli/cli/v2/pkg/surveyext"
|
||||
)
|
||||
|
||||
type AskStubber struct {
|
||||
stubs []*QuestionStub
|
||||
}
|
||||
|
||||
type testing interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// Deprecated: use PrompterMock
|
||||
func NewAskStubber(t testing) *AskStubber {
|
||||
as, teardown := InitAskStubber()
|
||||
t.Cleanup(func() {
|
||||
teardown()
|
||||
for _, s := range as.stubs {
|
||||
if !s.matched {
|
||||
t.Errorf("unmatched prompt stub: %+v", s)
|
||||
}
|
||||
}
|
||||
})
|
||||
return as
|
||||
}
|
||||
|
||||
// Deprecated: use NewAskStubber
|
||||
func InitAskStubber() (*AskStubber, func()) {
|
||||
origSurveyAsk := SurveyAsk
|
||||
origSurveyAskOne := SurveyAskOne
|
||||
as := AskStubber{}
|
||||
|
||||
answerFromStub := func(p survey.Prompt, fieldName string, response interface{}) error {
|
||||
var message string
|
||||
var defaultValue interface{}
|
||||
var options []string
|
||||
switch pt := p.(type) {
|
||||
case *survey.Confirm:
|
||||
message = pt.Message
|
||||
defaultValue = pt.Default
|
||||
case *survey.Input:
|
||||
message = pt.Message
|
||||
defaultValue = pt.Default
|
||||
case *survey.Select:
|
||||
message = pt.Message
|
||||
options = pt.Options
|
||||
case *survey.MultiSelect:
|
||||
message = pt.Message
|
||||
options = pt.Options
|
||||
case *survey.Password:
|
||||
message = pt.Message
|
||||
case *surveyext.GhEditor:
|
||||
message = pt.Message
|
||||
defaultValue = pt.Default
|
||||
default:
|
||||
return fmt.Errorf("prompt type %T is not supported by the stubber", pt)
|
||||
}
|
||||
|
||||
var stub *QuestionStub
|
||||
for _, s := range as.stubs {
|
||||
if !s.matched && (s.message == "" && strings.EqualFold(s.Name, fieldName) || s.message == message) {
|
||||
stub = s
|
||||
stub.matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if stub == nil {
|
||||
return fmt.Errorf("no prompt stub for %q", message)
|
||||
}
|
||||
|
||||
if len(stub.options) > 0 {
|
||||
if err := compareOptions(stub.options, options); err != nil {
|
||||
return fmt.Errorf("stubbed options mismatch for %q: %v", message, err)
|
||||
}
|
||||
}
|
||||
|
||||
userValue := stub.Value
|
||||
|
||||
if stringValue, ok := stub.Value.(string); ok && len(options) > 0 {
|
||||
foundIndex := -1
|
||||
for i, o := range options {
|
||||
if o == stringValue {
|
||||
foundIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundIndex < 0 {
|
||||
return fmt.Errorf("answer %q not found in options for %q: %v", stringValue, message, options)
|
||||
}
|
||||
userValue = core.OptionAnswer{
|
||||
Value: stringValue,
|
||||
Index: foundIndex,
|
||||
}
|
||||
}
|
||||
|
||||
if stub.Default {
|
||||
if defaultIndex, ok := defaultValue.(int); ok && len(options) > 0 {
|
||||
userValue = core.OptionAnswer{
|
||||
Value: options[defaultIndex],
|
||||
Index: defaultIndex,
|
||||
}
|
||||
} else if defaultValue == nil && len(options) > 0 {
|
||||
userValue = core.OptionAnswer{
|
||||
Value: options[0],
|
||||
Index: 0,
|
||||
}
|
||||
} else {
|
||||
userValue = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
if err := core.WriteAnswer(response, fieldName, userValue); err != nil {
|
||||
topic := fmt.Sprintf("field %q", fieldName)
|
||||
if fieldName == "" {
|
||||
topic = fmt.Sprintf("%q", message)
|
||||
}
|
||||
return fmt.Errorf("AskStubber failed writing the answer for %s: %w", topic, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
|
||||
return answerFromStub(p, "", response)
|
||||
}
|
||||
|
||||
SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error {
|
||||
for _, q := range qs {
|
||||
if err := answerFromStub(q.Prompt, q.Name, response); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
teardown := func() {
|
||||
SurveyAsk = origSurveyAsk
|
||||
SurveyAskOne = origSurveyAskOne
|
||||
}
|
||||
return &as, teardown
|
||||
}
|
||||
|
||||
type QuestionStub struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
Default bool
|
||||
|
||||
matched bool
|
||||
message string
|
||||
options []string
|
||||
}
|
||||
|
||||
// AssertOptions asserts the options presented to the user in Selects and MultiSelects.
|
||||
func (s *QuestionStub) AssertOptions(opts []string) *QuestionStub {
|
||||
s.options = opts
|
||||
return s
|
||||
}
|
||||
|
||||
// AnswerWith defines an answer for the given stub.
|
||||
func (s *QuestionStub) AnswerWith(v interface{}) *QuestionStub {
|
||||
s.Value = v
|
||||
return s
|
||||
}
|
||||
|
||||
// AnswerDefault marks the current stub to be answered with the default value for the prompt question.
|
||||
func (s *QuestionStub) AnswerDefault() *QuestionStub {
|
||||
s.Default = true
|
||||
return s
|
||||
}
|
||||
|
||||
// Deprecated: use StubPrompt
|
||||
func (as *AskStubber) StubOne(value interface{}) {
|
||||
as.Stub([]*QuestionStub{{Value: value}})
|
||||
}
|
||||
|
||||
// Deprecated: use StubPrompt
|
||||
func (as *AskStubber) Stub(stubbedQuestions []*QuestionStub) {
|
||||
as.stubs = append(as.stubs, stubbedQuestions...)
|
||||
}
|
||||
|
||||
// StubPrompt records a stub for an interactive prompt matched by its message.
|
||||
func (as *AskStubber) StubPrompt(msg string) *QuestionStub {
|
||||
stub := &QuestionStub{message: msg}
|
||||
as.stubs = append(as.stubs, stub)
|
||||
return stub
|
||||
}
|
||||
|
||||
func compareOptions(expected, got []string) error {
|
||||
if len(expected) != len(got) {
|
||||
return fmt.Errorf("expected %v, got %v (length mismatch)", expected, got)
|
||||
}
|
||||
for i, v := range expected {
|
||||
if v != got[i] {
|
||||
return fmt.Errorf("expected %v, got %v", expected, got)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue