Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: UX Multi Select Component (v2) #4862

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions cli/azd/.vscode/cspell-azd-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ gosec
goterm
gotest
gotestsum
govet
grpcserver
hotspot
ignorefile
Expand Down
18 changes: 12 additions & 6 deletions cli/azd/extensions/microsoft.azd.demo/internal/cmd/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,18 @@ func newPromptCommand() *cobra.Command {
}
}

providerOptions := []string{}
providerOptions := []*azdext.SelectChoice{}
for _, provider := range providerList {
providerOptions = append(providerOptions, *provider.Namespace)
providerOptions = append(providerOptions, &azdext.SelectChoice{
Label: *provider.Namespace,
Value: *provider.ID,
})
}

providerSelectResponse, err := azdClient.Prompt().Select(ctx, &azdext.SelectRequest{
Options: &azdext.SelectOptions{
Message: "Select a resource provider",
Allowed: providerOptions,
Choices: providerOptions,
},
})
if err != nil {
Expand All @@ -135,9 +138,12 @@ func newPromptCommand() *cobra.Command {
return err
}

resourceTypeOptions := []string{}
resourceTypeOptions := []*azdext.SelectChoice{}
for _, resourceType := range resourceTypesResponse.Value {
resourceTypeOptions = append(resourceTypeOptions, *resourceType.ResourceType)
resourceTypeOptions = append(resourceTypeOptions, &azdext.SelectChoice{
Label: *resourceType.ResourceType,
Value: *resourceType.ResourceType,
})
}

resourceTypes := []*armresources.ProviderResourceType{}
Expand All @@ -146,7 +152,7 @@ func newPromptCommand() *cobra.Command {
Select(ctx, &azdext.SelectRequest{
Options: &azdext.SelectOptions{
Message: fmt.Sprintf("Select a %s resource type", *selectedProvider.Namespace),
Allowed: resourceTypeOptions,
Choices: resourceTypeOptions,
},
})
if err != nil {
Expand Down
13 changes: 9 additions & 4 deletions cli/azd/grpc/proto/prompt.proto
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ message PromptResourceGroupResourceResponse {
message ConfirmOptions {
optional bool default_value = 1;
string message = 2;
string helpMessage = 3;
string help_message = 3;
string hint = 4;
string placeholder = 5;
}
Expand All @@ -112,15 +112,20 @@ message PromptOptions {
string validation_message = 5;
string required_message = 6;
bool required = 7;
string defaultValue = 8;
string default_value = 8;
bool clear_on_completion = 9;
bool ignore_hint_keys = 10;
}

message SelectChoice {
string value = 1;
string label = 2;
}

message SelectOptions {
optional int32 SelectedIndex = 1;
optional int32 selected_index = 1;
string message = 2;
repeated string allowed = 3;
repeated SelectChoice choices = 3;
string help_message = 4;
string hint = 5;
int32 display_count = 6;
Expand Down
16 changes: 12 additions & 4 deletions cli/azd/internal/grpcserver/prompt_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,26 @@ func (s *promptService) Confirm(ctx context.Context, req *azdext.ConfirmRequest)
}

confirm := ux.NewConfirm(options)
value, err := confirm.Ask()
value, err := confirm.Ask(ctx)

return &azdext.ConfirmResponse{
Value: value,
}, err
}

func (s *promptService) Select(ctx context.Context, req *azdext.SelectRequest) (*azdext.SelectResponse, error) {
choices := make([]*ux.SelectChoice, len(req.Options.Choices))
for i, choice := range req.Options.Choices {
choices[i] = &ux.SelectChoice{
Value: choice.Value,
Label: choice.Label,
}
}

options := &ux.SelectOptions{
SelectedIndex: convertToInt(req.Options.SelectedIndex),
Message: req.Options.Message,
Allowed: req.Options.Allowed,
Choices: choices,
HelpMessage: req.Options.HelpMessage,
Hint: req.Options.Hint,
DisplayCount: int(req.Options.DisplayCount),
Expand All @@ -57,7 +65,7 @@ func (s *promptService) Select(ctx context.Context, req *azdext.SelectRequest) (
}

selectPrompt := ux.NewSelect(options)
value, err := selectPrompt.Ask()
value, err := selectPrompt.Ask(ctx)

return &azdext.SelectResponse{
Value: convertToInt32(value),
Expand All @@ -79,7 +87,7 @@ func (s *promptService) Prompt(ctx context.Context, req *azdext.PromptRequest) (
}

prompt := ux.NewPrompt(options)
value, err := prompt.Ask()
value, err := prompt.Ask(ctx)

return &azdext.PromptResponse{
Value: value,
Expand Down
484 changes: 272 additions & 212 deletions cli/azd/pkg/azdext/prompt.pb.go

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions cli/azd/pkg/input/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ type AskerConsole struct {
consoleWidth *atomic.Int32
// holds the last 2 bytes written by message or messageUX. This is used to detect when there is already an empty
// line (\n\n)
last2Byte [2]byte
last2Byte [2]byte
signalChan chan os.Signal
}

type ConsoleOptions struct {
Expand Down Expand Up @@ -407,6 +408,9 @@ func (c *AskerConsole) ShowSpinner(ctx context.Context, title string, format Spi
// calling Start may result in an additional line of output being written in non-tty scenarios
_ = c.spinner.Start()
}

watchTerminalInterrupt(c)

c.spinnerLineMu.Unlock()
}

Expand Down Expand Up @@ -474,6 +478,8 @@ func (c *AskerConsole) StopSpinner(ctx context.Context, lastMessage string, form
fmt.Fprintln(c.writer, lastMessage)
}

stopTerminalInterrupt(c)

c.spinnerLineMu.Unlock()
}

Expand Down Expand Up @@ -939,10 +945,9 @@ func watchTerminalResize(c *AskerConsole) {
}

func watchTerminalInterrupt(c *AskerConsole) {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
signal.Notify(c.signalChan, os.Interrupt)
go func() {
<-signalChan
<-c.signalChan

// unhide the cursor if applicable
_ = c.spinner.Stop()
Expand All @@ -951,6 +956,10 @@ func watchTerminalInterrupt(c *AskerConsole) {
}()
}

func stopTerminalInterrupt(c *AskerConsole) {
signal.Stop(c.signalChan)
}

// Writers that back the underlying console.
type Writers struct {
// The writer to write output to.
Expand Down Expand Up @@ -987,6 +996,7 @@ func NewConsole(
isTerminal: isTerminal,
currentIndent: atomic.NewString(""),
noPrompt: noPrompt,
signalChan: make(chan os.Signal, 1),
}

if writers.Spinner == nil {
Expand Down Expand Up @@ -1015,7 +1025,6 @@ func NewConsole(
if isTerminal {
c.consoleWidth = atomic.NewInt32(consoleWidth())
watchTerminalResize(c)
watchTerminalInterrupt(c)
}

return c
Expand Down
4 changes: 4 additions & 0 deletions cli/azd/pkg/output/colors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ func WithGrayFormat(text string, a ...interface{}) string {
return color.HiBlackString(text, a...)
}

func WithHintFormat(text string, a ...interface{}) string {
return color.MagentaString(text, a...)
}

func WithBold(text string, a ...interface{}) string {
format := color.New(color.Bold)
return format.Sprintf(text, a...)
Expand Down
37 changes: 22 additions & 15 deletions cli/azd/pkg/prompt/prompt_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/config"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/ux"
"github.com/fatih/color"
)

var (
Expand Down Expand Up @@ -235,7 +235,7 @@ func (ps *promptService) PromptSubscription(
return subscriptions, nil
},
DisplayResource: func(subscription *account.Subscription) (string, error) {
return fmt.Sprintf("%s %s", subscription.Name, color.HiBlackString("(%s)", subscription.Id)), nil
return fmt.Sprintf("%s %s", subscription.Name, output.WithGrayFormat("(%s)", subscription.Id)), nil
},
Selected: func(subscription *account.Subscription) bool {
return strings.EqualFold(subscription.Id, defaultSubscriptionId)
Expand Down Expand Up @@ -311,7 +311,7 @@ func (ps *promptService) PromptLocation(
return locations, nil
},
DisplayResource: func(location *account.Location) (string, error) {
return fmt.Sprintf("%s %s", location.RegionalDisplayName, color.HiBlackString("(%s)", location.Name)), nil
return fmt.Sprintf("%s %s", location.RegionalDisplayName, output.WithGrayFormat("(%s)", location.Name)), nil
},
Selected: func(resource *account.Location) bool {
return resource.Name == defaultLocation
Expand Down Expand Up @@ -383,7 +383,7 @@ func (ps *promptService) PromptResourceGroup(
return fmt.Sprintf(
"%s %s",
resourceGroup.Name,
color.HiBlackString("(Location: %s)", resourceGroup.Location),
output.WithGrayFormat("(Location: %s)", resourceGroup.Location),
), nil
},
Selected: func(resourceGroup *azapi.ResourceGroup) bool {
Expand All @@ -394,7 +394,7 @@ func (ps *promptService) PromptResourceGroup(
Message: "Enter the name for the resource group",
})

resourceGroupName, err := namePrompt.Ask()
resourceGroupName, err := namePrompt.Ask(ctx)
if err != nil {
return nil, err
}
Expand All @@ -405,7 +405,7 @@ func (ps *promptService) PromptResourceGroup(

var resourceGroup *azapi.ResourceGroup

taskName := fmt.Sprintf("Creating resource group %s", color.CyanString(resourceGroupName))
taskName := fmt.Sprintf("Creating resource group %s", output.WithHighLightFormat(resourceGroupName))

err = ux.NewTaskList(nil).
AddTask(ux.TaskOptions{
Expand Down Expand Up @@ -553,7 +553,7 @@ func (ps *promptService) PromptSubscriptionResource(
return fmt.Sprintf(
"%s %s",
parsedResource.Name,
color.HiBlackString("(%s)", parsedResource.ResourceGroupName),
output.WithGrayFormat("(%s)", parsedResource.ResourceGroupName),
), nil
},
Selected: options.Selected,
Expand Down Expand Up @@ -770,17 +770,19 @@ func PromptCustomResource[T any](ctx context.Context, options CustomResourceOpti

hasCustomDisplay := options.DisplayResource != nil

var choices []string
var choices []*ux.SelectChoice

if allowNewResource {
choices = make([]string, len(resources)+1)
choices[0] = mergedSelectorOptions.NewResourceMessage
choices = make([]*ux.SelectChoice, len(resources)+1)
choices[0] = &ux.SelectChoice{
Label: mergedSelectorOptions.NewResourceMessage,
}

if defaultIndex != nil {
*defaultIndex++
}
} else {
choices = make([]string, len(resources))
choices = make([]*ux.SelectChoice, len(resources))
}

for i, resource := range resources {
Expand All @@ -797,10 +799,15 @@ func PromptCustomResource[T any](ctx context.Context, options CustomResourceOpti
displayValue = fmt.Sprintf("%v", resource)
}

choice := &ux.SelectChoice{
Value: displayValue,
Label: displayValue,
}

if allowNewResource {
choices[i+1] = displayValue
choices[i+1] = choice
} else {
choices[i] = displayValue
choices[i] = choice
}
}

Expand All @@ -812,11 +819,11 @@ func PromptCustomResource[T any](ctx context.Context, options CustomResourceOpti
Hint: mergedSelectorOptions.Hint,
EnableFiltering: mergedSelectorOptions.EnableFiltering,
Writer: mergedSelectorOptions.Writer,
Allowed: choices,
Choices: choices,
SelectedIndex: defaultIndex,
})

userSelectedIndex, err := resourceSelector.Ask()
userSelectedIndex, err := resourceSelector.Ask(ctx)
if err != nil {
return nil, err
}
Expand Down
Loading