mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: clean up sort and filter code (#4585)
* refactor: port away from extra SortType struct * refactor: add validator to flowy_database and clean up unused structs * refactor: port away from extra FilterType struct * chore: analysis options * fix: clippy and dart/ts compile * fix: tauri build --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
@ -81,10 +81,8 @@ class SortBackendService {
|
|||||||
required FieldType fieldType,
|
required FieldType fieldType,
|
||||||
}) {
|
}) {
|
||||||
final deleteSortPayload = DeleteSortPayloadPB.create()
|
final deleteSortPayload = DeleteSortPayloadPB.create()
|
||||||
..fieldId = fieldId
|
|
||||||
..sortId = sortId
|
..sortId = sortId
|
||||||
..viewId = viewId
|
..viewId = viewId;
|
||||||
..fieldType = fieldType;
|
|
||||||
|
|
||||||
final payload = DatabaseSettingChangesetPB.create()
|
final payload = DatabaseSettingChangesetPB.create()
|
||||||
..viewId = viewId
|
..viewId = viewId
|
||||||
|
@ -57,8 +57,6 @@ export async function deleteSort(viewId: string, sort: Sort): Promise<void> {
|
|||||||
delete_sort: {
|
delete_sort: {
|
||||||
view_id: viewId,
|
view_id: viewId,
|
||||||
sort_id: sort.id,
|
sort_id: sort.id,
|
||||||
field_id: sort.fieldId,
|
|
||||||
field_type: sort.fieldType,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
18
frontend/rust-lib/Cargo.lock
generated
18
frontend/rust-lib/Cargo.lock
generated
@ -1102,7 +1102,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa",
|
"itoa",
|
||||||
"phf 0.11.2",
|
"phf 0.8.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3588,7 +3588,7 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros 0.8.0",
|
"phf_macros",
|
||||||
"phf_shared 0.8.0",
|
"phf_shared 0.8.0",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
@ -3608,7 +3608,6 @@ version = "0.11.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros 0.11.2",
|
|
||||||
"phf_shared 0.11.2",
|
"phf_shared 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3676,19 +3675,6 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_macros"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
|
||||||
dependencies = [
|
|
||||||
"phf_generator 0.11.2",
|
|
||||||
"phf_shared 0.11.2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.47",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -6,6 +6,7 @@ use collab_database::fields::Field;
|
|||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
use crate::entities::parser::NotEmptyStr;
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
@ -13,7 +14,7 @@ use crate::entities::{
|
|||||||
NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||||
};
|
};
|
||||||
use crate::services::field::SelectOptionIds;
|
use crate::services::field::SelectOptionIds;
|
||||||
use crate::services::filter::{Filter, FilterType};
|
use crate::services::filter::Filter;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
pub struct FilterPB {
|
pub struct FilterPB {
|
||||||
@ -73,60 +74,28 @@ impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||||
pub struct DeleteFilterPayloadPB {
|
pub struct DeleteFilterPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub field_id: String,
|
pub field_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
|
|
||||||
#[pb(index = 3)]
|
#[pb(index = 3)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub filter_id: String,
|
pub filter_id: String,
|
||||||
|
|
||||||
#[pb(index = 4)]
|
#[pb(index = 4)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
|
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id)
|
|
||||||
.map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
let field_id = NotEmptyStr::parse(self.field_id)
|
|
||||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let filter_id = NotEmptyStr::parse(self.filter_id)
|
|
||||||
.map_err(|_| ErrorCode::UnexpectedEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let filter_type = FilterType {
|
|
||||||
filter_id: filter_id.clone(),
|
|
||||||
field_id,
|
|
||||||
field_type: self.field_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(DeleteFilterParams {
|
|
||||||
view_id,
|
|
||||||
filter_id,
|
|
||||||
filter_type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeleteFilterParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub filter_id: String,
|
|
||||||
pub filter_type: FilterType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
|
||||||
pub struct UpdateFilterPayloadPB {
|
pub struct UpdateFilterPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub field_id: String,
|
pub field_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
@ -134,12 +103,14 @@ pub struct UpdateFilterPayloadPB {
|
|||||||
|
|
||||||
/// Create a new filter if the filter_id is None
|
/// Create a new filter if the filter_id is None
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 3, one_of)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub filter_id: Option<String>,
|
pub filter_id: Option<String>,
|
||||||
|
|
||||||
#[pb(index = 4)]
|
#[pb(index = 4)]
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
|
|
||||||
#[pb(index = 5)]
|
#[pb(index = 5)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use std::convert::TryInto;
|
|||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
use crate::entities::parser::NotEmptyStr;
|
||||||
use crate::entities::RowMetaPB;
|
use crate::entities::RowMetaPB;
|
||||||
@ -130,15 +131,18 @@ pub struct GroupByFieldParams {
|
|||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone, Validate)]
|
||||||
pub struct UpdateGroupPB {
|
pub struct UpdateGroupPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub group_id: String,
|
pub group_id: String,
|
||||||
|
|
||||||
#[pb(index = 3)]
|
#[pb(index = 3)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub field_id: String,
|
pub field_id: String,
|
||||||
|
|
||||||
#[pb(index = 4, one_of)]
|
#[pb(index = 4, one_of)]
|
||||||
|
@ -5,12 +5,12 @@ use strum_macros::EnumIter;
|
|||||||
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
use crate::entities::parser::NotEmptyStr;
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
CalendarLayoutSettingPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteSortParams,
|
CalendarLayoutSettingPB, DeleteFilterPayloadPB, DeleteSortPayloadPB, RepeatedFieldSettingsPB,
|
||||||
DeleteSortPayloadPB, RepeatedFieldSettingsPB, RepeatedFilterPB, RepeatedGroupSettingPB,
|
RepeatedFilterPB, RepeatedGroupSettingPB, RepeatedSortPB, UpdateFilterPayloadPB, UpdateGroupPB,
|
||||||
RepeatedSortPB, UpdateFilterParams, UpdateFilterPayloadPB, UpdateGroupPB, UpdateSortParams,
|
|
||||||
UpdateSortPayloadPB,
|
UpdateSortPayloadPB,
|
||||||
};
|
};
|
||||||
use crate::services::setting::{BoardLayoutSetting, CalendarLayoutSetting};
|
use crate::services::setting::{BoardLayoutSetting, CalendarLayoutSetting};
|
||||||
@ -69,84 +69,36 @@ impl std::convert::From<DatabaseLayoutPB> for DatabaseLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, Validate, ProtoBuf)]
|
||||||
pub struct DatabaseSettingChangesetPB {
|
pub struct DatabaseSettingChangesetPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2, one_of)]
|
#[pb(index = 2, one_of)]
|
||||||
pub layout_type: Option<DatabaseLayoutPB>,
|
pub layout_type: Option<DatabaseLayoutPB>,
|
||||||
|
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 3, one_of)]
|
||||||
|
#[validate]
|
||||||
pub update_filter: Option<UpdateFilterPayloadPB>,
|
pub update_filter: Option<UpdateFilterPayloadPB>,
|
||||||
|
|
||||||
#[pb(index = 4, one_of)]
|
#[pb(index = 4, one_of)]
|
||||||
|
#[validate]
|
||||||
pub delete_filter: Option<DeleteFilterPayloadPB>,
|
pub delete_filter: Option<DeleteFilterPayloadPB>,
|
||||||
|
|
||||||
#[pb(index = 5, one_of)]
|
#[pb(index = 5, one_of)]
|
||||||
|
#[validate]
|
||||||
pub update_group: Option<UpdateGroupPB>,
|
pub update_group: Option<UpdateGroupPB>,
|
||||||
|
|
||||||
#[pb(index = 6, one_of)]
|
#[pb(index = 6, one_of)]
|
||||||
|
#[validate]
|
||||||
pub update_sort: Option<UpdateSortPayloadPB>,
|
pub update_sort: Option<UpdateSortPayloadPB>,
|
||||||
|
|
||||||
#[pb(index = 7, one_of)]
|
#[pb(index = 7, one_of)]
|
||||||
|
#[validate]
|
||||||
pub delete_sort: Option<DeleteSortPayloadPB>,
|
pub delete_sort: Option<DeleteSortPayloadPB>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<DatabaseSettingChangesetParams> for DatabaseSettingChangesetPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<DatabaseSettingChangesetParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id)
|
|
||||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let insert_filter = match self.update_filter {
|
|
||||||
None => None,
|
|
||||||
Some(payload) => Some(payload.try_into()?),
|
|
||||||
};
|
|
||||||
|
|
||||||
let delete_filter = match self.delete_filter {
|
|
||||||
None => None,
|
|
||||||
Some(payload) => Some(payload.try_into()?),
|
|
||||||
};
|
|
||||||
|
|
||||||
let alert_sort = match self.update_sort {
|
|
||||||
None => None,
|
|
||||||
Some(payload) => Some(payload.try_into()?),
|
|
||||||
};
|
|
||||||
|
|
||||||
let delete_sort = match self.delete_sort {
|
|
||||||
None => None,
|
|
||||||
Some(payload) => Some(payload.try_into()?),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(DatabaseSettingChangesetParams {
|
|
||||||
view_id,
|
|
||||||
layout_type: self.layout_type.map(|ty| ty.into()),
|
|
||||||
insert_filter,
|
|
||||||
delete_filter,
|
|
||||||
alert_sort,
|
|
||||||
delete_sort,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DatabaseSettingChangesetParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub layout_type: Option<DatabaseLayout>,
|
|
||||||
pub insert_filter: Option<UpdateFilterParams>,
|
|
||||||
pub delete_filter: Option<DeleteFilterParams>,
|
|
||||||
pub alert_sort: Option<UpdateSortParams>,
|
|
||||||
pub delete_sort: Option<DeleteSortParams>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DatabaseSettingChangesetParams {
|
|
||||||
pub fn is_filter_changed(&self) -> bool {
|
|
||||||
self.insert_filter.is_some() || self.delete_filter.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
|
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
|
||||||
pub struct DatabaseLayoutSettingPB {
|
pub struct DatabaseLayoutSettingPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::ErrorCode;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
|
||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::services::sort::{Sort, SortCondition, SortType};
|
use crate::services::sort::{Sort, SortCondition};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
pub struct SortPB {
|
pub struct SortPB {
|
||||||
@ -91,12 +90,14 @@ impl std::convert::From<SortConditionPB> for SortCondition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||||
pub struct UpdateSortPayloadPB {
|
pub struct UpdateSortPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub field_id: String,
|
pub field_id: String,
|
||||||
|
|
||||||
#[pb(index = 3)]
|
#[pb(index = 3)]
|
||||||
@ -110,95 +111,14 @@ pub struct UpdateSortPayloadPB {
|
|||||||
pub condition: SortConditionPB,
|
pub condition: SortConditionPB,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<UpdateSortParams> for UpdateSortPayloadPB {
|
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<UpdateSortParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id)
|
|
||||||
.map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let field_id = NotEmptyStr::parse(self.field_id)
|
|
||||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let sort_id = match self.sort_id {
|
|
||||||
None => None,
|
|
||||||
Some(sort_id) => Some(
|
|
||||||
NotEmptyStr::parse(sort_id)
|
|
||||||
.map_err(|_| ErrorCode::SortIdIsEmpty)?
|
|
||||||
.0,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(UpdateSortParams {
|
|
||||||
view_id,
|
|
||||||
field_id,
|
|
||||||
sort_id,
|
|
||||||
field_type: self.field_type,
|
|
||||||
condition: self.condition.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UpdateSortParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub field_id: String,
|
|
||||||
/// Create a new sort if the sort is None
|
|
||||||
pub sort_id: Option<String>,
|
|
||||||
pub field_type: FieldType,
|
|
||||||
pub condition: SortCondition,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
|
||||||
pub struct DeleteSortPayloadPB {
|
pub struct DeleteSortPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub field_id: String,
|
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub field_type: FieldType,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub sort_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<DeleteSortParams> for DeleteSortPayloadPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<DeleteSortParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id)
|
|
||||||
.map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
let field_id = NotEmptyStr::parse(self.field_id)
|
|
||||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let sort_id = NotEmptyStr::parse(self.sort_id)
|
|
||||||
.map_err(|_| ErrorCode::UnexpectedEmpty)?
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let sort_type = SortType {
|
|
||||||
sort_id: sort_id.clone(),
|
|
||||||
field_id,
|
|
||||||
field_type: self.field_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(DeleteSortParams {
|
|
||||||
view_id,
|
|
||||||
sort_type,
|
|
||||||
sort_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DeleteSortParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub sort_type: SortType,
|
|
||||||
pub sort_id: String,
|
pub sort_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,27 +87,30 @@ pub(crate) async fn update_database_setting_handler(
|
|||||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
) -> Result<(), FlowyError> {
|
) -> Result<(), FlowyError> {
|
||||||
let manager = upgrade_manager(manager)?;
|
let manager = upgrade_manager(manager)?;
|
||||||
let params: DatabaseSettingChangesetParams = data.into_inner().try_into()?;
|
let params = data.try_into_inner()?;
|
||||||
let editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
let editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||||
|
|
||||||
if let Some(update_filter) = params.insert_filter {
|
if let Some(update_filter) = params.update_filter {
|
||||||
editor.create_or_update_filter(update_filter).await?;
|
editor
|
||||||
|
.create_or_update_filter(update_filter.try_into()?)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(delete_filter) = params.delete_filter {
|
if let Some(delete_filter) = params.delete_filter {
|
||||||
editor.delete_filter(delete_filter).await?;
|
editor.delete_filter(delete_filter).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(update_sort) = params.alert_sort {
|
if let Some(update_sort) = params.update_sort {
|
||||||
let _ = editor.create_or_update_sort(update_sort).await?;
|
let _ = editor.create_or_update_sort(update_sort).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(delete_sort) = params.delete_sort {
|
if let Some(delete_sort) = params.delete_sort {
|
||||||
editor.delete_sort(delete_sort).await?;
|
editor.delete_sort(delete_sort).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(layout_type) = params.layout_type {
|
if let Some(layout_type) = params.layout_type {
|
||||||
editor
|
editor
|
||||||
.update_view_layout(¶ms.view_id, layout_type)
|
.update_view_layout(¶ms.view_id, layout_type.into())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -208,19 +208,19 @@ impl DatabaseEditor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
pub async fn delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> {
|
||||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||||
view_editor.v_delete_filter(params).await?;
|
view_editor.v_delete_filter(params).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_or_update_sort(&self, params: UpdateSortParams) -> FlowyResult<Sort> {
|
pub async fn create_or_update_sort(&self, params: UpdateSortPayloadPB) -> FlowyResult<Sort> {
|
||||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||||
let sort = view_editor.v_insert_sort(params).await?;
|
let sort = view_editor.insert_or_update_sort(params).await?;
|
||||||
Ok(sort)
|
Ok(sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
pub async fn delete_sort(&self, params: DeleteSortPayloadPB) -> FlowyResult<()> {
|
||||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||||
view_editor.v_delete_sort(params).await?;
|
view_editor.v_delete_sort(params).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -15,11 +15,11 @@ use flowy_error::{FlowyError, FlowyResult};
|
|||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
|
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterPayloadPB,
|
||||||
DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB,
|
DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB,
|
||||||
LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, RowMetaPB,
|
LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, RowMetaPB,
|
||||||
RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB,
|
RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB,
|
||||||
UpdateFilterParams, UpdateSortParams,
|
UpdateFilterParams, UpdateSortPayloadPB,
|
||||||
};
|
};
|
||||||
use crate::notification::{send_notification, DatabaseNotification};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
||||||
@ -38,11 +38,11 @@ use crate::services::database_view::{
|
|||||||
};
|
};
|
||||||
use crate::services::field_settings::FieldSettings;
|
use crate::services::field_settings::FieldSettings;
|
||||||
use crate::services::filter::{
|
use crate::services::filter::{
|
||||||
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
|
Filter, FilterChangeset, FilterContext, FilterController, UpdatedFilter,
|
||||||
};
|
};
|
||||||
use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowContext, RowChangeset};
|
use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowContext, RowChangeset};
|
||||||
use crate::services::setting::CalendarLayoutSetting;
|
use crate::services::setting::CalendarLayoutSetting;
|
||||||
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
use crate::services::sort::{Sort, SortChangeset, SortController};
|
||||||
|
|
||||||
use super::notify_did_update_calculation;
|
use super::notify_did_update_calculation;
|
||||||
use super::view_calculations::make_calculations_controller;
|
use super::view_calculations::make_calculations_controller;
|
||||||
@ -499,7 +499,7 @@ impl DatabaseViewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub async fn v_insert_sort(&self, params: UpdateSortParams) -> FlowyResult<Sort> {
|
pub async fn insert_or_update_sort(&self, params: UpdateSortPayloadPB) -> FlowyResult<Sort> {
|
||||||
let is_exist = params.sort_id.is_some();
|
let is_exist = params.sort_id.is_some();
|
||||||
let sort_id = match params.sort_id {
|
let sort_id = match params.sort_id {
|
||||||
None => gen_database_sort_id(),
|
None => gen_database_sort_id(),
|
||||||
@ -510,18 +510,18 @@ impl DatabaseViewEditor {
|
|||||||
id: sort_id,
|
id: sort_id,
|
||||||
field_id: params.field_id.clone(),
|
field_id: params.field_id.clone(),
|
||||||
field_type: params.field_type,
|
field_type: params.field_type,
|
||||||
condition: params.condition,
|
condition: params.condition.into(),
|
||||||
};
|
};
|
||||||
let sort_type = SortType::from(&sort);
|
|
||||||
let mut sort_controller = self.sort_controller.write().await;
|
let mut sort_controller = self.sort_controller.write().await;
|
||||||
self.delegate.insert_sort(&self.view_id, sort.clone());
|
self.delegate.insert_sort(&self.view_id, sort.clone());
|
||||||
let changeset = if is_exist {
|
let changeset = if is_exist {
|
||||||
sort_controller
|
sort_controller
|
||||||
.did_receive_changes(SortChangeset::from_update(sort_type))
|
.apply_changeset(SortChangeset::from_update(sort.clone()))
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
sort_controller
|
sort_controller
|
||||||
.did_receive_changes(SortChangeset::from_insert(sort_type))
|
.apply_changeset(SortChangeset::from_insert(sort.clone()))
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
drop(sort_controller);
|
drop(sort_controller);
|
||||||
@ -529,18 +529,17 @@ impl DatabaseViewEditor {
|
|||||||
Ok(sort)
|
Ok(sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> {
|
pub async fn v_delete_sort(&self, params: DeleteSortPayloadPB) -> FlowyResult<()> {
|
||||||
let notification = self
|
let notification = self
|
||||||
.sort_controller
|
.sort_controller
|
||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.did_receive_changes(SortChangeset::from_delete(DeletedSortType::from(
|
.apply_changeset(SortChangeset::from_delete(params.sort_id.clone()))
|
||||||
params.clone(),
|
|
||||||
)))
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.delegate.remove_sort(&self.view_id, ¶ms.sort_id);
|
self.delegate.remove_sort(&self.view_id, ¶ms.sort_id);
|
||||||
notify_did_update_sort(notification).await;
|
notify_did_update_sort(notification).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,25 +634,20 @@ impl DatabaseViewEditor {
|
|||||||
condition: params.condition,
|
condition: params.condition,
|
||||||
content: params.content,
|
content: params.content,
|
||||||
};
|
};
|
||||||
let filter_type = FilterType::from(&filter);
|
|
||||||
let filter_controller = self.filter_controller.clone();
|
let filter_controller = self.filter_controller.clone();
|
||||||
let changeset = if is_exist {
|
let changeset = if is_exist {
|
||||||
let old_filter_type = self
|
let old_filter = self.delegate.get_filter(&self.view_id, &filter.id);
|
||||||
.delegate
|
|
||||||
.get_filter(&self.view_id, &filter.id)
|
|
||||||
.map(|field| FilterType::from(&field));
|
|
||||||
|
|
||||||
self.delegate.insert_filter(&self.view_id, filter);
|
self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||||
filter_controller
|
filter_controller
|
||||||
.did_receive_changes(FilterChangeset::from_update(UpdatedFilterType::new(
|
.did_receive_changes(FilterChangeset::from_update(UpdatedFilter::new(
|
||||||
old_filter_type,
|
old_filter, filter,
|
||||||
filter_type,
|
|
||||||
)))
|
)))
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
self.delegate.insert_filter(&self.view_id, filter);
|
self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||||
filter_controller
|
filter_controller
|
||||||
.did_receive_changes(FilterChangeset::from_insert(filter_type))
|
.did_receive_changes(FilterChangeset::from_insert(filter))
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
drop(filter_controller);
|
drop(filter_controller);
|
||||||
@ -665,16 +659,20 @@ impl DatabaseViewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub async fn v_delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
pub async fn v_delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> {
|
||||||
let filter_type = params.filter_type;
|
let filter_context = FilterContext {
|
||||||
|
filter_id: params.filter_id.clone(),
|
||||||
|
field_id: params.field_id.clone(),
|
||||||
|
field_type: params.field_type,
|
||||||
|
};
|
||||||
let changeset = self
|
let changeset = self
|
||||||
.filter_controller
|
.filter_controller
|
||||||
.did_receive_changes(FilterChangeset::from_delete(filter_type.clone()))
|
.did_receive_changes(FilterChangeset::from_delete(filter_context.clone()))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self
|
self
|
||||||
.delegate
|
.delegate
|
||||||
.delete_filter(&self.view_id, &filter_type.filter_id);
|
.delete_filter(&self.view_id, ¶ms.filter_id);
|
||||||
if changeset.is_some() {
|
if changeset.is_some() {
|
||||||
notify_did_update_filter(changeset.unwrap()).await;
|
notify_did_update_filter(changeset.unwrap()).await;
|
||||||
}
|
}
|
||||||
@ -799,11 +797,12 @@ impl DatabaseViewEditor {
|
|||||||
.delegate
|
.delegate
|
||||||
.get_filter_by_field_id(&self.view_id, field_id)
|
.get_filter_by_field_id(&self.view_id, field_id)
|
||||||
{
|
{
|
||||||
let mut old = FilterType::from(&filter);
|
let old = Filter {
|
||||||
old.field_type = FieldType::from(old_field.field_type);
|
field_type: FieldType::from(old_field.field_type),
|
||||||
let new = FilterType::from(&filter);
|
..filter.clone()
|
||||||
let filter_type = UpdatedFilterType::new(Some(old), new);
|
};
|
||||||
let filter_changeset = FilterChangeset::from_update(filter_type);
|
let updated_filter = UpdatedFilter::new(Some(old), filter);
|
||||||
|
let filter_changeset = FilterChangeset::from_update(updated_filter);
|
||||||
let filter_controller = self.filter_controller.clone();
|
let filter_controller = self.filter_controller.clone();
|
||||||
af_spawn(async move {
|
af_spawn(async move {
|
||||||
if let Some(notification) = filter_controller
|
if let Some(notification) = filter_controller
|
||||||
|
@ -237,7 +237,7 @@ impl FilterController {
|
|||||||
let mut notification: Option<FilterChangesetNotificationPB> = None;
|
let mut notification: Option<FilterChangesetNotificationPB> = None;
|
||||||
|
|
||||||
if let Some(filter_type) = &changeset.insert_filter {
|
if let Some(filter_type) = &changeset.insert_filter {
|
||||||
if let Some(filter) = self.filter_from_filter_id(&filter_type.filter_id).await {
|
if let Some(filter) = self.filter_from_filter_id(&filter_type.id).await {
|
||||||
notification = Some(FilterChangesetNotificationPB::from_insert(
|
notification = Some(FilterChangesetNotificationPB::from_insert(
|
||||||
&self.view_id,
|
&self.view_id,
|
||||||
vec![filter],
|
vec![filter],
|
||||||
@ -245,7 +245,7 @@ impl FilterController {
|
|||||||
}
|
}
|
||||||
if let Some(filter) = self
|
if let Some(filter) = self
|
||||||
.delegate
|
.delegate
|
||||||
.get_filter(&self.view_id, &filter_type.filter_id)
|
.get_filter(&self.view_id, &filter_type.id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
self.refresh_filters(vec![filter]).await;
|
self.refresh_filters(vec![filter]).await;
|
||||||
@ -255,9 +255,9 @@ impl FilterController {
|
|||||||
if let Some(updated_filter_type) = changeset.update_filter {
|
if let Some(updated_filter_type) = changeset.update_filter {
|
||||||
if let Some(old_filter_type) = updated_filter_type.old {
|
if let Some(old_filter_type) = updated_filter_type.old {
|
||||||
let new_filter = self
|
let new_filter = self
|
||||||
.filter_from_filter_id(&updated_filter_type.new.filter_id)
|
.filter_from_filter_id(&updated_filter_type.new.id)
|
||||||
.await;
|
.await;
|
||||||
let old_filter = self.filter_from_filter_id(&old_filter_type.filter_id).await;
|
let old_filter = self.filter_from_filter_id(&old_filter_type.id).await;
|
||||||
|
|
||||||
// Get the filter id
|
// Get the filter id
|
||||||
let mut filter_id = old_filter.map(|filter| filter.id);
|
let mut filter_id = old_filter.map(|filter| filter.id);
|
||||||
@ -282,14 +282,17 @@ impl FilterController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(filter_type) = &changeset.delete_filter {
|
if let Some(filter_context) = &changeset.delete_filter {
|
||||||
if let Some(filter) = self.filter_from_filter_id(&filter_type.filter_id).await {
|
if let Some(filter) = self.filter_from_filter_id(&filter_context.filter_id).await {
|
||||||
notification = Some(FilterChangesetNotificationPB::from_delete(
|
notification = Some(FilterChangesetNotificationPB::from_delete(
|
||||||
&self.view_id,
|
&self.view_id,
|
||||||
vec![filter],
|
vec![filter],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.cell_filter_cache.write().remove(&filter_type.field_id);
|
self
|
||||||
|
.cell_filter_cache
|
||||||
|
.write()
|
||||||
|
.remove(&filter_context.field_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -3,7 +3,7 @@ use collab::core::any_map::AnyMapExtension;
|
|||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
use collab_database::views::{FilterMap, FilterMapBuilder};
|
use collab_database::views::{FilterMap, FilterMapBuilder};
|
||||||
|
|
||||||
use crate::entities::{DeleteFilterParams, FieldType, FilterPB, InsertedRowPB};
|
use crate::entities::{FieldType, FilterPB, InsertedRowPB};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Filter {
|
pub struct Filter {
|
||||||
@ -63,66 +63,56 @@ impl TryFrom<FilterMap> for Filter {
|
|||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FilterChangeset {
|
pub struct FilterChangeset {
|
||||||
pub(crate) insert_filter: Option<FilterType>,
|
pub(crate) insert_filter: Option<Filter>,
|
||||||
pub(crate) update_filter: Option<UpdatedFilterType>,
|
pub(crate) update_filter: Option<UpdatedFilter>,
|
||||||
pub(crate) delete_filter: Option<FilterType>,
|
pub(crate) delete_filter: Option<FilterContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UpdatedFilterType {
|
pub struct UpdatedFilter {
|
||||||
pub old: Option<FilterType>,
|
pub old: Option<Filter>,
|
||||||
pub new: FilterType,
|
pub new: Filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdatedFilterType {
|
impl UpdatedFilter {
|
||||||
pub fn new(old: Option<FilterType>, new: FilterType) -> UpdatedFilterType {
|
pub fn new(old: Option<Filter>, new: Filter) -> UpdatedFilter {
|
||||||
Self { old, new }
|
Self { old, new }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterChangeset {
|
impl FilterChangeset {
|
||||||
pub fn from_insert(filter_type: FilterType) -> Self {
|
pub fn from_insert(filter: Filter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_filter: Some(filter_type),
|
insert_filter: Some(filter),
|
||||||
update_filter: None,
|
update_filter: None,
|
||||||
delete_filter: None,
|
delete_filter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_update(filter_type: UpdatedFilterType) -> Self {
|
pub fn from_update(filter: UpdatedFilter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_filter: None,
|
insert_filter: None,
|
||||||
update_filter: Some(filter_type),
|
update_filter: Some(filter),
|
||||||
delete_filter: None,
|
delete_filter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from_delete(filter_type: FilterType) -> Self {
|
pub fn from_delete(filter_context: FilterContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_filter: None,
|
insert_filter: None,
|
||||||
update_filter: None,
|
update_filter: None,
|
||||||
delete_filter: Some(filter_type),
|
delete_filter: Some(filter_context),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash, Eq, PartialEq, Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FilterType {
|
pub struct FilterContext {
|
||||||
pub filter_id: String,
|
pub filter_id: String,
|
||||||
pub field_id: String,
|
pub field_id: String,
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<&Filter> for FilterType {
|
impl From<&FilterPB> for FilterContext {
|
||||||
fn from(filter: &Filter) -> Self {
|
|
||||||
Self {
|
|
||||||
filter_id: filter.id.clone(),
|
|
||||||
field_id: filter.field_id.clone(),
|
|
||||||
field_type: filter.field_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::From<&FilterPB> for FilterType {
|
|
||||||
fn from(filter: &FilterPB) -> Self {
|
fn from(filter: &FilterPB) -> Self {
|
||||||
Self {
|
Self {
|
||||||
filter_id: filter.id.clone(),
|
filter_id: filter.id.clone(),
|
||||||
@ -132,29 +122,6 @@ impl std::convert::From<&FilterPB> for FilterType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Hash, Eq, PartialEq, Debug, Clone)]
|
|
||||||
// pub struct InsertedFilterType {
|
|
||||||
// pub field_id: String,
|
|
||||||
// pub filter_id: Option<String>,
|
|
||||||
// pub field_type: FieldType,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// impl std::convert::From<&Filter> for InsertedFilterType {
|
|
||||||
// fn from(params: &Filter) -> Self {
|
|
||||||
// Self {
|
|
||||||
// field_id: params.field_id.clone(),
|
|
||||||
// filter_id: Some(params.id.clone()),
|
|
||||||
// field_type: params.field_type.clone(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl std::convert::From<&DeleteFilterParams> for FilterType {
|
|
||||||
fn from(params: &DeleteFilterParams) -> Self {
|
|
||||||
params.filter_type.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FilterResultNotification {
|
pub struct FilterResultNotification {
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
@ -179,39 +179,25 @@ impl SortController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
pub async fn did_receive_changes(
|
pub async fn apply_changeset(&mut self, changeset: SortChangeset) -> SortChangesetNotificationPB {
|
||||||
&mut self,
|
|
||||||
changeset: SortChangeset,
|
|
||||||
) -> SortChangesetNotificationPB {
|
|
||||||
let mut notification = SortChangesetNotificationPB::new(self.view_id.clone());
|
let mut notification = SortChangesetNotificationPB::new(self.view_id.clone());
|
||||||
|
|
||||||
if let Some(insert_sort) = changeset.insert_sort {
|
if let Some(insert_sort) = changeset.insert_sort {
|
||||||
if let Some(sort) = self
|
if let Some(sort) = self.delegate.get_sort(&self.view_id, &insert_sort.id).await {
|
||||||
.delegate
|
|
||||||
.get_sort(&self.view_id, &insert_sort.sort_id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
notification.insert_sorts.push(sort.as_ref().into());
|
notification.insert_sorts.push(sort.as_ref().into());
|
||||||
self.sorts.push(sort);
|
self.sorts.push(sort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(delete_sort_type) = changeset.delete_sort {
|
if let Some(sort_id) = changeset.delete_sort {
|
||||||
if let Some(index) = self
|
if let Some(index) = self.sorts.iter().position(|sort| sort.id == sort_id) {
|
||||||
.sorts
|
|
||||||
.iter()
|
|
||||||
.position(|sort| sort.id == delete_sort_type.sort_id)
|
|
||||||
{
|
|
||||||
let sort = self.sorts.remove(index);
|
let sort = self.sorts.remove(index);
|
||||||
notification.delete_sorts.push(sort.as_ref().into());
|
notification.delete_sorts.push(sort.as_ref().into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(update_sort) = changeset.update_sort {
|
if let Some(update_sort) = changeset.update_sort {
|
||||||
if let Some(updated_sort) = self
|
if let Some(updated_sort) = self.delegate.get_sort(&self.view_id, &update_sort.id).await {
|
||||||
.delegate
|
|
||||||
.get_sort(&self.view_id, &update_sort.sort_id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
notification.update_sorts.push(updated_sort.as_ref().into());
|
notification.update_sorts.push(updated_sort.as_ref().into());
|
||||||
if let Some(index) = self
|
if let Some(index) = self
|
||||||
.sorts
|
.sorts
|
||||||
|
@ -5,7 +5,7 @@ use collab::core::any_map::AnyMapExtension;
|
|||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
use collab_database::views::{SortMap, SortMapBuilder};
|
use collab_database::views::{SortMap, SortMapBuilder};
|
||||||
|
|
||||||
use crate::entities::{DeleteSortParams, FieldType};
|
use crate::entities::FieldType;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sort {
|
pub struct Sort {
|
||||||
@ -98,23 +98,6 @@ impl From<i64> for SortCondition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash, Eq, PartialEq, Debug, Clone)]
|
|
||||||
pub struct SortType {
|
|
||||||
pub sort_id: String,
|
|
||||||
pub field_id: String,
|
|
||||||
pub field_type: FieldType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Sort> for SortType {
|
|
||||||
fn from(data: &Sort) -> Self {
|
|
||||||
Self {
|
|
||||||
sort_id: data.id.clone(),
|
|
||||||
field_id: data.field_id.clone(),
|
|
||||||
field_type: data.field_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ReorderAllRowsResult {
|
pub struct ReorderAllRowsResult {
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
@ -140,13 +123,13 @@ pub struct ReorderSingleRowResult {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SortChangeset {
|
pub struct SortChangeset {
|
||||||
pub(crate) insert_sort: Option<SortType>,
|
pub(crate) insert_sort: Option<Sort>,
|
||||||
pub(crate) update_sort: Option<SortType>,
|
pub(crate) update_sort: Option<Sort>,
|
||||||
pub(crate) delete_sort: Option<DeletedSortType>,
|
pub(crate) delete_sort: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SortChangeset {
|
impl SortChangeset {
|
||||||
pub fn from_insert(sort: SortType) -> Self {
|
pub fn from_insert(sort: Sort) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_sort: Some(sort),
|
insert_sort: Some(sort),
|
||||||
update_sort: None,
|
update_sort: None,
|
||||||
@ -154,7 +137,7 @@ impl SortChangeset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_update(sort: SortType) -> Self {
|
pub fn from_update(sort: Sort) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_sort: None,
|
insert_sort: None,
|
||||||
update_sort: Some(sort),
|
update_sort: Some(sort),
|
||||||
@ -162,26 +145,11 @@ impl SortChangeset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_delete(deleted_sort: DeletedSortType) -> Self {
|
pub fn from_delete(sort_id: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
insert_sort: None,
|
insert_sort: None,
|
||||||
update_sort: None,
|
update_sort: None,
|
||||||
delete_sort: Some(deleted_sort),
|
delete_sort: Some(sort_id),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeletedSortType {
|
|
||||||
pub sort_type: SortType,
|
|
||||||
pub sort_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::From<DeleteSortParams> for DeletedSortType {
|
|
||||||
fn from(params: DeleteSortParams) -> Self {
|
|
||||||
Self {
|
|
||||||
sort_type: params.sort_type,
|
|
||||||
sort_id: params.sort_id,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,321 +1,445 @@
|
|||||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
|
||||||
#![allow(clippy::all)]
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use collab_database::rows::RowId;
|
||||||
use collab_database::rows::{Row, RowId};
|
use flowy_database2::services::filter::FilterContext;
|
||||||
use futures::TryFutureExt;
|
|
||||||
use tokio::sync::broadcast::Receiver;
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
use flowy_database2::entities::{CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB, DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterParams, FieldType, FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB, SelectOptionPB, TextFilterConditionPB, TextFilterPB, UpdateFilterParams, UpdateFilterPayloadPB};
|
use flowy_database2::entities::{
|
||||||
|
CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB,
|
||||||
|
DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterPayloadPB, FieldType,
|
||||||
|
FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB,
|
||||||
|
TextFilterConditionPB, TextFilterPB, UpdateFilterParams, UpdateFilterPayloadPB,
|
||||||
|
};
|
||||||
use flowy_database2::services::database_view::DatabaseViewChanged;
|
use flowy_database2::services::database_view::DatabaseViewChanged;
|
||||||
use flowy_database2::services::field::SelectOption;
|
|
||||||
use flowy_database2::services::filter::FilterType;
|
|
||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::database::database_editor::DatabaseEditorTest;
|
use crate::database::database_editor::DatabaseEditorTest;
|
||||||
|
|
||||||
pub struct FilterRowChanged {
|
pub struct FilterRowChanged {
|
||||||
pub(crate) showing_num_of_rows: usize,
|
pub(crate) showing_num_of_rows: usize,
|
||||||
pub(crate) hiding_num_of_rows: usize,
|
pub(crate) hiding_num_of_rows: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FilterScript {
|
pub enum FilterScript {
|
||||||
UpdateTextCell {
|
UpdateTextCell {
|
||||||
row_id: RowId,
|
row_id: RowId,
|
||||||
text: String,
|
text: String,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
UpdateChecklistCell{
|
UpdateChecklistCell {
|
||||||
row_id: RowId,
|
row_id: RowId,
|
||||||
selected_option_ids: Vec<String>,
|
selected_option_ids: Vec<String>,
|
||||||
},
|
},
|
||||||
UpdateSingleSelectCell {
|
UpdateSingleSelectCell {
|
||||||
row_id: RowId,
|
row_id: RowId,
|
||||||
option_id: String,
|
option_id: String,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
InsertFilter {
|
InsertFilter {
|
||||||
payload: UpdateFilterPayloadPB,
|
payload: UpdateFilterPayloadPB,
|
||||||
},
|
},
|
||||||
CreateTextFilter {
|
CreateTextFilter {
|
||||||
condition: TextFilterConditionPB,
|
condition: TextFilterConditionPB,
|
||||||
content: String,
|
content: String,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
UpdateTextFilter {
|
UpdateTextFilter {
|
||||||
filter: FilterPB,
|
filter: FilterPB,
|
||||||
condition: TextFilterConditionPB,
|
condition: TextFilterConditionPB,
|
||||||
content: String,
|
content: String,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
CreateNumberFilter {
|
CreateNumberFilter {
|
||||||
condition: NumberFilterConditionPB,
|
condition: NumberFilterConditionPB,
|
||||||
content: String,
|
content: String,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
CreateCheckboxFilter {
|
CreateCheckboxFilter {
|
||||||
condition: CheckboxFilterConditionPB,
|
condition: CheckboxFilterConditionPB,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
CreateDateFilter{
|
CreateDateFilter {
|
||||||
condition: DateFilterConditionPB,
|
condition: DateFilterConditionPB,
|
||||||
start: Option<i64>,
|
start: Option<i64>,
|
||||||
end: Option<i64>,
|
end: Option<i64>,
|
||||||
timestamp: Option<i64>,
|
timestamp: Option<i64>,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
CreateMultiSelectFilter {
|
CreateMultiSelectFilter {
|
||||||
condition: SelectOptionConditionPB,
|
condition: SelectOptionConditionPB,
|
||||||
option_ids: Vec<String>,
|
option_ids: Vec<String>,
|
||||||
},
|
},
|
||||||
CreateSingleSelectFilter {
|
CreateSingleSelectFilter {
|
||||||
condition: SelectOptionConditionPB,
|
condition: SelectOptionConditionPB,
|
||||||
option_ids: Vec<String>,
|
option_ids: Vec<String>,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
CreateChecklistFilter {
|
CreateChecklistFilter {
|
||||||
condition: ChecklistFilterConditionPB,
|
condition: ChecklistFilterConditionPB,
|
||||||
changed: Option<FilterRowChanged>,
|
changed: Option<FilterRowChanged>,
|
||||||
},
|
},
|
||||||
AssertFilterCount {
|
AssertFilterCount {
|
||||||
count: i32,
|
count: i32,
|
||||||
},
|
},
|
||||||
DeleteFilter {
|
DeleteFilter {
|
||||||
filter_id: String,
|
filter_context: FilterContext,
|
||||||
filter_type: FilterType,
|
changed: Option<FilterRowChanged>,
|
||||||
changed: Option<FilterRowChanged>,
|
},
|
||||||
},
|
AssertFilterContent {
|
||||||
AssertFilterContent {
|
filter_id: String,
|
||||||
filter_id: String,
|
condition: i64,
|
||||||
condition: i64,
|
content: String,
|
||||||
content: String
|
},
|
||||||
},
|
AssertNumberOfVisibleRows {
|
||||||
AssertNumberOfVisibleRows {
|
expected: usize,
|
||||||
expected: usize,
|
},
|
||||||
},
|
#[allow(dead_code)]
|
||||||
#[allow(dead_code)]
|
AssertGridSetting {
|
||||||
AssertGridSetting {
|
expected_setting: DatabaseViewSettingPB,
|
||||||
expected_setting: DatabaseViewSettingPB,
|
},
|
||||||
},
|
Wait {
|
||||||
Wait { millisecond: u64 }
|
millisecond: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DatabaseFilterTest {
|
pub struct DatabaseFilterTest {
|
||||||
inner: DatabaseEditorTest,
|
inner: DatabaseEditorTest,
|
||||||
recv: Option<Receiver<DatabaseViewChanged>>,
|
recv: Option<Receiver<DatabaseViewChanged>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseFilterTest {
|
impl DatabaseFilterTest {
|
||||||
pub async fn new() -> Self {
|
pub async fn new() -> Self {
|
||||||
let editor_test = DatabaseEditorTest::new_grid().await;
|
let editor_test = DatabaseEditorTest::new_grid().await;
|
||||||
Self {
|
Self {
|
||||||
inner: editor_test,
|
inner: editor_test,
|
||||||
recv: None,
|
recv: None,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn view_id(&self) -> String {
|
|
||||||
self.view_id.clone()
|
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 run_scripts(&mut self, scripts: Vec<FilterScript>) {
|
||||||
|
for script in scripts {
|
||||||
|
self.run_script(script).await;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_all_filters(&self) -> Vec<FilterPB> {
|
pub async fn run_script(&mut self, script: FilterScript) {
|
||||||
self.editor.get_all_filters(&self.view_id).await.items
|
match script {
|
||||||
|
FilterScript::UpdateTextCell {
|
||||||
|
row_id,
|
||||||
|
text,
|
||||||
|
changed,
|
||||||
|
} => {
|
||||||
|
self.recv = Some(
|
||||||
|
self
|
||||||
|
.editor
|
||||||
|
.subscribe_view_changed(&self.view_id())
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
self.assert_future_changed(changed).await;
|
||||||
|
self.update_text_cell(row_id, &text).await.unwrap();
|
||||||
|
},
|
||||||
|
FilterScript::UpdateChecklistCell {
|
||||||
|
row_id,
|
||||||
|
selected_option_ids,
|
||||||
|
} => {
|
||||||
|
self
|
||||||
|
.set_checklist_cell(row_id, selected_option_ids)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
},
|
||||||
|
FilterScript::UpdateSingleSelectCell {
|
||||||
|
row_id,
|
||||||
|
option_id,
|
||||||
|
changed,
|
||||||
|
} => {
|
||||||
|
self.recv = Some(
|
||||||
|
self
|
||||||
|
.editor
|
||||||
|
.subscribe_view_changed(&self.view_id())
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
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::RichText);
|
||||||
|
let text_filter = TextFilterPB { condition, content };
|
||||||
|
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, text_filter);
|
||||||
|
self.insert_filter(payload).await;
|
||||||
|
},
|
||||||
|
FilterScript::UpdateTextFilter {
|
||||||
|
filter,
|
||||||
|
condition,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
|
||||||
|
self.insert_filter(payload).await;
|
||||||
|
},
|
||||||
|
FilterScript::CreateSingleSelectFilter {
|
||||||
|
condition,
|
||||||
|
option_ids,
|
||||||
|
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::SingleSelect);
|
||||||
|
let filter = SelectOptionFilterPB {
|
||||||
|
condition,
|
||||||
|
option_ids,
|
||||||
|
};
|
||||||
|
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
|
||||||
|
self.insert_filter(payload).await;
|
||||||
|
},
|
||||||
|
FilterScript::CreateChecklistFilter { 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::Checklist);
|
||||||
|
let filter = ChecklistFilterPB { condition };
|
||||||
|
let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
|
||||||
|
self.insert_filter(payload).await;
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
FilterScript::DeleteFilter {
|
||||||
|
filter_context,
|
||||||
|
changed,
|
||||||
|
} => {
|
||||||
|
self.recv = Some(
|
||||||
|
self
|
||||||
|
.editor
|
||||||
|
.subscribe_view_changed(&self.view_id())
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
self.editor.delete_filter(params).await.unwrap();
|
||||||
|
},
|
||||||
|
FilterScript::AssertGridSetting { expected_setting } => {
|
||||||
|
let setting = self
|
||||||
|
.editor
|
||||||
|
.get_database_view_setting(&self.view_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(expected_setting, setting);
|
||||||
|
},
|
||||||
|
FilterScript::AssertNumberOfVisibleRows { expected } => {
|
||||||
|
let grid = self.editor.get_database_data(&self.view_id).await.unwrap();
|
||||||
|
assert_eq!(grid.rows.len(), expected);
|
||||||
|
},
|
||||||
|
FilterScript::Wait { millisecond } => {
|
||||||
|
tokio::time::sleep(Duration::from_millis(millisecond)).await;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run_scripts(&mut self, scripts: Vec<FilterScript>) {
|
async fn assert_future_changed(&mut self, change: Option<FilterRowChanged>) {
|
||||||
for script in scripts {
|
if change.is_none() {
|
||||||
self.run_script(script).await;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_script(&mut self, script: FilterScript) {
|
|
||||||
match script {
|
|
||||||
FilterScript::UpdateTextCell { row_id, text, changed} => {
|
|
||||||
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
|
|
||||||
self.assert_future_changed(changed).await;
|
|
||||||
self.update_text_cell(row_id, &text).await.unwrap();
|
|
||||||
}
|
|
||||||
FilterScript::UpdateChecklistCell { row_id, selected_option_ids } => {
|
|
||||||
self.set_checklist_cell( row_id, selected_option_ids).await.unwrap();
|
|
||||||
}
|
|
||||||
FilterScript::UpdateSingleSelectCell { row_id, option_id, changed} => {
|
|
||||||
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
|
|
||||||
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, 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::RichText);
|
|
||||||
let text_filter= TextFilterPB {
|
|
||||||
condition,
|
|
||||||
content
|
|
||||||
};
|
|
||||||
let payload =
|
|
||||||
UpdateFilterPayloadPB::new(
|
|
||||||
& self.view_id(),
|
|
||||||
&field, text_filter);
|
|
||||||
self.insert_filter(payload).await;
|
|
||||||
}
|
|
||||||
FilterScript::UpdateTextFilter { filter, condition, 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.into(),
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
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 };
|
|
||||||
let payload =
|
|
||||||
UpdateFilterPayloadPB::new(&self.view_id(), &field, filter);
|
|
||||||
self.insert_filter(payload).await;
|
|
||||||
}
|
|
||||||
FilterScript::CreateSingleSelectFilter { condition, option_ids, 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::SingleSelect);
|
|
||||||
let filter = SelectOptionFilterPB { condition, option_ids };
|
|
||||||
let payload =
|
|
||||||
UpdateFilterPayloadPB::new(& self.view_id(), &field, filter);
|
|
||||||
self.insert_filter(payload).await;
|
|
||||||
}
|
|
||||||
FilterScript::CreateChecklistFilter { 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::Checklist);
|
|
||||||
// let type_option = self.get_checklist_type_option(&field_rev.id);
|
|
||||||
let filter = ChecklistFilterPB { condition };
|
|
||||||
let payload =
|
|
||||||
UpdateFilterPayloadPB::new(& self.view_id(), &field, filter);
|
|
||||||
self.insert_filter(payload).await;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
FilterScript::DeleteFilter { filter_id, filter_type ,changed} => {
|
|
||||||
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
|
|
||||||
self.assert_future_changed(changed).await;
|
|
||||||
let params = DeleteFilterParams { filter_id, view_id: self.view_id(),filter_type };
|
|
||||||
let _ = self.editor.delete_filter(params).await.unwrap();
|
|
||||||
}
|
|
||||||
FilterScript::AssertGridSetting { expected_setting } => {
|
|
||||||
let setting = self.editor.get_database_view_setting(&self.view_id).await.unwrap();
|
|
||||||
assert_eq!(expected_setting, setting);
|
|
||||||
}
|
|
||||||
FilterScript::AssertNumberOfVisibleRows { expected } => {
|
|
||||||
let grid = self.editor.get_database_data(&self.view_id).await.unwrap();
|
|
||||||
assert_eq!(grid.rows.len(), expected);
|
|
||||||
}
|
|
||||||
FilterScript::Wait { millisecond } => {
|
|
||||||
tokio::time::sleep(Duration::from_millis(millisecond)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn assert_future_changed(&mut self, change: Option<FilterRowChanged>) {
|
|
||||||
if change.is_none() {return;}
|
|
||||||
let change = change.unwrap();
|
|
||||||
let mut receiver = self.recv.take().unwrap();
|
|
||||||
af_spawn(async move {
|
|
||||||
match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await {
|
|
||||||
Ok(changed) => {
|
|
||||||
match changed.unwrap() { DatabaseViewChanged::FilterNotification(notification) => {
|
|
||||||
assert_eq!(notification.visible_rows.len(), change.showing_num_of_rows, "visible rows not match");
|
|
||||||
assert_eq!(notification.invisible_rows.len(), change.hiding_num_of_rows, "invisible rows not match");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
panic!("Process filter task timeout: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn insert_filter(&self, payload: UpdateFilterPayloadPB) {
|
|
||||||
let params: UpdateFilterParams = payload.try_into().unwrap();
|
|
||||||
let _ = self.editor.create_or_update_filter(params).await.unwrap();
|
|
||||||
}
|
}
|
||||||
|
let change = change.unwrap();
|
||||||
|
let mut receiver = self.recv.take().unwrap();
|
||||||
|
af_spawn(async move {
|
||||||
|
match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await {
|
||||||
|
Ok(changed) => {
|
||||||
|
if let DatabaseViewChanged::FilterNotification(notification) = changed.unwrap() {
|
||||||
|
assert_eq!(
|
||||||
|
notification.visible_rows.len(),
|
||||||
|
change.showing_num_of_rows,
|
||||||
|
"visible rows not match"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
notification.invisible_rows.len(),
|
||||||
|
change.hiding_num_of_rows,
|
||||||
|
"invisible rows not match"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Process filter task timeout: {:?}", e);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_filter(&self, payload: UpdateFilterPayloadPB) {
|
||||||
|
let params: UpdateFilterParams = payload.try_into().unwrap();
|
||||||
|
self.editor.create_or_update_filter(params).await.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::ops::Deref for DatabaseFilterTest {
|
impl std::ops::Deref for DatabaseFilterTest {
|
||||||
type Target = DatabaseEditorTest;
|
type Target = DatabaseEditorTest;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::DerefMut for DatabaseFilterTest {
|
impl std::ops::DerefMut for DatabaseFilterTest {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.inner
|
&mut self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use flowy_database2::entities::{
|
use flowy_database2::entities::{
|
||||||
FieldType, TextFilterConditionPB, TextFilterPB, UpdateFilterPayloadPB,
|
FieldType, TextFilterConditionPB, TextFilterPB, UpdateFilterPayloadPB,
|
||||||
};
|
};
|
||||||
use flowy_database2::services::filter::FilterType;
|
use flowy_database2::services::filter::FilterContext;
|
||||||
|
|
||||||
use crate::database::filter_test::script::FilterScript::*;
|
use crate::database::filter_test::script::FilterScript::*;
|
||||||
use crate::database::filter_test::script::*;
|
use crate::database::filter_test::script::*;
|
||||||
@ -44,8 +44,7 @@ async fn grid_filter_text_is_not_empty_test() {
|
|||||||
test
|
test
|
||||||
.run_scripts(vec![
|
.run_scripts(vec![
|
||||||
DeleteFilter {
|
DeleteFilter {
|
||||||
filter_id: filter.id.clone(),
|
filter_context: FilterContext::from(&filter),
|
||||||
filter_type: FilterType::from(&filter),
|
|
||||||
changed: Some(FilterRowChanged {
|
changed: Some(FilterRowChanged {
|
||||||
showing_num_of_rows: 1,
|
showing_num_of_rows: 1,
|
||||||
hiding_num_of_rows: 0,
|
hiding_num_of_rows: 0,
|
||||||
@ -208,8 +207,7 @@ async fn grid_filter_delete_test() {
|
|||||||
test
|
test
|
||||||
.run_scripts(vec![
|
.run_scripts(vec![
|
||||||
DeleteFilter {
|
DeleteFilter {
|
||||||
filter_id: filter.id.clone(),
|
filter_context: FilterContext::from(&filter),
|
||||||
filter_type: FilterType::from(&filter),
|
|
||||||
changed: None,
|
changed: None,
|
||||||
},
|
},
|
||||||
AssertFilterCount { count: 0 },
|
AssertFilterCount { count: 0 },
|
||||||
|
@ -7,10 +7,10 @@ use collab_database::rows::RowId;
|
|||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use tokio::sync::broadcast::Receiver;
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
use flowy_database2::entities::{DeleteSortParams, FieldType, UpdateSortParams};
|
use flowy_database2::entities::{DeleteSortPayloadPB, FieldType, UpdateSortPayloadPB};
|
||||||
use flowy_database2::services::cell::stringify_cell_data;
|
use flowy_database2::services::cell::stringify_cell_data;
|
||||||
use flowy_database2::services::database_view::DatabaseViewChanged;
|
use flowy_database2::services::database_view::DatabaseViewChanged;
|
||||||
use flowy_database2::services::sort::{Sort, SortCondition, SortType};
|
use flowy_database2::services::sort::{Sort, SortCondition};
|
||||||
|
|
||||||
use crate::database::database_editor::DatabaseEditorTest;
|
use crate::database::database_editor::DatabaseEditorTest;
|
||||||
|
|
||||||
@ -20,7 +20,6 @@ pub enum SortScript {
|
|||||||
condition: SortCondition,
|
condition: SortCondition,
|
||||||
},
|
},
|
||||||
DeleteSort {
|
DeleteSort {
|
||||||
sort: Sort,
|
|
||||||
sort_id: String,
|
sort_id: String,
|
||||||
},
|
},
|
||||||
AssertCellContentOrder {
|
AssertCellContentOrder {
|
||||||
@ -71,17 +70,17 @@ impl DatabaseSortTest {
|
|||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
let params = UpdateSortParams {
|
let params = UpdateSortPayloadPB {
|
||||||
view_id: self.view_id.clone(),
|
view_id: self.view_id.clone(),
|
||||||
field_id: field.id.clone(),
|
field_id: field.id.clone(),
|
||||||
sort_id: None,
|
sort_id: None,
|
||||||
field_type: FieldType::from(field.field_type),
|
field_type: FieldType::from(field.field_type),
|
||||||
condition,
|
condition: condition.into(),
|
||||||
};
|
};
|
||||||
let sort_rev = self.editor.create_or_update_sort(params).await.unwrap();
|
let sort_rev = self.editor.create_or_update_sort(params).await.unwrap();
|
||||||
self.current_sort_rev = Some(sort_rev);
|
self.current_sort_rev = Some(sort_rev);
|
||||||
},
|
},
|
||||||
SortScript::DeleteSort { sort, sort_id } => {
|
SortScript::DeleteSort { sort_id } => {
|
||||||
self.recv = Some(
|
self.recv = Some(
|
||||||
self
|
self
|
||||||
.editor
|
.editor
|
||||||
@ -89,9 +88,8 @@ impl DatabaseSortTest {
|
|||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
let params = DeleteSortParams {
|
let params = DeleteSortPayloadPB {
|
||||||
view_id: self.view_id.clone(),
|
view_id: self.view_id.clone(),
|
||||||
sort_type: SortType::from(&sort),
|
|
||||||
sort_id,
|
sort_id,
|
||||||
};
|
};
|
||||||
self.editor.delete_sort(params).await.unwrap();
|
self.editor.delete_sort(params).await.unwrap();
|
||||||
|
@ -93,7 +93,6 @@ async fn sort_text_by_ascending_and_delete_sort_test() {
|
|||||||
let sort = test.current_sort_rev.as_ref().unwrap();
|
let sort = test.current_sort_rev.as_ref().unwrap();
|
||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
DeleteSort {
|
DeleteSort {
|
||||||
sort: sort.clone(),
|
|
||||||
sort_id: sort.id.clone(),
|
sort_id: sort.id.clone(),
|
||||||
},
|
},
|
||||||
AssertCellContentOrder {
|
AssertCellContentOrder {
|
||||||
|
Reference in New Issue
Block a user