Skip to content

Commit 87c3892

Browse files
dashboard changes
1. add below fields to dashboard creation - a. tags - list of strings b. created - created datetime c. is favorite - true/false, default false 2. ensure title is unique 3. add API to get all tags - `GET /api/v1/dashboards/list_tags`
1 parent b8a9bd0 commit 87c3892

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

src/handlers/http/modal/server.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ impl Server {
279279
.authorize(Action::ListDashboard),
280280
),
281281
)
282+
.service(
283+
web::resource("/list_tags").route(
284+
web::get()
285+
.to(dashboards::list_tags)
286+
.authorize(Action::ListDashboard),
287+
),
288+
)
282289
.service(
283290
web::scope("/{dashboard_id}")
284291
.service(

src/handlers/http/users/dashboards.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ pub async fn add_tile(
145145
Ok((web::Json(dashboard), StatusCode::OK))
146146
}
147147

148+
pub async fn list_tags() -> Result<impl Responder, DashboardError> {
149+
let tags = DASHBOARDS.list_tags().await;
150+
Ok((web::Json(tags), StatusCode::OK))
151+
}
152+
148153
#[derive(Debug, thiserror::Error)]
149154
pub enum DashboardError {
150155
#[error("Failed to connect to storage: {0}")]

src/users/dashboards.rs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@ pub struct Tile {
5252
pub other_fields: Option<serde_json::Map<String, Value>>,
5353
}
5454
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
55+
#[serde(rename_all = "camelCase")]
5556
pub struct Dashboard {
5657
pub version: Option<String>,
5758
pub title: String,
5859
pub author: Option<String>,
5960
pub dashboard_id: Option<Ulid>,
61+
pub created: Option<DateTime<Utc>>,
6062
pub modified: Option<DateTime<Utc>>,
63+
pub tags: Option<Vec<String>>,
64+
pub is_favorite: Option<bool>, // whether the dashboard is marked as favorite, default is false
6165
dashboard_type: Option<DashboardType>,
6266
pub tiles: Option<Vec<Tile>>,
6367
}
@@ -77,6 +81,11 @@ impl Dashboard {
7781
if self.tiles.is_none() {
7882
self.tiles = Some(Vec::new());
7983
}
84+
85+
// if is_favorite is None, set it to false, else set it to the current value
86+
self.is_favorite = Some(
87+
self.is_favorite.unwrap_or(false), // default to false if not set
88+
);
8089
}
8190

8291
/// create a summary of the dashboard
@@ -96,6 +105,13 @@ impl Dashboard {
96105
);
97106
}
98107

108+
if let Some(created) = &self.created {
109+
map.insert(
110+
"created".to_string(),
111+
serde_json::Value::String(created.to_string()),
112+
);
113+
}
114+
99115
if let Some(modified) = &self.modified {
100116
map.insert(
101117
"modified".to_string(),
@@ -110,6 +126,22 @@ impl Dashboard {
110126
);
111127
}
112128

129+
if let Some(tags) = &self.tags {
130+
map.insert(
131+
"tags".to_string(),
132+
serde_json::Value::Array(
133+
tags.iter()
134+
.map(|tag| serde_json::Value::String(tag.clone()))
135+
.collect(),
136+
),
137+
);
138+
}
139+
140+
map.insert(
141+
"is_favorite".to_string(),
142+
serde_json::Value::Bool(self.is_favorite.unwrap_or(false)),
143+
);
144+
113145
map
114146
}
115147
}
@@ -175,6 +207,16 @@ impl Dashboards {
175207
let dashboard_id = dashboard
176208
.dashboard_id
177209
.ok_or(DashboardError::Metadata("Dashboard ID must be provided"))?;
210+
211+
// ensure the dashboard has unique title
212+
let dashboards = self.0.read().await;
213+
let has_duplicate = dashboards
214+
.iter()
215+
.any(|d| d.title == dashboard.title && d.dashboard_id != dashboard.dashboard_id);
216+
217+
if has_duplicate {
218+
return Err(DashboardError::Metadata("Dashboard title must be unique"));
219+
}
178220
let path = dashboard_path(user_id, &format!("{dashboard_id}.json"));
179221

180222
let store = PARSEABLE.storage.get_object_store();
@@ -194,6 +236,7 @@ impl Dashboards {
194236
user_id: &str,
195237
dashboard: &mut Dashboard,
196238
) -> Result<(), DashboardError> {
239+
dashboard.created = Some(Utc::now());
197240
dashboard.set_metadata(user_id, None);
198241

199242
self.save_dashboard(user_id, dashboard).await?;
@@ -211,10 +254,12 @@ impl Dashboards {
211254
dashboard_id: Ulid,
212255
dashboard: &mut Dashboard,
213256
) -> Result<(), DashboardError> {
214-
self.ensure_dashboard_ownership(dashboard_id, user_id)
257+
let existing_dashboard = self
258+
.ensure_dashboard_ownership(dashboard_id, user_id)
215259
.await?;
216260

217261
dashboard.set_metadata(user_id, Some(dashboard_id));
262+
dashboard.created = existing_dashboard.created;
218263
self.save_dashboard(user_id, dashboard).await?;
219264

220265
let mut dashboards = self.0.write().await;
@@ -288,6 +333,20 @@ impl Dashboards {
288333
self.0.read().await.clone()
289334
}
290335

336+
/// List tags from all dashboards
337+
/// This function returns a list of unique tags from all dashboards
338+
pub async fn list_tags(&self) -> Vec<String> {
339+
let dashboards = self.0.read().await;
340+
let mut tags = dashboards
341+
.iter()
342+
.filter_map(|d| d.tags.as_ref())
343+
.flat_map(|t| t.iter().cloned())
344+
.collect::<Vec<String>>();
345+
tags.sort();
346+
tags.dedup();
347+
tags
348+
}
349+
291350
/// Ensure the user is the owner of the dashboard
292351
/// This function is called when updating or deleting a dashboard
293352
/// check if the user is the owner of the dashboard
@@ -296,10 +355,13 @@ impl Dashboards {
296355
&self,
297356
dashboard_id: Ulid,
298357
user_id: &str,
299-
) -> Result<(), DashboardError> {
358+
) -> Result<Dashboard, DashboardError> {
300359
self.get_dashboard_by_user(dashboard_id, user_id)
301360
.await
302-
.ok_or_else(|| DashboardError::Unauthorized)
303-
.map(|_| ())
361+
.ok_or_else(|| {
362+
DashboardError::Metadata(
363+
"Dashboard does not exist or you do not have permission to access it",
364+
)
365+
})
304366
}
305367
}

0 commit comments

Comments
 (0)