mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -1,7 +1,7 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct CheckboxFilterPB {
|
||||
@ -9,9 +9,8 @@ pub struct CheckboxFilterPB {
|
||||
pub condition: CheckboxFilterConditionPB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
#[derive(Default)]
|
||||
pub enum CheckboxFilterConditionPB {
|
||||
#[default]
|
||||
IsChecked = 0,
|
||||
@ -24,7 +23,7 @@ impl std::convert::From<CheckboxFilterConditionPB> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for CheckboxFilterConditionPB {
|
||||
impl TryFrom<u8> for CheckboxFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
@ -36,22 +35,10 @@ impl std::convert::TryFrom<u8> for CheckboxFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for CheckboxFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
impl ParseFilterData for CheckboxFilterPB {
|
||||
fn parse(condition: u8, _content: String) -> Self {
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(CheckboxFilterConditionPB::IsChecked),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Filter> for CheckboxFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterConditionPB::try_from(filter.condition as u8)
|
||||
condition: CheckboxFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(CheckboxFilterConditionPB::IsChecked),
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct ChecklistFilterPB {
|
||||
@ -36,22 +36,10 @@ impl std::convert::TryFrom<u8> for ChecklistFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for ChecklistFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ChecklistFilterPB {
|
||||
condition: ChecklistFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(ChecklistFilterConditionPB::IsIncomplete),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Filter> for ChecklistFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
ChecklistFilterPB {
|
||||
condition: ChecklistFilterConditionPB::try_from(filter.condition as u8)
|
||||
impl ParseFilterData for ChecklistFilterPB {
|
||||
fn parse(condition: u8, _content: String) -> Self {
|
||||
Self {
|
||||
condition: ChecklistFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(ChecklistFilterConditionPB::IsIncomplete),
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DateFilterPB {
|
||||
@ -79,37 +79,17 @@ impl std::convert::TryFrom<u8> for DateFilterConditionPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromFilterString for DateFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let condition = DateFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(DateFilterConditionPB::DateIs);
|
||||
let mut date_filter = DateFilterPB {
|
||||
|
||||
impl ParseFilterData for DateFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
let condition =
|
||||
DateFilterConditionPB::try_from(condition).unwrap_or(DateFilterConditionPB::DateIs);
|
||||
let mut date_filter = Self {
|
||||
condition,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&filter.content) {
|
||||
date_filter.start = content.start;
|
||||
date_filter.end = content.end;
|
||||
date_filter.timestamp = content.timestamp;
|
||||
};
|
||||
|
||||
date_filter
|
||||
}
|
||||
}
|
||||
impl std::convert::From<&Filter> for DateFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
let condition = DateFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(DateFilterConditionPB::DateIs);
|
||||
let mut date_filter = DateFilterPB {
|
||||
condition,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&filter.content) {
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&content) {
|
||||
date_filter.start = content.start;
|
||||
date_filter.end = content.end;
|
||||
date_filter.timestamp = content.timestamp;
|
||||
|
@ -1,54 +1,22 @@
|
||||
use crate::entities::FilterPB;
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
use crate::entities::RepeatedFilterPB;
|
||||
use crate::services::filter::Filter;
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct FilterChangesetNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub insert_filters: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub delete_filters: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub update_filters: Vec<UpdatedFilter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct UpdatedFilter {
|
||||
#[pb(index = 1)]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub filter: Option<FilterPB>,
|
||||
pub filters: RepeatedFilterPB,
|
||||
}
|
||||
|
||||
impl FilterChangesetNotificationPB {
|
||||
pub fn from_insert(view_id: &str, filters: Vec<FilterPB>) -> Self {
|
||||
pub fn from_filters(view_id: &str, filters: &Vec<Filter>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: filters,
|
||||
delete_filters: Default::default(),
|
||||
update_filters: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn from_delete(view_id: &str, filters: Vec<FilterPB>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: Default::default(),
|
||||
delete_filters: filters,
|
||||
update_filters: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_update(view_id: &str, filters: Vec<UpdatedFilter>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: Default::default(),
|
||||
delete_filters: Default::default(),
|
||||
update_filters: filters,
|
||||
filters: filters.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct NumberFilterPB {
|
||||
@ -49,24 +49,12 @@ impl std::convert::TryFrom<u8> for NumberFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for NumberFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
impl ParseFilterData for NumberFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
NumberFilterPB {
|
||||
condition: NumberFilterConditionPB::try_from(filter.condition as u8)
|
||||
condition: NumberFilterConditionPB::try_from(condition)
|
||||
.unwrap_or(NumberFilterConditionPB::Equal),
|
||||
content: filter.content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<&Filter> for NumberFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
NumberFilterPB {
|
||||
condition: NumberFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(NumberFilterConditionPB::Equal),
|
||||
content: filter.content.clone(),
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RelationFilterPB {
|
||||
@ -8,17 +8,8 @@ pub struct RelationFilterPB {
|
||||
pub condition: i64,
|
||||
}
|
||||
|
||||
impl FromFilterString for RelationFilterPB {
|
||||
fn from_filter(_filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
RelationFilterPB { condition: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Filter> for RelationFilterPB {
|
||||
fn from(_filter: &Filter) -> Self {
|
||||
impl ParseFilterData for RelationFilterPB {
|
||||
fn parse(_condition: u8, _content: String) -> Self {
|
||||
RelationFilterPB { condition: 0 }
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ use std::str::FromStr;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::{field::SelectOptionIds, filter::ParseFilterData};
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SelectOptionFilterPB {
|
||||
@ -45,27 +44,14 @@ impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromFilterString for SelectOptionFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let ids = SelectOptionIds::from_str(&filter.content).unwrap_or_default();
|
||||
SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::try_from(filter.condition as u8)
|
||||
impl ParseFilterData for SelectOptionFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
Self {
|
||||
condition: SelectOptionConditionPB::try_from(condition)
|
||||
.unwrap_or(SelectOptionConditionPB::OptionIs),
|
||||
option_ids: ids.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Filter> for SelectOptionFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
let ids = SelectOptionIds::from_str(&filter.content).unwrap_or_default();
|
||||
SelectOptionFilterPB {
|
||||
condition: SelectOptionConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(SelectOptionConditionPB::OptionIs),
|
||||
option_ids: ids.into_inner(),
|
||||
option_ids: SelectOptionIds::from_str(&content)
|
||||
.unwrap_or_default()
|
||||
.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::services::filter::{Filter, FromFilterString};
|
||||
use crate::services::filter::ParseFilterData;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct TextFilterPB {
|
||||
@ -51,25 +51,11 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromFilterString for TextFilterPB {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
TextFilterPB {
|
||||
condition: TextFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(TextFilterConditionPB::Is),
|
||||
content: filter.content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Filter> for TextFilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
TextFilterPB {
|
||||
condition: TextFilterConditionPB::try_from(filter.condition as u8)
|
||||
.unwrap_or(TextFilterConditionPB::Is),
|
||||
content: filter.content.clone(),
|
||||
impl ParseFilterData for TextFilterPB {
|
||||
fn parse(condition: u8, content: String) -> Self {
|
||||
Self {
|
||||
condition: TextFilterConditionPB::try_from(condition).unwrap_or(TextFilterConditionPB::Is),
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,216 +1,274 @@
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::fields::Field;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use protobuf::ProtobufError;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType,
|
||||
NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, RelationFilterPB,
|
||||
SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::Filter;
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterInner};
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, ProtoBuf_Enum, Eq, PartialEq, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum FilterType {
|
||||
#[default]
|
||||
Data = 0,
|
||||
And = 1,
|
||||
Or = 2,
|
||||
}
|
||||
|
||||
impl From<&FilterInner> for FilterType {
|
||||
fn from(value: &FilterInner) -> Self {
|
||||
match value {
|
||||
FilterInner::And { .. } => Self::And,
|
||||
FilterInner::Or { .. } => Self::Or,
|
||||
FilterInner::Data { .. } => Self::Data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)]
|
||||
pub struct FilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
pub filter_type: FilterType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub children: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub data: Option<FilterDataPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)]
|
||||
pub struct FilterDataPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
#[pb(index = 3)]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<&Filter> for FilterPB {
|
||||
impl From<&Filter> for FilterPB {
|
||||
fn from(filter: &Filter) -> Self {
|
||||
let bytes: Bytes = match filter.field_type {
|
||||
FieldType::RichText => TextFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::Number => NumberFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
DateFilterPB::from(filter).try_into().unwrap()
|
||||
match &filter.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => Self {
|
||||
id: filter.id.clone(),
|
||||
filter_type: FilterType::from(&filter.inner),
|
||||
children: children.iter().map(FilterPB::from).collect(),
|
||||
data: None,
|
||||
},
|
||||
FilterInner::Data {
|
||||
field_id,
|
||||
field_type,
|
||||
condition_and_content,
|
||||
} => {
|
||||
let bytes: Result<Bytes, ProtobufError> = match field_type {
|
||||
FieldType::RichText | FieldType::URL => condition_and_content
|
||||
.cloned::<TextFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::Number => condition_and_content
|
||||
.cloned::<NumberFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::DateTime | FieldType::CreatedTime | FieldType::LastEditedTime => {
|
||||
condition_and_content
|
||||
.cloned::<DateFilterPB>()
|
||||
.unwrap()
|
||||
.try_into()
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => condition_and_content
|
||||
.cloned::<SelectOptionFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::Checklist => condition_and_content
|
||||
.cloned::<ChecklistFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
FieldType::Checkbox => condition_and_content
|
||||
.cloned::<CheckboxFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
|
||||
FieldType::Relation => condition_and_content
|
||||
.cloned::<RelationFilterPB>()
|
||||
.unwrap()
|
||||
.try_into(),
|
||||
};
|
||||
|
||||
Self {
|
||||
id: filter.id.clone(),
|
||||
filter_type: FilterType::Data,
|
||||
children: vec![],
|
||||
data: Some(FilterDataPB {
|
||||
field_id: field_id.clone(),
|
||||
field_type: *field_type,
|
||||
data: bytes.unwrap().to_vec(),
|
||||
}),
|
||||
}
|
||||
},
|
||||
FieldType::SingleSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::MultiSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::Checklist => ChecklistFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::Checkbox => CheckboxFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::URL => TextFilterPB::from(filter).try_into().unwrap(),
|
||||
FieldType::Relation => RelationFilterPB::from(filter).try_into().unwrap(),
|
||||
};
|
||||
Self {
|
||||
id: filter.id.clone(),
|
||||
field_id: filter.field_id.clone(),
|
||||
field_type: filter.field_type,
|
||||
data: bytes.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FilterDataPB> for FilterInner {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: FilterDataPB) -> Result<Self, Self::Error> {
|
||||
let bytes: &[u8] = value.data.as_ref();
|
||||
let condition_and_content = match value.field_type {
|
||||
FieldType::RichText | FieldType::URL => {
|
||||
BoxAny::new(TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
BoxAny::new(CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Number => {
|
||||
BoxAny::new(NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
BoxAny::new(DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
BoxAny::new(SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Checklist => {
|
||||
BoxAny::new(ChecklistFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
FieldType::Relation => {
|
||||
BoxAny::new(RelationFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?)
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self::Data {
|
||||
field_id: value.field_id,
|
||||
field_type: value.field_type,
|
||||
condition_and_content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FilterPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<Filter>>> for RepeatedFilterPB {
|
||||
fn from(filters: Vec<Arc<Filter>>) -> Self {
|
||||
impl From<&Vec<Filter>> for RepeatedFilterPB {
|
||||
fn from(filters: &Vec<Filter>) -> Self {
|
||||
RepeatedFilterPB {
|
||||
items: filters.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
items: filters.iter().map(|filter| filter.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
|
||||
impl From<Vec<FilterPB>> for RepeatedFilterPB {
|
||||
fn from(items: Vec<FilterPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||
pub struct DeleteFilterPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||
pub field_id: String,
|
||||
pub struct InsertFilterPB {
|
||||
/// If None, the filter will be the root of a new filter tree
|
||||
#[pb(index = 1, one_of)]
|
||||
#[validate(custom = "crate::entities::utils::validate_filter_id")]
|
||||
pub parent_filter_id: Option<String>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
#[validate(custom = "crate::entities::utils::validate_filter_id")]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||
pub view_id: String,
|
||||
pub data: FilterDataPB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||
pub struct UpdateFilterPayloadPB {
|
||||
pub struct UpdateFilterTypePB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||
pub field_id: String,
|
||||
#[validate(custom = "crate::entities::utils::validate_filter_id")]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
pub filter_type: FilterType,
|
||||
}
|
||||
|
||||
/// Create a new filter if the filter_id is None
|
||||
#[pb(index = 3, one_of)]
|
||||
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||
pub struct UpdateFilterDataPB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom = "crate::entities::utils::validate_filter_id")]
|
||||
pub filter_id: Option<String>,
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub data: Vec<u8>,
|
||||
#[pb(index = 2)]
|
||||
pub data: FilterDataPB,
|
||||
}
|
||||
|
||||
#[pb(index = 5)]
|
||||
#[derive(ProtoBuf, Debug, Default, Clone, Validate)]
|
||||
pub struct DeleteFilterPB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom = "crate::entities::utils::validate_filter_id")]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
}
|
||||
|
||||
impl UpdateFilterPayloadPB {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<T: TryInto<Bytes, Error = ::protobuf::ProtobufError>>(
|
||||
view_id: &str,
|
||||
field: &Field,
|
||||
data: T,
|
||||
) -> Self {
|
||||
let data = data.try_into().unwrap_or_else(|_| Bytes::new());
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
Self {
|
||||
view_id: view_id.to_owned(),
|
||||
field_id: field.id.clone(),
|
||||
field_type,
|
||||
filter_id: None,
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<UpdateFilterParams> for UpdateFilterPayloadPB {
|
||||
impl TryFrom<InsertFilterPB> for FilterChangeset {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<UpdateFilterParams, 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 = match self.filter_id {
|
||||
None => None,
|
||||
Some(filter_id) => Some(
|
||||
NotEmptyStr::parse(filter_id)
|
||||
.map_err(|_| ErrorCode::FilterIdIsEmpty)?
|
||||
.0,
|
||||
),
|
||||
fn try_from(value: InsertFilterPB) -> Result<Self, Self::Error> {
|
||||
let changeset = Self::Insert {
|
||||
parent_filter_id: value.parent_filter_id,
|
||||
data: value.data.try_into()?,
|
||||
};
|
||||
let condition;
|
||||
let mut content = "".to_string();
|
||||
let bytes: &[u8] = self.data.as_ref();
|
||||
|
||||
match self.field_type {
|
||||
FieldType::RichText | FieldType::URL => {
|
||||
let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = filter.content;
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
},
|
||||
FieldType::Number => {
|
||||
let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = filter.content;
|
||||
},
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = DateFilterContentPB {
|
||||
start: filter.start,
|
||||
end: filter.end,
|
||||
timestamp: filter.timestamp,
|
||||
}
|
||||
.to_string();
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
|
||||
let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = SelectOptionIds::from(filter.option_ids).to_string();
|
||||
},
|
||||
FieldType::Relation => {
|
||||
let filter = RelationFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(UpdateFilterParams {
|
||||
view_id,
|
||||
field_id,
|
||||
filter_id,
|
||||
field_type: self.field_type,
|
||||
condition: condition as i64,
|
||||
content,
|
||||
})
|
||||
Ok(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateFilterParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
/// Create a new filter if the filter_id is None
|
||||
pub filter_id: Option<String>,
|
||||
pub field_type: FieldType,
|
||||
pub condition: i64,
|
||||
pub content: String,
|
||||
impl TryFrom<UpdateFilterDataPB> for FilterChangeset {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: UpdateFilterDataPB) -> Result<Self, Self::Error> {
|
||||
let changeset = Self::UpdateData {
|
||||
filter_id: value.filter_id,
|
||||
data: value.data.try_into()?,
|
||||
};
|
||||
|
||||
Ok(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<UpdateFilterTypePB> for FilterChangeset {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: UpdateFilterTypePB) -> Result<Self, Self::Error> {
|
||||
if matches!(value.filter_type, FilterType::Data) {
|
||||
return Err(ErrorCode::InvalidParams);
|
||||
}
|
||||
|
||||
let changeset = Self::UpdateType {
|
||||
filter_id: value.filter_id,
|
||||
filter_type: value.filter_type,
|
||||
};
|
||||
Ok(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeleteFilterPB> for FilterChangeset {
|
||||
fn from(value: DeleteFilterPB) -> Self {
|
||||
Self::Delete {
|
||||
filter_id: value.filter_id,
|
||||
field_id: value.field_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ use validator::Validate;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CalendarLayoutSettingPB, DeleteFilterPayloadPB, DeleteSortPayloadPB, RepeatedFieldSettingsPB,
|
||||
RepeatedFilterPB, RepeatedGroupSettingPB, RepeatedSortPB, UpdateFilterPayloadPB, UpdateGroupPB,
|
||||
UpdateSortPayloadPB,
|
||||
CalendarLayoutSettingPB, DeleteFilterPB, DeleteSortPayloadPB, InsertFilterPB,
|
||||
RepeatedFieldSettingsPB, RepeatedFilterPB, RepeatedGroupSettingPB, RepeatedSortPB,
|
||||
UpdateFilterDataPB, UpdateFilterTypePB, UpdateGroupPB, UpdateSortPayloadPB,
|
||||
};
|
||||
use crate::services::setting::{BoardLayoutSetting, CalendarLayoutSetting};
|
||||
|
||||
@ -79,26 +79,34 @@ pub struct DatabaseSettingChangesetPB {
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
#[validate]
|
||||
pub update_filter: Option<UpdateFilterPayloadPB>,
|
||||
pub insert_filter: Option<InsertFilterPB>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
#[validate]
|
||||
pub delete_filter: Option<DeleteFilterPayloadPB>,
|
||||
pub update_filter_type: Option<UpdateFilterTypePB>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
#[validate]
|
||||
pub update_group: Option<UpdateGroupPB>,
|
||||
pub update_filter_data: Option<UpdateFilterDataPB>,
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
#[validate]
|
||||
pub update_sort: Option<UpdateSortPayloadPB>,
|
||||
pub delete_filter: Option<DeleteFilterPB>,
|
||||
|
||||
#[pb(index = 7, one_of)]
|
||||
#[validate]
|
||||
pub reorder_sort: Option<ReorderSortPayloadPB>,
|
||||
pub update_group: Option<UpdateGroupPB>,
|
||||
|
||||
#[pb(index = 8, one_of)]
|
||||
#[validate]
|
||||
pub update_sort: Option<UpdateSortPayloadPB>,
|
||||
|
||||
#[pb(index = 9, one_of)]
|
||||
#[validate]
|
||||
pub reorder_sort: Option<ReorderSortPayloadPB>,
|
||||
|
||||
#[pb(index = 10, one_of)]
|
||||
#[validate]
|
||||
pub delete_sort: Option<DeleteSortPayloadPB>,
|
||||
}
|
||||
|
||||
|
@ -91,14 +91,28 @@ pub(crate) async fn update_database_setting_handler(
|
||||
let params = data.try_into_inner()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
|
||||
if let Some(update_filter) = params.update_filter {
|
||||
if let Some(payload) = params.insert_filter {
|
||||
database_editor
|
||||
.create_or_update_filter(update_filter.try_into()?)
|
||||
.modify_view_filters(¶ms.view_id, payload.try_into()?)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(delete_filter) = params.delete_filter {
|
||||
database_editor.delete_filter(delete_filter).await?;
|
||||
if let Some(payload) = params.update_filter_type {
|
||||
database_editor
|
||||
.modify_view_filters(¶ms.view_id, payload.try_into()?)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(payload) = params.update_filter_data {
|
||||
database_editor
|
||||
.modify_view_filters(¶ms.view_id, payload.try_into()?)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(payload) = params.delete_filter {
|
||||
database_editor
|
||||
.modify_view_filters(¶ms.view_id, payload.into())
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(update_sort) = params.update_sort {
|
||||
|
@ -36,8 +36,8 @@ impl CalculationsService {
|
||||
let mut sum = 0.0;
|
||||
let mut len = 0.0;
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
for row_cell in row_cells {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
@ -131,8 +131,8 @@ impl CalculationsService {
|
||||
|
||||
fn calculate_count_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if !row_cells.is_empty() {
|
||||
return format!(
|
||||
@ -154,8 +154,8 @@ impl CalculationsService {
|
||||
|
||||
fn calculate_count_non_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
if !row_cells.is_empty() {
|
||||
return format!(
|
||||
@ -183,8 +183,8 @@ impl CalculationsService {
|
||||
let mut values = vec![];
|
||||
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
if let Some(handler) =
|
||||
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
for row_cell in row_cells {
|
||||
if let Some(cell) = &row_cell.cell {
|
||||
|
@ -4,4 +4,3 @@ use std::sync::Arc;
|
||||
use crate::utils::cache::AnyTypeCache;
|
||||
|
||||
pub type CellCache = Arc<RwLock<AnyTypeCache<u64>>>;
|
||||
pub type CellFilterCache = Arc<RwLock<AnyTypeCache<String>>>;
|
||||
|
@ -75,7 +75,7 @@ pub fn apply_cell_changeset(
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Result<Cell, FlowyError> {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache)
|
||||
match TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
None => Ok(Cell::default()),
|
||||
@ -128,7 +128,7 @@ pub fn try_decode_cell_to_cell_protobuf(
|
||||
field: &Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache)
|
||||
match TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)
|
||||
{
|
||||
None => Ok(CellProtobufBlob::default()),
|
||||
@ -143,7 +143,7 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
|
||||
field: &Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Option<T> {
|
||||
let handler = TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache)
|
||||
let handler = TypeOptionCellExt::new(field, cell_data_cache)
|
||||
.get_type_option_cell_data_handler(to_field_type)?;
|
||||
handler
|
||||
.get_cell_data(cell, from_field_type, field)
|
||||
@ -152,8 +152,8 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
|
||||
}
|
||||
|
||||
/// Returns a string that represents the current field_type's cell data.
|
||||
/// For example, The string of the Multi-Select cell will be a list of the option's name
|
||||
/// separated by a comma.
|
||||
/// For example, a Multi-Select cell will be represented by a list of the options' names
|
||||
/// separated by commas.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@ -162,16 +162,13 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
|
||||
/// * `from_field_type`: the original field type of the passed-in cell data.
|
||||
/// * `field`: used to get the corresponding TypeOption for the specified field type.
|
||||
///
|
||||
/// returns: String
|
||||
pub fn stringify_cell_data(
|
||||
cell: &Cell,
|
||||
to_field_type: &FieldType,
|
||||
from_field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> String {
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field, None)
|
||||
.get_type_option_cell_data_handler(from_field_type)
|
||||
{
|
||||
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(from_field_type) {
|
||||
None => "".to_string(),
|
||||
Some(handler) => handler.handle_stringify_cell(cell, to_field_type, field),
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ use std::sync::Arc;
|
||||
use collab_database::database::MutexDatabase;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, OrderObjectPosition};
|
||||
use collab_database::views::{
|
||||
DatabaseLayout, DatabaseView, FilterMap, LayoutSetting, OrderObjectPosition,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
@ -32,7 +34,7 @@ use crate::services::field::{
|
||||
use crate::services::field_settings::{
|
||||
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
|
||||
};
|
||||
use crate::services::filter::Filter;
|
||||
use crate::services::filter::{Filter, FilterChangeset};
|
||||
use crate::services::group::{default_group_setting, GroupChangesets, GroupSetting, RowChangeset};
|
||||
use crate::services::share::csv::{CSVExport, CSVFormat};
|
||||
use crate::services::sort::Sort;
|
||||
@ -214,16 +216,13 @@ impl DatabaseEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn create_or_update_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.v_insert_filter(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.v_delete_filter(params).await?;
|
||||
pub async fn modify_view_filters(
|
||||
&self,
|
||||
view_id: &str,
|
||||
changeset: FilterChangeset,
|
||||
) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(view_id).await?;
|
||||
view_editor.v_modify_filters(changeset).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -267,7 +266,8 @@ impl DatabaseEditor {
|
||||
|
||||
pub async fn get_all_filters(&self, view_id: &str) -> RepeatedFilterPB {
|
||||
if let Ok(view_editor) = self.database_views.get_view_editor(view_id).await {
|
||||
view_editor.v_get_all_filters().await.into()
|
||||
let filters = view_editor.v_get_all_filters().await;
|
||||
RepeatedFilterPB::from(&filters)
|
||||
} else {
|
||||
RepeatedFilterPB { items: vec![] }
|
||||
}
|
||||
@ -1259,10 +1259,9 @@ impl DatabaseEditor {
|
||||
row_ids: Option<&Vec<String>>,
|
||||
) -> FlowyResult<Vec<RelatedRowDataPB>> {
|
||||
let primary_field = self.database.lock().fields.get_primary_field().unwrap();
|
||||
let handler =
|
||||
TypeOptionCellExt::new_with_cell_data_cache(&primary_field, Some(self.cell_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&FieldType::RichText)
|
||||
.ok_or(FlowyError::internal())?;
|
||||
let handler = TypeOptionCellExt::new(&primary_field, Some(self.cell_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&FieldType::RichText)
|
||||
.ok_or(FlowyError::internal())?;
|
||||
|
||||
let row_data = {
|
||||
let database = self.database.lock();
|
||||
@ -1566,13 +1565,12 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
.get_calculation::<Calculation>(view_id, field_id)
|
||||
}
|
||||
|
||||
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>> {
|
||||
fn get_all_filters(&self, view_id: &str) -> Vec<Filter> {
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.get_all_filters(view_id)
|
||||
.into_iter()
|
||||
.map(Arc::new)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -1581,7 +1579,14 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
}
|
||||
|
||||
fn insert_filter(&self, view_id: &str, filter: Filter) {
|
||||
self.database.lock().insert_filter(view_id, filter);
|
||||
self.database.lock().insert_filter(view_id, &filter);
|
||||
}
|
||||
|
||||
fn save_filters(&self, view_id: &str, filters: &[Filter]) {
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.save_filters::<Filter, FilterMap>(view_id, filters);
|
||||
}
|
||||
|
||||
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter> {
|
||||
@ -1591,15 +1596,6 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
.get_filter::<Filter>(view_id, filter_id)
|
||||
}
|
||||
|
||||
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter> {
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.get_all_filters::<Filter>(view_id)
|
||||
.into_iter()
|
||||
.find(|filter| filter.field_id == field_id)
|
||||
}
|
||||
|
||||
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting> {
|
||||
self.database.lock().get_layout_setting(view_id, layout_ty)
|
||||
}
|
||||
@ -1632,7 +1628,7 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
field: &Field,
|
||||
field_type: &FieldType,
|
||||
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
|
||||
TypeOptionCellExt::new_with_cell_data_cache(field, Some(self.cell_cache.clone()))
|
||||
TypeOptionCellExt::new(field, Some(self.cell_cache.clone()))
|
||||
.get_type_option_cell_data_handler(field_type)
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,7 @@ use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::database::{
|
||||
gen_database_calculation_id, gen_database_filter_id, gen_database_sort_id,
|
||||
};
|
||||
use collab_database::database::{gen_database_calculation_id, gen_database_sort_id};
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView};
|
||||
@ -15,11 +13,10 @@ use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::af_spawn;
|
||||
|
||||
use crate::entities::{
|
||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterPayloadPB,
|
||||
DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB,
|
||||
LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB,
|
||||
RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB,
|
||||
UpdateFilterParams, UpdateSortPayloadPB,
|
||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType,
|
||||
FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB, LayoutSettingChangeset,
|
||||
LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB,
|
||||
SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
||||
@ -37,9 +34,7 @@ use crate::services::database_view::{
|
||||
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
|
||||
};
|
||||
use crate::services::field_settings::FieldSettings;
|
||||
use crate::services::filter::{
|
||||
Filter, FilterChangeset, FilterContext, FilterController, UpdatedFilter,
|
||||
};
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterController};
|
||||
use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowContext, RowChangeset};
|
||||
use crate::services::setting::CalendarLayoutSetting;
|
||||
use crate::services::sort::{Sort, SortChangeset, SortController};
|
||||
@ -618,73 +613,28 @@ impl DatabaseViewEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn v_get_all_filters(&self) -> Vec<Arc<Filter>> {
|
||||
pub async fn v_get_all_filters(&self) -> Vec<Filter> {
|
||||
self.delegate.get_all_filters(&self.view_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn v_insert_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> {
|
||||
let is_exist = params.filter_id.is_some();
|
||||
let filter_id = match params.filter_id {
|
||||
None => gen_database_filter_id(),
|
||||
Some(filter_id) => filter_id,
|
||||
};
|
||||
let filter = Filter {
|
||||
id: filter_id.clone(),
|
||||
field_id: params.field_id.clone(),
|
||||
field_type: params.field_type,
|
||||
condition: params.condition,
|
||||
content: params.content,
|
||||
};
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
let changeset = if is_exist {
|
||||
let old_filter = self.delegate.get_filter(&self.view_id, &filter.id);
|
||||
|
||||
self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||
filter_controller
|
||||
.did_receive_changes(FilterChangeset::from_update(UpdatedFilter::new(
|
||||
old_filter, filter,
|
||||
)))
|
||||
.await
|
||||
} else {
|
||||
self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||
filter_controller
|
||||
.did_receive_changes(FilterChangeset::from_insert(filter))
|
||||
.await
|
||||
};
|
||||
drop(filter_controller);
|
||||
|
||||
if let Some(changeset) = changeset {
|
||||
notify_did_update_filter(changeset).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn v_delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> {
|
||||
let filter_context = FilterContext {
|
||||
filter_id: params.filter_id.clone(),
|
||||
field_id: params.field_id.clone(),
|
||||
field_type: params.field_type,
|
||||
};
|
||||
let changeset = self
|
||||
.filter_controller
|
||||
.did_receive_changes(FilterChangeset::from_delete(filter_context.clone()))
|
||||
.await;
|
||||
|
||||
self
|
||||
.delegate
|
||||
.delete_filter(&self.view_id, ¶ms.filter_id);
|
||||
if changeset.is_some() {
|
||||
notify_did_update_filter(changeset.unwrap()).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn v_get_filter(&self, filter_id: &str) -> Option<Filter> {
|
||||
self.delegate.get_filter(&self.view_id, filter_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn v_modify_filters(&self, changeset: FilterChangeset) -> FlowyResult<()> {
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
|
||||
// self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||
|
||||
let notification = filter_controller.apply_changeset(changeset).await;
|
||||
|
||||
drop(filter_controller);
|
||||
|
||||
notify_did_update_filter(notification).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current calendar settings
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn v_get_layout_settings(&self, layout_ty: &DatabaseLayout) -> LayoutSettingParams {
|
||||
@ -830,25 +780,13 @@ impl DatabaseViewEditor {
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(filter) = self
|
||||
.delegate
|
||||
.get_filter_by_field_id(&self.view_id, field_id)
|
||||
{
|
||||
let old = Filter {
|
||||
field_type: FieldType::from(old_field.field_type),
|
||||
..filter.clone()
|
||||
};
|
||||
let updated_filter = UpdatedFilter::new(Some(old), filter);
|
||||
let filter_changeset = FilterChangeset::from_update(updated_filter);
|
||||
if old_field.field_type != field.field_type {
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
af_spawn(async move {
|
||||
if let Some(notification) = filter_controller
|
||||
.did_receive_changes(filter_changeset)
|
||||
.await
|
||||
{
|
||||
notify_did_update_filter(notification).await;
|
||||
}
|
||||
});
|
||||
let changeset = FilterChangeset::DeleteAllWithFieldId {
|
||||
field_id: field.id.clone(),
|
||||
};
|
||||
let notification = filter_controller.apply_changeset(changeset).await;
|
||||
notify_did_update_filter(notification).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{RowDetail, RowId};
|
||||
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use lib_infra::future::Fut;
|
||||
|
||||
use crate::services::cell::CellCache;
|
||||
use crate::services::database_view::{
|
||||
@ -46,11 +46,6 @@ pub async fn make_filter_controller(
|
||||
struct DatabaseViewFilterDelegateImpl(Arc<dyn DatabaseViewOperation>);
|
||||
|
||||
impl FilterDelegate for DatabaseViewFilterDelegateImpl {
|
||||
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>> {
|
||||
let filter = self.0.get_filter(view_id, filter_id).map(Arc::new);
|
||||
to_fut(async move { filter })
|
||||
}
|
||||
|
||||
fn get_field(&self, field_id: &str) -> Option<Field> {
|
||||
self.0.get_field(field_id)
|
||||
}
|
||||
@ -66,4 +61,8 @@ impl FilterDelegate for DatabaseViewFilterDelegateImpl {
|
||||
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>> {
|
||||
self.0.get_row(view_id, rows_id)
|
||||
}
|
||||
|
||||
fn save_filters(&self, view_id: &str, filters: &[Filter]) {
|
||||
self.0.save_filters(view_id, filters)
|
||||
}
|
||||
}
|
||||
|
@ -91,15 +91,15 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
|
||||
|
||||
fn remove_calculation(&self, view_id: &str, calculation_id: &str);
|
||||
|
||||
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>>;
|
||||
fn get_all_filters(&self, view_id: &str) -> Vec<Filter>;
|
||||
|
||||
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
|
||||
|
||||
fn delete_filter(&self, view_id: &str, filter_id: &str);
|
||||
|
||||
fn insert_filter(&self, view_id: &str, filter: Filter);
|
||||
|
||||
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
|
||||
|
||||
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter>;
|
||||
fn save_filters(&self, view_id: &str, filters: &[Filter]);
|
||||
|
||||
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting>;
|
||||
|
||||
|
@ -177,7 +177,7 @@ impl TypeOptionCellDataFilter for TimestampTypeOption {
|
||||
_filter: &<Self as TypeOption>::CellFilter,
|
||||
_cell_data: &<Self as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
false
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption,
|
||||
RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, URLTypeOption,
|
||||
};
|
||||
use crate::services::filter::FromFilterString;
|
||||
use crate::services::filter::ParseFilterData;
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
pub trait TypeOption {
|
||||
@ -58,7 +58,7 @@ pub trait TypeOption {
|
||||
type CellProtobufType: TryInto<Bytes, Error = ProtobufError> + Debug;
|
||||
|
||||
/// Represents the filter configuration for this type option.
|
||||
type CellFilter: FromFilterString + Send + Sync + 'static;
|
||||
type CellFilter: ParseFilterData + Clone + Send + Sync + 'static;
|
||||
}
|
||||
/// This trait providing serialization and deserialization methods for cell data.
|
||||
///
|
||||
|
@ -9,9 +9,7 @@ use flowy_error::FlowyResult;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{
|
||||
CellCache, CellDataChangeset, CellDataDecoder, CellFilterCache, CellProtobufBlob,
|
||||
};
|
||||
use crate::services::cell::{CellCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob};
|
||||
use crate::services::field::checklist_type_option::ChecklistTypeOption;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption,
|
||||
@ -55,7 +53,7 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering;
|
||||
|
||||
fn handle_cell_filter(&self, field_type: &FieldType, field: &Field, cell: &Cell) -> bool;
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool;
|
||||
|
||||
/// Format the cell to string using the passed-in [FieldType] and [Field].
|
||||
/// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell.
|
||||
@ -100,7 +98,6 @@ impl AsRef<u64> for CellDataCacheKey {
|
||||
struct TypeOptionCellDataHandlerImpl<T> {
|
||||
inner: T,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
cell_filter_cache: Option<CellFilterCache>,
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandlerImpl<T>
|
||||
@ -122,13 +119,11 @@ where
|
||||
|
||||
pub fn new_with_boxed(
|
||||
inner: T,
|
||||
cell_filter_cache: Option<CellFilterCache>,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
) -> Box<dyn TypeOptionCellDataHandler> {
|
||||
Self {
|
||||
inner,
|
||||
cell_data_cache,
|
||||
cell_filter_cache,
|
||||
}
|
||||
.into_boxed()
|
||||
}
|
||||
@ -308,11 +303,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_cell_filter(&self, field_type: &FieldType, field: &Field, cell: &Cell) -> bool {
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool {
|
||||
let perform_filter = || {
|
||||
let filter_cache = self.cell_filter_cache.as_ref()?.read();
|
||||
let cell_filter = filter_cache.get::<<Self as TypeOption>::CellFilter>(&field.id)?;
|
||||
let cell_data = self.get_decoded_cell_data(cell, field_type, field).ok()?;
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let cell_filter = filter.downcast_ref::<<Self as TypeOption>::CellFilter>()?;
|
||||
let cell_data = self.get_decoded_cell_data(cell, &field_type, field).ok()?;
|
||||
Some(self.apply_filter(cell_filter, &cell_data))
|
||||
};
|
||||
|
||||
@ -362,28 +357,16 @@ where
|
||||
pub struct TypeOptionCellExt<'a> {
|
||||
field: &'a Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
cell_filter_cache: Option<CellFilterCache>,
|
||||
}
|
||||
|
||||
impl<'a> TypeOptionCellExt<'a> {
|
||||
pub fn new_with_cell_data_cache(field: &'a Field, cell_data_cache: Option<CellCache>) -> Self {
|
||||
pub fn new(field: &'a Field, cell_data_cache: Option<CellCache>) -> Self {
|
||||
Self {
|
||||
field,
|
||||
cell_data_cache,
|
||||
cell_filter_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
field: &'a Field,
|
||||
cell_data_cache: Option<CellCache>,
|
||||
cell_filter_cache: Option<CellFilterCache>,
|
||||
) -> Self {
|
||||
let mut this = Self::new_with_cell_data_cache(field, cell_data_cache);
|
||||
this.cell_filter_cache = cell_filter_cache;
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get_cells<T>(&self) -> Vec<T> {
|
||||
let field_type = FieldType::from(self.field.field_type);
|
||||
match self.get_type_option_cell_data_handler(&field_type) {
|
||||
@ -403,103 +386,63 @@ impl<'a> TypeOptionCellExt<'a> {
|
||||
.field
|
||||
.get_type_option::<RichTextTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::Number => self
|
||||
.field
|
||||
.get_type_option::<NumberTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::DateTime => self
|
||||
.field
|
||||
.get_type_option::<DateTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::LastEditedTime | FieldType::CreatedTime => self
|
||||
.field
|
||||
.get_type_option::<TimestampTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::SingleSelect => self
|
||||
.field
|
||||
.get_type_option::<SingleSelectTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::MultiSelect => self
|
||||
.field
|
||||
.get_type_option::<MultiSelectTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::Checkbox => self
|
||||
.field
|
||||
.get_type_option::<CheckboxTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::URL => {
|
||||
self
|
||||
.field
|
||||
.get_type_option::<URLTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
})
|
||||
},
|
||||
FieldType::Checklist => self
|
||||
.field
|
||||
.get_type_option::<ChecklistTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
FieldType::Relation => self
|
||||
.field
|
||||
.get_type_option::<RelationTypeOption>(field_type)
|
||||
.map(|type_option| {
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(
|
||||
type_option,
|
||||
self.cell_filter_cache.clone(),
|
||||
self.cell_data_cache.clone(),
|
||||
)
|
||||
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::database::gen_database_filter_id;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{Cell, Row, RowDetail, RowId};
|
||||
use collab_database::rows::{Row, RowDetail, RowId};
|
||||
use dashmap::DashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::RwLock;
|
||||
@ -14,33 +15,26 @@ use lib_infra::priority_task::{QualityOfService, Task, TaskContent, TaskDispatch
|
||||
|
||||
use crate::entities::filter_entities::*;
|
||||
use crate::entities::{FieldType, InsertedRowPB, RowMetaPB};
|
||||
use crate::services::cell::{CellCache, CellFilterCache};
|
||||
use crate::services::cell::CellCache;
|
||||
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
|
||||
use crate::services::field::*;
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification};
|
||||
use crate::utils::cache::AnyTypeCache;
|
||||
use crate::services::field::TypeOptionCellExt;
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterInner, FilterResultNotification};
|
||||
|
||||
pub trait FilterDelegate: Send + Sync + 'static {
|
||||
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>>;
|
||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
|
||||
}
|
||||
|
||||
pub trait FromFilterString {
|
||||
fn from_filter(filter: &Filter) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn save_filters(&self, view_id: &str, filters: &[Filter]);
|
||||
}
|
||||
|
||||
pub struct FilterController {
|
||||
view_id: String,
|
||||
handler_id: String,
|
||||
delegate: Box<dyn FilterDelegate>,
|
||||
result_by_row_id: DashMap<RowId, FilterResult>,
|
||||
result_by_row_id: DashMap<RowId, bool>,
|
||||
cell_cache: CellCache,
|
||||
cell_filter_cache: CellFilterCache,
|
||||
filters: RwLock<Vec<Filter>>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
notifier: DatabaseViewChangedNotifier,
|
||||
}
|
||||
@ -57,26 +51,23 @@ impl FilterController {
|
||||
handler_id: &str,
|
||||
delegate: T,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
filters: Vec<Arc<Filter>>,
|
||||
filters: Vec<Filter>,
|
||||
cell_cache: CellCache,
|
||||
notifier: DatabaseViewChangedNotifier,
|
||||
) -> Self
|
||||
where
|
||||
T: FilterDelegate + 'static,
|
||||
{
|
||||
let this = Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
handler_id: handler_id.to_string(),
|
||||
delegate: Box::new(delegate),
|
||||
result_by_row_id: DashMap::default(),
|
||||
cell_cache,
|
||||
// Cache by field_id
|
||||
cell_filter_cache: AnyTypeCache::<String>::new(),
|
||||
filters: RwLock::new(filters),
|
||||
task_scheduler,
|
||||
notifier,
|
||||
};
|
||||
this.refresh_filters(filters).await;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
@ -100,7 +91,9 @@ impl FilterController {
|
||||
}
|
||||
|
||||
pub async fn filter_rows(&self, rows: &mut Vec<Arc<RowDetail>>) {
|
||||
if self.cell_filter_cache.read().is_empty() {
|
||||
let filters = self.filters.read().await;
|
||||
|
||||
if filters.is_empty() {
|
||||
return;
|
||||
}
|
||||
let field_by_field_id = self.get_field_map().await;
|
||||
@ -110,7 +103,7 @@ impl FilterController {
|
||||
&self.result_by_row_id,
|
||||
&field_by_field_id,
|
||||
&self.cell_cache,
|
||||
&self.cell_filter_cache,
|
||||
&filters,
|
||||
);
|
||||
});
|
||||
|
||||
@ -118,7 +111,7 @@ impl FilterController {
|
||||
self
|
||||
.result_by_row_id
|
||||
.get(&row_detail.row.id)
|
||||
.map(|result| result.is_visible())
|
||||
.map(|result| *result)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
}
|
||||
@ -144,12 +137,14 @@ impl FilterController {
|
||||
let event_type = FilterEvent::from_str(predicate).unwrap();
|
||||
match event_type {
|
||||
FilterEvent::FilterDidChanged => self.filter_all_rows().await?,
|
||||
FilterEvent::RowDidChanged(row_id) => self.filter_row(row_id).await?,
|
||||
FilterEvent::RowDidChanged(row_id) => self.filter_single_row(row_id).await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn filter_row(&self, row_id: RowId) -> FlowyResult<()> {
|
||||
async fn filter_single_row(&self, row_id: RowId) -> FlowyResult<()> {
|
||||
let filters = self.filters.read().await;
|
||||
|
||||
if let Some((_, row_detail)) = self.delegate.get_row(&self.view_id, &row_id).await {
|
||||
let field_by_field_id = self.get_field_map().await;
|
||||
let mut notification = FilterResultNotification::new(self.view_id.clone());
|
||||
@ -158,7 +153,7 @@ impl FilterController {
|
||||
&self.result_by_row_id,
|
||||
&field_by_field_id,
|
||||
&self.cell_cache,
|
||||
&self.cell_filter_cache,
|
||||
&filters,
|
||||
) {
|
||||
if is_visible {
|
||||
if let Some((index, _row)) = self.delegate.get_row(&self.view_id, &row_id).await {
|
||||
@ -179,6 +174,8 @@ impl FilterController {
|
||||
}
|
||||
|
||||
async fn filter_all_rows(&self) -> FlowyResult<()> {
|
||||
let filters = self.filters.read().await;
|
||||
|
||||
let field_by_field_id = self.get_field_map().await;
|
||||
let mut visible_rows = vec![];
|
||||
let mut invisible_rows = vec![];
|
||||
@ -195,7 +192,7 @@ impl FilterController {
|
||||
&self.result_by_row_id,
|
||||
&field_by_field_id,
|
||||
&self.cell_cache,
|
||||
&self.cell_filter_cache,
|
||||
&filters,
|
||||
) {
|
||||
if is_visible {
|
||||
let row_meta = RowMetaPB::from(row_detail.as_ref());
|
||||
@ -211,15 +208,16 @@ impl FilterController {
|
||||
invisible_rows,
|
||||
visible_rows,
|
||||
};
|
||||
tracing::Span::current().record("filter_result", format!("{:?}", ¬ification).as_str());
|
||||
tracing::trace!("filter result {:?}", filters);
|
||||
let _ = self
|
||||
.notifier
|
||||
.send(DatabaseViewChanged::FilterNotification(notification));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn did_receive_row_changed(&self, row_id: RowId) {
|
||||
if !self.cell_filter_cache.read().is_empty() {
|
||||
if !self.filters.read().await.is_empty() {
|
||||
self
|
||||
.gen_task(
|
||||
FilterEvent::RowDidChanged(row_id),
|
||||
@ -230,206 +228,196 @@ impl FilterController {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn did_receive_changes(
|
||||
&self,
|
||||
changeset: FilterChangeset,
|
||||
) -> Option<FilterChangesetNotificationPB> {
|
||||
let mut notification: Option<FilterChangesetNotificationPB> = None;
|
||||
pub async fn apply_changeset(&self, changeset: FilterChangeset) -> FilterChangesetNotificationPB {
|
||||
let mut filters = self.filters.write().await;
|
||||
|
||||
if let Some(filter_type) = &changeset.insert_filter {
|
||||
if let Some(filter) = self.filter_from_filter_id(&filter_type.id).await {
|
||||
notification = Some(FilterChangesetNotificationPB::from_insert(
|
||||
&self.view_id,
|
||||
vec![filter],
|
||||
));
|
||||
}
|
||||
if let Some(filter) = self
|
||||
.delegate
|
||||
.get_filter(&self.view_id, &filter_type.id)
|
||||
.await
|
||||
{
|
||||
self.refresh_filters(vec![filter]).await;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(updated_filter_type) = changeset.update_filter {
|
||||
if let Some(old_filter_type) = updated_filter_type.old {
|
||||
let new_filter = self
|
||||
.filter_from_filter_id(&updated_filter_type.new.id)
|
||||
.await;
|
||||
let old_filter = self.filter_from_filter_id(&old_filter_type.id).await;
|
||||
|
||||
// Get the filter id
|
||||
let mut filter_id = old_filter.map(|filter| filter.id);
|
||||
if filter_id.is_none() {
|
||||
filter_id = new_filter.as_ref().map(|filter| filter.id.clone());
|
||||
match changeset {
|
||||
FilterChangeset::Insert {
|
||||
parent_filter_id,
|
||||
data,
|
||||
} => {
|
||||
let new_filter = Filter {
|
||||
id: gen_database_filter_id(),
|
||||
inner: data,
|
||||
};
|
||||
match parent_filter_id {
|
||||
Some(parent_filter_id) => {
|
||||
if let Some(parent_filter) = filters
|
||||
.iter_mut()
|
||||
.find_map(|filter| filter.find_filter(&parent_filter_id))
|
||||
{
|
||||
// TODO(RS): error handling for inserting filters
|
||||
let _result = parent_filter.insert_filter(new_filter);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
filters.push(new_filter);
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(filter_id) = filter_id {
|
||||
// Update the corresponding filter in the cache
|
||||
if let Some(filter) = self.delegate.get_filter(&self.view_id, &filter_id).await {
|
||||
self.refresh_filters(vec![filter]).await;
|
||||
},
|
||||
FilterChangeset::UpdateType {
|
||||
filter_id,
|
||||
filter_type,
|
||||
} => {
|
||||
for filter in filters.iter_mut() {
|
||||
let filter = filter.find_filter(&filter_id);
|
||||
if let Some(filter) = filter {
|
||||
let result = filter.convert_to_and_or_filter_type(filter_type);
|
||||
if result.is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
notification = Some(FilterChangesetNotificationPB::from_update(
|
||||
&self.view_id,
|
||||
vec![UpdatedFilter {
|
||||
filter_id,
|
||||
filter: new_filter,
|
||||
}],
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
FilterChangeset::UpdateData { filter_id, data } => {
|
||||
if let Some(filter) = filters
|
||||
.iter_mut()
|
||||
.find_map(|filter| filter.find_filter(&filter_id))
|
||||
{
|
||||
// TODO(RS): error handling for updating filter data
|
||||
let _result = filter.update_filter_data(data);
|
||||
}
|
||||
},
|
||||
FilterChangeset::Delete {
|
||||
filter_id,
|
||||
field_id: _,
|
||||
} => {
|
||||
for (position, filter) in filters.iter_mut().enumerate() {
|
||||
if filter.id == filter_id {
|
||||
filters.remove(position);
|
||||
break;
|
||||
}
|
||||
let parent_filter = filter.find_parent_of_filter(&filter_id);
|
||||
if let Some(filter) = parent_filter {
|
||||
let result = filter.delete_filter(&filter_id);
|
||||
if result.is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FilterChangeset::DeleteAllWithFieldId { field_id } => {
|
||||
let mut filter_ids: Vec<String> = vec![];
|
||||
for filter in filters.iter_mut() {
|
||||
filter.find_all_filters_with_field_id(&field_id, &mut filter_ids);
|
||||
}
|
||||
|
||||
for filter_id in filter_ids {
|
||||
for (position, filter) in filters.iter_mut().enumerate() {
|
||||
if filter.id == filter_id {
|
||||
filters.remove(position);
|
||||
break;
|
||||
}
|
||||
let parent_filter = filter.find_parent_of_filter(&filter_id);
|
||||
if let Some(filter) = parent_filter {
|
||||
let _ = filter.delete_filter(&filter_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(filter_context) = &changeset.delete_filter {
|
||||
if let Some(filter) = self.filter_from_filter_id(&filter_context.filter_id).await {
|
||||
notification = Some(FilterChangesetNotificationPB::from_delete(
|
||||
&self.view_id,
|
||||
vec![filter],
|
||||
));
|
||||
}
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.remove(&filter_context.field_id);
|
||||
}
|
||||
self.delegate.save_filters(&self.view_id, &filters);
|
||||
|
||||
self
|
||||
.gen_task(FilterEvent::FilterDidChanged, QualityOfService::Background)
|
||||
.await;
|
||||
tracing::trace!("{:?}", notification);
|
||||
notification
|
||||
}
|
||||
|
||||
async fn filter_from_filter_id(&self, filter_id: &str) -> Option<FilterPB> {
|
||||
self
|
||||
.delegate
|
||||
.get_filter(&self.view_id, filter_id)
|
||||
.await
|
||||
.map(|filter| FilterPB::from(filter.as_ref()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn refresh_filters(&self, filters: Vec<Arc<Filter>>) {
|
||||
for filter in filters {
|
||||
let field_id = &filter.field_id;
|
||||
tracing::trace!("Create filter with type: {:?}", filter.field_type);
|
||||
match &filter.field_type {
|
||||
FieldType::RichText => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, TextFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::Number => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, NumberFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, DateFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, SelectOptionFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, CheckboxFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::URL => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, TextFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::Checklist => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, ChecklistFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
FieldType::Relation => {
|
||||
self
|
||||
.cell_filter_cache
|
||||
.write()
|
||||
.insert(field_id, RelationFilterPB::from_filter(filter.as_ref()));
|
||||
},
|
||||
}
|
||||
}
|
||||
FilterChangesetNotificationPB::from_filters(&self.view_id, &filters)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns None if there is no change in this row after applying the filter
|
||||
/// Returns `Some` if the visibility of the row changed after applying the filter and `None`
|
||||
/// otherwise
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn filter_row(
|
||||
row: &Row,
|
||||
result_by_row_id: &DashMap<RowId, FilterResult>,
|
||||
result_by_row_id: &DashMap<RowId, bool>,
|
||||
field_by_field_id: &HashMap<String, Arc<Field>>,
|
||||
cell_data_cache: &CellCache,
|
||||
cell_filter_cache: &CellFilterCache,
|
||||
filters: &Vec<Filter>,
|
||||
) -> Option<(RowId, bool)> {
|
||||
// Create a filter result cache if it's not exist
|
||||
let mut filter_result = result_by_row_id.entry(row.id.clone()).or_default();
|
||||
let old_is_visible = filter_result.is_visible();
|
||||
// Create a filter result cache if it doesn't exist
|
||||
let mut filter_result = result_by_row_id.entry(row.id.clone()).or_insert(true);
|
||||
let old_is_visible = *filter_result;
|
||||
|
||||
// Iterate each cell of the row to check its visibility
|
||||
for (field_id, field) in field_by_field_id {
|
||||
if !cell_filter_cache.read().contains(field_id) {
|
||||
filter_result.visible_by_field_id.remove(field_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = row.cells.get(field_id).cloned();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
// if the visibility of the cell_rew is changed, which means the visibility of the
|
||||
// row is changed too.
|
||||
if let Some(is_visible) =
|
||||
filter_cell(&field_type, field, cell, cell_data_cache, cell_filter_cache)
|
||||
{
|
||||
filter_result
|
||||
.visible_by_field_id
|
||||
.insert(field_id.to_string(), is_visible);
|
||||
let mut new_is_visible = true;
|
||||
for filter in filters {
|
||||
if let Some(is_visible) = apply_filter(row, field_by_field_id, cell_data_cache, filter) {
|
||||
new_is_visible = new_is_visible && is_visible;
|
||||
}
|
||||
}
|
||||
|
||||
let is_visible = filter_result.is_visible();
|
||||
if old_is_visible != is_visible {
|
||||
Some((row.id.clone(), is_visible))
|
||||
*filter_result = new_is_visible;
|
||||
|
||||
if old_is_visible != new_is_visible {
|
||||
Some((row.id.clone(), new_is_visible))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Returns None if there is no change in this cell after applying the filter
|
||||
// Returns Some if the visibility of the cell is changed
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, fields(cell_content))]
|
||||
fn filter_cell(
|
||||
field_type: &FieldType,
|
||||
field: &Arc<Field>,
|
||||
cell: Option<Cell>,
|
||||
/// Recursively applies a `Filter` to a `Row`'s cells.
|
||||
fn apply_filter(
|
||||
row: &Row,
|
||||
field_by_field_id: &HashMap<String, Arc<Field>>,
|
||||
cell_data_cache: &CellCache,
|
||||
cell_filter_cache: &CellFilterCache,
|
||||
filter: &Filter,
|
||||
) -> Option<bool> {
|
||||
let handler = TypeOptionCellExt::new(
|
||||
field.as_ref(),
|
||||
Some(cell_data_cache.clone()),
|
||||
Some(cell_filter_cache.clone()),
|
||||
)
|
||||
.get_type_option_cell_data_handler(field_type)?;
|
||||
let is_visible =
|
||||
handler.handle_cell_filter(field_type, field.as_ref(), &cell.unwrap_or_default());
|
||||
Some(is_visible)
|
||||
match &filter.inner {
|
||||
FilterInner::And { children } => {
|
||||
if children.is_empty() {
|
||||
return None;
|
||||
}
|
||||
for child_filter in children.iter() {
|
||||
if let Some(false) = apply_filter(row, field_by_field_id, cell_data_cache, child_filter) {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
Some(true)
|
||||
},
|
||||
FilterInner::Or { children } => {
|
||||
if children.is_empty() {
|
||||
return None;
|
||||
}
|
||||
for child_filter in children.iter() {
|
||||
if let Some(true) = apply_filter(row, field_by_field_id, cell_data_cache, child_filter) {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
Some(false)
|
||||
},
|
||||
FilterInner::Data {
|
||||
field_id,
|
||||
field_type,
|
||||
condition_and_content,
|
||||
} => {
|
||||
let field = match field_by_field_id.get(field_id) {
|
||||
Some(field) => field,
|
||||
None => {
|
||||
tracing::error!("cannot find field");
|
||||
return Some(false);
|
||||
},
|
||||
};
|
||||
if *field_type != FieldType::from(field.field_type) {
|
||||
tracing::error!("field type of filter doesn't match field type of field");
|
||||
return Some(false);
|
||||
}
|
||||
let cell = row.cells.get(field_id).cloned();
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
if let Some(handler) = TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
Some(handler.handle_cell_filter(
|
||||
field.as_ref(),
|
||||
&cell.unwrap_or_default(),
|
||||
condition_and_content,
|
||||
))
|
||||
} else {
|
||||
Some(true)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -446,6 +434,7 @@ impl ToString for FilterEvent {
|
||||
|
||||
impl FromStr for FilterEvent {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
|
@ -1,125 +1,397 @@
|
||||
use std::mem;
|
||||
|
||||
use anyhow::bail;
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::database::gen_database_filter_id;
|
||||
use collab_database::rows::RowId;
|
||||
use collab_database::views::{FilterMap, FilterMapBuilder};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::{FieldType, FilterPB, InsertedRowPB};
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, FilterType,
|
||||
InsertedRowPB, NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub trait ParseFilterData {
|
||||
fn parse(condition: u8, content: String) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Filter {
|
||||
pub id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
pub condition: i64,
|
||||
pub content: String,
|
||||
pub inner: FilterInner,
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
/// Recursively determine whether there are any data filters in the filter tree.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match &self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => children
|
||||
.iter()
|
||||
.map(|filter| filter.is_empty())
|
||||
.all(|is_empty| is_empty),
|
||||
FilterInner::Data { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_filter(&mut self, filter_id: &str) -> Option<&mut Self> {
|
||||
if self.id == filter_id {
|
||||
return Some(self);
|
||||
}
|
||||
match &mut self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => {
|
||||
for child_filter in children.iter_mut() {
|
||||
let result = child_filter.find_filter(filter_id);
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
None
|
||||
},
|
||||
FilterInner::Data { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_parent_of_filter(&mut self, filter_id: &str) -> Option<&mut Self> {
|
||||
if self.id == filter_id {
|
||||
return None;
|
||||
}
|
||||
match &mut self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => {
|
||||
for child_filter in children.iter_mut() {
|
||||
if child_filter.id == filter_id {
|
||||
return Some(child_filter);
|
||||
}
|
||||
let result = child_filter.find_parent_of_filter(filter_id);
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
None
|
||||
},
|
||||
FilterInner::Data { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// converts a filter from And/Or/Data to And/Or. If the current type of the filter is Data,
|
||||
/// return the FilterInner after the conversion.
|
||||
pub fn convert_to_and_or_filter_type(
|
||||
&mut self,
|
||||
filter_type: FilterType,
|
||||
) -> FlowyResult<Option<FilterInner>> {
|
||||
match (&mut self.inner, filter_type) {
|
||||
(FilterInner::And { children }, FilterType::Or) => {
|
||||
self.inner = FilterInner::Or {
|
||||
children: mem::take(children),
|
||||
};
|
||||
Ok(None)
|
||||
},
|
||||
(FilterInner::Or { children }, FilterType::And) => {
|
||||
self.inner = FilterInner::And {
|
||||
children: mem::take(children),
|
||||
};
|
||||
Ok(None)
|
||||
},
|
||||
(FilterInner::Data { .. }, FilterType::And) => {
|
||||
let mut inner = FilterInner::And { children: vec![] };
|
||||
mem::swap(&mut self.inner, &mut inner);
|
||||
Ok(Some(inner))
|
||||
},
|
||||
(FilterInner::Data { .. }, FilterType::Or) => {
|
||||
let mut inner = FilterInner::Or { children: vec![] };
|
||||
mem::swap(&mut self.inner, &mut inner);
|
||||
Ok(Some(inner))
|
||||
},
|
||||
(_, FilterType::Data) => {
|
||||
// from And/Or to Data
|
||||
Err(FlowyError::internal().with_context(format!(
|
||||
"conversion from {:?} to FilterType::Data not supported",
|
||||
FilterType::from(&self.inner)
|
||||
)))
|
||||
},
|
||||
_ => {
|
||||
tracing::warn!("conversion to the same filter type");
|
||||
Ok(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_filter(&mut self, filter: Filter) -> FlowyResult<()> {
|
||||
match &mut self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => {
|
||||
children.push(filter);
|
||||
},
|
||||
FilterInner::Data { .. } => {
|
||||
// convert to FilterInner::And by default
|
||||
let old_filter = self
|
||||
.convert_to_and_or_filter_type(FilterType::And)
|
||||
.and_then(|result| {
|
||||
result.ok_or_else(|| FlowyError::internal().with_context("failed to convert filter"))
|
||||
})?;
|
||||
self.insert_filter(Filter {
|
||||
id: gen_database_filter_id(),
|
||||
inner: old_filter,
|
||||
})?;
|
||||
self.insert_filter(filter)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_filter_data(&mut self, filter_data: FilterInner) -> FlowyResult<()> {
|
||||
match &self.inner {
|
||||
FilterInner::And { .. } | FilterInner::Or { .. } => Err(FlowyError::internal().with_context(
|
||||
format!("unexpected filter type {:?}", FilterType::from(&self.inner)),
|
||||
)),
|
||||
_ => {
|
||||
self.inner = filter_data;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_filter(&mut self, filter_id: &str) -> FlowyResult<()> {
|
||||
match &mut self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => children
|
||||
.iter()
|
||||
.position(|filter| filter.id == filter_id)
|
||||
.map(|position| {
|
||||
children.remove(position);
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
FlowyError::internal()
|
||||
.with_context(format!("filter with filter_id {:?} not found", filter_id))
|
||||
}),
|
||||
FilterInner::Data { .. } => Err(
|
||||
FlowyError::internal().with_context("unexpected parent filter type of FilterInner::Data"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_all_filters_with_field_id(&mut self, matching_field_id: &str, ids: &mut Vec<String>) {
|
||||
match &mut self.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => {
|
||||
for child_filter in children.iter_mut() {
|
||||
child_filter.find_all_filters_with_field_id(matching_field_id, ids);
|
||||
}
|
||||
},
|
||||
FilterInner::Data {
|
||||
field_id,
|
||||
field_type: _,
|
||||
condition_and_content: _,
|
||||
} => {
|
||||
if field_id == matching_field_id {
|
||||
ids.push(self.id.clone());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FilterInner {
|
||||
And {
|
||||
children: Vec<Filter>,
|
||||
},
|
||||
Or {
|
||||
children: Vec<Filter>,
|
||||
},
|
||||
Data {
|
||||
field_id: String,
|
||||
field_type: FieldType,
|
||||
condition_and_content: BoxAny,
|
||||
},
|
||||
}
|
||||
|
||||
impl FilterInner {
|
||||
pub fn new_data(
|
||||
field_id: String,
|
||||
field_type: FieldType,
|
||||
condition: i64,
|
||||
content: String,
|
||||
) -> Self {
|
||||
let condition_and_content = match field_type {
|
||||
FieldType::RichText | FieldType::URL => {
|
||||
BoxAny::new(TextFilterPB::parse(condition as u8, content))
|
||||
},
|
||||
FieldType::Number => BoxAny::new(NumberFilterPB::parse(condition as u8, content)),
|
||||
FieldType::DateTime | FieldType::CreatedTime | FieldType::LastEditedTime => {
|
||||
BoxAny::new(DateFilterPB::parse(condition as u8, content))
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
BoxAny::new(SelectOptionFilterPB::parse(condition as u8, content))
|
||||
},
|
||||
FieldType::Checklist => BoxAny::new(ChecklistFilterPB::parse(condition as u8, content)),
|
||||
FieldType::Checkbox => BoxAny::new(CheckboxFilterPB::parse(condition as u8, content)),
|
||||
FieldType::Relation => BoxAny::new(RelationFilterPB::parse(condition as u8, content)),
|
||||
};
|
||||
|
||||
FilterInner::Data {
|
||||
field_id,
|
||||
field_type,
|
||||
condition_and_content,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_repr(&self) -> i64 {
|
||||
match self {
|
||||
FilterInner::And { .. } => FILTER_AND_INDEX,
|
||||
FilterInner::Or { .. } => FILTER_OR_INDEX,
|
||||
FilterInner::Data { .. } => FILTER_DATA_INDEX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FILTER_ID: &str = "id";
|
||||
const FILTER_TYPE: &str = "filter_type";
|
||||
const FIELD_ID: &str = "field_id";
|
||||
const FIELD_TYPE: &str = "ty";
|
||||
const FILTER_CONDITION: &str = "condition";
|
||||
const FILTER_CONTENT: &str = "content";
|
||||
const FILTER_CHILDREN: &str = "children";
|
||||
|
||||
impl From<Filter> for FilterMap {
|
||||
fn from(data: Filter) -> Self {
|
||||
FilterMapBuilder::new()
|
||||
.insert_str_value(FILTER_ID, data.id)
|
||||
.insert_str_value(FIELD_ID, data.field_id)
|
||||
.insert_str_value(FILTER_CONTENT, data.content)
|
||||
.insert_i64_value(FIELD_TYPE, data.field_type.into())
|
||||
.insert_i64_value(FILTER_CONDITION, data.condition)
|
||||
.build()
|
||||
const FILTER_AND_INDEX: i64 = 0;
|
||||
const FILTER_OR_INDEX: i64 = 1;
|
||||
const FILTER_DATA_INDEX: i64 = 2;
|
||||
|
||||
impl<'a> From<&'a Filter> for FilterMap {
|
||||
fn from(filter: &'a Filter) -> Self {
|
||||
let mut builder = FilterMapBuilder::new()
|
||||
.insert_str_value(FILTER_ID, &filter.id)
|
||||
.insert_i64_value(FILTER_TYPE, filter.inner.get_int_repr());
|
||||
|
||||
builder = match &filter.inner {
|
||||
FilterInner::And { children } | FilterInner::Or { children } => {
|
||||
builder.insert_maps(FILTER_CHILDREN, children.iter().collect::<Vec<&Filter>>())
|
||||
},
|
||||
FilterInner::Data {
|
||||
field_id,
|
||||
field_type,
|
||||
condition_and_content,
|
||||
} => {
|
||||
let get_raw_condition_and_content = || -> Option<(u8, String)> {
|
||||
let (condition, content) = match field_type {
|
||||
FieldType::RichText | FieldType::URL => {
|
||||
let filter = condition_and_content.cloned::<TextFilterPB>()?;
|
||||
(filter.condition as u8, filter.content)
|
||||
},
|
||||
FieldType::Number => {
|
||||
let filter = condition_and_content.cloned::<NumberFilterPB>()?;
|
||||
(filter.condition as u8, filter.content)
|
||||
},
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
let filter = condition_and_content.cloned::<DateFilterPB>()?;
|
||||
let content = DateFilterContentPB {
|
||||
start: filter.start,
|
||||
end: filter.end,
|
||||
timestamp: filter.timestamp,
|
||||
}
|
||||
.to_string();
|
||||
(filter.condition as u8, content)
|
||||
},
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let filter = condition_and_content.cloned::<SelectOptionFilterPB>()?;
|
||||
let content = SelectOptionIds::from(filter.option_ids).to_string();
|
||||
(filter.condition as u8, content)
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
let filter = condition_and_content.cloned::<CheckboxFilterPB>()?;
|
||||
(filter.condition as u8, "".to_string())
|
||||
},
|
||||
FieldType::Checklist => {
|
||||
let filter = condition_and_content.cloned::<ChecklistFilterPB>()?;
|
||||
(filter.condition as u8, "".to_string())
|
||||
},
|
||||
FieldType::Relation => {
|
||||
let filter = condition_and_content.cloned::<RelationFilterPB>()?;
|
||||
(filter.condition as u8, "".to_string())
|
||||
},
|
||||
};
|
||||
Some((condition, content))
|
||||
};
|
||||
|
||||
let (condition, content) = get_raw_condition_and_content().unwrap_or_else(|| {
|
||||
tracing::error!("cannot deserialize filter condition and content filter properly");
|
||||
Default::default()
|
||||
});
|
||||
|
||||
builder
|
||||
.insert_str_value(FIELD_ID, field_id)
|
||||
.insert_i64_value(FIELD_TYPE, field_type.into())
|
||||
.insert_i64_value(FILTER_CONDITION, condition as i64)
|
||||
.insert_str_value(FILTER_CONTENT, content)
|
||||
},
|
||||
};
|
||||
|
||||
builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FilterMap> for Filter {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(filter: FilterMap) -> Result<Self, Self::Error> {
|
||||
match (
|
||||
filter.get_str_value(FILTER_ID),
|
||||
filter.get_str_value(FIELD_ID),
|
||||
) {
|
||||
(Some(id), Some(field_id)) => {
|
||||
let condition = filter.get_i64_value(FILTER_CONDITION).unwrap_or(0);
|
||||
let content = filter.get_str_value(FILTER_CONTENT).unwrap_or_default();
|
||||
let field_type = filter
|
||||
.get_i64_value(FIELD_TYPE)
|
||||
.map(FieldType::from)
|
||||
.unwrap_or_default();
|
||||
Ok(Filter {
|
||||
id,
|
||||
field_id,
|
||||
field_type,
|
||||
condition,
|
||||
content,
|
||||
})
|
||||
fn try_from(filter_map: FilterMap) -> Result<Self, Self::Error> {
|
||||
let filter_id = filter_map
|
||||
.get_str_value(FILTER_ID)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid filter data"))?;
|
||||
let filter_type = filter_map
|
||||
.get_i64_value(FILTER_TYPE)
|
||||
.unwrap_or(FILTER_DATA_INDEX);
|
||||
|
||||
let filter = Filter {
|
||||
id: filter_id,
|
||||
inner: match filter_type {
|
||||
FILTER_AND_INDEX => FilterInner::And {
|
||||
children: filter_map.try_get_array(FILTER_CHILDREN),
|
||||
},
|
||||
FILTER_OR_INDEX => FilterInner::Or {
|
||||
children: filter_map.try_get_array(FILTER_CHILDREN),
|
||||
},
|
||||
FILTER_DATA_INDEX => {
|
||||
let field_id = filter_map
|
||||
.get_str_value(FIELD_ID)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid filter data"))?;
|
||||
let field_type = filter_map
|
||||
.get_i64_value(FIELD_TYPE)
|
||||
.map(FieldType::from)
|
||||
.unwrap_or_default();
|
||||
let condition = filter_map.get_i64_value(FILTER_CONDITION).unwrap_or(0);
|
||||
let content = filter_map.get_str_value(FILTER_CONTENT).unwrap_or_default();
|
||||
|
||||
FilterInner::new_data(field_id, field_type, condition, content)
|
||||
},
|
||||
_ => bail!("Unsupported filter type"),
|
||||
},
|
||||
_ => {
|
||||
bail!("Invalid filter data")
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
Ok(filter)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct FilterChangeset {
|
||||
pub(crate) insert_filter: Option<Filter>,
|
||||
pub(crate) update_filter: Option<UpdatedFilter>,
|
||||
pub(crate) delete_filter: Option<FilterContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdatedFilter {
|
||||
pub old: Option<Filter>,
|
||||
pub new: Filter,
|
||||
}
|
||||
|
||||
impl UpdatedFilter {
|
||||
pub fn new(old: Option<Filter>, new: Filter) -> UpdatedFilter {
|
||||
Self { old, new }
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterChangeset {
|
||||
pub fn from_insert(filter: Filter) -> Self {
|
||||
Self {
|
||||
insert_filter: Some(filter),
|
||||
update_filter: None,
|
||||
delete_filter: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_update(filter: UpdatedFilter) -> Self {
|
||||
Self {
|
||||
insert_filter: None,
|
||||
update_filter: Some(filter),
|
||||
delete_filter: None,
|
||||
}
|
||||
}
|
||||
pub fn from_delete(filter_context: FilterContext) -> Self {
|
||||
Self {
|
||||
insert_filter: None,
|
||||
update_filter: None,
|
||||
delete_filter: Some(filter_context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FilterContext {
|
||||
pub filter_id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl From<&FilterPB> for FilterContext {
|
||||
fn from(filter: &FilterPB) -> Self {
|
||||
Self {
|
||||
filter_id: filter.id.clone(),
|
||||
field_id: filter.field_id.clone(),
|
||||
field_type: filter.field_type,
|
||||
}
|
||||
}
|
||||
pub enum FilterChangeset {
|
||||
Insert {
|
||||
parent_filter_id: Option<String>,
|
||||
data: FilterInner,
|
||||
},
|
||||
UpdateType {
|
||||
filter_id: String,
|
||||
filter_type: FilterType,
|
||||
},
|
||||
UpdateData {
|
||||
filter_id: String,
|
||||
data: FilterInner,
|
||||
},
|
||||
Delete {
|
||||
filter_id: String,
|
||||
field_id: String,
|
||||
},
|
||||
DeleteAllWithFieldId {
|
||||
field_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::services::filter::FilterController;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::priority_task::{TaskContent, TaskHandler};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct FilterTaskHandler {
|
||||
@ -40,21 +39,3 @@ impl TaskHandler for FilterTaskHandler {
|
||||
})
|
||||
}
|
||||
}
|
||||
/// Refresh the filter according to the field id.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterResult {
|
||||
pub(crate) visible_by_field_id: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
impl FilterResult {
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
let mut is_visible = true;
|
||||
for visible in self.visible_by_field_id.values() {
|
||||
if !is_visible {
|
||||
break;
|
||||
}
|
||||
is_visible = *visible;
|
||||
}
|
||||
is_visible
|
||||
}
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ fn cmp_cell(
|
||||
cell_data_cache: &CellCache,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match TypeOptionCellExt::new_with_cell_data_cache(field.as_ref(), Some(cell_data_cache.clone()))
|
||||
match TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone()))
|
||||
.get_type_option_cell_data_handler(&field_type)
|
||||
{
|
||||
None => default_order(),
|
||||
|
Reference in New Issue
Block a user