Improve experience for multiple builtin templates (#1052)

## Changes
This enhances the template selection experience a bit as we add more and
more built-in templates (like
https://github.com/databricks/cli/pull/1051 and
https://github.com/databricks/cli/pull/1059):

### New experience:
<img width="661" alt="image"
src="https://github.com/databricks/cli/assets/58432911/afe3b84d-8a77-47f3-b9c2-f827f7893cd7">

### Current experience:
<img width="265" alt="image"
src="https://github.com/databricks/cli/assets/58432911/36f8d568-819f-4920-83b1-fb76109ea3d1">

---------

Co-authored-by: shreyas-goenka <88374338+shreyas-goenka@users.noreply.github.com>
This commit is contained in:
Lennart Kats (databricks) 2023-12-27 13:03:08 +01:00 committed by GitHub
parent f2408eda62
commit 10a8ce4562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 24 deletions

View File

@ -27,35 +27,58 @@ type nativeTemplate struct {
aliases []string aliases []string
} }
const customTemplate = "custom..."
var nativeTemplates = []nativeTemplate{ var nativeTemplates = []nativeTemplate{
{ {
name: "default-python", name: "default-python",
description: "The default Python template", description: "The default Python template for Notebooks / Delta Live Tables / Workflows",
}, },
{ {
name: "mlops-stacks", name: "mlops-stacks",
gitUrl: "https://github.com/databricks/mlops-stacks", gitUrl: "https://github.com/databricks/mlops-stacks",
description: "The Databricks MLOps Stacks template (https://github.com/databricks/mlops-stacks)", description: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)",
aliases: []string{"mlops-stack"}, aliases: []string{"mlops-stack"},
}, },
{
name: customTemplate,
description: "Bring your own template",
},
} }
func nativeTemplateDescriptions() string { // Return template descriptions for command-line help
func nativeTemplateHelpDescriptions() string {
var lines []string var lines []string
for _, template := range nativeTemplates { for _, template := range nativeTemplates {
lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description)) if template.name != customTemplate {
lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description))
}
} }
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
} }
func nativeTemplateOptions() []string { // Return template options for an interactive prompt
names := make([]string, 0, len(nativeTemplates)) func nativeTemplateOptions() []cmdio.Tuple {
names := make([]cmdio.Tuple, 0, len(nativeTemplates))
for _, template := range nativeTemplates { for _, template := range nativeTemplates {
names = append(names, template.name) tuple := cmdio.Tuple{
Name: template.name,
Id: template.description,
}
names = append(names, tuple)
} }
return names return names
} }
func getNativeTemplateByDescription(description string) string {
for _, template := range nativeTemplates {
if template.description == description {
return template.name
}
}
return ""
}
func getUrlForNativeTemplate(name string) string { func getUrlForNativeTemplate(name string) string {
for _, template := range nativeTemplates { for _, template := range nativeTemplates {
if template.name == name { if template.name == name {
@ -99,7 +122,7 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f
- a local file system path with a template directory - a local file system path with a template directory
- a Git repository URL, e.g. https://github.com/my/repository - a Git repository URL, e.g. https://github.com/my/repository
See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateDescriptions()), See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateHelpDescriptions()),
} }
var configFile string var configFile string
@ -134,10 +157,17 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf
if !cmdio.IsPromptSupported(ctx) { if !cmdio.IsPromptSupported(ctx) {
return errors.New("please specify a template") return errors.New("please specify a template")
} }
templatePath, err = cmdio.AskSelect(ctx, "Template to use", nativeTemplateOptions()) description, err := cmdio.SelectOrdered(ctx, nativeTemplateOptions(), "Template to use")
if err != nil { if err != nil {
return err return err
} }
templatePath = getNativeTemplateByDescription(description)
}
if templatePath == customTemplate {
cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.")
cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.")
return nil
} }
// Expand templatePath to a git URL if it's an alias for a known native template // Expand templatePath to a git URL if it's an alias for a known native template

View File

@ -3,6 +3,7 @@ package bundle
import ( import (
"testing" "testing"
"github.com/databricks/cli/libs/cmdio"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -27,11 +28,18 @@ func TestBundleInitRepoName(t *testing.T) {
} }
func TestNativeTemplateOptions(t *testing.T) { func TestNativeTemplateOptions(t *testing.T) {
assert.Equal(t, []string{"default-python", "mlops-stacks"}, nativeTemplateOptions()) expected := []cmdio.Tuple{
{Name: "default-python", Id: "The default Python template for Notebooks / Delta Live Tables / Workflows"},
{Name: "mlops-stacks", Id: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)"},
{Name: "custom...", Id: "Bring your own template"},
}
assert.Equal(t, expected, nativeTemplateOptions())
} }
func TestNativeTemplateDescriptions(t *testing.T) { func TestNativeTemplateHelpDescriptions(t *testing.T) {
assert.Equal(t, "- default-python: The default Python template\n- mlops-stacks: The Databricks MLOps Stacks template (https://github.com/databricks/mlops-stacks)", nativeTemplateDescriptions()) expected := `- default-python: The default Python template for Notebooks / Delta Live Tables / Workflows
- mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)`
assert.Equal(t, expected, nativeTemplateHelpDescriptions())
} }
func TestGetUrlForNativeTemplate(t *testing.T) { func TestGetUrlForNativeTemplate(t *testing.T) {

View File

@ -155,19 +155,13 @@ func RenderReader(ctx context.Context, r io.Reader) error {
} }
} }
type tuple struct{ Name, Id string } type Tuple struct{ Name, Id string }
func (c *cmdIO) Select(names map[string]string, label string) (id string, err error) { func (c *cmdIO) Select(items []Tuple, label string) (id string, err error) {
if !c.interactive { if !c.interactive {
return "", fmt.Errorf("expected to have %s", label) return "", fmt.Errorf("expected to have %s", label)
} }
var items []tuple
for k, v := range names {
items = append(items, tuple{k, v})
}
slices.SortFunc(items, func(a, b tuple) int {
return strings.Compare(a.Name, b.Name)
})
idx, _, err := (&promptui.Select{ idx, _, err := (&promptui.Select{
Label: label, Label: label,
Items: items, Items: items,
@ -190,13 +184,25 @@ func (c *cmdIO) Select(names map[string]string, label string) (id string, err er
return return
} }
// Show a selection prompt where the user can pick one of the name/id items.
// The items are sorted alphabetically by name.
func Select[V any](ctx context.Context, names map[string]V, label string) (id string, err error) { func Select[V any](ctx context.Context, names map[string]V, label string) (id string, err error) {
c := fromContext(ctx) c := fromContext(ctx)
stringNames := map[string]string{} var items []Tuple
for k, v := range names { for k, v := range names {
stringNames[k] = fmt.Sprint(v) items = append(items, Tuple{k, fmt.Sprint(v)})
} }
return c.Select(stringNames, label) slices.SortFunc(items, func(a, b Tuple) int {
return strings.Compare(a.Name, b.Name)
})
return c.Select(items, label)
}
// Show a selection prompt where the user can pick one of the name/id items.
// The items appear in the order specified in the "items" argument.
func SelectOrdered(ctx context.Context, items []Tuple, label string) (id string, err error) {
c := fromContext(ctx)
return c.Select(items, label)
} }
func (c *cmdIO) Secret(label string) (value string, err error) { func (c *cmdIO) Secret(label string) (value string, err error) {