浏览代码

chore: add text filter & number filter tests

appflowy 2 年之前
父节点
当前提交
da0a7f01b3

+ 0 - 393
frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs

@@ -1,393 +0,0 @@
-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::{FieldRevision, GridFilterRevision};
-use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
-use std::convert::TryInto;
-use std::sync::Arc;
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridFilter {
-    #[pb(index = 1)]
-    pub id: String,
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct RepeatedGridFilter {
-    #[pb(index = 1)]
-    pub items: Vec<GridFilter>,
-}
-
-impl std::convert::From<&Arc<GridFilterRevision>> for GridFilter {
-    fn from(rev: &Arc<GridFilterRevision>) -> Self {
-        Self { id: rev.id.clone() }
-    }
-}
-
-impl std::convert::From<&Vec<Arc<GridFilterRevision>>> for RepeatedGridFilter {
-    fn from(revs: &Vec<Arc<GridFilterRevision>>) -> Self {
-        RepeatedGridFilter {
-            items: revs.iter().map(|rev| rev.into()).collect(),
-        }
-    }
-}
-
-impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
-    fn from(items: Vec<GridFilter>) -> Self {
-        Self { items }
-    }
-}
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct DeleteFilterPayload {
-    #[pb(index = 1)]
-    pub filter_id: String,
-
-    #[pb(index = 2)]
-    pub field_type: FieldType,
-}
-
-impl TryInto<DeleteFilterParams> for DeleteFilterPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
-        let filter_id = NotEmptyStr::parse(self.filter_id)
-            .map_err(|_| ErrorCode::UnexpectedEmptyString)?
-            .0;
-        Ok(DeleteFilterParams {
-            filter_id,
-            field_type_rev: self.field_type.into(),
-        })
-    }
-}
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct CreateGridFilterPayload {
-    #[pb(index = 1)]
-    pub field_id: String,
-
-    #[pb(index = 2)]
-    pub field_type: FieldType,
-
-    #[pb(index = 3)]
-    pub condition: i32,
-
-    #[pb(index = 4, one_of)]
-    pub content: Option<String>,
-}
-
-impl CreateGridFilterPayload {
-    #[allow(dead_code)]
-    pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
-        Self {
-            field_id: field_rev.id.clone(),
-            field_type: field_rev.field_type_rev.into(),
-            condition: condition.into(),
-            content,
-        }
-    }
-}
-
-impl TryInto<CreateGridFilterParams> for CreateGridFilterPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
-        let field_id = NotEmptyStr::parse(self.field_id)
-            .map_err(|_| ErrorCode::FieldIdIsEmpty)?
-            .0;
-        let condition = self.condition as u8;
-        match self.field_type {
-            FieldType::RichText | FieldType::URL => {
-                let _ = TextFilterCondition::try_from(condition)?;
-            }
-            FieldType::Checkbox => {
-                let _ = CheckboxCondition::try_from(condition)?;
-            }
-            FieldType::Number => {
-                let _ = NumberFilterCondition::try_from(condition)?;
-            }
-            FieldType::DateTime => {
-                let _ = DateFilterCondition::try_from(condition)?;
-            }
-            FieldType::SingleSelect | FieldType::MultiSelect => {
-                let _ = SelectOptionCondition::try_from(condition)?;
-            }
-        }
-
-        Ok(CreateGridFilterParams {
-            field_id,
-            field_type_rev: self.field_type.into(),
-            condition,
-            content: self.content,
-        })
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridTextFilter {
-    #[pb(index = 1)]
-    pub condition: TextFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum TextFilterCondition {
-    Is = 0,
-    IsNot = 1,
-    Contains = 2,
-    DoesNotContain = 3,
-    StartsWith = 4,
-    EndsWith = 5,
-    TextIsEmpty = 6,
-    TextIsNotEmpty = 7,
-}
-impl std::convert::From<TextFilterCondition> for i32 {
-    fn from(value: TextFilterCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for TextFilterCondition {
-    fn default() -> Self {
-        TextFilterCondition::Is
-    }
-}
-impl std::convert::TryFrom<u8> for TextFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(TextFilterCondition::Is),
-            1 => Ok(TextFilterCondition::IsNot),
-            2 => Ok(TextFilterCondition::Contains),
-            3 => Ok(TextFilterCondition::DoesNotContain),
-            4 => Ok(TextFilterCondition::StartsWith),
-            5 => Ok(TextFilterCondition::EndsWith),
-            6 => Ok(TextFilterCondition::TextIsEmpty),
-            7 => Ok(TextFilterCondition::TextIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridTextFilter {
-            condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridNumberFilter {
-    #[pb(index = 1)]
-    pub condition: NumberFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum NumberFilterCondition {
-    Equal = 0,
-    NotEqual = 1,
-    GreaterThan = 2,
-    LessThan = 3,
-    GreaterThanOrEqualTo = 4,
-    LessThanOrEqualTo = 5,
-    NumberIsEmpty = 6,
-    NumberIsNotEmpty = 7,
-}
-impl std::default::Default for NumberFilterCondition {
-    fn default() -> Self {
-        NumberFilterCondition::Equal
-    }
-}
-
-impl std::convert::From<NumberFilterCondition> for i32 {
-    fn from(value: NumberFilterCondition) -> Self {
-        value as i32
-    }
-}
-impl std::convert::TryFrom<u8> for NumberFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(n: u8) -> Result<Self, Self::Error> {
-        match n {
-            0 => Ok(NumberFilterCondition::Equal),
-            1 => Ok(NumberFilterCondition::NotEqual),
-            2 => Ok(NumberFilterCondition::GreaterThan),
-            3 => Ok(NumberFilterCondition::LessThan),
-            4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo),
-            5 => Ok(NumberFilterCondition::LessThanOrEqualTo),
-            6 => Ok(NumberFilterCondition::NumberIsEmpty),
-            7 => Ok(NumberFilterCondition::NumberIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridNumberFilter {
-            condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridSelectOptionFilter {
-    #[pb(index = 1)]
-    pub condition: SelectOptionCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum SelectOptionCondition {
-    OptionIs = 0,
-    OptionIsNot = 1,
-    OptionIsEmpty = 2,
-    OptionIsNotEmpty = 3,
-}
-
-impl std::convert::From<SelectOptionCondition> for i32 {
-    fn from(value: SelectOptionCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for SelectOptionCondition {
-    fn default() -> Self {
-        SelectOptionCondition::OptionIs
-    }
-}
-
-impl std::convert::TryFrom<u8> for SelectOptionCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(SelectOptionCondition::OptionIs),
-            1 => Ok(SelectOptionCondition::OptionIsNot),
-            2 => Ok(SelectOptionCondition::OptionIsEmpty),
-            3 => Ok(SelectOptionCondition::OptionIsNotEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridSelectOptionFilter {
-            condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridDateFilter {
-    #[pb(index = 1)]
-    pub condition: DateFilterCondition,
-
-    #[pb(index = 2, one_of)]
-    pub content: Option<String>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum DateFilterCondition {
-    DateIs = 0,
-    DateBefore = 1,
-    DateAfter = 2,
-    DateOnOrBefore = 3,
-    DateOnOrAfter = 4,
-    DateWithIn = 5,
-    DateIsEmpty = 6,
-}
-
-impl std::default::Default for DateFilterCondition {
-    fn default() -> Self {
-        DateFilterCondition::DateIs
-    }
-}
-
-impl std::convert::TryFrom<u8> for DateFilterCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(DateFilterCondition::DateIs),
-            1 => Ok(DateFilterCondition::DateBefore),
-            2 => Ok(DateFilterCondition::DateAfter),
-            3 => Ok(DateFilterCondition::DateOnOrBefore),
-            4 => Ok(DateFilterCondition::DateOnOrAfter),
-            5 => Ok(DateFilterCondition::DateWithIn),
-            6 => Ok(DateFilterCondition::DateIsEmpty),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-impl std::convert::From<Arc<GridFilterRevision>> for GridDateFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridDateFilter {
-            condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs),
-            content: rev.content.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridCheckboxFilter {
-    #[pb(index = 1)]
-    pub condition: CheckboxCondition,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
-#[repr(u8)]
-pub enum CheckboxCondition {
-    IsChecked = 0,
-    IsUnChecked = 1,
-}
-
-impl std::convert::From<CheckboxCondition> for i32 {
-    fn from(value: CheckboxCondition) -> Self {
-        value as i32
-    }
-}
-
-impl std::default::Default for CheckboxCondition {
-    fn default() -> Self {
-        CheckboxCondition::IsChecked
-    }
-}
-
-impl std::convert::TryFrom<u8> for CheckboxCondition {
-    type Error = ErrorCode;
-
-    fn try_from(value: u8) -> Result<Self, Self::Error> {
-        match value {
-            0 => Ok(CheckboxCondition::IsChecked),
-            1 => Ok(CheckboxCondition::IsUnChecked),
-            _ => Err(ErrorCode::InvalidData),
-        }
-    }
-}
-
-impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
-    fn from(rev: Arc<GridFilterRevision>) -> Self {
-        GridCheckboxFilter {
-            condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked),
-        }
-    }
-}

+ 49 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs

@@ -0,0 +1,49 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridCheckboxFilter {
+    #[pb(index = 1)]
+    pub condition: CheckboxCondition,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum CheckboxCondition {
+    IsChecked = 0,
+    IsUnChecked = 1,
+}
+
+impl std::convert::From<CheckboxCondition> for i32 {
+    fn from(value: CheckboxCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for CheckboxCondition {
+    fn default() -> Self {
+        CheckboxCondition::IsChecked
+    }
+}
+
+impl std::convert::TryFrom<u8> for CheckboxCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(CheckboxCondition::IsChecked),
+            1 => Ok(CheckboxCondition::IsUnChecked),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridCheckboxFilter {
+            condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked),
+        }
+    }
+}

+ 56 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs

@@ -0,0 +1,56 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridDateFilter {
+    #[pb(index = 1)]
+    pub condition: DateFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum DateFilterCondition {
+    DateIs = 0,
+    DateBefore = 1,
+    DateAfter = 2,
+    DateOnOrBefore = 3,
+    DateOnOrAfter = 4,
+    DateWithIn = 5,
+    DateIsEmpty = 6,
+}
+
+impl std::default::Default for DateFilterCondition {
+    fn default() -> Self {
+        DateFilterCondition::DateIs
+    }
+}
+
+impl std::convert::TryFrom<u8> for DateFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(DateFilterCondition::DateIs),
+            1 => Ok(DateFilterCondition::DateBefore),
+            2 => Ok(DateFilterCondition::DateAfter),
+            3 => Ok(DateFilterCondition::DateOnOrBefore),
+            4 => Ok(DateFilterCondition::DateOnOrAfter),
+            5 => Ok(DateFilterCondition::DateWithIn),
+            6 => Ok(DateFilterCondition::DateIsEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+impl std::convert::From<Arc<GridFilterRevision>> for GridDateFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridDateFilter {
+            condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs),
+            content: rev.content.clone(),
+        }
+    }
+}

+ 13 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs

@@ -0,0 +1,13 @@
+mod checkbox_filter;
+mod date_filter;
+mod number_filter;
+mod select_option_filter;
+mod text_filter;
+mod util;
+
+pub use checkbox_filter::*;
+pub use date_filter::*;
+pub use number_filter::*;
+pub use select_option_filter::*;
+pub use text_filter::*;
+pub use util::*;

+ 142 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs

@@ -0,0 +1,142 @@
+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, Error};
+use std::str::FromStr;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridNumberFilter {
+    #[pb(index = 1)]
+    pub condition: NumberFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+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 {
+    Equal = 0,
+    NotEqual = 1,
+    GreaterThan = 2,
+    LessThan = 3,
+    GreaterThanOrEqualTo = 4,
+    LessThanOrEqualTo = 5,
+    NumberIsEmpty = 6,
+    NumberIsNotEmpty = 7,
+}
+
+impl std::default::Default for NumberFilterCondition {
+    fn default() -> Self {
+        NumberFilterCondition::Equal
+    }
+}
+
+impl std::convert::From<NumberFilterCondition> for i32 {
+    fn from(value: NumberFilterCondition) -> Self {
+        value as i32
+    }
+}
+impl std::convert::TryFrom<u8> for NumberFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(n: u8) -> Result<Self, Self::Error> {
+        match n {
+            0 => Ok(NumberFilterCondition::Equal),
+            1 => Ok(NumberFilterCondition::NotEqual),
+            2 => Ok(NumberFilterCondition::GreaterThan),
+            3 => Ok(NumberFilterCondition::LessThan),
+            4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo),
+            5 => Ok(NumberFilterCondition::LessThanOrEqualTo),
+            6 => Ok(NumberFilterCondition::NumberIsEmpty),
+            7 => Ok(NumberFilterCondition::NumberIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridNumberFilter {
+            condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
+            content: rev.content.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::entities::{GridNumberFilter, NumberFilterCondition};
+    use crate::services::field::number_currency::Currency;
+    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 vec![("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 vec![("$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 vec![("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 vec![("12", true), ("1234", false), ("30", true), ("", true)] {
+            let data = NumberCellData::from_str(num_str).unwrap();
+            assert_eq!(number_filter.apply(&data), r);
+        }
+    }
+}

+ 57 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs

@@ -0,0 +1,57 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridSelectOptionFilter {
+    #[pb(index = 1)]
+    pub condition: SelectOptionCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum SelectOptionCondition {
+    OptionIs = 0,
+    OptionIsNot = 1,
+    OptionIsEmpty = 2,
+    OptionIsNotEmpty = 3,
+}
+
+impl std::convert::From<SelectOptionCondition> for i32 {
+    fn from(value: SelectOptionCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for SelectOptionCondition {
+    fn default() -> Self {
+        SelectOptionCondition::OptionIs
+    }
+}
+
+impl std::convert::TryFrom<u8> for SelectOptionCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(SelectOptionCondition::OptionIs),
+            1 => Ok(SelectOptionCondition::OptionIsNot),
+            2 => Ok(SelectOptionCondition::OptionIsEmpty),
+            3 => Ok(SelectOptionCondition::OptionIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridSelectOptionFilter {
+            condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
+            content: rev.content.clone(),
+        }
+    }
+}

+ 148 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs

@@ -0,0 +1,148 @@
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::revision::GridFilterRevision;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridTextFilter {
+    #[pb(index = 1)]
+    pub condition: TextFilterCondition,
+
+    #[pb(index = 2, one_of)]
+    pub content: Option<String>,
+}
+
+impl GridTextFilter {
+    pub fn apply(&self, s: &str) -> bool {
+        let s = s.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 {
+            return false;
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum TextFilterCondition {
+    Is = 0,
+    IsNot = 1,
+    Contains = 2,
+    DoesNotContain = 3,
+    StartsWith = 4,
+    EndsWith = 5,
+    TextIsEmpty = 6,
+    TextIsNotEmpty = 7,
+}
+
+impl std::convert::From<TextFilterCondition> for i32 {
+    fn from(value: TextFilterCondition) -> Self {
+        value as i32
+    }
+}
+
+impl std::default::Default for TextFilterCondition {
+    fn default() -> Self {
+        TextFilterCondition::Is
+    }
+}
+impl std::convert::TryFrom<u8> for TextFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(TextFilterCondition::Is),
+            1 => Ok(TextFilterCondition::IsNot),
+            2 => Ok(TextFilterCondition::Contains),
+            3 => Ok(TextFilterCondition::DoesNotContain),
+            4 => Ok(TextFilterCondition::StartsWith),
+            5 => Ok(TextFilterCondition::EndsWith),
+            6 => Ok(TextFilterCondition::TextIsEmpty),
+            7 => Ok(TextFilterCondition::TextIsNotEmpty),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
+    fn from(rev: Arc<GridFilterRevision>) -> Self {
+        GridTextFilter {
+            condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
+            content: rev.content.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::entities::{GridTextFilter, TextFilterCondition};
+
+    #[test]
+    fn text_filter_equal_test() {
+        let text_filter = GridTextFilter {
+            condition: TextFilterCondition::Is,
+            content: Some("appflowy".to_owned()),
+        };
+
+        assert_eq!(text_filter.apply("AppFlowy"), true);
+        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);
+    }
+}

+ 128 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs

@@ -0,0 +1,128 @@
+use crate::entities::{
+    CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
+    TextFilterCondition,
+};
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::parser::NotEmptyStr;
+use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision};
+use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
+use std::convert::TryInto;
+use std::sync::Arc;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GridFilter {
+    #[pb(index = 1)]
+    pub id: String,
+}
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct RepeatedGridFilter {
+    #[pb(index = 1)]
+    pub items: Vec<GridFilter>,
+}
+
+impl std::convert::From<&Arc<GridFilterRevision>> for GridFilter {
+    fn from(rev: &Arc<GridFilterRevision>) -> Self {
+        Self { id: rev.id.clone() }
+    }
+}
+
+impl std::convert::From<&Vec<Arc<GridFilterRevision>>> for RepeatedGridFilter {
+    fn from(revs: &Vec<Arc<GridFilterRevision>>) -> Self {
+        RepeatedGridFilter {
+            items: revs.iter().map(|rev| rev.into()).collect(),
+        }
+    }
+}
+
+impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
+    fn from(items: Vec<GridFilter>) -> Self {
+        Self { items }
+    }
+}
+
+#[derive(ProtoBuf, Debug, Default, Clone)]
+pub struct DeleteFilterPayload {
+    #[pb(index = 1)]
+    pub filter_id: String,
+
+    #[pb(index = 2)]
+    pub field_type: FieldType,
+}
+
+impl TryInto<DeleteFilterParams> for DeleteFilterPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
+        let filter_id = NotEmptyStr::parse(self.filter_id)
+            .map_err(|_| ErrorCode::UnexpectedEmptyString)?
+            .0;
+        Ok(DeleteFilterParams {
+            filter_id,
+            field_type_rev: self.field_type.into(),
+        })
+    }
+}
+
+#[derive(ProtoBuf, Debug, Default, Clone)]
+pub struct CreateGridFilterPayload {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2)]
+    pub field_type: FieldType,
+
+    #[pb(index = 3)]
+    pub condition: i32,
+
+    #[pb(index = 4, one_of)]
+    pub content: Option<String>,
+}
+
+impl CreateGridFilterPayload {
+    #[allow(dead_code)]
+    pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
+        Self {
+            field_id: field_rev.id.clone(),
+            field_type: field_rev.field_type_rev.into(),
+            condition: condition.into(),
+            content,
+        }
+    }
+}
+
+impl TryInto<CreateGridFilterParams> for CreateGridFilterPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
+        let field_id = NotEmptyStr::parse(self.field_id)
+            .map_err(|_| ErrorCode::FieldIdIsEmpty)?
+            .0;
+        let condition = self.condition as u8;
+        match self.field_type {
+            FieldType::RichText | FieldType::URL => {
+                let _ = TextFilterCondition::try_from(condition)?;
+            }
+            FieldType::Checkbox => {
+                let _ = CheckboxCondition::try_from(condition)?;
+            }
+            FieldType::Number => {
+                let _ = NumberFilterCondition::try_from(condition)?;
+            }
+            FieldType::DateTime => {
+                let _ = DateFilterCondition::try_from(condition)?;
+            }
+            FieldType::SingleSelect | FieldType::MultiSelect => {
+                let _ = SelectOptionCondition::try_from(condition)?;
+            }
+        }
+
+        Ok(CreateGridFilterParams {
+            field_id,
+            field_type_rev: self.field_type.into(),
+            condition,
+            content: self.content,
+        })
+    }
+}

+ 9 - 6
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -42,9 +42,10 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 const YES: &str = "Yes";
 const NO: &str = "No";
 
-impl CellFilterOperation<GridCheckboxFilter, CheckboxCellData> for CheckboxTypeOption {
-    fn apply_filter(&self, _cell_data: CheckboxCellData, _filter: &GridCheckboxFilter) -> bool {
-        false
+impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult<bool> {
+        let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?;
+        Ok(false)
     }
 }
 
@@ -97,9 +98,11 @@ fn string_to_bool(bool_str: &str) -> bool {
 }
 
 pub struct CheckboxCellData(String);
-impl std::convert::From<AnyCellData> for CheckboxCellData {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        CheckboxCellData(any_cell_data.cell_data)
+impl std::convert::TryFrom<AnyCellData> for CheckboxCellData {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        todo!()
     }
 }
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -117,9 +117,9 @@ impl DateTypeOption {
     }
 }
 
-impl CellFilterOperation<GridDateFilter, AnyCellData> for DateTypeOption {
-    fn apply_filter(&self, _cell_data: AnyCellData, _filter: &GridDateFilter) -> bool {
-        false
+impl CellFilterOperation<GridDateFilter> for DateTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridDateFilter) -> FlowyResult<bool> {
+        Ok(false)
     }
 }
 

+ 108 - 105
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -1,6 +1,7 @@
 use crate::impl_type_option;
 
 use crate::entities::{FieldType, GridNumberFilter};
+use crate::services::field::number_currency::Currency;
 use crate::services::field::type_options::number_type_option::format::*;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
 use crate::services::row::{
@@ -10,7 +11,9 @@ use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
-use rust_decimal::Decimal;
+use rust_decimal::prelude::Zero;
+use rust_decimal::{Decimal, Error};
+use rusty_money::Money;
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
@@ -76,24 +79,13 @@ impl NumberTypeOption {
         Self::default()
     }
 
-    fn cell_content_from_number_str(&self, s: &str) -> FlowyResult<String> {
+    fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
         match self.format {
-            NumberFormat::Num => {
-                if let Ok(v) = s.parse::<f64>() {
-                    return Ok(v.to_string());
-                }
-
-                if let Ok(v) = s.parse::<i64>() {
-                    return Ok(v.to_string());
-                }
-
-                Ok("".to_string())
-            }
-            NumberFormat::Percent => {
-                let content = s.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Ok(content)
-            }
-            _ => self.money_from_number_str(s),
+            NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) {
+                Ok(value, ..) => Ok(NumberCellData::from_decimal(value)),
+                Err(_) => Ok(NumberCellData::new()),
+            },
+            _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format),
         }
     }
 
@@ -101,49 +93,24 @@ impl NumberTypeOption {
         self.format = format;
         self.symbol = format.symbol();
     }
+}
 
-    fn money_from_number_str(&self, s: &str) -> FlowyResult<String> {
-        let mut number = self.strip_currency_symbol(s);
-
-        if s.is_empty() {
-            return Ok("".to_string());
-        }
-
-        match Decimal::from_str(&number) {
-            Ok(mut decimal) => {
-                decimal.set_sign_positive(self.sign_positive);
-                let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string();
-                Ok(money)
-            }
-            Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) {
-                Ok(money) => Ok(money.to_string()),
-                Err(_) => {
-                    number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
-                    if number.chars().all(char::is_numeric) {
-                        self.money_from_number_str(&number)
-                    } else {
-                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
-                    }
-                }
-            },
-        }
-    }
-
-    fn strip_currency_symbol<T: ToString>(&self, s: T) -> String {
-        let mut s = s.to_string();
-        for symbol in CURRENCY_SYMBOL.iter() {
-            if s.starts_with(symbol) {
-                s = s.strip_prefix(symbol).unwrap_or("").to_string();
-                break;
-            }
+pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
+    let mut s = s.to_string();
+    for symbol in CURRENCY_SYMBOL.iter() {
+        if s.starts_with(symbol) {
+            s = s.strip_prefix(symbol).unwrap_or("").to_string();
+            break;
         }
-        s
     }
+    s
 }
-impl CellFilterOperation<GridNumberFilter, AnyCellData> for NumberTypeOption {
-    fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridNumberFilter) -> bool {
-        let _number_cell_data = NumberCellData::from_number_type_option(self, any_cell_data);
-        false
+impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult<bool> {
+        let cell_data = any_cell_data.cell_data;
+        let num_cell_data = self.format_cell_data(&cell_data)?;
+
+        Ok(filter.apply(&num_cell_data))
     }
 }
 
@@ -162,28 +129,9 @@ impl CellDataOperation<String> for NumberTypeOption {
         }
 
         let cell_data = cell_data.into();
-        match self.format {
-            NumberFormat::Num => {
-                if let Ok(v) = cell_data.parse::<f64>() {
-                    return Ok(DecodedCellData::new(v.to_string()));
-                }
-
-                if let Ok(v) = cell_data.parse::<i64>() {
-                    return Ok(DecodedCellData::new(v.to_string()));
-                }
-
-                Ok(DecodedCellData::default())
-            }
-            NumberFormat::Percent => {
-                let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Ok(DecodedCellData::new(content))
-            }
-            _ => {
-                let content = self
-                    .money_from_number_str(&cell_data)
-                    .unwrap_or_else(|_| "".to_string());
-                Ok(DecodedCellData::new(content))
-            }
+        match self.format_cell_data(&cell_data) {
+            Ok(num) => Ok(DecodedCellData::new(num.to_string())),
+            Err(_) => Ok(DecodedCellData::default()),
         }
     }
 
@@ -193,7 +141,7 @@ impl CellDataOperation<String> for NumberTypeOption {
     {
         let changeset = changeset.into();
         let data = changeset.trim().to_string();
-        let _ = self.cell_content_from_number_str(&data)?;
+        let _ = self.format_cell_data(&data)?;
         Ok(data)
     }
 }
@@ -213,33 +161,88 @@ impl std::default::Default for NumberTypeOption {
 }
 
 #[derive(Default)]
-pub struct NumberCellData(String);
+pub struct NumberCellData {
+    decimal: Option<Decimal>,
+    money: Option<String>,
+}
 
 impl NumberCellData {
-    fn from_number_type_option(type_option: &NumberTypeOption, any_cell_data: AnyCellData) -> Self {
-        let cell_data = any_cell_data.cell_data;
-        match type_option.format {
-            NumberFormat::Num => {
-                if let Ok(v) = cell_data.parse::<f64>() {
-                    return Self(v.to_string());
-                }
+    pub fn new() -> Self {
+        Self {
+            decimal: Default::default(),
+            money: None,
+        }
+    }
 
-                if let Ok(v) = cell_data.parse::<i64>() {
-                    return Self(v.to_string());
+    pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult<Self> {
+        let mut num_str = strip_currency_symbol(s);
+        let currency = format.currency();
+        if num_str.is_empty() {
+            return Ok(Self::default());
+        }
+        match Decimal::from_str(&num_str) {
+            Ok(mut decimal) => {
+                decimal.set_sign_positive(sign_positive);
+                let money = Money::from_decimal(decimal, currency);
+                Ok(Self::from_money(money))
+            }
+            Err(_) => match Money::from_str(&num_str, currency) {
+                Ok(money) => Ok(NumberCellData::from_money(money)),
+                Err(_) => {
+                    num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
+                    if num_str.chars().all(char::is_numeric) {
+                        Self::from_format_str(&num_str, sign_positive, format)
+                    } else {
+                        Err(FlowyError::invalid_data().context("Should only contain numbers"))
+                    }
                 }
+            },
+        }
+    }
 
-                Self::default()
-            }
-            NumberFormat::Percent => {
-                let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                Self(content)
-            }
-            _ => {
-                let content = type_option
-                    .money_from_number_str(&cell_data)
-                    .unwrap_or_else(|_| "".to_string());
-                Self(content)
-            }
+    pub fn from_decimal(decimal: Decimal) -> Self {
+        Self {
+            decimal: Some(decimal),
+            money: None,
+        }
+    }
+
+    pub fn from_money(money: Money<Currency>) -> Self {
+        Self {
+            decimal: Some(money.amount().clone()),
+            money: Some(money.to_string()),
+        }
+    }
+
+    pub fn decimal(&self) -> &Option<Decimal> {
+        &self.decimal
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.decimal.is_none()
+    }
+}
+
+impl FromStr for NumberCellData {
+    type Err = rust_decimal::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if s.is_empty() {
+            return Ok(Self::default());
+        }
+        let decimal = Decimal::from_str(s)?;
+        Ok(Self::from_decimal(decimal))
+    }
+}
+
+impl ToString for NumberCellData {
+    fn to_string(&self) -> String {
+        match &self.money {
+            None => match self.decimal {
+                None => String::default(),
+                Some(decimal) => decimal.to_string(),
+            },
+            Some(money) => money.to_string(),
         }
     }
 }
@@ -248,7 +251,7 @@ impl NumberCellData {
 mod tests {
     use crate::entities::FieldType;
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{NumberFormat, NumberTypeOption};
+    use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption};
     use crate::services::row::CellDataOperation;
     use flowy_grid_data_model::revision::FieldRevision;
     use strum::IntoEnumIterator;
@@ -266,10 +269,10 @@ mod tests {
     fn number_type_option_strip_symbol_test() {
         let mut type_option = NumberTypeOption::new();
         type_option.format = NumberFormat::USD;
-        assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned());
+        assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned());
 
         type_option.format = NumberFormat::Yuan;
-        assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned());
+        assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned());
     }
 
     #[test]

+ 14 - 10
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -97,9 +97,10 @@ impl SelectOptionOperation for SingleSelectTypeOption {
     }
 }
 
-impl CellFilterOperation<GridSelectOptionFilter, SelectOptionIds> for SingleSelectTypeOption {
-    fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool {
-        false
+impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
+        let ids: SelectOptionIds = any_cell_data.try_into()?;
+        Ok(false)
     }
 }
 
@@ -200,9 +201,10 @@ impl SelectOptionOperation for MultiSelectTypeOption {
         &mut self.options
     }
 }
-impl CellFilterOperation<GridSelectOptionFilter, SelectOptionIds> for MultiSelectTypeOption {
-    fn apply_filter(&self, _cell_data: SelectOptionIds, _filter: &GridSelectOptionFilter) -> bool {
-        false
+impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
+        let ids: SelectOptionIds = any_cell_data.try_into()?;
+        Ok(false)
     }
 }
 impl CellDataOperation<String> for MultiSelectTypeOption {
@@ -291,10 +293,12 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
 }
 
 pub struct SelectOptionIds(Vec<String>);
-impl std::convert::From<AnyCellData> for SelectOptionIds {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        let ids = select_option_ids(any_cell_data.cell_data);
-        Self(ids)
+impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        let ids = select_option_ids(value.cell_data);
+        Ok(Self(ids))
     }
 }
 

+ 13 - 6
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs

@@ -32,9 +32,14 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellFilterOperation<GridTextFilter, TextCellData> for RichTextTypeOption {
-    fn apply_filter(&self, _cell_data: TextCellData, _filter: &GridTextFilter) -> bool {
-        false
+impl CellFilterOperation<GridTextFilter> for RichTextTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
+        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.0))
     }
 }
 
@@ -74,9 +79,11 @@ impl CellDataOperation<String> for RichTextTypeOption {
 }
 
 pub struct TextCellData(String);
-impl std::convert::From<AnyCellData> for TextCellData {
-    fn from(any_data: AnyCellData) -> Self {
-        TextCellData(any_data.cell_data)
+impl std::convert::TryFrom<AnyCellData> for TextCellData {
+    type Error = FlowyError;
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        Ok(TextCellData(value.cell_data))
     }
 }
 

+ 14 - 6
frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs

@@ -35,9 +35,9 @@ pub struct URLTypeOption {
 }
 impl_type_option!(URLTypeOption, FieldType::URL);
 
-impl CellFilterOperation<GridTextFilter, URLCellData> for URLTypeOption {
-    fn apply_filter(&self, _cell_data: URLCellData, _filter: &GridTextFilter) -> bool {
-        false
+impl CellFilterOperation<GridTextFilter> for URLTypeOption {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
+        Ok(false)
     }
 }
 
@@ -121,9 +121,17 @@ impl FromStr for URLCellData {
     }
 }
 
-impl std::convert::From<AnyCellData> for URLCellData {
-    fn from(any_cell_data: AnyCellData) -> Self {
-        URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default()
+// impl std::convert::From<AnyCellData> for URLCellData {
+//     fn from(any_cell_data: AnyCellData) -> Self {
+//         URLCellData::from_str(&any_cell_data.cell_data).unwrap_or_default()
+//     }
+// }
+
+impl std::convert::TryFrom<AnyCellData> for URLCellData {
+    type Error = ();
+
+    fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
+        todo!()
     }
 }
 

+ 15 - 8
frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs

@@ -185,54 +185,61 @@ fn filter_cell(
             Some(
                 field_rev
                     .get_type_option_entry::<RichTextTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .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_entry::<NumberTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data, filter.value()),
+                    .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_entry::<DateTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data, filter.value()),
+                    .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_entry::<SingleSelectTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .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_entry::<MultiSelectTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .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_entry::<CheckboxTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .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_entry::<URLTypeOption>(field_type_rev)?
-                    .apply_filter(any_cell_data.into(), filter.value()),
+                    .apply_filter(any_cell_data, filter.value())
+                    .ok(),
             )
         }),
     }?;
 
-    let is_visible = !is_hidden;
+    let is_visible = !is_hidden.unwrap_or(false);
     match filter_result.visible_by_field_id.get(&filter_id) {
         None => {
             if is_visible {

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
 use std::str::FromStr;
 
-pub trait CellFilterOperation<T, C: From<AnyCellData>> {
-    fn apply_filter(&self, cell_data: C, filter: &T) -> bool;
+pub trait CellFilterOperation<T> {
+    fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult<bool>;
 }
 
 pub trait CellDataOperation<D> {