refactor: improve type option filter logic (#4652)

This commit is contained in:
Richard Shiue 2024-02-22 07:09:12 +08:00 committed by GitHub
parent 6f173c2ada
commit c500cd1287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 189 additions and 110 deletions

View File

@ -43,9 +43,8 @@ impl FromStr for DateFilterContentPB {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)] #[repr(u8)]
#[derive(Default)]
pub enum DateFilterConditionPB { pub enum DateFilterConditionPB {
#[default] #[default]
DateIs = 0, DateIs = 0,

View File

@ -12,9 +12,8 @@ pub struct NumberFilterPB {
pub content: String, pub content: String,
} }
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)] #[repr(u8)]
#[derive(Default)]
pub enum NumberFilterConditionPB { pub enum NumberFilterConditionPB {
#[default] #[default]
Equal = 0, Equal = 0,

View File

@ -1,65 +1,108 @@
use crate::entities::{DateFilterConditionPB, DateFilterPB}; use crate::entities::{DateFilterConditionPB, DateFilterPB};
use chrono::NaiveDateTime;
use chrono::{NaiveDate, NaiveDateTime};
use super::DateCellData;
impl DateFilterPB { impl DateFilterPB {
pub fn is_visible<T: Into<Option<i64>>>(&self, cell_timestamp: T) -> bool { /// Returns `None` if the DateFilterPB doesn't have the necessary data for
match cell_timestamp.into() { /// the condition. For example, `start` and `end` timestamps for
None => DateFilterConditionPB::DateIsEmpty == self.condition, /// `DateFilterConditionPB::DateWithin`.
Some(timestamp) => { pub fn is_visible(&self, cell_data: &DateCellData) -> Option<bool> {
match self.condition { let strategy = match self.condition {
DateFilterConditionPB::DateIsNotEmpty => { DateFilterConditionPB::DateIs => DateFilterStrategy::On(self.timestamp?),
return true; DateFilterConditionPB::DateBefore => DateFilterStrategy::Before(self.timestamp?),
}, DateFilterConditionPB::DateAfter => DateFilterStrategy::After(self.timestamp?),
DateFilterConditionPB::DateIsEmpty => { DateFilterConditionPB::DateOnOrBefore => DateFilterStrategy::OnOrBefore(self.timestamp?),
return false; DateFilterConditionPB::DateOnOrAfter => DateFilterStrategy::OnOrAfter(self.timestamp?),
}, DateFilterConditionPB::DateWithIn => DateFilterStrategy::DateWithin {
_ => {}, start: self.start?,
} end: self.end?,
let cell_time = NaiveDateTime::from_timestamp_opt(timestamp, 0);
let cell_date = cell_time.map(|time| time.date());
match self.timestamp {
None => {
if self.start.is_none() {
return true;
}
if self.end.is_none() {
return true;
}
let start_time = NaiveDateTime::from_timestamp_opt(*self.start.as_ref().unwrap(), 0);
let start_date = start_time.map(|time| time.date());
let end_time = NaiveDateTime::from_timestamp_opt(*self.end.as_ref().unwrap(), 0);
let end_date = end_time.map(|time| time.date());
cell_date >= start_date && cell_date <= end_date
},
Some(timestamp) => {
let expected_timestamp = NaiveDateTime::from_timestamp_opt(timestamp, 0);
let expected_date = expected_timestamp.map(|time| time.date());
// We assume that the cell_timestamp doesn't contain hours, just day.
match self.condition {
DateFilterConditionPB::DateIs => cell_date == expected_date,
DateFilterConditionPB::DateBefore => cell_date < expected_date,
DateFilterConditionPB::DateAfter => cell_date > expected_date,
DateFilterConditionPB::DateOnOrBefore => cell_date <= expected_date,
DateFilterConditionPB::DateOnOrAfter => cell_date >= expected_date,
_ => true,
}
},
}
}, },
DateFilterConditionPB::DateIsEmpty => DateFilterStrategy::Empty,
DateFilterConditionPB::DateIsNotEmpty => DateFilterStrategy::NotEmpty,
};
Some(strategy.filter(cell_data))
}
}
#[inline]
fn naive_date_from_timestamp(timestamp: i64) -> Option<NaiveDate> {
NaiveDateTime::from_timestamp_opt(timestamp, 0).map(|date_time: NaiveDateTime| date_time.date())
}
enum DateFilterStrategy {
On(i64),
Before(i64),
After(i64),
OnOrBefore(i64),
OnOrAfter(i64),
DateWithin { start: i64, end: i64 },
Empty,
NotEmpty,
}
impl DateFilterStrategy {
fn filter(self, cell_data: &DateCellData) -> bool {
match self {
DateFilterStrategy::On(expected_timestamp) => cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_date = naive_date_from_timestamp(expected_timestamp);
cell_date == expected_date
}),
DateFilterStrategy::Before(expected_timestamp) => {
cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_date = naive_date_from_timestamp(expected_timestamp);
cell_date < expected_date
})
},
DateFilterStrategy::After(expected_timestamp) => {
cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_date = naive_date_from_timestamp(expected_timestamp);
cell_date > expected_date
})
},
DateFilterStrategy::OnOrBefore(expected_timestamp) => {
cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_date = naive_date_from_timestamp(expected_timestamp);
cell_date <= expected_date
})
},
DateFilterStrategy::OnOrAfter(expected_timestamp) => {
cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_date = naive_date_from_timestamp(expected_timestamp);
cell_date >= expected_date
})
},
DateFilterStrategy::DateWithin { start, end } => {
cell_data.timestamp.is_some_and(|timestamp| {
let cell_date = naive_date_from_timestamp(timestamp);
let expected_start_date = naive_date_from_timestamp(start);
let expected_end_date = naive_date_from_timestamp(end);
cell_date >= expected_start_date && cell_date <= expected_end_date
})
},
DateFilterStrategy::Empty => {
cell_data.timestamp.is_none() && cell_data.end_timestamp.is_none()
},
DateFilterStrategy::NotEmpty => cell_data.timestamp.is_some(),
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::all)]
use crate::entities::{DateFilterConditionPB, DateFilterPB}; use crate::entities::{DateFilterConditionPB, DateFilterPB};
use crate::services::field::DateCellData;
fn to_cell_data(timestamp: i32) -> DateCellData {
DateCellData::new(timestamp as i64, false, false, "".to_string())
}
#[test] #[test]
fn date_filter_is_test() { fn date_filter_is_test() {
@ -70,10 +113,11 @@ mod tests {
start: None, start: None,
}; };
for (val, visible) in vec![(1668387885, true), (1647251762, false)] { for (val, visible) in [(1668387885, true), (1647251762, false)] {
assert_eq!(filter.is_visible(val as i64), visible); assert_eq!(filter.is_visible(&to_cell_data(val)).unwrap(), visible);
} }
} }
#[test] #[test]
fn date_filter_before_test() { fn date_filter_before_test() {
let filter = DateFilterPB { let filter = DateFilterPB {
@ -83,8 +127,13 @@ mod tests {
end: None, end: None,
}; };
for (val, visible, msg) in vec![(1668387884, false, "1"), (1647251762, true, "2")] { for (val, visible, msg) in [(1668387884, false, "1"), (1647251762, true, "2")] {
assert_eq!(filter.is_visible(val as i64), visible, "{}", msg); assert_eq!(
filter.is_visible(&to_cell_data(val)).unwrap(),
visible,
"{}",
msg
);
} }
} }
@ -97,8 +146,8 @@ mod tests {
end: None, end: None,
}; };
for (val, visible) in vec![(1668387884, true), (1668387885, true)] { for (val, visible) in [(1668387884, true), (1668387885, true)] {
assert_eq!(filter.is_visible(val as i64), visible); assert_eq!(filter.is_visible(&to_cell_data(val)).unwrap(), visible);
} }
} }
#[test] #[test]
@ -110,8 +159,8 @@ mod tests {
end: None, end: None,
}; };
for (val, visible) in vec![(1668387888, false), (1668531885, true), (0, false)] { for (val, visible) in [(1668387888, false), (1668531885, true), (0, false)] {
assert_eq!(filter.is_visible(val as i64), visible); assert_eq!(filter.is_visible(&to_cell_data(val)).unwrap(), visible);
} }
} }
@ -124,12 +173,12 @@ mod tests {
timestamp: None, timestamp: None,
}; };
for (val, visible, _msg) in vec![ for (val, visible, _msg) in [
(1668272685, true, "11/13"), (1668272685, true, "11/13"),
(1668359085, true, "11/14"), (1668359085, true, "11/14"),
(1668704685, false, "11/18"), (1668704685, false, "11/18"),
] { ] {
assert_eq!(filter.is_visible(val as i64), visible); assert_eq!(filter.is_visible(&to_cell_data(val)).unwrap(), visible);
} }
} }
@ -142,8 +191,16 @@ mod tests {
timestamp: None, timestamp: None,
}; };
for (val, visible) in vec![(None, true), (Some(123), false)] { for (val, visible) in [(None, true), (Some(123), false)] {
assert_eq!(filter.is_visible(val), visible); assert_eq!(
filter
.is_visible(&DateCellData {
timestamp: val,
..Default::default()
})
.unwrap(),
visible
);
} }
} }
} }

View File

@ -19,9 +19,6 @@ use crate::services::field::{
}; };
use crate::services::sort::SortCondition; use crate::services::sort::SortCondition;
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
/// So, storing the field type is necessary to distinguish the field type.
/// Most of the cases, each [FieldType] has its own [TypeOption] implementation.
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct DateTypeOption { pub struct DateTypeOption {
pub date_format: DateFormat, pub date_format: DateFormat,
@ -351,8 +348,7 @@ impl TypeOptionCellDataFilter for DateTypeOption {
if !field_type.is_date() { if !field_type.is_date() {
return true; return true;
} }
filter.is_visible(cell_data).unwrap_or(true)
filter.is_visible(cell_data.timestamp)
} }
} }

View File

@ -5,32 +5,62 @@ use rust_decimal::Decimal;
use std::str::FromStr; use std::str::FromStr;
impl NumberFilterPB { impl NumberFilterPB {
pub fn is_visible(&self, num_cell_data: &NumberCellFormat) -> bool { pub fn is_visible(&self, cell_data: &NumberCellFormat) -> Option<bool> {
if self.content.is_empty() { let expected_decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero());
match self.condition {
NumberFilterConditionPB::NumberIsEmpty => { let strategy = match self.condition {
return num_cell_data.is_empty(); NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal),
}, NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal),
NumberFilterConditionPB::NumberIsNotEmpty => { NumberFilterConditionPB::GreaterThan => NumberFilterStrategy::GreaterThan(expected_decimal),
return !num_cell_data.is_empty(); NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal),
}, NumberFilterConditionPB::GreaterThanOrEqualTo => {
_ => {}, NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal)
}
}
match num_cell_data.decimal().as_ref() {
None => false,
Some(cell_decimal) => {
let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero());
match self.condition {
NumberFilterConditionPB::Equal => cell_decimal == &decimal,
NumberFilterConditionPB::NotEqual => cell_decimal != &decimal,
NumberFilterConditionPB::GreaterThan => cell_decimal > &decimal,
NumberFilterConditionPB::LessThan => cell_decimal < &decimal,
NumberFilterConditionPB::GreaterThanOrEqualTo => cell_decimal >= &decimal,
NumberFilterConditionPB::LessThanOrEqualTo => cell_decimal <= &decimal,
_ => true,
}
}, },
NumberFilterConditionPB::LessThanOrEqualTo => {
NumberFilterStrategy::LessThanOrEqualTo(expected_decimal)
},
NumberFilterConditionPB::NumberIsEmpty => NumberFilterStrategy::Empty,
NumberFilterConditionPB::NumberIsNotEmpty => NumberFilterStrategy::NotEmpty,
};
Some(strategy.filter(cell_data))
}
}
enum NumberFilterStrategy {
Equal(Decimal),
NotEqual(Decimal),
GreaterThan(Decimal),
LessThan(Decimal),
GreaterThanOrEqualTo(Decimal),
LessThanOrEqualTo(Decimal),
Empty,
NotEmpty,
}
impl NumberFilterStrategy {
fn filter(self, cell_data: &NumberCellFormat) -> bool {
match self {
NumberFilterStrategy::Equal(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal == expected_value),
NumberFilterStrategy::NotEqual(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal != expected_value),
NumberFilterStrategy::GreaterThan(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal > expected_value),
NumberFilterStrategy::LessThan(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal < expected_value),
NumberFilterStrategy::GreaterThanOrEqualTo(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal >= expected_value),
NumberFilterStrategy::LessThanOrEqualTo(expected_value) => cell_data
.decimal()
.is_some_and(|decimal| decimal <= expected_value),
NumberFilterStrategy::Empty => cell_data.is_empty(),
NumberFilterStrategy::NotEmpty => !cell_data.is_empty(),
} }
} }
} }
@ -39,6 +69,7 @@ impl NumberFilterPB {
mod tests { mod tests {
use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
use crate::services::field::{NumberCellFormat, NumberFormat}; use crate::services::field::{NumberCellFormat, NumberFormat};
#[test] #[test]
fn number_filter_equal_test() { fn number_filter_equal_test() {
let number_filter = NumberFilterPB { let number_filter = NumberFilterPB {
@ -48,15 +79,16 @@ mod tests {
for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { for (num_str, visible) in [("123", true), ("1234", false), ("", false)] {
let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default(); let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default();
assert_eq!(number_filter.is_visible(&data), visible); assert_eq!(number_filter.is_visible(&data), Some(visible));
} }
let format = NumberFormat::USD; let format = NumberFormat::USD;
for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] {
let data = NumberCellFormat::from_format_str(num_str, &format).unwrap(); let data = NumberCellFormat::from_format_str(num_str, &format).unwrap();
assert_eq!(number_filter.is_visible(&data), visible); assert_eq!(number_filter.is_visible(&data), Some(visible));
} }
} }
#[test] #[test]
fn number_filter_greater_than_test() { fn number_filter_greater_than_test() {
let number_filter = NumberFilterPB { let number_filter = NumberFilterPB {
@ -65,7 +97,7 @@ mod tests {
}; };
for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] {
let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default(); let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default();
assert_eq!(number_filter.is_visible(&data), visible); assert_eq!(number_filter.is_visible(&data), Some(visible));
} }
} }
@ -77,7 +109,7 @@ mod tests {
}; };
for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] {
let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default(); let data = NumberCellFormat::from_format_str(num_str, &NumberFormat::Num).unwrap_or_default();
assert_eq!(number_filter.is_visible(&data), visible); assert_eq!(number_filter.is_visible(&data), Some(visible));
} }
} }
} }

View File

@ -120,10 +120,7 @@ impl NumberTypeOption {
Self::default() Self::default()
} }
pub(crate) fn format_cell_data( fn format_cell_data(&self, num_cell_data: &NumberCellData) -> FlowyResult<NumberCellFormat> {
&self,
num_cell_data: &NumberCellData,
) -> FlowyResult<NumberCellFormat> {
match self.format { match self.format {
NumberFormat::Num => { NumberFormat::Num => {
if SCIENTIFIC_NOTATION_REGEX if SCIENTIFIC_NOTATION_REGEX
@ -252,7 +249,7 @@ impl TypeOptionCellDataFilter for NumberTypeOption {
return true; return true;
} }
match self.format_cell_data(cell_data) { match self.format_cell_data(cell_data) {
Ok(cell_data) => filter.is_visible(&cell_data), Ok(cell_data) => filter.is_visible(&cell_data).unwrap_or(true),
Err(_) => true, Err(_) => true,
} }
} }

View File

@ -174,15 +174,14 @@ impl CellDataChangeset for TimestampTypeOption {
impl TypeOptionCellDataFilter for TimestampTypeOption { impl TypeOptionCellDataFilter for TimestampTypeOption {
fn apply_filter( fn apply_filter(
&self, &self,
filter: &<Self as TypeOption>::CellFilter, _filter: &<Self as TypeOption>::CellFilter,
field_type: &FieldType, field_type: &FieldType,
cell_data: &<Self as TypeOption>::CellData, _cell_data: &<Self as TypeOption>::CellData,
) -> bool { ) -> bool {
if !field_type.is_last_edited_time() && !field_type.is_created_time() { if !field_type.is_last_edited_time() && !field_type.is_created_time() {
return true; return true;
} }
false
filter.is_visible(cell_data.timestamp)
} }
} }