Merge pull request #600 from AppFlowy-IO/feat/row_filter_test

Feat/row filter test
This commit is contained in:
Nathan.fooo 2022-07-10 22:50:35 +08:00 committed by GitHub
commit 766af21bc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 985 additions and 503 deletions

View File

@ -50,6 +50,7 @@ lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file
[features]
default = []
default = ["filter"]
dart = ["lib-infra/dart"]
filter = []
flowy_unit_test = ["flowy-revision/flowy_unit_test"]

View File

@ -1,4 +1,3 @@
use crate::services::field::CheckboxCellData;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::revision::GridFilterRevision;
@ -10,16 +9,6 @@ pub struct GridCheckboxFilter {
pub condition: CheckboxCondition,
}
impl GridCheckboxFilter {
pub fn apply(&self, cell_data: &CheckboxCellData) -> bool {
let is_check = cell_data.is_check();
match self.condition {
CheckboxCondition::IsChecked => is_check,
CheckboxCondition::IsUnChecked => !is_check,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum CheckboxCondition {
@ -58,20 +47,3 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridCheckboxFilter {
}
}
}
#[cfg(test)]
mod tests {
use crate::entities::{CheckboxCondition, GridCheckboxFilter};
use crate::services::field::CheckboxCellData;
#[test]
fn checkbox_filter_is_check_test() {
let checkbox_filter = GridCheckboxFilter {
condition: CheckboxCondition::IsChecked,
};
for (value, r) in [("true", true), ("yes", true), ("false", false), ("no", false)] {
let data = CheckboxCellData(value.to_owned());
assert_eq!(checkbox_filter.apply(&data), r);
}
}
}

View File

@ -1,6 +1,10 @@
use crate::entities::FieldType;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::GridFilterRevision;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@ -9,7 +13,77 @@ pub struct GridDateFilter {
pub condition: DateFilterCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
pub start: Option<i64>,
#[pb(index = 3, one_of)]
pub end: Option<i64>,
}
#[derive(ProtoBuf, Default, Clone, Debug)]
pub struct CreateGridDateFilterPayload {
#[pb(index = 1)]
pub field_id: String,
#[pb(index = 2)]
pub field_type: FieldType,
#[pb(index = 3)]
pub condition: DateFilterCondition,
#[pb(index = 4, one_of)]
pub start: Option<i64>,
#[pb(index = 5, one_of)]
pub end: Option<i64>,
}
pub struct CreateGridDateFilterParams {
pub field_id: String,
pub field_type: FieldType,
pub condition: DateFilterCondition,
pub start: Option<i64>,
pub end: Option<i64>,
}
impl TryInto<CreateGridDateFilterParams> for CreateGridDateFilterPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridDateFilterParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
Ok(CreateGridDateFilterParams {
field_id,
condition: self.condition,
start: self.start,
field_type: self.field_type,
end: self.end,
})
}
}
#[derive(Serialize, Deserialize, Default)]
struct DateRange {
start: Option<i64>,
end: Option<i64>,
}
impl ToString for DateRange {
fn to_string(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "".to_string())
}
}
impl FromStr for DateRange {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
@ -48,9 +122,21 @@ impl std::convert::TryFrom<u8> for DateFilterCondition {
}
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(),
}
let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs);
let mut filter = GridDateFilter {
condition,
..Default::default()
};
if let Some(range) = rev
.content
.as_ref()
.and_then(|content| DateRange::from_str(content).ok())
{
filter.start = range.start;
filter.end = range.end;
};
filter
}
}

View File

@ -1,10 +1,7 @@
use crate::services::field::NumberCellData;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::revision::GridFilterRevision;
use rust_decimal::prelude::Zero;
use rust_decimal::Decimal;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@ -16,31 +13,6 @@ pub struct GridNumberFilter {
pub content: Option<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 {
@ -91,52 +63,3 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridNumberFilter {
}
}
}
#[cfg(test)]
mod tests {
use crate::entities::{GridNumberFilter, NumberFilterCondition};
use crate::services::field::{NumberCellData, NumberFormat};
use std::str::FromStr;
#[test]
fn number_filter_equal_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::Equal,
content: Some("123".to_owned()),
};
for (num_str, r) in [("123", true), ("1234", false), ("", false)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.apply(&data), r);
}
let format = NumberFormat::USD;
for (num_str, r) in [("$123", true), ("1234", false), ("", false)] {
let data = NumberCellData::from_format_str(num_str, true, &format).unwrap();
assert_eq!(number_filter.apply(&data), r);
}
}
#[test]
fn number_filter_greater_than_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::GreaterThan,
content: Some("12".to_owned()),
};
for (num_str, r) in [("123", true), ("10", false), ("30", true), ("", false)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.apply(&data), r);
}
}
#[test]
fn number_filter_less_than_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::LessThan,
content: Some("100".to_owned()),
};
for (num_str, r) in [("12", true), ("1234", false), ("30", true), ("", true)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.apply(&data), r);
}
}
}

View File

@ -1,5 +1,4 @@
#![allow(clippy::needless_collect)]
use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions};
use crate::services::field::select_option::SelectOptionIds;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::revision::GridFilterRevision;
@ -13,36 +12,6 @@ pub struct GridSelectOptionFilter {
#[pb(index = 2)]
pub option_ids: Vec<String>,
}
impl GridSelectOptionFilter {
pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool {
let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
match self.condition {
SelectOptionCondition::OptionIs => {
// if selected options equal to filter's options, then the required_options will be empty.
let required_options = self
.option_ids
.iter()
.filter(|id| !selected_option_ids.contains(id))
.collect::<Vec<_>>();
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
!required_options.is_empty()
}
SelectOptionCondition::OptionIsNot => {
for option_id in selected_option_ids {
if self.option_ids.contains(option_id) {
return true;
}
}
false
}
SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum SelectOptionCondition {
@ -87,35 +56,3 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
#[test]
fn select_option_filter_is_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let filter_1 = GridSelectOptionFilter {
condition: SelectOptionCondition::OptionIs,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
assert_eq!(
filter_1.apply(&SelectedSelectOptions {
options: vec![option_1.clone(), option_2.clone()],
}),
false
);
assert_eq!(
filter_1.apply(&SelectedSelectOptions {
options: vec![option_1.clone()],
}),
true,
);
}
}

View File

@ -12,27 +12,6 @@ pub struct GridTextFilter {
pub content: Option<String>,
}
impl GridTextFilter {
pub fn apply<T: AsRef<str>>(&self, cell_data: T) -> bool {
let cell_data = cell_data.as_ref();
let s = cell_data.to_lowercase();
if let Some(content) = self.content.as_ref() {
match self.condition {
TextFilterCondition::Is => &s == content,
TextFilterCondition::IsNot => &s != content,
TextFilterCondition::Contains => s.contains(content),
TextFilterCondition::DoesNotContain => !s.contains(content),
TextFilterCondition::StartsWith => s.starts_with(content),
TextFilterCondition::EndsWith => s.ends_with(content),
TextFilterCondition::TextIsEmpty => s.is_empty(),
TextFilterCondition::TextIsNotEmpty => !s.is_empty(),
}
} else {
false
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum TextFilterCondition {
@ -83,68 +62,3 @@ impl std::convert::From<Arc<GridFilterRevision>> for GridTextFilter {
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{GridTextFilter, TextFilterCondition};
#[test]
fn text_filter_equal_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::Is,
content: Some("appflowy".to_owned()),
};
assert!(text_filter.apply("AppFlowy"));
assert_eq!(text_filter.apply("appflowy"), true);
assert_eq!(text_filter.apply("Appflowy"), true);
assert_eq!(text_filter.apply("AppFlowy.io"), false);
}
#[test]
fn text_filter_start_with_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::StartsWith,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.apply("AppFlowy.io"), true);
assert_eq!(text_filter.apply(""), false);
assert_eq!(text_filter.apply("https"), false);
}
#[test]
fn text_filter_end_with_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::EndsWith,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.apply("https://github.com/appflowy"), true);
assert_eq!(text_filter.apply("App"), false);
assert_eq!(text_filter.apply("appflowy.io"), false);
}
#[test]
fn text_filter_empty_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::TextIsEmpty,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.apply(""), true);
assert_eq!(text_filter.apply("App"), false);
}
#[test]
fn text_filter_contain_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::Contains,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.apply("https://github.com/appflowy"), true);
assert_eq!(text_filter.apply("AppFlowy"), true);
assert_eq!(text_filter.apply("App"), false);
assert_eq!(text_filter.apply(""), false);
assert_eq!(text_filter.apply("github"), false);
}
}

View File

@ -22,16 +22,16 @@ pub struct RepeatedGridFilter {
pub items: Vec<GridFilter>,
}
impl std::convert::From<&Arc<GridFilterRevision>> for GridFilter {
fn from(rev: &Arc<GridFilterRevision>) -> Self {
impl std::convert::From<&GridFilterRevision> for GridFilter {
fn from(rev: &GridFilterRevision) -> Self {
Self { id: rev.id.clone() }
}
}
impl std::convert::From<&Vec<Arc<GridFilterRevision>>> for RepeatedGridFilter {
fn from(revs: &Vec<Arc<GridFilterRevision>>) -> Self {
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(),
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}
@ -45,9 +45,12 @@ impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct DeleteFilterPayload {
#[pb(index = 1)]
pub filter_id: String,
pub field_id: String,
#[pb(index = 2)]
pub filter_id: String,
#[pb(index = 3)]
pub field_type: FieldType,
}
@ -55,10 +58,14 @@ impl TryInto<DeleteFilterParams> for DeleteFilterPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
let filter_id = NotEmptyStr::parse(self.filter_id)
.map_err(|_| ErrorCode::UnexpectedEmptyString)?
.0;
Ok(DeleteFilterParams {
field_id,
filter_id,
field_type_rev: self.field_type.into(),
})

View File

@ -4,6 +4,7 @@ use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::GridGroupRevision;
use flowy_sync::entities::grid::CreateGridGroupParams;
use std::convert::TryInto;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridGroup {
@ -39,10 +40,10 @@ impl std::convert::From<Vec<GridGroup>> for RepeatedGridGroup {
}
}
impl std::convert::From<&Vec<GridGroupRevision>> for RepeatedGridGroup {
fn from(revs: &Vec<GridGroupRevision>) -> Self {
impl std::convert::From<Vec<Arc<GridGroupRevision>>> for RepeatedGridGroup {
fn from(revs: Vec<Arc<GridGroupRevision>>) -> Self {
RepeatedGridGroup {
items: revs.iter().map(|rev| rev.into()).collect(),
items: revs.iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}

View File

@ -9,49 +9,45 @@ use flowy_grid_data_model::revision::GridLayoutRevision;
use flowy_sync::entities::grid::GridSettingChangesetParams;
use std::collections::HashMap;
use std::convert::TryInto;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSetting {
#[pb(index = 1)]
pub filters_by_layout_ty: HashMap<String, RepeatedGridFilter>,
pub layouts: Vec<GridLayout>,
#[pb(index = 2)]
pub groups_by_layout_ty: HashMap<String, RepeatedGridGroup>,
pub current_layout_type: GridLayoutType,
#[pb(index = 3)]
pub sorts_by_layout_ty: HashMap<String, RepeatedGridSort>,
pub filters_by_field_id: HashMap<String, RepeatedGridFilter>,
#[pb(index = 4)]
pub groups_by_field_id: HashMap<String, RepeatedGridGroup>,
#[pb(index = 5)]
pub sorts_by_field_id: HashMap<String, RepeatedGridSort>,
}
//
// impl std::convert::From<&GridSettingRevision> for GridSetting {
// fn from(rev: &GridSettingRevision) -> Self {
// let filters_by_layout_ty: HashMap<String, RepeatedGridFilter> = rev
// .filters
// .iter()
// .map(|(layout_rev, filter_revs)| (layout_rev.to_string(), filter_revs.into()))
// .collect();
//
// let groups_by_layout_ty: HashMap<String, RepeatedGridGroup> = rev
// .groups
// .iter()
// .map(|(layout_rev, group_revs)| (layout_rev.to_string(), group_revs.into()))
// .collect();
//
// let sorts_by_layout_ty: HashMap<String, RepeatedGridSort> = rev
// .sorts
// .iter()
// .map(|(layout_rev, sort_revs)| (layout_rev.to_string(), sort_revs.into()))
// .collect();
//
// GridSetting {
// filters_by_layout_ty,
// groups_by_layout_ty,
// sorts_by_layout_ty,
// }
// }
// }
//
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridLayout {
#[pb(index = 1)]
ty: GridLayoutType,
}
impl GridLayout {
pub fn all() -> Vec<GridLayout> {
let mut layouts = vec![];
for layout_ty in GridLayoutType::iter() {
layouts.push(GridLayout { ty: layout_ty })
}
layouts
}
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)]
#[repr(u8)]
pub enum GridLayoutType {
Table = 0,

View File

@ -4,6 +4,7 @@ use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::GridSortRevision;
use flowy_sync::entities::grid::CreateGridSortParams;
use std::convert::TryInto;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSort {
@ -30,10 +31,10 @@ pub struct RepeatedGridSort {
pub items: Vec<GridSort>,
}
impl std::convert::From<&Vec<GridSortRevision>> for RepeatedGridSort {
fn from(revs: &Vec<GridSortRevision>) -> Self {
impl std::convert::From<Vec<Arc<GridSortRevision>>> for RepeatedGridSort {
fn from(revs: Vec<Arc<GridSortRevision>>) -> Self {
RepeatedGridSort {
items: revs.iter().map(|rev| rev.into()).collect(),
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}

View File

@ -363,9 +363,16 @@ pub(crate) async fn get_select_option_handler(
data_result(SelectOptionCellData::default())
}
Some(field_rev) => {
//
let cell_rev = editor.get_cell_rev(&params.row_id, &params.field_id).await?;
let type_option = select_option_operation(&field_rev)?;
let any_cell_data: AnyCellData = cell_rev.try_into()?;
let any_cell_data: AnyCellData = match cell_rev {
None => AnyCellData {
data: "".to_string(),
field_type: field_rev.field_type_rev.clone().into(),
},
Some(cell_rev) => cell_rev.try_into()?,
};
let option_context = type_option.selected_select_option(any_cell_data);
data_result(option_context)
}

View File

@ -9,7 +9,7 @@ use std::str::FromStr;
/// So it will return an empty data. You could check the CellDataOperation trait for more information.
#[derive(Debug, Serialize, Deserialize)]
pub struct AnyCellData {
pub cell_data: String,
pub data: String,
pub field_type: FieldType,
}
@ -38,21 +38,10 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData {
}
}
impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
impl std::convert::TryFrom<CellRevision> for AnyCellData {
type Error = FlowyError;
fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
match value {
None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
Some(cell_rev) => AnyCellData::try_from(cell_rev),
}
}
}
impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
type Error = FlowyError;
fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
fn try_from(value: CellRevision) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
@ -60,7 +49,7 @@ impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
impl AnyCellData {
pub fn new(content: String, field_type: FieldType) -> Self {
AnyCellData {
cell_data: content,
data: content,
field_type,
}
}

View File

@ -51,7 +51,10 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
if let Ok(any_cell_data) = data.try_into() {
let AnyCellData { cell_data, field_type } = any_cell_data;
let AnyCellData {
data: cell_data,
field_type,
} = any_cell_data;
let to_field_type = field_rev.field_type_rev.into();
match try_decode_cell_data(CellData(Some(cell_data)), field_rev, &field_type, &to_field_type) {
Ok(cell_data) => cell_data,

View File

@ -65,7 +65,7 @@ pub fn make_selected_select_options<T: TryInto<AnyCellData>>(
options: &[SelectOption],
) -> Vec<SelectOption> {
if let Ok(type_option_cell_data) = any_cell_data.try_into() {
let ids = SelectOptionIds::from(type_option_cell_data.cell_data);
let ids = SelectOptionIds::from(type_option_cell_data.data);
ids.iter()
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
.collect()
@ -151,7 +151,7 @@ impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
type Error = FlowyError;
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
Ok(Self::from(value.cell_data))
Ok(Self::from(value.data))
}
}

View File

@ -1,8 +1,6 @@
use crate::entities::{FieldType, GridCheckboxFilter};
use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
};
use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
@ -42,16 +40,6 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
const YES: &str = "Yes";
const NO: &str = "No";
impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult<bool> {
if !any_cell_data.is_checkbox() {
return Ok(true);
}
let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?;
Ok(filter.apply(&checkbox_cell_data))
}
}
impl CellDataOperation<String, String> for CheckboxTypeOption {
fn decode_cell_data(
&self,

View File

@ -1,9 +1,8 @@
use crate::entities::{CellChangeset, FieldType, GridDateFilter};
use crate::entities::{CellChangeset, FieldType};
use crate::entities::{CellIdentifier, CellIdentifierPayload};
use crate::impl_type_option;
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
FromCellChangeset, FromCellString,
AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellChangeset, FromCellString,
};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
@ -110,6 +109,10 @@ impl DateTypeOption {
fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
let native = NaiveDateTime::from_timestamp(timestamp, 0);
let native2 = NaiveDateTime::from_timestamp(timestamp, 0);
if native > native2 {}
self.utc_date_time_from_native(native)
}
@ -118,19 +121,10 @@ impl DateTypeOption {
}
}
impl CellFilterOperation<GridDateFilter> for DateTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridDateFilter) -> FlowyResult<bool> {
if !any_cell_data.is_date() {
return Ok(true);
}
Ok(false)
}
}
impl CellDataOperation<TimestampParser, DateCellChangeset> for DateTypeOption {
impl CellDataOperation<DateTimestamp, DateCellChangeset> for DateTypeOption {
fn decode_cell_data(
&self,
cell_data: CellData<TimestampParser>,
cell_data: CellData<DateTimestamp>,
decoded_field_type: &FieldType,
_field_rev: &FieldRevision,
) -> FlowyResult<DecodedCellData> {
@ -168,17 +162,36 @@ impl CellDataOperation<TimestampParser, DateCellChangeset> for DateTypeOption {
}
}
pub struct TimestampParser(i64);
pub struct DateTimestamp(i64);
impl AsRef<i64> for DateTimestamp {
fn as_ref(&self) -> &i64 {
&self.0
}
}
impl FromCellString for TimestampParser {
impl std::convert::From<DateTimestamp> for i64 {
fn from(timestamp: DateTimestamp) -> Self {
timestamp.0
}
}
impl FromCellString for DateTimestamp {
fn from_cell_str(s: &str) -> FlowyResult<Self>
where
Self: Sized,
{
let num = s.parse::<i64>().unwrap_or(0);
Ok(TimestampParser(num))
Ok(DateTimestamp(num))
}
}
impl std::convert::From<AnyCellData> for DateTimestamp {
fn from(data: AnyCellData) -> Self {
let num = data.data.parse::<i64>().unwrap_or(0);
DateTimestamp(num)
}
}
#[derive(Default)]
pub struct DateTypeOptionBuilder(DateTypeOption);
impl_into_box_type_option_builder!(DateTypeOptionBuilder);

View File

@ -1,12 +1,10 @@
use crate::entities::{FieldType, GridSelectOptionFilter};
use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
};
use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
use crate::services::field::select_option::{
make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
SelectOptionOperation, SELECTION_IDS_SEPARATOR,
};
use crate::services::field::type_options::util::get_cell_data;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
@ -46,16 +44,7 @@ impl SelectOptionOperation for MultiSelectTypeOption {
&mut self.options
}
}
impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
if !any_cell_data.is_multi_select() {
return Ok(true);
}
let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
Ok(filter.apply(&selected_options))
}
}
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSelectTypeOption {
fn decode_cell_data(
&self,

View File

@ -1,9 +1,7 @@
use crate::impl_type_option;
use crate::entities::{FieldType, GridNumberFilter};
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
};
use crate::entities::FieldType;
use crate::services::cell::{CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
use crate::services::field::number_currency::Currency;
use crate::services::field::type_options::number_type_option::format::*;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
@ -79,7 +77,7 @@ impl NumberTypeOption {
Self::default()
}
fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult<NumberCellData> {
match self.format {
NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) {
Ok(value, ..) => Ok(NumberCellData::from_decimal(value)),
@ -105,18 +103,6 @@ pub(crate) fn strip_currency_symbol<T: ToString>(s: T) -> String {
}
s
}
impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult<bool> {
if !any_cell_data.is_number() {
return Ok(true);
}
let cell_data = any_cell_data.cell_data;
let num_cell_data = self.format_cell_data(&cell_data)?;
Ok(filter.apply(&num_cell_data))
}
}
impl CellDataOperation<String, String> for NumberTypeOption {
fn decode_cell_data(

View File

@ -1,8 +1,6 @@
use crate::entities::{FieldType, GridSelectOptionFilter};
use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
};
use crate::services::cell::{AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData};
use crate::services::field::select_option::{
make_selected_select_options, SelectOption, SelectOptionCellChangeset, SelectOptionCellData, SelectOptionIds,
SelectOptionOperation,
@ -43,16 +41,6 @@ impl SelectOptionOperation for SingleSelectTypeOption {
}
}
impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
if !any_cell_data.is_single_select() {
return Ok(true);
}
let _ids: SelectOptionIds = any_cell_data.try_into()?;
Ok(false)
}
}
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for SingleSelectTypeOption {
fn decode_cell_data(
&self,

View File

@ -1,8 +1,7 @@
use crate::entities::{FieldType, GridTextFilter};
use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{
try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation,
DecodedCellData,
try_decode_cell_data, AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData,
};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
@ -33,17 +32,6 @@ pub struct RichTextTypeOption {
}
impl_type_option!(RichTextTypeOption, FieldType::RichText);
impl CellFilterOperation<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))
}
}
impl CellDataOperation<String, String> for RichTextTypeOption {
fn decode_cell_data(
&self,
@ -88,7 +76,7 @@ impl std::convert::TryFrom<AnyCellData> for TextCellData {
type Error = FlowyError;
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
Ok(TextCellData(value.cell_data))
Ok(TextCellData(value.data))
}
}

View File

@ -1,9 +1,9 @@
use crate::entities::{FieldType, GridTextFilter};
use crate::entities::FieldType;
use crate::impl_type_option;
use crate::services::cell::{
AnyCellData, CellData, CellDataChangeset, CellDataOperation, CellFilterOperation, DecodedCellData, FromCellString,
AnyCellData, CellData, CellDataChangeset, CellDataOperation, DecodedCellData, FromCellString,
};
use crate::services::field::{BoxTypeOptionBuilder, TextCellData, TypeOptionBuilder};
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use fancy_regex::Regex;
use flowy_derive::ProtoBuf;
@ -34,17 +34,6 @@ pub struct URLTypeOption {
}
impl_type_option!(URLTypeOption, FieldType::URL);
impl CellFilterOperation<GridTextFilter> for URLTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
if !any_cell_data.is_url() {
return Ok(true);
}
let text_cell_data: TextCellData = any_cell_data.try_into()?;
Ok(filter.apply(&text_cell_data))
}
}
impl CellDataOperation<URLCellData, String> for URLTypeOption {
fn decode_cell_data(
&self,
@ -125,7 +114,7 @@ impl std::convert::TryFrom<AnyCellData> for URLCellData {
type Error = FlowyError;
fn try_from(data: AnyCellData) -> Result<Self, Self::Error> {
serde_json::from_str::<URLCellData>(&data.cell_data).map_err(internal_error)
serde_json::from_str::<URLCellData>(&data.data).map_err(internal_error)
}
}

View File

@ -4,7 +4,7 @@ use std::str::FromStr;
pub fn get_cell_data(cell_rev: &CellRevision) -> String {
match AnyCellData::from_str(&cell_rev.data) {
Ok(type_option) => type_option.cell_data,
Ok(type_option) => type_option.data,
Err(_) => String::new(),
}
}

View File

@ -1,20 +1,19 @@
use crate::entities::{
FieldType, GridCheckboxFilter, GridDateFilter, GridNumberFilter, GridSelectOptionFilter, GridTextFilter,
};
use dashmap::DashMap;
use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
use flowy_sync::client_grid::GridRevisionPad;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
type RowId = String;
#[derive(Default)]
pub(crate) struct FilterResultCache {
// key: row id
inner: DashMap<String, FilterResult>,
inner: DashMap<RowId, FilterResult>,
}
impl FilterResultCache {
@ -70,7 +69,7 @@ pub(crate) struct FilterCache {
impl FilterCache {
pub(crate) async fn from_grid_pad(grid_pad: &Arc<RwLock<GridRevisionPad>>) -> Arc<Self> {
let this = Arc::new(Self::default());
let _ = reload_filter_cache(this.clone(), None, grid_pad).await;
let _ = refresh_filter_cache(this.clone(), None, grid_pad).await;
this
}
@ -101,7 +100,8 @@ impl FilterCache {
}
}
pub(crate) async fn reload_filter_cache(
/// Refresh the filter according to the field id.
pub(crate) async fn refresh_filter_cache(
cache: Arc<FilterCache>,
field_ids: Option<Vec<String>>,
grid_pad: &Arc<RwLock<GridRevisionPad>>,

View File

@ -7,7 +7,7 @@ use crate::services::field::{
SingleSelectTypeOption, URLTypeOption,
};
use crate::services::filter::filter_cache::{
reload_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache,
refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache,
};
use crate::services::grid_editor_task::GridServiceTaskScheduler;
use crate::services::row::GridBlockSnapshot;
@ -62,6 +62,7 @@ impl GridFilterService {
let mut changesets = vec![];
for (index, block) in task_context.blocks.into_iter().enumerate() {
// The row_ids contains the row that its visibility was changed.
let row_ids = block
.row_revs
.par_iter()
@ -74,6 +75,8 @@ impl GridFilterService {
let mut visible_rows = vec![];
let mut hide_rows = vec![];
// Query the filter result from the cache
for row_id in row_ids {
if self
.filter_result_cache
@ -93,8 +96,11 @@ impl GridFilterService {
visible_rows,
..Default::default()
};
// Save the changeset for each block
changesets.push(changeset);
}
self.notify(changesets).await;
Ok(())
}
@ -106,7 +112,7 @@ impl GridFilterService {
if let Some(filter_id) = &changeset.insert_filter {
let field_ids = Some(vec![filter_id.field_id.clone()]);
reload_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await;
refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await;
}
if let Some(filter_id) = &changeset.delete_filter {
@ -179,7 +185,7 @@ fn filter_cell(
field_type,
};
let any_cell_data = AnyCellData::try_from(cell_rev).ok()?;
let is_hidden = match &filter_id.field_type {
let is_visible = match &filter_id.field_type {
FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
@ -238,7 +244,7 @@ fn filter_cell(
}),
}?;
let is_visible = !is_hidden.unwrap_or(false);
let is_visible = !is_visible.unwrap_or(true);
match filter_result.visible_by_field_id.get(&filter_id) {
None => {
if is_visible {

View File

@ -0,0 +1,52 @@
use crate::entities::{CheckboxCondition, GridCheckboxFilter};
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{CheckboxCellData, CheckboxTypeOption};
use flowy_error::FlowyResult;
impl GridCheckboxFilter {
pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool {
let is_check = cell_data.is_check();
match self.condition {
CheckboxCondition::IsChecked => is_check,
CheckboxCondition::IsUnChecked => !is_check,
}
}
}
impl CellFilterOperation<GridCheckboxFilter> for CheckboxTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult<bool> {
if !any_cell_data.is_checkbox() {
return Ok(true);
}
let checkbox_cell_data: CheckboxCellData = any_cell_data.try_into()?;
Ok(filter.is_visible(&checkbox_cell_data))
}
}
#[cfg(test)]
mod tests {
use crate::entities::{CheckboxCondition, GridCheckboxFilter};
use crate::services::field::CheckboxCellData;
#[test]
fn checkbox_filter_is_check_test() {
let checkbox_filter = GridCheckboxFilter {
condition: CheckboxCondition::IsChecked,
};
for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] {
let data = CheckboxCellData(value.to_owned());
assert_eq!(checkbox_filter.is_visible(&data), visible);
}
}
#[test]
fn checkbox_filter_is_uncheck_test() {
let checkbox_filter = GridCheckboxFilter {
condition: CheckboxCondition::IsUnChecked,
};
for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] {
let data = CheckboxCellData(value.to_owned());
assert_eq!(checkbox_filter.is_visible(&data), visible);
}
}
}

View File

@ -0,0 +1,107 @@
use crate::entities::{DateFilterCondition, GridDateFilter};
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{DateTimestamp, DateTypeOption};
use flowy_error::FlowyResult;
impl GridDateFilter {
pub fn is_visible<T: Into<i64>>(&self, cell_timestamp: T) -> bool {
if self.start.is_none() {
return false;
}
let cell_timestamp = cell_timestamp.into();
let start_timestamp = *self.start.as_ref().unwrap();
// We assume that the cell_timestamp doesn't contain hours, just day.
match self.condition {
DateFilterCondition::DateIs => cell_timestamp == start_timestamp,
DateFilterCondition::DateBefore => cell_timestamp < start_timestamp,
DateFilterCondition::DateAfter => cell_timestamp > start_timestamp,
DateFilterCondition::DateOnOrBefore => cell_timestamp <= start_timestamp,
DateFilterCondition::DateOnOrAfter => cell_timestamp >= start_timestamp,
DateFilterCondition::DateWithIn => {
if let Some(end_timestamp) = self.end.as_ref() {
cell_timestamp >= start_timestamp && cell_timestamp <= *end_timestamp
} else {
false
}
}
DateFilterCondition::DateIsEmpty => cell_timestamp == 0_i64,
}
}
}
impl CellFilterOperation<GridDateFilter> for DateTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridDateFilter) -> FlowyResult<bool> {
if !any_cell_data.is_date() {
return Ok(true);
}
let timestamp: DateTimestamp = any_cell_data.into();
Ok(filter.is_visible(timestamp))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{DateFilterCondition, GridDateFilter};
#[test]
fn date_filter_is_test() {
let filter = GridDateFilter {
condition: DateFilterCondition::DateIs,
start: Some(123),
end: None,
};
for (val, visible) in vec![(123, true), (12, false)] {
assert_eq!(filter.is_visible(val as i64), visible);
}
}
#[test]
fn date_filter_before_test() {
let filter = GridDateFilter {
condition: DateFilterCondition::DateBefore,
start: Some(123),
end: None,
};
for (val, visible) in vec![(123, false), (122, true)] {
assert_eq!(filter.is_visible(val as i64), visible);
}
}
#[test]
fn date_filter_before_or_on_test() {
let filter = GridDateFilter {
condition: DateFilterCondition::DateOnOrBefore,
start: Some(123),
end: None,
};
for (val, visible) in vec![(123, true), (122, true)] {
assert_eq!(filter.is_visible(val as i64), visible);
}
}
#[test]
fn date_filter_after_test() {
let filter = GridDateFilter {
condition: DateFilterCondition::DateAfter,
start: Some(123),
end: None,
};
for (val, visible) in vec![(1234, true), (122, false), (0, false)] {
assert_eq!(filter.is_visible(val as i64), visible);
}
}
#[test]
fn date_filter_within_test() {
let filter = GridDateFilter {
condition: DateFilterCondition::DateWithIn,
start: Some(123),
end: Some(130),
};
for (val, visible) in vec![(123, true), (130, true), (132, false)] {
assert_eq!(filter.is_visible(val as i64), visible);
}
}
}

View File

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

View File

@ -0,0 +1,94 @@
use crate::entities::{GridNumberFilter, NumberFilterCondition};
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{NumberCellData, NumberTypeOption};
use flowy_error::FlowyResult;
use rust_decimal::prelude::Zero;
use rust_decimal::Decimal;
use std::str::FromStr;
impl GridNumberFilter {
pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool {
if self.content.is_none() {
return false;
}
let content = self.content.as_ref().unwrap();
let zero_decimal = Decimal::zero();
let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal);
match Decimal::from_str(content) {
Ok(decimal) => match self.condition {
NumberFilterCondition::Equal => cell_decimal == &decimal,
NumberFilterCondition::NotEqual => cell_decimal != &decimal,
NumberFilterCondition::GreaterThan => cell_decimal > &decimal,
NumberFilterCondition::LessThan => cell_decimal < &decimal,
NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal,
NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal,
NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(),
NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(),
},
Err(_) => false,
}
}
}
impl CellFilterOperation<GridNumberFilter> for NumberTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult<bool> {
if !any_cell_data.is_number() {
return Ok(true);
}
let cell_data = any_cell_data.data;
let num_cell_data = self.format_cell_data(&cell_data)?;
Ok(filter.is_visible(&num_cell_data))
}
}
#[cfg(test)]
mod tests {
use crate::entities::{GridNumberFilter, NumberFilterCondition};
use crate::services::field::{NumberCellData, NumberFormat};
use std::str::FromStr;
#[test]
fn number_filter_equal_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::Equal,
content: Some("123".to_owned()),
};
for (num_str, visible) in [("123", true), ("1234", false), ("", false)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.is_visible(&data), visible);
}
let format = NumberFormat::USD;
for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] {
let data = NumberCellData::from_format_str(num_str, true, &format).unwrap();
assert_eq!(number_filter.is_visible(&data), visible);
}
}
#[test]
fn number_filter_greater_than_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::GreaterThan,
content: Some("12".to_owned()),
};
for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.is_visible(&data), visible);
}
}
#[test]
fn number_filter_less_than_test() {
let number_filter = GridNumberFilter {
condition: NumberFilterCondition::LessThan,
content: Some("100".to_owned()),
};
for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] {
let data = NumberCellData::from_str(num_str).unwrap();
assert_eq!(number_filter.is_visible(&data), visible);
}
}
}

View File

@ -0,0 +1,109 @@
#![allow(clippy::needless_collect)]
use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::select_option::{SelectOptionOperation, SelectedSelectOptions};
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
use flowy_error::FlowyResult;
impl GridSelectOptionFilter {
pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool {
let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
match self.condition {
SelectOptionCondition::OptionIs => {
if self.option_ids.len() != selected_option_ids.len() {
return true;
}
// if selected options equal to filter's options, then the required_options will be empty.
let required_options = self
.option_ids
.iter()
.filter(|id| !selected_option_ids.contains(id))
.collect::<Vec<_>>();
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
!required_options.is_empty()
}
SelectOptionCondition::OptionIsNot => {
for option_id in selected_option_ids {
if self.option_ids.contains(option_id) {
return true;
}
}
false
}
SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
}
}
}
impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
if !any_cell_data.is_multi_select() {
return Ok(true);
}
let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
Ok(filter.is_visible(&selected_options))
}
}
impl CellFilterOperation<GridSelectOptionFilter> for SingleSelectTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
if !any_cell_data.is_single_select() {
return Ok(true);
}
let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
Ok(filter.is_visible(&selected_options))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
#[test]
fn select_option_filter_is_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
let filter_1 = GridSelectOptionFilter {
condition: SelectOptionCondition::OptionIs,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
assert_eq!(
filter_1.is_visible(&SelectedSelectOptions {
options: vec![option_1.clone(), option_2.clone()],
}),
false
);
assert_eq!(
filter_1.is_visible(&SelectedSelectOptions {
options: vec![option_1.clone(), option_2.clone(), option_3.clone()],
}),
true
);
assert_eq!(
filter_1.is_visible(&SelectedSelectOptions {
options: vec![option_1.clone(), option_3.clone()],
}),
true
);
assert_eq!(filter_1.is_visible(&SelectedSelectOptions { options: vec![] }), true);
assert_eq!(
filter_1.is_visible(&SelectedSelectOptions {
options: vec![option_1.clone()],
}),
true,
);
}
}

View File

@ -0,0 +1,100 @@
use crate::entities::{GridTextFilter, TextFilterCondition};
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{RichTextTypeOption, TextCellData};
use flowy_error::FlowyResult;
impl GridTextFilter {
pub fn is_visible<T: AsRef<str>>(&self, cell_data: T) -> bool {
let cell_data = cell_data.as_ref();
let s = cell_data.to_lowercase();
if let Some(content) = self.content.as_ref() {
match self.condition {
TextFilterCondition::Is => &s == content,
TextFilterCondition::IsNot => &s != content,
TextFilterCondition::Contains => s.contains(content),
TextFilterCondition::DoesNotContain => !s.contains(content),
TextFilterCondition::StartsWith => s.starts_with(content),
TextFilterCondition::EndsWith => s.ends_with(content),
TextFilterCondition::TextIsEmpty => s.is_empty(),
TextFilterCondition::TextIsNotEmpty => !s.is_empty(),
}
} else {
false
}
}
}
impl CellFilterOperation<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.is_visible(text_cell_data))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{GridTextFilter, TextFilterCondition};
#[test]
fn text_filter_equal_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::Is,
content: Some("appflowy".to_owned()),
};
assert!(text_filter.is_visible("AppFlowy"));
assert_eq!(text_filter.is_visible("appflowy"), true);
assert_eq!(text_filter.is_visible("Appflowy"), true);
assert_eq!(text_filter.is_visible("AppFlowy.io"), false);
}
#[test]
fn text_filter_start_with_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::StartsWith,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.is_visible("AppFlowy.io"), true);
assert_eq!(text_filter.is_visible(""), false);
assert_eq!(text_filter.is_visible("https"), false);
}
#[test]
fn text_filter_end_with_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::EndsWith,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true);
assert_eq!(text_filter.is_visible("App"), false);
assert_eq!(text_filter.is_visible("appflowy.io"), false);
}
#[test]
fn text_filter_empty_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::TextIsEmpty,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.is_visible(""), true);
assert_eq!(text_filter.is_visible("App"), false);
}
#[test]
fn text_filter_contain_test() {
let text_filter = GridTextFilter {
condition: TextFilterCondition::Contains,
content: Some("appflowy".to_owned()),
};
assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true);
assert_eq!(text_filter.is_visible("AppFlowy"), true);
assert_eq!(text_filter.is_visible("App"), false);
assert_eq!(text_filter.is_visible(""), false);
assert_eq!(text_filter.is_visible("github"), false);
}
}

View File

@ -0,0 +1,15 @@
use crate::entities::GridTextFilter;
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{TextCellData, URLTypeOption};
use flowy_error::FlowyResult;
impl CellFilterOperation<GridTextFilter> for URLTypeOption {
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult<bool> {
if !any_cell_data.is_url() {
return Ok(true);
}
let text_cell_data: TextCellData = any_cell_data.try_into()?;
Ok(filter.is_visible(&text_cell_data))
}
}

View File

@ -1,4 +1,5 @@
mod filter_cache;
mod filter_service;
mod impls;
pub(crate) use filter_service::*;

View File

@ -11,6 +11,7 @@ use crate::services::row::{
make_grid_blocks, make_row_from_row_rev, make_row_rev_from_context, make_rows_from_row_revs,
CreateRowRevisionBuilder, CreateRowRevisionPayload, GridBlockSnapshot,
};
use crate::services::setting::make_grid_setting;
use bytes::Bytes;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::revision::*;
@ -453,17 +454,21 @@ impl GridRevisionEditor {
}
pub async fn get_grid_setting(&self) -> FlowyResult<GridSetting> {
// let read_guard = self.grid_pad.read().await;
// let grid_setting_rev = read_guard.get_grid_setting_rev();
// Ok(grid_setting_rev.into())
todo!()
let read_guard = self.grid_pad.read().await;
let grid_setting_rev = read_guard.get_grid_setting_rev();
let field_revs = read_guard.get_field_revs(None)?;
let grid_setting = make_grid_setting(grid_setting_rev, &field_revs);
Ok(grid_setting)
}
pub async fn get_grid_filter(&self, layout_type: &GridLayoutType) -> FlowyResult<Vec<GridFilter>> {
let read_guard = self.grid_pad.read().await;
let layout_rev = layout_type.clone().into();
match read_guard.get_filters(Some(&layout_rev), None) {
Some(filter_revs) => Ok(filter_revs.iter().map(GridFilter::from).collect::<Vec<GridFilter>>()),
Some(filter_revs) => Ok(filter_revs
.iter()
.map(|filter_rev| filter_rev.as_ref().into())
.collect::<Vec<GridFilter>>()),
None => Ok(vec![]),
}
}

View File

@ -1,5 +1,10 @@
use crate::entities::GridLayoutType;
use crate::entities::{
GridLayout, GridLayoutType, GridSetting, RepeatedGridFilter, RepeatedGridGroup, RepeatedGridSort,
};
use flowy_grid_data_model::revision::{FieldRevision, GridSettingRevision};
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
use std::collections::HashMap;
use std::sync::Arc;
pub struct GridSettingChangesetBuilder {
params: GridSettingChangesetParams,
@ -34,3 +39,42 @@ impl GridSettingChangesetBuilder {
self.params
}
}
pub fn make_grid_setting(grid_setting_rev: &GridSettingRevision, field_revs: &[Arc<FieldRevision>]) -> GridSetting {
let current_layout_type: GridLayoutType = grid_setting_rev.layout.clone().into();
let filters_by_field_id = grid_setting_rev
.get_all_filter(field_revs)
.map(|filters_by_field_id| {
filters_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridFilter>>()
})
.unwrap_or_default();
let groups_by_field_id = grid_setting_rev
.get_all_group()
.map(|groups_by_field_id| {
groups_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridGroup>>()
})
.unwrap_or_default();
let sorts_by_field_id = grid_setting_rev
.get_all_sort()
.map(|sorts_by_field_id| {
sorts_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridSort>>()
})
.unwrap_or_default();
GridSetting {
layouts: GridLayout::all(),
current_layout_type,
filters_by_field_id,
groups_by_field_id,
sorts_by_field_id,
}
}

View File

@ -0,0 +1,2 @@
mod script;
mod text_filter_test;

View File

@ -0,0 +1,91 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
use flowy_grid::entities::{CreateGridFilterPayload, GridLayoutType, GridSetting};
use flowy_grid::services::setting::GridSettingChangesetBuilder;
use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
use crate::grid::script::GridEditorTest;
pub enum FilterScript {
#[allow(dead_code)]
UpdateGridSetting {
params: GridSettingChangesetParams,
},
InsertGridTableFilter {
payload: CreateGridFilterPayload,
},
AssertTableFilterCount {
count: i32,
},
DeleteGridTableFilter {
filter_id: String,
field_rev: FieldRevision,
},
#[allow(dead_code)]
AssertGridSetting {
expected_setting: GridSetting,
},
}
pub struct GridFilterTest {
pub editor_test: GridEditorTest,
}
impl GridFilterTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new().await;
Self {
editor_test
}
}
pub async fn run_scripts(&mut self, scripts: Vec<FilterScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn run_script(&mut self, script: FilterScript) {
match script {
FilterScript::UpdateGridSetting { params } => {
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
FilterScript::InsertGridTableFilter { payload } => {
let params: CreateGridFilterParams = payload.try_into().unwrap();
let layout_type = GridLayoutType::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.insert_filter(params)
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
FilterScript::AssertTableFilterCount { count } => {
let layout_type = GridLayoutType::Table;
let filters = self.editor.get_grid_filter(&layout_type).await.unwrap();
assert_eq!(count as usize, filters.len());
}
FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
let layout_type = GridLayoutType::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.field_type_rev })
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
FilterScript::AssertGridSetting { expected_setting } => {
let setting = self.editor.get_grid_setting().await.unwrap();
assert_eq!(expected_setting, setting);
}
}
}
}
impl std::ops::Deref for GridFilterTest {
type Target = GridEditorTest;
fn deref(&self) -> &Self::Target {
&self.editor_test
}
}

View File

@ -1,33 +1,34 @@
use crate::grid::script::EditorScript::*;
use crate::grid::script::*;
use flowy_grid::entities::CreateGridFilterPayload;
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::*;
use flowy_grid::entities::{CreateGridFilterPayload, TextFilterCondition};
use flowy_grid_data_model::revision::FieldRevision;
#[tokio::test]
async fn grid_filter_create_test() {
let test = GridEditorTest::new().await;
let mut test = GridFilterTest::new().await;
let field_rev = test.text_field();
let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
GridEditorTest::new().await.run_scripts(scripts).await;
test.run_scripts(scripts).await;
}
#[tokio::test]
#[should_panic]
async fn grid_filter_invalid_condition_panic_test() {
let test = GridEditorTest::new().await;
let field_rev = test.text_field();
let mut test = GridFilterTest::new().await;
let field_rev = test.text_field().clone();
// 100 is not a valid condition, so this test should be panic.
let payload = CreateGridFilterPayload::new(field_rev, 100, Some("abc".to_owned()));
let payload = CreateGridFilterPayload::new(&field_rev, 100, Some("".to_owned()));
let scripts = vec![InsertGridTableFilter { payload }];
GridEditorTest::new().await.run_scripts(scripts).await;
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_delete_test() {
let mut test = GridEditorTest::new().await;
let mut test = GridFilterTest::new().await;
let field_rev = test.text_field().clone();
let payload = CreateGridFilterPayload::new(&field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc");
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
test.run_scripts(scripts).await;
@ -35,7 +36,7 @@ async fn grid_filter_delete_test() {
test.run_scripts(vec![
DeleteGridTableFilter {
filter_id: filter.id,
field_type: field_rev.field_type.clone(),
field_rev,
},
AssertTableFilterCount { count: 0 },
])
@ -44,3 +45,7 @@ async fn grid_filter_delete_test() {
#[tokio::test]
async fn grid_filter_get_rows_test() {}
fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> CreateGridFilterPayload {
CreateGridFilterPayload::new(field_rev, condition, Some(s.to_owned()))
}

View File

@ -2,7 +2,7 @@ mod block_test;
mod cell_test;
mod field_test;
mod field_util;
// mod filter_test;
mod filter_test;
mod row_test;
mod row_util;
mod script;

View File

@ -1,16 +1,19 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
use bytes::Bytes;
use flowy_grid::entities::*;
use flowy_grid::services::field::select_option::SelectOption;
use flowy_grid::services::field::*;
use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
use flowy_grid::services::row::CreateRowRevisionPayload;
use flowy_grid::services::setting::GridSettingChangesetBuilder;
use flowy_grid::entities::*;
use flowy_grid_data_model::revision::*;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_sync::client_grid::GridBuilder;
use flowy_sync::entities::grid::{
CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams,
};
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use std::collections::HashMap;
@ -18,8 +21,6 @@ use std::sync::Arc;
use std::time::Duration;
use strum::EnumCount;
use tokio::time::sleep;
use flowy_grid::services::field::select_option::SelectOption;
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams};
pub enum EditorScript {
CreateField {
@ -82,7 +83,7 @@ pub enum EditorScript {
},
DeleteGridTableFilter {
filter_id: String,
field_type: FieldType,
field_rev: FieldRevision,
},
#[allow(dead_code)]
AssertGridSetting {
@ -170,10 +171,7 @@ impl GridEditorTest {
assert_eq!(self.field_count, self.field_revs.len());
}
EditorScript::AssertFieldCount(count) => {
assert_eq!(
self.editor.get_field_revs(None).await.unwrap().len(),
count
);
assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count);
}
EditorScript::AssertFieldEqual { field_index, field_rev } => {
let field_revs = self.editor.get_field_revs(None).await.unwrap();
@ -204,14 +202,16 @@ impl GridEditorTest {
}
EditorScript::CreateEmptyRow => {
let row_order = self.editor.create_row(None).await.unwrap();
self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order);
self.row_order_by_row_id
.insert(row_order.row_id().to_owned(), row_order);
self.row_revs = self.get_row_revs().await;
self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
}
EditorScript::CreateRow { payload: context } => {
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
for row_order in row_orders {
self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order);
self.row_order_by_row_id
.insert(row_order.row_id().to_owned(), row_order);
}
self.row_revs = self.get_row_revs().await;
self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
@ -271,10 +271,14 @@ impl GridEditorTest {
let filters = self.editor.get_grid_filter(&layout_type).await.unwrap();
assert_eq!(count as usize, filters.len());
}
EditorScript::DeleteGridTableFilter { filter_id ,field_type} => {
EditorScript::DeleteGridTableFilter { filter_id, field_rev } => {
let layout_type = GridLayoutType::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.delete_filter(DeleteFilterParams { filter_id, field_type_rev: field_type.into() })
.delete_filter(DeleteFilterParams {
field_id: field_rev.id,
filter_id,
field_type_rev: field_rev.field_type_rev,
})
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
}

View File

@ -5,7 +5,7 @@ pub enum PayloadError {}
// TODO: support stream data
#[derive(Clone)]
#[cfg_attr(feature = "user_serde", derive(serde::Serialize))]
#[cfg_attr(feature = "use_serde", derive(serde::Serialize))]
pub enum Payload {
None,
Bytes(Bytes),

View File

@ -9,7 +9,7 @@ use derivative::*;
use std::{convert::TryFrom, fmt, fmt::Formatter};
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "user_serde", derive(serde::Serialize))]
#[cfg_attr(feature = "use_serde", derive(serde::Serialize))]
pub enum StatusCode {
Ok = 0,
Err = 1,
@ -18,7 +18,7 @@ pub enum StatusCode {
// serde user guide: https://serde.rs/field-attrs.html
#[derive(Debug, Clone, Derivative)]
#[cfg_attr(feature = "user_serde", derive(serde::Serialize))]
#[cfg_attr(feature = "use_serde", derive(serde::Serialize))]
pub struct EventResponse {
#[derivative(Debug = "ignore")]
pub payload: Payload,

View File

@ -31,6 +31,11 @@ pub struct GridRevision {
pub fields: Vec<Arc<FieldRevision>>,
pub blocks: Vec<Arc<GridBlockMetaRevision>>,
#[cfg(feature = "filter")]
#[serde(default)]
pub setting: GridSettingRevision,
#[cfg(not(feature = "filter"))]
#[serde(default, skip)]
pub setting: GridSettingRevision,
}

View File

@ -1,8 +1,9 @@
use crate::revision::FieldTypeRevision;
use crate::revision::{FieldRevision, FieldTypeRevision};
use indexmap::IndexMap;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use serde_repr::*;
use std::collections::HashMap;
use std::sync::Arc;
pub fn gen_grid_filter_id() -> String {
@ -17,20 +18,33 @@ pub fn gen_grid_sort_id() -> String {
nanoid!(6)
}
/// Each layout contains multiple key/value.
/// Key: field_id
/// Value: this value also contains key/value.
/// Key: FieldType,
/// Value: the corresponding filter.
///
/// This overall struct is described below:
/// GridSettingRevision
/// layout:
/// field_id:
/// FieldType: GridFilterRevision
/// FieldType: GridFilterRevision
/// field_id:
/// FieldType: GridFilterRevision
/// FieldType: GridFilterRevision
/// layout:
/// field_id:
/// FieldType: GridFilterRevision
/// FieldType: GridFilterRevision
///
/// Group and sorts will be the same structure as filters.
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
pub struct GridSettingRevision {
pub layout: GridLayoutRevision,
// layout:
// field_id:
// FieldType: GridFilterRevision
// FieldType: GridFilterRevision
// layout:
// field_id:
// FieldType: GridFilterRevision
// field_id:
// FieldType: GridFilterRevision
#[serde(with = "indexmap::serde_seq")]
pub filters: IndexMap<GridLayoutRevision, IndexMap<String, GridFilterRevisionMap>>,
filters: IndexMap<GridLayoutRevision, IndexMap<String, GridFilterRevisionMap>>,
#[serde(skip, with = "indexmap::serde_seq")]
pub groups: IndexMap<GridLayoutRevision, Vec<GridGroupRevision>>,
@ -39,7 +53,44 @@ pub struct GridSettingRevision {
pub sorts: IndexMap<GridLayoutRevision, Vec<GridSortRevision>>,
}
pub type FiltersByFieldId = HashMap<String, Vec<Arc<GridFilterRevision>>>;
pub type GroupsByFieldId = HashMap<String, Vec<Arc<GridGroupRevision>>>;
pub type SortsByFieldId = HashMap<String, Vec<Arc<GridSortRevision>>>;
impl GridSettingRevision {
pub fn get_all_group(&self) -> Option<GroupsByFieldId> {
None
}
pub fn get_all_sort(&self) -> Option<SortsByFieldId> {
None
}
/// Return the Filters of the current layout
pub fn get_all_filter(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FiltersByFieldId> {
let layout = &self.layout;
// Acquire the read lock of the filters.
let filter_rev_map_by_field_id = self.filters.get(layout)?;
// Get the filters according to the FieldType, so we need iterate the field_revs.
let filters_by_field_id = field_revs
.iter()
.flat_map(|field_rev| {
let field_type = &field_rev.field_type_rev;
let field_id = &field_rev.id;
let filter_rev_map: &GridFilterRevisionMap = filter_rev_map_by_field_id.get(field_id)?;
let filters: Vec<Arc<GridFilterRevision>> = filter_rev_map.get(field_type)?.clone();
Some((field_rev.id.clone(), filters))
})
.collect::<FiltersByFieldId>();
Some(filters_by_field_id)
}
#[allow(dead_code)]
fn get_filter_rev_map(&self, layout: &GridLayoutRevision, field_id: &str) -> Option<&GridFilterRevisionMap> {
let filter_rev_map_by_field_id = self.filters.get(layout)?;
filter_rev_map_by_field_id.get(field_id)
}
pub fn get_mut_filters(
&mut self,
layout: &GridLayoutRevision,
@ -56,12 +107,12 @@ impl GridSettingRevision {
&self,
layout: &GridLayoutRevision,
field_id: &str,
field_type: &FieldTypeRevision,
field_type_rev: &FieldTypeRevision,
) -> Option<Vec<Arc<GridFilterRevision>>> {
self.filters
.get(layout)
.and_then(|filter_rev_map_by_field_id| filter_rev_map_by_field_id.get(field_id))
.and_then(|filter_rev_map| filter_rev_map.get(field_type))
.and_then(|filter_rev_map| filter_rev_map.get(field_type_rev))
.cloned()
}

View File

@ -17,8 +17,8 @@ pub type GridRevisionDelta = PlainTextDelta;
pub type GridRevisionDeltaBuilder = PlainTextDeltaBuilder;
pub struct GridRevisionPad {
pub(crate) grid_rev: Arc<GridRevision>,
pub(crate) delta: GridRevisionDelta,
grid_rev: Arc<GridRevision>,
delta: GridRevisionDelta,
}
pub trait JsonDeserializer {
@ -358,10 +358,9 @@ impl GridRevisionPad {
if is_contain {
// Only return the filters for the current fields' type.
if let Some(mut t_filter_revs) =
self.grid_rev
.setting
.get_filters(layout_ty, &field_rev.id, &field_rev.field_type_rev)
let field_id = &field_rev.id;
let field_type_rev = &field_rev.field_type_rev;
if let Some(mut t_filter_revs) = self.grid_rev.setting.get_filters(layout_ty, field_id, field_type_rev)
{
filter_revs.append(&mut t_filter_revs);
}
@ -396,7 +395,7 @@ impl GridRevisionPad {
if let Some(params) = changeset.delete_filter {
match grid_rev
.setting
.get_mut_filters(&layout_rev, &params.filter_id, &params.field_type_rev)
.get_mut_filters(&layout_rev, &params.field_id, &params.field_type_rev)
{
Some(filters) => {
filters.retain(|filter| filter.id != params.filter_id);

View File

@ -24,6 +24,7 @@ pub struct CreateGridFilterParams {
}
pub struct DeleteFilterParams {
pub field_id: String,
pub filter_id: String,
pub field_type_rev: FieldTypeRevision,
}

View File

@ -114,12 +114,12 @@ fn generate_dart_protobuf_files(
check_pb_dart_plugin();
let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned();
paths.iter().for_each(|path| {
if cmd_lib::run_cmd! {
let result = cmd_lib::run_cmd! {
${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path}
}
.is_err()
{
panic!("Generate dart pb file failed with: {}", path)
};
if result.is_err() {
panic!("Generate dart pb file failed with: {}, {:?}", path, result)
};
});