diff --git a/apps/api/libs/database/src/enums.rs b/apps/api/libs/database/src/enums.rs index 30c42fb03..9b3e0dff2 100644 --- a/apps/api/libs/database/src/enums.rs +++ b/apps/api/libs/database/src/enums.rs @@ -233,6 +233,8 @@ pub enum AssetType { MetricFile, #[serde(rename = "dashboard")] DashboardFile, + #[serde(rename = "report")] + ReportFile, } impl AssetType { @@ -244,6 +246,7 @@ impl AssetType { AssetType::Chat => "chat", AssetType::MetricFile => "metric", AssetType::DashboardFile => "dashboard", + AssetType::ReportFile => "report", } } } @@ -591,6 +594,7 @@ impl ToSql for AssetType { AssetType::Chat => out.write_all(b"chat")?, AssetType::DashboardFile => out.write_all(b"dashboard_file")?, AssetType::MetricFile => out.write_all(b"metric_file")?, + AssetType::ReportFile => out.write_all(b"report_file")?, } Ok(IsNull::No) } @@ -605,6 +609,7 @@ impl FromSql for AssetType { b"chat" => Ok(AssetType::Chat), b"dashboard_file" => Ok(AssetType::DashboardFile), b"metric_file" => Ok(AssetType::MetricFile), + b"report_file" => Ok(AssetType::ReportFile), _ => Err("Unrecognized enum variant".into()), } } diff --git a/apps/api/libs/database/src/models.rs b/apps/api/libs/database/src/models.rs index f963839f4..3d3bd9384 100644 --- a/apps/api/libs/database/src/models.rs +++ b/apps/api/libs/database/src/models.rs @@ -105,6 +105,27 @@ pub struct MetricFile { pub workspace_sharing_enabled_at: Option>, } +#[derive(Queryable, Insertable, Identifiable, Debug, Clone, Serialize)] +#[diesel(table_name = report_files)] +pub struct ReportFile { + pub id: Uuid, + pub name: String, + pub content: String, + pub organization_id: Uuid, + pub created_by: Uuid, + pub created_at: DateTime, + pub updated_at: DateTime, + pub deleted_at: Option>, + pub publicly_accessible: bool, + pub publicly_enabled_by: Option, + pub public_expiry_date: Option>, + pub version_history: VersionHistory, + pub public_password: Option, + pub workspace_sharing: WorkspaceSharing, + pub workspace_sharing_enabled_by: Option, + pub workspace_sharing_enabled_at: Option>, +} + #[derive(Queryable, Insertable, Identifiable, Associations, Debug, Clone, Serialize)] #[diesel(belongs_to(Organization))] #[diesel(belongs_to(User, foreign_key = created_by))] diff --git a/apps/api/libs/database/src/schema.rs b/apps/api/libs/database/src/schema.rs index 2bc6054f5..224ee9f46 100644 --- a/apps/api/libs/database/src/schema.rs +++ b/apps/api/libs/database/src/schema.rs @@ -463,6 +463,30 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use super::sql_types::WorkspaceSharingEnum; + + report_files (id) { + id -> Uuid, + name -> Varchar, + content -> Text, + organization_id -> Uuid, + created_by -> Uuid, + created_at -> Timestamptz, + updated_at -> Timestamptz, + deleted_at -> Nullable, + publicly_accessible -> Bool, + publicly_enabled_by -> Nullable, + public_expiry_date -> Nullable, + version_history -> Jsonb, + public_password -> Nullable, + workspace_sharing -> WorkspaceSharingEnum, + workspace_sharing_enabled_by -> Nullable, + workspace_sharing_enabled_at -> Nullable, + } +} + diesel::table! { use diesel::sql_types::*; use super::sql_types::UserOrganizationRoleEnum; @@ -757,6 +781,7 @@ diesel::allow_tables_to_appear_in_same_query!( metric_files_to_dashboard_files, metric_files_to_datasets, organizations, + report_files, permission_groups, permission_groups_to_identities, permission_groups_to_users, diff --git a/apps/api/libs/handlers/src/favorites/favorites_utils.rs b/apps/api/libs/handlers/src/favorites/favorites_utils.rs index 109191b5c..1806ef3eb 100644 --- a/apps/api/libs/handlers/src/favorites/favorites_utils.rs +++ b/apps/api/libs/handlers/src/favorites/favorites_utils.rs @@ -10,7 +10,7 @@ use database::{ enums::AssetType, pool::get_pg_pool, models::UserFavorite, - schema::{collections, collections_to_assets, dashboard_files, chats, messages_deprecated, threads_deprecated, user_favorites, metric_files}, + schema::{collections, collections_to_assets, dashboard_files, chats, messages_deprecated, threads_deprecated, user_favorites, metric_files, report_files}, }; use middleware::AuthenticatedUser; @@ -108,10 +108,21 @@ pub async fn list_user_favorites(user: &AuthenticatedUser) -> Result { - (dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res) + let reports_favorites = { + let report_ids = Arc::new( + user_favorites + .iter() + .filter(|(_, f)| f == &AssetType::ReportFile) + .map(|f| f.0) + .collect::>(), + ); + tokio::spawn(async move { get_favorite_reports(report_ids).await }) + }; + + let (dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res, reports_fav_res) = + match tokio::try_join!(dashboard_favorites, collection_favorites, threads_favorites, metrics_favorites, chats_favorites, reports_favorites) { + Ok((dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res, reports_fav_res)) => { + (dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res, reports_fav_res) } Err(e) => { tracing::error!("Error getting favorite assets: {}", e); @@ -159,6 +170,14 @@ pub async fn list_user_favorites(user: &AuthenticatedUser) -> Result reports, + Err(e) => { + tracing::error!("Error getting favorite reports: {}", e); + return Err(anyhow!("Error getting favorite reports: {}", e)); + } + }; + let mut favorites: Vec = Vec::with_capacity(user_favorites.len()); for favorite in &user_favorites { @@ -211,6 +230,15 @@ pub async fn list_user_favorites(user: &AuthenticatedUser) -> Result { + if let Some(report) = favorite_reports.iter().find(|r| r.id == favorite.0) { + favorites.push(FavoriteObject { + id: report.id, + name: report.name.clone(), + type_: AssetType::ReportFile, + }); + } + } _ => {} } } @@ -329,15 +357,20 @@ async fn get_assets_from_collections( tokio::spawn(async move { get_chats_from_collections(&collection_ids).await }) }; + let reports_handle = { + let collection_ids = Arc::clone(&collection_ids); + tokio::spawn(async move { get_reports_from_collections(&collection_ids).await }) + }; + let collection_name_handle = { let collection_ids = Arc::clone(&collection_ids); tokio::spawn(async move { get_collection_names(&collection_ids).await }) }; - let (dashboards_res, metrics_res, chats_res, collection_name_res) = - match tokio::join!(dashboards_handle, metrics_handle, chats_handle, collection_name_handle) { - (Ok(dashboards), Ok(metrics), Ok(chats), Ok(collection_name)) => { - (dashboards, metrics, chats, collection_name) + let (dashboards_res, metrics_res, chats_res, reports_res, collection_name_res) = + match tokio::join!(dashboards_handle, metrics_handle, chats_handle, reports_handle, collection_name_handle) { + (Ok(dashboards), Ok(metrics), Ok(chats), Ok(reports), Ok(collection_name)) => { + (dashboards, metrics, chats, reports, collection_name) } _ => { return Err(anyhow!( @@ -361,6 +394,11 @@ async fn get_assets_from_collections( Err(e) => return Err(anyhow!("Error getting chats from collection: {:?}", e)), }; + let reports = match reports_res { + Ok(reports) => reports, + Err(e) => return Err(anyhow!("Error getting reports from collection: {:?}", e)), + }; + let collection_names = match collection_name_res { Ok(collection_names) => collection_names, Err(e) => return Err(anyhow!("Error getting collection name: {:?}", e)), @@ -407,6 +445,18 @@ async fn get_assets_from_collections( }), ); + assets.extend( + reports + .iter() + .filter_map(|(report_collection_id, favorite_object)| { + if *report_collection_id == collection_id { + Some(favorite_object.clone()) + } else { + None + } + }), + ); + collection_favorites.push(FavoriteObject { id: collection_id, name: collection_name, @@ -568,6 +618,50 @@ async fn get_chats_from_collections( Ok(chat_objects) } +async fn get_reports_from_collections( + collection_ids: &[Uuid], +) -> Result> { + let mut conn = match get_pg_pool().get().await { + Ok(conn) => conn, + Err(e) => return Err(anyhow!("Error getting connection from pool: {:?}", e)), + }; + + let report_records: Vec<(Uuid, Uuid, String)> = match report_files::table + .inner_join( + collections_to_assets::table.on(report_files::id.eq(collections_to_assets::asset_id)), + ) + .select(( + collections_to_assets::collection_id, + report_files::id, + report_files::name, + )) + .filter(collections_to_assets::collection_id.eq_any(collection_ids)) + .filter(collections_to_assets::asset_type.eq(AssetType::ReportFile)) + .filter(report_files::deleted_at.is_null()) + .filter(collections_to_assets::deleted_at.is_null()) + .load::<(Uuid, Uuid, String)>(&mut conn) + .await + { + Ok(report_records) => report_records, + Err(e) => return Err(anyhow!("Error loading report records: {:?}", e)), + }; + + let report_objects: Vec<(Uuid, FavoriteObject)> = report_records + .iter() + .map(|(collection_id, id, name)| { + ( + *collection_id, + FavoriteObject { + id: *id, + name: name.clone(), + type_: AssetType::ReportFile, + }, + ) + }) + .collect(); + Ok(report_objects) +} + async fn get_favorite_metrics(metric_ids: Arc>) -> Result> { let mut conn = match get_pg_pool().get().await { Ok(conn) => conn, @@ -597,6 +691,35 @@ async fn get_favorite_metrics(metric_ids: Arc>) -> Result>) -> Result> { + let mut conn = match get_pg_pool().get().await { + Ok(conn) => conn, + Err(e) => return Err(anyhow!("Error getting connection from pool: {:?}", e)), + }; + + let report_records: Vec<(Uuid, String)> = match report_files::table + .select((report_files::id, report_files::name)) + .filter(report_files::id.eq_any(report_ids.as_ref())) + .filter(report_files::deleted_at.is_null()) + .load::<(Uuid, String)>(&mut conn) + .await + { + Ok(report_records) => report_records, + Err(diesel::NotFound) => return Err(anyhow!("Reports not found")), + Err(e) => return Err(anyhow!("Error loading report records: {:?}", e)), + }; + + let favorite_reports = report_records + .iter() + .map(|(id, name)| FavoriteObject { + id: *id, + name: name.clone(), + type_: AssetType::ReportFile, + }) + .collect(); + Ok(favorite_reports) +} + pub async fn update_favorites(user: &AuthenticatedUser, favorites: &[Uuid]) -> Result<()> { let mut conn = match get_pg_pool().get().await { Ok(conn) => conn, diff --git a/apps/web/src/controllers/ReportsListController/ReportItemsContainer.tsx b/apps/web/src/controllers/ReportsListController/ReportItemsContainer.tsx index 4feff10d5..75c9b7a48 100644 --- a/apps/web/src/controllers/ReportsListController/ReportItemsContainer.tsx +++ b/apps/web/src/controllers/ReportsListController/ReportItemsContainer.tsx @@ -155,7 +155,7 @@ const TitleCell = React.memo<{ name: string; chatId: string }>(({ name, chatId }