diff --git a/api/libs/database/src/types/dashboard_yml.rs b/api/libs/database/src/types/dashboard_yml.rs index c1f0fbfe8..0e23e7e18 100644 --- a/api/libs/database/src/types/dashboard_yml.rs +++ b/api/libs/database/src/types/dashboard_yml.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use chrono::{DateTime, Utc}; use diesel::{ deserialize::FromSql, pg::Pg, @@ -14,24 +13,40 @@ use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, Clone, FromSqlRow, AsExpression)] #[diesel(sql_type = Jsonb)] +#[serde(rename_all = "camelCase")] pub struct DashboardYml { + #[serde(alias = "name")] pub name: String, + + #[serde(alias = "description")] pub description: Option, + + #[serde(alias = "rows")] pub rows: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] pub struct Row { pub items: Vec, // max number of items in a row is 4, min is 1 + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "row_height")] pub row_height: Option, // max is 550, min is 320 + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(alias = "column_sizes")] pub column_sizes: Option>, // max sum of elements is 12 min is 3 + + #[serde(skip_serializing_if = "Option::is_none", default)] + pub id: Option, // incremental id for rows } #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] pub struct RowItem { // This id is the id of the metric or item reference that goes here in the dashboard. + #[serde(alias = "id")] pub id: Uuid, } @@ -48,6 +63,13 @@ impl DashboardYml { file.name = String::from("New Dashboard"); } + // Add row IDs if they don't exist + for (index, row) in file.rows.iter_mut().enumerate() { + if row.id.is_none() { + row.id = Some((index as u32) + 1); + } + } + // Validate the file match file.validate() { Ok(_) => Ok(file), @@ -66,7 +88,7 @@ impl DashboardYml { for row in &self.rows { // Check row height constraints if let Some(row_height) = row.row_height { - if row_height < 320 || row_height > 550 { + if !(320..=550).contains(&row_height) { return Err(anyhow::anyhow!( "Row height must be between 320 and 550, got {}", row_height @@ -75,7 +97,7 @@ impl DashboardYml { } // Check number of items constraint - if row.items.len() < 1 || row.items.len() > 4 { + if row.items.is_empty() || row.items.len() > 4 { return Err(anyhow::anyhow!( "Number of items in row must be between 1 and 4, got {}", row.items.len() @@ -85,7 +107,7 @@ impl DashboardYml { // Check column sizes sum to valid amount if let Some(column_sizes) = &row.column_sizes { let sum: u32 = column_sizes.iter().sum(); - if sum < 3 || sum > 12 { + if !(3..=12).contains(&sum) { return Err(anyhow::anyhow!( "Sum of column sizes must be between 3 and 12, got {}", sum @@ -110,6 +132,26 @@ impl DashboardYml { serde_json::to_value(self) .map_err(|e| anyhow::anyhow!("Failed to serialize dashboard yml: {}", e)) } + + /// Returns the next available row ID based on existing rows + pub fn get_next_row_id(&self) -> u32 { + self.rows + .iter() + .filter_map(|row| row.id) + .max() + .map_or(1, |max_id| max_id + 1) + } + + /// Adds a new row with provided items and auto-generates the row ID + pub fn add_row(&mut self, items: Vec, row_height: Option, column_sizes: Option>) { + let next_id = self.get_next_row_id(); + self.rows.push(Row { + items, + row_height, + column_sizes, + id: Some(next_id), + }); + } } impl FromSql for DashboardYml { diff --git a/api/libs/handlers/src/metrics/get_metric_handler.rs b/api/libs/handlers/src/metrics/get_metric_handler.rs index a73bd17f5..f24c59810 100644 --- a/api/libs/handlers/src/metrics/get_metric_handler.rs +++ b/api/libs/handlers/src/metrics/get_metric_handler.rs @@ -165,12 +165,17 @@ pub async fn get_metric_handler(metric_id: &Uuid, user_id: &Uuid) -> Result