Skip to content

Commit bd47132

Browse files
Improve dashboards test server to match to the real server (#3743)
## Why Used in #3725 to provide local test coverage. ## Tests Existing tests pass with slight modifications.
1 parent f58fa40 commit bd47132

File tree

8 files changed

+139
-22
lines changed

8 files changed

+139
-22
lines changed

acceptance/bundle/generate/dashboard-inplace/output.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ Deployment complete!
1212
=== update the dashboard
1313
>>> [CLI] lakeview update [DASHBOARD_ID] --serialized-dashboard {"a":"b"}
1414
{
15-
"etag":"[UUID]",
15+
"create_time":"[TIMESTAMP]",
16+
"dashboard_id":"[DASHBOARD_ID]",
17+
"display_name":"test dashboard",
18+
"etag":"[NUMID]",
1619
"lifecycle_state":"ACTIVE",
17-
"serialized_dashboard":"{\"a\":\"b\"}"
20+
"parent_path":"/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources",
21+
"path":"/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources/test dashboard.lvdash.json",
22+
"serialized_dashboard":"{\"a\":\"b\"}",
23+
"update_time":"[TIMESTAMP]Z"
1824
}
1925

2026
=== update the dashboard file using bundle generate

acceptance/bundle/generate/dashboard/dashboard.json renamed to acceptance/bundle/generate/dashboard/dashboard.json.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"display_name": "test dashboard",
3-
"parent_path": "/test",
3+
"parent_path": "/Workspace/test-$UNIQUE_NAME",
44
"serialized_dashboard": "{\"pages\":[{\"displayName\":\"New Page\",\"layout\":[],\"name\":\"12345678\"}]}"
55
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
>>> [CLI] workspace mkdirs /Workspace/test-[UNIQUE_NAME]
3+
24
>>> [CLI] bundle generate dashboard --existing-id [DASHBOARD_ID] --dashboard-dir out/dashboard --resource-dir out/resource
35
Writing dashboard to "out/dashboard/test_dashboard.lvdash.json"
46
Writing configuration to "out/resource/test_dashboard.dashboard.yml"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
trace $CLI workspace mkdirs /Workspace/test-$UNIQUE_NAME
2+
13
# create a dashboard to import
4+
envsubst < dashboard.json.tmpl > dashboard.json
25
dashboard_id=$($CLI lakeview create --json @dashboard.json | jq -r '.dashboard_id')
6+
rm dashboard.json
37

48
trace $CLI bundle generate dashboard --existing-id $dashboard_id --dashboard-dir out/dashboard --resource-dir out/resource

acceptance/bundle/generate/dashboard/test.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ New = '/'
55
[[Repls]]
66
Old = "[0-9a-f]{32}"
77
New = "[DASHBOARD_ID]"
8+
9+
[Env]
10+
MSYS_NO_PATHCONV = "1"

libs/testserver/dashboards.go

Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
11
package testserver
22

33
import (
4+
"crypto/rand"
5+
"encoding/hex"
46
"encoding/json"
7+
"fmt"
8+
"path"
9+
"path/filepath"
10+
"strconv"
511
"strings"
12+
"time"
613

714
"github.com/databricks/databricks-sdk-go/service/dashboards"
815
"github.com/databricks/databricks-sdk-go/service/workspace"
916
)
1017

18+
// Generate 32 character hex string for dashboard ID
19+
func generateDashboardId() (string, error) {
20+
randomBytes := make([]byte, 16)
21+
_, err := rand.Read(randomBytes)
22+
if err != nil {
23+
return "", err
24+
}
25+
return hex.EncodeToString(randomBytes), nil
26+
}
27+
1128
func (s *FakeWorkspace) DashboardCreate(req Request) Response {
1229
defer s.LockUnlock()()
1330

@@ -18,17 +35,42 @@ func (s *FakeWorkspace) DashboardCreate(req Request) Response {
1835
}
1936
}
2037

21-
// Lakeview API strips hyphens from a uuid for dashboards
22-
dashboard.DashboardId = strings.ReplaceAll(nextUUID(), "-", "")
38+
if _, ok := s.directories[dashboard.ParentPath]; !ok {
39+
return Response{
40+
StatusCode: 404,
41+
Body: map[string]string{
42+
"message": fmt.Sprintf("Path (%s) doesn't exist.", dashboard.ParentPath),
43+
},
44+
}
45+
}
46+
47+
var err error
48+
dashboard.DashboardId, err = generateDashboardId()
49+
if err != nil {
50+
return Response{
51+
StatusCode: 500,
52+
Body: map[string]string{
53+
"message": "Failed to generate dashboard ID",
54+
},
55+
}
56+
}
2357

2458
// All dashboards are active by default:
2559
dashboard.LifecycleState = dashboards.LifecycleStateActive
2660

61+
// Remove /Workspace prefix from parent_path. This matches the remote behavior.
62+
if strings.HasPrefix(dashboard.ParentPath, "/Workspace/") {
63+
dashboard.ParentPath = strings.TrimPrefix(dashboard.ParentPath, "/Workspace")
64+
}
65+
2766
// Change path field if parent_path is provided
2867
if dashboard.ParentPath != "" {
2968
dashboard.Path = dashboard.ParentPath + "/" + dashboard.DisplayName + ".lvdash.json"
3069
}
3170

71+
dashboard.CreateTime = strings.TrimSuffix(time.Now().UTC().Format(time.RFC3339), "Z")
72+
dashboard.UpdateTime = dashboard.CreateTime
73+
3274
// Parse serializedDashboard into json and put it back as a string
3375
if dashboard.SerializedDashboard != "" {
3476
var dashboardContent map[string]any
@@ -42,46 +84,76 @@ func (s *FakeWorkspace) DashboardCreate(req Request) Response {
4284
}
4385
}
4486
if updatedContent, err := json.Marshal(dashboardContent); err == nil {
45-
dashboard.SerializedDashboard = string(updatedContent)
87+
dashboard.SerializedDashboard = string(updatedContent) + "\n"
4688
}
4789
}
4890
}
91+
dashboard.Etag = "80611980"
4992

5093
s.Dashboards[dashboard.DashboardId] = dashboard
51-
s.files[dashboard.Path] = FileEntry{
94+
workspacePath := path.Join("/Workspace", dashboard.Path)
95+
s.files[workspacePath] = FileEntry{
5296
Info: workspace.ObjectInfo{
5397
ObjectType: "DASHBOARD",
54-
Path: dashboard.Path,
55-
ObjectId: nextID(),
98+
// Include the /Workspace prefix for workspace get-status API.
99+
Path: workspacePath,
100+
ResourceId: dashboard.DashboardId,
56101
},
57102
Data: []byte(dashboard.SerializedDashboard),
58103
}
59104

60105
return Response{
61-
Body: dashboards.Dashboard{
62-
DashboardId: dashboard.DashboardId,
63-
Etag: nextUUID(),
64-
},
106+
Body: dashboard,
65107
}
66108
}
67109

68110
func (s *FakeWorkspace) DashboardUpdate(req Request) Response {
69111
defer s.LockUnlock()()
70112

71-
var dashboard dashboards.Dashboard
72-
if err := json.Unmarshal(req.Body, &dashboard); err != nil {
113+
var updateReq dashboards.Dashboard
114+
if err := json.Unmarshal(req.Body, &updateReq); err != nil {
73115
return Response{
74116
StatusCode: 400,
75117
}
76118
}
77119

78-
// Update the etag for the dashboard.
79-
dashboard.Etag = nextUUID()
120+
dashboardId := req.Vars["dashboard_id"]
121+
dashboard, ok := s.Dashboards[dashboardId]
122+
if !ok {
123+
return Response{
124+
StatusCode: 404,
125+
}
126+
}
80127

81-
// All dashboards are active by default:
128+
// Update etag.
129+
prevEtag, err := strconv.Atoi(dashboard.Etag)
130+
if err != nil {
131+
return Response{
132+
Body: map[string]string{
133+
"message": "Invalid etag: " + dashboard.Etag,
134+
},
135+
StatusCode: 400,
136+
}
137+
}
138+
nextEtag := prevEtag + 1
139+
dashboard.Etag = strconv.Itoa(nextEtag)
140+
141+
// Update the dashboard.
82142
dashboard.LifecycleState = dashboards.LifecycleStateActive
143+
if updateReq.DisplayName != "" {
144+
dashboard.DisplayName = updateReq.DisplayName
145+
dir := filepath.Dir(dashboard.Path)
146+
base := updateReq.DisplayName + ".lvdash.json"
147+
dashboard.Path = filepath.Join(dir, base)
148+
}
149+
if updateReq.SerializedDashboard != "" {
150+
dashboard.SerializedDashboard = updateReq.SerializedDashboard
151+
}
152+
if updateReq.WarehouseId != "" {
153+
dashboard.WarehouseId = updateReq.WarehouseId
154+
}
155+
dashboard.UpdateTime = time.Now().UTC().Format(time.RFC3339)
83156

84-
dashboardId := req.Vars["dashboard_id"]
85157
s.Dashboards[dashboardId] = dashboard
86158

87159
return Response{
@@ -92,16 +164,41 @@ func (s *FakeWorkspace) DashboardUpdate(req Request) Response {
92164
func (s *FakeWorkspace) DashboardPublish(req Request) Response {
93165
defer s.LockUnlock()()
94166

95-
var dashboard dashboards.Dashboard
96-
if err := json.Unmarshal(req.Body, &dashboard); err != nil {
167+
var publishReq dashboards.PublishRequest
168+
if err := json.Unmarshal(req.Body, &publishReq); err != nil {
97169
return Response{
98170
StatusCode: 400,
99171
}
100172
}
101173

174+
dashboardId := req.Vars["dashboard_id"]
175+
dashboard, ok := s.Dashboards[dashboardId]
176+
if !ok {
177+
return Response{
178+
StatusCode: 404,
179+
}
180+
}
181+
182+
publishedDashboard := dashboards.PublishedDashboard{
183+
WarehouseId: dashboard.WarehouseId,
184+
DisplayName: dashboard.DisplayName,
185+
EmbedCredentials: publishReq.EmbedCredentials,
186+
}
187+
188+
if publishReq.WarehouseId != "" {
189+
publishedDashboard.WarehouseId = publishReq.WarehouseId
190+
}
191+
if publishReq.EmbedCredentials {
192+
publishedDashboard.EmbedCredentials = publishReq.EmbedCredentials
193+
}
194+
195+
s.PublishedDashboards[dashboardId] = publishedDashboard
196+
102197
return Response{
103198
Body: dashboards.PublishedDashboard{
104-
WarehouseId: dashboard.WarehouseId,
199+
WarehouseId: publishedDashboard.WarehouseId,
200+
DisplayName: publishedDashboard.DisplayName,
201+
EmbedCredentials: publishedDashboard.EmbedCredentials,
105202
},
106203
}
107204
}

libs/testserver/fake_workspace.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type FakeWorkspace struct {
9999
SchemasGrants map[string][]catalog.PrivilegeAssignment
100100
Volumes map[string]catalog.VolumeInfo
101101
Dashboards map[string]dashboards.Dashboard
102+
PublishedDashboards map[string]dashboards.PublishedDashboard
102103
SqlWarehouses map[string]sql.GetWarehouseResponse
103104
Alerts map[string]sql.AlertV2
104105
Experiments map[string]ml.GetExperimentResponse
@@ -202,6 +203,7 @@ func NewFakeWorkspace(url, token string) *FakeWorkspace {
202203
RegisteredModels: map[string]catalog.RegisteredModelInfo{},
203204
Volumes: map[string]catalog.VolumeInfo{},
204205
Dashboards: map[string]dashboards.Dashboard{},
206+
PublishedDashboards: map[string]dashboards.PublishedDashboard{},
205207
SqlWarehouses: map[string]sql.GetWarehouseResponse{},
206208
Repos: map[string]workspace.RepoInfo{},
207209
Acls: map[string][]workspace.AclItem{},

libs/testserver/handlers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ func AddDefaultHandlers(server *Server) {
238238
server.Handle("DELETE", "/api/2.0/lakeview/dashboards/{dashboard_id}", func(req Request) any {
239239
return MapDelete(req.Workspace, req.Workspace.Dashboards, req.Vars["dashboard_id"])
240240
})
241+
server.Handle("GET", "/api/2.0/lakeview/dashboards/{dashboard_id}/published", func(req Request) any {
242+
return MapGet(req.Workspace, req.Workspace.PublishedDashboards, req.Vars["dashboard_id"])
243+
})
241244

242245
// Pipelines:
243246

0 commit comments

Comments
 (0)