-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for AI/BI dashboards (#1743)
## Changes This change adds support for modeling [AI/BI dashboards][docs] in DABs. [Example bundle configuration][example] is located in the `bundle-examples` repository. [docs]: https://docs.databricks.com/en/dashboards/index.html#dashboards [example]: https://github.com/databricks/bundle-examples/tree/main/knowledge_base/dashboard_nyc_taxi ## Tests * Added unit tests for self-contained parts * Integration test for e2e dashboard deployment and remote change modification
- Loading branch information
Showing
30 changed files
with
1,251 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package mutator | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/databricks/cli/bundle" | ||
"github.com/databricks/cli/libs/diag" | ||
"github.com/databricks/cli/libs/dyn" | ||
) | ||
|
||
type configureDashboardDefaults struct{} | ||
|
||
func ConfigureDashboardDefaults() bundle.Mutator { | ||
return &configureDashboardDefaults{} | ||
} | ||
|
||
func (m *configureDashboardDefaults) Name() string { | ||
return "ConfigureDashboardDefaults" | ||
} | ||
|
||
func (m *configureDashboardDefaults) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
|
||
pattern := dyn.NewPattern( | ||
dyn.Key("resources"), | ||
dyn.Key("dashboards"), | ||
dyn.AnyKey(), | ||
) | ||
|
||
// Configure defaults for all dashboards. | ||
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { | ||
return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { | ||
var err error | ||
v, err = setIfNotExists(v, dyn.NewPath(dyn.Key("parent_path")), dyn.V(b.Config.Workspace.ResourcePath)) | ||
if err != nil { | ||
return dyn.InvalidValue, err | ||
} | ||
v, err = setIfNotExists(v, dyn.NewPath(dyn.Key("embed_credentials")), dyn.V(false)) | ||
if err != nil { | ||
return dyn.InvalidValue, err | ||
} | ||
return v, nil | ||
}) | ||
}) | ||
|
||
diags = diags.Extend(diag.FromErr(err)) | ||
return diags | ||
} | ||
|
||
func setIfNotExists(v dyn.Value, path dyn.Path, defaultValue dyn.Value) (dyn.Value, error) { | ||
// Get the field at the specified path (if set). | ||
_, err := dyn.GetByPath(v, path) | ||
switch { | ||
case dyn.IsNoSuchKeyError(err): | ||
// OK, we'll set the default value. | ||
break | ||
case dyn.IsCannotTraverseNilError(err): | ||
// Cannot traverse the value, skip it. | ||
return v, nil | ||
case err == nil: | ||
// The field is set, skip it. | ||
return v, nil | ||
default: | ||
// Return the error. | ||
return v, err | ||
} | ||
|
||
// Set the field at the specified path. | ||
return dyn.SetByPath(v, path, defaultValue) | ||
} |
130 changes: 130 additions & 0 deletions
130
bundle/config/mutator/configure_dashboard_defaults_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package mutator_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/databricks/cli/bundle" | ||
"github.com/databricks/cli/bundle/config" | ||
"github.com/databricks/cli/bundle/config/mutator" | ||
"github.com/databricks/cli/bundle/config/resources" | ||
"github.com/databricks/cli/bundle/internal/bundletest" | ||
"github.com/databricks/cli/libs/dyn" | ||
"github.com/databricks/databricks-sdk-go/service/dashboards" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConfigureDashboardDefaultsParentPath(t *testing.T) { | ||
b := &bundle.Bundle{ | ||
Config: config.Root{ | ||
Workspace: config.Workspace{ | ||
ResourcePath: "/foo/bar", | ||
}, | ||
Resources: config.Resources{ | ||
Dashboards: map[string]*resources.Dashboard{ | ||
"d1": { | ||
// Empty string is skipped. | ||
// See below for how it is set. | ||
CreateDashboardRequest: &dashboards.CreateDashboardRequest{ | ||
ParentPath: "", | ||
}, | ||
}, | ||
"d2": { | ||
// Non-empty string is skipped. | ||
CreateDashboardRequest: &dashboards.CreateDashboardRequest{ | ||
ParentPath: "already-set", | ||
}, | ||
}, | ||
"d3": { | ||
// No parent path set. | ||
}, | ||
"d4": nil, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
// We can't set an empty string in the typed configuration. | ||
// Do it on the dyn.Value directly. | ||
bundletest.Mutate(t, b, func(v dyn.Value) (dyn.Value, error) { | ||
return dyn.Set(v, "resources.dashboards.d1.parent_path", dyn.V("")) | ||
}) | ||
|
||
diags := bundle.Apply(context.Background(), b, mutator.ConfigureDashboardDefaults()) | ||
require.NoError(t, diags.Error()) | ||
|
||
var v dyn.Value | ||
var err error | ||
|
||
// Set to empty string; unchanged. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d1.parent_path") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, "", v.MustString()) | ||
} | ||
|
||
// Set to "already-set"; unchanged. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d2.parent_path") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, "already-set", v.MustString()) | ||
} | ||
|
||
// Not set; now set to the workspace resource path. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d3.parent_path") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, "/foo/bar", v.MustString()) | ||
} | ||
|
||
// No valid dashboard; no change. | ||
_, err = dyn.Get(b.Config.Value(), "resources.dashboards.d4.parent_path") | ||
assert.True(t, dyn.IsCannotTraverseNilError(err)) | ||
} | ||
|
||
func TestConfigureDashboardDefaultsEmbedCredentials(t *testing.T) { | ||
b := &bundle.Bundle{ | ||
Config: config.Root{ | ||
Resources: config.Resources{ | ||
Dashboards: map[string]*resources.Dashboard{ | ||
"d1": { | ||
EmbedCredentials: true, | ||
}, | ||
"d2": { | ||
EmbedCredentials: false, | ||
}, | ||
"d3": { | ||
// No parent path set. | ||
}, | ||
"d4": nil, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
diags := bundle.Apply(context.Background(), b, mutator.ConfigureDashboardDefaults()) | ||
require.NoError(t, diags.Error()) | ||
|
||
var v dyn.Value | ||
var err error | ||
|
||
// Set to true; still true. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d1.embed_credentials") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, true, v.MustBool()) | ||
} | ||
|
||
// Set to false; still false. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d2.embed_credentials") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, false, v.MustBool()) | ||
} | ||
|
||
// Not set; now false. | ||
v, err = dyn.Get(b.Config.Value(), "resources.dashboards.d3.embed_credentials") | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, false, v.MustBool()) | ||
} | ||
|
||
// No valid dashboard; no change. | ||
_, err = dyn.Get(b.Config.Value(), "resources.dashboards.d4.embed_credentials") | ||
assert.True(t, dyn.IsCannotTraverseNilError(err)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package mutator | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/databricks/cli/libs/dyn" | ||
) | ||
|
||
func (t *translateContext) applyDashboardTranslations(v dyn.Value) (dyn.Value, error) { | ||
// Convert the `file_path` field to a local absolute path. | ||
// We load the file at this path and use its contents for the dashboard contents. | ||
pattern := dyn.NewPattern( | ||
dyn.Key("resources"), | ||
dyn.Key("dashboards"), | ||
dyn.AnyKey(), | ||
dyn.Key("file_path"), | ||
) | ||
|
||
return dyn.MapByPattern(v, pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { | ||
key := p[2].Key() | ||
dir, err := v.Location().Directory() | ||
if err != nil { | ||
return dyn.InvalidValue, fmt.Errorf("unable to determine directory for dashboard %s: %w", key, err) | ||
} | ||
|
||
return t.rewriteRelativeTo(p, v, t.retainLocalAbsoluteFilePath, dir, "") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.