@@ -33,15 +33,21 @@ pub static DASHBOARDS: Lazy<Dashboards> = Lazy::new(Dashboards::default);
33
33
pub const CURRENT_DASHBOARD_VERSION : & str = "v1" ;
34
34
35
35
#[ derive( Debug , Serialize , Deserialize , Clone , PartialEq , Eq , Default ) ]
36
+ /// type of dashboard
37
+ /// Dashboard is the default type
38
+ /// Report is a type of dashboard that is used for reporting
36
39
pub enum DashboardType {
40
+ /// Dashboard is the default type
37
41
#[ default]
38
42
Dashboard ,
43
+ /// Report is a type of dashboard that is used for reporting
39
44
Report ,
40
45
}
41
46
42
47
#[ derive( Debug , Serialize , Deserialize , Default , Clone ) ]
43
48
pub struct Tile {
44
49
pub tile_id : Ulid ,
50
+ /// all other fields are variable and can be added as needed
45
51
#[ serde( flatten) ]
46
52
pub other_fields : Option < serde_json:: Map < String , Value > > ,
47
53
}
@@ -57,14 +63,22 @@ pub struct Dashboard {
57
63
}
58
64
59
65
impl Dashboard {
66
+ /// set metadata for the dashboard
67
+ /// add author, dashboard_id, version, modified, and dashboard_type
68
+ /// if dashboard_id is None, generate a new one
60
69
pub fn set_metadata ( & mut self , user_id : & str , dashboard_id : Option < Ulid > ) {
61
70
self . author = Some ( user_id. to_string ( ) ) ;
62
71
self . dashboard_id = dashboard_id. or_else ( || Some ( Ulid :: new ( ) ) ) ;
63
72
self . version = Some ( CURRENT_DASHBOARD_VERSION . to_string ( ) ) ;
64
73
self . modified = Some ( Utc :: now ( ) ) ;
65
74
self . dashboard_type = Some ( DashboardType :: Dashboard ) ;
75
+ if self . tiles . is_none ( ) {
76
+ self . tiles = Some ( Vec :: new ( ) ) ;
77
+ }
66
78
}
67
79
80
+ /// create a summary of the dashboard
81
+ /// used for listing dashboards
68
82
pub fn to_summary ( & self ) -> serde_json:: Map < String , serde_json:: Value > {
69
83
let mut map = serde_json:: Map :: new ( ) ;
70
84
@@ -98,15 +112,21 @@ impl Dashboard {
98
112
}
99
113
}
100
114
115
+ /// Validate the dashboard ID
116
+ /// Check if the dashboard ID is a valid ULID
117
+ /// If the dashboard ID is not valid, return an error
101
118
pub fn validate_dashboard_id ( dashboard_id : String ) -> Result < Ulid , DashboardError > {
102
119
Ulid :: from_string ( & dashboard_id)
103
- . map_err ( |_| DashboardError :: Metadata ( "Dashboard ID must be provided " ) )
120
+ . map_err ( |_| DashboardError :: Metadata ( "Invalid dashboard ID format - must be a valid ULID " ) )
104
121
}
105
122
106
123
#[ derive( Default , Debug ) ]
107
124
pub struct Dashboards ( RwLock < Vec < Dashboard > > ) ;
108
125
109
126
impl Dashboards {
127
+ /// Load all dashboards from the object store
128
+ /// and store them in memory
129
+ /// This function is called on server start
110
130
pub async fn load ( & self ) -> anyhow:: Result < ( ) > {
111
131
let mut this = vec ! [ ] ;
112
132
let store = PARSEABLE . storage . get_object_store ( ) ;
@@ -118,10 +138,21 @@ impl Dashboards {
118
138
continue ;
119
139
}
120
140
121
- let dashboard_value = serde_json:: from_slice :: < serde_json:: Value > ( & dashboard) ?;
122
- if let Ok ( dashboard) = serde_json:: from_value :: < Dashboard > ( dashboard_value) {
141
+ let dashboard_value = match serde_json:: from_slice :: < serde_json:: Value > ( & dashboard)
142
+ {
143
+ Ok ( value) => value,
144
+ Err ( err) => {
145
+ tracing:: warn!( "Failed to parse dashboard JSON: {}" , err) ;
146
+ continue ;
147
+ }
148
+ } ;
149
+
150
+ if let Ok ( dashboard) = serde_json:: from_value :: < Dashboard > ( dashboard_value. clone ( ) )
151
+ {
123
152
this. retain ( |d : & Dashboard | d. dashboard_id != dashboard. dashboard_id ) ;
124
153
this. push ( dashboard) ;
154
+ } else {
155
+ tracing:: warn!( "Failed to deserialize dashboard: {:?}" , dashboard_value) ;
125
156
}
126
157
}
127
158
}
@@ -132,6 +163,8 @@ impl Dashboards {
132
163
Ok ( ( ) )
133
164
}
134
165
166
+ /// Save the dashboard to the object store
167
+ /// This function is called when creating or updating a dashboard
135
168
async fn save_dashboard (
136
169
& self ,
137
170
user_id : & str ,
@@ -151,6 +184,9 @@ impl Dashboards {
151
184
Ok ( ( ) )
152
185
}
153
186
187
+ /// Create a new dashboard
188
+ /// This function is called when creating a new dashboard
189
+ /// add dashboard in memory and save it to the object store
154
190
pub async fn create (
155
191
& self ,
156
192
user_id : & str ,
@@ -164,19 +200,17 @@ impl Dashboards {
164
200
Ok ( ( ) )
165
201
}
166
202
203
+ /// Update an existing dashboard
204
+ /// This function is called when updating a dashboard
205
+ /// update dashboard in memory and save it to the object store
167
206
pub async fn update (
168
207
& self ,
169
208
user_id : & str ,
170
209
dashboard_id : Ulid ,
171
210
dashboard : & mut Dashboard ,
172
211
) -> Result < ( ) , DashboardError > {
173
- if self
174
- . get_dashboard_by_user ( dashboard_id, user_id)
175
- . await
176
- . is_none ( )
177
- {
178
- return Err ( DashboardError :: Unauthorized ) ;
179
- }
212
+ self . ensure_dashboard_ownership ( dashboard_id, user_id)
213
+ . await ?;
180
214
181
215
dashboard. set_metadata ( user_id, Some ( dashboard_id) ) ;
182
216
self . save_dashboard ( user_id, dashboard) . await ?;
@@ -188,18 +222,16 @@ impl Dashboards {
188
222
Ok ( ( ) )
189
223
}
190
224
225
+ /// Delete a dashboard
226
+ /// This function is called when deleting a dashboard
227
+ /// delete dashboard in memory and from the object store
191
228
pub async fn delete_dashboard (
192
229
& self ,
193
230
user_id : & str ,
194
231
dashboard_id : Ulid ,
195
232
) -> Result < ( ) , DashboardError > {
196
- if self
197
- . get_dashboard_by_user ( dashboard_id, user_id)
198
- . await
199
- . is_none ( )
200
- {
201
- return Err ( DashboardError :: Unauthorized ) ;
202
- }
233
+ self . ensure_dashboard_ownership ( dashboard_id, user_id)
234
+ . await ?;
203
235
204
236
let path = dashboard_path ( user_id, & format ! ( "{}.json" , dashboard_id) ) ;
205
237
let store = PARSEABLE . storage . get_object_store ( ) ;
@@ -208,12 +240,14 @@ impl Dashboards {
208
240
self . 0 . write ( ) . await . retain ( |d| {
209
241
d. dashboard_id
210
242
. as_ref ( )
211
- . is_some_and ( |id| * id = = dashboard_id)
243
+ . map_or ( true , |id| * id ! = dashboard_id)
212
244
} ) ;
213
245
214
246
Ok ( ( ) )
215
247
}
216
248
249
+ /// Get a dashboard by ID
250
+ /// fetch dashboard from memory
217
251
pub async fn get_dashboard ( & self , dashboard_id : Ulid ) -> Option < Dashboard > {
218
252
self . 0
219
253
. read ( )
@@ -227,6 +261,8 @@ impl Dashboards {
227
261
. cloned ( )
228
262
}
229
263
264
+ /// Get a dashboard by ID and user ID
265
+ /// fetch dashboard from memory
230
266
pub async fn get_dashboard_by_user (
231
267
& self ,
232
268
dashboard_id : Ulid ,
@@ -245,7 +281,23 @@ impl Dashboards {
245
281
. cloned ( )
246
282
}
247
283
284
+ /// List all dashboards
285
+ /// fetch all dashboards from memory
248
286
pub async fn list_dashboards ( & self ) -> Vec < Dashboard > {
249
287
self . 0 . read ( ) . await . clone ( )
250
288
}
289
+
290
+ /// Ensure the user is the owner of the dashboard
291
+ /// This function is called when updating or deleting a dashboard
292
+ /// check if the user is the owner of the dashboard
293
+ /// if the user is not the owner, return an error
294
+ async fn ensure_dashboard_ownership (
295
+ & self ,
296
+ dashboard_id : Ulid ,
297
+ user_id : & str ,
298
+ ) -> Result < Dashboard , DashboardError > {
299
+ self . get_dashboard_by_user ( dashboard_id, user_id)
300
+ . await
301
+ . ok_or ( DashboardError :: Unauthorized )
302
+ }
251
303
}
0 commit comments