From 6c3aa8a7e227a7b74bfd3a7ff56738e7d9c8bf27 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Sat, 6 Jan 2024 15:46:16 +0100 Subject: [PATCH 01/24] feat: initial calculation controller --- .../src/entities/calculation_entities.rs | 147 ++++++++++++++++++ .../flowy-database2/src/entities/macros.rs | 21 +++ .../flowy-database2/src/entities/mod.rs | 2 + .../src/services/calculations/controller.rs | 117 ++++++++++++++ .../src/services/calculations/entities.rs | 83 ++++++++++ .../src/services/calculations/mod.rs | 7 + .../src/services/calculations/task.rs | 42 +++++ .../src/services/database/database_editor.rs | 19 +++ .../src/services/database_view/mod.rs | 1 + .../database_view/view_calculations.rs | 53 +++++++ .../src/services/database_view/view_editor.rs | 20 ++- .../services/database_view/view_operation.rs | 3 + .../flowy-database2/src/services/mod.rs | 1 + 13 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/task.rs create mode 100644 frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs new file mode 100644 index 000000000000..678aa6d0a9b4 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs @@ -0,0 +1,147 @@ +use std::{ + fmt::{Display, Formatter}, + sync::Arc, +}; + +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +use crate::{impl_into_calculation_type, services::calculations::Calculation}; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct CalculationPB { + #[pb(index = 1)] + pub id: String, + + #[pb(index = 2)] + pub field_id: String, + + #[pb(index = 3)] + pub calculation_type: CalculationType, + + #[pb(index = 4)] + pub value: String, +} + +impl std::convert::From<&Calculation> for CalculationPB { + fn from(calculation: &Calculation) -> Self { + let calculation_type = calculation.calculation_type.into(); + + Self { + id: calculation.id.clone(), + field_id: calculation.field_id.clone(), + calculation_type, + value: calculation.value.clone(), + } + } +} + +// impl CalculationPB { +// pub fn new(calculation: Calculation) -> Self { +// let calculation_type = calculation.calculation_type.into(); +// Self { +// id: calculation.id, +// field_id: calculation.field_id, +// calculation_type, +// value: calculation.value, +// } +// } +// } + +// impl std::convert::From for CalculationPB { +// fn from(calculation: Calculation) -> Self { +// Self { +// id: calculation.id.clone(), +// field_id: calculation.field_id.clone(), +// calculation_type: calculation.calculation_type.into(), +// value: calculation.value, +// } +// } +// } + +// impl std::convert::From<&Calculation> for CalculationPB { +// fn from(calculation: &Calculation) -> Self { +// Self { +// id: calculation.id.clone(), +// field_id: calculation.field_id.clone(), +// calculation_type: calculation.calculation_type.into(), +// value: calculation.value, +// } +// } +// } + +#[derive( + Default, Debug, Copy, Clone, PartialEq, Hash, Eq, ProtoBuf_Enum, Serialize_repr, Deserialize_repr, +)] +#[repr(u8)] +pub enum CalculationType { + #[default] + Average = 0, // Number + Max = 1, // Number + Median = 2, // Number + Min = 3, // Number + Sum = 4, // Number +} + +impl Display for CalculationType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value: i64 = (*self).into(); + f.write_fmt(format_args!("{}", value)) + } +} + +impl AsRef for CalculationType { + fn as_ref(&self) -> &CalculationType { + self + } +} + +impl From<&CalculationType> for CalculationType { + fn from(calculation_type: &CalculationType) -> Self { + *calculation_type + } +} + +impl CalculationType { + pub fn value(&self) -> i64 { + (*self).into() + } +} + +impl_into_calculation_type!(i64); +impl_into_calculation_type!(u8); + +impl From for i64 { + fn from(ty: CalculationType) -> Self { + (ty as u8) as i64 + } +} + +impl From<&CalculationType> for i64 { + fn from(ty: &CalculationType) -> Self { + i64::from(*ty) + } +} + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct RepeatedCalculationsPB { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From>> for RepeatedCalculationsPB { + fn from(calculations: Vec>) -> Self { + RepeatedCalculationsPB { + items: calculations + .into_iter() + .map(|rev: Arc| rev.as_ref().into()) + .collect(), + } + } +} + +impl std::convert::From> for RepeatedCalculationsPB { + fn from(items: Vec) -> Self { + Self { items } + } +} diff --git a/frontend/rust-lib/flowy-database2/src/entities/macros.rs b/frontend/rust-lib/flowy-database2/src/entities/macros.rs index bb942cc78c48..03b9b2021d32 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/macros.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/macros.rs @@ -42,3 +42,24 @@ macro_rules! impl_into_field_visibility { } }; } + +#[macro_export] +macro_rules! impl_into_calculation_type { + ($target: ident) => { + impl std::convert::From<$target> for CalculationType { + fn from(ty: $target) -> Self { + match ty { + 0 => CalculationType::Average, + 1 => CalculationType::Max, + 2 => CalculationType::Median, + 3 => CalculationType::Min, + 4 => CalculationType::Sum, + _ => { + tracing::error!("🔴 Can't parse CalculationType from value: {}", ty); + CalculationType::Average + }, + } + } + } + }; +} diff --git a/frontend/rust-lib/flowy-database2/src/entities/mod.rs b/frontend/rust-lib/flowy-database2/src/entities/mod.rs index 8c0bc38063f9..861eac07ebe4 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/mod.rs @@ -14,6 +14,7 @@ mod share_entities; mod sort_entities; mod type_option_entities; mod view_entities; +pub mod calculation_entities; #[macro_use] mod macros; @@ -33,3 +34,4 @@ pub use share_entities::*; pub use sort_entities::*; pub use type_option_entities::*; pub use view_entities::*; +pub use calculation_entities::*; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs new file mode 100644 index 000000000000..dc0b64dbcf19 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -0,0 +1,117 @@ +use std::str::FromStr; +use std::sync::Arc; + +use collab_database::rows::RowCell; +use dashmap::DashMap; +use flowy_error::FlowyResult; +use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; + +use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; +use lib_infra::future::Fut; + +use crate::services::cell::CellCache; +use crate::services::database_view::DatabaseViewChangedNotifier; + +pub trait CalculationsDelegate: Send + Sync + 'static { + fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; +} + +pub struct CalculationsController { + view_id: String, + handler_id: String, + delegate: Box, + result_by_field_id: DashMap, + cell_cache: CellCache, + task_scheduler: Arc>, + notifier: DatabaseViewChangedNotifier, +} + +impl Drop for CalculationsController { + fn drop(&mut self) { + tracing::trace!("Drop {}", std::any::type_name::()); + } +} + +impl CalculationsController { + pub async fn new( + view_id: &str, + handler_id: &str, + delegate: T, + // calculations: Vec>, + cell_cache: CellCache, + task_scheduler: Arc>, + notifier: DatabaseViewChangedNotifier, + ) -> Self + where + T: CalculationsDelegate + 'static, + { + let this = Self { + view_id: view_id.to_string(), + handler_id: handler_id.to_string(), + delegate: Box::new(delegate), + result_by_field_id: DashMap::default(), + cell_cache, + task_scheduler, + notifier, + }; + this + } + + pub async fn close(&self) { + if let Ok(mut task_scheduler) = self.task_scheduler.try_write() { + task_scheduler.unregister_handler(&self.handler_id).await; + } else { + tracing::error!("Try to get the lock of task_scheduler failed"); + } + } + + #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] + async fn gen_task(&self, task_type: CalculationEvent, qos: QualityOfService) { + let task_id = self.task_scheduler.read().await.next_task_id(); + let task = Task::new( + &self.handler_id, + task_id, + TaskContent::Text(task_type.to_string()), + qos, + ); + self.task_scheduler.write().await.add_task(task); + } + + #[tracing::instrument( + name = "process_filter_task", + level = "trace", + skip_all, + fields(filter_result), + err + )] + pub async fn process(&self, predicate: &str) -> FlowyResult<()> { + let event_type = CalculationEvent::from_str(predicate).unwrap(); + match event_type { + CalculationEvent::CellDidChange => self.update_calculations().await?, + } + Ok(()) + } + + async fn update_calculations(&self) -> FlowyResult<()> { + todo!("Update Calculations"); + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +enum CalculationEvent { + CellDidChange, +} + +impl ToString for CalculationEvent { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +impl FromStr for CalculationEvent { + type Err = serde_json::Error; + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs new file mode 100644 index 000000000000..b863ed5ed3bb --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -0,0 +1,83 @@ +use anyhow::bail; +use collab::core::any_map::AnyMapExtension; +use collab_database::views::{CalculationMap, CalculationMapBuilder}; + +#[derive(Debug, Clone)] +pub struct Calculation { + pub id: String, + pub field_id: String, + pub calculation_type: i64, + pub value: String, +} + +// impl Calculation { +// pub fn new(id: String, field_id: String, calculation_type: i64, value: String) -> Self { +// Self { +// id, +// field_id, +// calculation_type, +// value, +// } +// } +// } + +const CALCULATION_ID: &str = "id"; +const FIELD_ID: &str = "field_id"; +const CALCULATION_TYPE: &str = "ty"; +const CALCULATION_VALUE: &str = "calculation_value"; + +impl From for CalculationMap { + fn from(data: Calculation) -> Self { + CalculationMapBuilder::new() + .insert_str_value(CALCULATION_ID, data.id) + .insert_str_value(FIELD_ID, data.field_id) + .insert_i64_value(CALCULATION_TYPE, data.calculation_type.into()) + .insert_str_value(CALCULATION_VALUE, data.value) + .build() + } +} + +impl TryFrom for Calculation { + type Error = anyhow::Error; + + fn try_from(calculation: CalculationMap) -> Result { + match ( + calculation.get_str_value(CALCULATION_ID), + calculation.get_str_value(FIELD_ID), + ) { + (Some(id), Some(field_id)) => { + let value = calculation + .get_str_value(CALCULATION_VALUE) + .unwrap_or_default(); + let calculation_type = calculation + .get_i64_value(CALCULATION_TYPE) + .unwrap_or_default(); + + Ok(Calculation { + id, + field_id, + calculation_type, + value, + }) + }, + _ => { + bail!("Invalid calculation data") + }, + } + } +} + +pub struct CalculationsResultNotification { + pub view_id: String, + + pub calculations: Vec, +} + +impl CalculationsResultNotification { + pub fn new(view_id: String) -> Self { + Self { + view_id, + calculations: vec![], + } + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs new file mode 100644 index 000000000000..72bfa3a92577 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs @@ -0,0 +1,7 @@ +mod controller; +mod entities; +mod task; + +pub use controller::*; +pub use entities::*; +pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs new file mode 100644 index 000000000000..5687fffc1f46 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs @@ -0,0 +1,42 @@ +use flowy_task::{TaskContent, TaskHandler}; +use lib_infra::future::BoxResultFuture; +use std::sync::Arc; + +use crate::services::calculations::CalculationsController; + +pub struct CalculationsTaskHandler { + handler_id: String, + calculations_controller: Arc, +} + +impl CalculationsTaskHandler { + pub fn new(handler_id: String, calculations_controller: Arc) -> Self { + Self { + handler_id, + calculations_controller, + } + } +} + +impl TaskHandler for CalculationsTaskHandler { + fn handler_id(&self) -> &str { + &self.handler_id + } + + fn handler_name(&self) -> &str { + "CalculationsTaskHandler" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { + let calculations_controller = self.calculations_controller.clone(); + Box::pin(async move { + if let TaskContent::Text(predicate) = content { + calculations_controller + .process(&predicate) + .await + .map_err(anyhow::Error::from)?; + } + Ok(()) + }) + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index e4797b4a37c1..33ec49b77d79 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -16,6 +16,7 @@ use lib_infra::future::{to_fut, Fut, FutureResult}; use crate::entities::*; use crate::notification::{send_notification, DatabaseNotification}; +use crate::services::calculations::Calculation; use crate::services::cell::{ apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellCache, ToCellChangeset, }; @@ -226,6 +227,14 @@ impl DatabaseEditor { Ok(()) } + pub async fn get_all_calculations(&self, view_id: &str) -> RepeatedCalculationsPB { + if let Ok(view_editor) = self.database_views.get_view_editor(view_id).await { + view_editor.v_get_all_calculations().await.into() + } else { + RepeatedCalculationsPB { items: vec![] } + } + } + pub async fn get_all_filters(&self, view_id: &str) -> RepeatedFilterPB { if let Ok(view_editor) = self.database_views.get_view_editor(view_id).await { view_editor.v_get_all_filters().await.into() @@ -1423,6 +1432,16 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { self.database.lock().remove_all_sorts(view_id); } + fn get_all_calculations(&self, view_id: &str) -> Vec> { + self + .database + .lock() + .get_all_calculations(view_id) + .into_iter() + .map(Arc::new) + .collect() + } + fn get_all_filters(&self, view_id: &str) -> Vec> { self .database diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/mod.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/mod.rs index 6522c8e917e1..1f4649450be1 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/mod.rs @@ -6,6 +6,7 @@ pub use views::*; mod layout_deps; mod notifier; +mod view_calculations; mod view_editor; mod view_filter; mod view_group; diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs new file mode 100644 index 000000000000..5287c92fadea --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use collab_database::rows::RowCell; +use lib_infra::future::Fut; + +use crate::services::calculations::{ + CalculationsController, CalculationsDelegate, CalculationsTaskHandler, +}; +use crate::services::cell::CellCache; +use crate::services::database_view::{ + gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation, +}; + +pub async fn make_calculations_controller( + view_id: &str, + delegate: Arc, + notifier: DatabaseViewChangedNotifier, + cell_cache: CellCache, +) -> Arc { + // let calculations = delegate.get_all_calculations(view_id); + let task_scheduler = delegate.get_task_scheduler(); + let calculations_delegate = DatabaseViewCalculationsDelegateImpl(delegate.clone()); + let handler_id = gen_handler_id(); + + let calculations_controller = CalculationsController::new( + view_id, + &handler_id, + calculations_delegate, + // calculations, + cell_cache, + task_scheduler.clone(), + notifier, + ) + .await; + + let calculations_controller = Arc::new(calculations_controller); + task_scheduler + .write() + .await + .register_handler(CalculationsTaskHandler::new( + handler_id, + calculations_controller.clone(), + )); + calculations_controller +} + +struct DatabaseViewCalculationsDelegateImpl(Arc); + +impl CalculationsDelegate for DatabaseViewCalculationsDelegateImpl { + fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>> { + self.0.get_cells_for_field(view_id, field_id) + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 28a8ffb00cd0..5a5c1a70c567 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -19,6 +19,7 @@ use crate::entities::{ SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams, }; use crate::notification::{send_notification, DatabaseNotification}; +use crate::services::calculations::{Calculation, CalculationsController}; use crate::services::cell::CellCache; use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow}; use crate::services::database_view::view_filter::make_filter_controller; @@ -40,11 +41,14 @@ use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowConte use crate::services::setting::CalendarLayoutSetting; use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType}; +use super::view_calculations::make_calculations_controller; + pub struct DatabaseViewEditor { pub view_id: String, delegate: Arc, group_controller: Arc>>>, filter_controller: Arc, + calculations_controller: Arc, sort_controller: Arc>, pub notifier: DatabaseViewChangedNotifier, } @@ -83,7 +87,16 @@ impl DatabaseViewEditor { delegate.clone(), notifier.clone(), filter_controller.clone(), - cell_cache, + cell_cache.clone(), + ) + .await; + + // Calculations + let calculations_controller = make_calculations_controller( + &view_id, + delegate.clone(), + notifier.clone(), + cell_cache.clone(), ) .await; @@ -93,6 +106,7 @@ impl DatabaseViewEditor { group_controller, filter_controller, sort_controller, + calculations_controller, notifier, }) } @@ -508,6 +522,10 @@ impl DatabaseViewEditor { Ok(()) } + pub async fn v_get_all_calculations(&self) -> Vec> { + self.delegate.get_all_calculations(&self.view_id) + } + pub async fn v_get_all_filters(&self) -> Vec> { self.delegate.get_all_filters(&self.view_id) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs index 46bb5d4cd724..2e114b7b1f97 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs @@ -12,6 +12,7 @@ use flowy_task::TaskDispatcher; use lib_infra::future::{Fut, FutureResult}; use crate::entities::{FieldType, FieldVisibility}; +use crate::services::calculations::Calculation; use crate::services::field::TypeOptionCellDataHandler; use crate::services::field_settings::FieldSettings; use crate::services::filter::Filter; @@ -80,6 +81,8 @@ pub trait DatabaseViewOperation: Send + Sync + 'static { fn remove_all_sorts(&self, view_id: &str); + fn get_all_calculations(&self, view_id: &str) -> Vec>; + fn get_all_filters(&self, view_id: &str) -> Vec>; fn delete_filter(&self, view_id: &str, filter_id: &str); diff --git a/frontend/rust-lib/flowy-database2/src/services/mod.rs b/frontend/rust-lib/flowy-database2/src/services/mod.rs index 3faf7725a11b..3f5770691442 100644 --- a/frontend/rust-lib/flowy-database2/src/services/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/services/mod.rs @@ -1,3 +1,4 @@ +pub mod calculations; pub mod cell; pub mod database; pub mod database_view; From 97b2a100de0ffc7fae86996d543241f4ac50ec0c Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 18 Jan 2024 17:24:03 +0100 Subject: [PATCH 02/24] fix: entities --- frontend/appflowy_tauri/src-tauri/Cargo.toml | 14 +- frontend/rust-lib/Cargo.lock | 7 - frontend/rust-lib/Cargo.toml | 18 +-- .../src/entities/calculation_entities.rs | 34 ----- frontend/rust-lib/flowy-database2/src/lib.rs | 1 + .../src/services/calculations/cache.rs | 6 + .../src/services/calculations/controller.rs | 11 +- .../src/services/calculations/entities.rs | 11 -- .../src/services/calculations/mod.rs | 2 + .../src/services/cell/cell_data_cache.rs | 123 +----------------- .../src/services/database/database_editor.rs | 5 +- .../src/services/database_view/view_editor.rs | 1 + .../src/services/filter/controller.rs | 3 +- .../flowy-database2/src/utils/cache.rs | 98 ++++++++++++++ .../rust-lib/flowy-database2/src/utils/mod.rs | 1 + 15 files changed, 135 insertions(+), 200 deletions(-) create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs create mode 100644 frontend/rust-lib/flowy-database2/src/utils/cache.rs create mode 100644 frontend/rust-lib/flowy-database2/src/utils/mod.rs diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index 54951afced26..f6edcac6aee0 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -67,13 +67,13 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "215 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } +collab = { path = "../../AppFlowy-Collab/collab" } +collab-folder = { path = "../../AppFlowy-Collab/collab-folder" } +collab-document = { path = "../../AppFlowy-Collab/collab-document" } +collab-database = { path = "../../AppFlowy-Collab/collab-database" } +collab-plugins = { path = "../../AppFlowy-Collab/collab-plugins" } +collab-user = { path = "../../AppFlowy-Collab/collab-user" } +collab-entity = { path = "../../AppFlowy-Collab/collab-entity" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index d30433798d45..ac49a7052d23 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -686,7 +686,6 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "async-trait", @@ -706,7 +705,6 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "async-trait", @@ -733,7 +731,6 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "collab", @@ -751,7 +748,6 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "bytes", @@ -765,7 +761,6 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "chrono", @@ -801,7 +796,6 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "async-trait", @@ -831,7 +825,6 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7" dependencies = [ "anyhow", "collab", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 9d2da4213aae..70f2bec4e7c0 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -35,7 +35,7 @@ resolver = "2" [workspace.dependencies] lib-dispatch = { workspace = true, path = "lib-dispatch" } lib-log = { workspace = true, path = "lib-log" } -lib-infra= { workspace = true, path = "lib-infra" } +lib-infra = { workspace = true, path = "lib-infra" } flowy-ast = { workspace = true, path = "build-tool/flowy-ast" } flowy-codegen = { workspace = true, path = "build-tool/flowy-codegen" } flowy-derive = { workspace = true, path = "build-tool/flowy-derive" } @@ -75,7 +75,7 @@ futures = "0.3.29" tokio = "1.34.0" tokio-stream = "0.1.14" async-trait = "0.1.74" -chrono = { version = "0.4.31", default-features = false, features = ["clock"] } +chrono = { version = "0.4.31", default-features = false, features = ["clock"] } lru = "0.12.0" [profile.dev] @@ -117,10 +117,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "215 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" } +collab = { path = "../AppFlowy-Collab/collab" } +collab-folder = { path = "../AppFlowy-Collab/collab-folder" } +collab-document = { path = "../AppFlowy-Collab/collab-document" } +collab-database = { path = "../AppFlowy-Collab/collab-database" } +collab-plugins = { path = "../AppFlowy-Collab/collab-plugins" } +collab-user = { path = "../AppFlowy-Collab/collab-user" } +collab-entity = { path = "../AppFlowy-Collab/collab-entity" } diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs index 678aa6d0a9b4..584c26a667a4 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs @@ -36,40 +36,6 @@ impl std::convert::From<&Calculation> for CalculationPB { } } -// impl CalculationPB { -// pub fn new(calculation: Calculation) -> Self { -// let calculation_type = calculation.calculation_type.into(); -// Self { -// id: calculation.id, -// field_id: calculation.field_id, -// calculation_type, -// value: calculation.value, -// } -// } -// } - -// impl std::convert::From for CalculationPB { -// fn from(calculation: Calculation) -> Self { -// Self { -// id: calculation.id.clone(), -// field_id: calculation.field_id.clone(), -// calculation_type: calculation.calculation_type.into(), -// value: calculation.value, -// } -// } -// } - -// impl std::convert::From<&Calculation> for CalculationPB { -// fn from(calculation: &Calculation) -> Self { -// Self { -// id: calculation.id.clone(), -// field_id: calculation.field_id.clone(), -// calculation_type: calculation.calculation_type.into(), -// value: calculation.value, -// } -// } -// } - #[derive( Default, Debug, Copy, Clone, PartialEq, Hash, Eq, ProtoBuf_Enum, Serialize_repr, Deserialize_repr, )] diff --git a/frontend/rust-lib/flowy-database2/src/lib.rs b/frontend/rust-lib/flowy-database2/src/lib.rs index 9e9719b350c3..df57b4b9c5bb 100644 --- a/frontend/rust-lib/flowy-database2/src/lib.rs +++ b/frontend/rust-lib/flowy-database2/src/lib.rs @@ -8,3 +8,4 @@ pub mod notification; mod protobuf; pub mod services; pub mod template; +pub mod utils; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs new file mode 100644 index 000000000000..ed099c39567a --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs @@ -0,0 +1,6 @@ +use parking_lot::RwLock; +use std::sync::Arc; + +use crate::utils::cache::AnyTypeCache; + +pub type CalculationsCache = Arc>>; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index dc0b64dbcf19..751d6c286f4b 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use std::sync::Arc; use collab_database::rows::RowCell; -use dashmap::DashMap; use flowy_error::FlowyResult; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; @@ -10,7 +9,7 @@ use tokio::sync::RwLock; use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; use lib_infra::future::Fut; -use crate::services::cell::CellCache; +use crate::services::calculations::CalculationsCache; use crate::services::database_view::DatabaseViewChangedNotifier; pub trait CalculationsDelegate: Send + Sync + 'static { @@ -21,8 +20,7 @@ pub struct CalculationsController { view_id: String, handler_id: String, delegate: Box, - result_by_field_id: DashMap, - cell_cache: CellCache, + calculations_cache: CalculationsCache, task_scheduler: Arc>, notifier: DatabaseViewChangedNotifier, } @@ -39,7 +37,7 @@ impl CalculationsController { handler_id: &str, delegate: T, // calculations: Vec>, - cell_cache: CellCache, + calculations_cache: CalculationsCache, task_scheduler: Arc>, notifier: DatabaseViewChangedNotifier, ) -> Self @@ -50,8 +48,7 @@ impl CalculationsController { view_id: view_id.to_string(), handler_id: handler_id.to_string(), delegate: Box::new(delegate), - result_by_field_id: DashMap::default(), - cell_cache, + calculations_cache, task_scheduler, notifier, }; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index b863ed5ed3bb..3561fc1e7b30 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -10,17 +10,6 @@ pub struct Calculation { pub value: String, } -// impl Calculation { -// pub fn new(id: String, field_id: String, calculation_type: i64, value: String) -> Self { -// Self { -// id, -// field_id, -// calculation_type, -// value, -// } -// } -// } - const CALCULATION_ID: &str = "id"; const FIELD_ID: &str = "field_id"; const CALCULATION_TYPE: &str = "ty"; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs index 72bfa3a92577..214bcbb8b7b9 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs @@ -1,7 +1,9 @@ +mod cache; mod controller; mod entities; mod task; +pub(crate) use cache::*; pub use controller::*; pub use entities::*; pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs index 03bce144da55..30e61dd09843 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs @@ -1,126 +1,7 @@ use parking_lot::RwLock; -use std::any::{type_name, Any}; -use std::collections::HashMap; -use std::fmt::Debug; -use std::hash::Hash; use std::sync::Arc; +use crate::utils::cache::AnyTypeCache; + pub type CellCache = Arc>>; pub type CellFilterCache = Arc>>; - -#[derive(Default, Debug)] -/// The better option is use LRU cache -pub struct AnyTypeCache(HashMap); - -impl AnyTypeCache -where - TypeValueKey: Clone + Hash + Eq, -{ - pub fn new() -> Arc>> { - Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) - } - - pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option - where - T: 'static + Send + Sync, - { - self - .0 - .insert(key.clone(), TypeValue::new(val)) - .and_then(downcast_owned) - } - - pub fn remove(&mut self, key: &TypeValueKey) { - self.0.remove(key); - } - - // pub fn remove>(&mut self, key: K) -> Option - // where - // T: 'static + Send + Sync, - // { - // self.0.remove(key.as_ref()).and_then(downcast_owned) - // } - - pub fn get(&self, key: &TypeValueKey) -> Option<&T> - where - T: 'static + Send + Sync, - { - self - .0 - .get(key) - .and_then(|type_value| type_value.boxed.downcast_ref()) - } - - pub fn get_mut(&mut self, key: &TypeValueKey) -> Option<&mut T> - where - T: 'static + Send + Sync, - { - self - .0 - .get_mut(key) - .and_then(|type_value| type_value.boxed.downcast_mut()) - } - - pub fn contains(&self, key: &TypeValueKey) -> bool { - self.0.contains_key(key) - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -fn downcast_owned(type_value: TypeValue) -> Option { - type_value.boxed.downcast().ok().map(|boxed| *boxed) -} - -#[derive(Debug)] -struct TypeValue { - boxed: Box, - #[allow(dead_code)] - ty: &'static str, -} - -impl TypeValue { - pub fn new(value: T) -> Self - where - T: Send + Sync + 'static, - { - Self { - boxed: Box::new(value), - ty: type_name::(), - } - } -} - -impl std::ops::Deref for TypeValue { - type Target = Box; - - fn deref(&self) -> &Self::Target { - &self.boxed - } -} - -impl std::ops::DerefMut for TypeValue { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.boxed - } -} - -// #[cfg(test)] -// mod tests { -// use crate::services::cell::CellDataCache; -// -// #[test] -// fn test() { -// let mut ext = CellDataCache::new(); -// ext.insert("1", "a".to_string()); -// ext.insert("2", 2); -// -// let a: &String = ext.get("1").unwrap(); -// assert_eq!(a, "a"); -// -// let a: Option<&usize> = ext.get("1"); -// assert!(a.is_none()); -// } -// } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 33ec49b77d79..88cbe1e42a91 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -17,9 +17,7 @@ use lib_infra::future::{to_fut, Fut, FutureResult}; use crate::entities::*; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::calculations::Calculation; -use crate::services::cell::{ - apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellCache, ToCellChangeset, -}; +use crate::services::cell::{apply_cell_changeset, get_cell_protobuf, CellCache, ToCellChangeset}; use crate::services::database::util::database_view_setting_pb_from_view; use crate::services::database::UpdatedRow; use crate::services::database_view::{ @@ -38,6 +36,7 @@ use crate::services::filter::Filter; use crate::services::group::{default_group_setting, GroupChangesets, GroupSetting, RowChangeset}; use crate::services::share::csv::{CSVExport, CSVFormat}; use crate::services::sort::Sort; +use crate::utils::cache::AnyTypeCache; #[derive(Clone)] pub struct DatabaseEditor { diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 5a5c1a70c567..da61f67a28b5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -114,6 +114,7 @@ impl DatabaseViewEditor { pub async fn close(&self) { self.sort_controller.write().await.close().await; self.filter_controller.close().await; + self.calculations_controller.close().await; } pub async fn v_get_view(&self) -> Option { diff --git a/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs b/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs index 8cbffd62f5af..12d54496e7de 100644 --- a/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs @@ -14,10 +14,11 @@ use lib_infra::future::Fut; use crate::entities::filter_entities::*; use crate::entities::{FieldType, InsertedRowPB, RowMetaPB}; -use crate::services::cell::{AnyTypeCache, CellCache, CellFilterCache}; +use crate::services::cell::{CellCache, CellFilterCache}; use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier}; use crate::services::field::*; use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification}; +use crate::utils::cache::AnyTypeCache; pub trait FilterDelegate: Send + Sync + 'static { fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut>>; diff --git a/frontend/rust-lib/flowy-database2/src/utils/cache.rs b/frontend/rust-lib/flowy-database2/src/utils/cache.rs new file mode 100644 index 000000000000..5f9bda50c996 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/utils/cache.rs @@ -0,0 +1,98 @@ +use parking_lot::RwLock; +use std::any::{type_name, Any}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; +use std::sync::Arc; + +#[derive(Default, Debug)] +/// The better option is use LRU cache +pub struct AnyTypeCache(HashMap); + +impl AnyTypeCache +where + TypeValueKey: Clone + Hash + Eq, +{ + pub fn new() -> Arc>> { + Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) + } + + pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option + where + T: 'static + Send + Sync, + { + self + .0 + .insert(key.clone(), TypeValue::new(val)) + .and_then(downcast_owned) + } + + pub fn remove(&mut self, key: &TypeValueKey) { + self.0.remove(key); + } + + pub fn get(&self, key: &TypeValueKey) -> Option<&T> + where + T: 'static + Send + Sync, + { + self + .0 + .get(key) + .and_then(|type_value| type_value.boxed.downcast_ref()) + } + + pub fn get_mut(&mut self, key: &TypeValueKey) -> Option<&mut T> + where + T: 'static + Send + Sync, + { + self + .0 + .get_mut(key) + .and_then(|type_value| type_value.boxed.downcast_mut()) + } + + pub fn contains(&self, key: &TypeValueKey) -> bool { + self.0.contains_key(key) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +fn downcast_owned(type_value: TypeValue) -> Option { + type_value.boxed.downcast().ok().map(|boxed| *boxed) +} + +#[derive(Debug)] +struct TypeValue { + boxed: Box, + #[allow(dead_code)] + ty: &'static str, +} + +impl TypeValue { + pub fn new(value: T) -> Self + where + T: Send + Sync + 'static, + { + Self { + boxed: Box::new(value), + ty: type_name::(), + } + } +} + +impl std::ops::Deref for TypeValue { + type Target = Box; + + fn deref(&self) -> &Self::Target { + &self.boxed + } +} + +impl std::ops::DerefMut for TypeValue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.boxed + } +} diff --git a/frontend/rust-lib/flowy-database2/src/utils/mod.rs b/frontend/rust-lib/flowy-database2/src/utils/mod.rs new file mode 100644 index 000000000000..a5c08fdb0d2c --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod cache; From 89f5eaa8fa50af283bcb91abc11e2a8d64334f19 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Tue, 23 Jan 2024 22:58:42 +0100 Subject: [PATCH 03/24] feat: calculations --- .../calculations/calculation_type_ext.dart | 18 ++ .../calculations/calculations_listener.dart | 54 +++++ .../calculations/calculations_service.dart | 46 ++++ .../calculations/calculations_bloc.dart | 167 +++++++++++++ .../database/grid/presentation/grid_page.dart | 58 +++-- .../calculations/calculations_row.dart | 227 ++++++++++++++++++ .../widgets/footer/grid_footer.dart | 12 +- .../grid/presentation/widgets/row/row.dart | 24 +- .../database/widgets/row/row_detail.dart | 7 +- frontend/resources/translations/en.json | 9 +- .../calculation/calculation_changeset.rs | 73 ++++++ .../{ => calculation}/calculation_entities.rs | 0 .../src/entities/calculation/mod.rs | 5 + .../flowy-database2/src/entities/mod.rs | 4 +- .../flowy-database2/src/event_handler.rs | 42 ++++ .../rust-lib/flowy-database2/src/event_map.rs | 13 + .../flowy-database2/src/notification.rs | 5 +- .../src/services/calculations/cache.rs | 2 +- .../src/services/calculations/controller.rs | 91 ++++++- .../src/services/calculations/entities.rs | 179 +++++++++++++- .../src/services/calculations/task.rs | 2 +- .../src/services/database/database_editor.rs | 23 ++ .../src/services/database_view/notifier.rs | 14 +- .../database_view/view_calculations.rs | 14 +- .../src/services/database_view/view_editor.rs | 101 +++++++- .../services/database_view/view_operation.rs | 4 + .../src/services/database_view/views.rs | 1 + 27 files changed, 1111 insertions(+), 84 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculation_type_ext.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart create mode 100644 frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs rename frontend/rust-lib/flowy-database2/src/entities/{ => calculation}/calculation_entities.rs (100%) create mode 100644 frontend/rust-lib/flowy-database2/src/entities/calculation/mod.rs diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculation_type_ext.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculation_type_ext.dart new file mode 100644 index 000000000000..3fe2087e6b99 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculation_type_ext.dart @@ -0,0 +1,18 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; + +extension CalcTypeLabel on CalculationType { + String get label => switch (this) { + CalculationType.Average => + LocaleKeys.grid_calculationTypeLabel_average.tr(), + CalculationType.Max => LocaleKeys.grid_calculationTypeLabel_max.tr(), + CalculationType.Median => + LocaleKeys.grid_calculationTypeLabel_median.tr(), + CalculationType.Min => LocaleKeys.grid_calculationTypeLabel_min.tr(), + CalculationType.Sum => LocaleKeys.grid_calculationTypeLabel_sum.tr(), + _ => throw UnimplementedError( + 'Label for $this has not been implemented', + ), + }; +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart new file mode 100644 index 000000000000..09d26a024fd8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart @@ -0,0 +1,54 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:appflowy/core/notification/grid_notification.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_infra/notifier.dart'; + +typedef UpdateCalculationValue + = Either; + +class CalculationsListener { + final String viewId; + + PublishNotifier? _calculationNotifier = + PublishNotifier(); + DatabaseNotificationListener? _listener; + + CalculationsListener({required this.viewId}); + + void start({ + required void Function(UpdateCalculationValue) onCalculationChanged, + }) { + _calculationNotifier?.addPublishListener(onCalculationChanged); + _listener = DatabaseNotificationListener( + objectId: viewId, + handler: _handler, + ); + } + + void _handler( + DatabaseNotification ty, + Either result, + ) { + switch (ty) { + case DatabaseNotification.DidUpdateCalculation: + _calculationNotifier?.value = result.fold( + (payload) => left( + CalculationChangesetNotificationPB.fromBuffer(payload), + ), + (err) => right(err), + ); + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _calculationNotifier?.dispose(); + _calculationNotifier = null; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart new file mode 100644 index 000000000000..80194c0e0442 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart @@ -0,0 +1,46 @@ +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:dartz/dartz.dart'; + +class CalculationsBackendService { + final String viewId; + + const CalculationsBackendService({required this.viewId}); + + // Get Calculations (initial fetch) + Future> getCalculations() async { + final payload = DatabaseViewIdPB()..value = viewId; + + return DatabaseEventGetAllCalculations(payload).send(); + } + + Future updateCalculation( + String fieldId, + CalculationType type, { + String? calculationId, + }) async { + final payload = UpdateCalculationChangesetPB() + ..viewId = viewId + ..fieldId = fieldId + ..calculationType = type; + + if (calculationId != null) { + payload.calculationId = calculationId; + } + + DatabaseEventUpdateCalculation(payload).send(); + } + + Future removeCalculation( + String fieldId, + String calculationId, + ) async { + final payload = RemoveCalculationChangesetPB() + ..viewId = viewId + ..fieldId = fieldId + ..calculationId = calculationId; + + DatabaseEventRemoveCalculation(payload).send(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart new file mode 100644 index 000000000000..d5a63fbea27c --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart @@ -0,0 +1,167 @@ +import 'package:appflowy/plugins/database/application/calculations/calculations_listener.dart'; +import 'package:appflowy/plugins/database/application/calculations/calculations_service.dart'; +import 'package:appflowy/plugins/database/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart'; +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'calculations_bloc.freezed.dart'; + +class CalculationsBloc extends Bloc { + final String viewId; + final FieldController _fieldController; + final CalculationsListener _calculationsListener; + late final CalculationsBackendService _calculationsService; + + CalculationsBloc({ + required this.viewId, + required FieldController fieldController, + }) : _fieldController = fieldController, + _calculationsListener = CalculationsListener(viewId: viewId), + _calculationsService = CalculationsBackendService(viewId: viewId), + super(CalculationsState.initial()) { + on((event, emit) async { + await event.when( + started: () async { + _startListening(); + _getAllCalculations(); + + add( + CalculationsEvent.didReceiveFieldUpdate( + _fieldController.fieldInfos, + ), + ); + }, + didReceiveFieldUpdate: (fields) async { + emit( + state.copyWith( + fields: fields + .where( + (e) => + e.visibility != null && + e.visibility != FieldVisibility.AlwaysHidden, + ) + .toList(), + ), + ); + }, + didReceiveCalculationsUpdate: (calculationsMap) async { + emit( + state.copyWith( + calculationsByFieldId: calculationsMap, + ), + ); + }, + updateCalculationType: (fieldId, type, calculationId) async { + await _calculationsService.updateCalculation( + fieldId, + type, + calculationId: calculationId, + ); + }, + removeCalculation: (fieldId, calculationId) async { + await _calculationsService.removeCalculation(fieldId, calculationId); + }, + ); + }); + } + + void _startListening() { + _fieldController.addListener( + listenWhen: () => !isClosed, + onReceiveFields: _onReceiveFields, + ); + + _calculationsListener.start( + onCalculationChanged: (changesetOrFailure) { + changesetOrFailure.fold( + (changeset) { + final calculationsMap = {...state.calculationsByFieldId}; + if (changeset.insertCalculations.isNotEmpty) { + for (final insert in changeset.insertCalculations) { + calculationsMap[insert.fieldId] = insert; + } + } + + if (changeset.deleteCalculations.isNotEmpty) { + for (final delete in changeset.deleteCalculations) { + calculationsMap.removeWhere((key, _) => key == delete.fieldId); + } + } + + add( + CalculationsEvent.didReceiveCalculationsUpdate( + calculationsMap, + ), + ); + }, + (_) => null, + ); + }, + ); + } + + void _onReceiveFields(List fields) => + add(CalculationsEvent.didReceiveFieldUpdate(fields)); + + Future _getAllCalculations() async { + final calculationsOrFailure = await _calculationsService.getCalculations(); + + final RepeatedCalculationsPB? calculations = + calculationsOrFailure.getLeftOrNull(); + if (calculations != null) { + final calculationMap = {}; + for (final calculation in calculations.items) { + calculationMap[calculation.fieldId] = calculation; + } + + add(CalculationsEvent.didReceiveCalculationsUpdate(calculationMap)); + } + } + + @override + Future close() async { + _fieldController.removeListener(onFieldsListener: _onReceiveFields); + super.close(); + } +} + +@freezed +class CalculationsEvent with _$CalculationsEvent { + const factory CalculationsEvent.started() = _Started; + + const factory CalculationsEvent.didReceiveFieldUpdate( + List fields, + ) = _DidReceiveFieldUpdate; + + const factory CalculationsEvent.didReceiveCalculationsUpdate( + Map calculationsByFieldId, + ) = _DidReceiveCalculationsUpdate; + + const factory CalculationsEvent.updateCalculationType( + String fieldId, + CalculationType type, { + @Default(null) String? calculationId, + }) = _UpdateCalculationType; + + const factory CalculationsEvent.removeCalculation( + String fieldId, + String calculationId, + ) = _RemoveCalculation; +} + +@freezed +class CalculationsState with _$CalculationsState { + const factory CalculationsState({ + required List fields, + required Map calculationsByFieldId, + }) = _CalculationsState; + + factory CalculationsState.initial() => const CalculationsState( + fields: [], + calculationsByFieldId: {}, + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart index 75a8b5bad77a..b97617489b16 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart @@ -1,32 +1,35 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart'; import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart'; import 'package:appflowy/plugins/database/widgets/row/cell_builder.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; import 'package:linked_scroll_controller/linked_scroll_controller.dart'; + +import '../../application/database_controller.dart'; import '../../application/row/row_cache.dart'; import '../../application/row/row_controller.dart'; +import '../../tab_bar/tab_bar_view.dart'; +import '../../widgets/row/row_detail.dart'; import '../application/grid_bloc.dart'; -import '../../application/database_controller.dart'; + import 'grid_scroll.dart'; -import '../../tab_bar/tab_bar_view.dart'; import 'layout/layout.dart'; import 'layout/sizes.dart'; -import 'widgets/row/row.dart'; import 'widgets/footer/grid_footer.dart'; import 'widgets/header/grid_header.dart'; -import '../../widgets/row/row_detail.dart'; +import 'widgets/row/row.dart'; import 'widgets/shortcuts.dart'; class ToggleExtensionNotifier extends ChangeNotifier { @@ -85,15 +88,15 @@ class DesktopGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder { } class GridPage extends StatefulWidget { - final DatabaseController databaseController; const GridPage({ + super.key, required this.view, required this.databaseController, this.onDeleted, - super.key, }); final ViewPB view; + final DatabaseController databaseController; final VoidCallback? onDeleted; @override @@ -101,11 +104,6 @@ class GridPage extends StatefulWidget { } class _GridPageState extends State { - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { return MultiBlocProvider( @@ -123,9 +121,7 @@ class _GridPageState extends State { loading: (_) => const Center(child: CircularProgressIndicator.adaptive()), finish: (result) => result.successOrFail.fold( - (_) => GridShortcuts( - child: GridPageContent(view: widget.view), - ), + (_) => GridShortcuts(child: GridPageContent(view: widget.view)), (err) => FlowyErrorPage.message( err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), @@ -140,12 +136,13 @@ class _GridPageState extends State { } class GridPageContent extends StatefulWidget { - final ViewPB view; const GridPageContent({ - required this.view, super.key, + required this.view, }); + final ViewPB view; + @override State createState() => _GridPageContentState(); } @@ -185,9 +182,10 @@ class _GridPageContentState extends State { } class _GridHeader extends StatelessWidget { - final ScrollController headerScrollController; const _GridHeader({required this.headerScrollController}); + final ScrollController headerScrollController; + @override Widget build(BuildContext context) { return BlocBuilder( @@ -257,7 +255,14 @@ class _GridRows extends StatelessWidget { index: index, ); }).toList() - ..add(const GridRowBottomBar(key: Key('gridFooter'))); + ..add(const GridRowBottomBar(key: Key('grid_footer'))) + ..add( + GridCalculationsRow( + key: const Key('grid_calculations'), + viewId: viewId, + ), + ); + return ReorderableListView.builder( /// This is a workaround related to /// https://github.com/flutter/flutter/issues/25652 @@ -275,7 +280,7 @@ class _GridRows extends StatelessWidget { } context.read().add(GridEvent.moveRow(fromIndex, toIndex)); }, - itemCount: rowInfos.length + 1, // the extra item is the footer + itemCount: children.length, itemBuilder: (context, index) => children[index], ); } @@ -381,9 +386,10 @@ class _GridFooter extends StatelessWidget { child: RichText( text: TextSpan( text: rowCountString(), - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).hintColor, - ), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Theme.of(context).hintColor), children: [ TextSpan( text: ' $rowCount', diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart new file mode 100644 index 000000000000..16e45312b181 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart'; +import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class GridCalculationsRow extends StatelessWidget { + final String viewId; + + const GridCalculationsRow({ + super.key, + required this.viewId, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CalculationsBloc( + viewId: context.read().databaseController.viewId, + fieldController: + context.read().databaseController.fieldController, + )..add(const CalculationsEvent.started()), + child: BlocBuilder( + builder: (context, state) { + return Padding( + padding: GridSize.contentInsets, + child: Row( + children: [ + ...state.fields.map( + (field) => CalculateCell( + key: Key( + '${field.id}-${state.calculationsByFieldId[field.id]?.id}', + ), + width: field.fieldSettings!.width.toDouble(), + fieldInfo: field, + calculation: state.calculationsByFieldId[field.id], + ), + ), + ], + ), + ); + }, + ), + ); + } +} + +class CalculateCell extends StatelessWidget { + const CalculateCell({ + super.key, + required this.fieldInfo, + required this.width, + this.calculation, + }); + + final FieldInfo fieldInfo; + final double width; + final CalculationPB? calculation; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 35, + width: width, + child: AppFlowyPopover( + constraints: BoxConstraints.loose(const Size(150, 200)), + direction: PopoverDirection.bottomWithCenterAligned, + popupBuilder: (_) => SingleChildScrollView( + child: Column( + children: [ + if (calculation != null) + RemoveCalculationButton( + onTap: () { + context.read().add( + CalculationsEvent.removeCalculation( + fieldInfo.id, + calculation!.id, + ), + ); + }, + ), + ...CalculationType.values.map( + (type) => CalculationTypeItem( + type: type, + onTap: () { + if (type != calculation?.calculationType) { + context.read().add( + CalculationsEvent.updateCalculationType( + fieldInfo.id, + type, + calculationId: calculation?.id, + ), + ); + } + }, + ), + ), + ], + ), + ), + child: fieldInfo.fieldType == FieldType.Number + ? calculation != null + ? _showCalculateValue(context) + : _showCalculateText(context) + : const SizedBox.shrink(), + ), + ); + } + + Widget _showCalculateText(BuildContext context) { + return FlowyButton( + radius: BorderRadius.zero, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: FlowyText( + 'Calculate', + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + const HSpace(8), + FlowySvg( + FlowySvgs.arrow_down_s, + color: Theme.of(context).hintColor, + ), + ], + ), + ); + } + + Widget _showCalculateValue(BuildContext context) { + return FlowyButton( + radius: BorderRadius.zero, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: FlowyText( + calculation!.calculationType.label, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + const HSpace(8), + calculation!.value.isEmpty + ? const SizedBox( + width: 12, + height: 12, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Flexible(child: FlowyText(calculation!.value)), + const HSpace(8), + FlowySvg( + FlowySvgs.arrow_down_s, + color: Theme.of(context).hintColor, + ), + ], + ), + ); + } +} + +class CalculationTypeItem extends StatelessWidget { + const CalculationTypeItem({ + super.key, + required this.type, + required this.onTap, + }); + + final CalculationType type; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + text: FlowyText.medium(type.label, overflow: TextOverflow.ellipsis), + onTap: () { + onTap(); + PopoverContainer.of(context).close(); + }, + ), + ); + } +} + +class RemoveCalculationButton extends StatelessWidget { + const RemoveCalculationButton({ + super.key, + required this.onTap, + }); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + text: const FlowyText.medium( + 'None', + overflow: TextOverflow.ellipsis, + ), + onTap: () { + onTap(); + PopoverContainer.of(context).close(); + }, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart index 0cdda12c7e6c..6d6840085e8e 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart @@ -1,13 +1,13 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; - import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GridAddRowButton extends StatelessWidget { @@ -16,6 +16,12 @@ class GridAddRowButton extends StatelessWidget { @override Widget build(BuildContext context) { return FlowyButton( + radius: BorderRadius.zero, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Theme.of(context).dividerColor), + ), + ), text: FlowyText( LocaleKeys.grid_row_newRow.tr(), color: Theme.of(context).hintColor, @@ -38,7 +44,7 @@ class GridRowBottomBar extends StatelessWidget { return Container( padding: GridSize.footerContentInsets, height: GridSize.footerHeight, - margin: const EdgeInsets.only(bottom: 8, top: 8), + // margin: const EdgeInsets.only(bottom: 8, top: 8), child: const GridAddRowButton(), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart index c599eaba760e..2239b39db76c 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart @@ -1,3 +1,6 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import "package:appflowy/generated/locale_keys.g.dart"; import 'package:appflowy/plugins/database/application/cell/cell_service.dart'; @@ -9,26 +12,16 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import '../../../../widgets/row/accessory/cell_accessory.dart'; import '../../../../widgets/row/cells/cell_container.dart'; import '../../layout/sizes.dart'; + import 'action.dart'; class GridRow extends StatefulWidget { - final RowId viewId; - final RowId rowId; - final RowController dataController; - final GridCellBuilder cellBuilder; - final void Function(BuildContext, GridCellBuilder) openDetailPage; - - final int? index; - final bool isDraggable; - const GridRow({ super.key, required this.viewId, @@ -40,6 +33,15 @@ class GridRow extends StatefulWidget { this.isDraggable = false, }); + final String viewId; + final RowId rowId; + final RowController dataController; + final GridCellBuilder cellBuilder; + final void Function(BuildContext, GridCellBuilder) openDetailPage; + + final int? index; + final bool isDraggable; + @override State createState() => _GridRowState(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart index 3c83045465cf..c3efeb144a9c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_detail_bloc.dart'; @@ -5,7 +7,6 @@ import 'package:appflowy/plugins/database/widgets/row/row_document.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; @@ -51,9 +52,7 @@ class _RowDetailPageState extends State { RowDetailBloc(rowController: widget.rowController) ..add(const RowDetailEvent.initial()), ), - BlocProvider.value( - value: getIt(), - ), + BlocProvider.value(value: getIt()), ], child: ListView( controller: scrollController, diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 95f17abd005f..760e924ad859 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -647,7 +647,14 @@ "showComplete": "Show all tasks" }, "menuName": "Grid", - "referencedGridPrefix": "View of" + "referencedGridPrefix": "View of", + "calculationTypeLabel": { + "average": "Average", + "max": "Max", + "median": "Median", + "min": "Min", + "sum": "Sum" + } }, "document": { "menuName": "Document", diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs new file mode 100644 index 000000000000..2594aa669197 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs @@ -0,0 +1,73 @@ +use flowy_derive::ProtoBuf; + +use super::{CalculationPB, CalculationType}; + +#[derive(Default, ProtoBuf)] +pub struct UpdateCalculationChangesetPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2, one_of)] + pub calculation_id: Option, + + #[pb(index = 3)] + pub field_id: String, + + #[pb(index = 4)] + pub calculation_type: CalculationType, +} + +#[derive(Default, ProtoBuf)] +pub struct RemoveCalculationChangesetPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub field_id: String, + + #[pb(index = 3)] + pub calculation_id: String, +} + +#[derive(Debug, Default, ProtoBuf)] +pub struct CalculationChangesetNotificationPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub insert_calculations: Vec, + + #[pb(index = 3)] + pub update_calculations: Vec, + + #[pb(index = 4)] + pub delete_calculations: Vec, +} + +impl CalculationChangesetNotificationPB { + pub fn from_insert(view_id: &str, calculations: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_calculations: calculations, + delete_calculations: Default::default(), + update_calculations: Default::default(), + } + } + pub fn from_delete(view_id: &str, calculations: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_calculations: Default::default(), + delete_calculations: calculations, + update_calculations: Default::default(), + } + } + + pub fn from_update(view_id: &str, calculations: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_calculations: Default::default(), + delete_calculations: Default::default(), + update_calculations: calculations, + } + } +} diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs similarity index 100% rename from frontend/rust-lib/flowy-database2/src/entities/calculation_entities.rs rename to frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation/mod.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/mod.rs new file mode 100644 index 000000000000..36668a022a27 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation/mod.rs @@ -0,0 +1,5 @@ +mod calculation_changeset; +mod calculation_entities; + +pub use calculation_changeset::*; +pub use calculation_entities::*; diff --git a/frontend/rust-lib/flowy-database2/src/entities/mod.rs b/frontend/rust-lib/flowy-database2/src/entities/mod.rs index 861eac07ebe4..63fd455832c9 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/mod.rs @@ -1,4 +1,5 @@ mod board_entities; +pub mod calculation; mod calendar_entities; mod cell_entities; mod database_entities; @@ -14,12 +15,12 @@ mod share_entities; mod sort_entities; mod type_option_entities; mod view_entities; -pub mod calculation_entities; #[macro_use] mod macros; pub use board_entities::*; +pub use calculation::*; pub use calendar_entities::*; pub use cell_entities::*; pub use database_entities::*; @@ -34,4 +35,3 @@ pub use share_entities::*; pub use sort_entities::*; pub use type_option_entities::*; pub use view_entities::*; -pub use calculation_entities::*; diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index eb447fc29e8e..db1b3e30283f 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -918,3 +918,45 @@ pub(crate) async fn update_field_settings_handler( .await?; Ok(()) } + +#[tracing::instrument(level = "debug", skip_all, err)] +pub(crate) async fn get_all_calculations_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> DataResult { + let manager = upgrade_manager(manager)?; + let view_id = data.into_inner(); + let database_editor = manager.get_database_with_view_id(view_id.as_ref()).await?; + + let calculations = database_editor.get_all_calculations(view_id.as_ref()).await; + + data_result_ok(calculations) +} + +#[tracing::instrument(level = "trace", skip(data, manager), err)] +pub(crate) async fn update_calculation_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> Result<(), FlowyError> { + let manager = upgrade_manager(manager)?; + let params: UpdateCalculationChangesetPB = data.into_inner(); + let editor = manager.get_database_with_view_id(¶ms.view_id).await?; + + editor.update_calculation(params).await?; + + Ok(()) +} + +#[tracing::instrument(level = "trace", skip(data, manager), err)] +pub(crate) async fn remove_calculation_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> Result<(), FlowyError> { + let manager = upgrade_manager(manager)?; + let params: RemoveCalculationChangesetPB = data.into_inner(); + let editor = manager.get_database_with_view_id(¶ms.view_id).await?; + + editor.remove_calculation(params).await?; + + Ok(()) +} diff --git a/frontend/rust-lib/flowy-database2/src/event_map.rs b/frontend/rust-lib/flowy-database2/src/event_map.rs index a7e2b60f4b8d..c2e7f108022f 100644 --- a/frontend/rust-lib/flowy-database2/src/event_map.rs +++ b/frontend/rust-lib/flowy-database2/src/event_map.rs @@ -79,6 +79,10 @@ pub fn init(database_manager: Weak) -> AFPlugin { .event(DatabaseEvent::GetFieldSettings, get_field_settings_handler) .event(DatabaseEvent::GetAllFieldSettings, get_all_field_settings_handler) .event(DatabaseEvent::UpdateFieldSettings, update_field_settings_handler) + // Calculations + .event(DatabaseEvent::GetAllCalculations, get_all_calculations_handler) + .event(DatabaseEvent::UpdateCalculation, update_calculation_handler) + .event(DatabaseEvent::RemoveCalculation, remove_calculation_handler) } /// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf) @@ -329,4 +333,13 @@ pub enum DatabaseEvent { /// Updates the field settings for a field in the given view #[event(input = "FieldSettingsChangesetPB")] UpdateFieldSettings = 162, + + #[event(input = "DatabaseViewIdPB", output = "RepeatedCalculationsPB")] + GetAllCalculations = 163, + + #[event(input = "UpdateCalculationChangesetPB")] + UpdateCalculation = 164, + + #[event(input = "RemoveCalculationChangesetPB")] + RemoveCalculation = 165, } diff --git a/frontend/rust-lib/flowy-database2/src/notification.rs b/frontend/rust-lib/flowy-database2/src/notification.rs index 5d7e7e1df44c..f4ed4c6acd85 100644 --- a/frontend/rust-lib/flowy-database2/src/notification.rs +++ b/frontend/rust-lib/flowy-database2/src/notification.rs @@ -50,6 +50,8 @@ pub enum DatabaseNotification { DidUpdateDatabaseSnapshotState = 85, // Trigger when the field setting is changed DidUpdateFieldSettings = 86, + // Trigger when Calculation changed + DidUpdateCalculation = 87, } impl std::convert::From for i32 { @@ -80,7 +82,8 @@ impl std::convert::From for DatabaseNotification { 82 => DatabaseNotification::DidUpdateDatabaseLayout, 83 => DatabaseNotification::DidDeleteDatabaseView, 84 => DatabaseNotification::DidMoveDatabaseViewToTrash, - 87 => DatabaseNotification::DidUpdateFieldSettings, + 86 => DatabaseNotification::DidUpdateFieldSettings, + 87 => DatabaseNotification::DidUpdateCalculation, _ => DatabaseNotification::Unknown, } } diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs index ed099c39567a..d406c88f0405 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/cache.rs @@ -3,4 +3,4 @@ use std::sync::Arc; use crate::utils::cache::AnyTypeCache; -pub type CalculationsCache = Arc>>; +pub type CalculationsByFieldIdCache = Arc>>; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index 751d6c286f4b..1a0550f724b0 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -1,26 +1,32 @@ use std::str::FromStr; use std::sync::Arc; -use collab_database::rows::RowCell; +use collab_database::rows::{RowCell, RowDetail, RowId}; use flowy_error::FlowyResult; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; -use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; use lib_infra::future::Fut; +use lib_infra::priority_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; -use crate::services::calculations::CalculationsCache; +use crate::entities::{CalculationChangesetNotificationPB, CalculationPB, CalculationType}; +use crate::services::calculations::CalculationsByFieldIdCache; use crate::services::database_view::DatabaseViewChangedNotifier; +use crate::utils::cache::AnyTypeCache; + +use super::{Calculation, CalculationChangeset}; pub trait CalculationsDelegate: Send + Sync + 'static { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; + + fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut)>>; } pub struct CalculationsController { view_id: String, handler_id: String, delegate: Box, - calculations_cache: CalculationsCache, + calculations_by_field_cache: CalculationsByFieldIdCache, task_scheduler: Arc>, notifier: DatabaseViewChangedNotifier, } @@ -36,8 +42,7 @@ impl CalculationsController { view_id: &str, handler_id: &str, delegate: T, - // calculations: Vec>, - calculations_cache: CalculationsCache, + calculations: Vec>, task_scheduler: Arc>, notifier: DatabaseViewChangedNotifier, ) -> Self @@ -48,10 +53,11 @@ impl CalculationsController { view_id: view_id.to_string(), handler_id: handler_id.to_string(), delegate: Box::new(delegate), - calculations_cache, + calculations_by_field_cache: AnyTypeCache::::new(), task_scheduler, notifier, }; + this.update_cache(calculations).await; this } @@ -59,7 +65,7 @@ impl CalculationsController { if let Ok(mut task_scheduler) = self.task_scheduler.try_write() { task_scheduler.unregister_handler(&self.handler_id).await; } else { - tracing::error!("Try to get the lock of task_scheduler failed"); + tracing::error!("Attempt to get the lock of task_scheduler failed"); } } @@ -85,19 +91,80 @@ impl CalculationsController { pub async fn process(&self, predicate: &str) -> FlowyResult<()> { let event_type = CalculationEvent::from_str(predicate).unwrap(); match event_type { - CalculationEvent::CellDidChange => self.update_calculations().await?, + CalculationEvent::RowDeleted(row_id) => self.update_calculation(row_id).await?, } Ok(()) } - async fn update_calculations(&self) -> FlowyResult<()> { - todo!("Update Calculations"); + pub async fn did_receive_row_changed(&self, _row_id: RowId) { + todo!("RowDeleted / CellChanged") + // let row = self.delegate.get_row(&self.view_id, &row_id.clone()).await; + + // self + // .gen_task( + // CalculationEvent::RowDeleted(row_id.into_inner()), + // QualityOfService::UserInteractive, + // ) + // .await + } + + pub async fn did_receive_changes( + &self, + changeset: CalculationChangeset, + ) -> Option { + let mut notification: Option = None; + + if let Some(insert) = &changeset.insert_calculation { + let row_cells: Vec> = self + .delegate + .get_cells_for_field(&self.view_id, &insert.field_id) + .await; + + let value = insert.calculate(row_cells); + notification = Some(CalculationChangesetNotificationPB::from_insert( + &self.view_id, + vec![CalculationPB { + id: insert.id.clone(), + field_id: insert.field_id.clone(), + calculation_type: CalculationType::from(insert.calculation_type), + value, + }], + )) + } + + if let Some(delete) = &changeset.delete_calculation { + notification = Some(CalculationChangesetNotificationPB::from_delete( + &self.view_id, + vec![CalculationPB { + id: delete.id.clone(), + field_id: delete.field_id.clone(), + calculation_type: CalculationType::from(delete.calculation_type), + value: delete.value.clone(), + }], + )) + } + + notification + } + + async fn update_cache(&self, calculations: Vec>) { + for calculation in calculations { + let field_id = &calculation.field_id; + self + .calculations_by_field_cache + .write() + .insert(field_id, calculation.clone()); + } + } + + async fn update_calculation(&self, _row_id: String) -> FlowyResult<()> { + todo!("update_calculation"); } } #[derive(Serialize, Deserialize, Clone, Debug)] enum CalculationEvent { - CellDidChange, + RowDeleted(String), } impl ToString for CalculationEvent { diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index 3561fc1e7b30..5e247dac94bc 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -1,6 +1,13 @@ +use std::sync::Arc; + use anyhow::bail; use collab::core::any_map::AnyMapExtension; -use collab_database::views::{CalculationMap, CalculationMapBuilder}; +use collab_database::{ + rows::RowCell, + views::{CalculationMap, CalculationMapBuilder}, +}; + +use crate::entities::{CalculationPB, CalculationType}; #[derive(Debug, Clone)] pub struct Calculation { @@ -26,6 +33,19 @@ impl From for CalculationMap { } } +impl std::convert::From<&CalculationPB> for Calculation { + fn from(calculation: &CalculationPB) -> Self { + let calculation_type = calculation.calculation_type.into(); + + Self { + id: calculation.id.clone(), + field_id: calculation.field_id.clone(), + calculation_type, + value: calculation.value.clone(), + } + } +} + impl TryFrom for Calculation { type Error = anyhow::Error; @@ -70,3 +90,160 @@ impl CalculationsResultNotification { } } } + +impl Calculation { + pub fn calculate(&self, row_cells: Vec>) -> String { + let ty: CalculationType = self.calculation_type.into(); + + match ty { + CalculationType::Average => self.calculate_average(row_cells), + CalculationType::Max => self.calculate_max(row_cells), + CalculationType::Median => self.calculate_median(row_cells), + CalculationType::Min => self.calculate_min(row_cells), + CalculationType::Sum => self.calculate_sum(row_cells), + } + } + + fn calculate_average(&self, row_cells: Vec>) -> String { + let mut sum = 0; + let mut len = 0; + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => { + sum += value; + len += 1; + }, + _ => tracing::info!("Failed to parse ({}) to i32", data), + } + } + } + } + + if len > 0 { + (sum / len).to_string().to_owned() + } else { + "0".to_owned() + } + } + + fn calculate_median(&self, row_cells: Vec>) -> String { + let mut values = vec![]; + + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => values.push(value), + _ => tracing::info!("Failed to parse ({}) to i32", data), + } + } + } + } + + values.sort(); + Self::median(&values).to_string() + } + + fn median(array: &Vec) -> f64 { + if (array.len() % 2) == 0 { + let left = array.len() / 2 - 1; + let right = array.len() / 2; + (array[left] + array[right]) as f64 / 2.0 + } else { + array[array.len() / 2] as f64 + } + } + + fn calculate_min(&self, row_cells: Vec>) -> String { + let mut first_match = true; + let mut min: i32 = 0; + + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => { + if first_match { + min = value; + first_match = true; + } else { + if value < min { + min = value; + } + } + }, + _ => tracing::info!("Failed to parse ({}) to i32", data), + } + } + } + } + + min.to_string() + } + + fn calculate_max(&self, row_cells: Vec>) -> String { + let mut max = 0; + + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => { + if value > max { + max = value; + } + }, + _ => tracing::info!("Failed to parse ({}) to i32", data), + } + } + } + } + + max.to_string() + } + + fn calculate_sum(&self, row_cells: Vec>) -> String { + let mut sum = 0; + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => sum += value, + _ => tracing::info!("Failed to parse ({}) to i32", data), + } + } + } + } + + sum.to_string() + } +} + +#[derive(Debug)] +pub struct CalculationChangeset { + pub(crate) insert_calculation: Option, + pub(crate) delete_calculation: Option, +} + +impl CalculationChangeset { + pub fn from_insert(calculation: Calculation) -> Self { + Self { + insert_calculation: Some(calculation), + delete_calculation: None, + } + } + + pub fn from_delete(calculation: Calculation) -> Self { + Self { + insert_calculation: None, + delete_calculation: Some(calculation), + } + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs index 5687fffc1f46..b8ae249c4bb5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/task.rs @@ -1,5 +1,5 @@ -use flowy_task::{TaskContent, TaskHandler}; use lib_infra::future::BoxResultFuture; +use lib_infra::priority_task::{TaskContent, TaskHandler}; use std::sync::Arc; use crate::services::calculations::CalculationsController; diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index d327c9369ee7..1415a5f1751a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -234,6 +234,18 @@ impl DatabaseEditor { } } + pub async fn update_calculation(&self, update: UpdateCalculationChangesetPB) -> FlowyResult<()> { + let view_editor = self.database_views.get_view_editor(&update.view_id).await?; + view_editor.v_update_calculations(update).await?; + Ok(()) + } + + pub async fn remove_calculation(&self, remove: RemoveCalculationChangesetPB) -> FlowyResult<()> { + let view_editor = self.database_views.get_view_editor(&remove.view_id).await?; + view_editor.v_remove_calculation(remove).await?; + Ok(()) + } + pub async fn get_all_filters(&self, view_id: &str) -> RepeatedFilterPB { if let Ok(view_editor) = self.database_views.get_view_editor(view_id).await { view_editor.v_get_all_filters().await.into() @@ -1587,6 +1599,17 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { .payload(FieldSettingsPB::from(new_field_settings)) .send() } + + fn update_calculation(&self, view_id: &str, calculation: Calculation) { + self + .database + .lock() + .update_calculation(view_id, calculation) + } + + fn remove_calculation(&self, view_id: &str, field_id: &str) { + self.database.lock().remove_calculation(view_id, field_id) + } } #[tracing::instrument(level = "trace", skip_all, err)] diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs index 173be64d96b9..2778f4740221 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs @@ -1,7 +1,8 @@ #![allow(clippy::while_let_loop)] use crate::entities::{ - DatabaseViewSettingPB, FilterChangesetNotificationPB, GroupChangesPB, GroupRowsNotificationPB, - ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangePB, SortChangesetNotificationPB, + CalculationChangesetNotificationPB, DatabaseViewSettingPB, FilterChangesetNotificationPB, + GroupChangesPB, GroupRowsNotificationPB, ReorderAllRowsPB, ReorderSingleRowPB, + RowsVisibilityChangePB, SortChangesetNotificationPB, }; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::filter::FilterResultNotification; @@ -94,6 +95,15 @@ pub async fn notify_did_update_filter(notification: FilterChangesetNotificationP .send(); } +pub async fn notify_did_update_calculation(notification: CalculationChangesetNotificationPB) { + send_notification( + ¬ification.view_id, + DatabaseNotification::DidUpdateCalculation, + ) + .payload(notification) + .send(); +} + pub async fn notify_did_update_sort(notification: SortChangesetNotificationPB) { if !notification.is_empty() { send_notification(¬ification.view_id, DatabaseNotification::DidUpdateSort) diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs index 5287c92fadea..9eb39dfe30da 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs @@ -1,12 +1,12 @@ use std::sync::Arc; -use collab_database::rows::RowCell; +use collab_database::rows::{RowCell, RowDetail, RowId}; use lib_infra::future::Fut; use crate::services::calculations::{ CalculationsController, CalculationsDelegate, CalculationsTaskHandler, }; -use crate::services::cell::CellCache; + use crate::services::database_view::{ gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation, }; @@ -15,9 +15,8 @@ pub async fn make_calculations_controller( view_id: &str, delegate: Arc, notifier: DatabaseViewChangedNotifier, - cell_cache: CellCache, ) -> Arc { - // let calculations = delegate.get_all_calculations(view_id); + let calculations = delegate.get_all_calculations(view_id); let task_scheduler = delegate.get_task_scheduler(); let calculations_delegate = DatabaseViewCalculationsDelegateImpl(delegate.clone()); let handler_id = gen_handler_id(); @@ -26,8 +25,7 @@ pub async fn make_calculations_controller( view_id, &handler_id, calculations_delegate, - // calculations, - cell_cache, + calculations, task_scheduler.clone(), notifier, ) @@ -50,4 +48,8 @@ impl CalculationsDelegate for DatabaseViewCalculationsDelegateImpl { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>> { self.0.get_cells_for_field(view_id, field_id) } + + fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut)>> { + self.0.get_row(view_id, row_id) + } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index da61f67a28b5..41c33fca7ba0 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -2,7 +2,9 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; -use collab_database::database::{gen_database_filter_id, gen_database_sort_id}; +use collab_database::database::{ + gen_database_calculation_id, gen_database_filter_id, gen_database_sort_id, +}; use collab_database::fields::{Field, TypeOptionData}; use collab_database::rows::{Cells, Row, RowDetail, RowId}; use collab_database::views::{DatabaseLayout, DatabaseView}; @@ -15,11 +17,12 @@ use lib_dispatch::prelude::af_spawn; use crate::entities::{ CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams, DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB, - LayoutSettingChangeset, LayoutSettingParams, RowMetaPB, RowsChangePB, - SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams, + LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, RowMetaPB, + RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, + UpdateFilterParams, UpdateSortParams, }; use crate::notification::{send_notification, DatabaseNotification}; -use crate::services::calculations::{Calculation, CalculationsController}; +use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController}; use crate::services::cell::CellCache; use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow}; use crate::services::database_view::view_filter::make_filter_controller; @@ -41,6 +44,7 @@ use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowConte use crate::services::setting::CalendarLayoutSetting; use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType}; +use super::notify_did_update_calculation; use super::view_calculations::make_calculations_controller; pub struct DatabaseViewEditor { @@ -48,8 +52,8 @@ pub struct DatabaseViewEditor { delegate: Arc, group_controller: Arc>>>, filter_controller: Arc, - calculations_controller: Arc, sort_controller: Arc>, + calculations_controller: Arc, pub notifier: DatabaseViewChangedNotifier, } @@ -92,13 +96,8 @@ impl DatabaseViewEditor { .await; // Calculations - let calculations_controller = make_calculations_controller( - &view_id, - delegate.clone(), - notifier.clone(), - cell_cache.clone(), - ) - .await; + let calculations_controller = + make_calculations_controller(&view_id, delegate.clone(), notifier.clone()).await; Ok(Self { view_id, @@ -226,11 +225,12 @@ impl DatabaseViewEditor { } } - // Each row update will trigger a filter and sort operation. We don't want + // Each row update will trigger a calculations, filter and sort operation. We don't want // to block the main thread, so we spawn a new task to do the work. let row_id = row_detail.row.id.clone(); let weak_filter_controller = Arc::downgrade(&self.filter_controller); let weak_sort_controller = Arc::downgrade(&self.sort_controller); + let weak_calculations_controller = Arc::downgrade(&self.calculations_controller); af_spawn(async move { if let Some(filter_controller) = weak_filter_controller.upgrade() { filter_controller @@ -241,6 +241,13 @@ impl DatabaseViewEditor { sort_controller .read() .await + .did_receive_row_changed(row_id.clone()) + .await; + } + + // If it was a Row deletion + if let Some(calculations_controller) = weak_calculations_controller.upgrade() { + calculations_controller .did_receive_row_changed(row_id) .await; } @@ -527,6 +534,74 @@ impl DatabaseViewEditor { self.delegate.get_all_calculations(&self.view_id) } + pub async fn v_update_calculations( + &self, + params: UpdateCalculationChangesetPB, + ) -> FlowyResult<()> { + let calculation_id = match params.calculation_id { + None => gen_database_calculation_id(), + Some(calculation_id) => calculation_id, + }; + + let calculation = Calculation { + id: calculation_id, + field_id: params.field_id, + calculation_type: params.calculation_type.value(), + value: "".to_owned(), // Empty for non-calculated + }; + + let calculations_controller = self.calculations_controller.clone(); + let changeset = calculations_controller + .did_receive_changes(CalculationChangeset::from_insert(calculation.clone())) + .await; + + if let Some(changeset) = changeset { + if !changeset.insert_calculations.is_empty() { + for insert in changeset.insert_calculations.clone() { + let calculation: Calculation = Calculation::from(&insert); + self + .delegate + .update_calculation(¶ms.view_id, calculation); + } + } + + notify_did_update_calculation(changeset).await; + } + + drop(calculations_controller); + + Ok(()) + } + + pub async fn v_remove_calculation( + &self, + params: RemoveCalculationChangesetPB, + ) -> FlowyResult<()> { + self + .delegate + .remove_calculation(¶ms.view_id, ¶ms.calculation_id); + + let calculation = Calculation { + id: params.calculation_id.clone(), + field_id: params.field_id.clone(), + calculation_type: 0, + value: "".to_owned(), + }; + + let calculations_controller = self.calculations_controller.clone(); + let changeset = calculations_controller + .did_receive_changes(CalculationChangeset::from_delete(calculation.clone())) + .await; + + drop(calculations_controller); + + if let Some(changeset) = changeset { + notify_did_update_calculation(changeset).await; + } + + Ok(()) + } + pub async fn v_get_all_filters(&self) -> Vec> { self.delegate.get_all_filters(&self.view_id) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs index 040635d89400..06b125ec6c18 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs @@ -83,6 +83,10 @@ pub trait DatabaseViewOperation: Send + Sync + 'static { fn get_all_calculations(&self, view_id: &str) -> Vec>; + fn update_calculation(&self, view_id: &str, calculation: Calculation); + + fn remove_calculation(&self, view_id: &str, calculation_id: &str); + fn get_all_filters(&self, view_id: &str) -> Vec>; fn delete_filter(&self, view_id: &str, filter_id: &str); diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/views.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/views.rs index 8859422b9c11..005cb48443a1 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/views.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/views.rs @@ -17,6 +17,7 @@ use crate::services::group::RowChangeset; pub type RowEventSender = broadcast::Sender; pub type RowEventReceiver = broadcast::Receiver; pub type EditorByViewId = HashMap>; + pub struct DatabaseViews { #[allow(dead_code)] database: Arc, From a8d8d3feaeb93b2f0fb84e817ce48f76c4ee768d Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Wed, 24 Jan 2024 13:55:43 +0100 Subject: [PATCH 04/24] fix: review comments and support floats --- .../src/services/calculations/entities.rs | 135 +++++++++--------- .../src/services/database_view/view_editor.rs | 21 +-- 2 files changed, 73 insertions(+), 83 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index 5e247dac94bc..be1b8d481155 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -27,7 +27,7 @@ impl From for CalculationMap { CalculationMapBuilder::new() .insert_str_value(CALCULATION_ID, data.id) .insert_str_value(FIELD_ID, data.field_id) - .insert_i64_value(CALCULATION_TYPE, data.calculation_type.into()) + .insert_i64_value(CALCULATION_TYPE, data.calculation_type) .insert_str_value(CALCULATION_VALUE, data.value) .build() } @@ -92,6 +92,15 @@ impl CalculationsResultNotification { } impl Calculation { + pub fn none(id: String, field_id: String) -> Self { + Self { + id, + field_id, + calculation_type: 0, + value: "".to_owned(), + } + } + pub fn calculate(&self, row_cells: Vec>) -> String { let ty: CalculationType = self.calculation_type.into(); @@ -105,124 +114,114 @@ impl Calculation { } fn calculate_average(&self, row_cells: Vec>) -> String { - let mut sum = 0; - let mut len = 0; + let mut sum = 0.0; + let mut len = 0.0; for row_cell in row_cells { if let Some(cell) = &row_cell.cell { let data = cell.get("data"); if let Some(data) = data { - match data.to_string().parse::() { + match data.to_string().parse::() { Ok(value) => { sum += value; - len += 1; + len += 1.0; }, - _ => tracing::info!("Failed to parse ({}) to i32", data), + _ => tracing::info!("Failed to parse ({}) to f64", data), } } } } - if len > 0 { - (sum / len).to_string().to_owned() + if len > 0.0 { + format!("{:.5}", sum / len) } else { "0".to_owned() } } fn calculate_median(&self, row_cells: Vec>) -> String { - let mut values = vec![]; + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => values.push(value), - _ => tracing::info!("Failed to parse ({}) to i32", data), - } - } - } + if !values.is_empty() { + Self::median(&values).to_string() + } else { + "".to_owned() } - - values.sort(); - Self::median(&values).to_string() } - fn median(array: &Vec) -> f64 { + fn median(array: &Vec) -> f64 { if (array.len() % 2) == 0 { let left = array.len() / 2 - 1; let right = array.len() / 2; - (array[left] + array[right]) as f64 / 2.0 + (array[left] + array[right]) / 2.0 } else { - array[array.len() / 2] as f64 + array[array.len() / 2] } } fn calculate_min(&self, row_cells: Vec>) -> String { - let mut first_match = true; - let mut min: i32 = 0; - - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => { - if first_match { - min = value; - first_match = true; - } else { - if value < min { - min = value; - } - } - }, - _ => tracing::info!("Failed to parse ({}) to i32", data), - } - } + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); + + if !values.is_empty() { + let min = values.iter().min_by(|a, b| a.total_cmp(b)); + if let Some(min) = min { + return min.to_string(); } } - min.to_string() + "".to_owned() } fn calculate_max(&self, row_cells: Vec>) -> String { - let mut max = 0; - - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => { - if value > max { - max = value; - } - }, - _ => tracing::info!("Failed to parse ({}) to i32", data), - } - } + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); + + if !values.is_empty() { + let max = values.iter().max_by(|a, b| a.total_cmp(b)); + if let Some(max) = max { + return max.to_string(); } } - max.to_string() + "".to_owned() } fn calculate_sum(&self, row_cells: Vec>) -> String { - let mut sum = 0; + let values = self.reduce_values_f64(row_cells, |values| values.clone()); + + if !values.is_empty() { + values.iter().sum::().to_string() + } else { + "".to_owned() + } + } + + fn reduce_values_f64(&self, row_cells: Vec>, f: F) -> T + where + F: FnOnce(&mut Vec) -> T, + { + let mut values = vec![]; + for row_cell in row_cells { if let Some(cell) = &row_cell.cell { let data = cell.get("data"); if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => sum += value, - _ => tracing::info!("Failed to parse ({}) to i32", data), + match data.to_string().parse::() { + Ok(value) => values.push(value), + _ => tracing::info!("Failed to parse ({}) to f64", data), } } } } - sum.to_string() + f(&mut values) } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 41c33fca7ba0..869a8e3fd6bb 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -91,7 +91,7 @@ impl DatabaseViewEditor { delegate.clone(), notifier.clone(), filter_controller.clone(), - cell_cache.clone(), + cell_cache, ) .await; @@ -550,8 +550,8 @@ impl DatabaseViewEditor { value: "".to_owned(), // Empty for non-calculated }; - let calculations_controller = self.calculations_controller.clone(); - let changeset = calculations_controller + let changeset = self + .calculations_controller .did_receive_changes(CalculationChangeset::from_insert(calculation.clone())) .await; @@ -568,8 +568,6 @@ impl DatabaseViewEditor { notify_did_update_calculation(changeset).await; } - drop(calculations_controller); - Ok(()) } @@ -581,20 +579,13 @@ impl DatabaseViewEditor { .delegate .remove_calculation(¶ms.view_id, ¶ms.calculation_id); - let calculation = Calculation { - id: params.calculation_id.clone(), - field_id: params.field_id.clone(), - calculation_type: 0, - value: "".to_owned(), - }; + let calculation = Calculation::none(params.calculation_id, params.field_id); - let calculations_controller = self.calculations_controller.clone(); - let changeset = calculations_controller + let changeset = self + .calculations_controller .did_receive_changes(CalculationChangeset::from_delete(calculation.clone())) .await; - drop(calculations_controller); - if let Some(changeset) = changeset { notify_did_update_calculation(changeset).await; } From 1c7be2752f6d3729edb702d93838dbe682487c40 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 25 Jan 2024 16:01:43 +0100 Subject: [PATCH 05/24] fix: abstract business logic into calculations service --- .../src/services/calculations/controller.rs | 9 +- .../src/services/calculations/entities.rs | 4 +- .../src/services/calculations/mod.rs | 2 + .../src/services/calculations/service.rs | 136 ++++++++++++++++++ .../src/services/database_view/view_editor.rs | 13 +- 5 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 frontend/rust-lib/flowy-database2/src/services/calculations/service.rs diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index 1a0550f724b0..599dde770c6c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -14,7 +14,7 @@ use crate::services::calculations::CalculationsByFieldIdCache; use crate::services::database_view::DatabaseViewChangedNotifier; use crate::utils::cache::AnyTypeCache; -use super::{Calculation, CalculationChangeset}; +use super::{Calculation, CalculationChangeset, CalculationsService}; pub trait CalculationsDelegate: Send + Sync + 'static { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; @@ -28,6 +28,7 @@ pub struct CalculationsController { delegate: Box, calculations_by_field_cache: CalculationsByFieldIdCache, task_scheduler: Arc>, + calculations_service: CalculationsService, notifier: DatabaseViewChangedNotifier, } @@ -55,6 +56,7 @@ impl CalculationsController { delegate: Box::new(delegate), calculations_by_field_cache: AnyTypeCache::::new(), task_scheduler, + calculations_service: CalculationsService::new(), notifier, }; this.update_cache(calculations).await; @@ -120,7 +122,10 @@ impl CalculationsController { .get_cells_for_field(&self.view_id, &insert.field_id) .await; - let value = insert.calculate(row_cells); + let value = self + .calculations_service + .calculate(insert.calculation_type, row_cells); + notification = Some(CalculationChangesetNotificationPB::from_insert( &self.view_id, vec![CalculationPB { diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index be1b8d481155..1858c3da5587 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -92,11 +92,11 @@ impl CalculationsResultNotification { } impl Calculation { - pub fn none(id: String, field_id: String) -> Self { + pub fn none(id: String, field_id: String, calculation_type: Option) -> Self { Self { id, field_id, - calculation_type: 0, + calculation_type: calculation_type.unwrap_or(0), value: "".to_owned(), } } diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs index 214bcbb8b7b9..4e6d9ac2543e 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/mod.rs @@ -1,9 +1,11 @@ mod cache; mod controller; mod entities; +mod service; mod task; pub(crate) use cache::*; pub use controller::*; pub use entities::*; +pub(crate) use service::*; pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs new file mode 100644 index 000000000000..894f3936876e --- /dev/null +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -0,0 +1,136 @@ +use std::sync::Arc; + +use collab_database::rows::RowCell; + +use crate::entities::CalculationType; + +pub struct CalculationsService {} + +impl CalculationsService { + pub fn new() -> Self { + Self {} + } + + pub fn calculate(&self, calculation_type: i64, row_cells: Vec>) -> String { + let ty: CalculationType = calculation_type.into(); + + match ty { + CalculationType::Average => self.calculate_average(row_cells), + CalculationType::Max => self.calculate_max(row_cells), + CalculationType::Median => self.calculate_median(row_cells), + CalculationType::Min => self.calculate_min(row_cells), + CalculationType::Sum => self.calculate_sum(row_cells), + } + } + + fn calculate_average(&self, row_cells: Vec>) -> String { + let mut sum = 0.0; + let mut len = 0.0; + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => { + sum += value; + len += 1.0; + }, + _ => tracing::info!("Failed to parse ({}) to f64", data), + } + } + } + } + + if len > 0.0 { + format!("{:.5}", sum / len) + } else { + "0".to_owned() + } + } + + fn calculate_median(&self, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); + + if !values.is_empty() { + Self::median(&values).to_string() + } else { + "".to_owned() + } + } + + fn median(array: &Vec) -> f64 { + if (array.len() % 2) == 0 { + let left = array.len() / 2 - 1; + let right = array.len() / 2; + (array[left] + array[right]) / 2.0 + } else { + array[array.len() / 2] + } + } + + fn calculate_min(&self, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); + + if !values.is_empty() { + let min = values.iter().min_by(|a, b| a.total_cmp(b)); + if let Some(min) = min { + return min.to_string(); + } + } + + "".to_owned() + } + + fn calculate_max(&self, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(row_cells, |values| { + values.sort_by(|a, b| a.partial_cmp(b).unwrap()); + values.clone() + }); + + if !values.is_empty() { + let max = values.iter().max_by(|a, b| a.total_cmp(b)); + if let Some(max) = max { + return max.to_string(); + } + } + + "".to_owned() + } + + fn calculate_sum(&self, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(row_cells, |values| values.clone()); + + if !values.is_empty() { + values.iter().sum::().to_string() + } else { + "".to_owned() + } + } + + fn reduce_values_f64(&self, row_cells: Vec>, f: F) -> T + where + F: FnOnce(&mut Vec) -> T, + { + let mut values = vec![]; + + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + let data = cell.get("data"); + if let Some(data) = data { + match data.to_string().parse::() { + Ok(value) => values.push(value), + _ => tracing::info!("Failed to parse ({}) to f64", data), + } + } + } + } + + f(&mut values) + } +} diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 869a8e3fd6bb..f30aeb764e4f 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -543,12 +543,11 @@ impl DatabaseViewEditor { Some(calculation_id) => calculation_id, }; - let calculation = Calculation { - id: calculation_id, - field_id: params.field_id, - calculation_type: params.calculation_type.value(), - value: "".to_owned(), // Empty for non-calculated - }; + let calculation = Calculation::none( + calculation_id, + params.field_id, + Some(params.calculation_type.value()), + ); let changeset = self .calculations_controller @@ -579,7 +578,7 @@ impl DatabaseViewEditor { .delegate .remove_calculation(¶ms.view_id, ¶ms.calculation_id); - let calculation = Calculation::none(params.calculation_id, params.field_id); + let calculation = Calculation::none(params.calculation_id, params.field_id, None); let changeset = self .calculations_controller From 64cb71e3f4c4e13e90a03719da8743249485fd20 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 25 Jan 2024 16:05:49 +0100 Subject: [PATCH 06/24] fix: clean calculation entities after merge --- .../src/services/calculations/entities.rs | 123 ------------------ 1 file changed, 123 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index 1858c3da5587..a66aa74c1a81 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -100,129 +100,6 @@ impl Calculation { value: "".to_owned(), } } - - pub fn calculate(&self, row_cells: Vec>) -> String { - let ty: CalculationType = self.calculation_type.into(); - - match ty { - CalculationType::Average => self.calculate_average(row_cells), - CalculationType::Max => self.calculate_max(row_cells), - CalculationType::Median => self.calculate_median(row_cells), - CalculationType::Min => self.calculate_min(row_cells), - CalculationType::Sum => self.calculate_sum(row_cells), - } - } - - fn calculate_average(&self, row_cells: Vec>) -> String { - let mut sum = 0.0; - let mut len = 0.0; - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => { - sum += value; - len += 1.0; - }, - _ => tracing::info!("Failed to parse ({}) to f64", data), - } - } - } - } - - if len > 0.0 { - format!("{:.5}", sum / len) - } else { - "0".to_owned() - } - } - - fn calculate_median(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { - values.sort_by(|a, b| a.partial_cmp(b).unwrap()); - values.clone() - }); - - if !values.is_empty() { - Self::median(&values).to_string() - } else { - "".to_owned() - } - } - - fn median(array: &Vec) -> f64 { - if (array.len() % 2) == 0 { - let left = array.len() / 2 - 1; - let right = array.len() / 2; - (array[left] + array[right]) / 2.0 - } else { - array[array.len() / 2] - } - } - - fn calculate_min(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { - values.sort_by(|a, b| a.partial_cmp(b).unwrap()); - values.clone() - }); - - if !values.is_empty() { - let min = values.iter().min_by(|a, b| a.total_cmp(b)); - if let Some(min) = min { - return min.to_string(); - } - } - - "".to_owned() - } - - fn calculate_max(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { - values.sort_by(|a, b| a.partial_cmp(b).unwrap()); - values.clone() - }); - - if !values.is_empty() { - let max = values.iter().max_by(|a, b| a.total_cmp(b)); - if let Some(max) = max { - return max.to_string(); - } - } - - "".to_owned() - } - - fn calculate_sum(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| values.clone()); - - if !values.is_empty() { - values.iter().sum::().to_string() - } else { - "".to_owned() - } - } - - fn reduce_values_f64(&self, row_cells: Vec>, f: F) -> T - where - F: FnOnce(&mut Vec) -> T, - { - let mut values = vec![]; - - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => values.push(value), - _ => tracing::info!("Failed to parse ({}) to f64", data), - } - } - } - } - - f(&mut values) - } } #[derive(Debug)] From 7c20f14fb0832bdfb04155adb861e1fa036556ef Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 25 Jan 2024 22:22:26 +0100 Subject: [PATCH 07/24] feat: react to changes to row/cell/field_type --- .../calculations/calculations_bloc.dart | 33 ++-- .../calculations/calculations_row.dart | 17 +- .../calculation/calculation_changeset.rs | 2 +- .../calculation/calculation_entities.rs | 13 ++ .../src/services/calculations/controller.rs | 172 ++++++++++++++++-- .../src/services/calculations/entities.rs | 29 +-- .../src/services/database/database_editor.rs | 15 +- .../src/services/database_view/notifier.rs | 7 + .../database_view/view_calculations.rs | 19 +- .../src/services/database_view/view_editor.rs | 31 +++- .../services/database_view/view_operation.rs | 2 + 11 files changed, 279 insertions(+), 61 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart index d5a63fbea27c..e05fc9d53bfb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart @@ -11,11 +11,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'calculations_bloc.freezed.dart'; class CalculationsBloc extends Bloc { - final String viewId; - final FieldController _fieldController; - final CalculationsListener _calculationsListener; - late final CalculationsBackendService _calculationsService; - CalculationsBloc({ required this.viewId, required FieldController fieldController, @@ -23,6 +18,21 @@ class CalculationsBloc extends Bloc { _calculationsListener = CalculationsListener(viewId: viewId), _calculationsService = CalculationsBackendService(viewId: viewId), super(CalculationsState.initial()) { + _dispatch(); + } + + final String viewId; + final FieldController _fieldController; + final CalculationsListener _calculationsListener; + late final CalculationsBackendService _calculationsService; + + @override + Future close() async { + _fieldController.removeListener(onFieldsListener: _onReceiveFields); + super.close(); + } + + void _dispatch() { on((event, emit) async { await event.when( started: () async { @@ -86,6 +96,13 @@ class CalculationsBloc extends Bloc { } } + if (changeset.updateCalculations.isNotEmpty) { + for (final update in changeset.updateCalculations) { + calculationsMap.removeWhere((key, _) => key == update.fieldId); + calculationsMap.addAll({update.fieldId: update}); + } + } + if (changeset.deleteCalculations.isNotEmpty) { for (final delete in changeset.deleteCalculations) { calculationsMap.removeWhere((key, _) => key == delete.fieldId); @@ -121,12 +138,6 @@ class CalculationsBloc extends Bloc { add(CalculationsEvent.didReceiveCalculationsUpdate(calculationMap)); } } - - @override - Future close() async { - _fieldController.removeListener(onFieldsListener: _onReceiveFields); - super.close(); - } } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart index 16e45312b181..54dfed924902 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart @@ -156,14 +156,15 @@ class CalculateCell extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - const HSpace(8), - calculation!.value.isEmpty - ? const SizedBox( - width: 12, - height: 12, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Flexible(child: FlowyText(calculation!.value)), + if (calculation!.value.isNotEmpty) ...[ + const HSpace(8), + Flexible( + child: FlowyText( + calculation!.value, + color: AFThemeExtension.of(context).textColor, + ), + ), + ], const HSpace(8), FlowySvg( FlowySvgs.arrow_down_s, diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs index 2594aa669197..f0292ffc6517 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs @@ -29,7 +29,7 @@ pub struct RemoveCalculationChangesetPB { pub calculation_id: String, } -#[derive(Debug, Default, ProtoBuf)] +#[derive(Debug, Default, ProtoBuf, Clone)] pub struct CalculationChangesetNotificationPB { #[pb(index = 1)] pub view_id: String, diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs index 584c26a667a4..479156bf6a21 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_entities.rs @@ -36,6 +36,19 @@ impl std::convert::From<&Calculation> for CalculationPB { } } +impl std::convert::From<&Arc> for CalculationPB { + fn from(calculation: &Arc) -> Self { + let calculation_type = calculation.calculation_type.into(); + + Self { + id: calculation.id.clone(), + field_id: calculation.field_id.clone(), + calculation_type, + value: calculation.value.clone(), + } + } +} + #[derive( Default, Debug, Copy, Clone, PartialEq, Hash, Eq, ProtoBuf_Enum, Serialize_repr, Deserialize_repr, )] diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index 599dde770c6c..17b09368dec2 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -1,7 +1,8 @@ use std::str::FromStr; use std::sync::Arc; -use collab_database::rows::{RowCell, RowDetail, RowId}; +use collab::core::any_map::AnyMapExtension; +use collab_database::rows::{Row, RowCell}; use flowy_error::FlowyResult; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; @@ -9,17 +10,20 @@ use tokio::sync::RwLock; use lib_infra::future::Fut; use lib_infra::priority_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; -use crate::entities::{CalculationChangesetNotificationPB, CalculationPB, CalculationType}; +use crate::entities::{ + CalculationChangesetNotificationPB, CalculationPB, CalculationType, FieldType, +}; use crate::services::calculations::CalculationsByFieldIdCache; -use crate::services::database_view::DatabaseViewChangedNotifier; +use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier}; use crate::utils::cache::AnyTypeCache; use super::{Calculation, CalculationChangeset, CalculationsService}; pub trait CalculationsDelegate: Send + Sync + 'static { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; - - fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut)>>; + fn get_calculation(&self, view_id: &str, field_id: &str) -> Fut>>; + fn update_calculation(&self, view_id: &str, calculation: Calculation); + fn remove_calculation(&self, view_id: &str, calculation_id: &str); } pub struct CalculationsController { @@ -93,21 +97,151 @@ impl CalculationsController { pub async fn process(&self, predicate: &str) -> FlowyResult<()> { let event_type = CalculationEvent::from_str(predicate).unwrap(); match event_type { - CalculationEvent::RowDeleted(row_id) => self.update_calculation(row_id).await?, + CalculationEvent::RowDeleted(row) => self.handle_row_deleted(row).await, + CalculationEvent::CellUpdated(field_id) => self.handle_cell_changed(field_id).await, + CalculationEvent::FieldTypeChanged(field_id, new_field_type) => { + self + .handle_field_type_changed(field_id, new_field_type) + .await + }, } + Ok(()) } - pub async fn did_receive_row_changed(&self, _row_id: RowId) { - todo!("RowDeleted / CellChanged") - // let row = self.delegate.get_row(&self.view_id, &row_id.clone()).await; + pub async fn did_receive_field_type_changed(&self, field_id: String, new_field_type: FieldType) { + self + .gen_task( + CalculationEvent::FieldTypeChanged(field_id, new_field_type), + QualityOfService::UserInteractive, + ) + .await + } + + pub async fn handle_field_type_changed(&self, field_id: String, new_field_type: FieldType) { + let calculation = self + .delegate + .get_calculation(&self.view_id, &field_id) + .await; + + if let Some(calculation) = calculation { + if new_field_type != FieldType::Number { + self + .delegate + .remove_calculation(&self.view_id, &calculation.id); + + let notification = CalculationChangesetNotificationPB::from_delete( + &self.view_id, + vec![CalculationPB::from(&calculation)], + ); + + let _ = self + .notifier + .send(DatabaseViewChanged::CalculationValueNotification( + notification, + )); + } + } + } + + pub async fn did_receive_cell_changed(&self, field_id: String) { + self + .gen_task( + CalculationEvent::CellUpdated(field_id), + QualityOfService::UserInteractive, + ) + .await + } + + async fn handle_cell_changed(&self, field_id: String) { + let calculation = self + .delegate + .get_calculation(&self.view_id, &field_id) + .await; + + if let Some(calculation) = calculation { + let update = self.get_updated_calculation(calculation).await; + if let Some(update) = update { + self + .delegate + .update_calculation(&self.view_id, update.clone()); + + let notification = CalculationChangesetNotificationPB::from_update( + &self.view_id, + vec![CalculationPB::from(&update)], + ); + + let _ = self + .notifier + .send(DatabaseViewChanged::CalculationValueNotification( + notification, + )); + } + } + } + + pub async fn did_receive_row_deleted(&self, row: Row) { + self + .gen_task( + CalculationEvent::RowDeleted(row), + QualityOfService::UserInteractive, + ) + .await + } + + async fn handle_row_deleted(&self, row: Row) { + let cells = row.cells.iter(); + + let mut updates = vec![]; + + // Iterate each cell in the row + for cell in cells { + let field_id = cell.0; + let value = cell.1.value().get("data"); + + // Only continue if there is a value in the cell + if let Some(_cell_value) = value { + let calculation = self.delegate.get_calculation(&self.view_id, field_id).await; + + if let Some(calculation) = calculation { + let update = self.get_updated_calculation(calculation.clone()).await; - // self - // .gen_task( - // CalculationEvent::RowDeleted(row_id.into_inner()), - // QualityOfService::UserInteractive, - // ) - // .await + if let Some(update) = update { + updates.push(CalculationPB::from(&update)); + self.delegate.update_calculation(&self.view_id, update); + } + } + } + } + + if !updates.is_empty() { + let notification = CalculationChangesetNotificationPB::from_update(&self.view_id, updates); + + let _ = self + .notifier + .send(DatabaseViewChanged::CalculationValueNotification( + notification, + )); + } + } + + async fn get_updated_calculation(&self, calculation: Arc) -> Option { + let row_cells = self + .delegate + .get_cells_for_field(&self.view_id, &calculation.field_id) + .await; + + if !row_cells.is_empty() { + let value = self + .calculations_service + .calculate(calculation.calculation_type, row_cells); + + if value != calculation.value { + return Some(calculation.with_value(value)); + } + } + + None } pub async fn did_receive_changes( @@ -161,15 +295,13 @@ impl CalculationsController { .insert(field_id, calculation.clone()); } } - - async fn update_calculation(&self, _row_id: String) -> FlowyResult<()> { - todo!("update_calculation"); - } } #[derive(Serialize, Deserialize, Clone, Debug)] enum CalculationEvent { - RowDeleted(String), + RowDeleted(Row), + CellUpdated(String), + FieldTypeChanged(String, FieldType), } impl ToString for CalculationEvent { diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs index a66aa74c1a81..f4502020ac0d 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/entities.rs @@ -1,13 +1,8 @@ -use std::sync::Arc; - use anyhow::bail; use collab::core::any_map::AnyMapExtension; -use collab_database::{ - rows::RowCell, - views::{CalculationMap, CalculationMapBuilder}, -}; +use collab_database::views::{CalculationMap, CalculationMapBuilder}; -use crate::entities::{CalculationPB, CalculationType}; +use crate::entities::CalculationPB; #[derive(Debug, Clone)] pub struct Calculation { @@ -76,17 +71,18 @@ impl TryFrom for Calculation { } } -pub struct CalculationsResultNotification { +#[derive(Clone)] +pub struct CalculationUpdatedNotification { pub view_id: String, - pub calculations: Vec, + pub calculation: Calculation, } -impl CalculationsResultNotification { - pub fn new(view_id: String) -> Self { +impl CalculationUpdatedNotification { + pub fn new(view_id: String, calculation: Calculation) -> Self { Self { view_id, - calculations: vec![], + calculation, } } } @@ -100,6 +96,15 @@ impl Calculation { value: "".to_owned(), } } + + pub fn with_value(&self, value: String) -> Self { + Self { + id: self.id.clone(), + field_id: self.field_id.clone(), + calculation_type: self.calculation_type, + value, + } + } } #[derive(Debug)] diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 1415a5f1751a..d45221aef408 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -384,6 +384,10 @@ impl DatabaseEditor { .set_field_type(new_field_type.into()) .set_type_option(new_field_type.into(), Some(transformed_type_option)); }); + + for view in self.database_views.editors().await { + view.v_did_update_field_type(field_id, new_field_type).await; + } }, } @@ -745,7 +749,9 @@ impl DatabaseEditor { let option_row = self.get_row_detail(view_id, &row_id); if let Some(new_row_detail) = option_row { for view in self.database_views.editors().await { - view.v_did_update_row(&old_row, &new_row_detail).await; + view + .v_did_update_row(&old_row, &new_row_detail, field_id.to_owned()) + .await; } } @@ -1453,6 +1459,13 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { .collect() } + fn get_calculation(&self, view_id: &str, field_id: &str) -> Option { + self + .database + .lock() + .get_calculation::(view_id, field_id) + } + fn get_all_filters(&self, view_id: &str) -> Vec> { self .database diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs index 2778f4740221..03267a7cf0a5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/notifier.rs @@ -16,6 +16,7 @@ pub enum DatabaseViewChanged { FilterNotification(FilterResultNotification), ReorderAllRowsNotification(ReorderAllRowsResult), ReorderSingleRowNotification(ReorderSingleRowResult), + CalculationValueNotification(CalculationChangesetNotificationPB), } pub type DatabaseViewChangedNotifier = broadcast::Sender; @@ -77,6 +78,12 @@ impl DatabaseViewChangedReceiverRunner { .payload(reorder_row) .send() }, + DatabaseViewChanged::CalculationValueNotification(notification) => send_notification( + ¬ification.view_id, + DatabaseNotification::DidUpdateCalculation, + ) + .payload(notification) + .send(), } }) .await; diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs index 9eb39dfe30da..04ffb88f4118 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs @@ -1,10 +1,10 @@ use std::sync::Arc; -use collab_database::rows::{RowCell, RowDetail, RowId}; -use lib_infra::future::Fut; +use collab_database::rows::RowCell; +use lib_infra::future::{to_fut, Fut}; use crate::services::calculations::{ - CalculationsController, CalculationsDelegate, CalculationsTaskHandler, + Calculation, CalculationsController, CalculationsDelegate, CalculationsTaskHandler, }; use crate::services::database_view::{ @@ -49,7 +49,16 @@ impl CalculationsDelegate for DatabaseViewCalculationsDelegateImpl { self.0.get_cells_for_field(view_id, field_id) } - fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut)>> { - self.0.get_row(view_id, row_id) + fn get_calculation(&self, view_id: &str, field_id: &str) -> Fut>> { + let calculation = self.0.get_calculation(view_id, field_id).map(Arc::new); + to_fut(async move { calculation }) + } + + fn update_calculation(&self, view_id: &str, calculation: Calculation) { + self.0.update_calculation(view_id, calculation) + } + + fn remove_calculation(&self, view_id: &str, calculation_id: &str) { + self.0.remove_calculation(view_id, calculation_id) } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index f30aeb764e4f..d6245c4c81fd 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -164,6 +164,8 @@ impl DatabaseViewEditor { #[tracing::instrument(level = "trace", skip_all)] pub async fn v_did_delete_row(&self, row: &Row) { + let deleted_row = row.clone(); + // Send the group notification if the current view has groups; let result = self .mut_group_controller(|group_controller, _| group_controller.did_delete_row(row)) @@ -184,15 +186,32 @@ impl DatabaseViewEditor { } } let changes = RowsChangePB::from_delete(row.id.clone().into_inner()); + send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) .payload(changes) .send(); + + // Updating calculations for each of the Rows cells is a tedious task + // Therefore we spawn a separate task for this + let weak_calculations_controller = Arc::downgrade(&self.calculations_controller); + af_spawn(async move { + if let Some(calculations_controller) = weak_calculations_controller.upgrade() { + calculations_controller + .did_receive_row_deleted(deleted_row) + .await; + } + }); } /// Notify the view that the row has been updated. If the view has groups, /// send the group notification with [GroupRowsNotificationPB]. Otherwise, /// send the view notification with [RowsChangePB] - pub async fn v_did_update_row(&self, old_row: &Option, row_detail: &RowDetail) { + pub async fn v_did_update_row( + &self, + old_row: &Option, + row_detail: &RowDetail, + field_id: String, + ) { let result = self .mut_group_controller(|group_controller, field| { Ok(group_controller.did_update_group_row(old_row, row_detail, &field)) @@ -245,10 +264,9 @@ impl DatabaseViewEditor { .await; } - // If it was a Row deletion if let Some(calculations_controller) = weak_calculations_controller.upgrade() { calculations_controller - .did_receive_row_changed(row_id) + .did_receive_cell_changed(field_id) .await; } }); @@ -996,6 +1014,13 @@ impl DatabaseViewEditor { Ok(()) } + pub async fn v_did_update_field_type(&self, field_id: &str, new_field_type: &FieldType) { + self + .calculations_controller + .did_receive_field_type_changed(field_id.to_owned(), new_field_type.to_owned()) + .await; + } + async fn mut_group_controller(&self, f: F) -> Option where F: FnOnce(&mut Box, Field) -> FlowyResult, diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs index 06b125ec6c18..291f98ac0324 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs @@ -83,6 +83,8 @@ pub trait DatabaseViewOperation: Send + Sync + 'static { fn get_all_calculations(&self, view_id: &str) -> Vec>; + fn get_calculation(&self, view_id: &str, field_id: &str) -> Option; + fn update_calculation(&self, view_id: &str, calculation: Calculation); fn remove_calculation(&self, view_id: &str, calculation_id: &str); From 8373f91be1e1dea4089652321a1ff9fd0545c5c1 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 25 Jan 2024 22:42:46 +0100 Subject: [PATCH 08/24] chore: changes after merging main --- .../application/calculations/calculations_listener.dart | 4 ++-- .../application/calculations/calculations_service.dart | 4 ++-- .../presentation/widgets/calculations/calculations_row.dart | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart index 09d26a024fd8..eb6939c64620 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_listener.dart @@ -11,14 +11,14 @@ typedef UpdateCalculationValue = Either; class CalculationsListener { + CalculationsListener({required this.viewId}); + final String viewId; PublishNotifier? _calculationNotifier = PublishNotifier(); DatabaseNotificationListener? _listener; - CalculationsListener({required this.viewId}); - void start({ required void Function(UpdateCalculationValue) onCalculationChanged, }) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart index 80194c0e0442..04234c46f03d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart @@ -4,10 +4,10 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:dartz/dartz.dart'; class CalculationsBackendService { - final String viewId; - const CalculationsBackendService({required this.viewId}); + final String viewId; + // Get Calculations (initial fetch) Future> getCalculations() async { final payload = DatabaseViewIdPB()..value = viewId; diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart index 54dfed924902..bee7308f1ae9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart @@ -14,13 +14,13 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GridCalculationsRow extends StatelessWidget { - final String viewId; - const GridCalculationsRow({ super.key, required this.viewId, }); + final String viewId; + @override Widget build(BuildContext context) { return BlocProvider( @@ -121,7 +121,6 @@ class CalculateCell extends StatelessWidget { radius: BorderRadius.zero, hoverColor: AFThemeExtension.of(context).lightGreyHover, text: Row( - mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: [ Flexible( @@ -146,7 +145,6 @@ class CalculateCell extends StatelessWidget { radius: BorderRadius.zero, hoverColor: AFThemeExtension.of(context).lightGreyHover, text: Row( - mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: [ Flexible( From fc7c0498530f636c9e91f1826cf30c86eedc59ab Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Sat, 27 Jan 2024 15:48:08 +0100 Subject: [PATCH 09/24] feat: handle delete field --- .../src/services/calculations/controller.rs | 37 ++++++++++++++++++- .../src/services/database/database_editor.rs | 5 +++ .../src/services/database_view/view_editor.rs | 7 ++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index 17b09368dec2..683181dd3acc 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -99,6 +99,7 @@ impl CalculationsController { match event_type { CalculationEvent::RowDeleted(row) => self.handle_row_deleted(row).await, CalculationEvent::CellUpdated(field_id) => self.handle_cell_changed(field_id).await, + CalculationEvent::FieldDeleted(field_id) => self.handle_field_deleted(field_id).await, CalculationEvent::FieldTypeChanged(field_id, new_field_type) => { self .handle_field_type_changed(field_id, new_field_type) @@ -109,6 +110,39 @@ impl CalculationsController { Ok(()) } + pub async fn did_receive_field_deleted(&self, field_id: String) { + self + .gen_task( + CalculationEvent::FieldDeleted(field_id), + QualityOfService::UserInteractive, + ) + .await + } + + async fn handle_field_deleted(&self, field_id: String) { + let calculation = self + .delegate + .get_calculation(&self.view_id, &field_id) + .await; + + if let Some(calculation) = calculation { + self + .delegate + .remove_calculation(&self.view_id, &calculation.id); + + let notification = CalculationChangesetNotificationPB::from_delete( + &self.view_id, + vec![CalculationPB::from(&calculation)], + ); + + let _ = self + .notifier + .send(DatabaseViewChanged::CalculationValueNotification( + notification, + )); + } + } + pub async fn did_receive_field_type_changed(&self, field_id: String, new_field_type: FieldType) { self .gen_task( @@ -118,7 +152,7 @@ impl CalculationsController { .await } - pub async fn handle_field_type_changed(&self, field_id: String, new_field_type: FieldType) { + async fn handle_field_type_changed(&self, field_id: String, new_field_type: FieldType) { let calculation = self .delegate .get_calculation(&self.view_id, &field_id) @@ -302,6 +336,7 @@ enum CalculationEvent { RowDeleted(Row), CellUpdated(String), FieldTypeChanged(String, FieldType), + FieldDeleted(String), } impl ToString for CalculationEvent { diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index d45221aef408..0ae80339aeef 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -330,6 +330,11 @@ impl DatabaseEditor { let notified_changeset = DatabaseFieldChangesetPB::delete(&database_id, vec![FieldIdPB::from(field_id)]); self.notify_did_update_database(notified_changeset).await?; + + for view in self.database_views.editors().await { + view.v_did_delete_field(field_id).await; + } + Ok(()) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index d6245c4c81fd..18426219d193 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -1014,6 +1014,13 @@ impl DatabaseViewEditor { Ok(()) } + pub async fn v_did_delete_field(&self, field_id: &str) { + self + .calculations_controller + .did_receive_field_deleted(field_id.to_owned()) + .await; + } + pub async fn v_did_update_field_type(&self, field_id: &str, new_field_type: &FieldType) { self .calculations_controller From 4550d6d3d09d9e89d0b435d02f47725633b5319f Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Mon, 29 Jan 2024 23:34:08 +0100 Subject: [PATCH 10/24] test: add grid calculations tests --- .../database/database_field_test.dart | 7 +- .../grid/grid_calculations_test.dart | 107 ++++++++++++++++++ .../util/database_test_op.dart | 27 +++++ 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart diff --git a/frontend/appflowy_flutter/integration_test/database/database_field_test.dart b/frontend/appflowy_flutter/integration_test/database/database_field_test.dart index 8c51a8b663bb..d196341d8ee7 100644 --- a/frontend/appflowy_flutter/integration_test/database/database_field_test.dart +++ b/frontend/appflowy_flutter/integration_test/database/database_field_test.dart @@ -39,12 +39,7 @@ void main() { await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); // Invoke the field editor - await tester.tapGridFieldWithName('Type'); - await tester.tapEditFieldButton(); - - await tester.tapSwitchFieldTypeButton(); - await tester.selectFieldType(FieldType.Checkbox); - await tester.dismissFieldEditor(); + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Checkbox); await tester.assertFieldTypeWithFieldName( 'Type', diff --git a/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart b/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart new file mode 100644 index 000000000000..c334e4a9994d --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart @@ -0,0 +1,107 @@ +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../util/database_test_op.dart'; +import '../util/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Grid Calculations', () { + testWidgets('add calculation and update cell', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + + // Change two Fields to Number + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + + expect(find.text('Calculate'), findsOneWidget); + + await tester.changeCalculateAtIndex(1, CalculationType.Sum); + + // Enter values in cells + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '100', + ); + + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '100', + ); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.text('200'), findsOneWidget); + }); + + testWidgets('add calculations and remove row', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + + // Change two Fields to Number + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + await tester.changeFieldTypeOfFieldWithName('Done', FieldType.Number); + + expect(find.text('Calculate'), findsNWidgets(2)); + + await tester.changeCalculateAtIndex(1, CalculationType.Sum); + await tester.changeCalculateAtIndex(2, CalculationType.Min); + + // Enter values in cells + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '100', + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '150', + ); + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '50', + cellIndex: 1, + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '100', + cellIndex: 1, + ); + + await tester.pumpAndSettle(); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect(find.text('250'), findsOneWidget); + expect(find.text('50'), findsNWidgets(2)); + + // Delete 1st row + await tester.hoverOnFirstRowOfGrid(); + await tester.tapRowMenuButtonInGrid(); + await tester.tapDeleteOnRowMenu(); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(find.text('150'), findsNWidgets(2)); + expect(find.text('100'), findsNWidgets(2)); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 09a77695308d..4d31add24377 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/number.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart'; @@ -717,6 +719,31 @@ extension AppFlowyDatabaseTest on WidgetTester { await pumpAndSettle(); } + Future changeFieldTypeOfFieldWithName( + String name, + FieldType type, + ) async { + await tapGridFieldWithName(name); + await tapEditFieldButton(); + + await tapSwitchFieldTypeButton(); + await selectFieldType(type); + await dismissFieldEditor(); + } + + Future changeCalculateAtIndex(int index, CalculationType type) async { + await tap(find.byType(CalculateCell).at(index)); + await pumpAndSettle(); + + await tap( + find.descendant( + of: find.byType(CalculationTypeItem), + matching: find.text(type.label), + ), + ); + await pumpAndSettle(); + } + /// Should call [tapGridFieldWithName] first. Future tapEditFieldButton() async { await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr()); From f5067d8197f36563fbebc66a17b3cdb4a7e73c4a Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Tue, 30 Jan 2024 18:15:35 +0100 Subject: [PATCH 11/24] fix: add validation + format numbers --- .../widgets/calculations/calculations_row.dart | 13 ++++++++++++- frontend/rust-lib/flowy-database2/Cargo.toml | 18 +++++++++++------- .../calculation/calculation_changeset.rs | 12 ++++++++++-- .../src/entities/database_entities.rs | 6 +++++- .../src/services/calculations/service.rs | 8 ++++---- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart index bee7308f1ae9..f42eff164d5f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart @@ -158,7 +158,7 @@ class CalculateCell extends StatelessWidget { const HSpace(8), Flexible( child: FlowyText( - calculation!.value, + _withoutTrailingZeros(calculation!.value), color: AFThemeExtension.of(context).textColor, ), ), @@ -172,6 +172,17 @@ class CalculateCell extends StatelessWidget { ), ); } + + String _withoutTrailingZeros(String value) { + final regex = RegExp(r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$'); + if (regex.hasMatch(value)) { + debugPrint("REACHED? ($value)"); + final match = regex.firstMatch(value)!; + return match.group(1)!; + } + + return value; + } } class CalculationTypeItem extends StatelessWidget { diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index 42216e725c0b..08c692a60315 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -14,10 +14,13 @@ collab-integrate = { workspace = true } flowy-database-pub = { workspace = true } flowy-derive.workspace = true -flowy-notification = { workspace = true } +flowy-notification = { workspace = true } parking_lot.workspace = true protobuf.workspace = true -flowy-error = { workspace = true, features = ["impl_from_dispatch_error", "impl_from_collab_database"]} +flowy-error = { workspace = true, features = [ + "impl_from_dispatch_error", + "impl_from_collab_database", +] } lib-dispatch = { workspace = true } tokio = { workspace = true, features = ["sync"] } bytes.workspace = true @@ -26,12 +29,12 @@ serde.workspace = true serde_json.workspace = true serde_repr.workspace = true lib-infra = { workspace = true } -chrono = { workspace = true, default-features = false, features = ["clock"] } +chrono = { workspace = true, default-features = false, features = ["clock"] } rust_decimal = "1.28.1" -rusty-money = {version = "0.4.1", features = ["iso"]} +rusty-money = { version = "0.4.1", features = ["iso"] } lazy_static = "1.4.0" -indexmap = {version = "2.1.0", features = ["serde"]} -url = { version = "2"} +indexmap = { version = "2.1.0", features = ["serde"] } +url = { version = "2" } fancy-regex = "0.11.0" futures.workspace = true dashmap = "5" @@ -45,6 +48,7 @@ csv = "1.1.6" strum = "0.25" strum_macros = "0.25" lru.workspace = true +validator = { version = "0.16.0", features = ["derive"] } [dev-dependencies] event-integration = { path = "../event-integration", default-features = false } @@ -56,4 +60,4 @@ flowy-codegen.workspace = true [features] dart = ["flowy-codegen/dart", "flowy-notification/dart"] ts = ["flowy-codegen/ts", "flowy-notification/ts"] -wasm_build = ["collab-plugins/wasm_build"] \ No newline at end of file +wasm_build = ["collab-plugins/wasm_build"] diff --git a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs index f0292ffc6517..a760c3cee57d 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/calculation/calculation_changeset.rs @@ -2,30 +2,38 @@ use flowy_derive::ProtoBuf; use super::{CalculationPB, CalculationType}; -#[derive(Default, ProtoBuf)] +use lib_infra::validator_fn::required_not_empty_str; +use validator::Validate; + +#[derive(Default, ProtoBuf, Validate)] pub struct UpdateCalculationChangesetPB { #[pb(index = 1)] + #[validate(custom = "required_not_empty_str")] pub view_id: String, #[pb(index = 2, one_of)] pub calculation_id: Option, #[pb(index = 3)] + #[validate(custom = "required_not_empty_str")] pub field_id: String, #[pb(index = 4)] pub calculation_type: CalculationType, } -#[derive(Default, ProtoBuf)] +#[derive(Default, ProtoBuf, Validate)] pub struct RemoveCalculationChangesetPB { #[pb(index = 1)] + #[validate(custom = "required_not_empty_str")] pub view_id: String, #[pb(index = 2)] + #[validate(custom = "required_not_empty_str")] pub field_id: String, #[pb(index = 3)] + #[validate(custom = "required_not_empty_str")] pub calculation_id: String, } diff --git a/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs index 2a61d6d0ed94..3a718cef299c 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs @@ -6,6 +6,9 @@ use collab_database::views::DatabaseLayout; use flowy_derive::ProtoBuf; use flowy_error::{ErrorCode, FlowyError}; +use lib_infra::validator_fn::required_not_empty_str; +use validator::Validate; + use crate::entities::parser::NotEmptyStr; use crate::entities::{DatabaseLayoutPB, FieldIdPB, RowMetaPB}; use crate::services::database::CreateDatabaseViewParams; @@ -66,9 +69,10 @@ impl AsRef for DatabaseIdPB { } } -#[derive(Clone, ProtoBuf, Default, Debug)] +#[derive(Clone, ProtoBuf, Default, Debug, Validate)] pub struct DatabaseViewIdPB { #[pb(index = 1)] + #[validate(custom = "required_not_empty_str")] pub value: String, } diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index 894f3936876e..a51024a0ed54 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -55,7 +55,7 @@ impl CalculationsService { }); if !values.is_empty() { - Self::median(&values).to_string() + format!("{:.5}", Self::median(&values)) } else { "".to_owned() } @@ -80,7 +80,7 @@ impl CalculationsService { if !values.is_empty() { let min = values.iter().min_by(|a, b| a.total_cmp(b)); if let Some(min) = min { - return min.to_string(); + return format!("{:.5}", min); } } @@ -96,7 +96,7 @@ impl CalculationsService { if !values.is_empty() { let max = values.iter().max_by(|a, b| a.total_cmp(b)); if let Some(max) = max { - return max.to_string(); + return format!("{:.5}", max); } } @@ -107,7 +107,7 @@ impl CalculationsService { let values = self.reduce_values_f64(row_cells, |values| values.clone()); if !values.is_empty() { - values.iter().sum::().to_string() + format!("{:.5}", values.iter().sum::()) } else { "".to_owned() } From 3dcfe1c093c1d1ab82588e6e94a4029e590899f0 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 01:23:32 +0800 Subject: [PATCH 12/24] refactor: get cell number --- .../src/services/calculations/controller.rs | 14 ++-- .../src/services/calculations/service.rs | 68 ++++++++++--------- .../src/services/cell/cell_operation.rs | 2 + .../database_view/view_calculations.rs | 5 ++ .../checkbox_type_option.rs | 9 +++ .../checklist_type_option/checklist.rs | 5 ++ .../date_type_option/date_type_option.rs | 4 ++ .../number_type_option/number_type_option.rs | 5 ++ .../select_type_option.rs | 4 ++ .../text_type_option/text_type_option.rs | 4 ++ .../timestamp_type_option.rs | 4 ++ .../field/type_options/type_option_cell.rs | 7 +- .../url_type_option/url_type_option.rs | 4 ++ 13 files changed, 95 insertions(+), 40 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index 683181dd3acc..e06dbd644ec3 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use std::sync::Arc; use collab::core::any_map::AnyMapExtension; +use collab_database::fields::Field; use collab_database::rows::{Row, RowCell}; use flowy_error::FlowyResult; use serde::{Deserialize, Serialize}; @@ -21,6 +22,7 @@ use super::{Calculation, CalculationChangeset, CalculationsService}; pub trait CalculationsDelegate: Send + Sync + 'static { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; + fn get_field(&self, field_id: &str) -> Option; fn get_calculation(&self, view_id: &str, field_id: &str) -> Fut>>; fn update_calculation(&self, view_id: &str, calculation: Calculation); fn remove_calculation(&self, view_id: &str, calculation_id: &str); @@ -264,11 +266,13 @@ impl CalculationsController { .delegate .get_cells_for_field(&self.view_id, &calculation.field_id) .await; + let field = self.delegate.get_field(&calculation.field_id)?; if !row_cells.is_empty() { - let value = self - .calculations_service - .calculate(calculation.calculation_type, row_cells); + let value = + self + .calculations_service + .calculate(&field, calculation.calculation_type, row_cells); if value != calculation.value { return Some(calculation.with_value(value)); @@ -290,9 +294,11 @@ impl CalculationsController { .get_cells_for_field(&self.view_id, &insert.field_id) .await; + let field = self.delegate.get_field(&insert.field_id)?; + let value = self .calculations_service - .calculate(insert.calculation_type, row_cells); + .calculate(&field, insert.calculation_type, row_cells); notification = Some(CalculationChangesetNotificationPB::from_insert( &self.view_id, diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index a51024a0ed54..e27a02b4b8b8 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -1,8 +1,11 @@ +use collab_database::fields::Field; use std::sync::Arc; use collab_database::rows::RowCell; -use crate::entities::CalculationType; +use crate::entities::{CalculationType, FieldType}; +use crate::services::cell::CellDataDecoder; +use crate::services::field::TypeOptionCellExt; pub struct CalculationsService {} @@ -11,32 +14,34 @@ impl CalculationsService { Self {} } - pub fn calculate(&self, calculation_type: i64, row_cells: Vec>) -> String { + pub fn calculate( + &self, + field: &Field, + calculation_type: i64, + row_cells: Vec>, + ) -> String { let ty: CalculationType = calculation_type.into(); match ty { - CalculationType::Average => self.calculate_average(row_cells), - CalculationType::Max => self.calculate_max(row_cells), - CalculationType::Median => self.calculate_median(row_cells), - CalculationType::Min => self.calculate_min(row_cells), - CalculationType::Sum => self.calculate_sum(row_cells), + CalculationType::Average => self.calculate_average(field, row_cells), + CalculationType::Max => self.calculate_max(field, row_cells), + CalculationType::Median => self.calculate_median(field, row_cells), + CalculationType::Min => self.calculate_min(field, row_cells), + CalculationType::Sum => self.calculate_sum(field, row_cells), } } - fn calculate_average(&self, row_cells: Vec>) -> String { + fn calculate_average(&self, field: &Field, row_cells: Vec>) -> String { let mut sum = 0.0; let mut len = 0.0; + let field_type = FieldType::from(field.field_type); + let handler = TypeOptionCellExt::new_with_cell_data_cache(field, None) + .get_type_option_cell_data_handler(&field_type)?; for row_cell in row_cells { if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => { - sum += value; - len += 1.0; - }, - _ => tracing::info!("Failed to parse ({}) to f64", data), - } + if let Some(value) = handler.numeric_cell(cell) { + sum += value; + len += 1.0; } } } @@ -48,8 +53,8 @@ impl CalculationsService { } } - fn calculate_median(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { + fn calculate_median(&self, field: &Field, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(field, row_cells, |values| { values.sort_by(|a, b| a.partial_cmp(b).unwrap()); values.clone() }); @@ -71,8 +76,8 @@ impl CalculationsService { } } - fn calculate_min(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { + fn calculate_min(&self, field: &Field, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(field, row_cells, |values| { values.sort_by(|a, b| a.partial_cmp(b).unwrap()); values.clone() }); @@ -87,8 +92,8 @@ impl CalculationsService { "".to_owned() } - fn calculate_max(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| { + fn calculate_max(&self, field: &Field, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(field, row_cells, |values| { values.sort_by(|a, b| a.partial_cmp(b).unwrap()); values.clone() }); @@ -103,8 +108,8 @@ impl CalculationsService { "".to_owned() } - fn calculate_sum(&self, row_cells: Vec>) -> String { - let values = self.reduce_values_f64(row_cells, |values| values.clone()); + fn calculate_sum(&self, field: &Field, row_cells: Vec>) -> String { + let values = self.reduce_values_f64(field, row_cells, |values| values.clone()); if !values.is_empty() { format!("{:.5}", values.iter().sum::()) @@ -113,20 +118,19 @@ impl CalculationsService { } } - fn reduce_values_f64(&self, row_cells: Vec>, f: F) -> T + fn reduce_values_f64(&self, field: &Field, row_cells: Vec>, f: F) -> T where F: FnOnce(&mut Vec) -> T, { let mut values = vec![]; + let field_type = FieldType::from(field.field_type); + let handler = TypeOptionCellExt::new_with_cell_data_cache(field, None) + .get_type_option_cell_data_handler(&field_type)?; for row_cell in row_cells { if let Some(cell) = &row_cell.cell { - let data = cell.get("data"); - if let Some(data) = data { - match data.to_string().parse::() { - Ok(value) => values.push(value), - _ => tracing::info!("Failed to parse ({}) to f64", data), - } + if let Some(value) = handler.numeric_cell(cell) { + values.push(value); } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index b53f2f458b8f..d65f4820e352 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -39,6 +39,8 @@ pub trait CellDataDecoder: TypeOption { /// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell] fn stringify_cell(&self, cell: &Cell) -> String; + + fn numeric_cell(&self, cell: &Cell) -> Option; } pub trait CellDataChangeset: TypeOption { diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs index 04ffb88f4118..f329e498a478 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_calculations.rs @@ -1,3 +1,4 @@ +use collab_database::fields::Field; use std::sync::Arc; use collab_database::rows::RowCell; @@ -49,6 +50,10 @@ impl CalculationsDelegate for DatabaseViewCalculationsDelegateImpl { self.0.get_cells_for_field(view_id, field_id) } + fn get_field(&self, field_id: &str) -> Option { + self.0.get_field(field_id) + } + fn get_calculation(&self, view_id: &str, field_id: &str) -> Fut>> { let calculation = self.0.get_calculation(view_id, field_id).map(Arc::new); to_fut(async move { calculation }) diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index 7645bf2564b9..66c20137a3bb 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs @@ -102,6 +102,15 @@ impl CellDataDecoder for CheckboxTypeOption { fn stringify_cell(&self, cell: &Cell) -> String { Self::CellData::from(cell).to_string() } + + fn numeric_cell(&self, cell: &Cell) -> Option { + let cell_data = self.parse_cell(cell).ok()?; + if cell_data.is_check() { + Some(1.0) + } else { + Some(0.0) + } + } } pub type CheckboxCellChangeset = String; diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs index 825e39b5390e..1169c89613c8 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs @@ -164,6 +164,11 @@ impl CellDataDecoder for ChecklistTypeOption { let cell_data = self.parse_cell(cell).unwrap_or_default(); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, _cell: &Cell) -> Option { + // return the percentage complete if needed + None + } } impl TypeOptionCellDataFilter for ChecklistTypeOption { diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs index 28921f1a2afd..e80348c41d50 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs @@ -251,6 +251,10 @@ impl CellDataDecoder for DateTypeOption { let cell_data = Self::CellData::from(cell); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, _cell: &Cell) -> Option { + None + } } impl CellDataChangeset for DateTypeOption { diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs index 0ee75faed71c..3b052ea0e220 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs @@ -208,6 +208,11 @@ impl CellDataDecoder for NumberTypeOption { let cell_data = Self::CellData::from(cell); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, cell: &Cell) -> Option { + let num_cell_data = self.parse_cell(cell)?; + num_cell_data.0.parse::().ok() + } } pub type NumberCellChangeset = String; diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs index 1e6aa781a1fc..0101db9c88c3 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -146,6 +146,10 @@ where let cell_data = Self::CellData::from(cell); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, _cell: &Cell) -> Option { + None + } } pub fn select_type_option_from_field( diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs index 0e8f48302c84..32e9096e8794 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs @@ -116,6 +116,10 @@ impl CellDataDecoder for RichTextTypeOption { fn stringify_cell(&self, cell: &Cell) -> String { Self::CellData::from(cell).to_string() } + + fn numeric_cell(&self, cell: &Cell) -> Option { + StrCellData::from(cell).0.parse::().ok() + } } impl CellDataChangeset for RichTextTypeOption { diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs index 549b777a3dcd..ada47d386469 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs @@ -152,6 +152,10 @@ impl CellDataDecoder for TimestampTypeOption { let cell_data = Self::CellData::from(cell); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, _cell: &Cell) -> Option { + None + } } impl CellDataChangeset for TimestampTypeOption { diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs index 2b69b74130a6..0d340a59096a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs @@ -341,10 +341,9 @@ where ) -> FlowyResult { // tracing::debug!("get_cell_data: {:?}", std::any::type_name::()); let cell_data = if self.transformable() { - match self.transform_type_option_cell(cell, field_type, field) { - None => self.get_decoded_cell_data(cell, field_type, field)?, - Some(cell_data) => cell_data, - } + self + .transform_type_option_cell(cell, field_type, field) + .unwrap_or_else(|| self.get_decoded_cell_data(cell, field_type, field)?) } else { self.get_decoded_cell_data(cell, field_type, field)? }; diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs index 37f93d8b6666..b236b3101853 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs @@ -82,6 +82,10 @@ impl CellDataDecoder for URLTypeOption { let cell_data = Self::CellData::from(cell); self.stringify_cell_data(cell_data) } + + fn numeric_cell(&self, _cell: &Cell) -> Option { + None + } } pub type URLCellChangeset = String; From 67f28dd76805931a2e5fcec557453ad7c42cf1eb Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 02:30:39 +0800 Subject: [PATCH 13/24] chore: bump collab --- frontend/appflowy_tauri/src-tauri/Cargo.lock | 15 ++++---- frontend/appflowy_tauri/src-tauri/Cargo.toml | 14 +++---- frontend/appflowy_web/wasm-libs/Cargo.toml | 14 +++---- frontend/rust-lib/Cargo.lock | 15 ++++---- frontend/rust-lib/Cargo.toml | 14 +++---- .../src/services/calculations/service.rs | 38 ++++++++++--------- .../src/services/cell/cell_operation.rs | 2 +- .../number_type_option/number_type_option.rs | 2 +- .../select_type_option.rs | 18 +++++---- .../field/type_options/type_option_cell.rs | 17 ++++++--- 10 files changed, 81 insertions(+), 68 deletions(-) diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index a107bc046e1b..ff7f3189565b 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -816,7 +816,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-trait", @@ -838,7 +838,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-trait", @@ -867,7 +867,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "collab", @@ -886,7 +886,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "bytes", @@ -901,7 +901,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "chrono", @@ -938,7 +938,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-stream", @@ -977,7 +977,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "collab", @@ -1802,6 +1802,7 @@ dependencies = [ "tokio", "tracing", "url", + "validator", ] [[package]] diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index b16784524a54..bfd2f3e87adb 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -72,10 +72,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } diff --git a/frontend/appflowy_web/wasm-libs/Cargo.toml b/frontend/appflowy_web/wasm-libs/Cargo.toml index 2c20f888acef..f373eda613d1 100644 --- a/frontend/appflowy_web/wasm-libs/Cargo.toml +++ b/frontend/appflowy_web/wasm-libs/Cargo.toml @@ -72,10 +72,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index d9c755fa9fa5..01a782bf50bc 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -744,7 +744,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-trait", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-trait", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "collab", @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "bytes", @@ -829,7 +829,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "chrono", @@ -866,7 +866,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "async-stream", @@ -905,7 +905,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d00b477a9b844d86b5caeff573ca395dc5bf7198#d00b477a9b844d86b5caeff573ca395dc5bf7198" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" dependencies = [ "anyhow", "collab", @@ -1790,6 +1790,7 @@ dependencies = [ "tokio", "tracing", "url", + "validator", ] [[package]] diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 4b53bbbadf0f..60e882fac2d4 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -115,10 +115,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d00b477a9b844d86b5caeff573ca395dc5bf7198" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index e27a02b4b8b8..3a0c40e5883d 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -1,11 +1,9 @@ -use collab_database::fields::Field; -use std::sync::Arc; - -use collab_database::rows::RowCell; - use crate::entities::{CalculationType, FieldType}; use crate::services::cell::CellDataDecoder; use crate::services::field::TypeOptionCellExt; +use collab_database::fields::Field; +use collab_database::rows::RowCell; +use std::sync::Arc; pub struct CalculationsService {} @@ -35,13 +33,15 @@ impl CalculationsService { let mut sum = 0.0; let mut len = 0.0; let field_type = FieldType::from(field.field_type); - let handler = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type)?; - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - if let Some(value) = handler.numeric_cell(cell) { - sum += value; - len += 1.0; + if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) + .get_type_option_cell_data_handler(&field_type) + { + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + if let Some(value) = handler.handle_numeric_cell(cell) { + sum += value; + len += 1.0; + } } } } @@ -125,12 +125,14 @@ impl CalculationsService { let mut values = vec![]; let field_type = FieldType::from(field.field_type); - let handler = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type)?; - for row_cell in row_cells { - if let Some(cell) = &row_cell.cell { - if let Some(value) = handler.numeric_cell(cell) { - values.push(value); + if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) + .get_type_option_cell_data_handler(&field_type) + { + for row_cell in row_cells { + if let Some(cell) = &row_cell.cell { + if let Some(value) = handler.handle_numeric_cell(cell) { + values.push(value); + } } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index d65f4820e352..07682f2e009d 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -174,7 +174,7 @@ pub fn stringify_cell_data( .get_type_option_cell_data_handler(from_field_type) { None => "".to_string(), - Some(handler) => handler.stringify_cell_str(cell, to_field_type, field), + Some(handler) => handler.handle_stringify_cell(cell, to_field_type, field), } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs index 3b052ea0e220..2241a4c3bb7f 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs @@ -210,7 +210,7 @@ impl CellDataDecoder for NumberTypeOption { } fn numeric_cell(&self, cell: &Cell) -> Option { - let num_cell_data = self.parse_cell(cell)?; + let num_cell_data = self.parse_cell(cell).ok()?; num_cell_data.0.parse::().ok() } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs index 0101db9c88c3..f46b764683cf 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -7,13 +7,15 @@ use flowy_error::{internal_error, ErrorCode, FlowyResult}; use crate::entities::{FieldType, SelectOptionCellDataPB}; use crate::services::cell::{ - CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset, + CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, + ToCellChangeset, }; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper; use crate::services::field::{ make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption, SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption, - TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR, + TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde, + TypeOptionTransform, SELECTION_IDS_SEPARATOR, }; /// Defines the shared actions used by SingleSelect or Multi-Select. @@ -118,10 +120,10 @@ where } } -impl CellDataDecoder for T +impl CellDataDecoder for T where - T: - SelectTypeOptionSharedAction + TypeOption + TypeOptionCellDataSerde, + C: Into + for<'a> From<&'a Cell>, + T: SelectTypeOptionSharedAction + TypeOption + TypeOptionCellDataSerde, { fn decode_cell( &self, @@ -132,9 +134,9 @@ where self.parse_cell(cell) } - fn stringify_cell_data(&self, cell_data: ::CellData) -> String { + fn stringify_cell_data(&self, cell_data: C) -> String { self - .get_selected_options(cell_data) + .get_selected_options(cell_data.into()) .select_options .into_iter() .map(|option| option.name) @@ -143,7 +145,7 @@ where } fn stringify_cell(&self, cell: &Cell) -> String { - let cell_data = Self::CellData::from(cell); + let cell_data = C::from(cell); self.stringify_cell_data(cell_data) } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs index 0d340a59096a..dfe28f63a403 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs @@ -62,7 +62,9 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static { /// For example, the field type of the [TypeOptionCellDataHandler] is [FieldType::Date], and /// the if field_type is [FieldType::RichText], then the string would be something like "Mar 14, 2022". /// - fn stringify_cell_str(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String; + fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String; + + fn handle_numeric_cell(&self, cell: &Cell) -> Option; /// Format the cell to [BoxCellData] using the passed-in [FieldType] and [Field]. /// The caller can get the cell data by calling [BoxCellData::unbox_or_none]. @@ -323,7 +325,7 @@ where /// is [FieldType::RichText], then the string will be transformed to a string that separated by comma with the /// option's name. /// - fn stringify_cell_str(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String { + fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String { if self.transformable() { let cell_data = self.transform_type_option_cell(cell, field_type, field); if let Some(cell_data) = cell_data { @@ -333,6 +335,10 @@ where self.stringify_cell(cell) } + fn handle_numeric_cell(&self, cell: &Cell) -> Option { + self.numeric_cell(cell) + } + fn get_cell_data( &self, cell: &Cell, @@ -341,9 +347,10 @@ where ) -> FlowyResult { // tracing::debug!("get_cell_data: {:?}", std::any::type_name::()); let cell_data = if self.transformable() { - self - .transform_type_option_cell(cell, field_type, field) - .unwrap_or_else(|| self.get_decoded_cell_data(cell, field_type, field)?) + match self.transform_type_option_cell(cell, field_type, field) { + None => self.get_decoded_cell_data(cell, field_type, field)?, + Some(cell_data) => cell_data, + } } else { self.get_decoded_cell_data(cell, field_type, field)? }; From 16bbd248dc06d8c573ba085b793ecea927f96f00 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 02:33:31 +0800 Subject: [PATCH 14/24] chore: fix clippy --- .../flowy-database2/src/services/calculations/service.rs | 2 +- .../flowy-database2/src/services/cell/cell_operation.rs | 2 ++ .../type_options/selection_type_option/select_type_option.rs | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index 3a0c40e5883d..76abb05f926c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -1,5 +1,5 @@ use crate::entities::{CalculationType, FieldType}; -use crate::services::cell::CellDataDecoder; + use crate::services::field::TypeOptionCellExt; use collab_database::fields::Field; use collab_database::rows::RowCell; diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index 07682f2e009d..1d4d9c051248 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -40,6 +40,8 @@ pub trait CellDataDecoder: TypeOption { /// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell] fn stringify_cell(&self, cell: &Cell) -> String; + // Decode the cell into f64 + // Different field type has different way to decode the cell data into f64 fn numeric_cell(&self, cell: &Cell) -> Option; } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs index f46b764683cf..6f78c808e567 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -7,14 +7,13 @@ use flowy_error::{internal_error, ErrorCode, FlowyResult}; use crate::entities::{FieldType, SelectOptionCellDataPB}; use crate::services::cell::{ - CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, + CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset, }; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper; use crate::services::field::{ make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption, - SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde, + SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption, TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR, }; From ef81e4539fc2457139f4c49ae36795a3acc9c5f7 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 02:36:47 +0800 Subject: [PATCH 15/24] chore: update docs --- .../rust-lib/flowy-database2/src/services/cell/cell_operation.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index 1d4d9c051248..ca5de920cece 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -42,6 +42,7 @@ pub trait CellDataDecoder: TypeOption { // Decode the cell into f64 // Different field type has different way to decode the cell data into f64 + // If the field type doesn't support to decode the cell data into f64, it will return None fn numeric_cell(&self, cell: &Cell) -> Option; } From e1f7f88083b30d43e1c78fec551f3df1ad4f57a1 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 02:37:00 +0800 Subject: [PATCH 16/24] chore: update docs --- .../flowy-database2/src/services/cell/cell_operation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index ca5de920cece..e8d1f41ad053 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -40,9 +40,9 @@ pub trait CellDataDecoder: TypeOption { /// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell] fn stringify_cell(&self, cell: &Cell) -> String; - // Decode the cell into f64 - // Different field type has different way to decode the cell data into f64 - // If the field type doesn't support to decode the cell data into f64, it will return None + /// Decode the cell into f64 + /// Different field type has different way to decode the cell data into f64 + /// If the field type doesn't support to decode the cell data into f64, it will return None fn numeric_cell(&self, cell: &Cell) -> Option; } From 681805ee7e1017669b033e7b1af1642557e462a8 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 04:46:43 +0800 Subject: [PATCH 17/24] chore: fmt --- .../selection_type_option/select_type_option.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs index 6f78c808e567..5d82147d74cb 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -7,14 +7,13 @@ use flowy_error::{internal_error, ErrorCode, FlowyResult}; use crate::entities::{FieldType, SelectOptionCellDataPB}; use crate::services::cell::{ - CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, - ToCellChangeset, + CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset, }; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper; use crate::services::field::{ make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption, - SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption, TypeOptionCellDataSerde, - TypeOptionTransform, SELECTION_IDS_SEPARATOR, + SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption, + TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR, }; /// Defines the shared actions used by SingleSelect or Multi-Select. From c61c12e52eb463ddd19bd0e08a23755c1289545c Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 05:34:54 +0800 Subject: [PATCH 18/24] chore: fix flutter --- .../application/calculations/calculations_service.dart | 4 ++-- .../grid/application/calculations/calculations_bloc.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart index 04234c46f03d..874c565c3e8c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart @@ -29,7 +29,7 @@ class CalculationsBackendService { payload.calculationId = calculationId; } - DatabaseEventUpdateCalculation(payload).send(); + await DatabaseEventUpdateCalculation(payload).send(); } Future removeCalculation( @@ -41,6 +41,6 @@ class CalculationsBackendService { ..fieldId = fieldId ..calculationId = calculationId; - DatabaseEventRemoveCalculation(payload).send(); + await DatabaseEventRemoveCalculation(payload).send(); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart index e05fc9d53bfb..f207fac251e6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart @@ -29,7 +29,7 @@ class CalculationsBloc extends Bloc { @override Future close() async { _fieldController.removeListener(onFieldsListener: _onReceiveFields); - super.close(); + await super.close(); } void _dispatch() { @@ -37,7 +37,7 @@ class CalculationsBloc extends Bloc { await event.when( started: () async { _startListening(); - _getAllCalculations(); + await _getAllCalculations(); add( CalculationsEvent.didReceiveFieldUpdate( From 05fa1ef988645177e69ca79c175d5539e55e82f9 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 31 Jan 2024 11:56:54 +0800 Subject: [PATCH 19/24] chore: collab rev --- frontend/appflowy_tauri/src-tauri/Cargo.lock | 14 +++++++------- frontend/appflowy_tauri/src-tauri/Cargo.toml | 14 +++++++------- frontend/appflowy_web/wasm-libs/Cargo.toml | 14 +++++++------- frontend/rust-lib/Cargo.lock | 14 +++++++------- frontend/rust-lib/Cargo.toml | 14 +++++++------- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index ff7f3189565b..062dbc79ca2b 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -816,7 +816,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-trait", @@ -838,7 +838,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-trait", @@ -867,7 +867,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "collab", @@ -886,7 +886,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "bytes", @@ -901,7 +901,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "chrono", @@ -938,7 +938,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-stream", @@ -977,7 +977,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "collab", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index bfd2f3e87adb..4164032c7222 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -72,10 +72,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } diff --git a/frontend/appflowy_web/wasm-libs/Cargo.toml b/frontend/appflowy_web/wasm-libs/Cargo.toml index f373eda613d1..e9044a6fa794 100644 --- a/frontend/appflowy_web/wasm-libs/Cargo.toml +++ b/frontend/appflowy_web/wasm-libs/Cargo.toml @@ -72,10 +72,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 01a782bf50bc..5d27f4c9a8ce 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -744,7 +744,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-trait", @@ -766,7 +766,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-trait", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "collab", @@ -814,7 +814,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "bytes", @@ -829,7 +829,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "chrono", @@ -866,7 +866,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "async-stream", @@ -905,7 +905,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bce89d2b883b3139b827c48ddcb7bbf84c074fd#9bce89d2b883b3139b827c48ddcb7bbf84c074fd" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6b0932581b4544a0800a0451b9522e6caab5570#a6b0932581b4544a0800a0451b9522e6caab5570" dependencies = [ "anyhow", "collab", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 60e882fac2d4..95db07e256bd 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -115,10 +115,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "199 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bce89d2b883b3139b827c48ddcb7bbf84c074fd" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6b0932581b4544a0800a0451b9522e6caab5570" } From 1ba6cbb4c59989ac287d0f1c03a4b3d5b5f6b518 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Wed, 31 Jan 2024 12:09:11 +0100 Subject: [PATCH 20/24] fix: cleanup and hover to show --- .../util/database_test_op.dart | 3 +- .../calculations/calculations_bloc.dart | 4 + .../widgets/calculations/calculate_cell.dart | 137 ++++++++++++ .../calculations/calculation_selector.dart | 59 +++++ .../calculations/calculation_type_item.dart | 33 +++ .../calculations/calculations_row.dart | 203 +----------------- .../remove_calculation_button.dart | 32 +++ 7 files changed, 273 insertions(+), 198 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 259285b5ce5a..ffad692a8c37 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -1,7 +1,8 @@ import 'dart:io'; import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; -import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/number.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart index f207fac251e6..0eb60dd3eb52 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart @@ -87,6 +87,10 @@ class CalculationsBloc extends Bloc { _calculationsListener.start( onCalculationChanged: (changesetOrFailure) { + if (isClosed) { + return; + } + changesetOrFailure.fold( (changeset) { final calculationsMap = {...state.calculationsByFieldId}; diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart new file mode 100644 index 000000000000..c2ff2952126f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; +import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CalculateCell extends StatefulWidget { + const CalculateCell({ + super.key, + required this.fieldInfo, + required this.width, + this.calculation, + }); + + final FieldInfo fieldInfo; + final double width; + final CalculationPB? calculation; + + @override + State createState() => _CalculateCellState(); +} + +class _CalculateCellState extends State { + bool isSelected = false; + + void setIsSelected(bool selected) => setState(() => isSelected = selected); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 35, + width: widget.width, + child: AppFlowyPopover( + constraints: BoxConstraints.loose(const Size(150, 200)), + direction: PopoverDirection.bottomWithCenterAligned, + onClose: () => setIsSelected(false), + popupBuilder: (_) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setIsSelected(true); + } + }); + + return SingleChildScrollView( + child: Column( + children: [ + if (widget.calculation != null) + RemoveCalculationButton( + onTap: () => context.read().add( + CalculationsEvent.removeCalculation( + widget.fieldInfo.id, + widget.calculation!.id, + ), + ), + ), + ...CalculationType.values.map( + (type) => CalculationTypeItem( + type: type, + onTap: () { + if (type != widget.calculation?.calculationType) { + context.read().add( + CalculationsEvent.updateCalculationType( + widget.fieldInfo.id, + type, + calculationId: widget.calculation?.id, + ), + ); + } + }, + ), + ), + ], + ), + ); + }, + child: widget.fieldInfo.fieldType == FieldType.Number + ? widget.calculation != null + ? _showCalculateValue(context) + : CalculationSelector(isSelected: isSelected) + : const SizedBox.shrink(), + ), + ); + } + + Widget _showCalculateValue(BuildContext context) { + return FlowyButton( + radius: BorderRadius.zero, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: FlowyText( + widget.calculation!.calculationType.label, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + if (widget.calculation!.value.isNotEmpty) ...[ + const HSpace(8), + Flexible( + child: FlowyText( + _withoutTrailingZeros(widget.calculation!.value), + color: AFThemeExtension.of(context).textColor, + ), + ), + ], + const HSpace(8), + FlowySvg( + FlowySvgs.arrow_down_s, + color: Theme.of(context).hintColor, + ), + ], + ), + ); + } + + String _withoutTrailingZeros(String value) { + final regex = RegExp(r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$'); + if (regex.hasMatch(value)) { + final match = regex.firstMatch(value)!; + return match.group(1)!; + } + + return value; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart new file mode 100644 index 000000000000..87922a9001a6 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; + +class CalculationSelector extends StatefulWidget { + const CalculationSelector({ + super.key, + required this.isSelected, + }); + + final bool isSelected; + + @override + State createState() => _CalculationSelectorState(); +} + +class _CalculationSelectorState extends State { + bool _isHovering = false; + + void _setHovering(bool isHovering) => + setState(() => _isHovering = isHovering); + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => _setHovering(true), + onExit: (_) => _setHovering(false), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: widget.isSelected || _isHovering ? 1 : 0, + child: FlowyButton( + radius: BorderRadius.zero, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: FlowyText( + 'Calculate', + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + const HSpace(8), + FlowySvg( + FlowySvgs.arrow_down_s, + color: Theme.of(context).hintColor, + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart new file mode 100644 index 000000000000..eb1a76fe18f2 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pbenum.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; + +class CalculationTypeItem extends StatelessWidget { + const CalculationTypeItem({ + super.key, + required this.type, + required this.onTap, + }); + + final CalculationType type; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + text: FlowyText.medium(type.label, overflow: TextOverflow.ellipsis), + onTap: () { + onTap(); + PopoverContainer.of(context).close(); + }, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart index f42eff164d5f..0d44e9419052 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart @@ -1,33 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; -import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart'; import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GridCalculationsRow extends StatelessWidget { - const GridCalculationsRow({ - super.key, - required this.viewId, - }); + const GridCalculationsRow({super.key, required this.viewId}); final String viewId; @override Widget build(BuildContext context) { + final gridBloc = context.read(); + return BlocProvider( create: (context) => CalculationsBloc( - viewId: context.read().databaseController.viewId, - fieldController: - context.read().databaseController.fieldController, + viewId: gridBloc.databaseController.viewId, + fieldController: gridBloc.databaseController.fieldController, )..add(const CalculationsEvent.started()), child: BlocBuilder( builder: (context, state) { @@ -53,185 +44,3 @@ class GridCalculationsRow extends StatelessWidget { ); } } - -class CalculateCell extends StatelessWidget { - const CalculateCell({ - super.key, - required this.fieldInfo, - required this.width, - this.calculation, - }); - - final FieldInfo fieldInfo; - final double width; - final CalculationPB? calculation; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 35, - width: width, - child: AppFlowyPopover( - constraints: BoxConstraints.loose(const Size(150, 200)), - direction: PopoverDirection.bottomWithCenterAligned, - popupBuilder: (_) => SingleChildScrollView( - child: Column( - children: [ - if (calculation != null) - RemoveCalculationButton( - onTap: () { - context.read().add( - CalculationsEvent.removeCalculation( - fieldInfo.id, - calculation!.id, - ), - ); - }, - ), - ...CalculationType.values.map( - (type) => CalculationTypeItem( - type: type, - onTap: () { - if (type != calculation?.calculationType) { - context.read().add( - CalculationsEvent.updateCalculationType( - fieldInfo.id, - type, - calculationId: calculation?.id, - ), - ); - } - }, - ), - ), - ], - ), - ), - child: fieldInfo.fieldType == FieldType.Number - ? calculation != null - ? _showCalculateValue(context) - : _showCalculateText(context) - : const SizedBox.shrink(), - ), - ); - } - - Widget _showCalculateText(BuildContext context) { - return FlowyButton( - radius: BorderRadius.zero, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Flexible( - child: FlowyText( - 'Calculate', - color: Theme.of(context).hintColor, - overflow: TextOverflow.ellipsis, - ), - ), - const HSpace(8), - FlowySvg( - FlowySvgs.arrow_down_s, - color: Theme.of(context).hintColor, - ), - ], - ), - ); - } - - Widget _showCalculateValue(BuildContext context) { - return FlowyButton( - radius: BorderRadius.zero, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Flexible( - child: FlowyText( - calculation!.calculationType.label, - color: Theme.of(context).hintColor, - overflow: TextOverflow.ellipsis, - ), - ), - if (calculation!.value.isNotEmpty) ...[ - const HSpace(8), - Flexible( - child: FlowyText( - _withoutTrailingZeros(calculation!.value), - color: AFThemeExtension.of(context).textColor, - ), - ), - ], - const HSpace(8), - FlowySvg( - FlowySvgs.arrow_down_s, - color: Theme.of(context).hintColor, - ), - ], - ), - ); - } - - String _withoutTrailingZeros(String value) { - final regex = RegExp(r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$'); - if (regex.hasMatch(value)) { - debugPrint("REACHED? ($value)"); - final match = regex.firstMatch(value)!; - return match.group(1)!; - } - - return value; - } -} - -class CalculationTypeItem extends StatelessWidget { - const CalculationTypeItem({ - super.key, - required this.type, - required this.onTap, - }); - - final CalculationType type; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: GridSize.popoverItemHeight, - child: FlowyButton( - text: FlowyText.medium(type.label, overflow: TextOverflow.ellipsis), - onTap: () { - onTap(); - PopoverContainer.of(context).close(); - }, - ), - ); - } -} - -class RemoveCalculationButton extends StatelessWidget { - const RemoveCalculationButton({ - super.key, - required this.onTap, - }); - - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: GridSize.popoverItemHeight, - child: FlowyButton( - text: const FlowyText.medium( - 'None', - overflow: TextOverflow.ellipsis, - ), - onTap: () { - onTap(); - PopoverContainer.of(context).close(); - }, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart new file mode 100644 index 000000000000..a968cc59b591 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; + +class RemoveCalculationButton extends StatelessWidget { + const RemoveCalculationButton({ + super.key, + required this.onTap, + }); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + text: const FlowyText.medium( + 'None', + overflow: TextOverflow.ellipsis, + ), + onTap: () { + onTap(); + PopoverContainer.of(context).close(); + }, + ), + ); + } +} From fe57f5a55b1c35c968655d5fd154fb8dad709ca4 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Wed, 31 Jan 2024 12:25:26 +0100 Subject: [PATCH 21/24] fix: localization --- .../integration_test/grid/grid_calculations_test.dart | 2 +- .../widgets/calculations/calculation_selector.dart | 4 +++- .../widgets/calculations/remove_calculation_button.dart | 6 ++++-- frontend/resources/translations/en.json | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart b/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart index c334e4a9994d..c49d399c62b9 100644 --- a/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart +++ b/frontend/appflowy_flutter/integration_test/grid/grid_calculations_test.dart @@ -17,7 +17,7 @@ void main() { await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); - // Change two Fields to Number + // Change one Field to Number await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); expect(find.text('Calculate'), findsOneWidget); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart index 87922a9001a6..09ea5c76c595 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -40,7 +42,7 @@ class _CalculationSelectorState extends State { children: [ Flexible( child: FlowyText( - 'Calculate', + LocaleKeys.grid_calculate.tr(), color: Theme.of(context).hintColor, overflow: TextOverflow.ellipsis, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart index a968cc59b591..369d27133e90 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -18,8 +20,8 @@ class RemoveCalculationButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: const FlowyText.medium( - 'None', + text: FlowyText.medium( + LocaleKeys.grid_calculationTypeLabel_none.tr(), overflow: TextOverflow.ellipsis, ), onTap: () { diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 571b87f0cca3..8070bf0fe26b 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -654,7 +654,9 @@ }, "menuName": "Grid", "referencedGridPrefix": "View of", + "calculate": "Calculate", "calculationTypeLabel": { + "none": "None", "average": "Average", "max": "Max", "median": "Median", @@ -1254,4 +1256,4 @@ "userIcon": "User icon" }, "noLogFiles": "There're no log files" -} \ No newline at end of file +} From 2967323da564d27b5ab6634c1b43aa4a18022314 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Wed, 31 Jan 2024 16:39:10 +0100 Subject: [PATCH 22/24] test: add basic rust test --- .../calculations_test/calculation_test.rs | 87 +++++++++++++++++++ .../tests/database/calculations_test/mod.rs | 2 + .../database/calculations_test/script.rs | 74 ++++++++++++++++ .../flowy-database2/tests/database/mod.rs | 1 + 4 files changed, 164 insertions(+) create mode 100644 frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs create mode 100644 frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs create mode 100644 frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs new file mode 100644 index 000000000000..2800596900b0 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use crate::database::calculations_test::script::{CalculationScript::*, DatabaseCalculationTest}; + +use collab_database::fields::Field; +use flowy_database2::entities::{CalculationType, FieldType, UpdateCalculationChangesetPB}; + +#[tokio::test] +async fn calculations_test() { + let mut test = DatabaseCalculationTest::new().await; + + let expected_sum = 25.00000; + let expected_min = 1.00000; + let expected_average = 5.00000; + let expected_max = 14.00000; + let expected_median = 3.00000; + + let view_id = &test.view_id; + let number_fields = test + .fields + .clone() + .into_iter() + .filter(|field| field.field_type == FieldType::Number as i64) + .collect::>>(); + let field_id = &number_fields.first().unwrap().id; + + let calculation_id = "calc_id".to_owned(); + let scripts = vec![ + // Insert Sum calculation first time + InsertCalculation { + payload: UpdateCalculationChangesetPB { + view_id: view_id.to_owned(), + field_id: field_id.to_owned(), + calculation_id: Some(calculation_id.clone()), + calculation_type: CalculationType::Sum, + }, + }, + AssertCalculationValue { + expected: expected_sum, + }, + InsertCalculation { + payload: UpdateCalculationChangesetPB { + view_id: view_id.to_owned(), + field_id: field_id.to_owned(), + calculation_id: Some(calculation_id.clone()), + calculation_type: CalculationType::Min, + }, + }, + AssertCalculationValue { + expected: expected_min, + }, + InsertCalculation { + payload: UpdateCalculationChangesetPB { + view_id: view_id.to_owned(), + field_id: field_id.to_owned(), + calculation_id: Some(calculation_id.clone()), + calculation_type: CalculationType::Average, + }, + }, + AssertCalculationValue { + expected: expected_average, + }, + InsertCalculation { + payload: UpdateCalculationChangesetPB { + view_id: view_id.to_owned(), + field_id: field_id.to_owned(), + calculation_id: Some(calculation_id.clone()), + calculation_type: CalculationType::Max, + }, + }, + AssertCalculationValue { + expected: expected_max, + }, + InsertCalculation { + payload: UpdateCalculationChangesetPB { + view_id: view_id.to_owned(), + field_id: field_id.to_owned(), + calculation_id: Some(calculation_id), + calculation_type: CalculationType::Median, + }, + }, + AssertCalculationValue { + expected: expected_median, + }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs new file mode 100644 index 000000000000..258788eb1c8c --- /dev/null +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs @@ -0,0 +1,2 @@ +mod calculation_test; +mod script; diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs new file mode 100644 index 000000000000..11a8c39f3562 --- /dev/null +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs @@ -0,0 +1,74 @@ +use tokio::sync::broadcast::Receiver; + +use flowy_database2::entities::UpdateCalculationChangesetPB; +use flowy_database2::services::database_view::DatabaseViewChanged; + +use crate::database::database_editor::DatabaseEditorTest; + +pub enum CalculationScript { + InsertCalculation { + payload: UpdateCalculationChangesetPB, + }, + AssertCalculationValue { + expected: f64, + }, +} + +pub struct DatabaseCalculationTest { + inner: DatabaseEditorTest, + recv: Option>, +} + +impl DatabaseCalculationTest { + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_grid().await; + Self { + inner: editor_test, + recv: None, + } + } + + pub fn view_id(&self) -> String { + self.view_id.clone() + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: CalculationScript) { + match script { + CalculationScript::InsertCalculation { payload } => { + self.recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id()) + .await + .unwrap(), + ); + let _ = self.editor.update_calculation(payload).await.unwrap(); + }, + CalculationScript::AssertCalculationValue { expected } => { + let calculations = self.editor.get_all_calculations(&self.view_id()).await; + let calculation = calculations.items.first().unwrap(); + assert_eq!(calculation.value, format!("{:.5}", expected)); + }, + } + } +} + +impl std::ops::Deref for DatabaseCalculationTest { + type Target = DatabaseEditorTest; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for DatabaseCalculationTest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/frontend/rust-lib/flowy-database2/tests/database/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/mod.rs index 5d916806d7e0..5333d54c337f 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/mod.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/mod.rs @@ -1,4 +1,5 @@ mod block_test; +mod calculations_test; mod cell_test; mod database_editor; mod field_settings_test; From 82acdd6fd0e9ea2bcd47172965eae97d160b7ba4 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Wed, 31 Jan 2024 16:53:15 +0100 Subject: [PATCH 23/24] fix: clippy --- .../flowy-database2/tests/database/calculations_test/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs index 11a8c39f3562..978acd84633f 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs @@ -48,7 +48,7 @@ impl DatabaseCalculationTest { .await .unwrap(), ); - let _ = self.editor.update_calculation(payload).await.unwrap(); + self.editor.update_calculation(payload).await.unwrap(); }, CalculationScript::AssertCalculationValue { expected } => { let calculations = self.editor.get_all_calculations(&self.view_id()).await; From 4692f541ef370fdeccc85c4dddb66d1eadfbedbe Mon Sep 17 00:00:00 2001 From: Mathias Mogensen Date: Thu, 1 Feb 2024 15:31:19 +0100 Subject: [PATCH 24/24] fix: support updating calculation on duplicate row --- .../src/services/calculations/controller.rs | 10 +++++----- .../src/services/database/database_editor.rs | 7 ++++++- .../src/services/database_view/view_editor.rs | 9 ++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs index e06dbd644ec3..849a4f9deb91 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/controller.rs @@ -99,7 +99,7 @@ impl CalculationsController { pub async fn process(&self, predicate: &str) -> FlowyResult<()> { let event_type = CalculationEvent::from_str(predicate).unwrap(); match event_type { - CalculationEvent::RowDeleted(row) => self.handle_row_deleted(row).await, + CalculationEvent::RowChanged(row) => self.handle_row_changed(row).await, CalculationEvent::CellUpdated(field_id) => self.handle_cell_changed(field_id).await, CalculationEvent::FieldDeleted(field_id) => self.handle_field_deleted(field_id).await, CalculationEvent::FieldTypeChanged(field_id, new_field_type) => { @@ -216,16 +216,16 @@ impl CalculationsController { } } - pub async fn did_receive_row_deleted(&self, row: Row) { + pub async fn did_receive_row_changed(&self, row: Row) { self .gen_task( - CalculationEvent::RowDeleted(row), + CalculationEvent::RowChanged(row), QualityOfService::UserInteractive, ) .await } - async fn handle_row_deleted(&self, row: Row) { + async fn handle_row_changed(&self, row: Row) { let cells = row.cells.iter(); let mut updates = vec![]; @@ -339,7 +339,7 @@ impl CalculationsController { #[derive(Serialize, Deserialize, Clone, Debug)] enum CalculationEvent { - RowDeleted(Row), + RowChanged(Row), CellUpdated(String), FieldTypeChanged(String, FieldType), FieldDeleted(String), diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 0ae80339aeef..bde5bc2cda84 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -434,7 +434,12 @@ impl DatabaseEditor { match params { None => warn!("Failed to duplicate row: {}", row_id), Some(params) => { - let _ = self.create_row(view_id, None, params).await; + let result = self.create_row(view_id, None, params).await; + if let Some(row_detail) = result.unwrap_or(None) { + for view in self.database_views.editors().await { + view.v_did_duplicate_row(&row_detail).await; + } + } }, } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 18426219d193..405625502657 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -162,6 +162,13 @@ impl DatabaseViewEditor { .send(); } + pub async fn v_did_duplicate_row(&self, row_detail: &RowDetail) { + self + .calculations_controller + .did_receive_row_changed(row_detail.clone().row) + .await; + } + #[tracing::instrument(level = "trace", skip_all)] pub async fn v_did_delete_row(&self, row: &Row) { let deleted_row = row.clone(); @@ -197,7 +204,7 @@ impl DatabaseViewEditor { af_spawn(async move { if let Some(calculations_controller) = weak_calculations_controller.upgrade() { calculations_controller - .did_receive_row_deleted(deleted_row) + .did_receive_row_changed(deleted_row) .await; } });