diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index ba3702038e..e09e156772 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -50,6 +50,7 @@ lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file [features] -default = [] +default = ["filter"] dart = ["lib-infra/dart"] +filter = [] flowy_unit_test = ["flowy-revision/flowy_unit_test"] \ No newline at end of file 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 c13cad33e8..bb31b7a3be 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,4 +1,3 @@ -use crate::services::field::CheckboxCellData; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use flowy_grid_data_model::revision::GridFilterRevision; @@ -10,16 +9,6 @@ pub struct GridCheckboxFilter { pub condition: CheckboxCondition, } -impl GridCheckboxFilter { - pub fn apply(&self, cell_data: &CheckboxCellData) -> bool { - let is_check = cell_data.is_check(); - match self.condition { - CheckboxCondition::IsChecked => is_check, - CheckboxCondition::IsUnChecked => !is_check, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum CheckboxCondition { @@ -58,20 +47,3 @@ impl std::convert::From> for GridCheckboxFilter { } } } - -#[cfg(test)] -mod tests { - use crate::entities::{CheckboxCondition, GridCheckboxFilter}; - use crate::services::field::CheckboxCellData; - - #[test] - fn checkbox_filter_is_check_test() { - let checkbox_filter = GridCheckboxFilter { - condition: CheckboxCondition::IsChecked, - }; - for (value, r) in [("true", true), ("yes", true), ("false", false), ("no", false)] { - let data = CheckboxCellData(value.to_owned()); - assert_eq!(checkbox_filter.apply(&data), r); - } - } -} 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 f892c8b10d..936b95216c 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 @@ -1,6 +1,10 @@ +use crate::entities::FieldType; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; +use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::GridFilterRevision; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] @@ -9,7 +13,77 @@ pub struct GridDateFilter { pub condition: DateFilterCondition, #[pb(index = 2, one_of)] - pub content: Option, + pub start: Option, + + #[pb(index = 3, one_of)] + pub end: Option, +} + +#[derive(ProtoBuf, Default, Clone, Debug)] +pub struct CreateGridDateFilterPayload { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] + pub field_type: FieldType, + + #[pb(index = 3)] + pub condition: DateFilterCondition, + + #[pb(index = 4, one_of)] + pub start: Option, + + #[pb(index = 5, one_of)] + pub end: Option, +} + +pub struct CreateGridDateFilterParams { + pub field_id: String, + + pub field_type: FieldType, + + pub condition: DateFilterCondition, + + pub start: Option, + + pub end: Option, +} + +impl TryInto for CreateGridDateFilterPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + Ok(CreateGridDateFilterParams { + field_id, + condition: self.condition, + start: self.start, + field_type: self.field_type, + end: self.end, + }) + } +} + +#[derive(Serialize, Deserialize, Default)] +struct DateRange { + start: Option, + end: Option, +} + +impl ToString for DateRange { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + } +} + +impl FromStr for DateRange { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] @@ -48,9 +122,21 @@ impl std::convert::TryFrom for DateFilterCondition { } impl std::convert::From> for GridDateFilter { fn from(rev: Arc) -> Self { - GridDateFilter { - condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs), - content: rev.content.clone(), - } + let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs); + let mut filter = GridDateFilter { + condition, + ..Default::default() + }; + + if let Some(range) = rev + .content + .as_ref() + .and_then(|content| DateRange::from_str(content).ok()) + { + filter.start = range.start; + filter.end = range.end; + }; + + filter } } 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 b54f94972d..097ff5330a 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,10 +1,7 @@ -use crate::services::field::NumberCellData; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use flowy_grid_data_model::revision::GridFilterRevision; -use rust_decimal::prelude::Zero; -use rust_decimal::Decimal; -use std::str::FromStr; + use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] @@ -16,31 +13,6 @@ pub struct GridNumberFilter { pub content: Option, } -impl GridNumberFilter { - pub fn apply(&self, num_cell_data: &NumberCellData) -> bool { - if self.content.is_none() { - return false; - } - - 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, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum NumberFilterCondition { @@ -91,52 +63,3 @@ impl std::convert::From> for GridNumberFilter { } } } - -#[cfg(test)] -mod tests { - use crate::entities::{GridNumberFilter, NumberFilterCondition}; - - use crate::services::field::{NumberCellData, NumberFormat}; - use std::str::FromStr; - #[test] - fn number_filter_equal_test() { - let number_filter = GridNumberFilter { - condition: NumberFilterCondition::Equal, - content: Some("123".to_owned()), - }; - - for (num_str, r) in [("123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_str(num_str).unwrap(); - assert_eq!(number_filter.apply(&data), r); - } - - let format = NumberFormat::USD; - for (num_str, r) in [("$123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); - assert_eq!(number_filter.apply(&data), r); - } - } - #[test] - fn number_filter_greater_than_test() { - let number_filter = GridNumberFilter { - condition: NumberFilterCondition::GreaterThan, - content: Some("12".to_owned()), - }; - for (num_str, r) in [("123", true), ("10", false), ("30", true), ("", false)] { - let data = NumberCellData::from_str(num_str).unwrap(); - assert_eq!(number_filter.apply(&data), r); - } - } - - #[test] - fn number_filter_less_than_test() { - let number_filter = GridNumberFilter { - condition: NumberFilterCondition::LessThan, - content: Some("100".to_owned()), - }; - for (num_str, r) in [("12", true), ("1234", false), ("30", true), ("", true)] { - let data = NumberCellData::from_str(num_str).unwrap(); - assert_eq!(number_filter.apply(&data), r); - } - } -} 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 15edec0707..73c39cb657 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,5 +1,4 @@ -#![allow(clippy::needless_collect)] -use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions}; +use crate::services::field::select_option::SelectOptionIds; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use flowy_grid_data_model::revision::GridFilterRevision; @@ -13,36 +12,6 @@ pub struct GridSelectOptionFilter { #[pb(index = 2)] pub option_ids: Vec, } - -impl GridSelectOptionFilter { - pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool { - let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); - match self.condition { - SelectOptionCondition::OptionIs => { - // if selected options equal to filter's options, then the required_options will be empty. - let required_options = self - .option_ids - .iter() - .filter(|id| !selected_option_ids.contains(id)) - .collect::>(); - - // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect - !required_options.is_empty() - } - SelectOptionCondition::OptionIsNot => { - for option_id in selected_option_ids { - if self.option_ids.contains(option_id) { - return true; - } - } - false - } - SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), - SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum SelectOptionCondition { @@ -87,35 +56,3 @@ impl std::convert::From> for GridSelectOptionFilter { } } } - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; - use crate::services::field::select_option::{SelectOption, SelectedSelectOptions}; - - #[test] - fn select_option_filter_is_test() { - let option_1 = SelectOption::new("A"); - let option_2 = SelectOption::new("B"); - - let filter_1 = GridSelectOptionFilter { - condition: SelectOptionCondition::OptionIs, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - - assert_eq!( - filter_1.apply(&SelectedSelectOptions { - options: vec![option_1.clone(), option_2.clone()], - }), - false - ); - - assert_eq!( - filter_1.apply(&SelectedSelectOptions { - options: vec![option_1.clone()], - }), - true, - ); - } -} 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 65253d2faa..7335e89129 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 @@ -12,27 +12,6 @@ pub struct GridTextFilter { pub content: Option, } -impl GridTextFilter { - pub fn apply>(&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 - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum TextFilterCondition { @@ -83,68 +62,3 @@ impl std::convert::From> for GridTextFilter { } } } - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{GridTextFilter, TextFilterCondition}; - - #[test] - fn text_filter_equal_test() { - let text_filter = GridTextFilter { - condition: TextFilterCondition::Is, - content: Some("appflowy".to_owned()), - }; - - assert!(text_filter.apply("AppFlowy")); - assert_eq!(text_filter.apply("appflowy"), true); - assert_eq!(text_filter.apply("Appflowy"), true); - assert_eq!(text_filter.apply("AppFlowy.io"), false); - } - #[test] - fn text_filter_start_with_test() { - let text_filter = GridTextFilter { - condition: TextFilterCondition::StartsWith, - content: Some("appflowy".to_owned()), - }; - - assert_eq!(text_filter.apply("AppFlowy.io"), true); - assert_eq!(text_filter.apply(""), false); - assert_eq!(text_filter.apply("https"), false); - } - - #[test] - fn text_filter_end_with_test() { - let text_filter = GridTextFilter { - condition: TextFilterCondition::EndsWith, - content: Some("appflowy".to_owned()), - }; - - assert_eq!(text_filter.apply("https://github.com/appflowy"), true); - assert_eq!(text_filter.apply("App"), false); - assert_eq!(text_filter.apply("appflowy.io"), false); - } - #[test] - fn text_filter_empty_test() { - let text_filter = GridTextFilter { - condition: TextFilterCondition::TextIsEmpty, - content: Some("appflowy".to_owned()), - }; - - assert_eq!(text_filter.apply(""), true); - assert_eq!(text_filter.apply("App"), false); - } - #[test] - fn text_filter_contain_test() { - let text_filter = GridTextFilter { - condition: TextFilterCondition::Contains, - content: Some("appflowy".to_owned()), - }; - - assert_eq!(text_filter.apply("https://github.com/appflowy"), true); - assert_eq!(text_filter.apply("AppFlowy"), true); - assert_eq!(text_filter.apply("App"), false); - assert_eq!(text_filter.apply(""), false); - assert_eq!(text_filter.apply("github"), false); - } -} 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 32ac38f986..bc520dea54 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 @@ -22,16 +22,16 @@ pub struct RepeatedGridFilter { pub items: Vec, } -impl std::convert::From<&Arc> for GridFilter { - fn from(rev: &Arc) -> Self { +impl std::convert::From<&GridFilterRevision> for GridFilter { + fn from(rev: &GridFilterRevision) -> Self { Self { id: rev.id.clone() } } } -impl std::convert::From<&Vec>> for RepeatedGridFilter { - fn from(revs: &Vec>) -> Self { +impl std::convert::From>> for RepeatedGridFilter { + fn from(revs: Vec>) -> Self { RepeatedGridFilter { - items: revs.iter().map(|rev| rev.into()).collect(), + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } @@ -45,9 +45,12 @@ impl std::convert::From> for RepeatedGridFilter { #[derive(ProtoBuf, Debug, Default, Clone)] pub struct DeleteFilterPayload { #[pb(index = 1)] - pub filter_id: String, + pub field_id: String, #[pb(index = 2)] + pub filter_id: String, + + #[pb(index = 3)] pub field_type: FieldType, } @@ -55,10 +58,14 @@ impl TryInto for DeleteFilterPayload { type Error = ErrorCode; fn try_into(self) -> Result { + 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 { + field_id, filter_id, field_type_rev: self.field_type.into(), }) diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs index dc090e6ea1..53d20bbc57 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs @@ -4,6 +4,7 @@ use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::GridGroupRevision; use flowy_sync::entities::grid::CreateGridGroupParams; use std::convert::TryInto; +use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GridGroup { @@ -39,10 +40,10 @@ impl std::convert::From> for RepeatedGridGroup { } } -impl std::convert::From<&Vec> for RepeatedGridGroup { - fn from(revs: &Vec) -> Self { +impl std::convert::From>> for RepeatedGridGroup { + fn from(revs: Vec>) -> Self { RepeatedGridGroup { - items: revs.iter().map(|rev| rev.into()).collect(), + items: revs.iter().map(|rev| rev.as_ref().into()).collect(), } } } 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 ce2d623b0c..740ab38def 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs @@ -9,49 +9,45 @@ use flowy_grid_data_model::revision::GridLayoutRevision; use flowy_sync::entities::grid::GridSettingChangesetParams; use std::collections::HashMap; use std::convert::TryInto; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GridSetting { #[pb(index = 1)] - pub filters_by_layout_ty: HashMap, + pub layouts: Vec, #[pb(index = 2)] - pub groups_by_layout_ty: HashMap, + pub current_layout_type: GridLayoutType, #[pb(index = 3)] - pub sorts_by_layout_ty: HashMap, + pub filters_by_field_id: HashMap, + + #[pb(index = 4)] + pub groups_by_field_id: HashMap, + + #[pb(index = 5)] + pub sorts_by_field_id: HashMap, } -// -// impl std::convert::From<&GridSettingRevision> for GridSetting { -// fn from(rev: &GridSettingRevision) -> Self { -// let filters_by_layout_ty: HashMap = rev -// .filters -// .iter() -// .map(|(layout_rev, filter_revs)| (layout_rev.to_string(), filter_revs.into())) -// .collect(); -// -// let groups_by_layout_ty: HashMap = rev -// .groups -// .iter() -// .map(|(layout_rev, group_revs)| (layout_rev.to_string(), group_revs.into())) -// .collect(); -// -// let sorts_by_layout_ty: HashMap = rev -// .sorts -// .iter() -// .map(|(layout_rev, sort_revs)| (layout_rev.to_string(), sort_revs.into())) -// .collect(); -// -// GridSetting { -// filters_by_layout_ty, -// groups_by_layout_ty, -// sorts_by_layout_ty, -// } -// } -// } -// -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridLayout { + #[pb(index = 1)] + ty: GridLayoutType, +} + +impl GridLayout { + pub fn all() -> Vec { + let mut layouts = vec![]; + for layout_ty in GridLayoutType::iter() { + layouts.push(GridLayout { ty: layout_ty }) + } + + layouts + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)] #[repr(u8)] pub enum GridLayoutType { Table = 0, diff --git a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs index 1f03808fa8..3a560460fe 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs @@ -4,6 +4,7 @@ use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::GridSortRevision; use flowy_sync::entities::grid::CreateGridSortParams; use std::convert::TryInto; +use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GridSort { @@ -30,10 +31,10 @@ pub struct RepeatedGridSort { pub items: Vec, } -impl std::convert::From<&Vec> for RepeatedGridSort { - fn from(revs: &Vec) -> Self { +impl std::convert::From>> for RepeatedGridSort { + fn from(revs: Vec>) -> Self { RepeatedGridSort { - items: revs.iter().map(|rev| rev.into()).collect(), + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index d45d5e31c7..7d470bdfb9 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -363,9 +363,16 @@ pub(crate) async fn get_select_option_handler( data_result(SelectOptionCellData::default()) } Some(field_rev) => { + // let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?; let type_option = select_option_operation(&field_rev)?; - let any_cell_data: AnyCellData = cell_rev.try_into()?; + let any_cell_data: AnyCellData = match cell_rev { + None => AnyCellData { + data: "".to_string(), + field_type: field_rev.field_type_rev.clone().into(), + }, + Some(cell_rev) => cell_rev.try_into()?, + }; let option_context = type_option.selected_select_option(any_cell_data); data_result(option_context) } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs index 8caeafd1ac..259fcc79ef 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs @@ -9,7 +9,7 @@ use std::str::FromStr; /// So it will return an empty data. You could check the CellDataOperation trait for more information. #[derive(Debug, Serialize, Deserialize)] pub struct AnyCellData { - pub cell_data: String, + pub data: String, pub field_type: FieldType, } @@ -38,21 +38,10 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData { } } -impl std::convert::TryFrom<&Option> for AnyCellData { +impl std::convert::TryFrom for AnyCellData { type Error = FlowyError; - fn try_from(value: &Option) -> Result { - match value { - None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")), - Some(cell_rev) => AnyCellData::try_from(cell_rev), - } - } -} - -impl std::convert::TryFrom> for AnyCellData { - type Error = FlowyError; - - fn try_from(value: Option) -> Result { + fn try_from(value: CellRevision) -> Result { Self::try_from(&value) } } @@ -60,7 +49,7 @@ impl std::convert::TryFrom> for AnyCellData { impl AnyCellData { pub fn new(content: String, field_type: FieldType) -> Self { AnyCellData { - cell_data: content, + data: content, field_type, } } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs index c866f2a167..5b74c26aa4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs @@ -51,7 +51,10 @@ pub fn apply_cell_data_changeset>( pub fn decode_any_cell_data>(data: T, field_rev: &FieldRevision) -> DecodedCellData { if let Ok(any_cell_data) = data.try_into() { - let AnyCellData { cell_data, field_type } = any_cell_data; + let AnyCellData { + data: cell_data, + field_type, + } = any_cell_data; let to_field_type = field_rev.field_type_rev.into(); match try_decode_cell_data(CellData(Some(cell_data)), field_rev, &field_type, &to_field_type) { Ok(cell_data) => cell_data, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs index e4b3d0f3d6..3e974e9f56 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/select_option.rs @@ -65,7 +65,7 @@ pub fn make_selected_select_options>( options: &[SelectOption], ) -> Vec { if let Ok(type_option_cell_data) = any_cell_data.try_into() { - let ids = SelectOptionIds::from(type_option_cell_data.cell_data); + let ids = SelectOptionIds::from(type_option_cell_data.data); ids.iter() .flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned()) .collect() @@ -151,7 +151,7 @@ impl std::convert::TryFrom for SelectOptionIds { type Error = FlowyError; fn try_from(value: AnyCellData) -> Result { - Ok(Self::from(value.cell_data)) + Ok(Self::from(value.data)) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 12361e3fcb..1808f0dc82 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -1,8 +1,6 @@ -use crate::entities::{FieldType, GridCheckboxFilter}; +use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, -}; +use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -42,16 +40,6 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); const YES: &str = "Yes"; const NO: &str = "No"; -impl CellFilterOperation for CheckboxTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult { - if !any_cell_data.is_checkbox() { - return Ok(true); - } - let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?; - Ok(filter.apply(&checkbox_cell_data)) - } -} - impl CellDataOperation for CheckboxTypeOption { fn decode_cell_data( &self, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs index 34b354b060..2200dad63c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs @@ -1,9 +1,8 @@ -use crate::entities::{CellChangeset, FieldType, GridDateFilter}; +use crate::entities::{CellChangeset, FieldType}; use crate::entities::{CellIdentifier, CellIdentifierPayload}; use crate::impl_type_option; use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, - FromCellChangeset, FromCellString, + AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellChangeset, FromCellString, }; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; @@ -110,6 +109,10 @@ impl DateTypeOption { fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { let native = NaiveDateTime::from_timestamp(timestamp, 0); + let native2 = NaiveDateTime::from_timestamp(timestamp, 0); + + if native > native2 {} + self.utc_date_time_from_native(native) } @@ -118,19 +121,10 @@ impl DateTypeOption { } } -impl CellFilterOperation for DateTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridDateFilter) -> FlowyResult { - if !any_cell_data.is_date() { - return Ok(true); - } - Ok(false) - } -} - -impl CellDataOperation for DateTypeOption { +impl CellDataOperation for DateTypeOption { fn decode_cell_data( &self, - cell_data: CellData, + cell_data: CellData, decoded_field_type: &FieldType, _field_rev: &FieldRevision, ) -> FlowyResult { @@ -168,17 +162,36 @@ impl CellDataOperation for DateTypeOption { } } -pub struct TimestampParser(i64); +pub struct DateTimestamp(i64); +impl AsRef for DateTimestamp { + fn as_ref(&self) -> &i64 { + &self.0 + } +} -impl FromCellString for TimestampParser { +impl std::convert::From for i64 { + fn from(timestamp: DateTimestamp) -> Self { + timestamp.0 + } +} + +impl FromCellString for DateTimestamp { fn from_cell_str(s: &str) -> FlowyResult where Self: Sized, { let num = s.parse::().unwrap_or(0); - Ok(TimestampParser(num)) + Ok(DateTimestamp(num)) } } + +impl std::convert::From for DateTimestamp { + fn from(data: AnyCellData) -> Self { + let num = data.data.parse::().unwrap_or(0); + DateTimestamp(num) + } +} + #[derive(Default)] pub struct DateTypeOptionBuilder(DateTypeOption); impl_into_box_type_option_builder!(DateTypeOptionBuilder); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs index 9d27b0f468..209ed234f7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/multi_select_type_option.rs @@ -1,12 +1,10 @@ -use crate::entities::{FieldType, GridSelectOptionFilter}; +use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, -}; +use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData}; use crate::services::field::select_option::{ make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds, - SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR, + SelectOptionOperation, SELECTION_IDS_SEPARATOR, }; use crate::services::field::type_options::util::get_cell_data; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; @@ -46,16 +44,7 @@ impl SelectOptionOperation for MultiSelectTypeOption { &mut self.options } } -impl CellFilterOperation for MultiSelectTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { - if !any_cell_data.is_multi_select() { - return Ok(true); - } - let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data)); - Ok(filter.apply(&selected_options)) - } -} impl CellDataOperation for MultiSelectTypeOption { fn decode_cell_data( &self, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index 690fd10841..c8f79df20c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -1,9 +1,7 @@ use crate::impl_type_option; -use crate::entities::{FieldType, GridNumberFilter}; -use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, -}; +use crate::entities::FieldType; +use crate::services::cell::{CellData, CellDataChangeset, CellDataOperation, DecodedCellData}; use crate::services::field::number_currency::Currency; use crate::services::field::type_options::number_type_option::format::*; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; @@ -79,7 +77,7 @@ impl NumberTypeOption { Self::default() } - fn format_cell_data(&self, s: &str) -> FlowyResult { + pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult { match self.format { NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) { Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), @@ -105,18 +103,6 @@ pub(crate) fn strip_currency_symbol(s: T) -> String { } s } -impl CellFilterOperation for NumberTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult { - if !any_cell_data.is_number() { - return Ok(true); - } - - let cell_data = any_cell_data.cell_data; - let num_cell_data = self.format_cell_data(&cell_data)?; - - Ok(filter.apply(&num_cell_data)) - } -} impl CellDataOperation for NumberTypeOption { fn decode_cell_data( diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs index b1e72d24a7..6094f8567b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/single_select_type_option.rs @@ -1,8 +1,6 @@ -use crate::entities::{FieldType, GridSelectOptionFilter}; +use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, -}; +use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData}; use crate::services::field::select_option::{ make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds, SelectOptionOperation, @@ -43,16 +41,6 @@ impl SelectOptionOperation for SingleSelectTypeOption { } } -impl CellFilterOperation for SingleSelectTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult { - if !any_cell_data.is_single_select() { - return Ok(true); - } - let _ids: SelectOptionIds = any_cell_data.try_into()?; - Ok(false) - } -} - impl CellDataOperation for SingleSelectTypeOption { fn decode_cell_data( &self, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index 0a727afb56..a23366e2a4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -1,8 +1,7 @@ -use crate::entities::{FieldType, GridTextFilter}; +use crate::entities::FieldType; use crate::impl_type_option; use crate::services::cell::{ - try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, - DecodedCellData, + try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, }; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; @@ -33,17 +32,6 @@ pub struct RichTextTypeOption { } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellFilterOperation for RichTextTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { - if !any_cell_data.is_text() { - return Ok(true); - } - - let text_cell_data: TextCellData = any_cell_data.try_into()?; - Ok(filter.apply(text_cell_data)) - } -} - impl CellDataOperation for RichTextTypeOption { fn decode_cell_data( &self, @@ -88,7 +76,7 @@ impl std::convert::TryFrom for TextCellData { type Error = FlowyError; fn try_from(value: AnyCellData) -> Result { - Ok(TextCellData(value.cell_data)) + Ok(TextCellData(value.data)) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs index 490d9dfaa1..74fc9fc26b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs @@ -1,9 +1,9 @@ -use crate::entities::{FieldType, GridTextFilter}; +use crate::entities::FieldType; use crate::impl_type_option; use crate::services::cell::{ - AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, FromCellString, + AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellString, }; -use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder}; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; use fancy_regex::Regex; use flowy_derive::ProtoBuf; @@ -34,17 +34,6 @@ pub struct URLTypeOption { } impl_type_option!(URLTypeOption, FieldType::URL); -impl CellFilterOperation for URLTypeOption { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { - if !any_cell_data.is_url() { - return Ok(true); - } - - let text_cell_data: TextCellData = any_cell_data.try_into()?; - Ok(filter.apply(&text_cell_data)) - } -} - impl CellDataOperation for URLTypeOption { fn decode_cell_data( &self, @@ -125,7 +114,7 @@ impl std::convert::TryFrom for URLCellData { type Error = FlowyError; fn try_from(data: AnyCellData) -> Result { - serde_json::from_str::(&data.cell_data).map_err(internal_error) + serde_json::from_str::(&data.data).map_err(internal_error) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs index c8170e5fad..549f502bb0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs @@ -4,7 +4,7 @@ use std::str::FromStr; pub fn get_cell_data(cell_rev: &CellRevision) -> String { match AnyCellData::from_str(&cell_rev.data) { - Ok(type_option) => type_option.cell_data, + Ok(type_option) => type_option.data, Err(_) => String::new(), } } 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 index 9f381e2874..a1f6d4cbbf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs @@ -1,20 +1,19 @@ use crate::entities::{ FieldType, GridCheckboxFilter, GridDateFilter, GridNumberFilter, GridSelectOptionFilter, GridTextFilter, }; - use dashmap::DashMap; - use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; use flowy_sync::client_grid::GridRevisionPad; - 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, + inner: DashMap, } impl FilterResultCache { @@ -70,7 +69,7 @@ pub(crate) struct FilterCache { impl FilterCache { pub(crate) async fn from_grid_pad(grid_pad: &Arc>) -> Arc { let this = Arc::new(Self::default()); - let _ = reload_filter_cache(this.clone(), None, grid_pad).await; + let _ = refresh_filter_cache(this.clone(), None, grid_pad).await; this } @@ -101,7 +100,8 @@ impl FilterCache { } } -pub(crate) async fn reload_filter_cache( +/// Refresh the filter according to the field id. +pub(crate) async fn refresh_filter_cache( cache: Arc, field_ids: Option>, grid_pad: &Arc>, 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 index 67dac70b6a..6442dd4710 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs @@ -7,7 +7,7 @@ use crate::services::field::{ SingleSelectTypeOption, URLTypeOption, }; use crate::services::filter::filter_cache::{ - reload_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache, + refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache, }; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::row::GridBlockSnapshot; @@ -62,6 +62,7 @@ impl GridFilterService { 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() @@ -74,6 +75,8 @@ impl GridFilterService { 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 @@ -93,8 +96,11 @@ impl GridFilterService { visible_rows, ..Default::default() }; + + // Save the changeset for each block changesets.push(changeset); } + self.notify(changesets).await; Ok(()) } @@ -106,7 +112,7 @@ impl GridFilterService { if let Some(filter_id) = &changeset.insert_filter { let field_ids = Some(vec![filter_id.field_id.clone()]); - reload_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await; + refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await; } if let Some(filter_id) = &changeset.delete_filter { @@ -179,7 +185,7 @@ fn filter_cell( field_type, }; let any_cell_data = AnyCellData::try_from(cell_rev).ok()?; - let is_hidden = match &filter_id.field_type { + let is_visible = match &filter_id.field_type { FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| { Some( field_rev @@ -238,7 +244,7 @@ fn filter_cell( }), }?; - let is_visible = !is_hidden.unwrap_or(false); + let is_visible = !is_visible.unwrap_or(true); match filter_result.visible_by_field_id.get(&filter_id) { None => { if is_visible { diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs new file mode 100644 index 0000000000..c5cf95d34b --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs @@ -0,0 +1,52 @@ +use crate::entities::{CheckboxCondition, GridCheckboxFilter}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{CheckboxCellData, CheckboxTypeOption}; +use flowy_error::FlowyResult; + +impl GridCheckboxFilter { + 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, + } + } +} + +impl CellFilterOperation for CheckboxTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult { + if !any_cell_data.is_checkbox() { + return Ok(true); + } + let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?; + Ok(filter.is_visible(&checkbox_cell_data)) + } +} + +#[cfg(test)] +mod tests { + use crate::entities::{CheckboxCondition, GridCheckboxFilter}; + use crate::services::field::CheckboxCellData; + + #[test] + fn checkbox_filter_is_check_test() { + let checkbox_filter = GridCheckboxFilter { + condition: CheckboxCondition::IsChecked, + }; + for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] { + let data = CheckboxCellData(value.to_owned()); + assert_eq!(checkbox_filter.is_visible(&data), visible); + } + } + + #[test] + fn checkbox_filter_is_uncheck_test() { + let checkbox_filter = GridCheckboxFilter { + condition: CheckboxCondition::IsUnChecked, + }; + for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] { + let data = CheckboxCellData(value.to_owned()); + assert_eq!(checkbox_filter.is_visible(&data), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs new file mode 100644 index 0000000000..ae920c4edb --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs @@ -0,0 +1,107 @@ +use crate::entities::{DateFilterCondition, GridDateFilter}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{DateTimestamp, DateTypeOption}; +use flowy_error::FlowyResult; + +impl GridDateFilter { + pub fn is_visible>(&self, cell_timestamp: T) -> bool { + if self.start.is_none() { + return false; + } + let cell_timestamp = cell_timestamp.into(); + let start_timestamp = *self.start.as_ref().unwrap(); + // We assume that the cell_timestamp doesn't contain hours, just day. + match self.condition { + DateFilterCondition::DateIs => cell_timestamp == start_timestamp, + DateFilterCondition::DateBefore => cell_timestamp < start_timestamp, + DateFilterCondition::DateAfter => cell_timestamp > start_timestamp, + DateFilterCondition::DateOnOrBefore => cell_timestamp <= start_timestamp, + DateFilterCondition::DateOnOrAfter => cell_timestamp >= start_timestamp, + DateFilterCondition::DateWithIn => { + if let Some(end_timestamp) = self.end.as_ref() { + cell_timestamp >= start_timestamp && cell_timestamp <= *end_timestamp + } else { + false + } + } + DateFilterCondition::DateIsEmpty => cell_timestamp == 0_i64, + } + } +} + +impl CellFilterOperation for DateTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridDateFilter) -> FlowyResult { + if !any_cell_data.is_date() { + return Ok(true); + } + let timestamp: DateTimestamp = any_cell_data.into(); + Ok(filter.is_visible(timestamp)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{DateFilterCondition, GridDateFilter}; + + #[test] + fn date_filter_is_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateIs, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, true), (12, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_before_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateBefore, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, false), (122, true)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_before_or_on_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateOnOrBefore, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, true), (122, true)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_after_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateAfter, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(1234, true), (122, false), (0, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_within_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateWithIn, + start: Some(123), + end: Some(130), + }; + + for (val, visible) in vec![(123, true), (130, true), (132, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } +} 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 new file mode 100644 index 0000000000..6fe93ae58c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs @@ -0,0 +1,13 @@ +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/impls/number_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs new file mode 100644 index 0000000000..7f3a5dd212 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs @@ -0,0 +1,94 @@ +use crate::entities::{GridNumberFilter, NumberFilterCondition}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{NumberCellData, NumberTypeOption}; +use flowy_error::FlowyResult; +use rust_decimal::prelude::Zero; +use rust_decimal::Decimal; +use std::str::FromStr; + +impl GridNumberFilter { + pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { + if self.content.is_none() { + return false; + } + + 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, + } + } +} + +impl CellFilterOperation for NumberTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult { + if !any_cell_data.is_number() { + return Ok(true); + } + + let cell_data = any_cell_data.data; + let num_cell_data = self.format_cell_data(&cell_data)?; + + Ok(filter.is_visible(&num_cell_data)) + } +} + +#[cfg(test)] +mod tests { + use crate::entities::{GridNumberFilter, NumberFilterCondition}; + + use crate::services::field::{NumberCellData, NumberFormat}; + use std::str::FromStr; + #[test] + fn number_filter_equal_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::Equal, + content: Some("123".to_owned()), + }; + + for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + + let format = NumberFormat::USD; + for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } + #[test] + fn number_filter_greater_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::GreaterThan, + content: Some("12".to_owned()), + }; + for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } + + #[test] + fn number_filter_less_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::LessThan, + content: Some("100".to_owned()), + }; + for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] { + let data = NumberCellData::from_str(num_str).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs new file mode 100644 index 0000000000..cd25bfef0e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs @@ -0,0 +1,109 @@ +#![allow(clippy::needless_collect)] + +use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::select_option::{SelectOptionOperation, SelectedSelectOptions}; +use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption}; +use flowy_error::FlowyResult; + +impl GridSelectOptionFilter { + 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 { + SelectOptionCondition::OptionIs => { + if self.option_ids.len() != selected_option_ids.len() { + return true; + } + + // if selected options equal to filter's options, then the required_options will be empty. + let required_options = self + .option_ids + .iter() + .filter(|id| !selected_option_ids.contains(id)) + .collect::>(); + + // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect + !required_options.is_empty() + } + SelectOptionCondition::OptionIsNot => { + for option_id in selected_option_ids { + if self.option_ids.contains(option_id) { + return true; + } + } + false + } + SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), + SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), + } + } +} + +impl CellFilterOperation for MultiSelectTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_multi_select() { + return Ok(true); + } + + let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data)); + Ok(filter.is_visible(&selected_options)) + } +} + +impl CellFilterOperation for SingleSelectTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_single_select() { + return Ok(true); + } + let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data)); + Ok(filter.is_visible(&selected_options)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; + use crate::services::field::select_option::{SelectOption, SelectedSelectOptions}; + + #[test] + fn select_option_filter_is_test() { + let option_1 = SelectOption::new("A"); + let option_2 = SelectOption::new("B"); + let option_3 = SelectOption::new("C"); + + let filter_1 = GridSelectOptionFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone()], + }), + false + ); + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone(), option_3.clone()], + }), + true + ); + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_3.clone()], + }), + true + ); + + assert_eq!(filter_1.is_visible(&SelectedSelectOptions { options: vec![] }), true); + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone()], + }), + true, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs new file mode 100644 index 0000000000..45165e35e2 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs @@ -0,0 +1,100 @@ +use crate::entities::{GridTextFilter, TextFilterCondition}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{RichTextTypeOption, TextCellData}; +use flowy_error::FlowyResult; + +impl GridTextFilter { + 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 + } + } +} + +impl CellFilterOperation for RichTextTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_text() { + return Ok(true); + } + + let text_cell_data: TextCellData = any_cell_data.try_into()?; + Ok(filter.is_visible(text_cell_data)) + } +} +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridTextFilter, TextFilterCondition}; + + #[test] + fn text_filter_equal_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Is, + content: Some("appflowy".to_owned()), + }; + + assert!(text_filter.is_visible("AppFlowy")); + assert_eq!(text_filter.is_visible("appflowy"), true); + assert_eq!(text_filter.is_visible("Appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy.io"), false); + } + #[test] + fn text_filter_start_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::StartsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("AppFlowy.io"), true); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("https"), false); + } + + #[test] + fn text_filter_end_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::EndsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible("appflowy.io"), false); + } + #[test] + fn text_filter_empty_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::TextIsEmpty, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible(""), true); + assert_eq!(text_filter.is_visible("App"), false); + } + #[test] + fn text_filter_contain_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Contains, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("github"), false); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs new file mode 100644 index 0000000000..50f9c815fc --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs @@ -0,0 +1,15 @@ +use crate::entities::GridTextFilter; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{TextCellData, URLTypeOption}; +use flowy_error::FlowyResult; + +impl CellFilterOperation for URLTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_url() { + return Ok(true); + } + + let text_cell_data: TextCellData = any_cell_data.try_into()?; + Ok(filter.is_visible(&text_cell_data)) + } +} 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 39ae6ba462..8a0067ff96 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs @@ -1,4 +1,5 @@ mod filter_cache; mod filter_service; +mod impls; pub(crate) use filter_service::*; 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 152c9a8d2a..1d63b6dd69 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -11,6 +11,7 @@ use crate::services::row::{ make_grid_blocks, make_row_from_row_rev, make_row_rev_from_context, make_rows_from_row_revs, CreateRowRevisionBuilder, CreateRowRevisionPayload, GridBlockSnapshot, }; +use crate::services::setting::make_grid_setting; use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::revision::*; @@ -453,17 +454,21 @@ impl GridRevisionEditor { } pub async fn get_grid_setting(&self) -> FlowyResult { - // let read_guard = self.grid_pad.read().await; - // let grid_setting_rev = read_guard.get_grid_setting_rev(); - // Ok(grid_setting_rev.into()) - todo!() + let read_guard = self.grid_pad.read().await; + let grid_setting_rev = read_guard.get_grid_setting_rev(); + let field_revs = read_guard.get_field_revs(None)?; + let grid_setting = make_grid_setting(grid_setting_rev, &field_revs); + Ok(grid_setting) } pub async fn get_grid_filter(&self, layout_type: &GridLayoutType) -> FlowyResult> { let read_guard = self.grid_pad.read().await; let layout_rev = layout_type.clone().into(); match read_guard.get_filters(Some(&layout_rev), None) { - Some(filter_revs) => Ok(filter_revs.iter().map(GridFilter::from).collect::>()), + Some(filter_revs) => Ok(filter_revs + .iter() + .map(|filter_rev| filter_rev.as_ref().into()) + .collect::>()), None => Ok(vec![]), } } 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 866b0a51e6..6880db5277 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,5 +1,10 @@ -use crate::entities::GridLayoutType; +use crate::entities::{ + GridLayout, GridLayoutType, GridSetting, RepeatedGridFilter, RepeatedGridGroup, RepeatedGridSort, +}; +use flowy_grid_data_model::revision::{FieldRevision, GridSettingRevision}; use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams}; +use std::collections::HashMap; +use std::sync::Arc; pub struct GridSettingChangesetBuilder { params: GridSettingChangesetParams, @@ -34,3 +39,42 @@ impl GridSettingChangesetBuilder { self.params } } + +pub fn make_grid_setting(grid_setting_rev: &GridSettingRevision, field_revs: &[Arc]) -> GridSetting { + let current_layout_type: GridLayoutType = grid_setting_rev.layout.clone().into(); + let filters_by_field_id = grid_setting_rev + .get_all_filter(field_revs) + .map(|filters_by_field_id| { + filters_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + let groups_by_field_id = grid_setting_rev + .get_all_group() + .map(|groups_by_field_id| { + groups_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + let sorts_by_field_id = grid_setting_rev + .get_all_sort() + .map(|sorts_by_field_id| { + sorts_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + + GridSetting { + layouts: GridLayout::all(), + current_layout_type, + filters_by_field_id, + groups_by_field_id, + sorts_by_field_id, + } +} 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 new file mode 100644 index 0000000000..4c6980c527 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs @@ -0,0 +1,2 @@ +mod script; +mod text_filter_test; 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 new file mode 100644 index 0000000000..d89b6a66a1 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs @@ -0,0 +1,91 @@ +#![cfg_attr(rustfmt, rustfmt::skip)] +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use flowy_grid::entities::{CreateGridFilterPayload, GridLayoutType, GridSetting}; +use flowy_grid::services::setting::GridSettingChangesetBuilder; +use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision}; +use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams}; +use crate::grid::script::GridEditorTest; + +pub enum FilterScript { + #[allow(dead_code)] + UpdateGridSetting { + params: GridSettingChangesetParams, + }, + InsertGridTableFilter { + payload: CreateGridFilterPayload, + }, + AssertTableFilterCount { + count: i32, + }, + DeleteGridTableFilter { + filter_id: String, + field_rev: FieldRevision, + }, + #[allow(dead_code)] + AssertGridSetting { + expected_setting: GridSetting, + }, +} + +pub struct GridFilterTest { + pub editor_test: GridEditorTest, +} + +impl GridFilterTest { + pub async fn new() -> Self { + let editor_test = GridEditorTest::new().await; + Self { + editor_test + } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: FilterScript) { + + match script { + FilterScript::UpdateGridSetting { params } => { + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::InsertGridTableFilter { payload } => { + let params: CreateGridFilterParams = payload.try_into().unwrap(); + let layout_type = GridLayoutType::Table; + let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) + .insert_filter(params) + .build(); + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::AssertTableFilterCount { count } => { + let layout_type = GridLayoutType::Table; + let filters = self.editor.get_grid_filter(&layout_type).await.unwrap(); + assert_eq!(count as usize, filters.len()); + } + FilterScript::DeleteGridTableFilter { filter_id, field_rev} => { + let layout_type = GridLayoutType::Table; + let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) + .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.field_type_rev }) + .build(); + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::AssertGridSetting { expected_setting } => { + let setting = self.editor.get_grid_setting().await.unwrap(); + assert_eq!(expected_setting, setting); + } + } + } +} + +impl std::ops::Deref for GridFilterTest { + type Target = GridEditorTest; + + fn deref(&self) -> &Self::Target { + &self.editor_test + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs similarity index 54% rename from frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs rename to frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs index aad4cb1e7d..29614b21b2 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs @@ -1,33 +1,34 @@ -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; -use flowy_grid::entities::CreateGridFilterPayload; +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::*; +use flowy_grid::entities::{CreateGridFilterPayload, TextFilterCondition}; +use flowy_grid_data_model::revision::FieldRevision; #[tokio::test] async fn grid_filter_create_test() { - let test = GridEditorTest::new().await; + let mut test = GridFilterTest::new().await; let field_rev = test.text_field(); let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; - GridEditorTest::new().await.run_scripts(scripts).await; + test.run_scripts(scripts).await; } #[tokio::test] #[should_panic] async fn grid_filter_invalid_condition_panic_test() { - let test = GridEditorTest::new().await; - let field_rev = test.text_field(); + let mut test = GridFilterTest::new().await; + let field_rev = test.text_field().clone(); // 100 is not a valid condition, so this test should be panic. - let payload = CreateGridFilterPayload::new(field_rev, 100, Some("abc".to_owned())); + let payload = CreateGridFilterPayload::new(&field_rev, 100, Some("".to_owned())); let scripts = vec![InsertGridTableFilter { payload }]; - GridEditorTest::new().await.run_scripts(scripts).await; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_delete_test() { - let mut test = GridEditorTest::new().await; + let mut test = GridFilterTest::new().await; let field_rev = test.text_field().clone(); - let payload = CreateGridFilterPayload::new(&field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); + let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc"); let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; test.run_scripts(scripts).await; @@ -35,7 +36,7 @@ async fn grid_filter_delete_test() { test.run_scripts(vec![ DeleteGridTableFilter { filter_id: filter.id, - field_type: field_rev.field_type.clone(), + field_rev, }, AssertTableFilterCount { count: 0 }, ]) @@ -44,3 +45,7 @@ async fn grid_filter_delete_test() { #[tokio::test] async fn grid_filter_get_rows_test() {} + +fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> CreateGridFilterPayload { + CreateGridFilterPayload::new(field_rev, condition, Some(s.to_owned())) +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs index 38cdb25b99..4d746661eb 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs @@ -2,7 +2,7 @@ mod block_test; mod cell_test; mod field_test; mod field_util; -// mod filter_test; +mod filter_test; mod row_test; mod row_util; mod script; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index e0ec211ba3..915749fb0e 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -1,16 +1,19 @@ -#![cfg_attr(rustfmt, rustfmt::skip)] #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] use bytes::Bytes; +use flowy_grid::entities::*; +use flowy_grid::services::field::select_option::SelectOption; use flowy_grid::services::field::*; use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor}; use flowy_grid::services::row::CreateRowRevisionPayload; use flowy_grid::services::setting::GridSettingChangesetBuilder; -use flowy_grid::entities::*; use flowy_grid_data_model::revision::*; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_sync::client_grid::GridBuilder; +use flowy_sync::entities::grid::{ + CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams, +}; use flowy_test::helper::ViewTest; use flowy_test::FlowySDKTest; use std::collections::HashMap; @@ -18,8 +21,6 @@ use std::sync::Arc; use std::time::Duration; use strum::EnumCount; use tokio::time::sleep; -use flowy_grid::services::field::select_option::SelectOption; -use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams}; pub enum EditorScript { CreateField { @@ -82,7 +83,7 @@ pub enum EditorScript { }, DeleteGridTableFilter { filter_id: String, - field_type: FieldType, + field_rev: FieldRevision, }, #[allow(dead_code)] AssertGridSetting { @@ -170,10 +171,7 @@ impl GridEditorTest { assert_eq!(self.field_count, self.field_revs.len()); } EditorScript::AssertFieldCount(count) => { - assert_eq!( - self.editor.get_field_revs(None).await.unwrap().len(), - count - ); + assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count); } EditorScript::AssertFieldEqual { field_index, field_rev } => { let field_revs = self.editor.get_field_revs(None).await.unwrap(); @@ -204,14 +202,16 @@ impl GridEditorTest { } EditorScript::CreateEmptyRow => { let row_order = self.editor.create_row(None).await.unwrap(); - self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order); + self.row_order_by_row_id + .insert(row_order.row_id().to_owned(), row_order); self.row_revs = self.get_row_revs().await; self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); } EditorScript::CreateRow { payload: context } => { let row_orders = self.editor.insert_rows(vec![context]).await.unwrap(); for row_order in row_orders { - self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order); + self.row_order_by_row_id + .insert(row_order.row_id().to_owned(), row_order); } self.row_revs = self.get_row_revs().await; self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); @@ -271,10 +271,14 @@ impl GridEditorTest { let filters = self.editor.get_grid_filter(&layout_type).await.unwrap(); assert_eq!(count as usize, filters.len()); } - EditorScript::DeleteGridTableFilter { filter_id ,field_type} => { + EditorScript::DeleteGridTableFilter { filter_id, field_rev } => { let layout_type = GridLayoutType::Table; let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) - .delete_filter(DeleteFilterParams { filter_id, field_type_rev: field_type.into() }) + .delete_filter(DeleteFilterParams { + field_id: field_rev.id, + filter_id, + field_type_rev: field_rev.field_type_rev, + }) .build(); let _ = self.editor.update_grid_setting(params).await.unwrap(); } diff --git a/frontend/rust-lib/lib-dispatch/src/request/payload.rs b/frontend/rust-lib/lib-dispatch/src/request/payload.rs index c5d67e8413..59edcf79d2 100644 --- a/frontend/rust-lib/lib-dispatch/src/request/payload.rs +++ b/frontend/rust-lib/lib-dispatch/src/request/payload.rs @@ -5,7 +5,7 @@ pub enum PayloadError {} // TODO: support stream data #[derive(Clone)] -#[cfg_attr(feature = "user_serde", derive(serde::Serialize))] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub enum Payload { None, Bytes(Bytes), diff --git a/frontend/rust-lib/lib-dispatch/src/response/response.rs b/frontend/rust-lib/lib-dispatch/src/response/response.rs index cfad0e6f85..a745f8ee1a 100644 --- a/frontend/rust-lib/lib-dispatch/src/response/response.rs +++ b/frontend/rust-lib/lib-dispatch/src/response/response.rs @@ -9,7 +9,7 @@ use derivative::*; use std::{convert::TryFrom, fmt, fmt::Formatter}; #[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "user_serde", derive(serde::Serialize))] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub enum StatusCode { Ok = 0, Err = 1, @@ -18,7 +18,7 @@ pub enum StatusCode { // serde user guide: https://serde.rs/field-attrs.html #[derive(Debug, Clone, Derivative)] -#[cfg_attr(feature = "user_serde", derive(serde::Serialize))] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub struct EventResponse { #[derivative(Debug = "ignore")] pub payload: Payload, diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs index 1350f6acf5..edceb25d9c 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs @@ -31,6 +31,11 @@ pub struct GridRevision { pub fields: Vec>, pub blocks: Vec>, + #[cfg(feature = "filter")] + #[serde(default)] + pub setting: GridSettingRevision, + + #[cfg(not(feature = "filter"))] #[serde(default, skip)] pub setting: GridSettingRevision, } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs index 3ffcedb1c4..d29d9d767b 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs @@ -1,8 +1,9 @@ -use crate::revision::FieldTypeRevision; +use crate::revision::{FieldRevision, FieldTypeRevision}; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; use serde_repr::*; +use std::collections::HashMap; use std::sync::Arc; pub fn gen_grid_filter_id() -> String { @@ -17,20 +18,33 @@ pub fn gen_grid_sort_id() -> String { nanoid!(6) } +/// Each layout contains multiple key/value. +/// Key: field_id +/// Value: this value also contains key/value. +/// Key: FieldType, +/// Value: the corresponding filter. +/// +/// This overall struct is described below: +/// GridSettingRevision +/// layout: +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// layout: +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// +/// Group and sorts will be the same structure as filters. #[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] pub struct GridSettingRevision { pub layout: GridLayoutRevision, - // layout: - // field_id: - // FieldType: GridFilterRevision - // FieldType: GridFilterRevision - // layout: - // field_id: - // FieldType: GridFilterRevision - // field_id: - // FieldType: GridFilterRevision + #[serde(with = "indexmap::serde_seq")] - pub filters: IndexMap>, + filters: IndexMap>, #[serde(skip, with = "indexmap::serde_seq")] pub groups: IndexMap>, @@ -39,7 +53,44 @@ pub struct GridSettingRevision { pub sorts: IndexMap>, } +pub type FiltersByFieldId = HashMap>>; +pub type GroupsByFieldId = HashMap>>; +pub type SortsByFieldId = HashMap>>; impl GridSettingRevision { + pub fn get_all_group(&self) -> Option { + None + } + + pub fn get_all_sort(&self) -> Option { + None + } + + /// Return the Filters of the current layout + pub fn get_all_filter(&self, field_revs: &[Arc]) -> Option { + let layout = &self.layout; + // Acquire the read lock of the filters. + let filter_rev_map_by_field_id = self.filters.get(layout)?; + // Get the filters according to the FieldType, so we need iterate the field_revs. + let filters_by_field_id = field_revs + .iter() + .flat_map(|field_rev| { + let field_type = &field_rev.field_type_rev; + let field_id = &field_rev.id; + + let filter_rev_map: &GridFilterRevisionMap = filter_rev_map_by_field_id.get(field_id)?; + let filters: Vec> = filter_rev_map.get(field_type)?.clone(); + Some((field_rev.id.clone(), filters)) + }) + .collect::(); + Some(filters_by_field_id) + } + + #[allow(dead_code)] + fn get_filter_rev_map(&self, layout: &GridLayoutRevision, field_id: &str) -> Option<&GridFilterRevisionMap> { + let filter_rev_map_by_field_id = self.filters.get(layout)?; + filter_rev_map_by_field_id.get(field_id) + } + pub fn get_mut_filters( &mut self, layout: &GridLayoutRevision, @@ -56,12 +107,12 @@ impl GridSettingRevision { &self, layout: &GridLayoutRevision, field_id: &str, - field_type: &FieldTypeRevision, + field_type_rev: &FieldTypeRevision, ) -> Option>> { self.filters .get(layout) .and_then(|filter_rev_map_by_field_id| filter_rev_map_by_field_id.get(field_id)) - .and_then(|filter_rev_map| filter_rev_map.get(field_type)) + .and_then(|filter_rev_map| filter_rev_map.get(field_type_rev)) .cloned() } 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 8b724d8133..e47305f7fa 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 @@ -17,8 +17,8 @@ pub type GridRevisionDelta = PlainTextDelta; pub type GridRevisionDeltaBuilder = PlainTextDeltaBuilder; pub struct GridRevisionPad { - pub(crate) grid_rev: Arc, - pub(crate) delta: GridRevisionDelta, + grid_rev: Arc, + delta: GridRevisionDelta, } pub trait JsonDeserializer { @@ -358,10 +358,9 @@ impl GridRevisionPad { if is_contain { // Only return the filters for the current fields' type. - if let Some(mut t_filter_revs) = - self.grid_rev - .setting - .get_filters(layout_ty, &field_rev.id, &field_rev.field_type_rev) + let field_id = &field_rev.id; + let field_type_rev = &field_rev.field_type_rev; + if let Some(mut t_filter_revs) = self.grid_rev.setting.get_filters(layout_ty, field_id, field_type_rev) { filter_revs.append(&mut t_filter_revs); } @@ -396,7 +395,7 @@ impl GridRevisionPad { if let Some(params) = changeset.delete_filter { match grid_rev .setting - .get_mut_filters(&layout_rev, ¶ms.filter_id, ¶ms.field_type_rev) + .get_mut_filters(&layout_rev, ¶ms.field_id, ¶ms.field_type_rev) { Some(filters) => { filters.retain(|filter| filter.id != params.filter_id); diff --git a/shared-lib/flowy-sync/src/entities/grid.rs b/shared-lib/flowy-sync/src/entities/grid.rs index 866562ccd6..fc3c14b4fb 100644 --- a/shared-lib/flowy-sync/src/entities/grid.rs +++ b/shared-lib/flowy-sync/src/entities/grid.rs @@ -24,6 +24,7 @@ pub struct CreateGridFilterParams { } pub struct DeleteFilterParams { + pub field_id: String, pub filter_id: String, pub field_type_rev: FieldTypeRevision, } diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index 3641b4729f..5f293e5e2c 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -114,12 +114,12 @@ fn generate_dart_protobuf_files( check_pb_dart_plugin(); let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); paths.iter().for_each(|path| { - if cmd_lib::run_cmd! { + let result = cmd_lib::run_cmd! { ${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path} - } - .is_err() - { - panic!("Generate dart pb file failed with: {}", path) + }; + + if result.is_err() { + panic!("Generate dart pb file failed with: {}, {:?}", path, result) }; });