Add ReportFile enum variant and implement related database schema and handlers

This commit is contained in:
dal 2025-08-22 08:44:48 -06:00
parent c21d84a5d2
commit 4277181a71
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
5 changed files with 184 additions and 10 deletions

View File

@ -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<sql_types::AssetTypeEnum, Pg> 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<sql_types::AssetTypeEnum, Pg> 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()),
}
}

View File

@ -105,6 +105,27 @@ pub struct MetricFile {
pub workspace_sharing_enabled_at: Option<DateTime<Utc>>,
}
#[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<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
pub publicly_accessible: bool,
pub publicly_enabled_by: Option<Uuid>,
pub public_expiry_date: Option<DateTime<Utc>>,
pub version_history: VersionHistory,
pub public_password: Option<String>,
pub workspace_sharing: WorkspaceSharing,
pub workspace_sharing_enabled_by: Option<Uuid>,
pub workspace_sharing_enabled_at: Option<DateTime<Utc>>,
}
#[derive(Queryable, Insertable, Identifiable, Associations, Debug, Clone, Serialize)]
#[diesel(belongs_to(Organization))]
#[diesel(belongs_to(User, foreign_key = created_by))]

View File

@ -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<Timestamptz>,
publicly_accessible -> Bool,
publicly_enabled_by -> Nullable<Uuid>,
public_expiry_date -> Nullable<Timestamptz>,
version_history -> Jsonb,
public_password -> Nullable<Text>,
workspace_sharing -> WorkspaceSharingEnum,
workspace_sharing_enabled_by -> Nullable<Uuid>,
workspace_sharing_enabled_at -> Nullable<Timestamptz>,
}
}
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,

View File

@ -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<Vec<Favorit
tokio::spawn(async move { get_favorite_chats(chat_ids).await })
};
let (dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res) =
match tokio::try_join!(dashboard_favorites, collection_favorites, threads_favorites, metrics_favorites, chats_favorites) {
Ok((dashboard_fav_res, collection_fav_res, threads_fav_res, metrics_fav_res, chats_fav_res)) => {
(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::<Vec<Uuid>>(),
);
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<Vec<Favorit
}
};
let favorite_reports = match reports_fav_res {
Ok(reports) => reports,
Err(e) => {
tracing::error!("Error getting favorite reports: {}", e);
return Err(anyhow!("Error getting favorite reports: {}", e));
}
};
let mut favorites: Vec<FavoriteObject> = Vec::with_capacity(user_favorites.len());
for favorite in &user_favorites {
@ -211,6 +230,15 @@ pub async fn list_user_favorites(user: &AuthenticatedUser) -> Result<Vec<Favorit
});
}
}
AssetType::ReportFile => {
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<Vec<(Uuid, FavoriteObject)>> {
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<Vec<Uuid>>) -> Result<Vec<FavoriteObject>> {
let mut conn = match get_pg_pool().get().await {
Ok(conn) => conn,
@ -597,6 +691,35 @@ async fn get_favorite_metrics(metric_ids: Arc<Vec<Uuid>>) -> Result<Vec<Favorite
Ok(favorite_metrics)
}
async fn get_favorite_reports(report_ids: Arc<Vec<Uuid>>) -> Result<Vec<FavoriteObject>> {
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,

View File

@ -155,7 +155,7 @@ const TitleCell = React.memo<{ name: string; chatId: string }>(({ name, chatId }
<div className="mr-2 flex items-center" onClick={onFavoriteDivClick}>
<FavoriteStar
id={chatId}
type={'chat'}
type={'report'}
iconStyle="tertiary"
title={name}
className="opacity-0 group-hover:opacity-100"