feat: advanced filters backend logic (#4688)

* feat: implement advanced filters

* test: adapt tests to changes

* test: add advanced filter tests

* chore: adapt flutter frontend to changes

* chore: adapt tauri frontend to changes

* chore: bump collab

* chore: launch review

---------

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Richard Shiue
2024-03-14 09:35:45 +08:00
committed by GitHub
parent 80e210b34a
commit 48cac4c5ac
50 changed files with 1915 additions and 1514 deletions

View File

@ -0,0 +1,314 @@
use bytes::Bytes;
use flowy_database2::entities::{
CheckboxFilterConditionPB, CheckboxFilterPB, DateFilterConditionPB, DateFilterPB, FieldType,
FilterDataPB, FilterPB, FilterType, NumberFilterConditionPB, NumberFilterPB,
};
use lib_infra::box_any::BoxAny;
use protobuf::ProtobufError;
use std::convert::TryInto;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged, FilterScript::*};
/// Create a single advanced filter:
///
/// 1. Add an OR filter
/// 2. Add a Checkbox and an AND filter to its children
/// 3. Add a DateTime and a Number filter to the AND filter's children
///
#[tokio::test]
async fn create_advanced_filter_test() {
let mut test = DatabaseFilterTest::new().await;
let create_checkbox_filter = || -> CheckboxFilterPB {
CheckboxFilterPB {
condition: CheckboxFilterConditionPB::IsChecked,
}
};
let create_date_filter = || -> DateFilterPB {
DateFilterPB {
condition: DateFilterConditionPB::DateAfter,
timestamp: Some(1651366800),
..Default::default()
}
};
let create_number_filter = || -> NumberFilterPB {
NumberFilterPB {
condition: NumberFilterConditionPB::NumberIsNotEmpty,
content: "".to_string(),
}
};
let scripts = vec![
CreateOrFilter {
parent_filter_id: None,
changed: None,
},
Wait { millisecond: 100 },
AssertFilters {
expected: vec![FilterPB {
id: "".to_string(),
filter_type: FilterType::Or,
children: vec![],
data: None,
}],
},
];
test.run_scripts(scripts).await;
// OR
let or_filter = test.get_filter(FilterType::Or, None).await.unwrap();
let checkbox_filter_bytes: Result<Bytes, ProtobufError> = create_checkbox_filter().try_into();
let checkbox_filter_bytes = checkbox_filter_bytes.unwrap().to_vec();
let scripts = vec![
CreateDataFilter {
parent_filter_id: Some(or_filter.id.clone()),
field_type: FieldType::Checkbox,
data: BoxAny::new(create_checkbox_filter()),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 4,
}),
},
CreateAndFilter {
parent_filter_id: Some(or_filter.id),
changed: None,
},
Wait { millisecond: 100 },
AssertFilters {
expected: vec![FilterPB {
id: "".to_string(),
filter_type: FilterType::Or,
children: vec![
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::Checkbox,
data: checkbox_filter_bytes.clone(),
}),
},
FilterPB {
id: "".to_string(),
filter_type: FilterType::And,
children: vec![],
data: None,
},
],
data: None,
}],
},
AssertNumberOfVisibleRows { expected: 3 },
];
test.run_scripts(scripts).await;
// IS_CHECK OR AND
let and_filter = test.get_filter(FilterType::And, None).await.unwrap();
let date_filter_bytes: Result<Bytes, ProtobufError> = create_date_filter().try_into();
let date_filter_bytes = date_filter_bytes.unwrap().to_vec();
let number_filter_bytes: Result<Bytes, ProtobufError> = create_number_filter().try_into();
let number_filter_bytes = number_filter_bytes.unwrap().to_vec();
let scripts = vec![
CreateDataFilter {
parent_filter_id: Some(and_filter.id.clone()),
field_type: FieldType::DateTime,
data: BoxAny::new(create_date_filter()),
changed: None,
},
CreateDataFilter {
parent_filter_id: Some(and_filter.id),
field_type: FieldType::Number,
data: BoxAny::new(create_number_filter()),
changed: None,
},
Wait { millisecond: 100 },
AssertFilters {
expected: vec![FilterPB {
id: "".to_string(),
filter_type: FilterType::Or,
children: vec![
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::Checkbox,
data: checkbox_filter_bytes,
}),
},
FilterPB {
id: "".to_string(),
filter_type: FilterType::And,
children: vec![
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::DateTime,
data: date_filter_bytes,
}),
},
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::Number,
data: number_filter_bytes,
}),
},
],
data: None,
},
],
data: None,
}],
},
AssertNumberOfVisibleRows { expected: 4 },
];
test.run_scripts(scripts).await;
// IS_CHECK OR (DATE > 1651366800 AND NUMBER NOT EMPTY)
}
/// Create the same advanced filter single advanced filter:
///
/// 1. Add an OR filter
/// 2. Add a Checkbox and a DateTime filter to its children
/// 3. Add a Number filter to the DateTime filter's children
///
#[tokio::test]
async fn create_advanced_filter_with_conversion_test() {
let mut test = DatabaseFilterTest::new().await;
let create_checkbox_filter = || -> CheckboxFilterPB {
CheckboxFilterPB {
condition: CheckboxFilterConditionPB::IsChecked,
}
};
let create_date_filter = || -> DateFilterPB {
DateFilterPB {
condition: DateFilterConditionPB::DateAfter,
timestamp: Some(1651366800),
..Default::default()
}
};
let create_number_filter = || -> NumberFilterPB {
NumberFilterPB {
condition: NumberFilterConditionPB::NumberIsNotEmpty,
content: "".to_string(),
}
};
let scripts = vec![CreateOrFilter {
parent_filter_id: None,
changed: None,
}];
test.run_scripts(scripts).await;
// IS_CHECK OR DATE > 1651366800
let or_filter = test.get_filter(FilterType::Or, None).await.unwrap();
let scripts = vec![
CreateDataFilter {
parent_filter_id: Some(or_filter.id.clone()),
field_type: FieldType::Checkbox,
data: BoxAny::new(create_checkbox_filter()),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 4,
}),
},
CreateDataFilter {
parent_filter_id: Some(or_filter.id.clone()),
field_type: FieldType::DateTime,
data: BoxAny::new(create_date_filter()),
changed: None,
},
];
test.run_scripts(scripts).await;
// OR
let date_filter = test
.get_filter(FilterType::Data, Some(FieldType::DateTime))
.await
.unwrap();
let checkbox_filter_bytes: Result<Bytes, ProtobufError> = create_checkbox_filter().try_into();
let checkbox_filter_bytes = checkbox_filter_bytes.unwrap().to_vec();
let date_filter_bytes: Result<Bytes, ProtobufError> = create_date_filter().try_into();
let date_filter_bytes = date_filter_bytes.unwrap().to_vec();
let number_filter_bytes: Result<Bytes, ProtobufError> = create_number_filter().try_into();
let number_filter_bytes = number_filter_bytes.unwrap().to_vec();
let scripts = vec![
CreateDataFilter {
parent_filter_id: Some(date_filter.id),
field_type: FieldType::Number,
data: BoxAny::new(create_number_filter()),
changed: None,
},
Wait { millisecond: 100 },
AssertFilters {
expected: vec![FilterPB {
id: "".to_string(),
filter_type: FilterType::Or,
children: vec![
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::Checkbox,
data: checkbox_filter_bytes,
}),
},
FilterPB {
id: "".to_string(),
filter_type: FilterType::And,
children: vec![
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::DateTime,
data: date_filter_bytes,
}),
},
FilterPB {
id: "".to_string(),
filter_type: FilterType::Data,
children: vec![],
data: Some(FilterDataPB {
field_id: "".to_string(),
field_type: FieldType::Number,
data: number_filter_bytes,
}),
},
],
data: None,
},
],
data: None,
}],
},
AssertNumberOfVisibleRows { expected: 4 },
];
test.run_scripts(scripts).await;
// IS_CHECK OR (DATE > 1651366800 AND NUMBER NOT EMPTY)
}

View File

@ -1,4 +1,5 @@
use flowy_database2::entities::CheckboxFilterConditionPB;
use flowy_database2::entities::{CheckboxFilterConditionPB, CheckboxFilterPB, FieldType};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
@ -6,27 +7,39 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}
#[tokio::test]
async fn grid_filter_checkbox_is_check_test() {
let mut test = DatabaseFilterTest::new().await;
let expected = 3;
let row_count = test.row_details.len();
// The initial number of unchecked is 3
// The initial number of checked is 2
let scripts = vec![CreateCheckboxFilter {
condition: CheckboxFilterConditionPB::IsChecked,
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - 3,
}),
}];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_checkbox_is_uncheck_test() {
let mut test = DatabaseFilterTest::new().await;
let expected = 4;
let row_count = test.row_details.len();
// The initial number of checked is 3
// The initial number of unchecked is 4
let scripts = vec![
CreateCheckboxFilter {
condition: CheckboxFilterConditionPB::IsUnChecked,
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Checkbox,
data: BoxAny::new(CheckboxFilterPB {
condition: CheckboxFilterConditionPB::IsChecked,
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
}),
},
AssertNumberOfVisibleRows { expected },
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_checkbox_is_uncheck_test() {
let mut test = DatabaseFilterTest::new().await;
let expected = 4;
let row_count = test.row_details.len();
let scripts = vec![
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Checkbox,
data: BoxAny::new(CheckboxFilterPB {
condition: CheckboxFilterConditionPB::IsUnChecked,
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,

View File

@ -1,5 +1,6 @@
use flowy_database2::entities::{ChecklistFilterConditionPB, FieldType};
use flowy_database2::entities::{ChecklistFilterConditionPB, ChecklistFilterPB, FieldType};
use flowy_database2::services::field::checklist_type_option::ChecklistCellData;
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
@ -16,8 +17,12 @@ async fn grid_filter_checklist_is_incomplete_test() {
row_id: test.row_details[0].row.id.clone(),
selected_option_ids: option_ids,
},
CreateChecklistFilter {
condition: ChecklistFilterConditionPB::IsIncomplete,
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Checklist,
data: BoxAny::new(ChecklistFilterPB {
condition: ChecklistFilterConditionPB::IsIncomplete,
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -39,8 +44,12 @@ async fn grid_filter_checklist_is_complete_test() {
row_id: test.row_details[0].row.id.clone(),
selected_option_ids: option_ids,
},
CreateChecklistFilter {
condition: ChecklistFilterConditionPB::IsComplete,
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Checklist,
data: BoxAny::new(ChecklistFilterPB {
condition: ChecklistFilterConditionPB::IsComplete,
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,

View File

@ -1,4 +1,5 @@
use flowy_database2::entities::DateFilterConditionPB;
use flowy_database2::entities::{DateFilterConditionPB, DateFilterPB, FieldType};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
@ -9,11 +10,15 @@ async fn grid_filter_date_is_test() {
let row_count = test.row_details.len();
let expected = 3;
let scripts = vec![
CreateDateFilter {
condition: DateFilterConditionPB::DateIs,
start: None,
end: None,
timestamp: Some(1647251762),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::DateTime,
data: BoxAny::new(DateFilterPB {
condition: DateFilterConditionPB::DateIs,
start: None,
end: None,
timestamp: Some(1647251762),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -30,11 +35,15 @@ async fn grid_filter_date_after_test() {
let row_count = test.row_details.len();
let expected = 3;
let scripts = vec![
CreateDateFilter {
condition: DateFilterConditionPB::DateAfter,
start: None,
end: None,
timestamp: Some(1647251762),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::DateTime,
data: BoxAny::new(DateFilterPB {
condition: DateFilterConditionPB::DateAfter,
start: None,
end: None,
timestamp: Some(1647251762),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -51,11 +60,15 @@ async fn grid_filter_date_on_or_after_test() {
let row_count = test.row_details.len();
let expected = 3;
let scripts = vec![
CreateDateFilter {
condition: DateFilterConditionPB::DateOnOrAfter,
start: None,
end: None,
timestamp: Some(1668359085),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::DateTime,
data: BoxAny::new(DateFilterPB {
condition: DateFilterConditionPB::DateOnOrAfter,
start: None,
end: None,
timestamp: Some(1668359085),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -72,11 +85,15 @@ async fn grid_filter_date_on_or_before_test() {
let row_count = test.row_details.len();
let expected = 4;
let scripts = vec![
CreateDateFilter {
condition: DateFilterConditionPB::DateOnOrBefore,
start: None,
end: None,
timestamp: Some(1668359085),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::DateTime,
data: BoxAny::new(DateFilterPB {
condition: DateFilterConditionPB::DateOnOrBefore,
start: None,
end: None,
timestamp: Some(1668359085),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -93,11 +110,15 @@ async fn grid_filter_date_within_test() {
let row_count = test.row_details.len();
let expected = 5;
let scripts = vec![
CreateDateFilter {
condition: DateFilterConditionPB::DateWithIn,
start: Some(1647251762),
end: Some(1668704685),
timestamp: None,
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::DateTime,
data: BoxAny::new(DateFilterPB {
condition: DateFilterConditionPB::DateWithIn,
start: Some(1647251762),
end: Some(1668704685),
timestamp: None,
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,

View File

@ -1,3 +1,4 @@
mod advanced_filter_test;
mod checkbox_filter_test;
mod checklist_filter_test;
mod date_filter_test;

View File

@ -1,4 +1,5 @@
use flowy_database2::entities::NumberFilterConditionPB;
use flowy_database2::entities::{FieldType, NumberFilterConditionPB, NumberFilterPB};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
@ -9,9 +10,13 @@ async fn grid_filter_number_is_equal_test() {
let row_count = test.row_details.len();
let expected = 1;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::Equal,
content: "1".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::Equal,
content: "1".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -28,9 +33,13 @@ async fn grid_filter_number_is_less_than_test() {
let row_count = test.row_details.len();
let expected = 2;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::LessThan,
content: "3".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::LessThan,
content: "3".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -48,9 +57,13 @@ async fn grid_filter_number_is_less_than_test2() {
let row_count = test.row_details.len();
let expected = 2;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::LessThan,
content: "$3".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::LessThan,
content: "$3".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -67,9 +80,13 @@ async fn grid_filter_number_is_less_than_or_equal_test() {
let row_count = test.row_details.len();
let expected = 3;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::LessThanOrEqualTo,
content: "3".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::LessThanOrEqualTo,
content: "3".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -86,9 +103,13 @@ async fn grid_filter_number_is_empty_test() {
let row_count = test.row_details.len();
let expected = 2;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::NumberIsEmpty,
content: "".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::NumberIsEmpty,
content: "".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -105,9 +126,13 @@ async fn grid_filter_number_is_not_empty_test() {
let row_count = test.row_details.len();
let expected = 5;
let scripts = vec![
CreateNumberFilter {
condition: NumberFilterConditionPB::NumberIsNotEmpty,
content: "".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::Number,
data: BoxAny::new(NumberFilterPB {
condition: NumberFilterConditionPB::NumberIsNotEmpty,
content: "".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,

View File

@ -3,14 +3,12 @@
use std::time::Duration;
use collab_database::rows::RowId;
use flowy_database2::services::filter::FilterContext;
use flowy_database2::services::filter::{FilterChangeset, FilterInner};
use lib_infra::box_any::BoxAny;
use tokio::sync::broadcast::Receiver;
use flowy_database2::entities::{
CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB,
DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterPayloadPB, FieldType,
FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB,
TextFilterConditionPB, TextFilterPB, UpdateFilterParams, UpdateFilterPayloadPB,
DatabaseViewSettingPB, FieldType, FilterPB, FilterType, TextFilterConditionPB, TextFilterPB,
};
use flowy_database2::services::database_view::DatabaseViewChanged;
use lib_dispatch::prelude::af_spawn;
@ -37,12 +35,10 @@ pub enum FilterScript {
option_id: String,
changed: Option<FilterRowChanged>,
},
InsertFilter {
payload: UpdateFilterPayloadPB,
},
CreateTextFilter {
condition: TextFilterConditionPB,
content: String,
CreateDataFilter {
parent_filter_id: Option<String>,
field_type: FieldType,
data: BoxAny,
changed: Option<FilterRowChanged>,
},
UpdateTextFilter {
@ -51,50 +47,36 @@ pub enum FilterScript {
content: String,
changed: Option<FilterRowChanged>,
},
CreateNumberFilter {
condition: NumberFilterConditionPB,
content: String,
CreateAndFilter {
parent_filter_id: Option<String>,
changed: Option<FilterRowChanged>,
},
CreateCheckboxFilter {
condition: CheckboxFilterConditionPB,
CreateOrFilter {
parent_filter_id: Option<String>,
changed: Option<FilterRowChanged>,
},
CreateDateFilter {
condition: DateFilterConditionPB,
start: Option<i64>,
end: Option<i64>,
timestamp: Option<i64>,
changed: Option<FilterRowChanged>,
},
CreateMultiSelectFilter {
condition: SelectOptionConditionPB,
option_ids: Vec<String>,
},
CreateSingleSelectFilter {
condition: SelectOptionConditionPB,
option_ids: Vec<String>,
changed: Option<FilterRowChanged>,
},
CreateChecklistFilter {
condition: ChecklistFilterConditionPB,
changed: Option<FilterRowChanged>,
},
AssertFilterCount {
count: i32,
},
DeleteFilter {
filter_context: FilterContext,
filter_id: String,
field_id: String,
changed: Option<FilterRowChanged>,
},
AssertFilterContent {
filter_id: String,
condition: i64,
content: String,
// CreateSimpleAdvancedFilter,
// CreateComplexAdvancedFilter,
AssertFilterCount {
count: usize,
},
AssertNumberOfVisibleRows {
expected: usize,
},
AssertFilters {
/// 1. assert that the filter type is correct
/// 2. if the filter is data, assert that the field_type, condition and content are correct
/// (no field_id)
/// 3. if the filter is and/or, assert that each child is correct as well.
expected: Vec<FilterPB>,
},
// AssertSimpleAdvancedFilter,
// AssertComplexAdvancedFilterResult,
#[allow(dead_code)]
AssertGridSetting {
expected_setting: DatabaseViewSettingPB,
@ -118,14 +100,54 @@ impl DatabaseFilterTest {
}
}
pub fn view_id(&self) -> String {
self.view_id.clone()
}
pub async fn get_all_filters(&self) -> Vec<FilterPB> {
self.editor.get_all_filters(&self.view_id).await.items
}
pub async fn get_filter(
&self,
filter_type: FilterType,
field_type: Option<FieldType>,
) -> Option<FilterPB> {
let filters = self.inner.editor.get_all_filters(&self.view_id).await;
for filter in filters.items.iter() {
let result = Self::find_filter(filter, filter_type, field_type);
if result.is_some() {
return result;
}
}
None
}
fn find_filter(
filter: &FilterPB,
filter_type: FilterType,
field_type: Option<FieldType>,
) -> Option<FilterPB> {
match &filter.filter_type {
FilterType::And | FilterType::Or if filter.filter_type == filter_type => Some(filter.clone()),
FilterType::And | FilterType::Or => {
for child_filter in filter.children.iter() {
if let Some(result) = Self::find_filter(child_filter, filter_type, field_type) {
return Some(result);
}
}
None
},
FilterType::Data
if filter.filter_type == filter_type
&& field_type.map_or(false, |field_type| {
field_type == filter.data.clone().unwrap().field_type
}) =>
{
Some(filter.clone())
},
_ => None,
}
}
pub async fn run_scripts(&mut self, scripts: Vec<FilterScript>) {
for script in scripts {
self.run_script(script).await;
@ -139,13 +161,7 @@ impl DatabaseFilterTest {
text,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
self.update_text_cell(row_id, &text).await.unwrap();
},
@ -163,46 +179,35 @@ impl DatabaseFilterTest {
option_id,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
self
.update_single_select_cell(row_id, &option_id)
.await
.unwrap();
},
FilterScript::InsertFilter { payload } => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.insert_filter(payload).await;
},
FilterScript::CreateTextFilter {
condition,
content,
FilterScript::CreateDataFilter {
parent_filter_id,
field_type,
data,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::RichText);
let text_filter = TextFilterPB { condition, content };
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, text_filter);
self.insert_filter(payload).await;
let field = self.get_first_field(field_type);
let params = FilterChangeset::Insert {
parent_filter_id,
data: FilterInner::Data {
field_id: field.id,
field_type,
condition_and_content: data,
},
};
self
.editor
.modify_view_filters(&self.view_id, params)
.await
.unwrap();
},
FilterScript::UpdateTextFilter {
filter,
@ -210,172 +215,76 @@ impl DatabaseFilterTest {
content,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.assert_future_changed(changed).await;
let params = UpdateFilterParams {
view_id: self.view_id(),
field_id: filter.field_id,
filter_id: Some(filter.id),
field_type: filter.field_type,
condition: condition as i64,
content,
};
self.editor.create_or_update_filter(params).await.unwrap();
},
FilterScript::CreateNumberFilter {
condition,
content,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::Number);
let number_filter = NumberFilterPB { condition, content };
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, number_filter);
self.insert_filter(payload).await;
},
FilterScript::CreateCheckboxFilter { condition, changed } => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::Checkbox);
let checkbox_filter = CheckboxFilterPB { condition };
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, checkbox_filter);
self.insert_filter(payload).await;
},
FilterScript::CreateDateFilter {
condition,
start,
end,
timestamp,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::DateTime);
let date_filter = DateFilterPB {
condition,
start,
end,
timestamp,
};
self.subscribe_view_changed().await;
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, date_filter);
self.insert_filter(payload).await;
},
FilterScript::CreateMultiSelectFilter {
condition,
option_ids,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
let field = self.get_first_field(FieldType::MultiSelect);
let filter = SelectOptionFilterPB {
condition,
option_ids,
self.assert_future_changed(changed).await;
let current_filter = filter.data.unwrap();
let params = FilterChangeset::UpdateData {
filter_id: filter.id,
data: FilterInner::Data {
field_id: current_filter.field_id,
field_type: current_filter.field_type,
condition_and_content: BoxAny::new(TextFilterPB { condition, content }),
},
};
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
self.insert_filter(payload).await;
self
.editor
.modify_view_filters(&self.view_id, params)
.await
.unwrap();
},
FilterScript::CreateSingleSelectFilter {
condition,
option_ids,
FilterScript::CreateAndFilter {
parent_filter_id,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::SingleSelect);
let filter = SelectOptionFilterPB {
condition,
option_ids,
let params = FilterChangeset::Insert {
parent_filter_id,
data: FilterInner::And { children: vec![] },
};
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
self.insert_filter(payload).await;
self
.editor
.modify_view_filters(&self.view_id, params)
.await
.unwrap();
},
FilterScript::CreateChecklistFilter { condition, changed } => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
FilterScript::CreateOrFilter {
parent_filter_id,
changed,
} => {
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
let field = self.get_first_field(FieldType::Checklist);
let filter = ChecklistFilterPB { condition };
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
self.insert_filter(payload).await;
let params = FilterChangeset::Insert {
parent_filter_id,
data: FilterInner::Or { children: vec![] },
};
self
.editor
.modify_view_filters(&self.view_id, params)
.await
.unwrap();
},
FilterScript::AssertFilterCount { count } => {
let filters = self.editor.get_all_filters(&self.view_id).await.items;
assert_eq!(count as usize, filters.len());
},
FilterScript::AssertFilterContent {
filter_id,
condition,
content,
} => {
let filter = self
.editor
.get_filter(&self.view_id, &filter_id)
.await
.unwrap();
assert_eq!(&filter.content, &content);
assert_eq!(filter.condition, condition);
assert_eq!(count, filters.len());
},
FilterScript::DeleteFilter {
filter_context,
filter_id,
field_id,
changed,
} => {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id())
.await
.unwrap(),
);
self.subscribe_view_changed().await;
self.assert_future_changed(changed).await;
let params = DeleteFilterPayloadPB {
filter_id: filter_context.filter_id,
view_id: self.view_id(),
field_id: filter_context.field_id,
field_type: filter_context.field_type,
let params = FilterChangeset::Delete {
filter_id,
field_id,
};
self.editor.delete_filter(params).await.unwrap();
self
.editor
.modify_view_filters(&self.view_id, params)
.await
.unwrap();
},
FilterScript::AssertGridSetting { expected_setting } => {
let setting = self
@ -385,6 +294,12 @@ impl DatabaseFilterTest {
.unwrap();
assert_eq!(expected_setting, setting);
},
FilterScript::AssertFilters { expected } => {
let actual = self.get_all_filters().await;
for (actual_filter, expected_filter) in actual.iter().zip(expected.iter()) {
Self::assert_filter(actual_filter, expected_filter);
}
},
FilterScript::AssertNumberOfVisibleRows { expected } => {
let grid = self.editor.get_database_data(&self.view_id).await.unwrap();
assert_eq!(grid.rows.len(), expected);
@ -395,6 +310,16 @@ impl DatabaseFilterTest {
}
}
async fn subscribe_view_changed(&mut self) {
self.recv = Some(
self
.editor
.subscribe_view_changed(&self.view_id)
.await
.unwrap(),
);
}
async fn assert_future_changed(&mut self, change: Option<FilterRowChanged>) {
if change.is_none() {
return;
@ -424,9 +349,24 @@ impl DatabaseFilterTest {
});
}
async fn insert_filter(&self, payload: UpdateFilterPayloadPB) {
let params: UpdateFilterParams = payload.try_into().unwrap();
self.editor.create_or_update_filter(params).await.unwrap();
fn assert_filter(actual: &FilterPB, expected: &FilterPB) {
assert_eq!(actual.filter_type, expected.filter_type);
assert_eq!(actual.children.is_empty(), expected.children.is_empty());
assert_eq!(actual.data.is_some(), expected.data.is_some());
match actual.filter_type {
FilterType::Data => {
let actual_data = actual.data.clone().unwrap();
let expected_data = expected.data.clone().unwrap();
assert_eq!(actual_data.field_type, expected_data.field_type);
assert_eq!(actual_data.data, expected_data.data);
},
FilterType::And | FilterType::Or => {
for (actual_child, expected_child) in actual.children.iter().zip(expected.children.iter()) {
Self::assert_filter(actual_child, expected_child);
}
},
}
}
}

View File

@ -1,4 +1,5 @@
use flowy_database2::entities::{FieldType, SelectOptionConditionPB};
use flowy_database2::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
@ -7,9 +8,14 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}
async fn grid_filter_multi_select_is_empty_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIsEmpty,
option_ids: vec![],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsEmpty,
option_ids: vec![],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 2 },
];
@ -20,9 +26,14 @@ async fn grid_filter_multi_select_is_empty_test() {
async fn grid_filter_multi_select_is_not_empty_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIsNotEmpty,
option_ids: vec![],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsNotEmpty,
option_ids: vec![],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 5 },
];
@ -35,9 +46,14 @@ async fn grid_filter_multi_select_is_test() {
let field = test.get_first_field(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field.id);
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(0).id, options.remove(0).id],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(0).id, options.remove(0).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 5 },
];
@ -50,9 +66,14 @@ async fn grid_filter_multi_select_is_test2() {
let field = test.get_first_field(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field.id);
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(1).id],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(1).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 4 },
];
@ -65,9 +86,13 @@ async fn grid_filter_single_select_is_empty_test() {
let expected = 3;
let row_count = test.row_details.len();
let scripts = vec![
CreateSingleSelectFilter {
condition: SelectOptionConditionPB::OptionIsEmpty,
option_ids: vec![],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsEmpty,
option_ids: vec![],
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -86,9 +111,13 @@ async fn grid_filter_single_select_is_test() {
let expected = 2;
let row_count = test.row_details.len();
let scripts = vec![
CreateSingleSelectFilter {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(0).id],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![options.remove(0).id],
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - expected,
@ -109,9 +138,13 @@ async fn grid_filter_single_select_is_test2() {
let row_count = test.row_details.len();
let scripts = vec![
CreateSingleSelectFilter {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![option.id.clone()],
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![option.id.clone()],
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: row_count - 2,

View File

@ -1,7 +1,5 @@
use flowy_database2::entities::{
FieldType, TextFilterConditionPB, TextFilterPB, UpdateFilterPayloadPB,
};
use flowy_database2::services::filter::FilterContext;
use flowy_database2::entities::{FieldType, TextFilterConditionPB, TextFilterPB};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
use crate::database::filter_test::script::*;
@ -10,9 +8,13 @@ use crate::database::filter_test::script::*;
async fn grid_filter_text_is_empty_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 5,
@ -28,9 +30,13 @@ async fn grid_filter_text_is_not_empty_test() {
let mut test = DatabaseFilterTest::new().await;
// Only one row's text of the initial rows is ""
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::TextIsNotEmpty,
content: "".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::TextIsNotEmpty,
content: "".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 1,
@ -44,7 +50,8 @@ async fn grid_filter_text_is_not_empty_test() {
test
.run_scripts(vec![
DeleteFilter {
filter_context: FilterContext::from(&filter),
filter_id: filter.id,
field_id: filter.data.unwrap().field_id,
changed: Some(FilterRowChanged {
showing_num_of_rows: 1,
hiding_num_of_rows: 0,
@ -59,9 +66,13 @@ async fn grid_filter_text_is_not_empty_test() {
async fn grid_filter_is_text_test() {
let mut test = DatabaseFilterTest::new().await;
// Only one row's text of the initial rows is "A"
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::Is,
content: "A".to_string(),
let scripts = vec![CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Is,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 5,
@ -73,9 +84,13 @@ async fn grid_filter_is_text_test() {
#[tokio::test]
async fn grid_filter_contain_text_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::Contains,
content: "A".to_string(),
let scripts = vec![CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Contains,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 2,
@ -90,9 +105,13 @@ async fn grid_filter_contain_text_test2() {
let row_detail = test.row_details.clone();
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::Contains,
content: "A".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Contains,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 2,
@ -114,9 +133,13 @@ async fn grid_filter_contain_text_test2() {
async fn grid_filter_does_not_contain_text_test() {
let mut test = DatabaseFilterTest::new().await;
// None of the initial rows contains the text "AB"
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::DoesNotContain,
content: "AB".to_string(),
let scripts = vec![CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::DoesNotContain,
content: "AB".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 0,
@ -128,9 +151,13 @@ async fn grid_filter_does_not_contain_text_test() {
#[tokio::test]
async fn grid_filter_start_with_text_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::StartsWith,
content: "A".to_string(),
let scripts = vec![CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::StartsWith,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 3,
@ -143,9 +170,13 @@ async fn grid_filter_start_with_text_test() {
async fn grid_filter_ends_with_text_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::EndsWith,
content: "A".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::EndsWith,
content: "A".to_string(),
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 2 },
@ -157,9 +188,13 @@ async fn grid_filter_ends_with_text_test() {
async fn grid_update_text_filter_test() {
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::EndsWith,
content: "A".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::EndsWith,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 4,
@ -190,14 +225,16 @@ async fn grid_update_text_filter_test() {
#[tokio::test]
async fn grid_filter_delete_test() {
let mut test = DatabaseFilterTest::new().await;
let field = test.get_first_field(FieldType::RichText).clone();
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
};
let payload = UpdateFilterPayloadPB::new(&test.view_id(), &field, text_filter);
let scripts = vec![
InsertFilter { payload },
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
changed: None,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
}),
},
AssertFilterCount { count: 1 },
AssertNumberOfVisibleRows { expected: 1 },
];
@ -207,7 +244,8 @@ async fn grid_filter_delete_test() {
test
.run_scripts(vec![
DeleteFilter {
filter_context: FilterContext::from(&filter),
filter_id: filter.id,
field_id: filter.data.unwrap().field_id,
changed: None,
},
AssertFilterCount { count: 0 },
@ -221,9 +259,13 @@ async fn grid_filter_update_empty_text_cell_test() {
let mut test = DatabaseFilterTest::new().await;
let row_details = test.row_details.clone();
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::TextIsEmpty,
content: "".to_string(),
}),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,
hiding_num_of_rows: 5,