chore: add text filter & number filter tests

This commit is contained in:
appflowy 2022-07-07 14:56:04 +08:00
parent 63c7af5fd2
commit da0a7f01b3
16 changed files with 774 additions and 542 deletions

View File

@ -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),
}
}
}

View File

@ -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),
}
}
}

View File

@ -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(),
}
}
}

View File

@ -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::*;

View File

@ -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);
}
}
}

View File

@ -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(),
}
}
}

View File

@ -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);
}
}

View File

@ -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,
})
}
}

View File

@ -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!()
}
}

View File

@ -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)
}
}

View File

@ -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;
}
}
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
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
}
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]

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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!()
}
}

View File

@ -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 {

View File

@ -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> {