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

Add support for ordering of input prompts #662

Merged
merged 8 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 61 additions & 9 deletions libs/template/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,47 @@ import (

"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/jsonschema"
"golang.org/x/exp/slices"
)

type Metadata struct {
// A ordered subset of names of input parameters defined in the template schema.
// Properties in this array will be prompted for first. Any properties not in this
// array will be prompted for in lexicographical order otherwise.
PromptOrder []string `json:"prompt_order"`
}

type config struct {
ctx context.Context
values map[string]any
schema *jsonschema.Schema
ctx context.Context
values map[string]any
schema *jsonschema.Schema
metadata *Metadata
}

func newConfig(ctx context.Context, schemaPath string) (*config, error) {
func newConfig(ctx context.Context, schemaPath string, metadataPath string) (*config, error) {
// Read config schema
schema, err := jsonschema.Load(schemaPath)
if err != nil {
return nil, err
}
if err := validateSchema(schema); err != nil {

// Read metadata
metadata := &Metadata{}
metadataBytes, err := os.ReadFile(metadataPath)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadataBytes, metadata)
if err != nil {
return nil, err
}

// Return config
return &config{
ctx: ctx,
schema: schema,
values: make(map[string]any, 0),
ctx: ctx,
schema: schema,
values: make(map[string]any, 0),
metadata: metadata,
}, nil
}

Expand Down Expand Up @@ -115,13 +133,47 @@ func (c *config) assignDefaultValues() error {
return nil
}

func (c *config) promptOrder() ([]string, error) {
// First add properties in the order that is explicitly defined in the metadata
promptOrder := make([]string, len(c.metadata.PromptOrder))
for i, name := range c.metadata.PromptOrder {
if _, ok := c.schema.Properties[name]; !ok {
return nil, fmt.Errorf("property not defined in schema but is defined in prompt_order: %s", name)
}
if slices.Contains(promptOrder, name) {
return nil, fmt.Errorf("property defined more than once in prompt_order: %s", name)
}
promptOrder[i] = name
}

// accumulate and sort leftover the properties
leftover := []string{}
for name := range c.schema.Properties {
if slices.Contains(promptOrder, name) {
continue
}
leftover = append(leftover, name)
}
slices.Sort(leftover)

// add leftover properties
promptOrder = append(promptOrder, leftover...)
return promptOrder, nil
}

// Prompts user for values for properties that do not have a value set yet
func (c *config) promptForValues() error {
for name, property := range c.schema.Properties {
promptOrder, err := c.promptOrder()
if err != nil {
return err
}

for _, name := range promptOrder {
// Config already has a value assigned
if _, ok := c.values[name]; ok {
continue
}
property := c.schema.Properties[name]

// Compute default value to display by converting it to a string
var defaultVal string
Expand Down
57 changes: 57 additions & 0 deletions libs/template/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,63 @@ func TestTemplateConfigValidateTypeForInvalidType(t *testing.T) {
assert.EqualError(t, err, `incorrect type for int_val. expected type integer, but value is "this-should-be-an-int"`)
}

func TestTemplatePromptOrder(t *testing.T) {
c := &config{
schema: &jsonschema.Schema{
Properties: map[string]*jsonschema.Schema{
"a": {Type: jsonschema.IntegerType},
"b": {Type: jsonschema.BooleanType},
"c": {Type: jsonschema.NumberType},
"d": {Type: jsonschema.IntegerType},
"e": {Type: jsonschema.IntegerType},
"f": {Type: jsonschema.IntegerType},
},
},
metadata: &Metadata{
PromptOrder: []string{"f", "b", "c"},
},
}

order, err := c.promptOrder()
require.NoError(t, err)
assert.Equal(t, []string{"f", "b", "c", "a", "d", "e"}, order)
}

func TestTemplatePromptOrderForUndefinedProperty(t *testing.T) {
c := &config{
schema: &jsonschema.Schema{
Properties: map[string]*jsonschema.Schema{
"a": {Type: jsonschema.IntegerType},
"b": {Type: jsonschema.BooleanType},
},
},
metadata: &Metadata{
PromptOrder: []string{"a", "c"},
},
}

_, err := c.promptOrder()
assert.EqualError(t, err, "property not defined in schema but is defined in prompt_order: c")
}

func TestTemplatePromptOrderForPropertyDefinedMultipleTimes(t *testing.T) {
c := &config{
schema: &jsonschema.Schema{
Properties: map[string]*jsonschema.Schema{
"a": {Type: jsonschema.IntegerType},
"b": {Type: jsonschema.BooleanType},
"c": {Type: jsonschema.BooleanType},
},
},
metadata: &Metadata{
PromptOrder: []string{"a", "b", "a"},
},
}

_, err := c.promptOrder()
assert.EqualError(t, err, "property defined more than once in prompt_order: a")
}

func TestTemplateValidateSchema(t *testing.T) {
var err error
toSchema := func(s string) *jsonschema.Schema {
Expand Down
4 changes: 3 additions & 1 deletion libs/template/materialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
const libraryDirName = "library"
const templateDirName = "template"
const schemaFileName = "databricks_template_schema.json"
const metadataFileName = "databricks_template_metadata.json"

// This function materializes the input templates as a project, using user defined
// configurations.
Expand All @@ -21,8 +22,9 @@ func Materialize(ctx context.Context, configFilePath, templateRoot, projectDir s
templatePath := filepath.Join(templateRoot, templateDirName)
libraryPath := filepath.Join(templateRoot, libraryDirName)
schemaPath := filepath.Join(templateRoot, schemaFileName)
metadataPath := filepath.Join(templateRoot, metadataFileName)

config, err := newConfig(ctx, schemaPath)
config, err := newConfig(ctx, schemaPath, metadataPath)
if err != nil {
return err
}
Expand Down