Skip to content

Commit bc00691

Browse files
update dashboard implementation
as per the new design in prism
1 parent 970a5a5 commit bc00691

File tree

4 files changed

+314
-305
lines changed

4 files changed

+314
-305
lines changed

src/handlers/http/modal/server.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,33 +250,40 @@ impl Server {
250250
web::resource("")
251251
.route(
252252
web::post()
253-
.to(dashboards::post)
253+
.to(dashboards::create_dashboard)
254254
.authorize(Action::CreateDashboard),
255255
)
256256
.route(
257257
web::get()
258-
.to(dashboards::list)
258+
.to(dashboards::list_dashboards)
259259
.authorize(Action::ListDashboard),
260260
),
261261
)
262262
.service(
263263
web::resource("/{dashboard_id}")
264264
.route(
265265
web::get()
266-
.to(dashboards::get)
266+
.to(dashboards::get_dashboard)
267267
.authorize(Action::GetDashboard),
268268
)
269269
.route(
270270
web::delete()
271-
.to(dashboards::delete)
271+
.to(dashboards::delete_dashboard)
272272
.authorize(Action::DeleteDashboard),
273273
)
274274
.route(
275275
web::put()
276-
.to(dashboards::update)
276+
.to(dashboards::update_dashboard)
277277
.authorize(Action::CreateDashboard),
278278
),
279279
)
280+
.service(
281+
web::resource("/add_tile").route(
282+
web::put()
283+
.to(dashboards::add_tile)
284+
.authorize(Action::CreateDashboard),
285+
),
286+
)
280287
}
281288

282289
// get the filters web scope

src/handlers/http/users/dashboards.rs

Lines changed: 81 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -18,140 +18,131 @@
1818

1919
use crate::{
2020
handlers::http::rbac::RBACError,
21-
parseable::PARSEABLE,
22-
storage::{object_storage::dashboard_path, ObjectStorageError},
23-
users::dashboards::{Dashboard, CURRENT_DASHBOARD_VERSION, DASHBOARDS},
24-
utils::{actix::extract_session_key_from_req, get_hash, get_user_from_request},
21+
storage::ObjectStorageError,
22+
users::dashboards::{validate_dashboard_id, Dashboard, Tile, DASHBOARDS},
23+
utils::{get_hash, get_user_from_request},
2524
};
2625
use actix_web::{
2726
http::header::ContentType,
2827
web::{self, Json, Path},
2928
HttpRequest, HttpResponse, Responder,
3029
};
31-
use bytes::Bytes;
32-
use rand::distributions::DistString;
33-
34-
use chrono::Utc;
3530
use http::StatusCode;
3631
use serde_json::Error as SerdeError;
3732

38-
pub async fn list(req: HttpRequest) -> Result<impl Responder, DashboardError> {
39-
let key =
40-
extract_session_key_from_req(&req).map_err(|e| DashboardError::Custom(e.to_string()))?;
41-
let dashboards = DASHBOARDS.list_dashboards(&key).await;
33+
pub async fn list_dashboards() -> Result<impl Responder, DashboardError> {
34+
let dashboards = DASHBOARDS.list_dashboards().await;
35+
let dashboard_summaries = dashboards
36+
.iter()
37+
.map(|dashboard| dashboard.to_summary())
38+
.collect::<Vec<_>>();
4239

43-
Ok((web::Json(dashboards), StatusCode::OK))
40+
Ok((web::Json(dashboard_summaries), StatusCode::OK))
4441
}
4542

46-
pub async fn get(
47-
req: HttpRequest,
48-
dashboard_id: Path<String>,
49-
) -> Result<impl Responder, DashboardError> {
50-
let user_id = get_user_from_request(&req)?;
51-
let dashboard_id = dashboard_id.into_inner();
43+
pub async fn get_dashboard(dashboard_id: Path<String>) -> Result<impl Responder, DashboardError> {
44+
let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?;
5245

53-
if let Some(dashboard) = DASHBOARDS
54-
.get_dashboard(&dashboard_id, &get_hash(&user_id))
46+
let dashboard = DASHBOARDS
47+
.get_dashboard(dashboard_id)
5548
.await
56-
{
57-
return Ok((web::Json(dashboard), StatusCode::OK));
58-
}
49+
.ok_or_else(|| DashboardError::Metadata("Dashboard does not exist"))?;
5950

60-
Err(DashboardError::Metadata("Dashboard does not exist"))
51+
Ok((web::Json(dashboard), StatusCode::OK))
6152
}
6253

63-
pub async fn post(
54+
pub async fn create_dashboard(
6455
req: HttpRequest,
6556
Json(mut dashboard): Json<Dashboard>,
6657
) -> Result<impl Responder, DashboardError> {
67-
let mut user_id = get_user_from_request(&req)?;
68-
user_id = get_hash(&user_id);
69-
let dashboard_id = get_hash(Utc::now().timestamp_micros().to_string().as_str());
70-
dashboard.dashboard_id = Some(dashboard_id.clone());
71-
dashboard.version = Some(CURRENT_DASHBOARD_VERSION.to_string());
72-
73-
dashboard.user_id = Some(user_id.clone());
74-
for tile in dashboard.tiles.iter_mut() {
75-
tile.tile_id = Some(get_hash(
76-
format!(
77-
"{}{}",
78-
rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 8),
79-
Utc::now().timestamp_micros()
80-
)
81-
.as_str(),
82-
));
58+
if dashboard.title.is_empty() {
59+
return Err(DashboardError::Metadata("Title must be provided"));
8360
}
84-
DASHBOARDS.update(&dashboard).await;
85-
86-
let path = dashboard_path(&user_id, &format!("{}.json", dashboard_id));
8761

88-
let store = PARSEABLE.storage.get_object_store();
89-
let dashboard_bytes = serde_json::to_vec(&dashboard)?;
90-
store
91-
.put_object(&path, Bytes::from(dashboard_bytes))
92-
.await?;
62+
let user_id = get_hash(&get_user_from_request(&req)?);
9363

64+
DASHBOARDS.create(&user_id, &mut dashboard).await?;
9465
Ok((web::Json(dashboard), StatusCode::OK))
9566
}
9667

97-
pub async fn update(
68+
pub async fn update_dashboard(
9869
req: HttpRequest,
9970
dashboard_id: Path<String>,
10071
Json(mut dashboard): Json<Dashboard>,
10172
) -> Result<impl Responder, DashboardError> {
102-
let mut user_id = get_user_from_request(&req)?;
103-
user_id = get_hash(&user_id);
104-
let dashboard_id = dashboard_id.into_inner();
73+
let user_id = get_hash(&get_user_from_request(&req)?);
74+
let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?;
10575

106-
if DASHBOARDS
107-
.get_dashboard(&dashboard_id, &user_id)
108-
.await
109-
.is_none()
110-
{
111-
return Err(DashboardError::Metadata("Dashboard does not exist"));
112-
}
113-
dashboard.dashboard_id = Some(dashboard_id.to_string());
114-
dashboard.user_id = Some(user_id.clone());
115-
dashboard.version = Some(CURRENT_DASHBOARD_VERSION.to_string());
116-
for tile in dashboard.tiles.iter_mut() {
117-
if tile.tile_id.is_none() {
118-
tile.tile_id = Some(get_hash(Utc::now().timestamp_micros().to_string().as_str()));
76+
// Validate all tiles have valid IDs
77+
if let Some(tiles) = &dashboard.tiles {
78+
if tiles.iter().any(|tile| tile.tile_id.is_nil()) {
79+
return Err(DashboardError::Metadata("Tile ID must be provided"));
11980
}
12081
}
121-
DASHBOARDS.update(&dashboard).await;
12282

123-
let path = dashboard_path(&user_id, &format!("{}.json", dashboard_id));
83+
// Check if tile_id are unique
84+
if let Some(tiles) = &dashboard.tiles {
85+
let unique_tiles: Vec<_> = tiles
86+
.iter()
87+
.map(|tile| tile.tile_id)
88+
.collect::<std::collections::HashSet<_>>()
89+
.into_iter()
90+
.collect();
91+
92+
if unique_tiles.len() != tiles.len() {
93+
return Err(DashboardError::Metadata("Tile IDs must be unique"));
94+
}
95+
}
12496

125-
let store = PARSEABLE.storage.get_object_store();
126-
let dashboard_bytes = serde_json::to_vec(&dashboard)?;
127-
store
128-
.put_object(&path, Bytes::from(dashboard_bytes))
97+
DASHBOARDS
98+
.update(&user_id, dashboard_id, &mut dashboard)
12999
.await?;
130100

131101
Ok((web::Json(dashboard), StatusCode::OK))
132102
}
133103

134-
pub async fn delete(
104+
pub async fn delete_dashboard(
135105
req: HttpRequest,
136106
dashboard_id: Path<String>,
137107
) -> Result<HttpResponse, DashboardError> {
138-
let mut user_id = get_user_from_request(&req)?;
139-
user_id = get_hash(&user_id);
140-
let dashboard_id = dashboard_id.into_inner();
141-
if DASHBOARDS
142-
.get_dashboard(&dashboard_id, &user_id)
108+
let user_id = get_hash(&get_user_from_request(&req)?);
109+
let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?;
110+
111+
DASHBOARDS.delete_dashboard(&user_id, dashboard_id).await?;
112+
113+
Ok(HttpResponse::Ok().finish())
114+
}
115+
116+
pub async fn add_tile(
117+
req: HttpRequest,
118+
dashboard_id: Path<String>,
119+
Json(tile): Json<Tile>,
120+
) -> Result<impl Responder, DashboardError> {
121+
if tile.tile_id.is_nil() {
122+
return Err(DashboardError::Metadata("Tile ID must be provided"));
123+
}
124+
125+
let user_id = get_hash(&get_user_from_request(&req)?);
126+
let dashboard_id = validate_dashboard_id(dashboard_id.into_inner())?;
127+
128+
let mut dashboard = DASHBOARDS
129+
.get_dashboard_by_user(dashboard_id, &user_id)
143130
.await
144-
.is_none()
145-
{
146-
return Err(DashboardError::Metadata("Dashboard does not exist"));
131+
.ok_or(DashboardError::Unauthorized)?;
132+
133+
let tiles = dashboard.tiles.get_or_insert_with(Vec::new);
134+
135+
// check if the tile already exists
136+
if tiles.iter().any(|t| t.tile_id == tile.tile_id) {
137+
return Err(DashboardError::Metadata("Tile already exists"));
147138
}
148-
let path = dashboard_path(&user_id, &format!("{}.json", dashboard_id));
149-
let store = PARSEABLE.storage.get_object_store();
150-
store.delete_object(&path).await?;
139+
tiles.push(tile);
151140

152-
DASHBOARDS.delete_dashboard(&dashboard_id).await;
141+
DASHBOARDS
142+
.update(&user_id, dashboard_id, &mut dashboard)
143+
.await?;
153144

154-
Ok(HttpResponse::Ok().finish())
145+
Ok((web::Json(dashboard), StatusCode::OK))
155146
}
156147

157148
#[derive(Debug, thiserror::Error)]
@@ -166,6 +157,8 @@ pub enum DashboardError {
166157
UserDoesNotExist(#[from] RBACError),
167158
#[error("Error: {0}")]
168159
Custom(String),
160+
#[error("Dashboard does not exist or is not accessible")]
161+
Unauthorized,
169162
}
170163

171164
impl actix_web::ResponseError for DashboardError {
@@ -176,6 +169,7 @@ impl actix_web::ResponseError for DashboardError {
176169
Self::Metadata(_) => StatusCode::BAD_REQUEST,
177170
Self::UserDoesNotExist(_) => StatusCode::NOT_FOUND,
178171
Self::Custom(_) => StatusCode::INTERNAL_SERVER_ERROR,
172+
Self::Unauthorized => StatusCode::UNAUTHORIZED,
179173
}
180174
}
181175

src/prism/home/mod.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ pub async fn generate_home_search_response(
259259
let (alert_titles, correlation_titles, dashboard_titles, filter_titles, stream_titles) = tokio::join!(
260260
get_alert_titles(key, query_value),
261261
get_correlation_titles(key, query_value),
262-
get_dashboard_titles(key, query_value),
262+
get_dashboard_titles(query_value),
263263
get_filter_titles(key, query_value),
264264
get_stream_titles(key)
265265
);
@@ -357,22 +357,20 @@ async fn get_correlation_titles(
357357
Ok(correlations)
358358
}
359359

360-
async fn get_dashboard_titles(
361-
key: &SessionKey,
362-
query_value: &str,
363-
) -> Result<Vec<Resource>, PrismHomeError> {
360+
async fn get_dashboard_titles(query_value: &str) -> Result<Vec<Resource>, PrismHomeError> {
364361
let dashboard_titles = DASHBOARDS
365-
.list_dashboards(key)
362+
.list_dashboards()
366363
.await
367364
.iter()
368365
.filter_map(|dashboard| {
369-
let dashboard_id = dashboard.dashboard_id.as_ref().unwrap().clone();
370-
if dashboard.name.to_lowercase().contains(query_value)
366+
let dashboard_id = *dashboard.dashboard_id.as_ref().unwrap();
367+
let dashboard_id = dashboard_id.to_string();
368+
if dashboard.title.to_lowercase().contains(query_value)
371369
|| dashboard_id.to_lowercase().contains(query_value)
372370
{
373371
Some(Resource {
374372
id: dashboard_id,
375-
name: dashboard.name.clone(),
373+
name: dashboard.title.clone(),
376374
resource_type: ResourceType::Dashboard,
377375
})
378376
} else {

0 commit comments

Comments
 (0)