mirror of https://github.com/databricks/cli.git
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:
parent
f2408eda62
commit
10a8ce4562
|
@ -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 {
|
||||||
|
if template.name != customTemplate {
|
||||||
lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description))
|
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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue