diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index 94e08c2b..72cf0b27 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -347,5 +347,5 @@ pub struct AFDatabaseRow { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AFDatabaseRowDetail { pub id: String, - pub cells: HashMap>, + pub cells: HashMap>, } diff --git a/src/biz/collab/ops.rs b/src/biz/collab/ops.rs index 051bea7e..379ac845 100644 --- a/src/biz/collab/ops.rs +++ b/src/biz/collab/ops.rs @@ -7,6 +7,7 @@ use collab::preclude::Collab; use collab_database::database::DatabaseBody; use collab_database::entity::FieldType; use collab_database::fields::Field; +use collab_database::fields::TypeOptions; use collab_database::rows::RowDetail; use collab_database::workspace_database::NoPersistenceDatabaseCollabService; use collab_database::workspace_database::WorkspaceDatabaseBody; @@ -535,6 +536,11 @@ pub async fn list_database_row_details( field_by_id }; + let mut selection_name_by_id: HashMap = HashMap::new(); + for field in field_by_id.values() { + add_to_selection_from_field(&mut selection_name_by_id, field); + } + let database_row_details = collab_storage .batch_get_collab(&uid, query_collabs, true) .await @@ -545,7 +551,11 @@ pub async fn list_database_row_details( let collab = Collab::new_with_source(CollabOrigin::Server, &id, ec.into(), vec![], false).unwrap(); let row_detail = RowDetail::from_collab(&collab).unwrap(); - let cells = convert_database_cells_human_readable(row_detail.row.cells, &field_by_id); + let cells = convert_database_cells_human_readable( + row_detail.row.cells, + &field_by_id, + &selection_name_by_id, + ); Some(AFDatabaseRowDetail { id, cells }) }, QueryCollabResult::Failed { error } => { @@ -561,8 +571,9 @@ pub async fn list_database_row_details( fn convert_database_cells_human_readable( db_cells: HashMap>, field_by_id: &HashMap, -) -> HashMap> { - let mut human_readable_records: HashMap> = + selection_name_by_id: &HashMap, +) -> HashMap> { + let mut human_readable_records: HashMap> = HashMap::with_capacity(db_cells.len()); for (field_id, cell) in db_cells { @@ -573,37 +584,132 @@ fn convert_database_cells_human_readable( continue; }, }; + let field_type = FieldType::from(field.field_type); - println!("field_type: {:#?}", field.field_type); - println!("type_options: {:#?}", field.type_options); - - let mut human_readable_cell: HashMap = HashMap::with_capacity(cell.len()); + let mut human_readable_cell: HashMap = + HashMap::with_capacity(cell.len()); for (key, value) in cell { - let value_str: String = match key.as_str() { + let serde_value: serde_json::Value = match key.as_str() { "created_at" | "last_modified" => match value.cast::() { Ok(timestamp) => chrono::DateTime::from_timestamp(timestamp, 0) .unwrap_or_default() - .to_rfc3339(), + .to_rfc3339() + .into(), Err(err) => { tracing::error!("Failed to cast timestamp: {:?}", err); - "".to_string() + serde_json::Value::Null }, }, - "field_type" => match value.cast::() { - Ok(field_type_int) => { - let field_type = FieldType::from(field_type_int); - format!("{:?}", field_type) - }, - Err(err) => { - tracing::error!("Failed to cast field type: {:?}", err); - String::from("Unknown") - }, + "field_type" => format!("{:?}", field_type).into(), + "data" => { + match field_type { + FieldType::DateTime => { + if let yrs::any::Any::String(value_str) = value { + let int_value = value_str.parse::().unwrap_or_default(); + chrono::DateTime::from_timestamp(int_value, 0) + .unwrap_or_default() + .to_rfc3339() + .into() + } else { + serde_json::to_value(value).unwrap_or_default() + } + }, + FieldType::Checklist => { + if let yrs::any::Any::String(value_str) = value { + serde_json::from_str(&value_str).unwrap_or_default() + } else { + serde_json::to_value(value).unwrap_or_default() + } + }, + FieldType::Media => { + if let yrs::any::Any::Array(arr) = value { + let mut acc = Vec::with_capacity(arr.len()); + for v in arr.as_ref() { + if let yrs::any::Any::String(value_str) = v { + let serde_value = serde_json::from_str(value_str).unwrap_or_default(); + acc.push(serde_value); + } + } + serde_json::Value::Array(acc) + } else { + serde_json::to_value(value).unwrap_or_default() + } + }, + FieldType::SingleSelect => { + if let yrs::any::Any::String(ref value_str) = value { + selection_name_by_id + .get(value_str.as_ref()) + .map(|v| v.to_string()) + .map(serde_json::Value::String) + .unwrap_or_else(|| value.to_string().into()) + } else { + serde_json::to_value(value).unwrap_or_default() + } + }, + FieldType::MultiSelect => { + if let yrs::any::Any::String(value_str) = value { + value_str + .split(',') + .filter_map(|v| selection_name_by_id.get(v).map(|v| v.to_string())) + .fold(String::new(), |mut acc, s| { + if !acc.is_empty() { + acc.push(','); + } + acc.push_str(&s); + acc + }) + .into() + } else { + serde_json::to_value(value).unwrap_or_default() + } + }, + // Handle different field types formatting as needed + _ => serde_json::to_value(value).unwrap_or_default(), + } }, - _ => value.to_string(), + _ => serde_json::to_value(value).unwrap_or_default(), }; - human_readable_cell.insert(key, value_str); + human_readable_cell.insert(key, serde_value); } human_readable_records.insert(field.name.clone(), human_readable_cell); } human_readable_records } + +fn add_to_selection_from_field(name_by_id: &mut HashMap, field: &Field) { + let field_type = FieldType::from(field.field_type); + match field_type { + FieldType::SingleSelect => { + add_to_selection_from_type_options(name_by_id, &field.type_options, &field_type); + }, + FieldType::MultiSelect => { + add_to_selection_from_type_options(name_by_id, &field.type_options, &field_type) + }, + _ => (), + } +} + +fn add_to_selection_from_type_options( + name_by_id: &mut HashMap, + type_options: &TypeOptions, + field_type: &FieldType, +) { + if let Some(type_opt) = type_options.get(&field_type.type_id()) { + if let Some(yrs::Any::String(arc_str)) = type_opt.get("content") { + if let Ok(serde_value) = serde_json::from_str::(arc_str) { + if let Some(selections) = serde_value.get("options").and_then(|v| v.as_array()) { + for selection in selections { + if let serde_json::Value::Object(selection) = selection { + if let (Some(id), Some(name)) = ( + selection.get("id").and_then(|v| v.as_str()), + selection.get("name").and_then(|v| v.as_str()), + ) { + name_by_id.insert(id.to_owned(), name.to_owned()); + } + } + } + } + } + } + }; +} diff --git a/tests/workspace/workspace_crud.rs b/tests/workspace/workspace_crud.rs index 3e9be5b3..b798b29a 100644 --- a/tests/workspace/workspace_crud.rs +++ b/tests/workspace/workspace_crud.rs @@ -45,7 +45,7 @@ async fn workspace_list_database() { // "field_type": "MultiSelect", // "last_modified": "2024-08-16T07:23:57+00:00", // "created_at": "2024-08-16T07:23:35+00:00", - // "data": "BD-T,6UxM", + // "data": "looks great,fast", // }, // "Description": { // "field_type": "RichText", @@ -54,7 +54,7 @@ async fn workspace_list_database() { // "data": "Install AppFlowy Mobile", // }, // "Status": { - // "data": "CEZD", + // "data": "To Do", // "field_type": "SingleSelect", // }, // }, @@ -64,14 +64,14 @@ async fn workspace_list_database() { row.cells["Multiselect"]["field_type"] == "MultiSelect" && row.cells["Multiselect"]["last_modified"] == "2024-08-16T07:23:57+00:00" && row.cells["Multiselect"]["created_at"] == "2024-08-16T07:23:35+00:00" - && row.cells["Multiselect"]["data"] == "BD-T,6UxM" + && row.cells["Multiselect"]["data"] == "looks great,fast" // Description && row.cells["Description"]["field_type"] == "RichText" && row.cells["Description"]["last_modified"] == "2024-08-16T07:17:03+00:00" && row.cells["Description"]["created_at"] == "2024-08-16T07:16:51+00:00" && row.cells["Description"]["data"] == "Install AppFlowy Mobile" // Status - && row.cells["Status"]["data"] == "CEZD" + && row.cells["Status"]["data"] == "To Do" && row.cells["Status"]["field_type"] == "SingleSelect" }) .unwrap();