mirror of https://github.com/databricks/cli.git
wip
This commit is contained in:
parent
cc3bcf992d
commit
b7a952d22e
|
@ -0,0 +1,19 @@
|
||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/databricks/cli/libs/dyn"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/dashboards"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConvertDashboardToValue(dashboard *dashboards.Dashboard, filePath string) (dyn.Value, error) {
|
||||||
|
// The majority of fields of the dashboard struct are read-only.
|
||||||
|
// We copy the relevant fields manually.
|
||||||
|
dv := map[string]dyn.Value{
|
||||||
|
"display_name": dyn.NewValue(dashboard.DisplayName, []dyn.Location{{Line: 1}}),
|
||||||
|
"parent_path": dyn.NewValue("${workspace.file_path}", []dyn.Location{{Line: 2}}),
|
||||||
|
"warehouse_id": dyn.NewValue(dashboard.WarehouseId, []dyn.Location{{Line: 3}}),
|
||||||
|
"definition_path": dyn.NewValue(filePath, []dyn.Location{{Line: 4}}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return dyn.V(dv), nil
|
||||||
|
}
|
|
@ -160,6 +160,11 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos
|
||||||
// the Databricks UI and via the SQL API.
|
// the Databricks UI and via the SQL API.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dashboards: Prefix
|
||||||
|
for i := range r.Dashboards {
|
||||||
|
r.Dashboards[i].DisplayName = prefix + r.Dashboards[i].DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ func newGenerateCommand() *cobra.Command {
|
||||||
|
|
||||||
cmd.AddCommand(generate.NewGenerateJobCommand())
|
cmd.AddCommand(generate.NewGenerateJobCommand())
|
||||||
cmd.AddCommand(generate.NewGeneratePipelineCommand())
|
cmd.AddCommand(generate.NewGeneratePipelineCommand())
|
||||||
|
cmd.AddCommand(generate.NewGenerateDashboardCommand())
|
||||||
cmd.PersistentFlags().StringVar(&key, "key", "", `resource key to use for the generated configuration`)
|
cmd.PersistentFlags().StringVar(&key, "key", "", `resource key to use for the generated configuration`)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/databricks/cli/bundle"
|
||||||
|
"github.com/databricks/cli/bundle/config/generate"
|
||||||
|
"github.com/databricks/cli/bundle/deploy/terraform"
|
||||||
|
"github.com/databricks/cli/bundle/phases"
|
||||||
|
"github.com/databricks/cli/cmd/root"
|
||||||
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
"github.com/databricks/cli/libs/dyn"
|
||||||
|
"github.com/databricks/cli/libs/dyn/yamlsaver"
|
||||||
|
"github.com/databricks/cli/libs/textutil"
|
||||||
|
"github.com/databricks/databricks-sdk-go"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/dashboards"
|
||||||
|
"github.com/databricks/databricks-sdk-go/service/workspace"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dashboard struct {
|
||||||
|
resourceDir string
|
||||||
|
dashboardDir string
|
||||||
|
force bool
|
||||||
|
|
||||||
|
// Relative path from the resource directory to the dashboard directory.
|
||||||
|
relativeDir string
|
||||||
|
|
||||||
|
existingDashboardPath string
|
||||||
|
existingDashboardId string
|
||||||
|
watch string
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) resolveDashboardID(ctx context.Context, w *databricks.WorkspaceClient) diag.Diagnostics {
|
||||||
|
if d.existingDashboardPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := w.Workspace.GetStatusByPath(ctx, d.existingDashboardPath)
|
||||||
|
if err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.ObjectType != workspace.ObjectTypeDashboard {
|
||||||
|
found := strings.ToLower(obj.ObjectType.String())
|
||||||
|
return diag.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: diag.Error,
|
||||||
|
Summary: fmt.Sprintf("expected a dashboard, found a %s", found),
|
||||||
|
Locations: []dyn.Location{{
|
||||||
|
File: d.existingDashboardPath,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.ResourceId == "" {
|
||||||
|
return diag.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: diag.Error,
|
||||||
|
Summary: "expected resource ID to be set",
|
||||||
|
Locations: []dyn.Location{{
|
||||||
|
File: d.existingDashboardPath,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.existingDashboardId = obj.ResourceId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) saveConfiguration(ctx context.Context, dashboard *dashboards.Dashboard) error {
|
||||||
|
// TODO: add flag
|
||||||
|
key := d.key
|
||||||
|
if key == "" {
|
||||||
|
key = textutil.NormalizeString(dashboard.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardFilePath := path.Join(d.relativeDir, fmt.Sprintf("%s.lvdash.json", key))
|
||||||
|
v, err := generate.ConvertDashboardToValue(dashboard, dashboardFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]dyn.Value{
|
||||||
|
"resources": dyn.V(map[string]dyn.Value{
|
||||||
|
"dashboards": dyn.V(map[string]dyn.Value{
|
||||||
|
key: v,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the output directory exists.
|
||||||
|
if err := os.MkdirAll(d.resourceDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(d.resourceDir, fmt.Sprintf("%s.yml", key))
|
||||||
|
saver := yamlsaver.NewSaverWithStyle(map[string]yaml.Style{
|
||||||
|
"display_name": yaml.DoubleQuotedStyle,
|
||||||
|
})
|
||||||
|
err = saver.SaveAsYAML(result, filename, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) remarshal(data []byte) ([]byte, error) {
|
||||||
|
var tmp any
|
||||||
|
var err error
|
||||||
|
err = json.Unmarshal(data, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out, err := json.MarshalIndent(tmp, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) saveSerializedDashboard(ctx context.Context, dashboard *dashboards.Dashboard, dst string) error {
|
||||||
|
// Unmarshal and remarshal the serialized dashboard to ensure it is formatted correctly.
|
||||||
|
// The result will have alphabetically sorted keys and be indented.
|
||||||
|
data, err := d.remarshal([]byte(dashboard.SerializedDashboard))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the output directory exists.
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(dst, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) watchForChanges(ctx context.Context, b *bundle.Bundle) error {
|
||||||
|
diags := bundle.Apply(ctx, b, bundle.Seq(
|
||||||
|
phases.Initialize(),
|
||||||
|
terraform.Interpolate(),
|
||||||
|
terraform.Write(),
|
||||||
|
terraform.StatePull(),
|
||||||
|
terraform.Load(terraform.ErrorOnEmptyState),
|
||||||
|
))
|
||||||
|
if err := diags.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dash, ok := b.Config.Resources.Dashboards[d.watch]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("dashboard %s not found", d.watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println(dash.DefinitionPath)
|
||||||
|
|
||||||
|
w := b.WorkspaceClient()
|
||||||
|
etag := ""
|
||||||
|
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := filepath.Rel(cwd, dash.DefinitionPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
dashboard, err := w.Lakeview.GetByDashboardId(ctx, dash.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println(dashboard.Path)
|
||||||
|
// fmt.Println(dashboard.Etag)
|
||||||
|
// fmt.Println(dashboard.UpdateTime)
|
||||||
|
|
||||||
|
// obj, err := w.Workspace.GetStatusByPath(ctx, "/Users/pieter.noordhuis@databricks.com/.bundle/dashboard-eng-work-generate/dev/files/[dev pieter_noordhuis] NYC Taxi Trip Analysis.lvdash.json")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fmt.Println(obj.ModifiedAt)
|
||||||
|
|
||||||
|
if etag != dashboard.Etag {
|
||||||
|
fmt.Printf("[%s]: Updating dashboard at %s\n", dashboard.UpdateTime, relPath)
|
||||||
|
d.saveSerializedDashboard(ctx, dashboard, dash.DefinitionPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
etag = dashboard.Etag
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dashboard) RunE(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx := cmd.Context()
|
||||||
|
b, diags := root.MustConfigureBundle(cmd)
|
||||||
|
if err := diags.Error(); err != nil {
|
||||||
|
return diags.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we know how the dashboard path is relative to the resource path.
|
||||||
|
rel, err := filepath.Rel(d.resourceDir, d.dashboardDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.relativeDir = filepath.ToSlash(rel)
|
||||||
|
|
||||||
|
w := b.WorkspaceClient()
|
||||||
|
|
||||||
|
if d.watch != "" {
|
||||||
|
return d.watchForChanges(ctx, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the dashboard ID if the path is given
|
||||||
|
diags = d.resolveDashboardID(ctx, w)
|
||||||
|
if diags.HasError() {
|
||||||
|
return diags.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboard, err := w.Lakeview.GetByDashboardId(ctx, d.existingDashboardId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.saveConfiguration(ctx, dashboard)
|
||||||
|
|
||||||
|
// TODO: add flag
|
||||||
|
key := d.key
|
||||||
|
if key == "" {
|
||||||
|
key = textutil.NormalizeString(dashboard.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(d.dashboardDir, fmt.Sprintf("%s.lvdash.json", key))
|
||||||
|
d.saveSerializedDashboard(ctx, dashboard, filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerateDashboardCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "dashboard",
|
||||||
|
Short: "Generate configuration for a dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &dashboard{}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&d.resourceDir, "resource-dir", "d", "./resources", `directory to write the configuration to`)
|
||||||
|
cmd.Flags().StringVarP(&d.dashboardDir, "dashboard-dir", "s", "./dashboards", `directory to write the dashboard representation to`)
|
||||||
|
cmd.Flags().BoolVarP(&d.force, "force", "f", false, `force overwrite existing files in the output directory`)
|
||||||
|
|
||||||
|
// Specify dashboard by workspace path
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&d.existingDashboardPath, "existing-dashboard-path", "", `workspace path of the dashboard to generate configuration for`)
|
||||||
|
cmd.Flags().StringVar(&d.existingDashboardId, "existing-dashboard-id", "", `ID of the dashboard to generate configuration for`)
|
||||||
|
cmd.Flags().StringVar(&d.watch, "watch-resource", "", `resource key of dashboard to watch for changes`)
|
||||||
|
|
||||||
|
cmd.MarkFlagsOneRequired(
|
||||||
|
"existing-dashboard-path",
|
||||||
|
"existing-dashboard-id",
|
||||||
|
"watch-resource",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd.RunE = d.RunE
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package generate
|
|
@ -0,0 +1,710 @@
|
||||||
|
{
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"displayName": "route revenue",
|
||||||
|
"name": "fdefd57c",
|
||||||
|
"query": "SELECT\n T.pickup_zip,\n T.dropoff_zip,\n T.route as `Route`,\n T.frequency as `Number Trips`,\n T.total_fare as `Total Revenue`\nFROM\n (\n SELECT\n pickup_zip,\n dropoff_zip,\n concat(pickup_zip, '-', dropoff_zip) AS route,\n count(*) as frequency,\n SUM(fare_amount) as total_fare\n FROM\n `samples`.`nyctaxi`.`trips`\n GROUP BY\n 1,2,3\n ) T\nORDER BY\n 1 ASC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "trips",
|
||||||
|
"name": "ecfcdc7c",
|
||||||
|
"query": "SELECT\n T.tpep_pickup_datetime,\n T.tpep_dropoff_datetime,\n T.fare_amount,\n T.pickup_zip,\n T.dropoff_zip,\n T.trip_distance,\n T.weekday,\n CASE\n WHEN T.weekday = 1 THEN 'Sunday'\n WHEN T.weekday = 2 THEN 'Monday'\n WHEN T.weekday = 3 THEN 'Tuesday'\n WHEN T.weekday = 4 THEN 'Wednesday'\n WHEN T.weekday = 5 THEN 'Thursday'\n WHEN T.weekday = 6 THEN 'Friday'\n WHEN T.weekday = 7 THEN 'Saturday'\n ELSE 'N/A'\n END AS day_of_week, \n T.fare_amount, \n T.trip_distance\nFROM\n (\n SELECT\n dayofweek(tpep_pickup_datetime) as weekday,\n *\n FROM\n `samples`.`nyctaxi`.`trips`\n WHERE\n trip_distance \u003e 0\n AND trip_distance \u003c 10\n AND fare_amount \u003e 0\n AND fare_amount \u003c 50\n ) T\nORDER BY\n T.weekday "
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"displayName": "New Page",
|
||||||
|
"layout": [
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 1,
|
||||||
|
"width": 2,
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "c4d87efe",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "dashboards/01ee564285a315dd80d473e76171660a/datasets/01ee564285a51daf810a8ffc5051bfee_tpep_dropoff_datetime",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`tpep_dropoff_datetime`",
|
||||||
|
"name": "tpep_dropoff_datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "COUNT_IF(`associative_filter_predicate_group`)",
|
||||||
|
"name": "tpep_dropoff_datetime_associativity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"displayName": "tpep_dropoff_datetime",
|
||||||
|
"fieldName": "tpep_dropoff_datetime",
|
||||||
|
"queryName": "dashboards/01ee564285a315dd80d473e76171660a/datasets/01ee564285a51daf810a8ffc5051bfee_tpep_dropoff_datetime"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Time Range"
|
||||||
|
},
|
||||||
|
"version": 2,
|
||||||
|
"widgetType": "filter-date-range-picker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 4,
|
||||||
|
"width": 3,
|
||||||
|
"x": 0,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "61e19e9c",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "main_query",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "COUNT(`*`)",
|
||||||
|
"name": "count(*)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "DATE_TRUNC(\"HOUR\", `tpep_pickup_datetime`)",
|
||||||
|
"name": "hourly(tpep_pickup_datetime)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"label": {
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Pickup Hour"
|
||||||
|
},
|
||||||
|
"displayName": "Pickup Hour",
|
||||||
|
"fieldName": "hourly(tpep_pickup_datetime)",
|
||||||
|
"scale": {
|
||||||
|
"type": "temporal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Number of Rides"
|
||||||
|
},
|
||||||
|
"displayName": "Number of Rides",
|
||||||
|
"fieldName": "count(*)",
|
||||||
|
"scale": {
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Pickup Hour Distribution"
|
||||||
|
},
|
||||||
|
"mark": {
|
||||||
|
"colors": [
|
||||||
|
"#077A9D",
|
||||||
|
"#FFAB00",
|
||||||
|
"#00A972",
|
||||||
|
"#FF3621",
|
||||||
|
"#8BCAE7",
|
||||||
|
"#AB4057",
|
||||||
|
"#99DDB4",
|
||||||
|
"#FCA4A1",
|
||||||
|
"#919191",
|
||||||
|
"#BF7080"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 3,
|
||||||
|
"widgetType": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 8,
|
||||||
|
"width": 4,
|
||||||
|
"x": 2,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "3b1dff20",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "main_query",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`day_of_week`",
|
||||||
|
"name": "day_of_week"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "`fare_amount`",
|
||||||
|
"name": "fare_amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "`trip_distance`",
|
||||||
|
"name": "trip_distance"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"color": {
|
||||||
|
"displayName": "Day of Week",
|
||||||
|
"fieldName": "day_of_week",
|
||||||
|
"scale": {
|
||||||
|
"type": "categorical"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Trip Distance (miles)"
|
||||||
|
},
|
||||||
|
"displayName": "trip_distance",
|
||||||
|
"fieldName": "trip_distance",
|
||||||
|
"scale": {
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Fare Amount (USD)"
|
||||||
|
},
|
||||||
|
"displayName": "fare_amount",
|
||||||
|
"fieldName": "fare_amount",
|
||||||
|
"scale": {
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Daily Fare Trends by Day of Week"
|
||||||
|
},
|
||||||
|
"version": 3,
|
||||||
|
"widgetType": "scatter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 1,
|
||||||
|
"width": 6,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "bd82f575",
|
||||||
|
"textbox_spec": "# NYC Taxi Trip Analysis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 4,
|
||||||
|
"width": 3,
|
||||||
|
"x": 3,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "e7b33e79",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "main_query",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "COUNT(`*`)",
|
||||||
|
"name": "count(*)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "DATE_TRUNC(\"HOUR\", `tpep_dropoff_datetime`)",
|
||||||
|
"name": "hourly(tpep_dropoff_datetime)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"x": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Dropoff Hour"
|
||||||
|
},
|
||||||
|
"displayName": "Dropoff Hour",
|
||||||
|
"fieldName": "hourly(tpep_dropoff_datetime)",
|
||||||
|
"scale": {
|
||||||
|
"type": "temporal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"axis": {
|
||||||
|
"title": "Number of Rides"
|
||||||
|
},
|
||||||
|
"displayName": "Number of Rides",
|
||||||
|
"fieldName": "count(*)",
|
||||||
|
"scale": {
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Dropoff Hour Distribution"
|
||||||
|
},
|
||||||
|
"mark": {
|
||||||
|
"colors": [
|
||||||
|
"#FFAB00",
|
||||||
|
"#FFAB00",
|
||||||
|
"#00A972",
|
||||||
|
"#FF3621",
|
||||||
|
"#8BCAE7",
|
||||||
|
"#AB4057",
|
||||||
|
"#99DDB4",
|
||||||
|
"#FCA4A1",
|
||||||
|
"#919191",
|
||||||
|
"#BF7080"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 3,
|
||||||
|
"widgetType": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 2,
|
||||||
|
"width": 2,
|
||||||
|
"x": 0,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "299e756c",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "main_query",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "COUNT(`*`)",
|
||||||
|
"name": "count(*)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"value": {
|
||||||
|
"displayName": "Count of Records",
|
||||||
|
"fieldName": "count(*)",
|
||||||
|
"style": {
|
||||||
|
"bold": true,
|
||||||
|
"color": "#E92828"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Total Trips"
|
||||||
|
},
|
||||||
|
"version": 2,
|
||||||
|
"widgetType": "counter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 1,
|
||||||
|
"width": 2,
|
||||||
|
"x": 2,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "61a54236",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082d1205adc131b86b10198e_pickup_zip",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "fdefd57c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`pickup_zip`",
|
||||||
|
"name": "pickup_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "COUNT_IF(`associative_filter_predicate_group`)",
|
||||||
|
"name": "pickup_zip_associativity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082e1ff49c3209776820e82e_pickup_zip",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`pickup_zip`",
|
||||||
|
"name": "pickup_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "COUNT_IF(`associative_filter_predicate_group`)",
|
||||||
|
"name": "pickup_zip_associativity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"displayName": "pickup_zip",
|
||||||
|
"fieldName": "pickup_zip",
|
||||||
|
"queryName": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082e1ff49c3209776820e82e_pickup_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "pickup_zip",
|
||||||
|
"fieldName": "pickup_zip",
|
||||||
|
"queryName": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082d1205adc131b86b10198e_pickup_zip"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Pickup Zip"
|
||||||
|
},
|
||||||
|
"version": 2,
|
||||||
|
"widgetType": "filter-multi-select"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 6,
|
||||||
|
"width": 2,
|
||||||
|
"x": 0,
|
||||||
|
"y": 4
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "985e7eb4",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "main_query",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "fdefd57c",
|
||||||
|
"disaggregated": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`Number Trips`",
|
||||||
|
"name": "Number Trips"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "`Route`",
|
||||||
|
"name": "Route"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "`Total Revenue`",
|
||||||
|
"name": "Total Revenue"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"allowHTMLByDefault": false,
|
||||||
|
"condensed": true,
|
||||||
|
"encodings": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"alignContent": "left",
|
||||||
|
"allowHTML": false,
|
||||||
|
"allowSearch": false,
|
||||||
|
"booleanValues": [
|
||||||
|
"false",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"displayAs": "string",
|
||||||
|
"displayName": "Route",
|
||||||
|
"fieldName": "Route",
|
||||||
|
"highlightLinks": false,
|
||||||
|
"imageHeight": "",
|
||||||
|
"imageTitleTemplate": "{{ @ }}",
|
||||||
|
"imageUrlTemplate": "{{ @ }}",
|
||||||
|
"imageWidth": "",
|
||||||
|
"linkOpenInNewTab": true,
|
||||||
|
"linkTextTemplate": "{{ @ }}",
|
||||||
|
"linkTitleTemplate": "{{ @ }}",
|
||||||
|
"linkUrlTemplate": "{{ @ }}",
|
||||||
|
"order": 100000,
|
||||||
|
"preserveWhitespace": false,
|
||||||
|
"title": "Route",
|
||||||
|
"type": "string",
|
||||||
|
"useMonospaceFont": false,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alignContent": "right",
|
||||||
|
"allowHTML": false,
|
||||||
|
"allowSearch": false,
|
||||||
|
"booleanValues": [
|
||||||
|
"false",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"displayAs": "number",
|
||||||
|
"displayName": "Number Trips",
|
||||||
|
"fieldName": "Number Trips",
|
||||||
|
"highlightLinks": false,
|
||||||
|
"imageHeight": "",
|
||||||
|
"imageTitleTemplate": "{{ @ }}",
|
||||||
|
"imageUrlTemplate": "{{ @ }}",
|
||||||
|
"imageWidth": "",
|
||||||
|
"linkOpenInNewTab": true,
|
||||||
|
"linkTextTemplate": "{{ @ }}",
|
||||||
|
"linkTitleTemplate": "{{ @ }}",
|
||||||
|
"linkUrlTemplate": "{{ @ }}",
|
||||||
|
"numberFormat": "0",
|
||||||
|
"order": 100001,
|
||||||
|
"preserveWhitespace": false,
|
||||||
|
"title": "Number Trips",
|
||||||
|
"type": "integer",
|
||||||
|
"useMonospaceFont": false,
|
||||||
|
"visible": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alignContent": "right",
|
||||||
|
"allowHTML": false,
|
||||||
|
"allowSearch": false,
|
||||||
|
"booleanValues": [
|
||||||
|
"false",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"cellFormat": {
|
||||||
|
"default": {
|
||||||
|
"foregroundColor": "#85CADE"
|
||||||
|
},
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"column": "Total Revenue",
|
||||||
|
"fn": "\u003c",
|
||||||
|
"literal": "51"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"foregroundColor": "#9C2638"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"column": "Total Revenue",
|
||||||
|
"fn": "\u003c",
|
||||||
|
"literal": "101"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"foregroundColor": "#FFD465"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"column": "Total Revenue",
|
||||||
|
"fn": "\u003c",
|
||||||
|
"literal": "6001"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"foregroundColor": "#1FA873"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"displayAs": "number",
|
||||||
|
"displayName": "Total Revenue",
|
||||||
|
"fieldName": "Total Revenue",
|
||||||
|
"highlightLinks": false,
|
||||||
|
"imageHeight": "",
|
||||||
|
"imageTitleTemplate": "{{ @ }}",
|
||||||
|
"imageUrlTemplate": "{{ @ }}",
|
||||||
|
"imageWidth": "",
|
||||||
|
"linkOpenInNewTab": true,
|
||||||
|
"linkTextTemplate": "{{ @ }}",
|
||||||
|
"linkTitleTemplate": "{{ @ }}",
|
||||||
|
"linkUrlTemplate": "{{ @ }}",
|
||||||
|
"numberFormat": "$0.00",
|
||||||
|
"order": 100002,
|
||||||
|
"preserveWhitespace": false,
|
||||||
|
"title": "Total Revenue",
|
||||||
|
"type": "float",
|
||||||
|
"useMonospaceFont": false,
|
||||||
|
"visible": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Route Revenue Attribution"
|
||||||
|
},
|
||||||
|
"invisibleColumns": [
|
||||||
|
{
|
||||||
|
"alignContent": "right",
|
||||||
|
"allowHTML": false,
|
||||||
|
"allowSearch": false,
|
||||||
|
"booleanValues": [
|
||||||
|
"false",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"displayAs": "number",
|
||||||
|
"highlightLinks": false,
|
||||||
|
"imageHeight": "",
|
||||||
|
"imageTitleTemplate": "{{ @ }}",
|
||||||
|
"imageUrlTemplate": "{{ @ }}",
|
||||||
|
"imageWidth": "",
|
||||||
|
"linkOpenInNewTab": true,
|
||||||
|
"linkTextTemplate": "{{ @ }}",
|
||||||
|
"linkTitleTemplate": "{{ @ }}",
|
||||||
|
"linkUrlTemplate": "{{ @ }}",
|
||||||
|
"name": "pickup_zip",
|
||||||
|
"numberFormat": "0",
|
||||||
|
"order": 100000,
|
||||||
|
"preserveWhitespace": false,
|
||||||
|
"title": "pickup_zip",
|
||||||
|
"type": "integer",
|
||||||
|
"useMonospaceFont": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alignContent": "right",
|
||||||
|
"allowHTML": false,
|
||||||
|
"allowSearch": false,
|
||||||
|
"booleanValues": [
|
||||||
|
"false",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"displayAs": "number",
|
||||||
|
"highlightLinks": false,
|
||||||
|
"imageHeight": "",
|
||||||
|
"imageTitleTemplate": "{{ @ }}",
|
||||||
|
"imageUrlTemplate": "{{ @ }}",
|
||||||
|
"imageWidth": "",
|
||||||
|
"linkOpenInNewTab": true,
|
||||||
|
"linkTextTemplate": "{{ @ }}",
|
||||||
|
"linkTitleTemplate": "{{ @ }}",
|
||||||
|
"linkUrlTemplate": "{{ @ }}",
|
||||||
|
"name": "dropoff_zip",
|
||||||
|
"numberFormat": "0",
|
||||||
|
"order": 100001,
|
||||||
|
"preserveWhitespace": false,
|
||||||
|
"title": "dropoff_zip",
|
||||||
|
"type": "integer",
|
||||||
|
"useMonospaceFont": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"itemsPerPage": 25,
|
||||||
|
"paginationSize": "default",
|
||||||
|
"version": 1,
|
||||||
|
"widgetType": "table",
|
||||||
|
"withRowNumber": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"height": 1,
|
||||||
|
"width": 2,
|
||||||
|
"x": 4,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"name": "b346c038",
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"name": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082d1205adc131b86b10198e_dropoff_zip",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "fdefd57c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`dropoff_zip`",
|
||||||
|
"name": "dropoff_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "COUNT_IF(`associative_filter_predicate_group`)",
|
||||||
|
"name": "dropoff_zip_associativity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082e1ff49c3209776820e82e_dropoff_zip",
|
||||||
|
"query": {
|
||||||
|
"datasetName": "ecfcdc7c",
|
||||||
|
"disaggregated": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"expression": "`dropoff_zip`",
|
||||||
|
"name": "dropoff_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "COUNT_IF(`associative_filter_predicate_group`)",
|
||||||
|
"name": "dropoff_zip_associativity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spec": {
|
||||||
|
"encodings": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"displayName": "dropoff_zip",
|
||||||
|
"fieldName": "dropoff_zip",
|
||||||
|
"queryName": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082e1ff49c3209776820e82e_dropoff_zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"displayName": "dropoff_zip",
|
||||||
|
"fieldName": "dropoff_zip",
|
||||||
|
"queryName": "dashboards/01eed0e4082a1c7e903cac7e74114376/datasets/01eed0e4082d1205adc131b86b10198e_dropoff_zip"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frame": {
|
||||||
|
"showTitle": true,
|
||||||
|
"title": "Dropoff Zip"
|
||||||
|
},
|
||||||
|
"version": 2,
|
||||||
|
"widgetType": "filter-multi-select"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "b51b1363"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
bundle:
|
||||||
|
name: dashboard-eng-work-generate
|
||||||
|
|
||||||
|
workspace:
|
||||||
|
host: https://e2-dogfood.staging.cloud.databricks.com
|
||||||
|
|
||||||
|
include:
|
||||||
|
- resources/*.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
- group_name: users
|
||||||
|
level: CAN_VIEW
|
||||||
|
|
||||||
|
targets:
|
||||||
|
dev:
|
||||||
|
mode: development
|
|
@ -0,0 +1,23 @@
|
||||||
|
resources:
|
||||||
|
dashboards:
|
||||||
|
nyc_taxi_trip_analysis:
|
||||||
|
display_name: "NYC Taxi Trip Analysis"
|
||||||
|
parent_path: ${workspace.file_path}
|
||||||
|
warehouse_id: 4fe75792cd0d304c
|
||||||
|
definition_path: ../dashboards/nyc_taxi_trip_analysis.lvdash.json
|
||||||
|
|
||||||
|
# To be implemented when ready in the product:
|
||||||
|
#
|
||||||
|
# catalog: ${var.default_catalog}
|
||||||
|
# schema: ${var.default_schema}
|
||||||
|
# schedules:
|
||||||
|
# - name: Daily
|
||||||
|
# # ...
|
||||||
|
permissions:
|
||||||
|
# Allow all users to view the dashboard
|
||||||
|
- group_name: users
|
||||||
|
level: CAN_READ
|
||||||
|
|
||||||
|
# Allow all account users to view the dashboard
|
||||||
|
- group_name: account users
|
||||||
|
level: CAN_READ
|
Loading…
Reference in New Issue