diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 64afec8864..6406619147 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -955,6 +955,7 @@ dependencies = [ name = "flowy-grid" version = "0.1.0" dependencies = [ + "anyhow", "atomic_refcell", "bytes", "chrono", @@ -970,6 +971,7 @@ dependencies = [ "flowy-http-model", "flowy-revision", "flowy-sync", + "flowy-task", "flowy-test", "futures", "grid-rev-model", @@ -1078,6 +1080,7 @@ dependencies = [ "flowy-http-model", "flowy-net", "flowy-revision", + "flowy-task", "flowy-user", "futures-core", "grid-rev-model", @@ -1118,6 +1121,19 @@ dependencies = [ "url", ] +[[package]] +name = "flowy-task" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_refcell", + "futures", + "lib-infra", + "rand 0.8.5", + "tokio", + "tracing", +] + [[package]] name = "flowy-test" version = "0.1.0" diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 075258b97b..cf12cbe38f 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -15,6 +15,7 @@ members = [ "flowy-error", "flowy-revision", "flowy-grid", + "flowy-task", ] [profile.dev] diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index a6a001d708..53ac68dfc5 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -44,7 +44,7 @@ pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) { log::error!("sdk not init yet."); return; } - Some(e) => e.dispatcher.clone(), + Some(e) => e.event_dispatcher.clone(), }; let _ = EventDispatcher::async_send_with_callback(dispatcher, request, move |resp: EventResponse| { log::trace!("[FFI]: Post data to dart through {} port", port); @@ -62,7 +62,7 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 { log::error!("sdk not init yet."); return forget_rust(Vec::default()); } - Some(e) => e.dispatcher.clone(), + Some(e) => e.event_dispatcher.clone(), }; let _response = EventDispatcher::sync_send(dispatcher, request); diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index cc3a3df37a..8b32005378 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/frontend/rust-lib/flowy-error/src/errors.rs @@ -112,3 +112,5 @@ impl std::convert::From for FlowyError { FlowyError::internal().context(e) } } + +impl std::error::Error for FlowyError {} diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 9601face4d..7310b7b015 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" lib-dispatch = { path = "../lib-dispatch" } dart-notify = { path = "../dart-notify" } flowy-revision = { path = "../flowy-revision" } +flowy-task= { path = "../flowy-task" } flowy-error = { path = "../flowy-error", features = ["db"]} flowy-derive = { path = "../../../shared-lib/flowy-derive" } lib-ot = { path = "../../../shared-lib/lib-ot" } @@ -17,6 +18,7 @@ grid-rev-model = { path = "../../../shared-lib/grid-rev-model" } flowy-sync = { path = "../../../shared-lib/flowy-sync" } flowy-http-model = { path = "../../../shared-lib/flowy-http-model" } flowy-database = { path = "../flowy-database" } +anyhow = "1.0" strum = "0.21" strum_macros = "0.21" diff --git a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs index 8469138110..371c22fceb 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs @@ -591,6 +591,7 @@ impl std::convert::From<&FieldTypeRevision> for FieldType { FieldType::from(*ty) } } + impl std::convert::From for FieldType { fn from(ty: FieldTypeRevision) -> Self { match ty { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs index a88451bb13..724850fd85 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs @@ -1,49 +1,49 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use grid_rev_model::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct CheckboxFilterConfigurationPB { +pub struct CheckboxFilterPB { #[pb(index = 1)] - pub condition: CheckboxCondition, + pub condition: CheckboxFilterCondition, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] -pub enum CheckboxCondition { +pub enum CheckboxFilterCondition { IsChecked = 0, IsUnChecked = 1, } -impl std::convert::From for i32 { - fn from(value: CheckboxCondition) -> Self { - value as i32 +impl std::convert::From for u32 { + fn from(value: CheckboxFilterCondition) -> Self { + value as u32 } } -impl std::default::Default for CheckboxCondition { +impl std::default::Default for CheckboxFilterCondition { fn default() -> Self { - CheckboxCondition::IsChecked + CheckboxFilterCondition::IsChecked } } -impl std::convert::TryFrom for CheckboxCondition { +impl std::convert::TryFrom for CheckboxFilterCondition { type Error = ErrorCode; fn try_from(value: u8) -> Result { match value { - 0 => Ok(CheckboxCondition::IsChecked), - 1 => Ok(CheckboxCondition::IsUnChecked), + 0 => Ok(CheckboxFilterCondition::IsChecked), + 1 => Ok(CheckboxFilterCondition::IsUnChecked), _ => Err(ErrorCode::InvalidData), } } } -impl std::convert::From> for CheckboxFilterConfigurationPB { - fn from(rev: Arc) -> Self { - CheckboxFilterConfigurationPB { - condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), +impl std::convert::From> for CheckboxFilterPB { + fn from(rev: Arc) -> Self { + CheckboxFilterPB { + condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked), } } } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs index db6fc55c0e..820971cf36 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs @@ -2,13 +2,13 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::FieldType; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use grid_rev_model::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct DateFilterConfigurationPB { +pub struct DateFilterPB { #[pb(index = 1)] pub condition: DateFilterCondition, @@ -98,6 +98,11 @@ pub enum DateFilterCondition { DateIsEmpty = 6, } +impl std::convert::From for u32 { + fn from(value: DateFilterCondition) -> Self { + value as u32 + } +} impl std::default::Default for DateFilterCondition { fn default() -> Self { DateFilterCondition::DateIs @@ -120,19 +125,15 @@ impl std::convert::TryFrom for DateFilterCondition { } } } -impl std::convert::From> for DateFilterConfigurationPB { - fn from(rev: Arc) -> Self { +impl std::convert::From> for DateFilterPB { + fn from(rev: Arc) -> Self { let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs); - let mut filter = DateFilterConfigurationPB { + let mut filter = DateFilterPB { condition, ..Default::default() }; - if let Some(range) = rev - .content - .as_ref() - .and_then(|content| DateRange::from_str(content).ok()) - { + if let Ok(range) = DateRange::from_str(&rev.content) { filter.start = range.start; filter.end = range.end; }; diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs index 628d6d38d4..1322a2decd 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs @@ -1,16 +1,16 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use grid_rev_model::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct NumberFilterConfigurationPB { +pub struct NumberFilterPB { #[pb(index = 1)] pub condition: NumberFilterCondition, - #[pb(index = 2, one_of)] - pub content: Option, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] @@ -32,9 +32,9 @@ impl std::default::Default for NumberFilterCondition { } } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: NumberFilterCondition) -> Self { - value as i32 + value as u32 } } impl std::convert::TryFrom for NumberFilterCondition { @@ -55,9 +55,9 @@ impl std::convert::TryFrom for NumberFilterCondition { } } -impl std::convert::From> for NumberFilterConfigurationPB { - fn from(rev: Arc) -> Self { - NumberFilterConfigurationPB { +impl std::convert::From> for NumberFilterPB { + fn from(rev: Arc) -> Self { + NumberFilterPB { condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), content: rev.content.clone(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs index 2ef40c687e..4c8ac601ee 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs @@ -1,11 +1,11 @@ use crate::services::field::SelectOptionIds; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use grid_rev_model::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct SelectOptionFilterConfigurationPB { +pub struct SelectOptionFilterPB { #[pb(index = 1)] pub condition: SelectOptionCondition, @@ -21,9 +21,9 @@ pub enum SelectOptionCondition { OptionIsNotEmpty = 3, } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: SelectOptionCondition) -> Self { - value as i32 + value as u32 } } @@ -47,10 +47,10 @@ impl std::convert::TryFrom for SelectOptionCondition { } } -impl std::convert::From> for SelectOptionFilterConfigurationPB { - fn from(rev: Arc) -> Self { +impl std::convert::From> for SelectOptionFilterPB { + fn from(rev: Arc) -> Self { let ids = SelectOptionIds::from(rev.content.clone()); - SelectOptionFilterConfigurationPB { + SelectOptionFilterPB { condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), option_ids: ids.into_inner(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs index 31e5318cac..ee6075c5be 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs @@ -1,15 +1,15 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use grid_rev_model::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct TextFilterConfigurationPB { +pub struct TextFilterPB { #[pb(index = 1)] pub condition: TextFilterCondition, - #[pb(index = 2, one_of)] - pub content: Option, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] @@ -25,9 +25,9 @@ pub enum TextFilterCondition { TextIsNotEmpty = 7, } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: TextFilterCondition) -> Self { - value as i32 + value as u32 } } @@ -54,9 +54,9 @@ impl std::convert::TryFrom for TextFilterCondition { } } -impl std::convert::From> for TextFilterConfigurationPB { - fn from(rev: Arc) -> Self { - TextFilterConfigurationPB { +impl std::convert::From> for TextFilterPB { + fn from(rev: Arc) -> Self { + TextFilterPB { condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), content: rev.content.clone(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs index 3f0d5b7125..e3c44c67a3 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs @@ -1,16 +1,17 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{ - CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, + CheckboxFilterCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, TextFilterCondition, }; +use crate::services::filter::FilterType; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision}; +use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision}; use std::convert::TryInto; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridFilterConfigurationPB { +pub struct FilterPB { #[pb(index = 1)] pub id: String, } @@ -18,25 +19,25 @@ pub struct GridFilterConfigurationPB { #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedGridFilterConfigurationPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfigurationPB { - fn from(rev: &FilterConfigurationRevision) -> Self { +impl std::convert::From<&FilterRevision> for FilterPB { + fn from(rev: &FilterRevision) -> Self { Self { id: rev.id.clone() } } } -impl std::convert::From>> for RepeatedGridFilterConfigurationPB { - fn from(revs: Vec>) -> Self { +impl std::convert::From>> for RepeatedGridFilterConfigurationPB { + fn from(revs: Vec>) -> Self { RepeatedGridFilterConfigurationPB { items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } -impl std::convert::From> for RepeatedGridFilterConfigurationPB { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedGridFilterConfigurationPB { + fn from(items: Vec) -> Self { Self { items } } } @@ -47,10 +48,10 @@ pub struct DeleteFilterPayloadPB { pub field_id: String, #[pb(index = 2)] - pub filter_id: String, + pub field_type: FieldType, #[pb(index = 3)] - pub field_type: FieldType, + pub filter_id: String, } impl TryInto for DeleteFilterPayloadPB { @@ -60,25 +61,27 @@ impl TryInto for DeleteFilterPayloadPB { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; + let filter_id = NotEmptyStr::parse(self.filter_id) .map_err(|_| ErrorCode::UnexpectedEmptyString)? .0; - Ok(DeleteFilterParams { + + let filter_type = FilterType { field_id, - filter_id, - field_type_rev: self.field_type.into(), - }) + field_type: self.field_type, + }; + + Ok(DeleteFilterParams { filter_id, filter_type }) } } pub struct DeleteFilterParams { - pub field_id: String, + pub filter_type: FilterType, pub filter_id: String, - pub field_type_rev: FieldTypeRevision, } #[derive(ProtoBuf, Debug, Default, Clone)] -pub struct InsertFilterPayloadPB { +pub struct CreateFilterPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -86,15 +89,15 @@ pub struct InsertFilterPayloadPB { pub field_type: FieldType, #[pb(index = 3)] - pub condition: i32, + pub condition: u32, - #[pb(index = 4, one_of)] - pub content: Option, + #[pb(index = 4)] + pub content: String, } -impl InsertFilterPayloadPB { +impl CreateFilterPayloadPB { #[allow(dead_code)] - pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { + pub fn new>(field_rev: &FieldRevision, condition: T, content: String) -> Self { Self { field_id: field_rev.id.clone(), field_type: field_rev.ty.into(), @@ -104,10 +107,10 @@ impl InsertFilterPayloadPB { } } -impl TryInto for InsertFilterPayloadPB { +impl TryInto for CreateFilterPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; @@ -117,7 +120,7 @@ impl TryInto for InsertFilterPayloadPB { let _ = TextFilterCondition::try_from(condition)?; } FieldType::Checkbox => { - let _ = CheckboxCondition::try_from(condition)?; + let _ = CheckboxFilterCondition::try_from(condition)?; } FieldType::Number => { let _ = NumberFilterCondition::try_from(condition)?; @@ -130,7 +133,7 @@ impl TryInto for InsertFilterPayloadPB { } } - Ok(InsertFilterParams { + Ok(CreateFilterParams { field_id, field_type_rev: self.field_type.into(), condition, @@ -139,9 +142,9 @@ impl TryInto for InsertFilterPayloadPB { } } -pub struct InsertFilterParams { +pub struct CreateFilterParams { pub field_id: String, pub field_type_rev: FieldTypeRevision, pub condition: u8, - pub content: Option, + pub content: String, } diff --git a/frontend/rust-lib/flowy-grid/src/entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/mod.rs index 2246b3d41c..44daf6bedc 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/mod.rs @@ -1,12 +1,12 @@ -mod block_entities; +pub mod block_entities; mod cell_entities; mod field_entities; -mod filter_entities; +pub mod filter_entities; mod grid_entities; mod group_entities; pub mod parser; mod row_entities; -mod setting_entities; +pub mod setting_entities; pub use block_entities::*; pub use cell_entities::*; diff --git a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs index fc89402ae5..e42907ed8b 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs @@ -1,7 +1,7 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{ - DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, InsertFilterParams, - InsertFilterPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB, + CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, + DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB, RepeatedGridGroupConfigurationPB, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; @@ -84,7 +84,7 @@ pub struct GridSettingChangesetPayloadPB { pub layout_type: GridLayout, #[pb(index = 3, one_of)] - pub insert_filter: Option, + pub insert_filter: Option, #[pb(index = 4, one_of)] pub delete_filter: Option, @@ -138,7 +138,7 @@ impl TryInto for GridSettingChangesetPayloadPB { pub struct GridSettingChangesetParams { pub grid_id: String, pub layout_type: LayoutRevision, - pub insert_filter: Option, + pub insert_filter: Option, pub delete_filter: Option, pub insert_group: Option, pub delete_group: Option, diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index aeab802f83..e3b5fbb037 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -7,7 +7,7 @@ use crate::services::field::{ SelectOptionCellChangesetPayloadPB, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPayloadPB, SelectOptionPB, }; -use crate::services::row::make_row_from_row_rev; +use crate::services::row::{make_block_pbs, make_row_from_row_rev}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use grid_rev_model::FieldRevision; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; @@ -20,7 +20,7 @@ pub(crate) async fn get_grid_handler( ) -> DataResult { let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; - let grid = editor.get_grid_data().await?; + let grid = editor.get_grid().await?; data_result(grid) } @@ -31,7 +31,7 @@ pub(crate) async fn get_grid_setting_handler( ) -> DataResult { let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; - let grid_setting = editor.get_grid_setting().await?; + let grid_setting = editor.get_setting().await?; data_result(grid_setting) } @@ -68,8 +68,8 @@ pub(crate) async fn get_grid_blocks_handler( ) -> DataResult { let params: QueryGridBlocksParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id).await?; - let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?; - data_result(repeated_grid_block) + let blocks = editor.get_blocks(Some(params.block_ids)).await?; + data_result(make_block_pbs(blocks)) } #[tracing::instrument(level = "trace", skip(data, manager), err)] diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index e19663e8db..080a3b8d5c 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -7,7 +7,6 @@ use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::migration::GridMigration; use crate::services::persistence::rev_sqlite::SQLiteGridRevisionPersistence; use crate::services::persistence::GridDatabase; -use crate::services::tasks::GridTaskScheduler; use bytes::Bytes; use flowy_database::ConnectionPool; @@ -22,6 +21,7 @@ use grid_rev_model::{BuildGridContext, GridRevision, GridViewRevision}; use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; use crate::services::block_manager::make_grid_block_rev_manager; +use flowy_task::TaskDispatcher; use std::sync::Arc; use tokio::sync::RwLock; @@ -31,15 +31,13 @@ pub trait GridUser: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; } -pub type GridTaskSchedulerRwLock = Arc>; - pub struct GridManager { grid_editors: RwLock>>, grid_user: Arc, block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, - task_scheduler: GridTaskSchedulerRwLock, + task_scheduler: Arc>, migration: GridMigration, } @@ -47,12 +45,12 @@ impl GridManager { pub fn new( grid_user: Arc, _rev_web_socket: Arc, + task_scheduler: Arc>, database: Arc, ) -> Self { let grid_editors = RwLock::new(RefCountHashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); let block_index_cache = Arc::new(BlockIndexCache::new(database.clone())); - let task_scheduler = GridTaskScheduler::new(); let migration = GridMigration::new(grid_user.clone(), database); Self { grid_editors, @@ -111,7 +109,7 @@ impl GridManager { tracing::Span::current().record("grid_id", &grid_id); self.grid_editors.write().await.remove(grid_id); - self.task_scheduler.write().await.unregister_handler(grid_id); + // self.task_scheduler.write().await.unregister_handler(grid_id); Ok(()) } @@ -134,7 +132,7 @@ impl GridManager { .write() .await .insert(grid_id.to_string(), editor.clone()); - self.task_scheduler.write().await.register_handler(editor.clone()); + // self.task_scheduler.write().await.register_handler(editor.clone()); Ok(editor) } diff --git a/frontend/rust-lib/flowy-grid/src/services/block_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_editor.rs index 19483803bd..b8e1fc1874 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_editor.rs @@ -134,10 +134,10 @@ impl GridBlockRevisionEditor { pub async fn get_row_pb(&self, row_id: &str) -> FlowyResult> { let row_ids = Some(vec![Cow::Borrowed(row_id)]); - Ok(self.get_row_infos(row_ids).await?.pop()) + Ok(self.get_row_pbs(row_ids).await?.pop()) } - pub async fn get_row_infos(&self, row_ids: Option>>) -> FlowyResult> + pub async fn get_row_pbs(&self, row_ids: Option>>) -> FlowyResult> where T: AsRef + ToOwned + ?Sized, { diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs index 40c9d774c7..d23dc8cfd9 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs @@ -4,7 +4,7 @@ use crate::manager::GridUser; use crate::services::block_editor::{GridBlockRevisionCompress, GridBlockRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::rev_sqlite::SQLiteGridBlockRevisionPersistence; -use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlockSnapshot}; +use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlock}; use dashmap::DashMap; use flowy_database::ConnectionPool; use flowy_error::FlowyResult; @@ -209,34 +209,31 @@ impl GridBlockManager { } } - pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult> { + pub async fn get_row_revs(&self, block_id: &str) -> FlowyResult>> { let editor = self.get_block_editor(block_id).await?; - editor.get_row_infos::<&str>(None).await + editor.get_row_revs::<&str>(None).await } - pub(crate) async fn get_block_snapshots( - &self, - block_ids: Option>, - ) -> FlowyResult> { - let mut snapshots = vec![]; + pub(crate) async fn get_blocks(&self, block_ids: Option>) -> FlowyResult> { + let mut blocks = vec![]; match block_ids { None => { for iter in self.block_editors.iter() { let editor = iter.value(); let block_id = editor.block_id.clone(); let row_revs = editor.get_row_revs::<&str>(None).await?; - snapshots.push(GridBlockSnapshot { block_id, row_revs }); + blocks.push(GridBlock { block_id, row_revs }); } } Some(block_ids) => { for block_id in block_ids { let editor = self.get_block_editor(&block_id).await?; let row_revs = editor.get_row_revs::<&str>(None).await?; - snapshots.push(GridBlockSnapshot { block_id, row_revs }); + blocks.push(GridBlock { block_id, row_revs }); } } } - Ok(snapshots) + Ok(blocks) } async fn notify_did_update_block(&self, block_id: &str, changeset: GridBlockChangesetPB) -> FlowyResult<()> { diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs deleted file mode 100644 index 8ba1234d57..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::services::block_manager::GridBlockManager; -use crate::services::grid_view_manager::GridViewRowDelegate; - -use grid_rev_model::RowRevision; -use lib_infra::future::{wrap_future, AFFuture}; -use std::sync::Arc; - -impl GridViewRowDelegate for Arc { - fn gv_index_of_row(&self, row_id: &str) -> AFFuture> { - let block_manager = self.clone(); - let row_id = row_id.to_owned(); - wrap_future(async move { block_manager.index_of_row(&row_id).await }) - } - - fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>> { - let block_manager = self.clone(); - let row_id = row_id.to_owned(); - wrap_future(async move { - match block_manager.get_row_rev(&row_id).await { - Ok(row_rev) => row_rev, - Err(_) => None, - } - }) - } - - fn gv_row_revs(&self) -> AFFuture>> { - let block_manager = self.clone(); - - wrap_future(async move { - let blocks = block_manager.get_block_snapshots(None).await.unwrap(); - blocks - .into_iter() - .flat_map(|block| block.row_revs) - .collect::>>() - }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs similarity index 67% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs index 3239ff449d..5c3964cc4d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs @@ -1,20 +1,20 @@ -use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB}; +use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB}; use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB}; use flowy_error::FlowyResult; -impl CheckboxFilterConfigurationPB { +impl CheckboxFilterPB { pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { let is_check = cell_data.is_check(); match self.condition { - CheckboxCondition::IsChecked => is_check, - CheckboxCondition::IsUnChecked => !is_check, + CheckboxFilterCondition::IsChecked => is_check, + CheckboxFilterCondition::IsUnChecked => !is_check, } } } -impl CellFilterOperation for CheckboxTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &CheckboxFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for CheckboxTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &CheckboxFilterPB) -> FlowyResult { if !any_cell_data.is_checkbox() { return Ok(true); } @@ -26,14 +26,14 @@ impl CellFilterOperation for CheckboxTypeOptionPB #[cfg(test)] mod tests { - use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB}; + use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB}; use crate::services::field::CheckboxCellData; use std::str::FromStr; #[test] fn checkbox_filter_is_check_test() { - let checkbox_filter = CheckboxFilterConfigurationPB { - condition: CheckboxCondition::IsChecked, + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterCondition::IsChecked, }; for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] { let data = CheckboxCellData::from_str(value).unwrap(); @@ -43,8 +43,8 @@ mod tests { #[test] fn checkbox_filter_is_uncheck_test() { - let checkbox_filter = CheckboxFilterConfigurationPB { - condition: CheckboxCondition::IsUnChecked, + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterCondition::IsUnChecked, }; for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] { let data = CheckboxCellData::from_str(value).unwrap(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs index ebe5d1a6a8..309072caa6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod checkbox_filter; mod checkbox_tests; mod checkbox_type_option; mod checkbox_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs similarity index 85% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs index 18d968d2a4..ab997cc029 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs @@ -1,9 +1,9 @@ -use crate::entities::{DateFilterCondition, DateFilterConfigurationPB}; +use crate::entities::{DateFilterCondition, DateFilterPB}; use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; use crate::services::field::{DateTimestamp, DateTypeOptionPB}; use flowy_error::FlowyResult; -impl DateFilterConfigurationPB { +impl DateFilterPB { pub fn is_visible>(&self, cell_timestamp: T) -> bool { if self.start.is_none() { return false; @@ -29,8 +29,8 @@ impl DateFilterConfigurationPB { } } -impl CellFilterOperation for DateTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &DateFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for DateTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &DateFilterPB) -> FlowyResult { if !any_cell_data.is_date() { return Ok(true); } @@ -43,11 +43,11 @@ impl CellFilterOperation for DateTypeOptionPB { #[cfg(test)] mod tests { #![allow(clippy::all)] - use crate::entities::{DateFilterCondition, DateFilterConfigurationPB}; + use crate::entities::{DateFilterCondition, DateFilterPB}; #[test] fn date_filter_is_test() { - let filter = DateFilterConfigurationPB { + let filter = DateFilterPB { condition: DateFilterCondition::DateIs, start: Some(123), end: None, @@ -59,7 +59,7 @@ mod tests { } #[test] fn date_filter_before_test() { - let filter = DateFilterConfigurationPB { + let filter = DateFilterPB { condition: DateFilterCondition::DateBefore, start: Some(123), end: None, @@ -71,7 +71,7 @@ mod tests { } #[test] fn date_filter_before_or_on_test() { - let filter = DateFilterConfigurationPB { + let filter = DateFilterPB { condition: DateFilterCondition::DateOnOrBefore, start: Some(123), end: None, @@ -83,7 +83,7 @@ mod tests { } #[test] fn date_filter_after_test() { - let filter = DateFilterConfigurationPB { + let filter = DateFilterPB { condition: DateFilterCondition::DateAfter, start: Some(123), end: None, @@ -95,7 +95,7 @@ mod tests { } #[test] fn date_filter_within_test() { - let filter = DateFilterConfigurationPB { + let filter = DateFilterPB { condition: DateFilterCondition::DateWithIn, start: Some(123), end: Some(130), diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs index 395f2c9104..ff0c344957 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod date_filter; mod date_tests; mod date_type_option; mod date_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs index 4b2bcc1ecd..8136fb57c5 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::module_inception)] mod format; +mod number_filter; mod number_tests; mod number_type_option; mod number_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs similarity index 55% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs index 45ae0ac464..28d7cb46f0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs @@ -1,4 +1,4 @@ -use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB}; +use crate::entities::{NumberFilterCondition, NumberFilterPB}; use crate::services::cell::{AnyCellData, CellFilterOperation}; use crate::services::field::{NumberCellData, NumberTypeOptionPB}; use flowy_error::FlowyResult; @@ -6,33 +6,39 @@ use rust_decimal::prelude::Zero; use rust_decimal::Decimal; use std::str::FromStr; -impl NumberFilterConfigurationPB { +impl NumberFilterPB { pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { - if self.content.is_none() { - return false; + if self.content.is_empty() { + match self.condition { + NumberFilterCondition::NumberIsEmpty => { + return num_cell_data.is_empty(); + } + NumberFilterCondition::NumberIsNotEmpty => { + return !num_cell_data.is_empty(); + } + _ => {} + } } - - let content = self.content.as_ref().unwrap(); - let zero_decimal = Decimal::zero(); - let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal); - match Decimal::from_str(content) { - Ok(decimal) => match self.condition { - NumberFilterCondition::Equal => cell_decimal == &decimal, - NumberFilterCondition::NotEqual => cell_decimal != &decimal, - NumberFilterCondition::GreaterThan => cell_decimal > &decimal, - NumberFilterCondition::LessThan => cell_decimal < &decimal, - NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, - NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, - NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(), - NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(), - }, - Err(_) => false, + match num_cell_data.decimal().as_ref() { + None => false, + Some(cell_decimal) => { + let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); + match self.condition { + NumberFilterCondition::Equal => cell_decimal == &decimal, + NumberFilterCondition::NotEqual => cell_decimal != &decimal, + NumberFilterCondition::GreaterThan => cell_decimal > &decimal, + NumberFilterCondition::LessThan => cell_decimal < &decimal, + NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, + NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, + _ => true, + } + } } } } -impl CellFilterOperation for NumberTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &NumberFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for NumberTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &NumberFilterPB) -> FlowyResult { if !any_cell_data.is_number() { return Ok(true); } @@ -46,13 +52,13 @@ impl CellFilterOperation for NumberTypeOptionPB { #[cfg(test)] mod tests { - use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB}; + use crate::entities::{NumberFilterCondition, NumberFilterPB}; use crate::services::field::{NumberCellData, NumberFormat}; #[test] fn number_filter_equal_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::Equal, - content: Some("123".to_owned()), + content: "123".to_owned(), }; for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { @@ -68,9 +74,9 @@ mod tests { } #[test] fn number_filter_greater_than_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::GreaterThan, - content: Some("12".to_owned()), + content: "12".to_owned(), }; for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); @@ -80,11 +86,11 @@ mod tests { #[test] fn number_filter_less_than_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::LessThan, - content: Some("100".to_owned()), + content: "100".to_owned(), }; - for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] { + for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); assert_eq!(number_filter.is_visible(&data), visible); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs index 9932566605..fe117d791f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs @@ -1,4 +1,5 @@ mod multi_select_type_option; +mod select_filter; mod select_type_option; mod single_select_type_option; mod type_option_transform; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs similarity index 83% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs index b8f330b72c..11aff9a21f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs @@ -1,12 +1,12 @@ #![allow(clippy::needless_collect)] -use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB}; +use crate::entities::{SelectOptionCondition, SelectOptionFilterPB}; use crate::services::cell::{AnyCellData, CellFilterOperation}; use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions}; use flowy_error::FlowyResult; -impl SelectOptionFilterConfigurationPB { +impl SelectOptionFilterPB { pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool { let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); match self.condition { @@ -39,12 +39,8 @@ impl SelectOptionFilterConfigurationPB { } } -impl CellFilterOperation for MultiSelectTypeOptionPB { - fn apply_filter( - &self, - any_cell_data: AnyCellData, - filter: &SelectOptionFilterConfigurationPB, - ) -> FlowyResult { +impl CellFilterOperation for MultiSelectTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &SelectOptionFilterPB) -> FlowyResult { if !any_cell_data.is_multi_select() { return Ok(true); } @@ -54,12 +50,8 @@ impl CellFilterOperation for MultiSelectTypeO } } -impl CellFilterOperation for SingleSelectTypeOptionPB { - fn apply_filter( - &self, - any_cell_data: AnyCellData, - filter: &SelectOptionFilterConfigurationPB, - ) -> FlowyResult { +impl CellFilterOperation for SingleSelectTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &SelectOptionFilterPB) -> FlowyResult { if !any_cell_data.is_single_select() { return Ok(true); } @@ -71,7 +63,7 @@ impl CellFilterOperation for SingleSelectType #[cfg(test)] mod tests { #![allow(clippy::all)] - use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB}; + use crate::entities::{SelectOptionCondition, SelectOptionFilterPB}; use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; #[test] @@ -80,7 +72,7 @@ mod tests { let option_2 = SelectOptionPB::new("B"); let option_3 = SelectOptionPB::new("C"); - let filter_1 = SelectOptionFilterConfigurationPB { + let filter_1 = SelectOptionFilterPB { condition: SelectOptionCondition::OptionIs, option_ids: vec![option_1.id.clone(), option_2.id.clone()], }; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs index b6bf9a1a1b..9537ee8f33 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod text_filter; mod text_tests; mod text_type_option; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs similarity index 59% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs index 86cb2aadfa..12e96f40ab 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs @@ -1,31 +1,27 @@ -use crate::entities::{TextFilterCondition, TextFilterConfigurationPB}; +use crate::entities::{TextFilterCondition, TextFilterPB}; use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; use crate::services::field::{RichTextTypeOptionPB, TextCellData}; use flowy_error::FlowyResult; -impl TextFilterConfigurationPB { +impl TextFilterPB { pub fn is_visible>(&self, cell_data: T) -> bool { - let cell_data = cell_data.as_ref(); - let s = cell_data.to_lowercase(); - if let Some(content) = self.content.as_ref() { - match self.condition { - TextFilterCondition::Is => &s == content, - TextFilterCondition::IsNot => &s != content, - TextFilterCondition::Contains => s.contains(content), - TextFilterCondition::DoesNotContain => !s.contains(content), - TextFilterCondition::StartsWith => s.starts_with(content), - TextFilterCondition::EndsWith => s.ends_with(content), - TextFilterCondition::TextIsEmpty => s.is_empty(), - TextFilterCondition::TextIsNotEmpty => !s.is_empty(), - } - } else { - false + let cell_data = cell_data.as_ref().to_lowercase(); + let content = &self.content.to_lowercase(); + match self.condition { + TextFilterCondition::Is => &cell_data == content, + TextFilterCondition::IsNot => &cell_data != content, + TextFilterCondition::Contains => cell_data.contains(content), + TextFilterCondition::DoesNotContain => !cell_data.contains(content), + TextFilterCondition::StartsWith => cell_data.starts_with(content), + TextFilterCondition::EndsWith => cell_data.ends_with(content), + TextFilterCondition::TextIsEmpty => cell_data.is_empty(), + TextFilterCondition::TextIsNotEmpty => !cell_data.is_empty(), } } } -impl CellFilterOperation for RichTextTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for RichTextTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterPB) -> FlowyResult { if !any_cell_data.is_text() { return Ok(true); } @@ -38,13 +34,13 @@ impl CellFilterOperation for RichTextTypeOptionPB { #[cfg(test)] mod tests { #![allow(clippy::all)] - use crate::entities::{TextFilterCondition, TextFilterConfigurationPB}; + use crate::entities::{TextFilterCondition, TextFilterPB}; #[test] fn text_filter_equal_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::Is, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert!(text_filter.is_visible("AppFlowy")); @@ -54,9 +50,9 @@ mod tests { } #[test] fn text_filter_start_with_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::StartsWith, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("AppFlowy.io"), true); @@ -66,9 +62,9 @@ mod tests { #[test] fn text_filter_end_with_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::EndsWith, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); @@ -77,9 +73,9 @@ mod tests { } #[test] fn text_filter_empty_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::TextIsEmpty, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible(""), true); @@ -87,9 +83,9 @@ mod tests { } #[test] fn text_filter_contain_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::Contains, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs index 8f6cb884df..bbe195c628 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod url_filter; mod url_tests; mod url_type_option; mod url_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs similarity index 73% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs index 4f0a7b93cd..ee9340bed7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs @@ -1,10 +1,10 @@ -use crate::entities::TextFilterConfigurationPB; +use crate::entities::TextFilterPB; use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; use crate::services::field::{TextCellData, URLTypeOptionPB}; use flowy_error::FlowyResult; -impl CellFilterOperation for URLTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for URLTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterPB) -> FlowyResult { if !any_cell_data.is_url() { return Ok(true); } diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs new file mode 100644 index 0000000000..d424b8b0f2 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs @@ -0,0 +1,94 @@ +use crate::entities::{CheckboxFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB}; +use crate::services::filter::FilterType; + +use std::collections::HashMap; + +#[derive(Default)] +pub(crate) struct FilterMap { + pub(crate) text_filter: HashMap, + pub(crate) url_filter: HashMap, + pub(crate) number_filter: HashMap, + pub(crate) date_filter: HashMap, + pub(crate) select_option_filter: HashMap, + pub(crate) checkbox_filter: HashMap, +} + +impl FilterMap { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn is_empty(&self) -> bool { + if !self.text_filter.is_empty() { + return false; + } + + if !self.url_filter.is_empty() { + return false; + } + + if !self.number_filter.is_empty() { + return false; + } + + if !self.number_filter.is_empty() { + return false; + } + + if !self.date_filter.is_empty() { + return false; + } + + if !self.select_option_filter.is_empty() { + return false; + } + + if !self.checkbox_filter.is_empty() { + return false; + } + true + } + + pub(crate) fn remove(&mut self, filter_id: &FilterType) { + let _ = match filter_id.field_type { + FieldType::RichText => { + let _ = self.text_filter.remove(filter_id); + } + FieldType::Number => { + let _ = self.number_filter.remove(filter_id); + } + FieldType::DateTime => { + let _ = self.date_filter.remove(filter_id); + } + FieldType::SingleSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::MultiSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::Checkbox => { + let _ = self.checkbox_filter.remove(filter_id); + } + FieldType::URL => { + let _ = self.url_filter.remove(filter_id); + } + }; + } +} + +/// Refresh the filter according to the field id. +#[derive(Default)] +pub(crate) struct FilterResult { + pub(crate) visible_by_filter_id: HashMap, +} + +impl FilterResult { + pub(crate) fn is_visible(&self) -> bool { + for visible in self.visible_by_filter_id.values() { + if visible == &false { + return false; + } + } + true + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs b/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs new file mode 100644 index 0000000000..a56e60fac2 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs @@ -0,0 +1,405 @@ +use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::filter_entities::*; +use crate::entities::setting_entities::*; +use crate::entities::{FieldType, GridBlockChangesetPB}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::*; +use crate::services::filter::{FilterMap, FilterResult, FILTER_HANDLER_ID}; +use crate::services::row::GridBlock; +use flowy_error::FlowyResult; +use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; +use grid_rev_model::{CellRevision, FieldId, FieldRevision, FieldTypeRevision, FilterRevision, RowRevision}; +use lib_infra::future::Fut; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +type RowId = String; +pub trait GridViewFilterDelegate: Send + Sync + 'static { + fn get_filter_rev(&self, filter_id: FilterType) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_blocks(&self) -> Fut>; +} + +pub struct FilterController { + view_id: String, + delegate: Box, + filter_map: FilterMap, + result_by_row_id: HashMap, + task_scheduler: Arc>, +} +impl FilterController { + pub async fn new( + view_id: &str, + delegate: T, + task_scheduler: Arc>, + filter_revs: Vec>, + ) -> Self + where + T: GridViewFilterDelegate, + { + let mut this = Self { + view_id: view_id.to_string(), + delegate: Box::new(delegate), + filter_map: FilterMap::new(), + result_by_row_id: HashMap::default(), + task_scheduler, + }; + this.load_filters(filter_revs).await; + this + } + + pub async fn close(&self) { + self.task_scheduler.write().await.unregister_handler(FILTER_HANDLER_ID); + } + + #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] + async fn gen_task(&mut self, predicate: &str) { + let task_id = self.task_scheduler.read().await.next_task_id(); + let task = Task::new( + FILTER_HANDLER_ID, + task_id, + TaskContent::Text(predicate.to_owned()), + QualityOfService::UserInteractive, + ); + self.task_scheduler.write().await.add_task(task); + } + + pub async fn filter_row_revs(&mut self, row_revs: &mut Vec>) { + if self.filter_map.is_empty() { + return; + } + let field_rev_by_field_id = self.get_filter_revs_map().await; + let _ = row_revs + .iter() + .flat_map(|row_rev| { + filter_row( + row_rev, + &self.filter_map, + &mut self.result_by_row_id, + &field_rev_by_field_id, + ) + }) + .collect::>(); + + row_revs.retain(|row_rev| { + self.result_by_row_id + .get(&row_rev.id) + .map(|result| result.is_visible()) + .unwrap_or(false) + }); + } + + async fn get_filter_revs_map(&self) -> HashMap> { + self.delegate + .get_field_revs(None) + .await + .into_iter() + .map(|field_rev| (field_rev.id.clone(), field_rev)) + .collect::>>() + } + + pub async fn process(&mut self, _predicate: &str) -> FlowyResult<()> { + let field_rev_by_field_id = self.get_filter_revs_map().await; + let mut changesets = vec![]; + for block in self.delegate.get_blocks().await.into_iter() { + // The row_ids contains the row that its visibility was changed. + let row_ids = block + .row_revs + .iter() + .flat_map(|row_rev| { + filter_row( + row_rev, + &self.filter_map, + &mut self.result_by_row_id, + &field_rev_by_field_id, + ) + }) + .collect::>(); + + let mut visible_rows = vec![]; + let mut hide_rows = vec![]; + + // Query the filter result from the cache + for row_id in row_ids { + if self + .result_by_row_id + .get(&row_id) + .map(|result| result.is_visible()) + .unwrap_or(false) + { + visible_rows.push(row_id); + } else { + hide_rows.push(row_id); + } + } + + let changeset = GridBlockChangesetPB { + block_id: block.block_id, + hide_rows, + visible_rows, + ..Default::default() + }; + + // Save the changeset for each block + changesets.push(changeset); + } + + self.notify(changesets).await; + Ok(()) + } + + pub async fn apply_changeset(&mut self, changeset: FilterChangeset) { + if let Some(filter_id) = &changeset.insert_filter { + let filter_revs = self.delegate.get_filter_rev(filter_id.clone()).await; + let _ = self.load_filters(filter_revs).await; + } + + if let Some(filter_id) = &changeset.delete_filter { + self.filter_map.remove(filter_id); + } + + self.gen_task("").await; + } + + async fn notify(&self, changesets: Vec) { + for changeset in changesets { + send_dart_notification(&self.view_id, GridNotification::DidUpdateGridBlock) + .payload(changeset) + .send(); + } + } + + async fn load_filters(&mut self, filter_revs: Vec>) { + for filter_rev in filter_revs { + if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await { + let filter_type = FilterType::from(&field_rev); + let field_type: FieldType = field_rev.ty.into(); + match &field_type { + FieldType::RichText => { + let _ = self + .filter_map + .text_filter + .insert(filter_type, TextFilterPB::from(filter_rev)); + } + FieldType::Number => { + let _ = self + .filter_map + .number_filter + .insert(filter_type, NumberFilterPB::from(filter_rev)); + } + FieldType::DateTime => { + let _ = self + .filter_map + .date_filter + .insert(filter_type, DateFilterPB::from(filter_rev)); + } + FieldType::SingleSelect | FieldType::MultiSelect => { + let _ = self + .filter_map + .select_option_filter + .insert(filter_type, SelectOptionFilterPB::from(filter_rev)); + } + FieldType::Checkbox => { + let _ = self + .filter_map + .checkbox_filter + .insert(filter_type, CheckboxFilterPB::from(filter_rev)); + } + FieldType::URL => { + let _ = self + .filter_map + .url_filter + .insert(filter_type, TextFilterPB::from(filter_rev)); + } + } + } + } + } +} + +/// Returns None if there is no change in this row after applying the filter +fn filter_row( + row_rev: &Arc, + filter_map: &FilterMap, + result_by_row_id: &mut HashMap, + field_rev_by_field_id: &HashMap>, +) -> Option { + // Create a filter result cache if it's not exist + let filter_result = result_by_row_id + .entry(row_rev.id.clone()) + .or_insert_with(FilterResult::default); + + // Iterate each cell of the row to check its visibility + for (field_id, cell_rev) in row_rev.cells.iter() { + let field_rev = field_rev_by_field_id.get(field_id)?; + let filter_type = FilterType::from(field_rev); + + // if the visibility of the cell_rew is changed, which means the visibility of the + // row is changed too. + if let Some(is_visible) = filter_cell(&filter_type, field_rev, filter_map, cell_rev) { + let prev_is_visible = filter_result.visible_by_filter_id.get(&filter_type).cloned(); + filter_result.visible_by_filter_id.insert(filter_type, is_visible); + match prev_is_visible { + None => { + if !is_visible { + return Some(row_rev.id.clone()); + } + } + Some(prev_is_visible) => { + if prev_is_visible != is_visible { + return Some(row_rev.id.clone()); + } + } + } + } + } + None +} + +// Return None if there is no change in this cell after applying the filter +fn filter_cell( + filter_id: &FilterType, + field_rev: &Arc, + filter_map: &FilterMap, + cell_rev: &CellRevision, +) -> Option { + let any_cell_data = AnyCellData::try_from(cell_rev).ok()?; + let is_visible = match &filter_id.field_type { + FieldType::RichText => filter_map.text_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::Number => filter_map.number_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::DateTime => filter_map.date_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::SingleSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::MultiSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::Checkbox => filter_map.checkbox_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::URL => filter_map.url_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + }?; + + is_visible +} + +pub struct FilterChangeset { + insert_filter: Option, + delete_filter: Option, +} + +impl FilterChangeset { + pub fn from_insert(filter_id: FilterType) -> Self { + Self { + insert_filter: Some(filter_id), + delete_filter: None, + } + } + + pub fn from_delete(filter_id: FilterType) -> Self { + Self { + insert_filter: None, + delete_filter: Some(filter_id), + } + } +} + +impl std::convert::From<&GridSettingChangesetParams> for FilterChangeset { + fn from(params: &GridSettingChangesetParams) -> Self { + let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterType { + field_id: insert_filter_params.field_id.clone(), + field_type: insert_filter_params.field_type_rev.into(), + }); + + let delete_filter = params + .delete_filter + .as_ref() + .map(|delete_filter_params| delete_filter_params.filter_type.clone()); + FilterChangeset { + insert_filter, + delete_filter, + } + } +} + +#[derive(Hash, Eq, PartialEq, Clone)] +pub struct FilterType { + pub field_id: String, + pub field_type: FieldType, +} + +impl FilterType { + pub fn field_type_rev(&self) -> FieldTypeRevision { + self.field_type.clone().into() + } +} + +impl std::convert::From<&Arc> for FilterType { + fn from(rev: &Arc) -> Self { + Self { + field_id: rev.id.clone(), + field_type: rev.ty.into(), + } + } +} + +impl std::convert::From<&CreateFilterParams> for FilterType { + fn from(params: &CreateFilterParams) -> Self { + let field_type: FieldType = params.field_type_rev.into(); + Self { + field_id: params.field_id.clone(), + field_type, + } + } +} + +impl std::convert::From<&DeleteFilterParams> for FilterType { + fn from(params: &DeleteFilterParams) -> Self { + params.filter_type.clone() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs deleted file mode 100644 index 2dec371ade..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::entities::{ - CheckboxFilterConfigurationPB, DateFilterConfigurationPB, FieldType, NumberFilterConfigurationPB, - SelectOptionFilterConfigurationPB, TextFilterConfigurationPB, -}; -use dashmap::DashMap; -use flowy_sync::client_grid::GridRevisionPad; -use grid_rev_model::{FieldRevision, FilterConfigurationRevision, RowRevision}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -type RowId = String; - -#[derive(Default)] -pub(crate) struct FilterResultCache { - // key: row id - inner: DashMap, -} - -impl FilterResultCache { - pub fn new() -> Arc { - let this = Self::default(); - Arc::new(this) - } -} - -impl std::ops::Deref for FilterResultCache { - type Target = DashMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[derive(Default)] -pub(crate) struct FilterResult { - #[allow(dead_code)] - pub(crate) row_index: i32, - pub(crate) visible_by_field_id: HashMap, -} - -impl FilterResult { - pub(crate) fn new(index: i32, _row_rev: &RowRevision) -> Self { - Self { - row_index: index, - visible_by_field_id: HashMap::new(), - } - } - - pub(crate) fn is_visible(&self) -> bool { - for visible in self.visible_by_field_id.values() { - if visible == &false { - return false; - } - } - true - } -} - -#[derive(Default)] -pub(crate) struct FilterCache { - pub(crate) text_filter: DashMap, - pub(crate) url_filter: DashMap, - pub(crate) number_filter: DashMap, - pub(crate) date_filter: DashMap, - pub(crate) select_option_filter: DashMap, - pub(crate) checkbox_filter: DashMap, -} - -impl FilterCache { - pub(crate) async fn from_grid_pad(grid_pad: &Arc>) -> Arc { - let this = Arc::new(Self::default()); - let _ = refresh_filter_cache(this.clone(), None, grid_pad).await; - this - } - - #[allow(dead_code)] - pub(crate) fn remove(&self, filter_id: &FilterId) { - let _ = match filter_id.field_type { - FieldType::RichText => { - let _ = self.text_filter.remove(filter_id); - } - FieldType::Number => { - let _ = self.number_filter.remove(filter_id); - } - FieldType::DateTime => { - let _ = self.date_filter.remove(filter_id); - } - FieldType::SingleSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::MultiSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::Checkbox => { - let _ = self.checkbox_filter.remove(filter_id); - } - FieldType::URL => { - let _ = self.url_filter.remove(filter_id); - } - }; - } -} - -/// Refresh the filter according to the field id. -pub(crate) async fn refresh_filter_cache( - cache: Arc, - _field_ids: Option>, - grid_pad: &Arc>, -) { - let grid_pad = grid_pad.read().await; - // let filters_revs = grid_pad.get_filters(field_ids).unwrap_or_default(); - // TODO nathan - let filter_revs: Vec> = vec![]; - - for filter_rev in filter_revs { - match grid_pad.get_field_rev(&filter_rev.field_id) { - None => {} - Some((_, field_rev)) => { - let filter_id = FilterId::from(field_rev); - let field_type: FieldType = field_rev.ty.into(); - match &field_type { - FieldType::RichText => { - let _ = cache - .text_filter - .insert(filter_id, TextFilterConfigurationPB::from(filter_rev)); - } - FieldType::Number => { - let _ = cache - .number_filter - .insert(filter_id, NumberFilterConfigurationPB::from(filter_rev)); - } - FieldType::DateTime => { - let _ = cache - .date_filter - .insert(filter_id, DateFilterConfigurationPB::from(filter_rev)); - } - FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = cache - .select_option_filter - .insert(filter_id, SelectOptionFilterConfigurationPB::from(filter_rev)); - } - FieldType::Checkbox => { - let _ = cache - .checkbox_filter - .insert(filter_id, CheckboxFilterConfigurationPB::from(filter_rev)); - } - FieldType::URL => { - let _ = cache - .url_filter - .insert(filter_id, TextFilterConfigurationPB::from(filter_rev)); - } - } - } - } - } -} -#[derive(Hash, Eq, PartialEq)] -pub(crate) struct FilterId { - pub(crate) field_id: String, - pub(crate) field_type: FieldType, -} - -impl std::convert::From<&Arc> for FilterId { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs deleted file mode 100644 index 8c1bf9a451..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ /dev/null @@ -1,294 +0,0 @@ -#![allow(clippy::all)] -#![allow(unused_attributes)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_results)] -use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{FieldType, GridBlockChangesetPB, GridSettingChangesetParams}; -use crate::services::block_manager::GridBlockManager; -use crate::services::cell::{AnyCellData, CellFilterOperation}; -use crate::services::field::{ - CheckboxTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB, RichTextTypeOptionPB, - SingleSelectTypeOptionPB, URLTypeOptionPB, -}; -use crate::services::filter::filter_cache::{ - refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache, -}; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::row::GridBlockSnapshot; -use crate::services::tasks::{FilterTaskContext, Task, TaskContent}; -use flowy_error::FlowyResult; -use flowy_sync::client_grid::GridRevisionPad; -use grid_rev_model::{CellRevision, FieldId, FieldRevision, RowRevision}; -use rayon::prelude::*; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub(crate) struct GridFilterService { - #[allow(dead_code)] - scheduler: Arc, - grid_pad: Arc>, - #[allow(dead_code)] - block_manager: Arc, - filter_cache: Arc, - filter_result_cache: Arc, -} -impl GridFilterService { - pub async fn new( - grid_pad: Arc>, - block_manager: Arc, - scheduler: S, - ) -> Self { - let scheduler = Arc::new(scheduler); - let filter_cache = FilterCache::from_grid_pad(&grid_pad).await; - let filter_result_cache = FilterResultCache::new(); - Self { - grid_pad, - block_manager, - scheduler, - filter_cache, - filter_result_cache, - } - } - - pub async fn process(&self, task_context: FilterTaskContext) -> FlowyResult<()> { - let field_revs = self - .grid_pad - .read() - .await - .get_field_revs(None)? - .into_iter() - .map(|field_rev| (field_rev.id.clone(), field_rev)) - .collect::>>(); - - let mut changesets = vec![]; - for (index, block) in task_context.blocks.into_iter().enumerate() { - // The row_ids contains the row that its visibility was changed. - let row_ids = block - .row_revs - .par_iter() - .flat_map(|row_rev| { - let filter_result_cache = self.filter_result_cache.clone(); - let filter_cache = self.filter_cache.clone(); - filter_row(index, row_rev, filter_cache, filter_result_cache, &field_revs) - }) - .collect::>(); - - let mut visible_rows = vec![]; - let mut hide_rows = vec![]; - - // Query the filter result from the cache - for row_id in row_ids { - if self - .filter_result_cache - .get(&row_id) - .map(|result| result.is_visible()) - .unwrap_or(false) - { - visible_rows.push(row_id); - } else { - hide_rows.push(row_id); - } - } - - let changeset = GridBlockChangesetPB { - block_id: block.block_id, - hide_rows, - visible_rows, - ..Default::default() - }; - - // Save the changeset for each block - changesets.push(changeset); - } - - self.notify(changesets).await; - Ok(()) - } - - pub async fn apply_changeset(&self, changeset: GridFilterChangeset) { - if !changeset.is_changed() { - return; - } - - if let Some(filter_id) = &changeset.insert_filter { - let field_ids = Some(vec![filter_id.field_id.clone()]); - refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await; - } - - if let Some(filter_id) = &changeset.delete_filter { - self.filter_cache.remove(filter_id); - } - - if let Ok(blocks) = self.block_manager.get_block_snapshots(None).await { - let _task = self.gen_task(blocks).await; - // let _ = self.scheduler.register_task(task).await; - } - } - - async fn gen_task(&self, blocks: Vec) -> Task { - let task_id = self.scheduler.gen_task_id().await; - let handler_id = self.grid_pad.read().await.grid_id(); - - let context = FilterTaskContext { blocks }; - Task::new(&handler_id, task_id, TaskContent::Filter(context)) - } - - async fn notify(&self, changesets: Vec) { - let grid_id = self.grid_pad.read().await.grid_id(); - for changeset in changesets { - send_dart_notification(&grid_id, GridNotification::DidUpdateGridBlock) - .payload(changeset) - .send(); - } - } -} - -// Return None if there is no change in this row after applying the filter -fn filter_row( - index: usize, - row_rev: &Arc, - filter_cache: Arc, - filter_result_cache: Arc, - field_revs: &HashMap>, -) -> Option { - let mut result = filter_result_cache - .entry(row_rev.id.clone()) - .or_insert(FilterResult::new(index as i32, row_rev)); - - for (field_id, cell_rev) in row_rev.cells.iter() { - match filter_cell(field_revs, result.value_mut(), &filter_cache, field_id, cell_rev) { - None => {} - Some(_) => { - return Some(row_rev.id.clone()); - } - } - } - None -} - -// Return None if there is no change in this cell after applying the filter -fn filter_cell( - field_revs: &HashMap>, - filter_result: &mut FilterResult, - filter_cache: &Arc, - field_id: &str, - cell_rev: &CellRevision, -) -> Option<()> { - let field_rev = field_revs.get(field_id)?; - let field_type = FieldType::from(field_rev.ty); - let field_type_rev = field_type.clone().into(); - let filter_id = FilterId { - field_id: field_id.to_owned(), - field_type, - }; - let any_cell_data = AnyCellData::try_from(cell_rev).ok()?; - let is_visible = match &filter_id.field_type { - FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - }?; - - let is_visible = !is_visible.unwrap_or(true); - match filter_result.visible_by_field_id.get(&filter_id) { - None => { - if is_visible { - None - } else { - filter_result.visible_by_field_id.insert(filter_id, is_visible); - Some(()) - } - } - Some(old_is_visible) => { - if old_is_visible != &is_visible { - filter_result.visible_by_field_id.insert(filter_id, is_visible); - Some(()) - } else { - None - } - } - } -} - -pub struct GridFilterChangeset { - insert_filter: Option, - delete_filter: Option, -} - -impl GridFilterChangeset { - fn is_changed(&self) -> bool { - self.insert_filter.is_some() || self.delete_filter.is_some() - } -} - -impl std::convert::From<&GridSettingChangesetParams> for GridFilterChangeset { - fn from(params: &GridSettingChangesetParams) -> Self { - let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterId { - field_id: insert_filter_params.field_id.clone(), - field_type: insert_filter_params.field_type_rev.into(), - }); - - let delete_filter = params.delete_filter.as_ref().map(|delete_filter_params| FilterId { - field_id: delete_filter_params.filter_id.clone(), - field_type: delete_filter_params.field_type_rev.into(), - }); - GridFilterChangeset { - insert_filter, - delete_filter, - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs deleted file mode 100644 index 6fe93ae58c..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod checkbox_filter; -mod date_filter; -mod number_filter; -mod select_option_filter; -mod text_filter; -mod url_filter; - -pub use checkbox_filter::*; -pub use date_filter::*; -pub use number_filter::*; -pub use select_option_filter::*; -pub use text_filter::*; -pub use url_filter::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs index 8a0067ff96..2eeeabc0ff 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs @@ -1,5 +1,7 @@ -mod filter_cache; -mod filter_service; -mod impls; +mod cache; +mod controller; +mod task; -pub(crate) use filter_service::*; +pub(crate) use cache::*; +pub use controller::*; +pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/task.rs b/frontend/rust-lib/flowy-grid/src/services/filter/task.rs new file mode 100644 index 0000000000..f26e23b987 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/task.rs @@ -0,0 +1,35 @@ +use crate::services::filter::FilterController; +use flowy_task::{TaskContent, TaskHandler}; +use lib_infra::future::BoxResultFuture; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub const FILTER_HANDLER_ID: &str = "grid_filter"; + +pub struct FilterTaskHandler(Arc>); +impl FilterTaskHandler { + pub fn new(filter_controller: Arc>) -> Self { + Self(filter_controller) + } +} + +impl TaskHandler for FilterTaskHandler { + fn handler_id(&self) -> &str { + FILTER_HANDLER_ID + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { + let filter_controller = self.0.clone(); + Box::pin(async move { + if let TaskContent::Text(predicate) = content { + let _ = filter_controller + .write() + .await + .process(&predicate) + .await + .map_err(anyhow::Error::from); + } + Ok(()) + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 698fcac573..0c77f04189 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,19 +1,21 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::entities::GridCellIdParams; use crate::entities::*; -use crate::manager::{GridTaskSchedulerRwLock, GridUser}; +use crate::manager::GridUser; use crate::services::block_manager::GridBlockManager; - use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes}; use crate::services::field::{ default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str, FieldBuilder, }; -use crate::services::filter::GridFilterService; + +use crate::services::filter::FilterType; +use crate::services::grid_editor_trait_impl::GridViewEditorDelegateImpl; use crate::services::grid_view_manager::GridViewManager; use crate::services::persistence::block_index::BlockIndexCache; -use crate::services::row::{make_grid_blocks, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder}; +use crate::services::row::{GridBlock, RowRevisionBuilder}; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_http_model::revision::Revision; use flowy_revision::{ @@ -22,10 +24,9 @@ use flowy_revision::{ use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeserializer}; use flowy_sync::errors::{CollaborateError, CollaborateResult}; use flowy_sync::util::make_operations_from_revisions; +use flowy_task::TaskDispatcher; use grid_rev_model::*; -use lib_infra::future::{wrap_future, FutureResult}; - -use flowy_database::ConnectionPool; +use lib_infra::future::{to_future, FutureResult}; use lib_ot::core::EmptyAttributes; use std::collections::HashMap; use std::sync::Arc; @@ -39,9 +40,6 @@ pub struct GridRevisionEditor { view_manager: Arc, rev_manager: Arc>>, block_manager: Arc, - - #[allow(dead_code)] - pub(crate) filter_service: Arc, } impl Drop for GridRevisionEditor { @@ -56,7 +54,7 @@ impl GridRevisionEditor { user: Arc, mut rev_manager: RevisionManager>, persistence: Arc, - task_scheduler: GridTaskSchedulerRwLock, + task_scheduler: Arc>, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); @@ -67,20 +65,14 @@ impl GridRevisionEditor { // Block manager let block_meta_revs = grid_pad.read().await.get_block_meta_revs(); let block_manager = Arc::new(GridBlockManager::new(&user, block_meta_revs, persistence).await?); - let filter_service = - GridFilterService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await; + let delegate = Arc::new(GridViewEditorDelegateImpl { + pad: grid_pad.clone(), + block_manager: block_manager.clone(), + task_scheduler, + }); // View manager - let view_manager = Arc::new( - GridViewManager::new( - grid_id.to_owned(), - user.clone(), - Arc::new(grid_pad.clone()), - Arc::new(block_manager.clone()), - Arc::new(task_scheduler.clone()), - ) - .await?, - ); + let view_manager = Arc::new(GridViewManager::new(grid_id.to_owned(), user.clone(), delegate).await?); let editor = Arc::new(Self { grid_id: grid_id.to_owned(), user, @@ -88,7 +80,6 @@ impl GridRevisionEditor { rev_manager, block_manager, view_manager, - filter_service: Arc::new(filter_service), }); Ok(editor) @@ -97,8 +88,11 @@ impl GridRevisionEditor { #[tracing::instrument(name = "close grid editor", level = "trace", skip_all)] pub fn close(&self) { let rev_manager = self.rev_manager.clone(); + let view_manager = self.view_manager.clone(); + let view_id = self.grid_id.clone(); tokio::spawn(async move { rev_manager.close().await; + view_manager.close(&view_id).await; }); } @@ -352,7 +346,6 @@ impl GridRevisionEditor { Ok(changeset) }) .await?; - let _ = self.view_manager.did_update_view_field(¶ms.field_id).await?; if is_type_option_changed { let _ = self .view_manager @@ -418,20 +411,16 @@ impl GridRevisionEditor { Ok(()) } - pub async fn get_rows(&self, block_id: &str) -> FlowyResult { - let block_ids = vec![block_id.to_owned()]; - let mut grid_block_snapshot = self.grid_block_snapshots(Some(block_ids)).await?; - - // For the moment, we only support one block. - // We can save the rows into multiple blocks and load them asynchronously in the future. - debug_assert_eq!(grid_block_snapshot.len(), 1); - if grid_block_snapshot.len() == 1 { - let snapshot = grid_block_snapshot.pop().unwrap(); - let rows = make_rows_from_row_revs(&snapshot.row_revs); - Ok(rows.into()) - } else { - Ok(vec![].into()) - } + pub async fn get_row_pbs(&self, block_id: &str) -> FlowyResult> { + let rows = self.block_manager.get_row_revs(block_id).await?; + let rows = self + .view_manager + .filter_rows(block_id, rows) + .await? + .into_iter() + .map(|row_rev| RowPB::from(&row_rev)) + .collect(); + Ok(rows) } pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { @@ -514,75 +503,12 @@ impl GridRevisionEditor { } } - pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult { - let block_snapshots = self.grid_block_snapshots(block_ids.clone()).await?; - make_grid_blocks(block_ids, block_snapshots) - } - pub async fn get_block_meta_revs(&self) -> FlowyResult>> { let block_meta_revs = self.grid_pad.read().await.get_block_meta_revs(); Ok(block_meta_revs) } - pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { - let changesets = self.block_manager.delete_rows(row_orders).await?; - for changeset in changesets { - let _ = self.update_block(changeset).await?; - } - Ok(()) - } - - pub async fn get_grid_data(&self) -> FlowyResult { - let pad_read_guard = self.grid_pad.read().await; - let field_orders = pad_read_guard - .get_field_revs(None)? - .iter() - .map(FieldIdPB::from) - .collect(); - let mut block_orders = vec![]; - for block_rev in pad_read_guard.get_block_meta_revs() { - let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?; - let block_order = BlockPB { - id: block_rev.block_id.clone(), - rows: row_orders, - }; - block_orders.push(block_order); - } - - Ok(GridPB { - id: self.grid_id.clone(), - fields: field_orders, - blocks: block_orders, - }) - } - - pub async fn get_grid_setting(&self) -> FlowyResult { - self.view_manager.get_setting().await - } - - pub async fn get_grid_filter(&self) -> FlowyResult> { - self.view_manager.get_filters().await - } - - pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - self.view_manager.insert_or_update_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.view_manager.delete_group(params).await - } - - pub async fn create_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { - let _ = self.view_manager.insert_or_update_filter(params).await?; - Ok(()) - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let _ = self.view_manager.delete_filter(params).await?; - Ok(()) - } - - pub async fn grid_block_snapshots(&self, block_ids: Option>) -> FlowyResult> { + pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult> { let block_ids = match block_ids { None => self .grid_pad @@ -594,8 +520,73 @@ impl GridRevisionEditor { .collect::>(), Some(block_ids) => block_ids, }; - let snapshots = self.block_manager.get_block_snapshots(Some(block_ids)).await?; - Ok(snapshots) + let blocks = self.block_manager.get_blocks(Some(block_ids)).await?; + Ok(blocks) + } + + pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { + let changesets = self.block_manager.delete_rows(row_orders).await?; + for changeset in changesets { + let _ = self.update_block(changeset).await?; + } + Ok(()) + } + + pub async fn get_grid(&self) -> FlowyResult { + let pad = self.grid_pad.read().await; + let fields = pad.get_field_revs(None)?.iter().map(FieldIdPB::from).collect(); + + let mut blocks = vec![]; + for block_rev in pad.get_block_meta_revs() { + let rows = self.get_row_pbs(&block_rev.block_id).await?; + let block = BlockPB { + id: block_rev.block_id.clone(), + rows, + }; + blocks.push(block); + } + + Ok(GridPB { + id: self.grid_id.clone(), + fields, + blocks, + }) + } + + pub async fn get_setting(&self) -> FlowyResult { + self.view_manager.get_setting().await + } + + pub async fn get_all_filters(&self) -> FlowyResult> { + Ok(self + .view_manager + .get_all_filters() + .await? + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect()) + } + + pub async fn get_filters(&self, filter_id: FilterType) -> FlowyResult>> { + self.view_manager.get_filters(&filter_id).await + } + + pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + self.view_manager.insert_or_update_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.view_manager.delete_group(params).await + } + + pub async fn create_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { + let _ = self.view_manager.insert_or_update_filter(params).await?; + Ok(()) + } + + pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let _ = self.view_manager.delete_filter(params).await?; + Ok(()) } pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { @@ -641,7 +632,7 @@ impl GridRevisionEditor { let block_manager = self.block_manager.clone(); self.view_manager .move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| { - wrap_future(async move { + to_future(async move { tracing::trace!("Row data changed: {:?}", row_changeset); let cell_changesets = row_changeset .cell_by_field_id diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs deleted file mode 100644 index f5c45811dd..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::manager::GridTaskSchedulerRwLock; -use crate::services::grid_editor::GridRevisionEditor; -use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskId}; -use flowy_error::FlowyError; -use futures::future::BoxFuture; -use lib_infra::future::BoxResultFuture; - -pub(crate) trait GridServiceTaskScheduler: Send + Sync + 'static { - fn gen_task_id(&self) -> BoxFuture; - fn add_task(&self, task: Task) -> BoxFuture<()>; -} - -impl GridTaskHandler for GridRevisionEditor { - fn handler_id(&self) -> &str { - &self.grid_id - } - - fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError> { - Box::pin(async move { - match content { - TaskContent::Snapshot => {} - TaskContent::Group => {} - TaskContent::Filter(context) => self.filter_service.process(context).await?, - } - Ok(()) - }) - } -} - -impl GridServiceTaskScheduler for GridTaskSchedulerRwLock { - fn gen_task_id(&self) -> BoxFuture { - let this = self.clone(); - Box::pin(async move { this.read().await.next_task_id() }) - } - - fn add_task(&self, task: Task) -> BoxFuture<()> { - let this = self.clone(); - Box::pin(async move { - this.write().await.add_task(task); - }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs index 0c2a1ae41a..d8b5481483 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs @@ -1,15 +1,24 @@ -use crate::services::grid_view_manager::GridViewFieldDelegate; +use crate::services::block_manager::GridBlockManager; +use crate::services::grid_view_editor::GridViewEditorDelegate; +use crate::services::row::GridBlock; use flowy_sync::client_grid::GridRevisionPad; -use grid_rev_model::FieldRevision; -use lib_infra::future::{wrap_future, AFFuture}; +use flowy_task::TaskDispatcher; +use grid_rev_model::{FieldRevision, RowRevision}; +use lib_infra::future::{to_future, Fut}; use std::sync::Arc; use tokio::sync::RwLock; -impl GridViewFieldDelegate for Arc> { - fn get_field_revs(&self) -> AFFuture>> { - let pad = self.clone(); - wrap_future(async move { - match pad.read().await.get_field_revs(None) { +pub(crate) struct GridViewEditorDelegateImpl { + pub(crate) pad: Arc>, + pub(crate) block_manager: Arc, + pub(crate) task_scheduler: Arc>, +} + +impl GridViewEditorDelegate for GridViewEditorDelegateImpl { + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + let pad = self.pad.clone(); + to_future(async move { + match pad.read().await.get_field_revs(field_ids) { Ok(field_revs) => field_revs, Err(e) => { tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e); @@ -19,14 +28,47 @@ impl GridViewFieldDelegate for Arc> { }) } - fn get_field_rev(&self, field_id: &str) -> AFFuture>> { - let pad = self.clone(); + fn get_field_rev(&self, field_id: &str) -> Fut>> { + let pad = self.pad.clone(); let field_id = field_id.to_owned(); - wrap_future(async move { - pad.read() - .await - .get_field_rev(&field_id) - .map(|(_, field_rev)| field_rev.clone()) + to_future(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) + } + + fn index_of_row(&self, row_id: &str) -> Fut> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_future(async move { block_manager.index_of_row(&row_id).await }) + } + + fn get_row_rev(&self, row_id: &str) -> Fut>> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_future(async move { + match block_manager.get_row_rev(&row_id).await { + Ok(row_rev) => row_rev, + Err(_) => None, + } }) } + + fn get_row_revs(&self) -> Fut>> { + let block_manager = self.block_manager.clone(); + + to_future(async move { + let blocks = block_manager.get_blocks(None).await.unwrap(); + blocks + .into_iter() + .flat_map(|block| block.row_revs) + .collect::>>() + }) + } + + fn get_blocks(&self) -> Fut> { + let block_manager = self.block_manager.clone(); + to_future(async move { block_manager.get_blocks(None).await.unwrap_or_default() }) + } + + fn get_task_scheduler(&self) -> Arc> { + self.task_scheduler.clone() + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index 8ee4c18f2f..ca2b589bc8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -1,16 +1,13 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{ - CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridGroupConfigurationPB, - GridLayout, GridLayoutPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertFilterParams, - InsertGroupParams, InsertedGroupPB, InsertedRowPB, MoveGroupParams, RepeatedGridFilterConfigurationPB, - RepeatedGridGroupConfigurationPB, RowPB, +use crate::entities::*; +use crate::services::filter::{ + FilterChangeset, FilterController, FilterTaskHandler, FilterType, GridViewFilterDelegate, }; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; use crate::services::group::{ - default_group_configuration, find_group_field, make_group_controller, GroupConfigurationReader, + default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader, GroupConfigurationWriter, GroupController, MoveGroupRowContext, }; +use crate::services::row::GridBlock; use bytes::Bytes; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; @@ -20,26 +17,39 @@ use flowy_revision::{ }; use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; use flowy_sync::util::make_operations_from_revisions; +use flowy_task::TaskDispatcher; use grid_rev_model::{ - gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision, - RowChangeset, RowRevision, + gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, RowChangeset, + RowRevision, }; -use lib_infra::future::{wrap_future, AFFuture, FutureResult}; +use lib_infra::future::{to_future, Fut, FutureResult}; use lib_ot::core::EmptyAttributes; use std::future::Future; use std::sync::Arc; use tokio::sync::RwLock; +pub trait GridViewEditorDelegate: Send + Sync + 'static { + /// If the field_ids is None, then it will return all the field revisions + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + + fn index_of_row(&self, row_id: &str) -> Fut>; + fn get_row_rev(&self, row_id: &str) -> Fut>>; + fn get_row_revs(&self) -> Fut>>; + fn get_blocks(&self) -> Fut>; + + fn get_task_scheduler(&self) -> Arc>; +} + #[allow(dead_code)] pub struct GridViewRevisionEditor { user_id: String, view_id: String, pad: Arc>, rev_manager: Arc>>, - field_delegate: Arc, - row_delegate: Arc, + delegate: Arc, group_controller: Arc>>, - scheduler: Arc, + filter_controller: Arc>, } impl GridViewRevisionEditor { #[tracing::instrument(level = "trace", skip_all, err)] @@ -47,9 +57,7 @@ impl GridViewRevisionEditor { user_id: &str, token: &str, view_id: String, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, + delegate: Arc, mut rev_manager: RevisionManager>, ) -> FlowyResult { let cloud = Arc::new(GridViewRevisionCloudService { @@ -63,23 +71,33 @@ impl GridViewRevisionEditor { view_id.clone(), pad.clone(), rev_manager.clone(), - field_delegate.clone(), - row_delegate.clone(), + delegate.clone(), ) .await?; + let user_id = user_id.to_owned(); + let group_controller = Arc::new(RwLock::new(group_controller)); + let filter_controller = make_filter_controller(&view_id, delegate.clone(), pad.clone()).await; Ok(Self { pad, user_id, view_id, rev_manager, - scheduler, - field_delegate, - row_delegate, - group_controller: Arc::new(RwLock::new(group_controller)), + delegate, + group_controller, + filter_controller, }) } + pub(crate) async fn close(&self) { + self.filter_controller.read().await.close().await; + } + + pub(crate) async fn filter_rows(&self, _block_id: &str, mut rows: Vec>) -> Vec> { + self.filter_controller.write().await.filter_row_revs(&mut rows).await; + rows + } + pub(crate) async fn duplicate_view_data(&self) -> FlowyResult { let json_str = self.pad.read().await.json_str()?; Ok(json_str) @@ -178,7 +196,14 @@ impl GridViewRevisionEditor { /// Only call once after grid view editor initialized #[tracing::instrument(level = "trace", skip(self))] pub(crate) async fn load_view_groups(&self) -> FlowyResult> { - let groups = self.group_controller.read().await.groups(); + let groups = self + .group_controller + .read() + .await + .groups() + .into_iter() + .cloned() + .collect::>(); tracing::trace!("Number of groups: {}", groups.len()); Ok(groups.into_iter().map(GroupPB::from).collect()) } @@ -212,32 +237,30 @@ impl GridViewRevisionEditor { Ok(()) } - pub(crate) async fn group_id(&self) -> String { - self.group_controller.read().await.field_id().to_owned() + pub(crate) async fn is_grouped(&self) -> bool { + self.group_controller.read().await.groups().len() > 1 } pub(crate) async fn get_view_setting(&self) -> GridSettingPB { - let field_revs = self.field_delegate.get_field_revs().await; + let field_revs = self.delegate.get_field_revs(None).await; let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs); grid_setting } - pub(crate) async fn get_view_filters(&self) -> Vec { - let field_revs = self.field_delegate.get_field_revs().await; - match self.pad.read().await.get_all_filters(&field_revs) { - None => vec![], - Some(filters) => filters - .into_values() - .flatten() - .map(|filter| GridFilterConfigurationPB::from(filter.as_ref())) - .collect(), - } + pub(crate) async fn get_all_view_filters(&self) -> Vec> { + let field_revs = self.delegate.get_field_revs(None).await; + self.pad.read().await.get_all_filters(&field_revs) + } + + pub(crate) async fn get_view_filters(&self, filter_id: &FilterType) -> Vec> { + let field_type_rev: FieldTypeRevision = filter_id.field_type.clone().into(); + self.pad.read().await.get_filters(&filter_id.field_id, &field_type_rev) } /// Initialize new group when grouping by a new field /// pub(crate) async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - if let Some(field_rev) = self.field_delegate.get_field_rev(¶ms.field_id).await { + if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await { let _ = self .modify(|pad| { let configuration = default_group_configuration(&field_rev); @@ -259,44 +282,64 @@ impl GridViewRevisionEditor { pub(crate) async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { self.modify(|pad| { - let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?; + let changeset = pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?; Ok(changeset) }) .await } - pub(crate) async fn insert_view_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { - self.modify(|pad| { - let filter_rev = FilterConfigurationRevision { - id: gen_grid_filter_id(), - field_id: params.field_id.clone(), - condition: params.condition, - content: params.content, - }; - let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?; - Ok(changeset) - }) - .await + pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { + let filter_type = FilterType::from(¶ms); + let _ = self + .modify(|pad| { + let filter_rev = FilterRevision { + id: gen_grid_filter_id(), + field_id: params.field_id.clone(), + condition: params.condition, + content: params.content, + }; + let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?; + Ok(changeset) + }) + .await?; + + self.filter_controller + .write() + .await + .apply_changeset(FilterChangeset::from_insert(filter_type)) + .await; + + Ok(()) } - pub(crate) async fn delete_view_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> { - self.modify(|pad| { - let changeset = pad.delete_filter( - &delete_filter.field_id, - &delete_filter.field_type_rev, - &delete_filter.filter_id, - )?; - Ok(changeset) - }) - .await + pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let filter_type = params.filter_type; + let field_type_rev = filter_type.field_type_rev(); + let _ = self + .modify(|pad| { + let changeset = pad.delete_filter(¶ms.filter_id, &filter_type.field_id, &field_type_rev)?; + Ok(changeset) + }) + .await?; + + self.filter_controller + .write() + .await + .apply_changeset(FilterChangeset::from_delete(filter_type)) + .await; + Ok(()) } + #[tracing::instrument(level = "trace", skip_all, err)] - pub(crate) async fn did_update_view_field(&self, field_id: &str) -> FlowyResult<()> { - let grouped_field_id = self.group_controller.read().await.field_id().to_owned(); - if grouped_field_id == field_id { - let _ = self.group_by_view_field(field_id).await?; - } else { - // Do nothing + pub(crate) async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let filter_type = FilterType::from(&field_rev); + let filter_changeset = FilterChangeset::from_insert(filter_type); + self.filter_controller + .write() + .await + .apply_changeset(filter_changeset) + .await; } Ok(()) } @@ -309,18 +352,23 @@ impl GridViewRevisionEditor { /// #[tracing::instrument(level = "debug", skip_all, err)] pub(crate) async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let row_revs = self.delegate.get_row_revs().await; let new_group_controller = new_group_controller_with_field_rev( self.user_id.clone(), self.view_id.clone(), self.pad.clone(), self.rev_manager.clone(), field_rev, - self.row_delegate.clone(), + row_revs, ) .await?; - let new_groups = new_group_controller.groups().into_iter().map(GroupPB::from).collect(); + let new_groups = new_group_controller + .groups() + .into_iter() + .map(|group| GroupPB::from(group.clone())) + .collect(); *self.group_controller.write().await = new_group_controller; let changeset = GroupViewChangesetPB { @@ -377,7 +425,7 @@ impl GridViewRevisionEditor { F: FnOnce(&mut Box, Arc) -> FlowyResult, { let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.field_delegate.get_field_rev(&group_field_id).await { + match self.delegate.get_field_rev(&group_field_id).await { None => None, Some(field_rev) => { let mut write_guard = self.group_controller.write().await; @@ -393,7 +441,7 @@ impl GridViewRevisionEditor { O: Future> + Sync + 'static, { let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.field_delegate.get_field_rev(&group_field_id).await { + match self.delegate.get_field_rev(&group_field_id).await { None => None, Some(field_rev) => { let _write_guard = self.group_controller.write().await; @@ -408,11 +456,11 @@ async fn new_group_controller( view_id: String, view_rev_pad: Arc>, rev_manager: Arc>>, - field_delegate: Arc, - row_delegate: Arc, + delegate: Arc, ) -> FlowyResult> { let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); - let field_revs = field_delegate.get_field_revs().await; + let field_revs = delegate.get_field_revs(None).await; + let row_revs = delegate.get_row_revs().await; let layout = view_rev_pad.read().await.layout(); // Read the group field or find a new group field let field_rev = configuration_reader @@ -426,27 +474,18 @@ async fn new_group_controller( }) .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); - new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_delegate).await + new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_revs).await } /// Returns a [GroupController] /// -/// # Arguments -/// -/// * `user_id`: -/// * `view_id`: -/// * `view_rev_pad`: -/// * `rev_manager`: -/// * `field_rev`: -/// * `row_delegate`: -/// async fn new_group_controller_with_field_rev( user_id: String, view_id: String, view_rev_pad: Arc>, rev_manager: Arc>>, field_rev: Arc, - row_delegate: Arc, + row_revs: Vec>, ) -> FlowyResult> { let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); let configuration_writer = GroupConfigurationWriterImpl { @@ -454,10 +493,30 @@ async fn new_group_controller_with_field_rev( rev_manager, view_pad: view_rev_pad, }; - let row_revs = row_delegate.gv_row_revs().await; make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await } +async fn make_filter_controller( + view_id: &str, + delegate: Arc, + pad: Arc>, +) -> Arc> { + let field_revs = delegate.get_field_revs(None).await; + let filter_revs = pad.read().await.get_all_filters(&field_revs); + let task_scheduler = delegate.get_task_scheduler(); + let filter_delegate = GridViewFilterDelegateImpl { + editor_delegate: delegate.clone(), + view_revision_pad: pad, + }; + let filter_controller = FilterController::new(view_id, filter_delegate, task_scheduler.clone(), filter_revs).await; + let filter_controller = Arc::new(RwLock::new(filter_controller)); + task_scheduler + .write() + .await + .register_handler(FilterTaskHandler::new(filter_controller.clone())); + filter_controller +} + async fn apply_change( _user_id: &str, rev_manager: Arc>>, @@ -477,7 +536,6 @@ struct GridViewRevisionCloudService { } impl RevisionCloudService for GridViewRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { FutureResult::new(async move { Ok(vec![]) }) } @@ -510,9 +568,9 @@ impl RevisionMergeable for GridViewRevisionCompress { struct GroupConfigurationReaderImpl(Arc>); impl GroupConfigurationReader for GroupConfigurationReaderImpl { - fn get_configuration(&self) -> AFFuture>> { + fn get_configuration(&self) -> Fut>> { let view_pad = self.0.clone(); - wrap_future(async move { + to_future(async move { let mut groups = view_pad.read().await.get_all_groups(); if groups.is_empty() { None @@ -536,13 +594,13 @@ impl GroupConfigurationWriter for GroupConfigurationWriterImpl { field_id: &str, field_type: FieldTypeRevision, group_configuration: GroupConfigurationRevision, - ) -> AFFuture> { + ) -> Fut> { let user_id = self.user_id.clone(); let rev_manager = self.rev_manager.clone(); let view_pad = self.view_pad.clone(); let field_id = field_id.to_owned(); - wrap_future(async move { + to_future(async move { let changeset = view_pad.write().await.insert_or_update_group_configuration( &field_id, &field_type, @@ -561,29 +619,15 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc>() - }) - .unwrap_or_default(); + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect::>(); let group_configurations = view_pad .get_groups_by_field_revs(field_revs) - .map(|groups_by_field_id| { - groups_by_field_id - .into_iter() - .flat_map(|(_, v)| { - let repeated_group: RepeatedGridGroupConfigurationPB = v.into(); - repeated_group.items - }) - .collect::>() - }) - .unwrap_or_default(); + .into_iter() + .map(|group| GridGroupConfigurationPB::from(group.as_ref())) + .collect::>(); GridSettingPB { layouts: GridLayoutPB::all(), @@ -593,6 +637,33 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc, + view_revision_pad: Arc>, +} + +impl GridViewFilterDelegate for GridViewFilterDelegateImpl { + fn get_filter_rev(&self, filter_id: FilterType) -> Fut>> { + let pad = self.view_revision_pad.clone(); + to_future(async move { + let field_type_rev: FieldTypeRevision = filter_id.field_type.into(); + pad.read().await.get_filters(&filter_id.field_id, &field_type_rev) + }) + } + + fn get_field_rev(&self, field_id: &str) -> Fut>> { + self.editor_delegate.get_field_rev(field_id) + } + + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + self.editor_delegate.get_field_revs(field_ids) + } + + fn get_blocks(&self) -> Fut> { + self.editor_delegate.get_blocks() + } +} + #[cfg(test)] mod tests { use flowy_sync::client_grid::GridOperations; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index 713b6cef8a..b626eed8f1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -1,62 +1,59 @@ use crate::entities::{ - CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridSettingPB, - InsertFilterParams, InsertGroupParams, MoveGroupParams, RepeatedGridGroupPB, RowPB, + CreateFilterParams, CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridSettingPB, InsertGroupParams, + MoveGroupParams, RepeatedGridGroupPB, RowPB, }; use crate::manager::GridUser; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::grid_view_editor::{GridViewRevisionCompress, GridViewRevisionEditor}; +use crate::services::grid_view_editor::{GridViewEditorDelegate, GridViewRevisionCompress, GridViewRevisionEditor}; use crate::services::persistence::rev_sqlite::SQLiteGridViewRevisionPersistence; + use dashmap::DashMap; use flowy_database::ConnectionPool; use flowy_error::FlowyResult; use flowy_revision::{ RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, SQLiteRevisionSnapshotPersistence, }; -use grid_rev_model::{FieldRevision, RowChangeset, RowRevision}; -use lib_infra::future::AFFuture; + +use crate::services::filter::FilterType; +use grid_rev_model::{FilterRevision, RowChangeset, RowRevision}; +use lib_infra::future::Fut; use std::sync::Arc; type ViewId = String; -pub trait GridViewFieldDelegate: Send + Sync + 'static { - fn get_field_revs(&self) -> AFFuture>>; - fn get_field_rev(&self, field_id: &str) -> AFFuture>>; -} - -pub trait GridViewRowDelegate: Send + Sync + 'static { - fn gv_index_of_row(&self, row_id: &str) -> AFFuture>; - fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>>; - fn gv_row_revs(&self) -> AFFuture>>; -} - pub(crate) struct GridViewManager { grid_id: String, user: Arc, - field_delegate: Arc, - row_delegate: Arc, + delegate: Arc, view_editors: DashMap>, - scheduler: Arc, } impl GridViewManager { pub(crate) async fn new( grid_id: String, user: Arc, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, + delegate: Arc, ) -> FlowyResult { Ok(Self { grid_id, user, - scheduler, - field_delegate, - row_delegate, + delegate, view_editors: DashMap::default(), }) } + pub(crate) async fn close(&self, _view_id: &str) { + if let Ok(editor) = self.get_default_view_editor().await { + let _ = editor.close().await; + } + } + + pub async fn filter_rows(&self, block_id: &str, rows: Vec>) -> FlowyResult>> { + let editor = self.get_default_view_editor().await?; + let rows = editor.filter_rows(block_id, rows).await; + Ok(rows) + } + pub(crate) async fn duplicate_grid_view(&self) -> FlowyResult { let editor = self.get_default_view_editor().await?; let view_data = editor.duplicate_view_data().await?; @@ -79,7 +76,7 @@ impl GridViewManager { /// Insert/Delete the group's row if the corresponding cell data was changed. pub(crate) async fn did_update_cell(&self, row_id: &str) { - match self.row_delegate.gv_get_row_rev(row_id).await { + match self.delegate.get_row_rev(row_id).await { None => { tracing::warn!("Can not find the row in grid view"); } @@ -108,12 +105,17 @@ impl GridViewManager { Ok(view_editor.get_view_setting().await) } - pub(crate) async fn get_filters(&self) -> FlowyResult> { + pub(crate) async fn get_all_filters(&self) -> FlowyResult>> { let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_view_filters().await) + Ok(view_editor.get_all_view_filters().await) } - pub(crate) async fn insert_or_update_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { + pub(crate) async fn get_filters(&self, filter_id: &FilterType) -> FlowyResult>> { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_view_filters(filter_id).await) + } + + pub(crate) async fn insert_or_update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { let view_editor = self.get_default_view_editor().await?; view_editor.insert_view_filter(params).await } @@ -153,7 +155,7 @@ impl GridViewManager { row_rev: Arc, to_group_id: String, to_row_id: Option, - recv_row_changeset: impl FnOnce(RowChangeset) -> AFFuture<()>, + recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>, ) -> FlowyResult<()> { let mut row_changeset = RowChangeset::new(row_rev.id.clone()); let view_editor = self.get_default_view_editor().await?; @@ -172,17 +174,6 @@ impl GridViewManager { Ok(()) } - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn did_update_view_field(&self, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - // Update the group if the group_id equal to the field_id - if view_editor.group_id().await != field_id { - return Ok(()); - } - let _ = view_editor.did_update_view_field(field_id).await?; - Ok(()) - } - /// Notifies the view's field type-option data is changed /// For the moment, only the groups will be generated after the type-option data changed. A /// [FieldRevision] has a property named type_options contains a list of type-option data. @@ -193,7 +184,11 @@ impl GridViewManager { #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> { let view_editor = self.get_default_view_editor().await?; - let _ = view_editor.did_update_view_field(field_id).await?; + if view_editor.is_grouped().await { + let _ = view_editor.group_by_view_field(field_id).await?; + } + + let _ = view_editor.did_update_view_field_type_option(field_id).await?; Ok(()) } @@ -201,16 +196,7 @@ impl GridViewManager { debug_assert!(!view_id.is_empty()); match self.view_editors.get(view_id) { None => { - let editor = Arc::new( - make_view_editor( - &self.user, - view_id, - self.field_delegate.clone(), - self.row_delegate.clone(), - self.scheduler.clone(), - ) - .await?, - ); + let editor = Arc::new(make_view_editor(&self.user, view_id, self.delegate.clone()).await?); self.view_editors.insert(view_id.to_owned(), editor.clone()); Ok(editor) } @@ -226,25 +212,14 @@ impl GridViewManager { async fn make_view_editor( user: &Arc, view_id: &str, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, + delegate: Arc, ) -> FlowyResult { let rev_manager = make_grid_view_rev_manager(user, view_id).await?; let user_id = user.user_id()?; let token = user.token()?; let view_id = view_id.to_owned(); - GridViewRevisionEditor::new( - &user_id, - &token, - view_id, - field_delegate, - row_delegate, - scheduler, - rev_manager, - ) - .await + GridViewRevisionEditor::new(&user_id, &token, view_id, delegate, rev_manager).await } pub async fn make_grid_view_rev_manager( diff --git a/frontend/rust-lib/flowy-grid/src/services/group/action.rs b/frontend/rust-lib/flowy-grid/src/services/group/action.rs index 394899e2b2..e0a0f3169d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/action.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/action.rs @@ -46,7 +46,7 @@ pub trait GroupControllerSharedActions: Send + Sync { fn field_id(&self) -> &str; /// Returns number of groups the current field has - fn groups(&self) -> Vec; + fn groups(&self) -> Vec<&Group>; /// Returns the index and the group data with group_id fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; diff --git a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs index 130f7cc301..01f30dbf6e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs @@ -5,14 +5,14 @@ use grid_rev_model::{ FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, }; use indexmap::IndexMap; -use lib_infra::future::AFFuture; +use lib_infra::future::Fut; use std::collections::HashMap; use std::fmt::Formatter; use std::marker::PhantomData; use std::sync::Arc; pub trait GroupConfigurationReader: Send + Sync + 'static { - fn get_configuration(&self) -> AFFuture>>; + fn get_configuration(&self) -> Fut>>; } pub trait GroupConfigurationWriter: Send + Sync + 'static { @@ -21,7 +21,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static { field_id: &str, field_type: FieldTypeRevision, group_configuration: GroupConfigurationRevision, - ) -> AFFuture>; + ) -> Fut>; } impl std::fmt::Display for GroupContext { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs index 71114b9326..64b59988eb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs @@ -165,8 +165,8 @@ where &self.field_id } - fn groups(&self) -> Vec { - self.group_ctx.groups().into_iter().cloned().collect() + fn groups(&self) -> Vec<&Group> { + self.group_ctx.groups() } fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs index a35b202021..eaa2d96b57 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs @@ -36,8 +36,8 @@ impl GroupControllerSharedActions for DefaultGroupController { &self.field_id } - fn groups(&self) -> Vec { - vec![self.group.clone()] + fn groups(&self) -> Vec<&Group> { + vec![&self.group] } fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index 1e759a082a..2248aa6063 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,12 +2,10 @@ mod util; pub mod block_editor; pub mod block_manager; -mod block_manager_trait_impl; pub mod cell; pub mod field; -mod filter; +pub mod filter; pub mod grid_editor; -mod grid_editor_task; mod grid_editor_trait_impl; pub mod grid_view_editor; pub mod grid_view_manager; @@ -15,5 +13,3 @@ pub mod group; pub mod persistence; pub mod row; pub mod setting; -mod snapshot; -pub mod tasks; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 22e9ded7d1..0b92889fca 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,10 +1,10 @@ use crate::entities::{BlockPB, RepeatedBlockPB, RowPB}; -use flowy_error::FlowyResult; + use grid_rev_model::RowRevision; use std::collections::HashMap; use std::sync::Arc; -pub struct GridBlockSnapshot { +pub struct GridBlock { pub(crate) block_id: String, pub row_revs: Vec>, } @@ -35,7 +35,7 @@ pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec { // Some((field_id, cell)) // } -pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc]) -> Vec { +pub(crate) fn make_row_pb_from_row_rev(row_revs: &[Arc]) -> Vec { row_revs.iter().map(RowPB::from).collect::>() } @@ -53,36 +53,13 @@ pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec>() } -pub(crate) fn make_grid_blocks( - block_ids: Option>, - block_snapshots: Vec, -) -> FlowyResult { - match block_ids { - None => Ok(block_snapshots - .into_iter() - .map(|snapshot| { - let row_orders = make_row_orders_from_row_revs(&snapshot.row_revs); - BlockPB::new(&snapshot.block_id, row_orders) - }) - .collect::>() - .into()), - Some(block_ids) => { - let block_meta_data_map: HashMap<&String, &Vec>> = block_snapshots - .iter() - .map(|data| (&data.block_id, &data.row_revs)) - .collect(); - - let mut grid_blocks = vec![]; - for block_id in block_ids { - match block_meta_data_map.get(&block_id) { - None => {} - Some(row_revs) => { - let row_orders = make_row_orders_from_row_revs(row_revs); - grid_blocks.push(BlockPB::new(&block_id, row_orders)); - } - } - } - Ok(grid_blocks.into()) - } - } +pub(crate) fn make_block_pbs(blocks: Vec) -> RepeatedBlockPB { + blocks + .into_iter() + .map(|block| { + let row_pbs = make_row_pb_from_row_rev(&block.row_revs); + BlockPB::new(&block.block_id, row_pbs) + }) + .collect::>() + .into() } diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs index 2bf8f1bbeb..16ee630cc7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs @@ -1,4 +1,4 @@ -use crate::entities::{DeleteFilterParams, GridLayout, GridSettingChangesetParams, InsertFilterParams}; +use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams}; pub struct GridSettingChangesetBuilder { params: GridSettingChangesetParams, @@ -17,7 +17,7 @@ impl GridSettingChangesetBuilder { Self { params } } - pub fn insert_filter(mut self, params: InsertFilterParams) -> Self { + pub fn insert_filter(mut self, params: CreateFilterParams) -> Self { self.params.insert_filter = Some(params); self } diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs deleted file mode 100644 index 9c00ccc85f..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod snapshot_service; - -pub use snapshot_service::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs deleted file mode 100644 index 2a47b70f48..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs +++ /dev/null @@ -1,7 +0,0 @@ -// pub struct GridSnapshotService {} -// -// impl GridSnapshotService { -// pub fn new() -> Self { -// Self {} -// } -// } diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs deleted file mode 100644 index 121329edfb..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::services::tasks::scheduler::GridTaskScheduler; - -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{watch, RwLock}; -use tokio::time::interval; - -pub struct GridTaskRunner { - scheduler: Arc>, - debounce_duration: Duration, - notifier: Option>, -} - -impl GridTaskRunner { - pub fn new( - scheduler: Arc>, - notifier: watch::Receiver, - debounce_duration: Duration, - ) -> Self { - Self { - scheduler, - debounce_duration, - notifier: Some(notifier), - } - } - - pub async fn run(mut self) { - let mut notifier = self - .notifier - .take() - .expect("The GridTaskRunner's notifier should only take once"); - - loop { - if notifier.changed().await.is_err() { - // The runner will be stopped if the corresponding Sender drop. - break; - } - - if *notifier.borrow() { - break; - } - let mut interval = interval(self.debounce_duration); - interval.tick().await; - let _ = self.scheduler.write().await.process_next_task().await; - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs deleted file mode 100644 index e88cd9fd9d..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::services::tasks::queue::GridTaskQueue; -use crate::services::tasks::runner::GridTaskRunner; -use crate::services::tasks::store::GridTaskStore; -use crate::services::tasks::task::Task; - -use crate::services::tasks::{TaskContent, TaskId, TaskStatus}; -use flowy_error::FlowyError; -use lib_infra::future::BoxResultFuture; -use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; - -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{watch, RwLock}; - -pub(crate) trait GridTaskHandler: Send + Sync + 'static { - fn handler_id(&self) -> &str; - - fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError>; -} - -#[derive(Clone)] -struct RefCountTaskHandler(Arc); -impl RefCountValue for RefCountTaskHandler { - fn did_remove(&self) {} -} - -pub struct GridTaskScheduler { - queue: GridTaskQueue, - store: GridTaskStore, - notifier: watch::Sender, - handlers: RefCountHashMap, -} - -impl GridTaskScheduler { - pub(crate) fn new() -> Arc> { - let (notifier, rx) = watch::channel(false); - - let scheduler = Self { - queue: GridTaskQueue::new(), - store: GridTaskStore::new(), - notifier, - handlers: RefCountHashMap::new(), - }; - // The runner will receive the newest value after start running. - scheduler.notify(); - - let scheduler = Arc::new(RwLock::new(scheduler)); - let debounce_duration = Duration::from_millis(300); - let runner = GridTaskRunner::new(scheduler.clone(), rx, debounce_duration); - tokio::spawn(runner.run()); - - scheduler - } - - pub(crate) fn register_handler(&mut self, handler: Arc) - where - T: GridTaskHandler, - { - let handler_id = handler.handler_id().to_owned(); - self.handlers.insert(handler_id, RefCountTaskHandler(handler)); - } - - pub(crate) fn unregister_handler>(&mut self, handler_id: T) { - self.handlers.remove(handler_id.as_ref()); - } - - #[allow(dead_code)] - pub(crate) fn stop(&mut self) { - let _ = self.notifier.send(true); - self.queue.clear(); - self.store.clear(); - } - - pub(crate) async fn process_next_task(&mut self) -> Option<()> { - let pending_task = self.queue.mut_head(|list| list.pop())?; - let mut task = self.store.remove_task(&pending_task.id)?; - let handler = self.handlers.get(&task.handler_id)?; - - let ret = task.ret.take()?; - let content = task.content.take()?; - - task.set_status(TaskStatus::Processing); - let _ = match handler.0.process_content(content).await { - Ok(_) => { - task.set_status(TaskStatus::Done); - let _ = ret.send(task.into()); - } - Err(e) => { - tracing::error!("Process task failed: {:?}", e); - task.set_status(TaskStatus::Failure); - let _ = ret.send(task.into()); - } - }; - self.notify(); - None - } - - pub(crate) fn add_task(&mut self, task: Task) { - assert!(!task.is_finished()); - self.queue.push(&task); - self.store.insert_task(task); - self.notify(); - } - - pub(crate) fn next_task_id(&self) -> TaskId { - self.store.next_task_id() - } - - pub(crate) fn notify(&self) { - let _ = self.notifier.send(false); - } -} - -#[cfg(test)] -mod tests { - use crate::services::grid_editor_task::GridServiceTaskScheduler; - use crate::services::tasks::{GridTaskHandler, GridTaskScheduler, Task, TaskContent, TaskStatus}; - use flowy_error::FlowyError; - use lib_infra::future::BoxResultFuture; - use lib_infra::ref_map::RefCountValue; - use std::sync::Arc; - use std::time::Duration; - use tokio::time::interval; - - #[tokio::test] - async fn task_scheduler_snapshot_task_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task = Task::new("1", task_id, TaskContent::Snapshot); - let rx = task.rx.take().unwrap(); - scheduler.write().await.add_task(task); - assert_eq!(rx.await.unwrap().status, TaskStatus::Done); - } - - #[tokio::test] - async fn task_scheduler_snapshot_task_cancel_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task = Task::new("1", task_id, TaskContent::Snapshot); - let rx = task.rx.take().unwrap(); - scheduler.write().await.add_task(task); - scheduler.write().await.stop(); - - assert_eq!(rx.await.unwrap().status, TaskStatus::Cancel); - } - - #[tokio::test] - async fn task_scheduler_multi_task_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task_1 = Task::new("1", task_id, TaskContent::Snapshot); - let rx_1 = task_1.rx.take().unwrap(); - - let task_id = scheduler.gen_task_id().await; - let mut task_2 = Task::new("1", task_id, TaskContent::Snapshot); - let rx_2 = task_2.rx.take().unwrap(); - - scheduler.write().await.add_task(task_1); - scheduler.write().await.add_task(task_2); - - assert_eq!(rx_1.await.unwrap().status, TaskStatus::Done); - assert_eq!(rx_2.await.unwrap().status, TaskStatus::Done); - } - struct MockGridTaskHandler(); - - impl RefCountValue for MockGridTaskHandler { - fn did_remove(&self) {} - } - - impl GridTaskHandler for MockGridTaskHandler { - fn handler_id(&self) -> &str { - "1" - } - - fn process_content(&self, _content: TaskContent) -> BoxResultFuture<(), FlowyError> { - Box::pin(async move { - let mut interval = interval(Duration::from_secs(1)); - interval.tick().await; - interval.tick().await; - Ok(()) - }) - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs deleted file mode 100644 index 6b88e4598f..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs +++ /dev/null @@ -1,129 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -use crate::services::row::GridBlockSnapshot; -use crate::services::tasks::queue::TaskHandlerId; -use std::cmp::Ordering; - -#[derive(Eq, Debug, Clone, Copy)] -pub enum TaskType { - /// Remove the row if it doesn't satisfy the filter. - Filter, - /// Generate snapshot for grid, unused by now. - Snapshot, - - Group, -} - -impl PartialEq for TaskType { - fn eq(&self, other: &Self) -> bool { - matches!( - (self, other), - (Self::Filter, Self::Filter) | (Self::Snapshot, Self::Snapshot) - ) - } -} - -pub type TaskId = u32; - -#[derive(Eq, Debug, Clone, Copy)] -pub struct PendingTask { - pub ty: TaskType, - pub id: TaskId, -} - -impl PartialEq for PendingTask { - fn eq(&self, other: &Self) -> bool { - self.id.eq(&other.id) - } -} - -impl PartialOrd for PendingTask { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PendingTask { - fn cmp(&self, other: &Self) -> Ordering { - match (self.ty, other.ty) { - // Snapshot - (TaskType::Snapshot, TaskType::Snapshot) => Ordering::Equal, - (TaskType::Snapshot, _) => Ordering::Greater, - (_, TaskType::Snapshot) => Ordering::Less, - // Group - (TaskType::Group, TaskType::Group) => self.id.cmp(&other.id).reverse(), - (TaskType::Group, _) => Ordering::Greater, - (_, TaskType::Group) => Ordering::Greater, - // Filter - (TaskType::Filter, TaskType::Filter) => self.id.cmp(&other.id).reverse(), - } - } -} - -pub(crate) struct FilterTaskContext { - pub blocks: Vec, -} - -pub(crate) enum TaskContent { - #[allow(dead_code)] - Snapshot, - Group, - Filter(FilterTaskContext), -} - -#[derive(Debug, Eq, PartialEq)] -pub(crate) enum TaskStatus { - Pending, - Processing, - Done, - Failure, - Cancel, -} - -pub(crate) struct Task { - pub id: TaskId, - pub handler_id: TaskHandlerId, - pub content: Option, - status: TaskStatus, - pub ret: Option>, - pub rx: Option>, -} - -pub(crate) struct TaskResult { - pub id: TaskId, - pub(crate) status: TaskStatus, -} - -impl std::convert::From for TaskResult { - fn from(task: Task) -> Self { - TaskResult { - id: task.id, - status: task.status, - } - } -} - -impl Task { - pub fn new(handler_id: &str, id: TaskId, content: TaskContent) -> Self { - let (ret, rx) = tokio::sync::oneshot::channel(); - Self { - handler_id: handler_id.to_owned(), - id, - content: Some(content), - ret: Some(ret), - rx: Some(rx), - status: TaskStatus::Pending, - } - } - - pub fn set_status(&mut self, status: TaskStatus) { - self.status = status; - } - - pub fn is_finished(&self) -> bool { - match self.status { - TaskStatus::Done => true, - _ => false, - } - } -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs new file mode 100644 index 0000000000..5753cfb334 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs @@ -0,0 +1,27 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::CheckboxFilterCondition; + +#[tokio::test] +async fn grid_filter_checkbox_is_check_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateCheckboxFilter { + condition: CheckboxFilterCondition::IsChecked, + }, + AssertNumberOfRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_checkbox_is_uncheck_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateCheckboxFilter { + condition: CheckboxFilterCondition::IsUnChecked, + }, + AssertNumberOfRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs new file mode 100644 index 0000000000..c8906154d0 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs @@ -0,0 +1,17 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::DateFilterCondition; + +#[tokio::test] +#[should_panic] +async fn grid_filter_date_is_check_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateIs, + content: "1647251762".to_string(), + }, + AssertNumberOfRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs index 4c6980c527..791576d92e 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs @@ -1,2 +1,5 @@ +mod checkbox_filter_test; +mod date_filter_test; +mod number_filter_test; mod script; mod text_filter_test; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs new file mode 100644 index 0000000000..328bc0d678 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs @@ -0,0 +1,82 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::NumberFilterCondition; + +#[tokio::test] +async fn grid_filter_number_is_equal_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::Equal, + content: "1".to_string(), + }, + AssertNumberOfRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_less_than_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThan, + content: "3".to_string(), + }, + AssertNumberOfRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +#[should_panic] +async fn grid_filter_number_is_less_than_test2() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThan, + content: "$3".to_string(), + }, + AssertNumberOfRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_less_than_or_equal_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThanOrEqualTo, + content: "3".to_string(), + }, + AssertNumberOfRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::NumberIsEmpty, + content: "".to_string(), + }, + AssertNumberOfRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_not_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::NumberIsNotEmpty, + content: "".to_string(), + }, + AssertNumberOfRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs index 371d1d76b2..39c4c6d9c3 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs @@ -3,21 +3,46 @@ #![allow(dead_code)] #![allow(unused_imports)] -use flowy_grid::entities::{InsertFilterParams, InsertFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB}; +use futures::TryFutureExt; +use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition}; use flowy_grid::services::setting::GridSettingChangesetBuilder; use grid_rev_model::{FieldRevision, FieldTypeRevision}; +use flowy_grid::services::filter::FilterType; use crate::grid::grid_editor::GridEditorTest; pub enum FilterScript { - InsertGridTableFilter { - payload: InsertFilterPayloadPB, + InsertFilter { + payload: CreateFilterPayloadPB, }, - AssertTableFilterCount { + CreateTextFilter { + condition: TextFilterCondition, + content: String, + }, + CreateNumberFilter { + condition: NumberFilterCondition, + content: String, + }, + CreateCheckboxFilter { + condition: CheckboxFilterCondition, + }, + CreateDateFilter{ + condition: DateFilterCondition, + content: String, + }, + AssertFilterCount { count: i32, }, - DeleteGridTableFilter { + DeleteFilter { filter_id: String, - field_rev: FieldRevision, + filter_type: FilterType, + }, + AssertFilterContent { + filter_type: FilterType, + condition: u32, + content: String + }, + AssertNumberOfRows{ + expected: usize, }, #[allow(dead_code)] AssertGridSetting { @@ -45,25 +70,65 @@ impl GridFilterTest { pub async fn run_script(&mut self, script: FilterScript) { match script { - - FilterScript::InsertGridTableFilter { payload } => { - let params: InsertFilterParams = payload.try_into().unwrap(); - let _ = self.editor.create_filter(params).await.unwrap(); + FilterScript::InsertFilter { payload } => { + self.insert_filter(payload).await; } - FilterScript::AssertTableFilterCount { count } => { - let filters = self.editor.get_grid_filter().await.unwrap(); + FilterScript::CreateTextFilter { condition, content} => { + let field_rev = self.get_field_rev(FieldType::RichText); + let payload = + CreateFilterPayloadPB::new(field_rev, condition, content); + self.insert_filter(payload).await; + } + FilterScript::CreateNumberFilter {condition, content} => { + let field_rev = self.get_field_rev(FieldType::Number); + let payload = + CreateFilterPayloadPB::new(field_rev, condition, content); + self.insert_filter(payload).await; + } + FilterScript::CreateCheckboxFilter {condition} => { + let field_rev = self.get_field_rev(FieldType::Checkbox); + let payload = + CreateFilterPayloadPB::new(field_rev, condition, "".to_string()); + self.insert_filter(payload).await; + } + FilterScript::CreateDateFilter { condition, content} => { + let field_rev = self.get_field_rev(FieldType::DateTime); + let payload = + CreateFilterPayloadPB::new(field_rev, condition, content); + self.insert_filter(payload).await; + } + FilterScript::AssertFilterCount { count } => { + let filters = self.editor.get_all_filters().await.unwrap(); assert_eq!(count as usize, filters.len()); } - FilterScript::DeleteGridTableFilter { filter_id, field_rev} => { - let params = DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty }; + FilterScript::AssertFilterContent { filter_type: filter_id, condition, content} => { + let filter = self.editor.get_filters(filter_id).await.unwrap().pop().unwrap(); + assert_eq!(&filter.content, &content); + assert_eq!(filter.condition as u32, condition); + + } + FilterScript::DeleteFilter { filter_id, filter_type } => { + let params = DeleteFilterParams { filter_type, filter_id }; let _ = self.editor.delete_filter(params).await.unwrap(); } FilterScript::AssertGridSetting { expected_setting } => { - let setting = self.editor.get_grid_setting().await.unwrap(); + let setting = self.editor.get_setting().await.unwrap(); assert_eq!(expected_setting, setting); } + FilterScript::AssertNumberOfRows { expected } => { + // + let grid = self.editor.get_grid().await.unwrap(); + let rows = grid.blocks.into_iter().map(|block| block.rows).flatten().collect::>(); + assert_eq!(rows.len(), expected); + } } } + + async fn insert_filter(&self, payload: CreateFilterPayloadPB) { + + let params: CreateFilterParams = payload.try_into().unwrap(); + let _ = self.editor.create_filter(params).await.unwrap(); + } } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs index 3893bc3d53..e5e361651e 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs @@ -1,26 +1,71 @@ use crate::grid::filter_test::script::FilterScript::*; use crate::grid::filter_test::script::*; -use flowy_grid::entities::{FieldType, InsertFilterPayloadPB, TextFilterCondition}; -use grid_rev_model::FieldRevision; +use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition}; +use flowy_grid::services::filter::FilterType; #[tokio::test] -async fn grid_filter_create_test() { +async fn grid_filter_text_is_empty_test() { let mut test = GridFilterTest::new().await; - let field_rev = test.get_field_rev(FieldType::RichText); - let payload = InsertFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::TextIsEmpty, + content: "".to_string(), + }, + AssertFilterCount { count: 1 }, + AssertNumberOfRows { expected: 0 }, + ]; test.run_scripts(scripts).await; } #[tokio::test] -#[should_panic] -async fn grid_filter_invalid_condition_panic_test() { +async fn grid_filter_is_text_test() { let mut test = GridFilterTest::new().await; - let field_rev = test.get_field_rev(FieldType::RichText).clone(); + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::Is, + content: "A".to_string(), + }, + AssertNumberOfRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} - // 100 is not a valid condition, so this test should be panic. - let payload = InsertFilterPayloadPB::new(&field_rev, 100, Some("".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }]; +#[tokio::test] +async fn grid_filter_contain_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::Contains, + content: "A".to_string(), + }, + AssertNumberOfRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_start_with_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::StartsWith, + content: "A".to_string(), + }, + AssertNumberOfRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_ends_with_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::EndsWith, + content: "A".to_string(), + }, + AssertNumberOfRows { expected: 2 }, + ]; test.run_scripts(scripts).await; } @@ -28,24 +73,22 @@ async fn grid_filter_invalid_condition_panic_test() { async fn grid_filter_delete_test() { let mut test = GridFilterTest::new().await; let field_rev = test.get_field_rev(FieldType::RichText).clone(); - let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc"); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + let payload = CreateFilterPayloadPB::new(&field_rev, TextFilterCondition::TextIsEmpty, "".to_string()); + let scripts = vec![ + InsertFilter { payload }, + AssertFilterCount { count: 1 }, + AssertNumberOfRows { expected: 0 }, + ]; test.run_scripts(scripts).await; let filter = test.grid_filters().await.pop().unwrap(); test.run_scripts(vec![ - DeleteGridTableFilter { + DeleteFilter { filter_id: filter.id, - field_rev: field_rev.as_ref().clone(), + filter_type: FilterType::from(&field_rev), }, - AssertTableFilterCount { count: 0 }, + AssertFilterCount { count: 0 }, + AssertNumberOfRows { expected: 5 }, ]) .await; } - -#[tokio::test] -async fn grid_filter_get_rows_test() {} - -fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> InsertFilterPayloadPB { - InsertFilterPayloadPB::new(field_rev, condition, Some(s.to_owned())) -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index 6ff1205bbc..0399442474 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -3,6 +3,7 @@ #![allow(unused_imports)] use crate::grid::block_test::util::GridRowTestBuilder; use bytes::Bytes; +use flowy_error::FlowyResult; use flowy_grid::entities::*; use flowy_grid::services::field::SelectOptionPB; use flowy_grid::services::field::*; @@ -55,7 +56,7 @@ impl GridEditorTest { let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); let field_revs = editor.get_field_revs(None).await.unwrap(); let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; + let row_revs = editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs; assert_eq!(block_meta_revs.len(), 1); // It seems like you should add the field in the make_test_grid() function. @@ -76,17 +77,11 @@ impl GridEditorTest { } pub async fn get_row_revs(&self) -> Vec> { - self.editor - .grid_block_snapshots(None) - .await - .unwrap() - .pop() - .unwrap() - .row_revs + self.editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs } - pub async fn grid_filters(&self) -> Vec { - self.editor.get_grid_filter().await.unwrap() + pub async fn grid_filters(&self) -> Vec { + self.editor.get_all_filters().await.unwrap() } pub fn get_field_rev(&self, field_type: FieldType) -> &Arc { @@ -239,7 +234,7 @@ fn make_test_grid() -> BuildGridContext { 3 => { for field_type in FieldType::iter() { match field_type { - FieldType::RichText => row_builder.insert_text_cell("D"), + FieldType::RichText => row_builder.insert_text_cell("DA"), FieldType::Number => row_builder.insert_number_cell("4"), FieldType::DateTime => row_builder.insert_date_cell("1647251762"), FieldType::SingleSelect => { @@ -253,8 +248,8 @@ fn make_test_grid() -> BuildGridContext { 4 => { for field_type in FieldType::iter() { match field_type { - FieldType::RichText => row_builder.insert_text_cell("E"), - FieldType::Number => row_builder.insert_number_cell("5"), + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell(""), FieldType::DateTime => row_builder.insert_date_cell("1647251762"), FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(2)) diff --git a/frontend/rust-lib/flowy-sdk/Cargo.toml b/frontend/rust-lib/flowy-sdk/Cargo.toml index 9ba6960ead..cc5b9a1329 100644 --- a/frontend/rust-lib/flowy-sdk/Cargo.toml +++ b/frontend/rust-lib/flowy-sdk/Cargo.toml @@ -16,6 +16,7 @@ grid-rev-model = { path = "../../../shared-lib/grid-rev-model" } flowy-database = { path = "../flowy-database" } flowy-document = { path = "../flowy-document", default-features = false } flowy-revision = { path = "../flowy-revision" } +flowy-task = { path = "../flowy-task" } tracing = { version = "0.1" } futures-core = { version = "0.3", default-features = false } diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs index fea93a8cef..d4266918e4 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs @@ -6,22 +6,29 @@ use flowy_grid::services::persistence::GridDatabase; use flowy_http_model::ws_data::ClientRevisionWSData; use flowy_net::ws::connection::FlowyWebSocketConnect; use flowy_revision::{RevisionWebSocket, WSStateReceiver}; +use flowy_task::TaskDispatcher; use flowy_user::services::UserSession; use futures_core::future::BoxFuture; use lib_infra::future::BoxResultFuture; use lib_ws::{WSChannel, WebSocketRawMessage}; use std::convert::TryInto; use std::sync::Arc; +use tokio::sync::RwLock; pub struct GridDepsResolver(); impl GridDepsResolver { - pub async fn resolve(ws_conn: Arc, user_session: Arc) -> Arc { + pub async fn resolve( + ws_conn: Arc, + user_session: Arc, + task_scheduler: Arc>, + ) -> Arc { let user = Arc::new(GridUserImpl(user_session.clone())); let rev_web_socket = Arc::new(GridRevisionWebSocket(ws_conn)); let grid_manager = Arc::new(GridManager::new( user.clone(), rev_web_socket, + task_scheduler, Arc::new(GridDatabaseImpl(user_session)), )); diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 666f31bc09..ec757c9e10 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -15,11 +15,13 @@ use flowy_net::{ local_server::LocalServer, ws::connection::{listen_on_websocket, FlowyWebSocketConnect}, }; +use flowy_task::{TaskDispatcher, TaskRunner}; use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig}; use lib_dispatch::prelude::*; use lib_dispatch::runtime::tokio_default_runtime; use module::mk_modules; pub use module::*; +use std::time::Duration; use std::{ fmt, sync::{ @@ -27,7 +29,7 @@ use std::{ Arc, }, }; -use tokio::sync::broadcast; +use tokio::sync::{broadcast, RwLock}; static INIT_LOG: AtomicBool = AtomicBool::new(false); @@ -103,9 +105,10 @@ pub struct FlowySDK { pub document_manager: Arc, pub folder_manager: Arc, pub grid_manager: Arc, - pub dispatcher: Arc, + pub event_dispatcher: Arc, pub ws_conn: Arc, pub local_server: Option>, + pub task_dispatcher: Arc>, } impl FlowySDK { @@ -114,6 +117,10 @@ impl FlowySDK { init_kv(&config.root); tracing::debug!("🔥 {:?}", config); let runtime = tokio_default_runtime().unwrap(); + let task_scheduler = TaskDispatcher::new(Duration::from_secs(2)); + let task_dispatcher = Arc::new(RwLock::new(task_scheduler)); + runtime.spawn(TaskRunner::run(task_dispatcher.clone())); + let (local_server, ws_conn) = mk_local_server(&config.server_config); let (user_session, document_manager, folder_manager, local_server, grid_manager) = runtime.block_on(async { let user_session = mk_user_session(&config, &local_server, &config.server_config); @@ -125,7 +132,8 @@ impl FlowySDK { &config.document, ); - let grid_manager = GridDepsResolver::resolve(ws_conn.clone(), user_session.clone()).await; + let grid_manager = + GridDepsResolver::resolve(ws_conn.clone(), user_session.clone(), task_dispatcher.clone()).await; let folder_manager = FolderDepsResolver::resolve( local_server.clone(), @@ -150,7 +158,7 @@ impl FlowySDK { ) }); - let dispatcher = Arc::new(EventDispatcher::construct(runtime, || { + let event_dispatcher = Arc::new(EventDispatcher::construct(runtime, || { mk_modules( &ws_conn, &folder_manager, @@ -162,7 +170,7 @@ impl FlowySDK { _start_listening( &config, - &dispatcher, + &event_dispatcher, &ws_conn, &user_session, &document_manager, @@ -176,20 +184,21 @@ impl FlowySDK { document_manager, folder_manager, grid_manager, - dispatcher, + event_dispatcher, ws_conn, local_server, + task_dispatcher, } } pub fn dispatcher(&self) -> Arc { - self.dispatcher.clone() + self.event_dispatcher.clone() } } fn _start_listening( config: &FlowySDKConfig, - dispatch: &EventDispatcher, + event_dispatch: &EventDispatcher, ws_conn: &Arc, user_session: &Arc, document_manager: &Arc, @@ -206,7 +215,7 @@ fn _start_listening( let document_manager = document_manager.clone(); let config = config.clone(); - dispatch.spawn(async move { + event_dispatch.spawn(async move { user_session.init(); listen_on_websocket(ws_conn.clone()); _listen_user_status( @@ -220,7 +229,7 @@ fn _start_listening( .await; }); - dispatch.spawn(async move { + event_dispatch.spawn(async move { _listen_network_status(subscribe_network_type, cloned_folder_manager).await; }); } diff --git a/frontend/rust-lib/flowy-sdk/src/module.rs b/frontend/rust-lib/flowy-sdk/src/module.rs index 58fe1f4f54..e6b89da6a6 100644 --- a/frontend/rust-lib/flowy-sdk/src/module.rs +++ b/frontend/rust-lib/flowy-sdk/src/module.rs @@ -11,20 +11,14 @@ pub fn mk_modules( folder_manager: &Arc, grid_manager: &Arc, user_session: &Arc, - text_block_manager: &Arc, + document_manager: &Arc, ) -> Vec { let user_module = mk_user_module(user_session.clone()); let folder_module = mk_folder_module(folder_manager.clone()); let network_module = mk_network_module(ws_conn.clone()); let grid_module = mk_grid_module(grid_manager.clone()); - let text_block_module = mk_text_block_module(text_block_manager.clone()); - vec![ - user_module, - folder_module, - network_module, - grid_module, - text_block_module, - ] + let document_module = mk_text_block_module(document_manager.clone()); + vec![user_module, folder_module, network_module, grid_module, document_module] } fn mk_user_module(user_session: Arc) -> Module { diff --git a/frontend/rust-lib/flowy-task/Cargo.toml b/frontend/rust-lib/flowy-task/Cargo.toml new file mode 100644 index 0000000000..e8ef930f0a --- /dev/null +++ b/frontend/rust-lib/flowy-task/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "flowy-task" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lib-infra = { path = "../../../shared-lib/lib-infra" } +tokio = {version = "1", features = ["sync", "macros", ]} +atomic_refcell = "0.1.8" +anyhow = "1.0" +tracing = { version = "0.1", features = ["log"] } + +[dev-dependencies] +rand = "0.8.5" +futures = "0.3.15" diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs b/frontend/rust-lib/flowy-task/src/lib.rs similarity index 90% rename from frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs rename to frontend/rust-lib/flowy-task/src/lib.rs index e91691cbd7..551f0e0293 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs +++ b/frontend/rust-lib/flowy-task/src/lib.rs @@ -1,5 +1,4 @@ mod queue; -mod runner; mod scheduler; mod store; mod task; diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs b/frontend/rust-lib/flowy-task/src/queue.rs similarity index 86% rename from frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs rename to frontend/rust-lib/flowy-task/src/queue.rs index a20fdab7c6..b87d6b5734 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs +++ b/frontend/rust-lib/flowy-task/src/queue.rs @@ -1,6 +1,5 @@ -use crate::services::tasks::task::{PendingTask, Task, TaskContent, TaskType}; +use crate::{PendingTask, Task}; use atomic_refcell::AtomicRefCell; - use std::cmp::Ordering; use std::collections::hash_map::Entry; use std::collections::{BinaryHeap, HashMap}; @@ -8,30 +7,25 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; #[derive(Default)] -pub(crate) struct GridTaskQueue { +pub(crate) struct TaskQueue { // index_tasks for quick access index_tasks: HashMap>>, queue: BinaryHeap>>, } -impl GridTaskQueue { +impl TaskQueue { pub(crate) fn new() -> Self { Self::default() } pub(crate) fn push(&mut self, task: &Task) { if task.content.is_none() { - tracing::warn!("Ignore task: {} with empty content", task.id); + tracing::warn!("The task:{} with empty content will be not executed", task.id); return; } - let task_type = match task.content.as_ref().unwrap() { - TaskContent::Snapshot => TaskType::Snapshot, - TaskContent::Group => TaskType::Group, - TaskContent::Filter { .. } => TaskType::Filter, - }; let pending_task = PendingTask { - ty: task_type, + qos: task.qos, id: task.id, }; match self.index_tasks.entry(task.handler_id.clone()) { diff --git a/frontend/rust-lib/flowy-task/src/scheduler.rs b/frontend/rust-lib/flowy-task/src/scheduler.rs new file mode 100644 index 0000000000..b9f8e65042 --- /dev/null +++ b/frontend/rust-lib/flowy-task/src/scheduler.rs @@ -0,0 +1,187 @@ +use crate::queue::TaskQueue; +use crate::store::TaskStore; +use crate::{Task, TaskContent, TaskId, TaskState}; +use anyhow::Error; +use lib_infra::future::BoxResultFuture; +use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; +use std::sync::Arc; +use std::time::Duration; + +use tokio::sync::{watch, RwLock}; +use tokio::time::interval; + +pub struct TaskDispatcher { + queue: TaskQueue, + store: TaskStore, + timeout: Duration, + handlers: RefCountHashMap, + + notifier: watch::Sender, + pub(crate) notifier_rx: Option>, +} + +impl TaskDispatcher { + pub fn new(timeout: Duration) -> Self { + let (notifier, notifier_rx) = watch::channel(false); + Self { + queue: TaskQueue::new(), + store: TaskStore::new(), + timeout, + handlers: RefCountHashMap::new(), + notifier, + notifier_rx: Some(notifier_rx), + } + } + + pub fn register_handler(&mut self, handler: T) + where + T: TaskHandler, + { + let handler_id = handler.handler_id().to_owned(); + self.handlers.insert(handler_id, RefCountTaskHandler(Arc::new(handler))); + } + + pub fn unregister_handler>(&mut self, handler_id: T) { + self.handlers.remove(handler_id.as_ref()); + } + + pub fn stop(&mut self) { + let _ = self.notifier.send(true); + self.queue.clear(); + self.store.clear(); + } + + pub(crate) async fn process_next_task(&mut self) -> Option<()> { + let pending_task = self.queue.mut_head(|list| list.pop())?; + let mut task = self.store.remove_task(&pending_task.id)?; + let ret = task.ret.take()?; + + // Do not execute the task if the task was cancelled. + if task.state().is_cancel() { + let _ = ret.send(task.into()); + self.notify(); + return None; + } + + let content = task.content.take()?; + if let Some(handler) = self.handlers.get(&task.handler_id) { + task.set_state(TaskState::Processing); + match tokio::time::timeout(self.timeout, handler.run(content)).await { + Ok(result) => match result { + Ok(_) => task.set_state(TaskState::Done), + Err(e) => { + tracing::error!("Process task failed: {:?}", e); + task.set_state(TaskState::Failure); + } + }, + Err(e) => { + tracing::error!("Process task timeout: {:?}", e); + task.set_state(TaskState::Timeout); + } + } + } else { + task.set_state(TaskState::Cancel); + } + let _ = ret.send(task.into()); + self.notify(); + None + } + + pub fn add_task(&mut self, task: Task) { + debug_assert!(!task.state().is_done()); + if task.state().is_done() { + return; + } + + self.queue.push(&task); + self.store.insert_task(task); + self.notify(); + } + + pub fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.store.read_task(task_id) + } + + pub fn cancel_task(&mut self, task_id: TaskId) { + if let Some(task) = self.store.mut_task(&task_id) { + task.set_state(TaskState::Cancel); + } + } + + pub fn next_task_id(&self) -> TaskId { + self.store.next_task_id() + } + + pub(crate) fn notify(&self) { + let _ = self.notifier.send(false); + } +} +pub struct TaskRunner(); +impl TaskRunner { + pub async fn run(dispatcher: Arc>) { + dispatcher.read().await.notify(); + let debounce_duration = Duration::from_millis(300); + let mut notifier = dispatcher.write().await.notifier_rx.take().expect("Only take once"); + loop { + // stops the runner if the notifier was closed. + if notifier.changed().await.is_err() { + break; + } + + // stops the runner if the value of notifier is `true` + if *notifier.borrow() { + break; + } + + let mut interval = interval(debounce_duration); + interval.tick().await; + let _ = dispatcher.write().await.process_next_task().await; + } + } +} + +pub trait TaskHandler: Send + Sync + 'static { + fn handler_id(&self) -> &str; + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>; +} + +impl TaskHandler for Box +where + T: TaskHandler, +{ + fn handler_id(&self) -> &str { + (**self).handler_id() + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } +} + +impl TaskHandler for Arc +where + T: TaskHandler, +{ + fn handler_id(&self) -> &str { + (**self).handler_id() + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } +} +#[derive(Clone)] +struct RefCountTaskHandler(Arc); + +impl RefCountValue for RefCountTaskHandler { + fn did_remove(&self) {} +} + +impl std::ops::Deref for RefCountTaskHandler { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs b/frontend/rust-lib/flowy-task/src/store.rs similarity index 73% rename from frontend/rust-lib/flowy-grid/src/services/tasks/store.rs rename to frontend/rust-lib/flowy-task/src/store.rs index 9f14889e4d..800b189f21 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs +++ b/frontend/rust-lib/flowy-task/src/store.rs @@ -1,16 +1,15 @@ -use crate::services::tasks::task::Task; -use crate::services::tasks::{TaskId, TaskStatus}; +use crate::{Task, TaskId, TaskState}; use std::collections::HashMap; use std::mem; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering::SeqCst; -pub(crate) struct GridTaskStore { +pub(crate) struct TaskStore { tasks: HashMap, task_id_counter: AtomicU32, } -impl GridTaskStore { +impl TaskStore { pub fn new() -> Self { Self { tasks: HashMap::new(), @@ -26,13 +25,20 @@ impl GridTaskStore { self.tasks.remove(task_id) } - #[allow(dead_code)] + pub(crate) fn mut_task(&mut self, task_id: &TaskId) -> Option<&mut Task> { + self.tasks.get_mut(task_id) + } + + pub(crate) fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.tasks.get(task_id) + } + pub(crate) fn clear(&mut self) { let tasks = mem::take(&mut self.tasks); tasks.into_values().for_each(|mut task| { if task.ret.is_some() { let ret = task.ret.take().unwrap(); - task.set_status(TaskStatus::Cancel); + task.set_state(TaskState::Cancel); let _ = ret.send(task.into()); } }); diff --git a/frontend/rust-lib/flowy-task/src/task.rs b/frontend/rust-lib/flowy-task/src/task.rs new file mode 100644 index 0000000000..02f2322898 --- /dev/null +++ b/frontend/rust-lib/flowy-task/src/task.rs @@ -0,0 +1,142 @@ +use crate::TaskHandlerId; +use std::cmp::Ordering; +use tokio::sync::oneshot::{Receiver, Sender}; + +#[derive(Eq, Debug, Clone, Copy)] +pub enum QualityOfService { + Background, + UserInteractive, +} + +impl PartialEq for QualityOfService { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (Self::Background, Self::Background) | (Self::UserInteractive, Self::UserInteractive) + ) + } +} + +pub type TaskId = u32; + +#[derive(Eq, Debug, Clone, Copy)] +pub struct PendingTask { + pub qos: QualityOfService, + pub id: TaskId, +} + +impl PartialEq for PendingTask { + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) + } +} + +impl PartialOrd for PendingTask { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PendingTask { + fn cmp(&self, other: &Self) -> Ordering { + match (self.qos, other.qos) { + // User interactive + (QualityOfService::UserInteractive, QualityOfService::UserInteractive) => self.id.cmp(&other.id), + (QualityOfService::UserInteractive, _) => Ordering::Greater, + (_, QualityOfService::UserInteractive) => Ordering::Less, + // background + (QualityOfService::Background, QualityOfService::Background) => self.id.cmp(&other.id), + } + } +} + +pub enum TaskContent { + Text(String), + Blob(Vec), +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum TaskState { + Pending, + Processing, + Done, + Failure, + Cancel, + Timeout, +} + +impl TaskState { + pub fn is_pending(&self) -> bool { + matches!(self, TaskState::Pending) + } + pub fn is_done(&self) -> bool { + matches!(self, TaskState::Done) + } + pub fn is_cancel(&self) -> bool { + matches!(self, TaskState::Cancel) + } + + pub fn is_processing(&self) -> bool { + matches!(self, TaskState::Processing) + } + + pub fn is_failed(&self) -> bool { + matches!(self, TaskState::Failure) + } +} + +pub struct Task { + pub id: TaskId, + pub handler_id: TaskHandlerId, + pub content: Option, + pub qos: QualityOfService, + state: TaskState, + pub ret: Option>, + pub recv: Option>, +} + +impl Task { + pub fn background(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::Background) + } + + pub fn user_interactive(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::UserInteractive) + } + + pub fn new(handler_id: &str, id: TaskId, content: TaskContent, qos: QualityOfService) -> Self { + let handler_id = handler_id.to_owned(); + let (ret, recv) = tokio::sync::oneshot::channel(); + Self { + handler_id, + id, + content: Some(content), + qos, + ret: Some(ret), + recv: Some(recv), + state: TaskState::Pending, + } + } + + pub fn state(&self) -> &TaskState { + &self.state + } + + pub(crate) fn set_state(&mut self, status: TaskState) { + self.state = status; + } +} + +pub struct TaskResult { + pub id: TaskId, + pub state: TaskState, +} + +impl std::convert::From for TaskResult { + fn from(task: Task) -> Self { + TaskResult { + id: task.id, + state: task.state().clone(), + } + } +} diff --git a/frontend/rust-lib/flowy-task/tests/main.rs b/frontend/rust-lib/flowy-task/tests/main.rs new file mode 100644 index 0000000000..d1f580aa65 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/main.rs @@ -0,0 +1 @@ +mod task_test; diff --git a/frontend/rust-lib/flowy-task/tests/task_test/mod.rs b/frontend/rust-lib/flowy-task/tests/task_test/mod.rs new file mode 100644 index 0000000000..864b94c3b7 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/mod.rs @@ -0,0 +1,3 @@ +mod script; +mod task_cancel_test; +mod task_order_test; diff --git a/frontend/rust-lib/flowy-task/tests/task_test/script.rs b/frontend/rust-lib/flowy-task/tests/task_test/script.rs new file mode 100644 index 0000000000..ef28315193 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/script.rs @@ -0,0 +1,196 @@ +use anyhow::Error; +use flowy_task::{Task, TaskContent, TaskDispatcher, TaskHandler, TaskId, TaskResult, TaskRunner, TaskState}; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use lib_infra::future::BoxResultFuture; +use lib_infra::ref_map::RefCountValue; +use rand::Rng; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::oneshot::Receiver; +use tokio::sync::RwLock; + +pub enum SearchScript { + AddTask { + task: Task, + }, + AddTasks { + tasks: Vec, + }, + #[allow(dead_code)] + Wait { + millisecond: u64, + }, + CancelTask { + task_id: TaskId, + }, + UnregisterHandler { + handler_id: String, + }, + AssertTaskStatus { + task_id: TaskId, + expected_status: TaskState, + }, + AssertExecuteOrder { + execute_order: Vec, + rets: Vec>, + }, +} + +pub struct SearchTest { + scheduler: Arc>, +} + +impl SearchTest { + pub async fn new() -> Self { + let duration = Duration::from_millis(1000); + let mut scheduler = TaskDispatcher::new(duration); + scheduler.register_handler(Arc::new(MockTextTaskHandler())); + scheduler.register_handler(Arc::new(MockBlobTaskHandler())); + scheduler.register_handler(Arc::new(MockTimeoutTaskHandler())); + + let scheduler = Arc::new(RwLock::new(scheduler)); + tokio::spawn(TaskRunner::run(scheduler.clone())); + + Self { scheduler } + } + + pub async fn next_task_id(&self) -> TaskId { + self.scheduler.read().await.next_task_id() + } + + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&self, script: SearchScript) { + match script { + SearchScript::AddTask { task } => { + self.scheduler.write().await.add_task(task); + } + SearchScript::CancelTask { task_id } => { + self.scheduler.write().await.cancel_task(task_id); + } + SearchScript::AddTasks { tasks } => { + let mut scheduler = self.scheduler.write().await; + for task in tasks { + scheduler.add_task(task); + } + } + SearchScript::Wait { millisecond } => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } + SearchScript::UnregisterHandler { handler_id } => { + self.scheduler.write().await.unregister_handler(handler_id); + } + SearchScript::AssertTaskStatus { + task_id, + expected_status, + } => { + let status = self.scheduler.read().await.read_task(&task_id).unwrap().state().clone(); + assert_eq!(status, expected_status); + } + SearchScript::AssertExecuteOrder { execute_order, rets } => { + let mut futures = FuturesUnordered::new(); + for ret in rets { + futures.push(ret); + } + let mut orders = vec![]; + while let Some(Ok(result)) = futures.next().await { + orders.push(result.id); + assert!(result.state.is_done()); + } + assert_eq!(execute_order, orders); + } + } + } +} + +pub struct MockTextTaskHandler(); +impl RefCountValue for MockTextTaskHandler { + fn did_remove(&self) {} +} + +impl TaskHandler for MockTextTaskHandler { + fn handler_id(&self) -> &str { + "1" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + let mut rng = rand::thread_rng(); + let millisecond = rng.gen_range(1..50); + Box::pin(async move { + match content { + TaskContent::Text(_s) => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } + TaskContent::Blob(_) => panic!("Only support text"), + } + Ok(()) + }) + } +} + +pub fn make_text_background_task(task_id: TaskId, s: &str) -> (Task, Receiver) { + let mut task = Task::background("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) +} + +pub fn make_text_user_interactive_task(task_id: TaskId, s: &str) -> (Task, Receiver) { + let mut task = Task::user_interactive("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) +} + +pub struct MockBlobTaskHandler(); +impl RefCountValue for MockBlobTaskHandler { + fn did_remove(&self) {} +} + +impl TaskHandler for MockBlobTaskHandler { + fn handler_id(&self) -> &str { + "2" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(bytes) => { + let _msg = String::from_utf8(bytes).unwrap(); + tokio::time::sleep(Duration::from_millis(20)).await; + } + } + Ok(()) + }) + } +} + +pub struct MockTimeoutTaskHandler(); + +impl TaskHandler for MockTimeoutTaskHandler { + fn handler_id(&self) -> &str { + "3" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(_bytes) => { + tokio::time::sleep(Duration::from_millis(2000)).await; + } + } + Ok(()) + }) + } +} + +pub fn make_timeout_task(task_id: TaskId) -> (Task, Receiver) { + let mut task = Task::background("3", task_id, TaskContent::Blob(vec![])); + let recv = task.recv.take().unwrap(); + (task, recv) +} diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs new file mode 100644 index 0000000000..f4a9422d86 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs @@ -0,0 +1,88 @@ +use crate::task_test::script::SearchScript::*; +use crate::task_test::script::{make_text_background_task, make_timeout_task, SearchTest}; +use flowy_task::{QualityOfService, Task, TaskContent, TaskState}; + +#[tokio::test] +async fn task_cancel_background_task_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "Hello world"); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AssertTaskStatus { + task_id: 1, + expected_status: TaskState::Pending, + }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Pending, + }, + CancelTask { task_id: 2 }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Cancel, + }, + ]) + .await; + + let result = ret_1.await.unwrap(); + assert_eq!(result.state, TaskState::Done); + + let result = ret_2.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_with_empty_handler_id_test() { + let test = SearchTest::new().await; + let mut task = Task::new( + "", + test.next_task_id().await, + TaskContent::Text("".to_owned()), + QualityOfService::Background, + ); + let ret = task.recv.take().unwrap(); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_can_not_find_handler_test() { + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, "Hello world"); + let handler_id = task.handler_id.clone(); + test.run_scripts(vec![UnregisterHandler { handler_id }, AddTask { task }]) + .await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_can_not_find_handler_test2() { + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; + let handler_id = "1".to_owned(); + for _i in 1..10000 { + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + tasks.push(task); + rets.push(ret); + } + + test.run_scripts(vec![UnregisterHandler { handler_id }, AddTasks { tasks }]) + .await; +} + +#[tokio::test] +async fn task_run_timeout_test() { + let test = SearchTest::new().await; + let (task, ret) = make_timeout_task(test.next_task_id().await); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Timeout); +} diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs new file mode 100644 index 0000000000..a2c084a42c --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs @@ -0,0 +1,111 @@ +use crate::task_test::script::{ + make_text_background_task, make_text_user_interactive_task, SearchScript::*, SearchTest, +}; + +#[tokio::test] +async fn task_add_single_background_task_test() { + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert!(result.state.is_done()) +} + +#[tokio::test] +async fn task_add_multiple_background_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} + +#[tokio::test] +async fn task_add_multiple_user_interactive_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_user_interactive_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} +#[tokio::test] +async fn task_add_multiple_different_kind_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![2, 3, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} + +#[tokio::test] +async fn task_add_multiple_different_kind_tasks_test2() { + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; + + for i in 0..10 { + let (task, ret) = if i % 2 == 0 { + make_text_background_task(test.next_task_id().await, "") + } else { + make_text_user_interactive_task(test.next_task_id().await, "") + }; + tasks.push(task); + rets.push(ret); + } + + test.run_scripts(vec![ + AddTasks { tasks }, + AssertExecuteOrder { + execute_order: vec![10, 8, 6, 4, 2, 9, 7, 5, 3, 1], + rets, + }, + ]) + .await; +} + +// #[tokio::test] +// async fn task_add_1000_tasks_test() { +// let test = SearchTest::new().await; +// let mut tasks = vec![]; +// let mut execute_order = vec![]; +// let mut rets = vec![]; +// +// for i in 1..1000 { +// let (task, ret) = make_text_background_task(test.next_task_id().await, ""); +// execute_order.push(i); +// tasks.push(task); +// rets.push(ret); +// } +// execute_order.reverse(); +// +// test.run_scripts(vec![AddTasks { tasks }, AssertExecuteOrder { execute_order, rets }]) +// .await; +// } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index d0812b3054..7e14a73eec 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -189,14 +189,6 @@ impl GridRevisionPad { }) } - pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { - self.grid_rev - .fields - .iter() - .enumerate() - .find(|(_, field)| field.id == field_id) - } - pub fn replace_field_rev( &mut self, field_rev: Arc, @@ -238,6 +230,14 @@ impl GridRevisionPad { self.grid_rev.fields.iter().any(|field| field.id == field_id) } + pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { + self.grid_rev + .fields + .iter() + .enumerate() + .find(|(_, field)| field.id == field_id) + } + pub fn get_field_revs(&self, field_ids: Option>) -> CollaborateResult>> { match field_ids { None => Ok(self.grid_rev.fields.clone()), diff --git a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs index 4b5f14b56b..43aa801d1f 100644 --- a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs @@ -3,8 +3,7 @@ use crate::util::{cal_diff, make_operations_from_revisions}; use flowy_http_model::revision::Revision; use flowy_http_model::util::md5; use grid_rev_model::{ - FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision, - GroupConfigurationRevision, GroupConfigurationsByFieldId, LayoutRevision, + FieldRevision, FieldTypeRevision, FilterRevision, GridViewRevision, GroupConfigurationRevision, LayoutRevision, }; use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; use std::sync::Arc; @@ -61,8 +60,12 @@ impl GridViewRevisionPad { Self::from_operations(view_id, operations) } - pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Option { - self.groups.get_objects_by_field_revs(field_revs) + pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { + self.groups + .get_objects_by_field_revs(field_revs) + .into_values() + .flatten() + .collect() } pub fn get_all_groups(&self) -> Vec> { @@ -113,9 +116,9 @@ impl GridViewRevisionPad { pub fn delete_group( &mut self, + group_id: &str, field_id: &str, field_type: &FieldTypeRevision, - group_id: &str, ) -> CollaborateResult> { self.modify(|view| { if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) { @@ -127,23 +130,23 @@ impl GridViewRevisionPad { }) } - pub fn get_all_filters(&self, field_revs: &[Arc]) -> Option { - self.filters.get_objects_by_field_revs(field_revs) + pub fn get_all_filters(&self, field_revs: &[Arc]) -> Vec> { + self.filters + .get_objects_by_field_revs(field_revs) + .into_values() + .flatten() + .collect() } - pub fn get_filters( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - ) -> Option>> { - self.filters.get_objects(field_id, field_type_rev) + pub fn get_filters(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Vec> { + self.filters.get_objects(field_id, field_type_rev).unwrap_or_default() } pub fn insert_filter( &mut self, field_id: &str, field_type: &FieldTypeRevision, - filter_rev: FilterConfigurationRevision, + filter_rev: FilterRevision, ) -> CollaborateResult> { self.modify(|view| { view.filters.add_object(field_id, field_type, filter_rev); @@ -153,9 +156,9 @@ impl GridViewRevisionPad { pub fn delete_filter( &mut self, + filter_id: &str, field_id: &str, field_type: &FieldTypeRevision, - filter_id: &str, ) -> CollaborateResult> { self.modify(|view| { if let Some(filters) = view.filters.get_mut_objects(field_id, field_type) { diff --git a/shared-lib/grid-rev-model/src/filter_rev.rs b/shared-lib/grid-rev-model/src/filter_rev.rs index 7079b52229..b48791b02f 100644 --- a/shared-lib/grid-rev-model/src/filter_rev.rs +++ b/shared-lib/grid-rev-model/src/filter_rev.rs @@ -1,9 +1,10 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct FilterConfigurationRevision { +pub struct FilterRevision { pub id: String, pub field_id: String, pub condition: u8, - pub content: Option, + #[serde(default)] + pub content: String, } diff --git a/shared-lib/grid-rev-model/src/grid_setting_rev.rs b/shared-lib/grid-rev-model/src/grid_setting_rev.rs index f5a8e0d40c..31d1b2ba47 100644 --- a/shared-lib/grid-rev-model/src/grid_setting_rev.rs +++ b/shared-lib/grid-rev-model/src/grid_setting_rev.rs @@ -1,4 +1,4 @@ -use crate::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision}; +use crate::{FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision}; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; @@ -19,8 +19,8 @@ pub fn gen_grid_sort_id() -> String { nanoid!(6) } -pub type FilterConfiguration = Configuration; -pub type FilterConfigurationsByFieldId = HashMap>>; +pub type FilterConfiguration = Configuration; +pub type FilterConfigurationsByFieldId = HashMap>>; // pub type GroupConfiguration = Configuration; pub type GroupConfigurationsByFieldId = HashMap>>; @@ -60,7 +60,7 @@ where .cloned() } - pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Option>>> { + pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> HashMap>> { // Get the objects according to the FieldType, so we need iterate the field_revs. let objects_by_field_id = field_revs .iter() @@ -73,7 +73,7 @@ where Some((field_rev.id.clone(), objects)) }) .collect::>>>(); - Some(objects_by_field_id) + objects_by_field_id } pub fn get_all_objects(&self) -> Vec> { diff --git a/shared-lib/lib-infra/src/future.rs b/shared-lib/lib-infra/src/future.rs index a6bad3b298..9c1fe5ee51 100644 --- a/shared-lib/lib-infra/src/future.rs +++ b/shared-lib/lib-infra/src/future.rs @@ -8,20 +8,20 @@ use std::{ task::{Context, Poll}, }; -pub fn wrap_future(f: T) -> AFFuture +pub fn to_future(f: T) -> Fut where T: Future + Send + Sync + 'static, { - AFFuture { fut: Box::pin(f) } + Fut { fut: Box::pin(f) } } #[pin_project] -pub struct AFFuture { +pub struct Fut { #[pin] pub fut: Pin + Sync + Send>>, } -impl Future for AFFuture +impl Future for Fut where T: Send + Sync, { diff --git a/shared-lib/lib-infra/src/ref_map.rs b/shared-lib/lib-infra/src/ref_map.rs index 8ef4ba8a6b..33731220fa 100644 --- a/shared-lib/lib-infra/src/ref_map.rs +++ b/shared-lib/lib-infra/src/ref_map.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::Arc; pub trait RefCountValue { - fn did_remove(&self); + fn did_remove(&self) {} } struct RefCountHandler {