mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
feat: Move dashboards by updating the folder_id (#6001)
Changes the logic to move a dashboard into a different folder. - Before: We created a copy of the dashboard in the new folder and then deleted the old dashboard. - Now: We update the `folder_id` foreign key on the dashboard so that it references the new parent folder. This is the third PR in a series of 3 PRs that should be merged in sequence 1. https://github.com/openobserve/openobserve/pull/5997 - Updates pattern matching on the "put dashboard" message so that new fields can be added to the "put dashboard" message without introducing compiler errors for openobserve when built with enterprise features 2. https://github.com/openobserve/o2-enterprise/pull/350 - Adds the optional `new_folder_id` field to the "put dashboard" message 3. https://github.com/openobserve/openobserve/pull/6001 - Changes the "move dashboard" logic so that instead of creating a copied dashboard and deleting the old one, it updates the dashboard's `folder_id` to reference a new parent folder. In this PR we also emit a "put dashboard" super cluster event that contains the new `folder_id` of the updated dashboard.
This commit is contained in:
@@ -166,6 +166,7 @@ pub async fn list_all() -> Result<Vec<(String, Dashboard)>, errors::Error> {
|
||||
pub async fn put(
|
||||
org_id: &str,
|
||||
folder_id: &str,
|
||||
new_folder_id: Option<&str>,
|
||||
mut dashboard: Dashboard,
|
||||
clone: bool,
|
||||
) -> Result<Dashboard, errors::Error> {
|
||||
@@ -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::<i16>(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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Dashboard, DashboardError> {
|
||||
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<Dashboard, DashboardError> {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user