diff --git a/src/infra/src/table/dashboards.rs b/src/infra/src/table/dashboards.rs index d9618bec1f..141dd553e0 100644 --- a/src/infra/src/table/dashboards.rs +++ b/src/infra/src/table/dashboards.rs @@ -166,6 +166,7 @@ pub async fn list_all() -> Result, errors::Error> { pub async fn put( org_id: &str, folder_id: &str, + new_folder_id: Option<&str>, mut dashboard: Dashboard, clone: bool, ) -> Result { @@ -201,59 +202,82 @@ pub async fn put( dashboard.set_updated_at(); } let updated_at = dashboard.updated_at; - let data = inner_data_as_json(dashboard)?; - let model = match get_model_from_folder(client, org_id, folder_id, &dashboard_id).await? { - None => { - // Destination folder does not exist so the dashboard can neither be - // created nor updated. - Err(errors::PutDashboardError::FolderDoesNotExist) - } - Some((folder_m, Some(dash_m))) => { - // Destination folder exists and dashboard already exists, so - // update the dashboard. - let mut dash_am = dash_m.into_active_model(); - dash_am.folder_id = Set(folder_m.id); - dash_am.owner = Set(owner); - dash_am.role = Set(role); - dash_am.title = Set(title); - dash_am.description = Set(description); - dash_am.data = Set(data); - dash_am.version = Set(version); - dash_am.updated_at = Set(updated_at); - let model: dashboards::Model = dash_am.update(client).await?.try_into_model()?; - Ok(model) - } - Some((folder_m, None)) => { - // Destination folder exists but dashboard does not exist, so create - // a new dashboard. - // TODO: Use timestamp in microseconds like all other resources - let created_at_unix: i64 = if let Some(created_at_tz) = created_at_depricated { - created_at_tz.timestamp() - } else { - chrono::Utc::now().timestamp() - }; + // Try to get the new folder if the dashboard is being moved to a new folder. + let maybe_new_folder_model = if let Some(new_folder_id) = new_folder_id { + let new_folder_model = folders::Entity::find() + .filter(folders::Column::Org.eq(org_id)) + .filter(folders::Column::Type.eq::(folder_type_into_i16(FolderType::Dashboards))) + .filter(folders::Column::FolderId.eq(new_folder_id)) + .one(client) + .await? + .ok_or(errors::PutDashboardError::FolderDoesNotExist)?; + Some(new_folder_model) + } else { + None + }; - let dash_am = dashboards::ActiveModel { - id: Set(svix_ksuid::Ksuid::new(None, None).to_string()), - dashboard_id: Set(dashboard_id.to_owned()), - folder_id: Set(folder_m.id), - owner: Set(owner), - role: Set(role), - title: Set(title), - description: Set(description), - data: Set(data), - version: Set(version), - created_at: Set(created_at_unix), - updated_at: Set(updated_at), - }; - let model: dashboards::Model = dash_am.insert(client).await?.try_into_model()?; - Ok(model) - } - }?; + let dashboard_model = + match get_model_from_folder(client, org_id, folder_id, &dashboard_id).await? { + None => { + // Destination folder does not exist so the dashboard can neither be + // created nor updated. + Err(errors::PutDashboardError::FolderDoesNotExist) + } + Some((folder_m, Some(dash_m))) => { + // Destination folder exists and dashboard already exists, so + // update the dashboard. - let dash = model.try_into()?; + // If the dashboard is being to a new folder, then update the folder ID foreign key. + // Otherwise keep using the older folder ID foreign key. + let folder_id = if let Some(new_folder_model) = maybe_new_folder_model { + new_folder_model.id + } else { + folder_m.id + }; + + let mut dash_am = dash_m.into_active_model(); + dash_am.folder_id = Set(folder_id); + dash_am.owner = Set(owner); + dash_am.role = Set(role); + dash_am.title = Set(title); + dash_am.description = Set(description); + dash_am.data = Set(data); + dash_am.version = Set(version); + dash_am.updated_at = Set(updated_at); + let model: dashboards::Model = dash_am.update(client).await?.try_into_model()?; + Ok(model) + } + Some((folder_m, None)) => { + // Destination folder exists but dashboard does not exist, so create + // a new dashboard. + // TODO: Use timestamp in microseconds like all other resources + let created_at_unix: i64 = if let Some(created_at_tz) = created_at_depricated { + created_at_tz.timestamp() + } else { + chrono::Utc::now().timestamp() + }; + + let dash_am = dashboards::ActiveModel { + id: Set(svix_ksuid::Ksuid::new(None, None).to_string()), + dashboard_id: Set(dashboard_id.to_owned()), + folder_id: Set(folder_m.id), + owner: Set(owner), + role: Set(role), + title: Set(title), + description: Set(description), + data: Set(data), + version: Set(version), + created_at: Set(created_at_unix), + updated_at: Set(updated_at), + }; + let model: dashboards::Model = dash_am.insert(client).await?.try_into_model()?; + Ok(model) + } + }?; + + let dash = dashboard_model.try_into()?; Ok(dash) } diff --git a/src/service/dashboards/mod.rs b/src/service/dashboards/mod.rs index 3ee78e6f01..7e7217b6b0 100644 --- a/src/service/dashboards/mod.rs +++ b/src/service/dashboards/mod.rs @@ -284,7 +284,7 @@ pub async fn create_dashboard( let dashboard = if table::folders::exists(org_id, folder_id, FolderType::Dashboards).await? { let dashboard_id = ider::generate(); - let saved = put(org_id, &dashboard_id, folder_id, dashboard, None).await?; + let saved = put(org_id, &dashboard_id, folder_id, None, dashboard, None).await?; set_ownership( org_id, "dashboards", @@ -306,7 +306,7 @@ pub async fn create_dashboard( .await .map_err(|_| DashboardError::CreateDefaultFolder)?; let dashboard_id = ider::generate(); - let saved = put(org_id, &dashboard_id, folder_id, dashboard, None).await?; + let saved = put(org_id, &dashboard_id, folder_id, None, dashboard, None).await?; set_ownership( org_id, "dashboards", @@ -343,7 +343,7 @@ pub async fn update_dashboard( dashboard: Dashboard, hash: Option<&str>, ) -> Result { - let dashboard = put(org_id, dashboard_id, folder_id, dashboard, hash).await?; + let dashboard = put(org_id, dashboard_id, folder_id, None, dashboard, hash).await?; #[cfg(feature = "enterprise")] if get_o2_config().super_cluster.enabled { @@ -428,14 +428,25 @@ pub async fn move_dashboard( return Err(DashboardError::MoveDestinationFolderNotFound); }; - // add the dashboard to the destination folder - put(org_id, dashboard_id, to_folder, dashboard.clone(), None).await?; + // update the dashboard so that it's parent folder is the destination folder + put( + org_id, + dashboard_id, + from_folder, + Some(to_folder), + dashboard.clone(), + Some(&dashboard.hash), + ) + .await?; // OFGA ownership #[cfg(feature = "enterprise")] { if get_o2_config().super_cluster.enabled { - let _ = o2_enterprise::enterprise::super_cluster::queue::dashboards_put( - org_id, to_folder, dashboard, + let _ = o2_enterprise::enterprise::super_cluster::queue::dashboards_put_v2( + org_id, + from_folder, + Some(to_folder), + dashboard, ) .await; } @@ -447,31 +458,6 @@ pub async fn move_dashboard( &get_ofga_type("folders"), ) .await; - } - } - - // delete the dashboard from the source folder - table::dashboards::delete_from_folder(org_id, from_folder, dashboard_id) - .await - .map_err(|e| { - DashboardError::MoveDashboardDeleteOld( - dashboard_id.to_string(), - from_folder.to_string(), - e.to_string(), - ) - })?; - - #[cfg(feature = "enterprise")] - { - if get_o2_config().super_cluster.enabled { - let _ = o2_enterprise::enterprise::super_cluster::queue::dashboards_delete( - org_id, - from_folder, - dashboard_id, - ) - .await; - } - if get_openfga_config().enabled { remove_parent_relation( dashboard_id, &get_ofga_type("dashboards"), @@ -489,6 +475,7 @@ async fn put( org_id: &str, dashboard_id: &str, folder_id: &str, + new_folder_id: Option<&str>, mut dashboard: Dashboard, hash: Option<&str>, ) -> Result { @@ -520,7 +507,7 @@ async fn put( dashboard.set_title(title); dashboard.set_dashboard_id(dashboard_id.to_owned()); - let dash = table::dashboards::put(org_id, folder_id, dashboard, false).await?; + let dash = table::dashboards::put(org_id, folder_id, new_folder_id, dashboard, false).await?; Ok(dash) } diff --git a/src/super_cluster_queue/dashboards.rs b/src/super_cluster_queue/dashboards.rs index 924137121a..c884663ff4 100644 --- a/src/super_cluster_queue/dashboards.rs +++ b/src/super_cluster_queue/dashboards.rs @@ -27,11 +27,19 @@ pub(crate) async fn process_msg(msg: DashboardMessage) -> Result<()> { DashboardMessage::Put { org_id, folder_id, + new_folder_id, dashboard, .. } => { // `clone` is always true for super cluster - table::dashboards::put(&org_id, &folder_id, dashboard, true).await?; + table::dashboards::put( + &org_id, + &folder_id, + new_folder_id.as_deref(), + dashboard, + true, + ) + .await?; } DashboardMessage::Delete { org_id,