11package testserver
22
33import (
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+
1128func (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
68110func (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 {
92164func (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}
0 commit comments