mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: config grid filter in backend & add tests
* chore: add search crate * chore: add task order test * chore: enable timeout * add task crate * chore: run filter task * chore: run filter task * chore: filter rows * chore: cache filter result * chore: filter rows when open a grid * chore: add tests * test: add number filter test * test: add checkbox fitler test * chore: fix test Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
a1e0282df0
commit
a0a16cc493
16
frontend/rust-lib/Cargo.lock
generated
16
frontend/rust-lib/Cargo.lock
generated
@ -955,6 +955,7 @@ dependencies = [
|
||||
name = "flowy-grid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"bytes",
|
||||
"chrono",
|
||||
@ -970,6 +971,7 @@ dependencies = [
|
||||
"flowy-http-model",
|
||||
"flowy-revision",
|
||||
"flowy-sync",
|
||||
"flowy-task",
|
||||
"flowy-test",
|
||||
"futures",
|
||||
"grid-rev-model",
|
||||
@ -1078,6 +1080,7 @@ dependencies = [
|
||||
"flowy-http-model",
|
||||
"flowy-net",
|
||||
"flowy-revision",
|
||||
"flowy-task",
|
||||
"flowy-user",
|
||||
"futures-core",
|
||||
"grid-rev-model",
|
||||
@ -1118,6 +1121,19 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-task"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"futures",
|
||||
"lib-infra",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-test"
|
||||
version = "0.1.0"
|
||||
|
@ -15,6 +15,7 @@ members = [
|
||||
"flowy-error",
|
||||
"flowy-revision",
|
||||
"flowy-grid",
|
||||
"flowy-task",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
@ -44,7 +44,7 @@ pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) {
|
||||
log::error!("sdk not init yet.");
|
||||
return;
|
||||
}
|
||||
Some(e) => e.dispatcher.clone(),
|
||||
Some(e) => e.event_dispatcher.clone(),
|
||||
};
|
||||
let _ = EventDispatcher::async_send_with_callback(dispatcher, request, move |resp: EventResponse| {
|
||||
log::trace!("[FFI]: Post data to dart through {} port", port);
|
||||
@ -62,7 +62,7 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 {
|
||||
log::error!("sdk not init yet.");
|
||||
return forget_rust(Vec::default());
|
||||
}
|
||||
Some(e) => e.dispatcher.clone(),
|
||||
Some(e) => e.event_dispatcher.clone(),
|
||||
};
|
||||
let _response = EventDispatcher::sync_send(dispatcher, request);
|
||||
|
||||
|
@ -112,3 +112,5 @@ impl std::convert::From<protobuf::ProtobufError> for FlowyError {
|
||||
FlowyError::internal().context(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FlowyError {}
|
||||
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
dart-notify = { path = "../dart-notify" }
|
||||
flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-task= { path = "../flowy-task" }
|
||||
flowy-error = { path = "../flowy-error", features = ["db"]}
|
||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||
lib-ot = { path = "../../../shared-lib/lib-ot" }
|
||||
@ -17,6 +18,7 @@ grid-rev-model = { path = "../../../shared-lib/grid-rev-model" }
|
||||
flowy-sync = { path = "../../../shared-lib/flowy-sync" }
|
||||
flowy-http-model = { path = "../../../shared-lib/flowy-http-model" }
|
||||
flowy-database = { path = "../flowy-database" }
|
||||
anyhow = "1.0"
|
||||
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
|
@ -591,6 +591,7 @@ impl std::convert::From<&FieldTypeRevision> for FieldType {
|
||||
FieldType::from(*ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<FieldTypeRevision> for FieldType {
|
||||
fn from(ty: FieldTypeRevision) -> Self {
|
||||
match ty {
|
||||
|
@ -1,49 +1,49 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterConfigurationRevision;
|
||||
use grid_rev_model::FilterRevision;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct CheckboxFilterConfigurationPB {
|
||||
pub struct CheckboxFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: CheckboxCondition,
|
||||
pub condition: CheckboxFilterCondition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum CheckboxCondition {
|
||||
pub enum CheckboxFilterCondition {
|
||||
IsChecked = 0,
|
||||
IsUnChecked = 1,
|
||||
}
|
||||
|
||||
impl std::convert::From<CheckboxCondition> for i32 {
|
||||
fn from(value: CheckboxCondition) -> Self {
|
||||
value as i32
|
||||
impl std::convert::From<CheckboxFilterCondition> for u32 {
|
||||
fn from(value: CheckboxFilterCondition) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for CheckboxCondition {
|
||||
impl std::default::Default for CheckboxFilterCondition {
|
||||
fn default() -> Self {
|
||||
CheckboxCondition::IsChecked
|
||||
CheckboxFilterCondition::IsChecked
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for CheckboxCondition {
|
||||
impl std::convert::TryFrom<u8> for CheckboxFilterCondition {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(CheckboxCondition::IsChecked),
|
||||
1 => Ok(CheckboxCondition::IsUnChecked),
|
||||
0 => Ok(CheckboxFilterCondition::IsChecked),
|
||||
1 => Ok(CheckboxFilterCondition::IsUnChecked),
|
||||
_ => Err(ErrorCode::InvalidData),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterConfigurationRevision>> for CheckboxFilterConfigurationPB {
|
||||
fn from(rev: Arc<FilterConfigurationRevision>) -> Self {
|
||||
CheckboxFilterConfigurationPB {
|
||||
condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked),
|
||||
impl std::convert::From<Arc<FilterRevision>> for CheckboxFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::FieldType;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterConfigurationRevision;
|
||||
use grid_rev_model::FilterRevision;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DateFilterConfigurationPB {
|
||||
pub struct DateFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: DateFilterCondition,
|
||||
|
||||
@ -98,6 +98,11 @@ pub enum DateFilterCondition {
|
||||
DateIsEmpty = 6,
|
||||
}
|
||||
|
||||
impl std::convert::From<DateFilterCondition> for u32 {
|
||||
fn from(value: DateFilterCondition) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
impl std::default::Default for DateFilterCondition {
|
||||
fn default() -> Self {
|
||||
DateFilterCondition::DateIs
|
||||
@ -120,19 +125,15 @@ impl std::convert::TryFrom<u8> for DateFilterCondition {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<Arc<FilterConfigurationRevision>> for DateFilterConfigurationPB {
|
||||
fn from(rev: Arc<FilterConfigurationRevision>) -> Self {
|
||||
impl std::convert::From<Arc<FilterRevision>> for DateFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs);
|
||||
let mut filter = DateFilterConfigurationPB {
|
||||
let mut filter = DateFilterPB {
|
||||
condition,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(range) = rev
|
||||
.content
|
||||
.as_ref()
|
||||
.and_then(|content| DateRange::from_str(content).ok())
|
||||
{
|
||||
if let Ok(range) = DateRange::from_str(&rev.content) {
|
||||
filter.start = range.start;
|
||||
filter.end = range.end;
|
||||
};
|
||||
|
@ -1,16 +1,16 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterConfigurationRevision;
|
||||
use grid_rev_model::FilterRevision;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct NumberFilterConfigurationPB {
|
||||
pub struct NumberFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: NumberFilterCondition,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub content: Option<String>,
|
||||
#[pb(index = 2)]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
@ -32,9 +32,9 @@ impl std::default::Default for NumberFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<NumberFilterCondition> for i32 {
|
||||
impl std::convert::From<NumberFilterCondition> for u32 {
|
||||
fn from(value: NumberFilterCondition) -> Self {
|
||||
value as i32
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
impl std::convert::TryFrom<u8> for NumberFilterCondition {
|
||||
@ -55,9 +55,9 @@ impl std::convert::TryFrom<u8> for NumberFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterConfigurationRevision>> for NumberFilterConfigurationPB {
|
||||
fn from(rev: Arc<FilterConfigurationRevision>) -> Self {
|
||||
NumberFilterConfigurationPB {
|
||||
impl std::convert::From<Arc<FilterRevision>> for NumberFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
NumberFilterPB {
|
||||
condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
|
||||
content: rev.content.clone(),
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterConfigurationRevision;
|
||||
use grid_rev_model::FilterRevision;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SelectOptionFilterConfigurationPB {
|
||||
pub struct SelectOptionFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: SelectOptionCondition,
|
||||
|
||||
@ -21,9 +21,9 @@ pub enum SelectOptionCondition {
|
||||
OptionIsNotEmpty = 3,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionCondition> for i32 {
|
||||
impl std::convert::From<SelectOptionCondition> for u32 {
|
||||
fn from(value: SelectOptionCondition) -> Self {
|
||||
value as i32
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,10 +47,10 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterConfigurationRevision>> for SelectOptionFilterConfigurationPB {
|
||||
fn from(rev: Arc<FilterConfigurationRevision>) -> Self {
|
||||
impl std::convert::From<Arc<FilterRevision>> for SelectOptionFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
let ids = SelectOptionIds::from(rev.content.clone());
|
||||
SelectOptionFilterConfigurationPB {
|
||||
SelectOptionFilterPB {
|
||||
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
|
||||
option_ids: ids.into_inner(),
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterConfigurationRevision;
|
||||
use grid_rev_model::FilterRevision;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct TextFilterConfigurationPB {
|
||||
pub struct TextFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: TextFilterCondition,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub content: Option<String>,
|
||||
#[pb(index = 2)]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
@ -25,9 +25,9 @@ pub enum TextFilterCondition {
|
||||
TextIsNotEmpty = 7,
|
||||
}
|
||||
|
||||
impl std::convert::From<TextFilterCondition> for i32 {
|
||||
impl std::convert::From<TextFilterCondition> for u32 {
|
||||
fn from(value: TextFilterCondition) -> Self {
|
||||
value as i32
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,9 +54,9 @@ impl std::convert::TryFrom<u8> for TextFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterConfigurationRevision>> for TextFilterConfigurationPB {
|
||||
fn from(rev: Arc<FilterConfigurationRevision>) -> Self {
|
||||
TextFilterConfigurationPB {
|
||||
impl std::convert::From<Arc<FilterRevision>> for TextFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
TextFilterPB {
|
||||
condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
|
||||
content: rev.content.clone(),
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
|
||||
CheckboxFilterCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
|
||||
TextFilterCondition,
|
||||
};
|
||||
use crate::services::filter::FilterType;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision};
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridFilterConfigurationPB {
|
||||
pub struct FilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
}
|
||||
@ -18,25 +19,25 @@ pub struct GridFilterConfigurationPB {
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedGridFilterConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<GridFilterConfigurationPB>,
|
||||
pub items: Vec<FilterPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfigurationPB {
|
||||
fn from(rev: &FilterConfigurationRevision) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for FilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
Self { id: rev.id.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<FilterConfigurationRevision>>> for RepeatedGridFilterConfigurationPB {
|
||||
fn from(revs: Vec<Arc<FilterConfigurationRevision>>) -> Self {
|
||||
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedGridFilterConfigurationPB {
|
||||
fn from(revs: Vec<Arc<FilterRevision>>) -> Self {
|
||||
RepeatedGridFilterConfigurationPB {
|
||||
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<GridFilterConfigurationPB>> for RepeatedGridFilterConfigurationPB {
|
||||
fn from(items: Vec<GridFilterConfigurationPB>) -> Self {
|
||||
impl std::convert::From<Vec<FilterPB>> for RepeatedGridFilterConfigurationPB {
|
||||
fn from(items: Vec<FilterPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
@ -47,10 +48,10 @@ pub struct DeleteFilterPayloadPB {
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub filter_id: String,
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
pub filter_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
|
||||
@ -60,25 +61,27 @@ impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
let filter_id = NotEmptyStr::parse(self.filter_id)
|
||||
.map_err(|_| ErrorCode::UnexpectedEmptyString)?
|
||||
.0;
|
||||
Ok(DeleteFilterParams {
|
||||
|
||||
let filter_type = FilterType {
|
||||
field_id,
|
||||
filter_id,
|
||||
field_type_rev: self.field_type.into(),
|
||||
})
|
||||
field_type: self.field_type,
|
||||
};
|
||||
|
||||
Ok(DeleteFilterParams { filter_id, filter_type })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteFilterParams {
|
||||
pub field_id: String,
|
||||
pub filter_type: FilterType,
|
||||
pub filter_id: String,
|
||||
pub field_type_rev: FieldTypeRevision,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct InsertFilterPayloadPB {
|
||||
pub struct CreateFilterPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
@ -86,15 +89,15 @@ pub struct InsertFilterPayloadPB {
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub condition: i32,
|
||||
pub condition: u32,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub content: Option<String>,
|
||||
#[pb(index = 4)]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl InsertFilterPayloadPB {
|
||||
impl CreateFilterPayloadPB {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
|
||||
pub fn new<T: Into<u32>>(field_rev: &FieldRevision, condition: T, content: String) -> Self {
|
||||
Self {
|
||||
field_id: field_rev.id.clone(),
|
||||
field_type: field_rev.ty.into(),
|
||||
@ -104,10 +107,10 @@ impl InsertFilterPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<InsertFilterParams> for InsertFilterPayloadPB {
|
||||
impl TryInto<CreateFilterParams> for CreateFilterPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<InsertFilterParams, Self::Error> {
|
||||
fn try_into(self) -> Result<CreateFilterParams, Self::Error> {
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
@ -117,7 +120,7 @@ impl TryInto<InsertFilterParams> for InsertFilterPayloadPB {
|
||||
let _ = TextFilterCondition::try_from(condition)?;
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = CheckboxCondition::try_from(condition)?;
|
||||
let _ = CheckboxFilterCondition::try_from(condition)?;
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = NumberFilterCondition::try_from(condition)?;
|
||||
@ -130,7 +133,7 @@ impl TryInto<InsertFilterParams> for InsertFilterPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InsertFilterParams {
|
||||
Ok(CreateFilterParams {
|
||||
field_id,
|
||||
field_type_rev: self.field_type.into(),
|
||||
condition,
|
||||
@ -139,9 +142,9 @@ impl TryInto<InsertFilterParams> for InsertFilterPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertFilterParams {
|
||||
pub struct CreateFilterParams {
|
||||
pub field_id: String,
|
||||
pub field_type_rev: FieldTypeRevision,
|
||||
pub condition: u8,
|
||||
pub content: Option<String>,
|
||||
pub content: String,
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
mod block_entities;
|
||||
pub mod block_entities;
|
||||
mod cell_entities;
|
||||
mod field_entities;
|
||||
mod filter_entities;
|
||||
pub mod filter_entities;
|
||||
mod grid_entities;
|
||||
mod group_entities;
|
||||
pub mod parser;
|
||||
mod row_entities;
|
||||
mod setting_entities;
|
||||
pub mod setting_entities;
|
||||
|
||||
pub use block_entities::*;
|
||||
pub use cell_entities::*;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, InsertFilterParams,
|
||||
InsertFilterPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB,
|
||||
CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
|
||||
DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB,
|
||||
RepeatedGridGroupConfigurationPB,
|
||||
};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
@ -84,7 +84,7 @@ pub struct GridSettingChangesetPayloadPB {
|
||||
pub layout_type: GridLayout,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub insert_filter: Option<InsertFilterPayloadPB>,
|
||||
pub insert_filter: Option<CreateFilterPayloadPB>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub delete_filter: Option<DeleteFilterPayloadPB>,
|
||||
@ -138,7 +138,7 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
|
||||
pub struct GridSettingChangesetParams {
|
||||
pub grid_id: String,
|
||||
pub layout_type: LayoutRevision,
|
||||
pub insert_filter: Option<InsertFilterParams>,
|
||||
pub insert_filter: Option<CreateFilterParams>,
|
||||
pub delete_filter: Option<DeleteFilterParams>,
|
||||
pub insert_group: Option<InsertGroupParams>,
|
||||
pub delete_group: Option<DeleteGroupParams>,
|
||||
|
@ -7,7 +7,7 @@ use crate::services::field::{
|
||||
SelectOptionCellChangesetPayloadPB, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPayloadPB,
|
||||
SelectOptionPB,
|
||||
};
|
||||
use crate::services::row::make_row_from_row_rev;
|
||||
use crate::services::row::{make_block_pbs, make_row_from_row_rev};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use grid_rev_model::FieldRevision;
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
@ -20,7 +20,7 @@ pub(crate) async fn get_grid_handler(
|
||||
) -> DataResult<GridPB, FlowyError> {
|
||||
let grid_id: GridIdPB = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id).await?;
|
||||
let grid = editor.get_grid_data().await?;
|
||||
let grid = editor.get_grid().await?;
|
||||
data_result(grid)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ pub(crate) async fn get_grid_setting_handler(
|
||||
) -> DataResult<GridSettingPB, FlowyError> {
|
||||
let grid_id: GridIdPB = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id).await?;
|
||||
let grid_setting = editor.get_grid_setting().await?;
|
||||
let grid_setting = editor.get_setting().await?;
|
||||
data_result(grid_setting)
|
||||
}
|
||||
|
||||
@ -68,8 +68,8 @@ pub(crate) async fn get_grid_blocks_handler(
|
||||
) -> DataResult<RepeatedBlockPB, FlowyError> {
|
||||
let params: QueryGridBlocksParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id).await?;
|
||||
let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?;
|
||||
data_result(repeated_grid_block)
|
||||
let blocks = editor.get_blocks(Some(params.block_ids)).await?;
|
||||
data_result(make_block_pbs(blocks))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
|
@ -7,7 +7,6 @@ use crate::services::persistence::kv::GridKVPersistence;
|
||||
use crate::services::persistence::migration::GridMigration;
|
||||
use crate::services::persistence::rev_sqlite::SQLiteGridRevisionPersistence;
|
||||
use crate::services::persistence::GridDatabase;
|
||||
use crate::services::tasks::GridTaskScheduler;
|
||||
use bytes::Bytes;
|
||||
|
||||
use flowy_database::ConnectionPool;
|
||||
@ -22,6 +21,7 @@ use grid_rev_model::{BuildGridContext, GridRevision, GridViewRevision};
|
||||
use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
|
||||
|
||||
use crate::services::block_manager::make_grid_block_rev_manager;
|
||||
use flowy_task::TaskDispatcher;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@ -31,15 +31,13 @@ pub trait GridUser: Send + Sync {
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
||||
}
|
||||
|
||||
pub type GridTaskSchedulerRwLock = Arc<RwLock<GridTaskScheduler>>;
|
||||
|
||||
pub struct GridManager {
|
||||
grid_editors: RwLock<RefCountHashMap<Arc<GridRevisionEditor>>>,
|
||||
grid_user: Arc<dyn GridUser>,
|
||||
block_index_cache: Arc<BlockIndexCache>,
|
||||
#[allow(dead_code)]
|
||||
kv_persistence: Arc<GridKVPersistence>,
|
||||
task_scheduler: GridTaskSchedulerRwLock,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
migration: GridMigration,
|
||||
}
|
||||
|
||||
@ -47,12 +45,12 @@ impl GridManager {
|
||||
pub fn new(
|
||||
grid_user: Arc<dyn GridUser>,
|
||||
_rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
database: Arc<dyn GridDatabase>,
|
||||
) -> Self {
|
||||
let grid_editors = RwLock::new(RefCountHashMap::new());
|
||||
let kv_persistence = Arc::new(GridKVPersistence::new(database.clone()));
|
||||
let block_index_cache = Arc::new(BlockIndexCache::new(database.clone()));
|
||||
let task_scheduler = GridTaskScheduler::new();
|
||||
let migration = GridMigration::new(grid_user.clone(), database);
|
||||
Self {
|
||||
grid_editors,
|
||||
@ -111,7 +109,7 @@ impl GridManager {
|
||||
tracing::Span::current().record("grid_id", &grid_id);
|
||||
|
||||
self.grid_editors.write().await.remove(grid_id);
|
||||
self.task_scheduler.write().await.unregister_handler(grid_id);
|
||||
// self.task_scheduler.write().await.unregister_handler(grid_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -134,7 +132,7 @@ impl GridManager {
|
||||
.write()
|
||||
.await
|
||||
.insert(grid_id.to_string(), editor.clone());
|
||||
self.task_scheduler.write().await.register_handler(editor.clone());
|
||||
// self.task_scheduler.write().await.register_handler(editor.clone());
|
||||
Ok(editor)
|
||||
}
|
||||
|
||||
|
@ -134,10 +134,10 @@ impl GridBlockRevisionEditor {
|
||||
|
||||
pub async fn get_row_pb(&self, row_id: &str) -> FlowyResult<Option<RowPB>> {
|
||||
let row_ids = Some(vec![Cow::Borrowed(row_id)]);
|
||||
Ok(self.get_row_infos(row_ids).await?.pop())
|
||||
Ok(self.get_row_pbs(row_ids).await?.pop())
|
||||
}
|
||||
|
||||
pub async fn get_row_infos<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<RowPB>>
|
||||
pub async fn get_row_pbs<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<RowPB>>
|
||||
where
|
||||
T: AsRef<str> + ToOwned + ?Sized,
|
||||
{
|
||||
|
@ -4,7 +4,7 @@ use crate::manager::GridUser;
|
||||
use crate::services::block_editor::{GridBlockRevisionCompress, GridBlockRevisionEditor};
|
||||
use crate::services::persistence::block_index::BlockIndexCache;
|
||||
use crate::services::persistence::rev_sqlite::SQLiteGridBlockRevisionPersistence;
|
||||
use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlockSnapshot};
|
||||
use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlock};
|
||||
use dashmap::DashMap;
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::FlowyResult;
|
||||
@ -209,34 +209,31 @@ impl GridBlockManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult<Vec<RowPB>> {
|
||||
pub async fn get_row_revs(&self, block_id: &str) -> FlowyResult<Vec<Arc<RowRevision>>> {
|
||||
let editor = self.get_block_editor(block_id).await?;
|
||||
editor.get_row_infos::<&str>(None).await
|
||||
editor.get_row_revs::<&str>(None).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_block_snapshots(
|
||||
&self,
|
||||
block_ids: Option<Vec<String>>,
|
||||
) -> FlowyResult<Vec<GridBlockSnapshot>> {
|
||||
let mut snapshots = vec![];
|
||||
pub(crate) async fn get_blocks(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlock>> {
|
||||
let mut blocks = vec![];
|
||||
match block_ids {
|
||||
None => {
|
||||
for iter in self.block_editors.iter() {
|
||||
let editor = iter.value();
|
||||
let block_id = editor.block_id.clone();
|
||||
let row_revs = editor.get_row_revs::<&str>(None).await?;
|
||||
snapshots.push(GridBlockSnapshot { block_id, row_revs });
|
||||
blocks.push(GridBlock { block_id, row_revs });
|
||||
}
|
||||
}
|
||||
Some(block_ids) => {
|
||||
for block_id in block_ids {
|
||||
let editor = self.get_block_editor(&block_id).await?;
|
||||
let row_revs = editor.get_row_revs::<&str>(None).await?;
|
||||
snapshots.push(GridBlockSnapshot { block_id, row_revs });
|
||||
blocks.push(GridBlock { block_id, row_revs });
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(snapshots)
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
async fn notify_did_update_block(&self, block_id: &str, changeset: GridBlockChangesetPB) -> FlowyResult<()> {
|
||||
|
@ -1,37 +0,0 @@
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
use crate::services::grid_view_manager::GridViewRowDelegate;
|
||||
|
||||
use grid_rev_model::RowRevision;
|
||||
use lib_infra::future::{wrap_future, AFFuture};
|
||||
use std::sync::Arc;
|
||||
|
||||
impl GridViewRowDelegate for Arc<GridBlockManager> {
|
||||
fn gv_index_of_row(&self, row_id: &str) -> AFFuture<Option<usize>> {
|
||||
let block_manager = self.clone();
|
||||
let row_id = row_id.to_owned();
|
||||
wrap_future(async move { block_manager.index_of_row(&row_id).await })
|
||||
}
|
||||
|
||||
fn gv_get_row_rev(&self, row_id: &str) -> AFFuture<Option<Arc<RowRevision>>> {
|
||||
let block_manager = self.clone();
|
||||
let row_id = row_id.to_owned();
|
||||
wrap_future(async move {
|
||||
match block_manager.get_row_rev(&row_id).await {
|
||||
Ok(row_rev) => row_rev,
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn gv_row_revs(&self) -> AFFuture<Vec<Arc<RowRevision>>> {
|
||||
let block_manager = self.clone();
|
||||
|
||||
wrap_future(async move {
|
||||
let blocks = block_manager.get_block_snapshots(None).await.unwrap();
|
||||
blocks
|
||||
.into_iter()
|
||||
.flat_map(|block| block.row_revs)
|
||||
.collect::<Vec<Arc<RowRevision>>>()
|
||||
})
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB};
|
||||
use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB};
|
||||
use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
|
||||
use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl CheckboxFilterConfigurationPB {
|
||||
impl CheckboxFilterPB {
|
||||
pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool {
|
||||
let is_check = cell_data.is_check();
|
||||
match self.condition {
|
||||
CheckboxCondition::IsChecked => is_check,
|
||||
CheckboxCondition::IsUnChecked => !is_check,
|
||||
CheckboxFilterCondition::IsChecked => is_check,
|
||||
CheckboxFilterCondition::IsUnChecked => !is_check,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<CheckboxFilterConfigurationPB> for CheckboxTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &CheckboxFilterConfigurationPB) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<CheckboxFilterPB> for CheckboxTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &CheckboxFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_checkbox() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -26,14 +26,14 @@ impl CellFilterOperation<CheckboxFilterConfigurationPB> for CheckboxTypeOptionPB
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB};
|
||||
use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB};
|
||||
use crate::services::field::CheckboxCellData;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn checkbox_filter_is_check_test() {
|
||||
let checkbox_filter = CheckboxFilterConfigurationPB {
|
||||
condition: CheckboxCondition::IsChecked,
|
||||
let checkbox_filter = CheckboxFilterPB {
|
||||
condition: CheckboxFilterCondition::IsChecked,
|
||||
};
|
||||
for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] {
|
||||
let data = CheckboxCellData::from_str(value).unwrap();
|
||||
@ -43,8 +43,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn checkbox_filter_is_uncheck_test() {
|
||||
let checkbox_filter = CheckboxFilterConfigurationPB {
|
||||
condition: CheckboxCondition::IsUnChecked,
|
||||
let checkbox_filter = CheckboxFilterPB {
|
||||
condition: CheckboxFilterCondition::IsUnChecked,
|
||||
};
|
||||
for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] {
|
||||
let data = CheckboxCellData::from_str(value).unwrap();
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod checkbox_filter;
|
||||
mod checkbox_tests;
|
||||
mod checkbox_type_option;
|
||||
mod checkbox_type_option_entities;
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::entities::{DateFilterCondition, DateFilterConfigurationPB};
|
||||
use crate::entities::{DateFilterCondition, DateFilterPB};
|
||||
use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
|
||||
use crate::services::field::{DateTimestamp, DateTypeOptionPB};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl DateFilterConfigurationPB {
|
||||
impl DateFilterPB {
|
||||
pub fn is_visible<T: Into<i64>>(&self, cell_timestamp: T) -> bool {
|
||||
if self.start.is_none() {
|
||||
return false;
|
||||
@ -29,8 +29,8 @@ impl DateFilterConfigurationPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<DateFilterConfigurationPB> for DateTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &DateFilterConfigurationPB) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<DateFilterPB> for DateTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &DateFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_date() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -43,11 +43,11 @@ impl CellFilterOperation<DateFilterConfigurationPB> for DateTypeOptionPB {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
use crate::entities::{DateFilterCondition, DateFilterConfigurationPB};
|
||||
use crate::entities::{DateFilterCondition, DateFilterPB};
|
||||
|
||||
#[test]
|
||||
fn date_filter_is_test() {
|
||||
let filter = DateFilterConfigurationPB {
|
||||
let filter = DateFilterPB {
|
||||
condition: DateFilterCondition::DateIs,
|
||||
start: Some(123),
|
||||
end: None,
|
||||
@ -59,7 +59,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn date_filter_before_test() {
|
||||
let filter = DateFilterConfigurationPB {
|
||||
let filter = DateFilterPB {
|
||||
condition: DateFilterCondition::DateBefore,
|
||||
start: Some(123),
|
||||
end: None,
|
||||
@ -71,7 +71,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn date_filter_before_or_on_test() {
|
||||
let filter = DateFilterConfigurationPB {
|
||||
let filter = DateFilterPB {
|
||||
condition: DateFilterCondition::DateOnOrBefore,
|
||||
start: Some(123),
|
||||
end: None,
|
||||
@ -83,7 +83,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn date_filter_after_test() {
|
||||
let filter = DateFilterConfigurationPB {
|
||||
let filter = DateFilterPB {
|
||||
condition: DateFilterCondition::DateAfter,
|
||||
start: Some(123),
|
||||
end: None,
|
||||
@ -95,7 +95,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn date_filter_within_test() {
|
||||
let filter = DateFilterConfigurationPB {
|
||||
let filter = DateFilterPB {
|
||||
condition: DateFilterCondition::DateWithIn,
|
||||
start: Some(123),
|
||||
end: Some(130),
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod date_filter;
|
||||
mod date_tests;
|
||||
mod date_type_option;
|
||||
mod date_type_option_entities;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod format;
|
||||
mod number_filter;
|
||||
mod number_tests;
|
||||
mod number_type_option;
|
||||
mod number_type_option_entities;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB};
|
||||
use crate::entities::{NumberFilterCondition, NumberFilterPB};
|
||||
use crate::services::cell::{AnyCellData, CellFilterOperation};
|
||||
use crate::services::field::{NumberCellData, NumberTypeOptionPB};
|
||||
use flowy_error::FlowyResult;
|
||||
@ -6,33 +6,39 @@ use rust_decimal::prelude::Zero;
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
|
||||
impl NumberFilterConfigurationPB {
|
||||
impl NumberFilterPB {
|
||||
pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool {
|
||||
if self.content.is_none() {
|
||||
return false;
|
||||
if self.content.is_empty() {
|
||||
match self.condition {
|
||||
NumberFilterCondition::NumberIsEmpty => {
|
||||
return num_cell_data.is_empty();
|
||||
}
|
||||
NumberFilterCondition::NumberIsNotEmpty => {
|
||||
return !num_cell_data.is_empty();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let content = self.content.as_ref().unwrap();
|
||||
let zero_decimal = Decimal::zero();
|
||||
let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal);
|
||||
match Decimal::from_str(content) {
|
||||
Ok(decimal) => match self.condition {
|
||||
NumberFilterCondition::Equal => cell_decimal == &decimal,
|
||||
NumberFilterCondition::NotEqual => cell_decimal != &decimal,
|
||||
NumberFilterCondition::GreaterThan => cell_decimal > &decimal,
|
||||
NumberFilterCondition::LessThan => cell_decimal < &decimal,
|
||||
NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal,
|
||||
NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal,
|
||||
NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(),
|
||||
NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(),
|
||||
},
|
||||
Err(_) => false,
|
||||
match num_cell_data.decimal().as_ref() {
|
||||
None => false,
|
||||
Some(cell_decimal) => {
|
||||
let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero());
|
||||
match self.condition {
|
||||
NumberFilterCondition::Equal => cell_decimal == &decimal,
|
||||
NumberFilterCondition::NotEqual => cell_decimal != &decimal,
|
||||
NumberFilterCondition::GreaterThan => cell_decimal > &decimal,
|
||||
NumberFilterCondition::LessThan => cell_decimal < &decimal,
|
||||
NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal,
|
||||
NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<NumberFilterConfigurationPB> for NumberTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &NumberFilterConfigurationPB) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<NumberFilterPB> for NumberTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &NumberFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_number() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -46,13 +52,13 @@ impl CellFilterOperation<NumberFilterConfigurationPB> for NumberTypeOptionPB {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB};
|
||||
use crate::entities::{NumberFilterCondition, NumberFilterPB};
|
||||
use crate::services::field::{NumberCellData, NumberFormat};
|
||||
#[test]
|
||||
fn number_filter_equal_test() {
|
||||
let number_filter = NumberFilterConfigurationPB {
|
||||
let number_filter = NumberFilterPB {
|
||||
condition: NumberFilterCondition::Equal,
|
||||
content: Some("123".to_owned()),
|
||||
content: "123".to_owned(),
|
||||
};
|
||||
|
||||
for (num_str, visible) in [("123", true), ("1234", false), ("", false)] {
|
||||
@ -68,9 +74,9 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn number_filter_greater_than_test() {
|
||||
let number_filter = NumberFilterConfigurationPB {
|
||||
let number_filter = NumberFilterPB {
|
||||
condition: NumberFilterCondition::GreaterThan,
|
||||
content: Some("12".to_owned()),
|
||||
content: "12".to_owned(),
|
||||
};
|
||||
for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] {
|
||||
let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap();
|
||||
@ -80,11 +86,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn number_filter_less_than_test() {
|
||||
let number_filter = NumberFilterConfigurationPB {
|
||||
let number_filter = NumberFilterPB {
|
||||
condition: NumberFilterCondition::LessThan,
|
||||
content: Some("100".to_owned()),
|
||||
content: "100".to_owned(),
|
||||
};
|
||||
for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] {
|
||||
for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] {
|
||||
let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap();
|
||||
assert_eq!(number_filter.is_visible(&data), visible);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
mod multi_select_type_option;
|
||||
mod select_filter;
|
||||
mod select_type_option;
|
||||
mod single_select_type_option;
|
||||
mod type_option_transform;
|
||||
|
@ -1,12 +1,12 @@
|
||||
#![allow(clippy::needless_collect)]
|
||||
|
||||
use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB};
|
||||
use crate::entities::{SelectOptionCondition, SelectOptionFilterPB};
|
||||
use crate::services::cell::{AnyCellData, CellFilterOperation};
|
||||
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl SelectOptionFilterConfigurationPB {
|
||||
impl SelectOptionFilterPB {
|
||||
pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool {
|
||||
let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
|
||||
match self.condition {
|
||||
@ -39,12 +39,8 @@ impl SelectOptionFilterConfigurationPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<SelectOptionFilterConfigurationPB> for MultiSelectTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
any_cell_data: AnyCellData,
|
||||
filter: &SelectOptionFilterConfigurationPB,
|
||||
) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<SelectOptionFilterPB> for MultiSelectTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_multi_select() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -54,12 +50,8 @@ impl CellFilterOperation<SelectOptionFilterConfigurationPB> for MultiSelectTypeO
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<SelectOptionFilterConfigurationPB> for SingleSelectTypeOptionPB {
|
||||
fn apply_filter(
|
||||
&self,
|
||||
any_cell_data: AnyCellData,
|
||||
filter: &SelectOptionFilterConfigurationPB,
|
||||
) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<SelectOptionFilterPB> for SingleSelectTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &SelectOptionFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_single_select() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -71,7 +63,7 @@ impl CellFilterOperation<SelectOptionFilterConfigurationPB> for SingleSelectType
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB};
|
||||
use crate::entities::{SelectOptionCondition, SelectOptionFilterPB};
|
||||
use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions};
|
||||
|
||||
#[test]
|
||||
@ -80,7 +72,7 @@ mod tests {
|
||||
let option_2 = SelectOptionPB::new("B");
|
||||
let option_3 = SelectOptionPB::new("C");
|
||||
|
||||
let filter_1 = SelectOptionFilterConfigurationPB {
|
||||
let filter_1 = SelectOptionFilterPB {
|
||||
condition: SelectOptionCondition::OptionIs,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod text_filter;
|
||||
mod text_tests;
|
||||
mod text_type_option;
|
||||
|
||||
|
@ -1,31 +1,27 @@
|
||||
use crate::entities::{TextFilterCondition, TextFilterConfigurationPB};
|
||||
use crate::entities::{TextFilterCondition, TextFilterPB};
|
||||
use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
|
||||
use crate::services::field::{RichTextTypeOptionPB, TextCellData};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl TextFilterConfigurationPB {
|
||||
impl TextFilterPB {
|
||||
pub fn is_visible<T: AsRef<str>>(&self, cell_data: T) -> bool {
|
||||
let cell_data = cell_data.as_ref();
|
||||
let s = cell_data.to_lowercase();
|
||||
if let Some(content) = self.content.as_ref() {
|
||||
match self.condition {
|
||||
TextFilterCondition::Is => &s == content,
|
||||
TextFilterCondition::IsNot => &s != content,
|
||||
TextFilterCondition::Contains => s.contains(content),
|
||||
TextFilterCondition::DoesNotContain => !s.contains(content),
|
||||
TextFilterCondition::StartsWith => s.starts_with(content),
|
||||
TextFilterCondition::EndsWith => s.ends_with(content),
|
||||
TextFilterCondition::TextIsEmpty => s.is_empty(),
|
||||
TextFilterCondition::TextIsNotEmpty => !s.is_empty(),
|
||||
}
|
||||
} else {
|
||||
false
|
||||
let cell_data = cell_data.as_ref().to_lowercase();
|
||||
let content = &self.content.to_lowercase();
|
||||
match self.condition {
|
||||
TextFilterCondition::Is => &cell_data == content,
|
||||
TextFilterCondition::IsNot => &cell_data != content,
|
||||
TextFilterCondition::Contains => cell_data.contains(content),
|
||||
TextFilterCondition::DoesNotContain => !cell_data.contains(content),
|
||||
TextFilterCondition::StartsWith => cell_data.starts_with(content),
|
||||
TextFilterCondition::EndsWith => cell_data.ends_with(content),
|
||||
TextFilterCondition::TextIsEmpty => cell_data.is_empty(),
|
||||
TextFilterCondition::TextIsNotEmpty => !cell_data.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<TextFilterConfigurationPB> for RichTextTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<TextFilterPB> for RichTextTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_text() {
|
||||
return Ok(true);
|
||||
}
|
||||
@ -38,13 +34,13 @@ impl CellFilterOperation<TextFilterConfigurationPB> for RichTextTypeOptionPB {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
use crate::entities::{TextFilterCondition, TextFilterConfigurationPB};
|
||||
use crate::entities::{TextFilterCondition, TextFilterPB};
|
||||
|
||||
#[test]
|
||||
fn text_filter_equal_test() {
|
||||
let text_filter = TextFilterConfigurationPB {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::Is,
|
||||
content: Some("appflowy".to_owned()),
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
assert!(text_filter.is_visible("AppFlowy"));
|
||||
@ -54,9 +50,9 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn text_filter_start_with_test() {
|
||||
let text_filter = TextFilterConfigurationPB {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::StartsWith,
|
||||
content: Some("appflowy".to_owned()),
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
assert_eq!(text_filter.is_visible("AppFlowy.io"), true);
|
||||
@ -66,9 +62,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn text_filter_end_with_test() {
|
||||
let text_filter = TextFilterConfigurationPB {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::EndsWith,
|
||||
content: Some("appflowy".to_owned()),
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true);
|
||||
@ -77,9 +73,9 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn text_filter_empty_test() {
|
||||
let text_filter = TextFilterConfigurationPB {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::TextIsEmpty,
|
||||
content: Some("appflowy".to_owned()),
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
assert_eq!(text_filter.is_visible(""), true);
|
||||
@ -87,9 +83,9 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn text_filter_contain_test() {
|
||||
let text_filter = TextFilterConfigurationPB {
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::Contains,
|
||||
content: Some("appflowy".to_owned()),
|
||||
content: "appflowy".to_owned(),
|
||||
};
|
||||
|
||||
assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true);
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod url_filter;
|
||||
mod url_tests;
|
||||
mod url_type_option;
|
||||
mod url_type_option_entities;
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::entities::TextFilterConfigurationPB;
|
||||
use crate::entities::TextFilterPB;
|
||||
use crate::services::cell::{AnyCellData, CellData, CellFilterOperation};
|
||||
use crate::services::field::{TextCellData, URLTypeOptionPB};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
impl CellFilterOperation<TextFilterConfigurationPB> for URLTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult<bool> {
|
||||
impl CellFilterOperation<TextFilterPB> for URLTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_url() {
|
||||
return Ok(true);
|
||||
}
|
94
frontend/rust-lib/flowy-grid/src/services/filter/cache.rs
Normal file
94
frontend/rust-lib/flowy-grid/src/services/filter/cache.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate::entities::{CheckboxFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB};
|
||||
use crate::services::filter::FilterType;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterMap {
|
||||
pub(crate) text_filter: HashMap<FilterType, TextFilterPB>,
|
||||
pub(crate) url_filter: HashMap<FilterType, TextFilterPB>,
|
||||
pub(crate) number_filter: HashMap<FilterType, NumberFilterPB>,
|
||||
pub(crate) date_filter: HashMap<FilterType, DateFilterPB>,
|
||||
pub(crate) select_option_filter: HashMap<FilterType, SelectOptionFilterPB>,
|
||||
pub(crate) checkbox_filter: HashMap<FilterType, CheckboxFilterPB>,
|
||||
}
|
||||
|
||||
impl FilterMap {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
if !self.text_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.url_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.number_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.number_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.date_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.select_option_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.checkbox_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, filter_id: &FilterType) {
|
||||
let _ = match filter_id.field_type {
|
||||
FieldType::RichText => {
|
||||
let _ = self.text_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = self.number_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = self.date_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
let _ = self.select_option_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let _ = self.select_option_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = self.checkbox_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = self.url_filter.remove(filter_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh the filter according to the field id.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterResult {
|
||||
pub(crate) visible_by_filter_id: HashMap<FilterType, bool>,
|
||||
}
|
||||
|
||||
impl FilterResult {
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
for visible in self.visible_by_filter_id.values() {
|
||||
if visible == &false {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
405
frontend/rust-lib/flowy-grid/src/services/filter/controller.rs
Normal file
405
frontend/rust-lib/flowy-grid/src/services/filter/controller.rs
Normal file
@ -0,0 +1,405 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::filter_entities::*;
|
||||
use crate::entities::setting_entities::*;
|
||||
use crate::entities::{FieldType, GridBlockChangesetPB};
|
||||
use crate::services::cell::{AnyCellData, CellFilterOperation};
|
||||
use crate::services::field::*;
|
||||
use crate::services::filter::{FilterMap, FilterResult, FILTER_HANDLER_ID};
|
||||
use crate::services::row::GridBlock;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher};
|
||||
use grid_rev_model::{CellRevision, FieldId, FieldRevision, FieldTypeRevision, FilterRevision, RowRevision};
|
||||
use lib_infra::future::Fut;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
type RowId = String;
|
||||
pub trait GridViewFilterDelegate: Send + Sync + 'static {
|
||||
fn get_filter_rev(&self, filter_id: FilterType) -> Fut<Vec<Arc<FilterRevision>>>;
|
||||
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
|
||||
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>>;
|
||||
fn get_blocks(&self) -> Fut<Vec<GridBlock>>;
|
||||
}
|
||||
|
||||
pub struct FilterController {
|
||||
view_id: String,
|
||||
delegate: Box<dyn GridViewFilterDelegate>,
|
||||
filter_map: FilterMap,
|
||||
result_by_row_id: HashMap<RowId, FilterResult>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
impl FilterController {
|
||||
pub async fn new<T>(
|
||||
view_id: &str,
|
||||
delegate: T,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
filter_revs: Vec<Arc<FilterRevision>>,
|
||||
) -> Self
|
||||
where
|
||||
T: GridViewFilterDelegate,
|
||||
{
|
||||
let mut this = Self {
|
||||
view_id: view_id.to_string(),
|
||||
delegate: Box::new(delegate),
|
||||
filter_map: FilterMap::new(),
|
||||
result_by_row_id: HashMap::default(),
|
||||
task_scheduler,
|
||||
};
|
||||
this.load_filters(filter_revs).await;
|
||||
this
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
self.task_scheduler.write().await.unregister_handler(FILTER_HANDLER_ID);
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))]
|
||||
async fn gen_task(&mut self, predicate: &str) {
|
||||
let task_id = self.task_scheduler.read().await.next_task_id();
|
||||
let task = Task::new(
|
||||
FILTER_HANDLER_ID,
|
||||
task_id,
|
||||
TaskContent::Text(predicate.to_owned()),
|
||||
QualityOfService::UserInteractive,
|
||||
);
|
||||
self.task_scheduler.write().await.add_task(task);
|
||||
}
|
||||
|
||||
pub async fn filter_row_revs(&mut self, row_revs: &mut Vec<Arc<RowRevision>>) {
|
||||
if self.filter_map.is_empty() {
|
||||
return;
|
||||
}
|
||||
let field_rev_by_field_id = self.get_filter_revs_map().await;
|
||||
let _ = row_revs
|
||||
.iter()
|
||||
.flat_map(|row_rev| {
|
||||
filter_row(
|
||||
row_rev,
|
||||
&self.filter_map,
|
||||
&mut self.result_by_row_id,
|
||||
&field_rev_by_field_id,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
row_revs.retain(|row_rev| {
|
||||
self.result_by_row_id
|
||||
.get(&row_rev.id)
|
||||
.map(|result| result.is_visible())
|
||||
.unwrap_or(false)
|
||||
});
|
||||
}
|
||||
|
||||
async fn get_filter_revs_map(&self) -> HashMap<String, Arc<FieldRevision>> {
|
||||
self.delegate
|
||||
.get_field_revs(None)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|field_rev| (field_rev.id.clone(), field_rev))
|
||||
.collect::<HashMap<String, Arc<FieldRevision>>>()
|
||||
}
|
||||
|
||||
pub async fn process(&mut self, _predicate: &str) -> FlowyResult<()> {
|
||||
let field_rev_by_field_id = self.get_filter_revs_map().await;
|
||||
let mut changesets = vec![];
|
||||
for block in self.delegate.get_blocks().await.into_iter() {
|
||||
// The row_ids contains the row that its visibility was changed.
|
||||
let row_ids = block
|
||||
.row_revs
|
||||
.iter()
|
||||
.flat_map(|row_rev| {
|
||||
filter_row(
|
||||
row_rev,
|
||||
&self.filter_map,
|
||||
&mut self.result_by_row_id,
|
||||
&field_rev_by_field_id,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut visible_rows = vec![];
|
||||
let mut hide_rows = vec![];
|
||||
|
||||
// Query the filter result from the cache
|
||||
for row_id in row_ids {
|
||||
if self
|
||||
.result_by_row_id
|
||||
.get(&row_id)
|
||||
.map(|result| result.is_visible())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
visible_rows.push(row_id);
|
||||
} else {
|
||||
hide_rows.push(row_id);
|
||||
}
|
||||
}
|
||||
|
||||
let changeset = GridBlockChangesetPB {
|
||||
block_id: block.block_id,
|
||||
hide_rows,
|
||||
visible_rows,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Save the changeset for each block
|
||||
changesets.push(changeset);
|
||||
}
|
||||
|
||||
self.notify(changesets).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn apply_changeset(&mut self, changeset: FilterChangeset) {
|
||||
if let Some(filter_id) = &changeset.insert_filter {
|
||||
let filter_revs = self.delegate.get_filter_rev(filter_id.clone()).await;
|
||||
let _ = self.load_filters(filter_revs).await;
|
||||
}
|
||||
|
||||
if let Some(filter_id) = &changeset.delete_filter {
|
||||
self.filter_map.remove(filter_id);
|
||||
}
|
||||
|
||||
self.gen_task("").await;
|
||||
}
|
||||
|
||||
async fn notify(&self, changesets: Vec<GridBlockChangesetPB>) {
|
||||
for changeset in changesets {
|
||||
send_dart_notification(&self.view_id, GridNotification::DidUpdateGridBlock)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_filters(&mut self, filter_revs: Vec<Arc<FilterRevision>>) {
|
||||
for filter_rev in filter_revs {
|
||||
if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await {
|
||||
let filter_type = FilterType::from(&field_rev);
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
match &field_type {
|
||||
FieldType::RichText => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.text_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev));
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.number_filter
|
||||
.insert(filter_type, NumberFilterPB::from(filter_rev));
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.date_filter
|
||||
.insert(filter_type, DateFilterPB::from(filter_rev));
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.select_option_filter
|
||||
.insert(filter_type, SelectOptionFilterPB::from(filter_rev));
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.checkbox_filter
|
||||
.insert(filter_type, CheckboxFilterPB::from(filter_rev));
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.url_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns None if there is no change in this row after applying the filter
|
||||
fn filter_row(
|
||||
row_rev: &Arc<RowRevision>,
|
||||
filter_map: &FilterMap,
|
||||
result_by_row_id: &mut HashMap<RowId, FilterResult>,
|
||||
field_rev_by_field_id: &HashMap<FieldId, Arc<FieldRevision>>,
|
||||
) -> Option<String> {
|
||||
// Create a filter result cache if it's not exist
|
||||
let filter_result = result_by_row_id
|
||||
.entry(row_rev.id.clone())
|
||||
.or_insert_with(FilterResult::default);
|
||||
|
||||
// Iterate each cell of the row to check its visibility
|
||||
for (field_id, cell_rev) in row_rev.cells.iter() {
|
||||
let field_rev = field_rev_by_field_id.get(field_id)?;
|
||||
let filter_type = FilterType::from(field_rev);
|
||||
|
||||
// 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(&filter_type, field_rev, filter_map, cell_rev) {
|
||||
let prev_is_visible = filter_result.visible_by_filter_id.get(&filter_type).cloned();
|
||||
filter_result.visible_by_filter_id.insert(filter_type, is_visible);
|
||||
match prev_is_visible {
|
||||
None => {
|
||||
if !is_visible {
|
||||
return Some(row_rev.id.clone());
|
||||
}
|
||||
}
|
||||
Some(prev_is_visible) => {
|
||||
if prev_is_visible != is_visible {
|
||||
return Some(row_rev.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Return None if there is no change in this cell after applying the filter
|
||||
fn filter_cell(
|
||||
filter_id: &FilterType,
|
||||
field_rev: &Arc<FieldRevision>,
|
||||
filter_map: &FilterMap,
|
||||
cell_rev: &CellRevision,
|
||||
) -> Option<bool> {
|
||||
let any_cell_data = AnyCellData::try_from(cell_rev).ok()?;
|
||||
let is_visible = match &filter_id.field_type {
|
||||
FieldType::RichText => filter_map.text_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<RichTextTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Number => filter_map.number_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<NumberTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::DateTime => filter_map.date_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<DateTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::SingleSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::MultiSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checkbox => filter_map.checkbox_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::URL => filter_map.url_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<URLTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
}?;
|
||||
|
||||
is_visible
|
||||
}
|
||||
|
||||
pub struct FilterChangeset {
|
||||
insert_filter: Option<FilterType>,
|
||||
delete_filter: Option<FilterType>,
|
||||
}
|
||||
|
||||
impl FilterChangeset {
|
||||
pub fn from_insert(filter_id: FilterType) -> Self {
|
||||
Self {
|
||||
insert_filter: Some(filter_id),
|
||||
delete_filter: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(filter_id: FilterType) -> Self {
|
||||
Self {
|
||||
insert_filter: None,
|
||||
delete_filter: Some(filter_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&GridSettingChangesetParams> for FilterChangeset {
|
||||
fn from(params: &GridSettingChangesetParams) -> Self {
|
||||
let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterType {
|
||||
field_id: insert_filter_params.field_id.clone(),
|
||||
field_type: insert_filter_params.field_type_rev.into(),
|
||||
});
|
||||
|
||||
let delete_filter = params
|
||||
.delete_filter
|
||||
.as_ref()
|
||||
.map(|delete_filter_params| delete_filter_params.filter_type.clone());
|
||||
FilterChangeset {
|
||||
insert_filter,
|
||||
delete_filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Clone)]
|
||||
pub struct FilterType {
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl FilterType {
|
||||
pub fn field_type_rev(&self) -> FieldTypeRevision {
|
||||
self.field_type.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Arc<FieldRevision>> for FilterType {
|
||||
fn from(rev: &Arc<FieldRevision>) -> Self {
|
||||
Self {
|
||||
field_id: rev.id.clone(),
|
||||
field_type: rev.ty.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&CreateFilterParams> for FilterType {
|
||||
fn from(params: &CreateFilterParams) -> Self {
|
||||
let field_type: FieldType = params.field_type_rev.into();
|
||||
Self {
|
||||
field_id: params.field_id.clone(),
|
||||
field_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&DeleteFilterParams> for FilterType {
|
||||
fn from(params: &DeleteFilterParams) -> Self {
|
||||
params.filter_type.clone()
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
use crate::entities::{
|
||||
CheckboxFilterConfigurationPB, DateFilterConfigurationPB, FieldType, NumberFilterConfigurationPB,
|
||||
SelectOptionFilterConfigurationPB, TextFilterConfigurationPB,
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use flowy_sync::client_grid::GridRevisionPad;
|
||||
use grid_rev_model::{FieldRevision, FilterConfigurationRevision, RowRevision};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
type RowId = String;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterResultCache {
|
||||
// key: row id
|
||||
inner: DashMap<RowId, FilterResult>,
|
||||
}
|
||||
|
||||
impl FilterResultCache {
|
||||
pub fn new() -> Arc<Self> {
|
||||
let this = Self::default();
|
||||
Arc::new(this)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for FilterResultCache {
|
||||
type Target = DashMap<String, FilterResult>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterResult {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) row_index: i32,
|
||||
pub(crate) visible_by_field_id: HashMap<FilterId, bool>,
|
||||
}
|
||||
|
||||
impl FilterResult {
|
||||
pub(crate) fn new(index: i32, _row_rev: &RowRevision) -> Self {
|
||||
Self {
|
||||
row_index: index,
|
||||
visible_by_field_id: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
for visible in self.visible_by_field_id.values() {
|
||||
if visible == &false {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FilterCache {
|
||||
pub(crate) text_filter: DashMap<FilterId, TextFilterConfigurationPB>,
|
||||
pub(crate) url_filter: DashMap<FilterId, TextFilterConfigurationPB>,
|
||||
pub(crate) number_filter: DashMap<FilterId, NumberFilterConfigurationPB>,
|
||||
pub(crate) date_filter: DashMap<FilterId, DateFilterConfigurationPB>,
|
||||
pub(crate) select_option_filter: DashMap<FilterId, SelectOptionFilterConfigurationPB>,
|
||||
pub(crate) checkbox_filter: DashMap<FilterId, CheckboxFilterConfigurationPB>,
|
||||
}
|
||||
|
||||
impl FilterCache {
|
||||
pub(crate) async fn from_grid_pad(grid_pad: &Arc<RwLock<GridRevisionPad>>) -> Arc<Self> {
|
||||
let this = Arc::new(Self::default());
|
||||
let _ = refresh_filter_cache(this.clone(), None, grid_pad).await;
|
||||
this
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn remove(&self, filter_id: &FilterId) {
|
||||
let _ = match filter_id.field_type {
|
||||
FieldType::RichText => {
|
||||
let _ = self.text_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = self.number_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = self.date_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
let _ = self.select_option_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let _ = self.select_option_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = self.checkbox_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = self.url_filter.remove(filter_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh the filter according to the field id.
|
||||
pub(crate) async fn refresh_filter_cache(
|
||||
cache: Arc<FilterCache>,
|
||||
_field_ids: Option<Vec<String>>,
|
||||
grid_pad: &Arc<RwLock<GridRevisionPad>>,
|
||||
) {
|
||||
let grid_pad = grid_pad.read().await;
|
||||
// let filters_revs = grid_pad.get_filters(field_ids).unwrap_or_default();
|
||||
// TODO nathan
|
||||
let filter_revs: Vec<Arc<FilterConfigurationRevision>> = vec![];
|
||||
|
||||
for filter_rev in filter_revs {
|
||||
match grid_pad.get_field_rev(&filter_rev.field_id) {
|
||||
None => {}
|
||||
Some((_, field_rev)) => {
|
||||
let filter_id = FilterId::from(field_rev);
|
||||
let field_type: FieldType = field_rev.ty.into();
|
||||
match &field_type {
|
||||
FieldType::RichText => {
|
||||
let _ = cache
|
||||
.text_filter
|
||||
.insert(filter_id, TextFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = cache
|
||||
.number_filter
|
||||
.insert(filter_id, NumberFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = cache
|
||||
.date_filter
|
||||
.insert(filter_id, DateFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let _ = cache
|
||||
.select_option_filter
|
||||
.insert(filter_id, SelectOptionFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = cache
|
||||
.checkbox_filter
|
||||
.insert(filter_id, CheckboxFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = cache
|
||||
.url_filter
|
||||
.insert(filter_id, TextFilterConfigurationPB::from(filter_rev));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
pub(crate) struct FilterId {
|
||||
pub(crate) field_id: String,
|
||||
pub(crate) field_type: FieldType,
|
||||
}
|
||||
|
||||
impl std::convert::From<&Arc<FieldRevision>> for FilterId {
|
||||
fn from(rev: &Arc<FieldRevision>) -> Self {
|
||||
Self {
|
||||
field_id: rev.id.clone(),
|
||||
field_type: rev.ty.into(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::{FieldType, GridBlockChangesetPB, GridSettingChangesetParams};
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
use crate::services::cell::{AnyCellData, CellFilterOperation};
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB, RichTextTypeOptionPB,
|
||||
SingleSelectTypeOptionPB, URLTypeOptionPB,
|
||||
};
|
||||
use crate::services::filter::filter_cache::{
|
||||
refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache,
|
||||
};
|
||||
use crate::services::grid_editor_task::GridServiceTaskScheduler;
|
||||
use crate::services::row::GridBlockSnapshot;
|
||||
use crate::services::tasks::{FilterTaskContext, Task, TaskContent};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_sync::client_grid::GridRevisionPad;
|
||||
use grid_rev_model::{CellRevision, FieldId, FieldRevision, RowRevision};
|
||||
use rayon::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub(crate) struct GridFilterService {
|
||||
#[allow(dead_code)]
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
grid_pad: Arc<RwLock<GridRevisionPad>>,
|
||||
#[allow(dead_code)]
|
||||
block_manager: Arc<GridBlockManager>,
|
||||
filter_cache: Arc<FilterCache>,
|
||||
filter_result_cache: Arc<FilterResultCache>,
|
||||
}
|
||||
impl GridFilterService {
|
||||
pub async fn new<S: GridServiceTaskScheduler>(
|
||||
grid_pad: Arc<RwLock<GridRevisionPad>>,
|
||||
block_manager: Arc<GridBlockManager>,
|
||||
scheduler: S,
|
||||
) -> Self {
|
||||
let scheduler = Arc::new(scheduler);
|
||||
let filter_cache = FilterCache::from_grid_pad(&grid_pad).await;
|
||||
let filter_result_cache = FilterResultCache::new();
|
||||
Self {
|
||||
grid_pad,
|
||||
block_manager,
|
||||
scheduler,
|
||||
filter_cache,
|
||||
filter_result_cache,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process(&self, task_context: FilterTaskContext) -> FlowyResult<()> {
|
||||
let field_revs = self
|
||||
.grid_pad
|
||||
.read()
|
||||
.await
|
||||
.get_field_revs(None)?
|
||||
.into_iter()
|
||||
.map(|field_rev| (field_rev.id.clone(), field_rev))
|
||||
.collect::<HashMap<String, Arc<FieldRevision>>>();
|
||||
|
||||
let mut changesets = vec![];
|
||||
for (index, block) in task_context.blocks.into_iter().enumerate() {
|
||||
// The row_ids contains the row that its visibility was changed.
|
||||
let row_ids = block
|
||||
.row_revs
|
||||
.par_iter()
|
||||
.flat_map(|row_rev| {
|
||||
let filter_result_cache = self.filter_result_cache.clone();
|
||||
let filter_cache = self.filter_cache.clone();
|
||||
filter_row(index, row_rev, filter_cache, filter_result_cache, &field_revs)
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut visible_rows = vec![];
|
||||
let mut hide_rows = vec![];
|
||||
|
||||
// Query the filter result from the cache
|
||||
for row_id in row_ids {
|
||||
if self
|
||||
.filter_result_cache
|
||||
.get(&row_id)
|
||||
.map(|result| result.is_visible())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
visible_rows.push(row_id);
|
||||
} else {
|
||||
hide_rows.push(row_id);
|
||||
}
|
||||
}
|
||||
|
||||
let changeset = GridBlockChangesetPB {
|
||||
block_id: block.block_id,
|
||||
hide_rows,
|
||||
visible_rows,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Save the changeset for each block
|
||||
changesets.push(changeset);
|
||||
}
|
||||
|
||||
self.notify(changesets).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn apply_changeset(&self, changeset: GridFilterChangeset) {
|
||||
if !changeset.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(filter_id) = &changeset.insert_filter {
|
||||
let field_ids = Some(vec![filter_id.field_id.clone()]);
|
||||
refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await;
|
||||
}
|
||||
|
||||
if let Some(filter_id) = &changeset.delete_filter {
|
||||
self.filter_cache.remove(filter_id);
|
||||
}
|
||||
|
||||
if let Ok(blocks) = self.block_manager.get_block_snapshots(None).await {
|
||||
let _task = self.gen_task(blocks).await;
|
||||
// let _ = self.scheduler.register_task(task).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn gen_task(&self, blocks: Vec<GridBlockSnapshot>) -> Task {
|
||||
let task_id = self.scheduler.gen_task_id().await;
|
||||
let handler_id = self.grid_pad.read().await.grid_id();
|
||||
|
||||
let context = FilterTaskContext { blocks };
|
||||
Task::new(&handler_id, task_id, TaskContent::Filter(context))
|
||||
}
|
||||
|
||||
async fn notify(&self, changesets: Vec<GridBlockChangesetPB>) {
|
||||
let grid_id = self.grid_pad.read().await.grid_id();
|
||||
for changeset in changesets {
|
||||
send_dart_notification(&grid_id, GridNotification::DidUpdateGridBlock)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return None if there is no change in this row after applying the filter
|
||||
fn filter_row(
|
||||
index: usize,
|
||||
row_rev: &Arc<RowRevision>,
|
||||
filter_cache: Arc<FilterCache>,
|
||||
filter_result_cache: Arc<FilterResultCache>,
|
||||
field_revs: &HashMap<FieldId, Arc<FieldRevision>>,
|
||||
) -> Option<String> {
|
||||
let mut result = filter_result_cache
|
||||
.entry(row_rev.id.clone())
|
||||
.or_insert(FilterResult::new(index as i32, row_rev));
|
||||
|
||||
for (field_id, cell_rev) in row_rev.cells.iter() {
|
||||
match filter_cell(field_revs, result.value_mut(), &filter_cache, field_id, cell_rev) {
|
||||
None => {}
|
||||
Some(_) => {
|
||||
return Some(row_rev.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Return None if there is no change in this cell after applying the filter
|
||||
fn filter_cell(
|
||||
field_revs: &HashMap<FieldId, Arc<FieldRevision>>,
|
||||
filter_result: &mut FilterResult,
|
||||
filter_cache: &Arc<FilterCache>,
|
||||
field_id: &str,
|
||||
cell_rev: &CellRevision,
|
||||
) -> Option<()> {
|
||||
let field_rev = field_revs.get(field_id)?;
|
||||
let field_type = FieldType::from(field_rev.ty);
|
||||
let field_type_rev = field_type.clone().into();
|
||||
let filter_id = FilterId {
|
||||
field_id: field_id.to_owned(),
|
||||
field_type,
|
||||
};
|
||||
let any_cell_data = AnyCellData::try_from(cell_rev).ok()?;
|
||||
let is_visible = match &filter_id.field_type {
|
||||
FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<RichTextTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<NumberTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<DateTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<SingleSelectTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<URLTypeOptionPB>(field_type_rev)?
|
||||
.apply_filter(any_cell_data, filter.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
}?;
|
||||
|
||||
let is_visible = !is_visible.unwrap_or(true);
|
||||
match filter_result.visible_by_field_id.get(&filter_id) {
|
||||
None => {
|
||||
if is_visible {
|
||||
None
|
||||
} else {
|
||||
filter_result.visible_by_field_id.insert(filter_id, is_visible);
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
Some(old_is_visible) => {
|
||||
if old_is_visible != &is_visible {
|
||||
filter_result.visible_by_field_id.insert(filter_id, is_visible);
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GridFilterChangeset {
|
||||
insert_filter: Option<FilterId>,
|
||||
delete_filter: Option<FilterId>,
|
||||
}
|
||||
|
||||
impl GridFilterChangeset {
|
||||
fn is_changed(&self) -> bool {
|
||||
self.insert_filter.is_some() || self.delete_filter.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&GridSettingChangesetParams> for GridFilterChangeset {
|
||||
fn from(params: &GridSettingChangesetParams) -> Self {
|
||||
let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterId {
|
||||
field_id: insert_filter_params.field_id.clone(),
|
||||
field_type: insert_filter_params.field_type_rev.into(),
|
||||
});
|
||||
|
||||
let delete_filter = params.delete_filter.as_ref().map(|delete_filter_params| FilterId {
|
||||
field_id: delete_filter_params.filter_id.clone(),
|
||||
field_type: delete_filter_params.field_type_rev.into(),
|
||||
});
|
||||
GridFilterChangeset {
|
||||
insert_filter,
|
||||
delete_filter,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
mod checkbox_filter;
|
||||
mod date_filter;
|
||||
mod number_filter;
|
||||
mod select_option_filter;
|
||||
mod text_filter;
|
||||
mod url_filter;
|
||||
|
||||
pub use checkbox_filter::*;
|
||||
pub use date_filter::*;
|
||||
pub use number_filter::*;
|
||||
pub use select_option_filter::*;
|
||||
pub use text_filter::*;
|
||||
pub use url_filter::*;
|
@ -1,5 +1,7 @@
|
||||
mod filter_cache;
|
||||
mod filter_service;
|
||||
mod impls;
|
||||
mod cache;
|
||||
mod controller;
|
||||
mod task;
|
||||
|
||||
pub(crate) use filter_service::*;
|
||||
pub(crate) use cache::*;
|
||||
pub use controller::*;
|
||||
pub(crate) use task::*;
|
||||
|
35
frontend/rust-lib/flowy-grid/src/services/filter/task.rs
Normal file
35
frontend/rust-lib/flowy-grid/src/services/filter/task.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::services::filter::FilterController;
|
||||
use flowy_task::{TaskContent, TaskHandler};
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub const FILTER_HANDLER_ID: &str = "grid_filter";
|
||||
|
||||
pub struct FilterTaskHandler(Arc<RwLock<FilterController>>);
|
||||
impl FilterTaskHandler {
|
||||
pub fn new(filter_controller: Arc<RwLock<FilterController>>) -> Self {
|
||||
Self(filter_controller)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskHandler for FilterTaskHandler {
|
||||
fn handler_id(&self) -> &str {
|
||||
FILTER_HANDLER_ID
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> {
|
||||
let filter_controller = self.0.clone();
|
||||
Box::pin(async move {
|
||||
if let TaskContent::Text(predicate) = content {
|
||||
let _ = filter_controller
|
||||
.write()
|
||||
.await
|
||||
.process(&predicate)
|
||||
.await
|
||||
.map_err(anyhow::Error::from);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::GridCellIdParams;
|
||||
use crate::entities::*;
|
||||
use crate::manager::{GridTaskSchedulerRwLock, GridUser};
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
|
||||
use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes};
|
||||
use crate::services::field::{
|
||||
default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str,
|
||||
FieldBuilder,
|
||||
};
|
||||
use crate::services::filter::GridFilterService;
|
||||
|
||||
use crate::services::filter::FilterType;
|
||||
use crate::services::grid_editor_trait_impl::GridViewEditorDelegateImpl;
|
||||
use crate::services::grid_view_manager::GridViewManager;
|
||||
use crate::services::persistence::block_index::BlockIndexCache;
|
||||
use crate::services::row::{make_grid_blocks, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder};
|
||||
use crate::services::row::{GridBlock, RowRevisionBuilder};
|
||||
use bytes::Bytes;
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_http_model::revision::Revision;
|
||||
use flowy_revision::{
|
||||
@ -22,10 +24,9 @@ use flowy_revision::{
|
||||
use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeserializer};
|
||||
use flowy_sync::errors::{CollaborateError, CollaborateResult};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use flowy_task::TaskDispatcher;
|
||||
use grid_rev_model::*;
|
||||
use lib_infra::future::{wrap_future, FutureResult};
|
||||
|
||||
use flowy_database::ConnectionPool;
|
||||
use lib_infra::future::{to_future, FutureResult};
|
||||
use lib_ot::core::EmptyAttributes;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@ -39,9 +40,6 @@ pub struct GridRevisionEditor {
|
||||
view_manager: Arc<GridViewManager>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
block_manager: Arc<GridBlockManager>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) filter_service: Arc<GridFilterService>,
|
||||
}
|
||||
|
||||
impl Drop for GridRevisionEditor {
|
||||
@ -56,7 +54,7 @@ impl GridRevisionEditor {
|
||||
user: Arc<dyn GridUser>,
|
||||
mut rev_manager: RevisionManager<Arc<ConnectionPool>>,
|
||||
persistence: Arc<BlockIndexCache>,
|
||||
task_scheduler: GridTaskSchedulerRwLock,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
) -> FlowyResult<Arc<Self>> {
|
||||
let token = user.token()?;
|
||||
let cloud = Arc::new(GridRevisionCloudService { token });
|
||||
@ -67,20 +65,14 @@ impl GridRevisionEditor {
|
||||
// Block manager
|
||||
let block_meta_revs = grid_pad.read().await.get_block_meta_revs();
|
||||
let block_manager = Arc::new(GridBlockManager::new(&user, block_meta_revs, persistence).await?);
|
||||
let filter_service =
|
||||
GridFilterService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await;
|
||||
let delegate = Arc::new(GridViewEditorDelegateImpl {
|
||||
pad: grid_pad.clone(),
|
||||
block_manager: block_manager.clone(),
|
||||
task_scheduler,
|
||||
});
|
||||
|
||||
// View manager
|
||||
let view_manager = Arc::new(
|
||||
GridViewManager::new(
|
||||
grid_id.to_owned(),
|
||||
user.clone(),
|
||||
Arc::new(grid_pad.clone()),
|
||||
Arc::new(block_manager.clone()),
|
||||
Arc::new(task_scheduler.clone()),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
let view_manager = Arc::new(GridViewManager::new(grid_id.to_owned(), user.clone(), delegate).await?);
|
||||
let editor = Arc::new(Self {
|
||||
grid_id: grid_id.to_owned(),
|
||||
user,
|
||||
@ -88,7 +80,6 @@ impl GridRevisionEditor {
|
||||
rev_manager,
|
||||
block_manager,
|
||||
view_manager,
|
||||
filter_service: Arc::new(filter_service),
|
||||
});
|
||||
|
||||
Ok(editor)
|
||||
@ -97,8 +88,11 @@ impl GridRevisionEditor {
|
||||
#[tracing::instrument(name = "close grid editor", level = "trace", skip_all)]
|
||||
pub fn close(&self) {
|
||||
let rev_manager = self.rev_manager.clone();
|
||||
let view_manager = self.view_manager.clone();
|
||||
let view_id = self.grid_id.clone();
|
||||
tokio::spawn(async move {
|
||||
rev_manager.close().await;
|
||||
view_manager.close(&view_id).await;
|
||||
});
|
||||
}
|
||||
|
||||
@ -352,7 +346,6 @@ impl GridRevisionEditor {
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
let _ = self.view_manager.did_update_view_field(¶ms.field_id).await?;
|
||||
if is_type_option_changed {
|
||||
let _ = self
|
||||
.view_manager
|
||||
@ -418,20 +411,16 @@ impl GridRevisionEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_rows(&self, block_id: &str) -> FlowyResult<RepeatedRowPB> {
|
||||
let block_ids = vec![block_id.to_owned()];
|
||||
let mut grid_block_snapshot = self.grid_block_snapshots(Some(block_ids)).await?;
|
||||
|
||||
// For the moment, we only support one block.
|
||||
// We can save the rows into multiple blocks and load them asynchronously in the future.
|
||||
debug_assert_eq!(grid_block_snapshot.len(), 1);
|
||||
if grid_block_snapshot.len() == 1 {
|
||||
let snapshot = grid_block_snapshot.pop().unwrap();
|
||||
let rows = make_rows_from_row_revs(&snapshot.row_revs);
|
||||
Ok(rows.into())
|
||||
} else {
|
||||
Ok(vec![].into())
|
||||
}
|
||||
pub async fn get_row_pbs(&self, block_id: &str) -> FlowyResult<Vec<RowPB>> {
|
||||
let rows = self.block_manager.get_row_revs(block_id).await?;
|
||||
let rows = self
|
||||
.view_manager
|
||||
.filter_rows(block_id, rows)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|row_rev| RowPB::from(&row_rev))
|
||||
.collect();
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
|
||||
@ -514,75 +503,12 @@ impl GridRevisionEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_blocks(&self, block_ids: Option<Vec<String>>) -> FlowyResult<RepeatedBlockPB> {
|
||||
let block_snapshots = self.grid_block_snapshots(block_ids.clone()).await?;
|
||||
make_grid_blocks(block_ids, block_snapshots)
|
||||
}
|
||||
|
||||
pub async fn get_block_meta_revs(&self) -> FlowyResult<Vec<Arc<GridBlockMetaRevision>>> {
|
||||
let block_meta_revs = self.grid_pad.read().await.get_block_meta_revs();
|
||||
Ok(block_meta_revs)
|
||||
}
|
||||
|
||||
pub async fn delete_rows(&self, row_orders: Vec<RowPB>) -> FlowyResult<()> {
|
||||
let changesets = self.block_manager.delete_rows(row_orders).await?;
|
||||
for changeset in changesets {
|
||||
let _ = self.update_block(changeset).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_grid_data(&self) -> FlowyResult<GridPB> {
|
||||
let pad_read_guard = self.grid_pad.read().await;
|
||||
let field_orders = pad_read_guard
|
||||
.get_field_revs(None)?
|
||||
.iter()
|
||||
.map(FieldIdPB::from)
|
||||
.collect();
|
||||
let mut block_orders = vec![];
|
||||
for block_rev in pad_read_guard.get_block_meta_revs() {
|
||||
let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?;
|
||||
let block_order = BlockPB {
|
||||
id: block_rev.block_id.clone(),
|
||||
rows: row_orders,
|
||||
};
|
||||
block_orders.push(block_order);
|
||||
}
|
||||
|
||||
Ok(GridPB {
|
||||
id: self.grid_id.clone(),
|
||||
fields: field_orders,
|
||||
blocks: block_orders,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_grid_setting(&self) -> FlowyResult<GridSettingPB> {
|
||||
self.view_manager.get_setting().await
|
||||
}
|
||||
|
||||
pub async fn get_grid_filter(&self) -> FlowyResult<Vec<GridFilterConfigurationPB>> {
|
||||
self.view_manager.get_filters().await
|
||||
}
|
||||
|
||||
pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.insert_or_update_group(params).await
|
||||
}
|
||||
|
||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.delete_group(params).await
|
||||
}
|
||||
|
||||
pub async fn create_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
let _ = self.view_manager.insert_or_update_filter(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
||||
let _ = self.view_manager.delete_filter(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn grid_block_snapshots(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlockSnapshot>> {
|
||||
pub async fn get_blocks(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlock>> {
|
||||
let block_ids = match block_ids {
|
||||
None => self
|
||||
.grid_pad
|
||||
@ -594,8 +520,73 @@ impl GridRevisionEditor {
|
||||
.collect::<Vec<String>>(),
|
||||
Some(block_ids) => block_ids,
|
||||
};
|
||||
let snapshots = self.block_manager.get_block_snapshots(Some(block_ids)).await?;
|
||||
Ok(snapshots)
|
||||
let blocks = self.block_manager.get_blocks(Some(block_ids)).await?;
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
pub async fn delete_rows(&self, row_orders: Vec<RowPB>) -> FlowyResult<()> {
|
||||
let changesets = self.block_manager.delete_rows(row_orders).await?;
|
||||
for changeset in changesets {
|
||||
let _ = self.update_block(changeset).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_grid(&self) -> FlowyResult<GridPB> {
|
||||
let pad = self.grid_pad.read().await;
|
||||
let fields = pad.get_field_revs(None)?.iter().map(FieldIdPB::from).collect();
|
||||
|
||||
let mut blocks = vec![];
|
||||
for block_rev in pad.get_block_meta_revs() {
|
||||
let rows = self.get_row_pbs(&block_rev.block_id).await?;
|
||||
let block = BlockPB {
|
||||
id: block_rev.block_id.clone(),
|
||||
rows,
|
||||
};
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
Ok(GridPB {
|
||||
id: self.grid_id.clone(),
|
||||
fields,
|
||||
blocks,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_setting(&self) -> FlowyResult<GridSettingPB> {
|
||||
self.view_manager.get_setting().await
|
||||
}
|
||||
|
||||
pub async fn get_all_filters(&self) -> FlowyResult<Vec<FilterPB>> {
|
||||
Ok(self
|
||||
.view_manager
|
||||
.get_all_filters()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|filter| FilterPB::from(filter.as_ref()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_filters(&self, filter_id: FilterType) -> FlowyResult<Vec<Arc<FilterRevision>>> {
|
||||
self.view_manager.get_filters(&filter_id).await
|
||||
}
|
||||
|
||||
pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.insert_or_update_group(params).await
|
||||
}
|
||||
|
||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.view_manager.delete_group(params).await
|
||||
}
|
||||
|
||||
pub async fn create_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
|
||||
let _ = self.view_manager.insert_or_update_filter(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
||||
let _ = self.view_manager.delete_filter(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {
|
||||
@ -641,7 +632,7 @@ impl GridRevisionEditor {
|
||||
let block_manager = self.block_manager.clone();
|
||||
self.view_manager
|
||||
.move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| {
|
||||
wrap_future(async move {
|
||||
to_future(async move {
|
||||
tracing::trace!("Row data changed: {:?}", row_changeset);
|
||||
let cell_changesets = row_changeset
|
||||
.cell_by_field_id
|
||||
|
@ -1,42 +0,0 @@
|
||||
use crate::manager::GridTaskSchedulerRwLock;
|
||||
use crate::services::grid_editor::GridRevisionEditor;
|
||||
use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskId};
|
||||
use flowy_error::FlowyError;
|
||||
use futures::future::BoxFuture;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
|
||||
pub(crate) trait GridServiceTaskScheduler: Send + Sync + 'static {
|
||||
fn gen_task_id(&self) -> BoxFuture<TaskId>;
|
||||
fn add_task(&self, task: Task) -> BoxFuture<()>;
|
||||
}
|
||||
|
||||
impl GridTaskHandler for GridRevisionEditor {
|
||||
fn handler_id(&self) -> &str {
|
||||
&self.grid_id
|
||||
}
|
||||
|
||||
fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError> {
|
||||
Box::pin(async move {
|
||||
match content {
|
||||
TaskContent::Snapshot => {}
|
||||
TaskContent::Group => {}
|
||||
TaskContent::Filter(context) => self.filter_service.process(context).await?,
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GridServiceTaskScheduler for GridTaskSchedulerRwLock {
|
||||
fn gen_task_id(&self) -> BoxFuture<TaskId> {
|
||||
let this = self.clone();
|
||||
Box::pin(async move { this.read().await.next_task_id() })
|
||||
}
|
||||
|
||||
fn add_task(&self, task: Task) -> BoxFuture<()> {
|
||||
let this = self.clone();
|
||||
Box::pin(async move {
|
||||
this.write().await.add_task(task);
|
||||
})
|
||||
}
|
||||
}
|
@ -1,15 +1,24 @@
|
||||
use crate::services::grid_view_manager::GridViewFieldDelegate;
|
||||
use crate::services::block_manager::GridBlockManager;
|
||||
use crate::services::grid_view_editor::GridViewEditorDelegate;
|
||||
use crate::services::row::GridBlock;
|
||||
use flowy_sync::client_grid::GridRevisionPad;
|
||||
use grid_rev_model::FieldRevision;
|
||||
use lib_infra::future::{wrap_future, AFFuture};
|
||||
use flowy_task::TaskDispatcher;
|
||||
use grid_rev_model::{FieldRevision, RowRevision};
|
||||
use lib_infra::future::{to_future, Fut};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
impl GridViewFieldDelegate for Arc<RwLock<GridRevisionPad>> {
|
||||
fn get_field_revs(&self) -> AFFuture<Vec<Arc<FieldRevision>>> {
|
||||
let pad = self.clone();
|
||||
wrap_future(async move {
|
||||
match pad.read().await.get_field_revs(None) {
|
||||
pub(crate) struct GridViewEditorDelegateImpl {
|
||||
pub(crate) pad: Arc<RwLock<GridRevisionPad>>,
|
||||
pub(crate) block_manager: Arc<GridBlockManager>,
|
||||
pub(crate) task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
|
||||
impl GridViewEditorDelegate for GridViewEditorDelegateImpl {
|
||||
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>> {
|
||||
let pad = self.pad.clone();
|
||||
to_future(async move {
|
||||
match pad.read().await.get_field_revs(field_ids) {
|
||||
Ok(field_revs) => field_revs,
|
||||
Err(e) => {
|
||||
tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e);
|
||||
@ -19,14 +28,47 @@ impl GridViewFieldDelegate for Arc<RwLock<GridRevisionPad>> {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_field_rev(&self, field_id: &str) -> AFFuture<Option<Arc<FieldRevision>>> {
|
||||
let pad = self.clone();
|
||||
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>> {
|
||||
let pad = self.pad.clone();
|
||||
let field_id = field_id.to_owned();
|
||||
wrap_future(async move {
|
||||
pad.read()
|
||||
.await
|
||||
.get_field_rev(&field_id)
|
||||
.map(|(_, field_rev)| field_rev.clone())
|
||||
to_future(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) })
|
||||
}
|
||||
|
||||
fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>> {
|
||||
let block_manager = self.block_manager.clone();
|
||||
let row_id = row_id.to_owned();
|
||||
to_future(async move { block_manager.index_of_row(&row_id).await })
|
||||
}
|
||||
|
||||
fn get_row_rev(&self, row_id: &str) -> Fut<Option<Arc<RowRevision>>> {
|
||||
let block_manager = self.block_manager.clone();
|
||||
let row_id = row_id.to_owned();
|
||||
to_future(async move {
|
||||
match block_manager.get_row_rev(&row_id).await {
|
||||
Ok(row_rev) => row_rev,
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_row_revs(&self) -> Fut<Vec<Arc<RowRevision>>> {
|
||||
let block_manager = self.block_manager.clone();
|
||||
|
||||
to_future(async move {
|
||||
let blocks = block_manager.get_blocks(None).await.unwrap();
|
||||
blocks
|
||||
.into_iter()
|
||||
.flat_map(|block| block.row_revs)
|
||||
.collect::<Vec<Arc<RowRevision>>>()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_blocks(&self) -> Fut<Vec<GridBlock>> {
|
||||
let block_manager = self.block_manager.clone();
|
||||
to_future(async move { block_manager.get_blocks(None).await.unwrap_or_default() })
|
||||
}
|
||||
|
||||
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>> {
|
||||
self.task_scheduler.clone()
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::{
|
||||
CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridGroupConfigurationPB,
|
||||
GridLayout, GridLayoutPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertFilterParams,
|
||||
InsertGroupParams, InsertedGroupPB, InsertedRowPB, MoveGroupParams, RepeatedGridFilterConfigurationPB,
|
||||
RepeatedGridGroupConfigurationPB, RowPB,
|
||||
use crate::entities::*;
|
||||
use crate::services::filter::{
|
||||
FilterChangeset, FilterController, FilterTaskHandler, FilterType, GridViewFilterDelegate,
|
||||
};
|
||||
use crate::services::grid_editor_task::GridServiceTaskScheduler;
|
||||
use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate};
|
||||
use crate::services::group::{
|
||||
default_group_configuration, find_group_field, make_group_controller, GroupConfigurationReader,
|
||||
default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader,
|
||||
GroupConfigurationWriter, GroupController, MoveGroupRowContext,
|
||||
};
|
||||
use crate::services::row::GridBlock;
|
||||
use bytes::Bytes;
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -20,26 +17,39 @@ use flowy_revision::{
|
||||
};
|
||||
use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use flowy_task::TaskDispatcher;
|
||||
use grid_rev_model::{
|
||||
gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision,
|
||||
RowChangeset, RowRevision,
|
||||
gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, RowChangeset,
|
||||
RowRevision,
|
||||
};
|
||||
use lib_infra::future::{wrap_future, AFFuture, FutureResult};
|
||||
use lib_infra::future::{to_future, Fut, FutureResult};
|
||||
use lib_ot::core::EmptyAttributes;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub trait GridViewEditorDelegate: Send + Sync + 'static {
|
||||
/// If the field_ids is None, then it will return all the field revisions
|
||||
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>>;
|
||||
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>>;
|
||||
|
||||
fn index_of_row(&self, row_id: &str) -> Fut<Option<usize>>;
|
||||
fn get_row_rev(&self, row_id: &str) -> Fut<Option<Arc<RowRevision>>>;
|
||||
fn get_row_revs(&self) -> Fut<Vec<Arc<RowRevision>>>;
|
||||
fn get_blocks(&self) -> Fut<Vec<GridBlock>>;
|
||||
|
||||
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GridViewRevisionEditor {
|
||||
user_id: String,
|
||||
view_id: String,
|
||||
pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
group_controller: Arc<RwLock<Box<dyn GroupController>>>,
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
filter_controller: Arc<RwLock<FilterController>>,
|
||||
}
|
||||
impl GridViewRevisionEditor {
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
@ -47,9 +57,7 @@ impl GridViewRevisionEditor {
|
||||
user_id: &str,
|
||||
token: &str,
|
||||
view_id: String,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
mut rev_manager: RevisionManager<Arc<ConnectionPool>>,
|
||||
) -> FlowyResult<Self> {
|
||||
let cloud = Arc::new(GridViewRevisionCloudService {
|
||||
@ -63,23 +71,33 @@ impl GridViewRevisionEditor {
|
||||
view_id.clone(),
|
||||
pad.clone(),
|
||||
rev_manager.clone(),
|
||||
field_delegate.clone(),
|
||||
row_delegate.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
let group_controller = Arc::new(RwLock::new(group_controller));
|
||||
let filter_controller = make_filter_controller(&view_id, delegate.clone(), pad.clone()).await;
|
||||
Ok(Self {
|
||||
pad,
|
||||
user_id,
|
||||
view_id,
|
||||
rev_manager,
|
||||
scheduler,
|
||||
field_delegate,
|
||||
row_delegate,
|
||||
group_controller: Arc::new(RwLock::new(group_controller)),
|
||||
delegate,
|
||||
group_controller,
|
||||
filter_controller,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn close(&self) {
|
||||
self.filter_controller.read().await.close().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn filter_rows(&self, _block_id: &str, mut rows: Vec<Arc<RowRevision>>) -> Vec<Arc<RowRevision>> {
|
||||
self.filter_controller.write().await.filter_row_revs(&mut rows).await;
|
||||
rows
|
||||
}
|
||||
|
||||
pub(crate) async fn duplicate_view_data(&self) -> FlowyResult<String> {
|
||||
let json_str = self.pad.read().await.json_str()?;
|
||||
Ok(json_str)
|
||||
@ -178,7 +196,14 @@ impl GridViewRevisionEditor {
|
||||
/// Only call once after grid view editor initialized
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn load_view_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
||||
let groups = self.group_controller.read().await.groups();
|
||||
let groups = self
|
||||
.group_controller
|
||||
.read()
|
||||
.await
|
||||
.groups()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect::<Vec<Group>>();
|
||||
tracing::trace!("Number of groups: {}", groups.len());
|
||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
||||
}
|
||||
@ -212,32 +237,30 @@ impl GridViewRevisionEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn group_id(&self) -> String {
|
||||
self.group_controller.read().await.field_id().to_owned()
|
||||
pub(crate) async fn is_grouped(&self) -> bool {
|
||||
self.group_controller.read().await.groups().len() > 1
|
||||
}
|
||||
|
||||
pub(crate) async fn get_view_setting(&self) -> GridSettingPB {
|
||||
let field_revs = self.field_delegate.get_field_revs().await;
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs);
|
||||
grid_setting
|
||||
}
|
||||
|
||||
pub(crate) async fn get_view_filters(&self) -> Vec<GridFilterConfigurationPB> {
|
||||
let field_revs = self.field_delegate.get_field_revs().await;
|
||||
match self.pad.read().await.get_all_filters(&field_revs) {
|
||||
None => vec![],
|
||||
Some(filters) => filters
|
||||
.into_values()
|
||||
.flatten()
|
||||
.map(|filter| GridFilterConfigurationPB::from(filter.as_ref()))
|
||||
.collect(),
|
||||
}
|
||||
pub(crate) async fn get_all_view_filters(&self) -> Vec<Arc<FilterRevision>> {
|
||||
let field_revs = self.delegate.get_field_revs(None).await;
|
||||
self.pad.read().await.get_all_filters(&field_revs)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_view_filters(&self, filter_id: &FilterType) -> Vec<Arc<FilterRevision>> {
|
||||
let field_type_rev: FieldTypeRevision = filter_id.field_type.clone().into();
|
||||
self.pad.read().await.get_filters(&filter_id.field_id, &field_type_rev)
|
||||
}
|
||||
|
||||
/// Initialize new group when grouping by a new field
|
||||
///
|
||||
pub(crate) async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
|
||||
if let Some(field_rev) = self.field_delegate.get_field_rev(¶ms.field_id).await {
|
||||
if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await {
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let configuration = default_group_configuration(&field_rev);
|
||||
@ -259,44 +282,64 @@ impl GridViewRevisionEditor {
|
||||
|
||||
pub(crate) async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?;
|
||||
let changeset = pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_view_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let filter_rev = FilterConfigurationRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
field_id: params.field_id.clone(),
|
||||
condition: params.condition,
|
||||
content: params.content,
|
||||
};
|
||||
let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await
|
||||
pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
|
||||
let filter_type = FilterType::from(¶ms);
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let filter_rev = FilterRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
field_id: params.field_id.clone(),
|
||||
condition: params.condition,
|
||||
content: params.content,
|
||||
};
|
||||
let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.filter_controller
|
||||
.write()
|
||||
.await
|
||||
.apply_changeset(FilterChangeset::from_insert(filter_type))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_view_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.delete_filter(
|
||||
&delete_filter.field_id,
|
||||
&delete_filter.field_type_rev,
|
||||
&delete_filter.filter_id,
|
||||
)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await
|
||||
pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
||||
let filter_type = params.filter_type;
|
||||
let field_type_rev = filter_type.field_type_rev();
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let changeset = pad.delete_filter(¶ms.filter_id, &filter_type.field_id, &field_type_rev)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.filter_controller
|
||||
.write()
|
||||
.await
|
||||
.apply_changeset(FilterChangeset::from_delete(filter_type))
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn did_update_view_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let grouped_field_id = self.group_controller.read().await.field_id().to_owned();
|
||||
if grouped_field_id == field_id {
|
||||
let _ = self.group_by_view_field(field_id).await?;
|
||||
} else {
|
||||
// Do nothing
|
||||
pub(crate) async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> {
|
||||
if let Some(field_rev) = self.delegate.get_field_rev(field_id).await {
|
||||
let filter_type = FilterType::from(&field_rev);
|
||||
let filter_changeset = FilterChangeset::from_insert(filter_type);
|
||||
self.filter_controller
|
||||
.write()
|
||||
.await
|
||||
.apply_changeset(filter_changeset)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -309,18 +352,23 @@ impl GridViewRevisionEditor {
|
||||
///
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await {
|
||||
if let Some(field_rev) = self.delegate.get_field_rev(field_id).await {
|
||||
let row_revs = self.delegate.get_row_revs().await;
|
||||
let new_group_controller = new_group_controller_with_field_rev(
|
||||
self.user_id.clone(),
|
||||
self.view_id.clone(),
|
||||
self.pad.clone(),
|
||||
self.rev_manager.clone(),
|
||||
field_rev,
|
||||
self.row_delegate.clone(),
|
||||
row_revs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let new_groups = new_group_controller.groups().into_iter().map(GroupPB::from).collect();
|
||||
let new_groups = new_group_controller
|
||||
.groups()
|
||||
.into_iter()
|
||||
.map(|group| GroupPB::from(group.clone()))
|
||||
.collect();
|
||||
|
||||
*self.group_controller.write().await = new_group_controller;
|
||||
let changeset = GroupViewChangesetPB {
|
||||
@ -377,7 +425,7 @@ impl GridViewRevisionEditor {
|
||||
F: FnOnce(&mut Box<dyn GroupController>, Arc<FieldRevision>) -> FlowyResult<T>,
|
||||
{
|
||||
let group_field_id = self.group_controller.read().await.field_id().to_owned();
|
||||
match self.field_delegate.get_field_rev(&group_field_id).await {
|
||||
match self.delegate.get_field_rev(&group_field_id).await {
|
||||
None => None,
|
||||
Some(field_rev) => {
|
||||
let mut write_guard = self.group_controller.write().await;
|
||||
@ -393,7 +441,7 @@ impl GridViewRevisionEditor {
|
||||
O: Future<Output = FlowyResult<T>> + Sync + 'static,
|
||||
{
|
||||
let group_field_id = self.group_controller.read().await.field_id().to_owned();
|
||||
match self.field_delegate.get_field_rev(&group_field_id).await {
|
||||
match self.delegate.get_field_rev(&group_field_id).await {
|
||||
None => None,
|
||||
Some(field_rev) => {
|
||||
let _write_guard = self.group_controller.write().await;
|
||||
@ -408,11 +456,11 @@ async fn new_group_controller(
|
||||
view_id: String,
|
||||
view_rev_pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
) -> FlowyResult<Box<dyn GroupController>> {
|
||||
let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone());
|
||||
let field_revs = field_delegate.get_field_revs().await;
|
||||
let field_revs = delegate.get_field_revs(None).await;
|
||||
let row_revs = delegate.get_row_revs().await;
|
||||
let layout = view_rev_pad.read().await.layout();
|
||||
// Read the group field or find a new group field
|
||||
let field_rev = configuration_reader
|
||||
@ -426,27 +474,18 @@ async fn new_group_controller(
|
||||
})
|
||||
.unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap());
|
||||
|
||||
new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_delegate).await
|
||||
new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_revs).await
|
||||
}
|
||||
|
||||
/// Returns a [GroupController]
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id`:
|
||||
/// * `view_id`:
|
||||
/// * `view_rev_pad`:
|
||||
/// * `rev_manager`:
|
||||
/// * `field_rev`:
|
||||
/// * `row_delegate`:
|
||||
///
|
||||
async fn new_group_controller_with_field_rev(
|
||||
user_id: String,
|
||||
view_id: String,
|
||||
view_rev_pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
field_rev: Arc<FieldRevision>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
row_revs: Vec<Arc<RowRevision>>,
|
||||
) -> FlowyResult<Box<dyn GroupController>> {
|
||||
let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone());
|
||||
let configuration_writer = GroupConfigurationWriterImpl {
|
||||
@ -454,10 +493,30 @@ async fn new_group_controller_with_field_rev(
|
||||
rev_manager,
|
||||
view_pad: view_rev_pad,
|
||||
};
|
||||
let row_revs = row_delegate.gv_row_revs().await;
|
||||
make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await
|
||||
}
|
||||
|
||||
async fn make_filter_controller(
|
||||
view_id: &str,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
) -> Arc<RwLock<FilterController>> {
|
||||
let field_revs = delegate.get_field_revs(None).await;
|
||||
let filter_revs = pad.read().await.get_all_filters(&field_revs);
|
||||
let task_scheduler = delegate.get_task_scheduler();
|
||||
let filter_delegate = GridViewFilterDelegateImpl {
|
||||
editor_delegate: delegate.clone(),
|
||||
view_revision_pad: pad,
|
||||
};
|
||||
let filter_controller = FilterController::new(view_id, filter_delegate, task_scheduler.clone(), filter_revs).await;
|
||||
let filter_controller = Arc::new(RwLock::new(filter_controller));
|
||||
task_scheduler
|
||||
.write()
|
||||
.await
|
||||
.register_handler(FilterTaskHandler::new(filter_controller.clone()));
|
||||
filter_controller
|
||||
}
|
||||
|
||||
async fn apply_change(
|
||||
_user_id: &str,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
@ -477,7 +536,6 @@ struct GridViewRevisionCloudService {
|
||||
}
|
||||
|
||||
impl RevisionCloudService for GridViewRevisionCloudService {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
}
|
||||
@ -510,9 +568,9 @@ impl RevisionMergeable for GridViewRevisionCompress {
|
||||
struct GroupConfigurationReaderImpl(Arc<RwLock<GridViewRevisionPad>>);
|
||||
|
||||
impl GroupConfigurationReader for GroupConfigurationReaderImpl {
|
||||
fn get_configuration(&self) -> AFFuture<Option<Arc<GroupConfigurationRevision>>> {
|
||||
fn get_configuration(&self) -> Fut<Option<Arc<GroupConfigurationRevision>>> {
|
||||
let view_pad = self.0.clone();
|
||||
wrap_future(async move {
|
||||
to_future(async move {
|
||||
let mut groups = view_pad.read().await.get_all_groups();
|
||||
if groups.is_empty() {
|
||||
None
|
||||
@ -536,13 +594,13 @@ impl GroupConfigurationWriter for GroupConfigurationWriterImpl {
|
||||
field_id: &str,
|
||||
field_type: FieldTypeRevision,
|
||||
group_configuration: GroupConfigurationRevision,
|
||||
) -> AFFuture<FlowyResult<()>> {
|
||||
) -> Fut<FlowyResult<()>> {
|
||||
let user_id = self.user_id.clone();
|
||||
let rev_manager = self.rev_manager.clone();
|
||||
let view_pad = self.view_pad.clone();
|
||||
let field_id = field_id.to_owned();
|
||||
|
||||
wrap_future(async move {
|
||||
to_future(async move {
|
||||
let changeset = view_pad.write().await.insert_or_update_group_configuration(
|
||||
&field_id,
|
||||
&field_type,
|
||||
@ -561,29 +619,15 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<Field
|
||||
let layout_type: GridLayout = view_pad.layout.clone().into();
|
||||
let filter_configurations = view_pad
|
||||
.get_all_filters(field_revs)
|
||||
.map(|filters_by_field_id| {
|
||||
filters_by_field_id
|
||||
.into_iter()
|
||||
.flat_map(|(_, v)| {
|
||||
let repeated_filter: RepeatedGridFilterConfigurationPB = v.into();
|
||||
repeated_filter.items
|
||||
})
|
||||
.collect::<Vec<GridFilterConfigurationPB>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.into_iter()
|
||||
.map(|filter| FilterPB::from(filter.as_ref()))
|
||||
.collect::<Vec<FilterPB>>();
|
||||
|
||||
let group_configurations = view_pad
|
||||
.get_groups_by_field_revs(field_revs)
|
||||
.map(|groups_by_field_id| {
|
||||
groups_by_field_id
|
||||
.into_iter()
|
||||
.flat_map(|(_, v)| {
|
||||
let repeated_group: RepeatedGridGroupConfigurationPB = v.into();
|
||||
repeated_group.items
|
||||
})
|
||||
.collect::<Vec<GridGroupConfigurationPB>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.into_iter()
|
||||
.map(|group| GridGroupConfigurationPB::from(group.as_ref()))
|
||||
.collect::<Vec<GridGroupConfigurationPB>>();
|
||||
|
||||
GridSettingPB {
|
||||
layouts: GridLayoutPB::all(),
|
||||
@ -593,6 +637,33 @@ pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<Field
|
||||
}
|
||||
}
|
||||
|
||||
struct GridViewFilterDelegateImpl {
|
||||
editor_delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
view_revision_pad: Arc<RwLock<GridViewRevisionPad>>,
|
||||
}
|
||||
|
||||
impl GridViewFilterDelegate for GridViewFilterDelegateImpl {
|
||||
fn get_filter_rev(&self, filter_id: FilterType) -> Fut<Vec<Arc<FilterRevision>>> {
|
||||
let pad = self.view_revision_pad.clone();
|
||||
to_future(async move {
|
||||
let field_type_rev: FieldTypeRevision = filter_id.field_type.into();
|
||||
pad.read().await.get_filters(&filter_id.field_id, &field_type_rev)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>> {
|
||||
self.editor_delegate.get_field_rev(field_id)
|
||||
}
|
||||
|
||||
fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<FieldRevision>>> {
|
||||
self.editor_delegate.get_field_revs(field_ids)
|
||||
}
|
||||
|
||||
fn get_blocks(&self) -> Fut<Vec<GridBlock>> {
|
||||
self.editor_delegate.get_blocks()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use flowy_sync::client_grid::GridOperations;
|
||||
|
@ -1,62 +1,59 @@
|
||||
use crate::entities::{
|
||||
CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridSettingPB,
|
||||
InsertFilterParams, InsertGroupParams, MoveGroupParams, RepeatedGridGroupPB, RowPB,
|
||||
CreateFilterParams, CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridSettingPB, InsertGroupParams,
|
||||
MoveGroupParams, RepeatedGridGroupPB, RowPB,
|
||||
};
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::grid_editor_task::GridServiceTaskScheduler;
|
||||
use crate::services::grid_view_editor::{GridViewRevisionCompress, GridViewRevisionEditor};
|
||||
|
||||
use crate::services::grid_view_editor::{GridViewEditorDelegate, GridViewRevisionCompress, GridViewRevisionEditor};
|
||||
use crate::services::persistence::rev_sqlite::SQLiteGridViewRevisionPersistence;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_revision::{
|
||||
RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, SQLiteRevisionSnapshotPersistence,
|
||||
};
|
||||
use grid_rev_model::{FieldRevision, RowChangeset, RowRevision};
|
||||
use lib_infra::future::AFFuture;
|
||||
|
||||
use crate::services::filter::FilterType;
|
||||
use grid_rev_model::{FilterRevision, RowChangeset, RowRevision};
|
||||
use lib_infra::future::Fut;
|
||||
use std::sync::Arc;
|
||||
|
||||
type ViewId = String;
|
||||
|
||||
pub trait GridViewFieldDelegate: Send + Sync + 'static {
|
||||
fn get_field_revs(&self) -> AFFuture<Vec<Arc<FieldRevision>>>;
|
||||
fn get_field_rev(&self, field_id: &str) -> AFFuture<Option<Arc<FieldRevision>>>;
|
||||
}
|
||||
|
||||
pub trait GridViewRowDelegate: Send + Sync + 'static {
|
||||
fn gv_index_of_row(&self, row_id: &str) -> AFFuture<Option<usize>>;
|
||||
fn gv_get_row_rev(&self, row_id: &str) -> AFFuture<Option<Arc<RowRevision>>>;
|
||||
fn gv_row_revs(&self) -> AFFuture<Vec<Arc<RowRevision>>>;
|
||||
}
|
||||
|
||||
pub(crate) struct GridViewManager {
|
||||
grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
view_editors: DashMap<ViewId, Arc<GridViewRevisionEditor>>,
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
}
|
||||
|
||||
impl GridViewManager {
|
||||
pub(crate) async fn new(
|
||||
grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
) -> FlowyResult<Self> {
|
||||
Ok(Self {
|
||||
grid_id,
|
||||
user,
|
||||
scheduler,
|
||||
field_delegate,
|
||||
row_delegate,
|
||||
delegate,
|
||||
view_editors: DashMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn close(&self, _view_id: &str) {
|
||||
if let Ok(editor) = self.get_default_view_editor().await {
|
||||
let _ = editor.close().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn filter_rows(&self, block_id: &str, rows: Vec<Arc<RowRevision>>) -> FlowyResult<Vec<Arc<RowRevision>>> {
|
||||
let editor = self.get_default_view_editor().await?;
|
||||
let rows = editor.filter_rows(block_id, rows).await;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
pub(crate) async fn duplicate_grid_view(&self) -> FlowyResult<String> {
|
||||
let editor = self.get_default_view_editor().await?;
|
||||
let view_data = editor.duplicate_view_data().await?;
|
||||
@ -79,7 +76,7 @@ impl GridViewManager {
|
||||
|
||||
/// Insert/Delete the group's row if the corresponding cell data was changed.
|
||||
pub(crate) async fn did_update_cell(&self, row_id: &str) {
|
||||
match self.row_delegate.gv_get_row_rev(row_id).await {
|
||||
match self.delegate.get_row_rev(row_id).await {
|
||||
None => {
|
||||
tracing::warn!("Can not find the row in grid view");
|
||||
}
|
||||
@ -108,12 +105,17 @@ impl GridViewManager {
|
||||
Ok(view_editor.get_view_setting().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfigurationPB>> {
|
||||
pub(crate) async fn get_all_filters(&self) -> FlowyResult<Vec<Arc<FilterRevision>>> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
Ok(view_editor.get_view_filters().await)
|
||||
Ok(view_editor.get_all_view_filters().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_or_update_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
pub(crate) async fn get_filters(&self, filter_id: &FilterType) -> FlowyResult<Vec<Arc<FilterRevision>>> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
Ok(view_editor.get_view_filters(filter_id).await)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_or_update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
view_editor.insert_view_filter(params).await
|
||||
}
|
||||
@ -153,7 +155,7 @@ impl GridViewManager {
|
||||
row_rev: Arc<RowRevision>,
|
||||
to_group_id: String,
|
||||
to_row_id: Option<String>,
|
||||
recv_row_changeset: impl FnOnce(RowChangeset) -> AFFuture<()>,
|
||||
recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>,
|
||||
) -> FlowyResult<()> {
|
||||
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
@ -172,17 +174,6 @@ impl GridViewManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) async fn did_update_view_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
// Update the group if the group_id equal to the field_id
|
||||
if view_editor.group_id().await != field_id {
|
||||
return Ok(());
|
||||
}
|
||||
let _ = view_editor.did_update_view_field(field_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Notifies the view's field type-option data is changed
|
||||
/// For the moment, only the groups will be generated after the type-option data changed. A
|
||||
/// [FieldRevision] has a property named type_options contains a list of type-option data.
|
||||
@ -193,7 +184,11 @@ impl GridViewManager {
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
let _ = view_editor.did_update_view_field(field_id).await?;
|
||||
if view_editor.is_grouped().await {
|
||||
let _ = view_editor.group_by_view_field(field_id).await?;
|
||||
}
|
||||
|
||||
let _ = view_editor.did_update_view_field_type_option(field_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -201,16 +196,7 @@ impl GridViewManager {
|
||||
debug_assert!(!view_id.is_empty());
|
||||
match self.view_editors.get(view_id) {
|
||||
None => {
|
||||
let editor = Arc::new(
|
||||
make_view_editor(
|
||||
&self.user,
|
||||
view_id,
|
||||
self.field_delegate.clone(),
|
||||
self.row_delegate.clone(),
|
||||
self.scheduler.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
let editor = Arc::new(make_view_editor(&self.user, view_id, self.delegate.clone()).await?);
|
||||
self.view_editors.insert(view_id.to_owned(), editor.clone());
|
||||
Ok(editor)
|
||||
}
|
||||
@ -226,25 +212,14 @@ impl GridViewManager {
|
||||
async fn make_view_editor(
|
||||
user: &Arc<dyn GridUser>,
|
||||
view_id: &str,
|
||||
field_delegate: Arc<dyn GridViewFieldDelegate>,
|
||||
row_delegate: Arc<dyn GridViewRowDelegate>,
|
||||
scheduler: Arc<dyn GridServiceTaskScheduler>,
|
||||
delegate: Arc<dyn GridViewEditorDelegate>,
|
||||
) -> FlowyResult<GridViewRevisionEditor> {
|
||||
let rev_manager = make_grid_view_rev_manager(user, view_id).await?;
|
||||
let user_id = user.user_id()?;
|
||||
let token = user.token()?;
|
||||
let view_id = view_id.to_owned();
|
||||
|
||||
GridViewRevisionEditor::new(
|
||||
&user_id,
|
||||
&token,
|
||||
view_id,
|
||||
field_delegate,
|
||||
row_delegate,
|
||||
scheduler,
|
||||
rev_manager,
|
||||
)
|
||||
.await
|
||||
GridViewRevisionEditor::new(&user_id, &token, view_id, delegate, rev_manager).await
|
||||
}
|
||||
|
||||
pub async fn make_grid_view_rev_manager(
|
||||
|
@ -46,7 +46,7 @@ pub trait GroupControllerSharedActions: Send + Sync {
|
||||
fn field_id(&self) -> &str;
|
||||
|
||||
/// Returns number of groups the current field has
|
||||
fn groups(&self) -> Vec<Group>;
|
||||
fn groups(&self) -> Vec<&Group>;
|
||||
|
||||
/// Returns the index and the group data with group_id
|
||||
fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
|
||||
|
@ -5,14 +5,14 @@ use grid_rev_model::{
|
||||
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use lib_infra::future::AFFuture;
|
||||
use lib_infra::future::Fut;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Formatter;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait GroupConfigurationReader: Send + Sync + 'static {
|
||||
fn get_configuration(&self) -> AFFuture<Option<Arc<GroupConfigurationRevision>>>;
|
||||
fn get_configuration(&self) -> Fut<Option<Arc<GroupConfigurationRevision>>>;
|
||||
}
|
||||
|
||||
pub trait GroupConfigurationWriter: Send + Sync + 'static {
|
||||
@ -21,7 +21,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static {
|
||||
field_id: &str,
|
||||
field_type: FieldTypeRevision,
|
||||
group_configuration: GroupConfigurationRevision,
|
||||
) -> AFFuture<FlowyResult<()>>;
|
||||
) -> Fut<FlowyResult<()>>;
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Display for GroupContext<T> {
|
||||
|
@ -165,8 +165,8 @@ where
|
||||
&self.field_id
|
||||
}
|
||||
|
||||
fn groups(&self) -> Vec<Group> {
|
||||
self.group_ctx.groups().into_iter().cloned().collect()
|
||||
fn groups(&self) -> Vec<&Group> {
|
||||
self.group_ctx.groups()
|
||||
}
|
||||
|
||||
fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
|
||||
|
@ -36,8 +36,8 @@ impl GroupControllerSharedActions for DefaultGroupController {
|
||||
&self.field_id
|
||||
}
|
||||
|
||||
fn groups(&self) -> Vec<Group> {
|
||||
vec![self.group.clone()]
|
||||
fn groups(&self) -> Vec<&Group> {
|
||||
vec![&self.group]
|
||||
}
|
||||
|
||||
fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> {
|
||||
|
@ -2,12 +2,10 @@ mod util;
|
||||
|
||||
pub mod block_editor;
|
||||
pub mod block_manager;
|
||||
mod block_manager_trait_impl;
|
||||
pub mod cell;
|
||||
pub mod field;
|
||||
mod filter;
|
||||
pub mod filter;
|
||||
pub mod grid_editor;
|
||||
mod grid_editor_task;
|
||||
mod grid_editor_trait_impl;
|
||||
pub mod grid_view_editor;
|
||||
pub mod grid_view_manager;
|
||||
@ -15,5 +13,3 @@ pub mod group;
|
||||
pub mod persistence;
|
||||
pub mod row;
|
||||
pub mod setting;
|
||||
mod snapshot;
|
||||
pub mod tasks;
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::entities::{BlockPB, RepeatedBlockPB, RowPB};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use grid_rev_model::RowRevision;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GridBlockSnapshot {
|
||||
pub struct GridBlock {
|
||||
pub(crate) block_id: String,
|
||||
pub row_revs: Vec<Arc<RowRevision>>,
|
||||
}
|
||||
@ -35,7 +35,7 @@ pub(crate) fn block_from_row_orders(row_orders: Vec<RowPB>) -> Vec<BlockPB> {
|
||||
// Some((field_id, cell))
|
||||
// }
|
||||
|
||||
pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<RowPB> {
|
||||
pub(crate) fn make_row_pb_from_row_rev(row_revs: &[Arc<RowRevision>]) -> Vec<RowPB> {
|
||||
row_revs.iter().map(RowPB::from).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
@ -53,36 +53,13 @@ pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<RowP
|
||||
row_revs.iter().map(make_row).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub(crate) fn make_grid_blocks(
|
||||
block_ids: Option<Vec<String>>,
|
||||
block_snapshots: Vec<GridBlockSnapshot>,
|
||||
) -> FlowyResult<RepeatedBlockPB> {
|
||||
match block_ids {
|
||||
None => Ok(block_snapshots
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
let row_orders = make_row_orders_from_row_revs(&snapshot.row_revs);
|
||||
BlockPB::new(&snapshot.block_id, row_orders)
|
||||
})
|
||||
.collect::<Vec<BlockPB>>()
|
||||
.into()),
|
||||
Some(block_ids) => {
|
||||
let block_meta_data_map: HashMap<&String, &Vec<Arc<RowRevision>>> = block_snapshots
|
||||
.iter()
|
||||
.map(|data| (&data.block_id, &data.row_revs))
|
||||
.collect();
|
||||
|
||||
let mut grid_blocks = vec![];
|
||||
for block_id in block_ids {
|
||||
match block_meta_data_map.get(&block_id) {
|
||||
None => {}
|
||||
Some(row_revs) => {
|
||||
let row_orders = make_row_orders_from_row_revs(row_revs);
|
||||
grid_blocks.push(BlockPB::new(&block_id, row_orders));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(grid_blocks.into())
|
||||
}
|
||||
}
|
||||
pub(crate) fn make_block_pbs(blocks: Vec<GridBlock>) -> RepeatedBlockPB {
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|block| {
|
||||
let row_pbs = make_row_pb_from_row_rev(&block.row_revs);
|
||||
BlockPB::new(&block.block_id, row_pbs)
|
||||
})
|
||||
.collect::<Vec<BlockPB>>()
|
||||
.into()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{DeleteFilterParams, GridLayout, GridSettingChangesetParams, InsertFilterParams};
|
||||
use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams};
|
||||
|
||||
pub struct GridSettingChangesetBuilder {
|
||||
params: GridSettingChangesetParams,
|
||||
@ -17,7 +17,7 @@ impl GridSettingChangesetBuilder {
|
||||
Self { params }
|
||||
}
|
||||
|
||||
pub fn insert_filter(mut self, params: InsertFilterParams) -> Self {
|
||||
pub fn insert_filter(mut self, params: CreateFilterParams) -> Self {
|
||||
self.params.insert_filter = Some(params);
|
||||
self
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
mod snapshot_service;
|
||||
|
||||
pub use snapshot_service::*;
|
@ -1,7 +0,0 @@
|
||||
// pub struct GridSnapshotService {}
|
||||
//
|
||||
// impl GridSnapshotService {
|
||||
// pub fn new() -> Self {
|
||||
// Self {}
|
||||
// }
|
||||
// }
|
@ -1,47 +0,0 @@
|
||||
use crate::services::tasks::scheduler::GridTaskScheduler;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{watch, RwLock};
|
||||
use tokio::time::interval;
|
||||
|
||||
pub struct GridTaskRunner {
|
||||
scheduler: Arc<RwLock<GridTaskScheduler>>,
|
||||
debounce_duration: Duration,
|
||||
notifier: Option<watch::Receiver<bool>>,
|
||||
}
|
||||
|
||||
impl GridTaskRunner {
|
||||
pub fn new(
|
||||
scheduler: Arc<RwLock<GridTaskScheduler>>,
|
||||
notifier: watch::Receiver<bool>,
|
||||
debounce_duration: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
scheduler,
|
||||
debounce_duration,
|
||||
notifier: Some(notifier),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(mut self) {
|
||||
let mut notifier = self
|
||||
.notifier
|
||||
.take()
|
||||
.expect("The GridTaskRunner's notifier should only take once");
|
||||
|
||||
loop {
|
||||
if notifier.changed().await.is_err() {
|
||||
// The runner will be stopped if the corresponding Sender drop.
|
||||
break;
|
||||
}
|
||||
|
||||
if *notifier.borrow() {
|
||||
break;
|
||||
}
|
||||
let mut interval = interval(self.debounce_duration);
|
||||
interval.tick().await;
|
||||
let _ = self.scheduler.write().await.process_next_task().await;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
use crate::services::tasks::queue::GridTaskQueue;
|
||||
use crate::services::tasks::runner::GridTaskRunner;
|
||||
use crate::services::tasks::store::GridTaskStore;
|
||||
use crate::services::tasks::task::Task;
|
||||
|
||||
use crate::services::tasks::{TaskContent, TaskId, TaskStatus};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{watch, RwLock};
|
||||
|
||||
pub(crate) trait GridTaskHandler: Send + Sync + 'static {
|
||||
fn handler_id(&self) -> &str;
|
||||
|
||||
fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RefCountTaskHandler(Arc<dyn GridTaskHandler>);
|
||||
impl RefCountValue for RefCountTaskHandler {
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
pub struct GridTaskScheduler {
|
||||
queue: GridTaskQueue,
|
||||
store: GridTaskStore,
|
||||
notifier: watch::Sender<bool>,
|
||||
handlers: RefCountHashMap<RefCountTaskHandler>,
|
||||
}
|
||||
|
||||
impl GridTaskScheduler {
|
||||
pub(crate) fn new() -> Arc<RwLock<Self>> {
|
||||
let (notifier, rx) = watch::channel(false);
|
||||
|
||||
let scheduler = Self {
|
||||
queue: GridTaskQueue::new(),
|
||||
store: GridTaskStore::new(),
|
||||
notifier,
|
||||
handlers: RefCountHashMap::new(),
|
||||
};
|
||||
// The runner will receive the newest value after start running.
|
||||
scheduler.notify();
|
||||
|
||||
let scheduler = Arc::new(RwLock::new(scheduler));
|
||||
let debounce_duration = Duration::from_millis(300);
|
||||
let runner = GridTaskRunner::new(scheduler.clone(), rx, debounce_duration);
|
||||
tokio::spawn(runner.run());
|
||||
|
||||
scheduler
|
||||
}
|
||||
|
||||
pub(crate) fn register_handler<T>(&mut self, handler: Arc<T>)
|
||||
where
|
||||
T: GridTaskHandler,
|
||||
{
|
||||
let handler_id = handler.handler_id().to_owned();
|
||||
self.handlers.insert(handler_id, RefCountTaskHandler(handler));
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
|
||||
self.handlers.remove(handler_id.as_ref());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn stop(&mut self) {
|
||||
let _ = self.notifier.send(true);
|
||||
self.queue.clear();
|
||||
self.store.clear();
|
||||
}
|
||||
|
||||
pub(crate) async fn process_next_task(&mut self) -> Option<()> {
|
||||
let pending_task = self.queue.mut_head(|list| list.pop())?;
|
||||
let mut task = self.store.remove_task(&pending_task.id)?;
|
||||
let handler = self.handlers.get(&task.handler_id)?;
|
||||
|
||||
let ret = task.ret.take()?;
|
||||
let content = task.content.take()?;
|
||||
|
||||
task.set_status(TaskStatus::Processing);
|
||||
let _ = match handler.0.process_content(content).await {
|
||||
Ok(_) => {
|
||||
task.set_status(TaskStatus::Done);
|
||||
let _ = ret.send(task.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Process task failed: {:?}", e);
|
||||
task.set_status(TaskStatus::Failure);
|
||||
let _ = ret.send(task.into());
|
||||
}
|
||||
};
|
||||
self.notify();
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn add_task(&mut self, task: Task) {
|
||||
assert!(!task.is_finished());
|
||||
self.queue.push(&task);
|
||||
self.store.insert_task(task);
|
||||
self.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn next_task_id(&self) -> TaskId {
|
||||
self.store.next_task_id()
|
||||
}
|
||||
|
||||
pub(crate) fn notify(&self) {
|
||||
let _ = self.notifier.send(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::services::grid_editor_task::GridServiceTaskScheduler;
|
||||
use crate::services::tasks::{GridTaskHandler, GridTaskScheduler, Task, TaskContent, TaskStatus};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::ref_map::RefCountValue;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::interval;
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_scheduler_snapshot_task_test() {
|
||||
let scheduler = GridTaskScheduler::new();
|
||||
scheduler
|
||||
.write()
|
||||
.await
|
||||
.register_handler(Arc::new(MockGridTaskHandler()));
|
||||
|
||||
let task_id = scheduler.gen_task_id().await;
|
||||
let mut task = Task::new("1", task_id, TaskContent::Snapshot);
|
||||
let rx = task.rx.take().unwrap();
|
||||
scheduler.write().await.add_task(task);
|
||||
assert_eq!(rx.await.unwrap().status, TaskStatus::Done);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_scheduler_snapshot_task_cancel_test() {
|
||||
let scheduler = GridTaskScheduler::new();
|
||||
scheduler
|
||||
.write()
|
||||
.await
|
||||
.register_handler(Arc::new(MockGridTaskHandler()));
|
||||
|
||||
let task_id = scheduler.gen_task_id().await;
|
||||
let mut task = Task::new("1", task_id, TaskContent::Snapshot);
|
||||
let rx = task.rx.take().unwrap();
|
||||
scheduler.write().await.add_task(task);
|
||||
scheduler.write().await.stop();
|
||||
|
||||
assert_eq!(rx.await.unwrap().status, TaskStatus::Cancel);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_scheduler_multi_task_test() {
|
||||
let scheduler = GridTaskScheduler::new();
|
||||
scheduler
|
||||
.write()
|
||||
.await
|
||||
.register_handler(Arc::new(MockGridTaskHandler()));
|
||||
|
||||
let task_id = scheduler.gen_task_id().await;
|
||||
let mut task_1 = Task::new("1", task_id, TaskContent::Snapshot);
|
||||
let rx_1 = task_1.rx.take().unwrap();
|
||||
|
||||
let task_id = scheduler.gen_task_id().await;
|
||||
let mut task_2 = Task::new("1", task_id, TaskContent::Snapshot);
|
||||
let rx_2 = task_2.rx.take().unwrap();
|
||||
|
||||
scheduler.write().await.add_task(task_1);
|
||||
scheduler.write().await.add_task(task_2);
|
||||
|
||||
assert_eq!(rx_1.await.unwrap().status, TaskStatus::Done);
|
||||
assert_eq!(rx_2.await.unwrap().status, TaskStatus::Done);
|
||||
}
|
||||
struct MockGridTaskHandler();
|
||||
|
||||
impl RefCountValue for MockGridTaskHandler {
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
impl GridTaskHandler for MockGridTaskHandler {
|
||||
fn handler_id(&self) -> &str {
|
||||
"1"
|
||||
}
|
||||
|
||||
fn process_content(&self, _content: TaskContent) -> BoxResultFuture<(), FlowyError> {
|
||||
Box::pin(async move {
|
||||
let mut interval = interval(Duration::from_secs(1));
|
||||
interval.tick().await;
|
||||
interval.tick().await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
#![allow(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
use crate::services::row::GridBlockSnapshot;
|
||||
use crate::services::tasks::queue::TaskHandlerId;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Eq, Debug, Clone, Copy)]
|
||||
pub enum TaskType {
|
||||
/// Remove the row if it doesn't satisfy the filter.
|
||||
Filter,
|
||||
/// Generate snapshot for grid, unused by now.
|
||||
Snapshot,
|
||||
|
||||
Group,
|
||||
}
|
||||
|
||||
impl PartialEq for TaskType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
matches!(
|
||||
(self, other),
|
||||
(Self::Filter, Self::Filter) | (Self::Snapshot, Self::Snapshot)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TaskId = u32;
|
||||
|
||||
#[derive(Eq, Debug, Clone, Copy)]
|
||||
pub struct PendingTask {
|
||||
pub ty: TaskType,
|
||||
pub id: TaskId,
|
||||
}
|
||||
|
||||
impl PartialEq for PendingTask {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id.eq(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PendingTask {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PendingTask {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.ty, other.ty) {
|
||||
// Snapshot
|
||||
(TaskType::Snapshot, TaskType::Snapshot) => Ordering::Equal,
|
||||
(TaskType::Snapshot, _) => Ordering::Greater,
|
||||
(_, TaskType::Snapshot) => Ordering::Less,
|
||||
// Group
|
||||
(TaskType::Group, TaskType::Group) => self.id.cmp(&other.id).reverse(),
|
||||
(TaskType::Group, _) => Ordering::Greater,
|
||||
(_, TaskType::Group) => Ordering::Greater,
|
||||
// Filter
|
||||
(TaskType::Filter, TaskType::Filter) => self.id.cmp(&other.id).reverse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FilterTaskContext {
|
||||
pub blocks: Vec<GridBlockSnapshot>,
|
||||
}
|
||||
|
||||
pub(crate) enum TaskContent {
|
||||
#[allow(dead_code)]
|
||||
Snapshot,
|
||||
Group,
|
||||
Filter(FilterTaskContext),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) enum TaskStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Done,
|
||||
Failure,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
pub(crate) struct Task {
|
||||
pub id: TaskId,
|
||||
pub handler_id: TaskHandlerId,
|
||||
pub content: Option<TaskContent>,
|
||||
status: TaskStatus,
|
||||
pub ret: Option<tokio::sync::oneshot::Sender<TaskResult>>,
|
||||
pub rx: Option<tokio::sync::oneshot::Receiver<TaskResult>>,
|
||||
}
|
||||
|
||||
pub(crate) struct TaskResult {
|
||||
pub id: TaskId,
|
||||
pub(crate) status: TaskStatus,
|
||||
}
|
||||
|
||||
impl std::convert::From<Task> for TaskResult {
|
||||
fn from(task: Task) -> Self {
|
||||
TaskResult {
|
||||
id: task.id,
|
||||
status: task.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(handler_id: &str, id: TaskId, content: TaskContent) -> Self {
|
||||
let (ret, rx) = tokio::sync::oneshot::channel();
|
||||
Self {
|
||||
handler_id: handler_id.to_owned(),
|
||||
id,
|
||||
content: Some(content),
|
||||
ret: Some(ret),
|
||||
rx: Some(rx),
|
||||
status: TaskStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: TaskStatus) {
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
pub fn is_finished(&self) -> bool {
|
||||
match self.status {
|
||||
TaskStatus::Done => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::GridFilterTest;
|
||||
use flowy_grid::entities::CheckboxFilterCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_check_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateCheckboxFilter {
|
||||
condition: CheckboxFilterCondition::IsChecked,
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_uncheck_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateCheckboxFilter {
|
||||
condition: CheckboxFilterCondition::IsUnChecked,
|
||||
},
|
||||
AssertNumberOfRows { expected: 3 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::GridFilterTest;
|
||||
use flowy_grid::entities::DateFilterCondition;
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn grid_filter_date_is_check_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateDateFilter {
|
||||
condition: DateFilterCondition::DateIs,
|
||||
content: "1647251762".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -1,2 +1,5 @@
|
||||
mod checkbox_filter_test;
|
||||
mod date_filter_test;
|
||||
mod number_filter_test;
|
||||
mod script;
|
||||
mod text_filter_test;
|
||||
|
@ -0,0 +1,82 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::GridFilterTest;
|
||||
use flowy_grid::entities::NumberFilterCondition;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_equal_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::Equal,
|
||||
content: "1".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_less_than_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::LessThan,
|
||||
content: "3".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn grid_filter_number_is_less_than_test2() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::LessThan,
|
||||
content: "$3".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_less_than_or_equal_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::LessThanOrEqualTo,
|
||||
content: "3".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 3 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::NumberIsEmpty,
|
||||
content: "".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_number_is_not_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition::NumberIsNotEmpty,
|
||||
content: "".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 4 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -3,21 +3,46 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use flowy_grid::entities::{InsertFilterParams, InsertFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB};
|
||||
use futures::TryFutureExt;
|
||||
use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition};
|
||||
use flowy_grid::services::setting::GridSettingChangesetBuilder;
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision};
|
||||
use flowy_grid::services::filter::FilterType;
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
|
||||
pub enum FilterScript {
|
||||
InsertGridTableFilter {
|
||||
payload: InsertFilterPayloadPB,
|
||||
InsertFilter {
|
||||
payload: CreateFilterPayloadPB,
|
||||
},
|
||||
AssertTableFilterCount {
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition,
|
||||
content: String,
|
||||
},
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterCondition,
|
||||
content: String,
|
||||
},
|
||||
CreateCheckboxFilter {
|
||||
condition: CheckboxFilterCondition,
|
||||
},
|
||||
CreateDateFilter{
|
||||
condition: DateFilterCondition,
|
||||
content: String,
|
||||
},
|
||||
AssertFilterCount {
|
||||
count: i32,
|
||||
},
|
||||
DeleteGridTableFilter {
|
||||
DeleteFilter {
|
||||
filter_id: String,
|
||||
field_rev: FieldRevision,
|
||||
filter_type: FilterType,
|
||||
},
|
||||
AssertFilterContent {
|
||||
filter_type: FilterType,
|
||||
condition: u32,
|
||||
content: String
|
||||
},
|
||||
AssertNumberOfRows{
|
||||
expected: usize,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
AssertGridSetting {
|
||||
@ -45,25 +70,65 @@ impl GridFilterTest {
|
||||
|
||||
pub async fn run_script(&mut self, script: FilterScript) {
|
||||
match script {
|
||||
|
||||
FilterScript::InsertGridTableFilter { payload } => {
|
||||
let params: InsertFilterParams = payload.try_into().unwrap();
|
||||
let _ = self.editor.create_filter(params).await.unwrap();
|
||||
FilterScript::InsertFilter { payload } => {
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::AssertTableFilterCount { count } => {
|
||||
let filters = self.editor.get_grid_filter().await.unwrap();
|
||||
FilterScript::CreateTextFilter { condition, content} => {
|
||||
let field_rev = self.get_field_rev(FieldType::RichText);
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateNumberFilter {condition, content} => {
|
||||
let field_rev = self.get_field_rev(FieldType::Number);
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateCheckboxFilter {condition} => {
|
||||
let field_rev = self.get_field_rev(FieldType::Checkbox);
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, "".to_string());
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateDateFilter { condition, content} => {
|
||||
let field_rev = self.get_field_rev(FieldType::DateTime);
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::AssertFilterCount { count } => {
|
||||
let filters = self.editor.get_all_filters().await.unwrap();
|
||||
assert_eq!(count as usize, filters.len());
|
||||
}
|
||||
FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
|
||||
let params = DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty };
|
||||
FilterScript::AssertFilterContent { filter_type: filter_id, condition, content} => {
|
||||
let filter = self.editor.get_filters(filter_id).await.unwrap().pop().unwrap();
|
||||
assert_eq!(&filter.content, &content);
|
||||
assert_eq!(filter.condition as u32, condition);
|
||||
|
||||
}
|
||||
FilterScript::DeleteFilter { filter_id, filter_type } => {
|
||||
let params = DeleteFilterParams { filter_type, filter_id };
|
||||
let _ = self.editor.delete_filter(params).await.unwrap();
|
||||
}
|
||||
FilterScript::AssertGridSetting { expected_setting } => {
|
||||
let setting = self.editor.get_grid_setting().await.unwrap();
|
||||
let setting = self.editor.get_setting().await.unwrap();
|
||||
assert_eq!(expected_setting, setting);
|
||||
}
|
||||
FilterScript::AssertNumberOfRows { expected } => {
|
||||
//
|
||||
let grid = self.editor.get_grid().await.unwrap();
|
||||
let rows = grid.blocks.into_iter().map(|block| block.rows).flatten().collect::<Vec<RowPB>>();
|
||||
assert_eq!(rows.len(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_filter(&self, payload: CreateFilterPayloadPB) {
|
||||
|
||||
let params: CreateFilterParams = payload.try_into().unwrap();
|
||||
let _ = self.editor.create_filter(params).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,26 +1,71 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::*;
|
||||
use flowy_grid::entities::{FieldType, InsertFilterPayloadPB, TextFilterCondition};
|
||||
use grid_rev_model::FieldRevision;
|
||||
use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition};
|
||||
use flowy_grid::services::filter::FilterType;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_create_test() {
|
||||
async fn grid_filter_text_is_empty_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let field_rev = test.get_field_rev(FieldType::RichText);
|
||||
let payload = InsertFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
|
||||
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::TextIsEmpty,
|
||||
content: "".to_string(),
|
||||
},
|
||||
AssertFilterCount { count: 1 },
|
||||
AssertNumberOfRows { expected: 0 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn grid_filter_invalid_condition_panic_test() {
|
||||
async fn grid_filter_is_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let field_rev = test.get_field_rev(FieldType::RichText).clone();
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::Is,
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
// 100 is not a valid condition, so this test should be panic.
|
||||
let payload = InsertFilterPayloadPB::new(&field_rev, 100, Some("".to_owned()));
|
||||
let scripts = vec![InsertGridTableFilter { payload }];
|
||||
#[tokio::test]
|
||||
async fn grid_filter_contain_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::Contains,
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 3 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_start_with_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::StartsWith,
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_ends_with_text_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateTextFilter {
|
||||
condition: TextFilterCondition::EndsWith,
|
||||
content: "A".to_string(),
|
||||
},
|
||||
AssertNumberOfRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
@ -28,24 +73,22 @@ async fn grid_filter_invalid_condition_panic_test() {
|
||||
async fn grid_filter_delete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let field_rev = test.get_field_rev(FieldType::RichText).clone();
|
||||
let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc");
|
||||
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
|
||||
let payload = CreateFilterPayloadPB::new(&field_rev, TextFilterCondition::TextIsEmpty, "".to_string());
|
||||
let scripts = vec![
|
||||
InsertFilter { payload },
|
||||
AssertFilterCount { count: 1 },
|
||||
AssertNumberOfRows { expected: 0 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let filter = test.grid_filters().await.pop().unwrap();
|
||||
test.run_scripts(vec![
|
||||
DeleteGridTableFilter {
|
||||
DeleteFilter {
|
||||
filter_id: filter.id,
|
||||
field_rev: field_rev.as_ref().clone(),
|
||||
filter_type: FilterType::from(&field_rev),
|
||||
},
|
||||
AssertTableFilterCount { count: 0 },
|
||||
AssertFilterCount { count: 0 },
|
||||
AssertNumberOfRows { expected: 5 },
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_get_rows_test() {}
|
||||
|
||||
fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> InsertFilterPayloadPB {
|
||||
InsertFilterPayloadPB::new(field_rev, condition, Some(s.to_owned()))
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#![allow(unused_imports)]
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use bytes::Bytes;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_grid::entities::*;
|
||||
use flowy_grid::services::field::SelectOptionPB;
|
||||
use flowy_grid::services::field::*;
|
||||
@ -55,7 +56,7 @@ impl GridEditorTest {
|
||||
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
|
||||
let field_revs = editor.get_field_revs(None).await.unwrap();
|
||||
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
|
||||
let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs;
|
||||
let row_revs = editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs;
|
||||
assert_eq!(block_meta_revs.len(), 1);
|
||||
|
||||
// It seems like you should add the field in the make_test_grid() function.
|
||||
@ -76,17 +77,11 @@ impl GridEditorTest {
|
||||
}
|
||||
|
||||
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
|
||||
self.editor
|
||||
.grid_block_snapshots(None)
|
||||
.await
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
.row_revs
|
||||
self.editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs
|
||||
}
|
||||
|
||||
pub async fn grid_filters(&self) -> Vec<GridFilterConfigurationPB> {
|
||||
self.editor.get_grid_filter().await.unwrap()
|
||||
pub async fn grid_filters(&self) -> Vec<FilterPB> {
|
||||
self.editor.get_all_filters().await.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
|
||||
@ -239,7 +234,7 @@ fn make_test_grid() -> BuildGridContext {
|
||||
3 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("D"),
|
||||
FieldType::RichText => row_builder.insert_text_cell("DA"),
|
||||
FieldType::Number => row_builder.insert_number_cell("4"),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
@ -253,8 +248,8 @@ fn make_test_grid() -> BuildGridContext {
|
||||
4 => {
|
||||
for field_type in FieldType::iter() {
|
||||
match field_type {
|
||||
FieldType::RichText => row_builder.insert_text_cell("E"),
|
||||
FieldType::Number => row_builder.insert_number_cell("5"),
|
||||
FieldType::RichText => row_builder.insert_text_cell("AE"),
|
||||
FieldType::Number => row_builder.insert_number_cell(""),
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(2))
|
||||
|
@ -16,6 +16,7 @@ grid-rev-model = { path = "../../../shared-lib/grid-rev-model" }
|
||||
flowy-database = { path = "../flowy-database" }
|
||||
flowy-document = { path = "../flowy-document", default-features = false }
|
||||
flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-task = { path = "../flowy-task" }
|
||||
|
||||
tracing = { version = "0.1" }
|
||||
futures-core = { version = "0.3", default-features = false }
|
||||
|
@ -6,22 +6,29 @@ use flowy_grid::services::persistence::GridDatabase;
|
||||
use flowy_http_model::ws_data::ClientRevisionWSData;
|
||||
use flowy_net::ws::connection::FlowyWebSocketConnect;
|
||||
use flowy_revision::{RevisionWebSocket, WSStateReceiver};
|
||||
use flowy_task::TaskDispatcher;
|
||||
use flowy_user::services::UserSession;
|
||||
use futures_core::future::BoxFuture;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_ws::{WSChannel, WebSocketRawMessage};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct GridDepsResolver();
|
||||
|
||||
impl GridDepsResolver {
|
||||
pub async fn resolve(ws_conn: Arc<FlowyWebSocketConnect>, user_session: Arc<UserSession>) -> Arc<GridManager> {
|
||||
pub async fn resolve(
|
||||
ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
user_session: Arc<UserSession>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
) -> Arc<GridManager> {
|
||||
let user = Arc::new(GridUserImpl(user_session.clone()));
|
||||
let rev_web_socket = Arc::new(GridRevisionWebSocket(ws_conn));
|
||||
let grid_manager = Arc::new(GridManager::new(
|
||||
user.clone(),
|
||||
rev_web_socket,
|
||||
task_scheduler,
|
||||
Arc::new(GridDatabaseImpl(user_session)),
|
||||
));
|
||||
|
||||
|
@ -15,11 +15,13 @@ use flowy_net::{
|
||||
local_server::LocalServer,
|
||||
ws::connection::{listen_on_websocket, FlowyWebSocketConnect},
|
||||
};
|
||||
use flowy_task::{TaskDispatcher, TaskRunner};
|
||||
use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig};
|
||||
use lib_dispatch::prelude::*;
|
||||
use lib_dispatch::runtime::tokio_default_runtime;
|
||||
use module::mk_modules;
|
||||
pub use module::*;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
fmt,
|
||||
sync::{
|
||||
@ -27,7 +29,7 @@ use std::{
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
|
||||
static INIT_LOG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
@ -103,9 +105,10 @@ pub struct FlowySDK {
|
||||
pub document_manager: Arc<DocumentManager>,
|
||||
pub folder_manager: Arc<FolderManager>,
|
||||
pub grid_manager: Arc<GridManager>,
|
||||
pub dispatcher: Arc<EventDispatcher>,
|
||||
pub event_dispatcher: Arc<EventDispatcher>,
|
||||
pub ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
pub local_server: Option<Arc<LocalServer>>,
|
||||
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
|
||||
impl FlowySDK {
|
||||
@ -114,6 +117,10 @@ impl FlowySDK {
|
||||
init_kv(&config.root);
|
||||
tracing::debug!("🔥 {:?}", config);
|
||||
let runtime = tokio_default_runtime().unwrap();
|
||||
let task_scheduler = TaskDispatcher::new(Duration::from_secs(2));
|
||||
let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
|
||||
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
||||
|
||||
let (local_server, ws_conn) = mk_local_server(&config.server_config);
|
||||
let (user_session, document_manager, folder_manager, local_server, grid_manager) = runtime.block_on(async {
|
||||
let user_session = mk_user_session(&config, &local_server, &config.server_config);
|
||||
@ -125,7 +132,8 @@ impl FlowySDK {
|
||||
&config.document,
|
||||
);
|
||||
|
||||
let grid_manager = GridDepsResolver::resolve(ws_conn.clone(), user_session.clone()).await;
|
||||
let grid_manager =
|
||||
GridDepsResolver::resolve(ws_conn.clone(), user_session.clone(), task_dispatcher.clone()).await;
|
||||
|
||||
let folder_manager = FolderDepsResolver::resolve(
|
||||
local_server.clone(),
|
||||
@ -150,7 +158,7 @@ impl FlowySDK {
|
||||
)
|
||||
});
|
||||
|
||||
let dispatcher = Arc::new(EventDispatcher::construct(runtime, || {
|
||||
let event_dispatcher = Arc::new(EventDispatcher::construct(runtime, || {
|
||||
mk_modules(
|
||||
&ws_conn,
|
||||
&folder_manager,
|
||||
@ -162,7 +170,7 @@ impl FlowySDK {
|
||||
|
||||
_start_listening(
|
||||
&config,
|
||||
&dispatcher,
|
||||
&event_dispatcher,
|
||||
&ws_conn,
|
||||
&user_session,
|
||||
&document_manager,
|
||||
@ -176,20 +184,21 @@ impl FlowySDK {
|
||||
document_manager,
|
||||
folder_manager,
|
||||
grid_manager,
|
||||
dispatcher,
|
||||
event_dispatcher,
|
||||
ws_conn,
|
||||
local_server,
|
||||
task_dispatcher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatcher(&self) -> Arc<EventDispatcher> {
|
||||
self.dispatcher.clone()
|
||||
self.event_dispatcher.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn _start_listening(
|
||||
config: &FlowySDKConfig,
|
||||
dispatch: &EventDispatcher,
|
||||
event_dispatch: &EventDispatcher,
|
||||
ws_conn: &Arc<FlowyWebSocketConnect>,
|
||||
user_session: &Arc<UserSession>,
|
||||
document_manager: &Arc<DocumentManager>,
|
||||
@ -206,7 +215,7 @@ fn _start_listening(
|
||||
let document_manager = document_manager.clone();
|
||||
let config = config.clone();
|
||||
|
||||
dispatch.spawn(async move {
|
||||
event_dispatch.spawn(async move {
|
||||
user_session.init();
|
||||
listen_on_websocket(ws_conn.clone());
|
||||
_listen_user_status(
|
||||
@ -220,7 +229,7 @@ fn _start_listening(
|
||||
.await;
|
||||
});
|
||||
|
||||
dispatch.spawn(async move {
|
||||
event_dispatch.spawn(async move {
|
||||
_listen_network_status(subscribe_network_type, cloned_folder_manager).await;
|
||||
});
|
||||
}
|
||||
|
@ -11,20 +11,14 @@ pub fn mk_modules(
|
||||
folder_manager: &Arc<FolderManager>,
|
||||
grid_manager: &Arc<GridManager>,
|
||||
user_session: &Arc<UserSession>,
|
||||
text_block_manager: &Arc<DocumentManager>,
|
||||
document_manager: &Arc<DocumentManager>,
|
||||
) -> Vec<Module> {
|
||||
let user_module = mk_user_module(user_session.clone());
|
||||
let folder_module = mk_folder_module(folder_manager.clone());
|
||||
let network_module = mk_network_module(ws_conn.clone());
|
||||
let grid_module = mk_grid_module(grid_manager.clone());
|
||||
let text_block_module = mk_text_block_module(text_block_manager.clone());
|
||||
vec![
|
||||
user_module,
|
||||
folder_module,
|
||||
network_module,
|
||||
grid_module,
|
||||
text_block_module,
|
||||
]
|
||||
let document_module = mk_text_block_module(document_manager.clone());
|
||||
vec![user_module, folder_module, network_module, grid_module, document_module]
|
||||
}
|
||||
|
||||
fn mk_user_module(user_session: Arc<UserSession>) -> Module {
|
||||
|
17
frontend/rust-lib/flowy-task/Cargo.toml
Normal file
17
frontend/rust-lib/flowy-task/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "flowy-task"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
tokio = {version = "1", features = ["sync", "macros", ]}
|
||||
atomic_refcell = "0.1.8"
|
||||
anyhow = "1.0"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
futures = "0.3.15"
|
@ -1,5 +1,4 @@
|
||||
mod queue;
|
||||
mod runner;
|
||||
mod scheduler;
|
||||
mod store;
|
||||
mod task;
|
@ -1,6 +1,5 @@
|
||||
use crate::services::tasks::task::{PendingTask, Task, TaskContent, TaskType};
|
||||
use crate::{PendingTask, Task};
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{BinaryHeap, HashMap};
|
||||
@ -8,30 +7,25 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct GridTaskQueue {
|
||||
pub(crate) struct TaskQueue {
|
||||
// index_tasks for quick access
|
||||
index_tasks: HashMap<TaskHandlerId, Arc<AtomicRefCell<TaskList>>>,
|
||||
queue: BinaryHeap<Arc<AtomicRefCell<TaskList>>>,
|
||||
}
|
||||
|
||||
impl GridTaskQueue {
|
||||
impl TaskQueue {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, task: &Task) {
|
||||
if task.content.is_none() {
|
||||
tracing::warn!("Ignore task: {} with empty content", task.id);
|
||||
tracing::warn!("The task:{} with empty content will be not executed", task.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let task_type = match task.content.as_ref().unwrap() {
|
||||
TaskContent::Snapshot => TaskType::Snapshot,
|
||||
TaskContent::Group => TaskType::Group,
|
||||
TaskContent::Filter { .. } => TaskType::Filter,
|
||||
};
|
||||
let pending_task = PendingTask {
|
||||
ty: task_type,
|
||||
qos: task.qos,
|
||||
id: task.id,
|
||||
};
|
||||
match self.index_tasks.entry(task.handler_id.clone()) {
|
187
frontend/rust-lib/flowy-task/src/scheduler.rs
Normal file
187
frontend/rust-lib/flowy-task/src/scheduler.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use crate::queue::TaskQueue;
|
||||
use crate::store::TaskStore;
|
||||
use crate::{Task, TaskContent, TaskId, TaskState};
|
||||
use anyhow::Error;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::sync::{watch, RwLock};
|
||||
use tokio::time::interval;
|
||||
|
||||
pub struct TaskDispatcher {
|
||||
queue: TaskQueue,
|
||||
store: TaskStore,
|
||||
timeout: Duration,
|
||||
handlers: RefCountHashMap<RefCountTaskHandler>,
|
||||
|
||||
notifier: watch::Sender<bool>,
|
||||
pub(crate) notifier_rx: Option<watch::Receiver<bool>>,
|
||||
}
|
||||
|
||||
impl TaskDispatcher {
|
||||
pub fn new(timeout: Duration) -> Self {
|
||||
let (notifier, notifier_rx) = watch::channel(false);
|
||||
Self {
|
||||
queue: TaskQueue::new(),
|
||||
store: TaskStore::new(),
|
||||
timeout,
|
||||
handlers: RefCountHashMap::new(),
|
||||
notifier,
|
||||
notifier_rx: Some(notifier_rx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_handler<T>(&mut self, handler: T)
|
||||
where
|
||||
T: TaskHandler,
|
||||
{
|
||||
let handler_id = handler.handler_id().to_owned();
|
||||
self.handlers.insert(handler_id, RefCountTaskHandler(Arc::new(handler)));
|
||||
}
|
||||
|
||||
pub fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
|
||||
self.handlers.remove(handler_id.as_ref());
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
let _ = self.notifier.send(true);
|
||||
self.queue.clear();
|
||||
self.store.clear();
|
||||
}
|
||||
|
||||
pub(crate) async fn process_next_task(&mut self) -> Option<()> {
|
||||
let pending_task = self.queue.mut_head(|list| list.pop())?;
|
||||
let mut task = self.store.remove_task(&pending_task.id)?;
|
||||
let ret = task.ret.take()?;
|
||||
|
||||
// Do not execute the task if the task was cancelled.
|
||||
if task.state().is_cancel() {
|
||||
let _ = ret.send(task.into());
|
||||
self.notify();
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = task.content.take()?;
|
||||
if let Some(handler) = self.handlers.get(&task.handler_id) {
|
||||
task.set_state(TaskState::Processing);
|
||||
match tokio::time::timeout(self.timeout, handler.run(content)).await {
|
||||
Ok(result) => match result {
|
||||
Ok(_) => task.set_state(TaskState::Done),
|
||||
Err(e) => {
|
||||
tracing::error!("Process task failed: {:?}", e);
|
||||
task.set_state(TaskState::Failure);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Process task timeout: {:?}", e);
|
||||
task.set_state(TaskState::Timeout);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
task.set_state(TaskState::Cancel);
|
||||
}
|
||||
let _ = ret.send(task.into());
|
||||
self.notify();
|
||||
None
|
||||
}
|
||||
|
||||
pub fn add_task(&mut self, task: Task) {
|
||||
debug_assert!(!task.state().is_done());
|
||||
if task.state().is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.queue.push(&task);
|
||||
self.store.insert_task(task);
|
||||
self.notify();
|
||||
}
|
||||
|
||||
pub fn read_task(&self, task_id: &TaskId) -> Option<&Task> {
|
||||
self.store.read_task(task_id)
|
||||
}
|
||||
|
||||
pub fn cancel_task(&mut self, task_id: TaskId) {
|
||||
if let Some(task) = self.store.mut_task(&task_id) {
|
||||
task.set_state(TaskState::Cancel);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_task_id(&self) -> TaskId {
|
||||
self.store.next_task_id()
|
||||
}
|
||||
|
||||
pub(crate) fn notify(&self) {
|
||||
let _ = self.notifier.send(false);
|
||||
}
|
||||
}
|
||||
pub struct TaskRunner();
|
||||
impl TaskRunner {
|
||||
pub async fn run(dispatcher: Arc<RwLock<TaskDispatcher>>) {
|
||||
dispatcher.read().await.notify();
|
||||
let debounce_duration = Duration::from_millis(300);
|
||||
let mut notifier = dispatcher.write().await.notifier_rx.take().expect("Only take once");
|
||||
loop {
|
||||
// stops the runner if the notifier was closed.
|
||||
if notifier.changed().await.is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
// stops the runner if the value of notifier is `true`
|
||||
if *notifier.borrow() {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut interval = interval(debounce_duration);
|
||||
interval.tick().await;
|
||||
let _ = dispatcher.write().await.process_next_task().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TaskHandler: Send + Sync + 'static {
|
||||
fn handler_id(&self) -> &str;
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>;
|
||||
}
|
||||
|
||||
impl<T> TaskHandler for Box<T>
|
||||
where
|
||||
T: TaskHandler,
|
||||
{
|
||||
fn handler_id(&self) -> &str {
|
||||
(**self).handler_id()
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
|
||||
(**self).run(content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TaskHandler for Arc<T>
|
||||
where
|
||||
T: TaskHandler,
|
||||
{
|
||||
fn handler_id(&self) -> &str {
|
||||
(**self).handler_id()
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
|
||||
(**self).run(content)
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct RefCountTaskHandler(Arc<dyn TaskHandler>);
|
||||
|
||||
impl RefCountValue for RefCountTaskHandler {
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RefCountTaskHandler {
|
||||
type Target = Arc<dyn TaskHandler>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
use crate::services::tasks::task::Task;
|
||||
use crate::services::tasks::{TaskId, TaskStatus};
|
||||
use crate::{Task, TaskId, TaskState};
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
||||
pub(crate) struct GridTaskStore {
|
||||
pub(crate) struct TaskStore {
|
||||
tasks: HashMap<TaskId, Task>,
|
||||
task_id_counter: AtomicU32,
|
||||
}
|
||||
|
||||
impl GridTaskStore {
|
||||
impl TaskStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tasks: HashMap::new(),
|
||||
@ -26,13 +25,20 @@ impl GridTaskStore {
|
||||
self.tasks.remove(task_id)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn mut_task(&mut self, task_id: &TaskId) -> Option<&mut Task> {
|
||||
self.tasks.get_mut(task_id)
|
||||
}
|
||||
|
||||
pub(crate) fn read_task(&self, task_id: &TaskId) -> Option<&Task> {
|
||||
self.tasks.get(task_id)
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
let tasks = mem::take(&mut self.tasks);
|
||||
tasks.into_values().for_each(|mut task| {
|
||||
if task.ret.is_some() {
|
||||
let ret = task.ret.take().unwrap();
|
||||
task.set_status(TaskStatus::Cancel);
|
||||
task.set_state(TaskState::Cancel);
|
||||
let _ = ret.send(task.into());
|
||||
}
|
||||
});
|
142
frontend/rust-lib/flowy-task/src/task.rs
Normal file
142
frontend/rust-lib/flowy-task/src/task.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use crate::TaskHandlerId;
|
||||
use std::cmp::Ordering;
|
||||
use tokio::sync::oneshot::{Receiver, Sender};
|
||||
|
||||
#[derive(Eq, Debug, Clone, Copy)]
|
||||
pub enum QualityOfService {
|
||||
Background,
|
||||
UserInteractive,
|
||||
}
|
||||
|
||||
impl PartialEq for QualityOfService {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
matches!(
|
||||
(self, other),
|
||||
(Self::Background, Self::Background) | (Self::UserInteractive, Self::UserInteractive)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TaskId = u32;
|
||||
|
||||
#[derive(Eq, Debug, Clone, Copy)]
|
||||
pub struct PendingTask {
|
||||
pub qos: QualityOfService,
|
||||
pub id: TaskId,
|
||||
}
|
||||
|
||||
impl PartialEq for PendingTask {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id.eq(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PendingTask {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PendingTask {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.qos, other.qos) {
|
||||
// User interactive
|
||||
(QualityOfService::UserInteractive, QualityOfService::UserInteractive) => self.id.cmp(&other.id),
|
||||
(QualityOfService::UserInteractive, _) => Ordering::Greater,
|
||||
(_, QualityOfService::UserInteractive) => Ordering::Less,
|
||||
// background
|
||||
(QualityOfService::Background, QualityOfService::Background) => self.id.cmp(&other.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TaskContent {
|
||||
Text(String),
|
||||
Blob(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum TaskState {
|
||||
Pending,
|
||||
Processing,
|
||||
Done,
|
||||
Failure,
|
||||
Cancel,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl TaskState {
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, TaskState::Pending)
|
||||
}
|
||||
pub fn is_done(&self) -> bool {
|
||||
matches!(self, TaskState::Done)
|
||||
}
|
||||
pub fn is_cancel(&self) -> bool {
|
||||
matches!(self, TaskState::Cancel)
|
||||
}
|
||||
|
||||
pub fn is_processing(&self) -> bool {
|
||||
matches!(self, TaskState::Processing)
|
||||
}
|
||||
|
||||
pub fn is_failed(&self) -> bool {
|
||||
matches!(self, TaskState::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Task {
|
||||
pub id: TaskId,
|
||||
pub handler_id: TaskHandlerId,
|
||||
pub content: Option<TaskContent>,
|
||||
pub qos: QualityOfService,
|
||||
state: TaskState,
|
||||
pub ret: Option<Sender<TaskResult>>,
|
||||
pub recv: Option<Receiver<TaskResult>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn background(handler_id: &str, id: TaskId, content: TaskContent) -> Self {
|
||||
Self::new(handler_id, id, content, QualityOfService::Background)
|
||||
}
|
||||
|
||||
pub fn user_interactive(handler_id: &str, id: TaskId, content: TaskContent) -> Self {
|
||||
Self::new(handler_id, id, content, QualityOfService::UserInteractive)
|
||||
}
|
||||
|
||||
pub fn new(handler_id: &str, id: TaskId, content: TaskContent, qos: QualityOfService) -> Self {
|
||||
let handler_id = handler_id.to_owned();
|
||||
let (ret, recv) = tokio::sync::oneshot::channel();
|
||||
Self {
|
||||
handler_id,
|
||||
id,
|
||||
content: Some(content),
|
||||
qos,
|
||||
ret: Some(ret),
|
||||
recv: Some(recv),
|
||||
state: TaskState::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> &TaskState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub(crate) fn set_state(&mut self, status: TaskState) {
|
||||
self.state = status;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskResult {
|
||||
pub id: TaskId,
|
||||
pub state: TaskState,
|
||||
}
|
||||
|
||||
impl std::convert::From<Task> for TaskResult {
|
||||
fn from(task: Task) -> Self {
|
||||
TaskResult {
|
||||
id: task.id,
|
||||
state: task.state().clone(),
|
||||
}
|
||||
}
|
||||
}
|
1
frontend/rust-lib/flowy-task/tests/main.rs
Normal file
1
frontend/rust-lib/flowy-task/tests/main.rs
Normal file
@ -0,0 +1 @@
|
||||
mod task_test;
|
3
frontend/rust-lib/flowy-task/tests/task_test/mod.rs
Normal file
3
frontend/rust-lib/flowy-task/tests/task_test/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod script;
|
||||
mod task_cancel_test;
|
||||
mod task_order_test;
|
196
frontend/rust-lib/flowy-task/tests/task_test/script.rs
Normal file
196
frontend/rust-lib/flowy-task/tests/task_test/script.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use anyhow::Error;
|
||||
use flowy_task::{Task, TaskContent, TaskDispatcher, TaskHandler, TaskId, TaskResult, TaskRunner, TaskState};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_infra::ref_map::RefCountValue;
|
||||
use rand::Rng;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::oneshot::Receiver;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub enum SearchScript {
|
||||
AddTask {
|
||||
task: Task,
|
||||
},
|
||||
AddTasks {
|
||||
tasks: Vec<Task>,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
Wait {
|
||||
millisecond: u64,
|
||||
},
|
||||
CancelTask {
|
||||
task_id: TaskId,
|
||||
},
|
||||
UnregisterHandler {
|
||||
handler_id: String,
|
||||
},
|
||||
AssertTaskStatus {
|
||||
task_id: TaskId,
|
||||
expected_status: TaskState,
|
||||
},
|
||||
AssertExecuteOrder {
|
||||
execute_order: Vec<u32>,
|
||||
rets: Vec<Receiver<TaskResult>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct SearchTest {
|
||||
scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
|
||||
impl SearchTest {
|
||||
pub async fn new() -> Self {
|
||||
let duration = Duration::from_millis(1000);
|
||||
let mut scheduler = TaskDispatcher::new(duration);
|
||||
scheduler.register_handler(Arc::new(MockTextTaskHandler()));
|
||||
scheduler.register_handler(Arc::new(MockBlobTaskHandler()));
|
||||
scheduler.register_handler(Arc::new(MockTimeoutTaskHandler()));
|
||||
|
||||
let scheduler = Arc::new(RwLock::new(scheduler));
|
||||
tokio::spawn(TaskRunner::run(scheduler.clone()));
|
||||
|
||||
Self { scheduler }
|
||||
}
|
||||
|
||||
pub async fn next_task_id(&self) -> TaskId {
|
||||
self.scheduler.read().await.next_task_id()
|
||||
}
|
||||
|
||||
pub async fn run_scripts(&self, scripts: Vec<SearchScript>) {
|
||||
for script in scripts {
|
||||
self.run_script(script).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_script(&self, script: SearchScript) {
|
||||
match script {
|
||||
SearchScript::AddTask { task } => {
|
||||
self.scheduler.write().await.add_task(task);
|
||||
}
|
||||
SearchScript::CancelTask { task_id } => {
|
||||
self.scheduler.write().await.cancel_task(task_id);
|
||||
}
|
||||
SearchScript::AddTasks { tasks } => {
|
||||
let mut scheduler = self.scheduler.write().await;
|
||||
for task in tasks {
|
||||
scheduler.add_task(task);
|
||||
}
|
||||
}
|
||||
SearchScript::Wait { millisecond } => {
|
||||
tokio::time::sleep(Duration::from_millis(millisecond)).await;
|
||||
}
|
||||
SearchScript::UnregisterHandler { handler_id } => {
|
||||
self.scheduler.write().await.unregister_handler(handler_id);
|
||||
}
|
||||
SearchScript::AssertTaskStatus {
|
||||
task_id,
|
||||
expected_status,
|
||||
} => {
|
||||
let status = self.scheduler.read().await.read_task(&task_id).unwrap().state().clone();
|
||||
assert_eq!(status, expected_status);
|
||||
}
|
||||
SearchScript::AssertExecuteOrder { execute_order, rets } => {
|
||||
let mut futures = FuturesUnordered::new();
|
||||
for ret in rets {
|
||||
futures.push(ret);
|
||||
}
|
||||
let mut orders = vec![];
|
||||
while let Some(Ok(result)) = futures.next().await {
|
||||
orders.push(result.id);
|
||||
assert!(result.state.is_done());
|
||||
}
|
||||
assert_eq!(execute_order, orders);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockTextTaskHandler();
|
||||
impl RefCountValue for MockTextTaskHandler {
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
impl TaskHandler for MockTextTaskHandler {
|
||||
fn handler_id(&self) -> &str {
|
||||
"1"
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let millisecond = rng.gen_range(1..50);
|
||||
Box::pin(async move {
|
||||
match content {
|
||||
TaskContent::Text(_s) => {
|
||||
tokio::time::sleep(Duration::from_millis(millisecond)).await;
|
||||
}
|
||||
TaskContent::Blob(_) => panic!("Only support text"),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_text_background_task(task_id: TaskId, s: &str) -> (Task, Receiver<TaskResult>) {
|
||||
let mut task = Task::background("1", task_id, TaskContent::Text(s.to_owned()));
|
||||
let recv = task.recv.take().unwrap();
|
||||
(task, recv)
|
||||
}
|
||||
|
||||
pub fn make_text_user_interactive_task(task_id: TaskId, s: &str) -> (Task, Receiver<TaskResult>) {
|
||||
let mut task = Task::user_interactive("1", task_id, TaskContent::Text(s.to_owned()));
|
||||
let recv = task.recv.take().unwrap();
|
||||
(task, recv)
|
||||
}
|
||||
|
||||
pub struct MockBlobTaskHandler();
|
||||
impl RefCountValue for MockBlobTaskHandler {
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
impl TaskHandler for MockBlobTaskHandler {
|
||||
fn handler_id(&self) -> &str {
|
||||
"2"
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
|
||||
Box::pin(async move {
|
||||
match content {
|
||||
TaskContent::Text(_) => panic!("Only support blob"),
|
||||
TaskContent::Blob(bytes) => {
|
||||
let _msg = String::from_utf8(bytes).unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockTimeoutTaskHandler();
|
||||
|
||||
impl TaskHandler for MockTimeoutTaskHandler {
|
||||
fn handler_id(&self) -> &str {
|
||||
"3"
|
||||
}
|
||||
|
||||
fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> {
|
||||
Box::pin(async move {
|
||||
match content {
|
||||
TaskContent::Text(_) => panic!("Only support blob"),
|
||||
TaskContent::Blob(_bytes) => {
|
||||
tokio::time::sleep(Duration::from_millis(2000)).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_timeout_task(task_id: TaskId) -> (Task, Receiver<TaskResult>) {
|
||||
let mut task = Task::background("3", task_id, TaskContent::Blob(vec![]));
|
||||
let recv = task.recv.take().unwrap();
|
||||
(task, recv)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
use crate::task_test::script::SearchScript::*;
|
||||
use crate::task_test::script::{make_text_background_task, make_timeout_task, SearchTest};
|
||||
use flowy_task::{QualityOfService, Task, TaskContent, TaskState};
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_cancel_background_task_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "Hello world");
|
||||
let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, "");
|
||||
test.run_scripts(vec![
|
||||
AddTask { task: task_1 },
|
||||
AddTask { task: task_2 },
|
||||
AssertTaskStatus {
|
||||
task_id: 1,
|
||||
expected_status: TaskState::Pending,
|
||||
},
|
||||
AssertTaskStatus {
|
||||
task_id: 2,
|
||||
expected_status: TaskState::Pending,
|
||||
},
|
||||
CancelTask { task_id: 2 },
|
||||
AssertTaskStatus {
|
||||
task_id: 2,
|
||||
expected_status: TaskState::Cancel,
|
||||
},
|
||||
])
|
||||
.await;
|
||||
|
||||
let result = ret_1.await.unwrap();
|
||||
assert_eq!(result.state, TaskState::Done);
|
||||
|
||||
let result = ret_2.await.unwrap();
|
||||
assert_eq!(result.state, TaskState::Cancel);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_with_empty_handler_id_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let mut task = Task::new(
|
||||
"",
|
||||
test.next_task_id().await,
|
||||
TaskContent::Text("".to_owned()),
|
||||
QualityOfService::Background,
|
||||
);
|
||||
let ret = task.recv.take().unwrap();
|
||||
test.run_scripts(vec![AddTask { task }]).await;
|
||||
|
||||
let result = ret.await.unwrap();
|
||||
assert_eq!(result.state, TaskState::Cancel);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_can_not_find_handler_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task, ret) = make_text_background_task(test.next_task_id().await, "Hello world");
|
||||
let handler_id = task.handler_id.clone();
|
||||
test.run_scripts(vec![UnregisterHandler { handler_id }, AddTask { task }])
|
||||
.await;
|
||||
|
||||
let result = ret.await.unwrap();
|
||||
assert_eq!(result.state, TaskState::Cancel);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_can_not_find_handler_test2() {
|
||||
let test = SearchTest::new().await;
|
||||
let mut tasks = vec![];
|
||||
let mut rets = vec![];
|
||||
let handler_id = "1".to_owned();
|
||||
for _i in 1..10000 {
|
||||
let (task, ret) = make_text_background_task(test.next_task_id().await, "");
|
||||
tasks.push(task);
|
||||
rets.push(ret);
|
||||
}
|
||||
|
||||
test.run_scripts(vec![UnregisterHandler { handler_id }, AddTasks { tasks }])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_run_timeout_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task, ret) = make_timeout_task(test.next_task_id().await);
|
||||
test.run_scripts(vec![AddTask { task }]).await;
|
||||
|
||||
let result = ret.await.unwrap();
|
||||
assert_eq!(result.state, TaskState::Timeout);
|
||||
}
|
111
frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs
Normal file
111
frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::task_test::script::{
|
||||
make_text_background_task, make_text_user_interactive_task, SearchScript::*, SearchTest,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_add_single_background_task_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task, ret) = make_text_background_task(test.next_task_id().await, "");
|
||||
test.run_scripts(vec![AddTask { task }]).await;
|
||||
|
||||
let result = ret.await.unwrap();
|
||||
assert!(result.state.is_done())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_add_multiple_background_tasks_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "");
|
||||
let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, "");
|
||||
let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, "");
|
||||
test.run_scripts(vec![
|
||||
AddTask { task: task_1 },
|
||||
AddTask { task: task_2 },
|
||||
AddTask { task: task_3 },
|
||||
AssertExecuteOrder {
|
||||
execute_order: vec![3, 2, 1],
|
||||
rets: vec![ret_1, ret_2, ret_3],
|
||||
},
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_add_multiple_user_interactive_tasks_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task_1, ret_1) = make_text_user_interactive_task(test.next_task_id().await, "");
|
||||
let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, "");
|
||||
let (task_3, ret_3) = make_text_user_interactive_task(test.next_task_id().await, "");
|
||||
test.run_scripts(vec![
|
||||
AddTask { task: task_1 },
|
||||
AddTask { task: task_2 },
|
||||
AddTask { task: task_3 },
|
||||
AssertExecuteOrder {
|
||||
execute_order: vec![3, 2, 1],
|
||||
rets: vec![ret_1, ret_2, ret_3],
|
||||
},
|
||||
])
|
||||
.await;
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn task_add_multiple_different_kind_tasks_test() {
|
||||
let test = SearchTest::new().await;
|
||||
let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "");
|
||||
let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, "");
|
||||
let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, "");
|
||||
test.run_scripts(vec![
|
||||
AddTask { task: task_1 },
|
||||
AddTask { task: task_2 },
|
||||
AddTask { task: task_3 },
|
||||
AssertExecuteOrder {
|
||||
execute_order: vec![2, 3, 1],
|
||||
rets: vec![ret_1, ret_2, ret_3],
|
||||
},
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn task_add_multiple_different_kind_tasks_test2() {
|
||||
let test = SearchTest::new().await;
|
||||
let mut tasks = vec![];
|
||||
let mut rets = vec![];
|
||||
|
||||
for i in 0..10 {
|
||||
let (task, ret) = if i % 2 == 0 {
|
||||
make_text_background_task(test.next_task_id().await, "")
|
||||
} else {
|
||||
make_text_user_interactive_task(test.next_task_id().await, "")
|
||||
};
|
||||
tasks.push(task);
|
||||
rets.push(ret);
|
||||
}
|
||||
|
||||
test.run_scripts(vec![
|
||||
AddTasks { tasks },
|
||||
AssertExecuteOrder {
|
||||
execute_order: vec![10, 8, 6, 4, 2, 9, 7, 5, 3, 1],
|
||||
rets,
|
||||
},
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn task_add_1000_tasks_test() {
|
||||
// let test = SearchTest::new().await;
|
||||
// let mut tasks = vec![];
|
||||
// let mut execute_order = vec![];
|
||||
// let mut rets = vec![];
|
||||
//
|
||||
// for i in 1..1000 {
|
||||
// let (task, ret) = make_text_background_task(test.next_task_id().await, "");
|
||||
// execute_order.push(i);
|
||||
// tasks.push(task);
|
||||
// rets.push(ret);
|
||||
// }
|
||||
// execute_order.reverse();
|
||||
//
|
||||
// test.run_scripts(vec![AddTasks { tasks }, AssertExecuteOrder { execute_order, rets }])
|
||||
// .await;
|
||||
// }
|
@ -189,14 +189,6 @@ impl GridRevisionPad {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc<FieldRevision>)> {
|
||||
self.grid_rev
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, field)| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn replace_field_rev(
|
||||
&mut self,
|
||||
field_rev: Arc<FieldRevision>,
|
||||
@ -238,6 +230,14 @@ impl GridRevisionPad {
|
||||
self.grid_rev.fields.iter().any(|field| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc<FieldRevision>)> {
|
||||
self.grid_rev
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, field)| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field_revs(&self, field_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<FieldRevision>>> {
|
||||
match field_ids {
|
||||
None => Ok(self.grid_rev.fields.clone()),
|
||||
|
@ -3,8 +3,7 @@ use crate::util::{cal_diff, make_operations_from_revisions};
|
||||
use flowy_http_model::revision::Revision;
|
||||
use flowy_http_model::util::md5;
|
||||
use grid_rev_model::{
|
||||
FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision,
|
||||
GroupConfigurationRevision, GroupConfigurationsByFieldId, LayoutRevision,
|
||||
FieldRevision, FieldTypeRevision, FilterRevision, GridViewRevision, GroupConfigurationRevision, LayoutRevision,
|
||||
};
|
||||
use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
|
||||
use std::sync::Arc;
|
||||
@ -61,8 +60,12 @@ impl GridViewRevisionPad {
|
||||
Self::from_operations(view_id, operations)
|
||||
}
|
||||
|
||||
pub fn get_groups_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
|
||||
self.groups.get_objects_by_field_revs(field_revs)
|
||||
pub fn get_groups_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<GroupConfigurationRevision>> {
|
||||
self.groups
|
||||
.get_objects_by_field_revs(field_revs)
|
||||
.into_values()
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_all_groups(&self) -> Vec<Arc<GroupConfigurationRevision>> {
|
||||
@ -113,9 +116,9 @@ impl GridViewRevisionPad {
|
||||
|
||||
pub fn delete_group(
|
||||
&mut self,
|
||||
group_id: &str,
|
||||
field_id: &str,
|
||||
field_type: &FieldTypeRevision,
|
||||
group_id: &str,
|
||||
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) {
|
||||
@ -127,23 +130,23 @@ impl GridViewRevisionPad {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
|
||||
self.filters.get_objects_by_field_revs(field_revs)
|
||||
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<FilterRevision>> {
|
||||
self.filters
|
||||
.get_objects_by_field_revs(field_revs)
|
||||
.into_values()
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_filters(
|
||||
&self,
|
||||
field_id: &str,
|
||||
field_type_rev: &FieldTypeRevision,
|
||||
) -> Option<Vec<Arc<FilterConfigurationRevision>>> {
|
||||
self.filters.get_objects(field_id, field_type_rev)
|
||||
pub fn get_filters(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Vec<Arc<FilterRevision>> {
|
||||
self.filters.get_objects(field_id, field_type_rev).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn insert_filter(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
field_type: &FieldTypeRevision,
|
||||
filter_rev: FilterConfigurationRevision,
|
||||
filter_rev: FilterRevision,
|
||||
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
view.filters.add_object(field_id, field_type, filter_rev);
|
||||
@ -153,9 +156,9 @@ impl GridViewRevisionPad {
|
||||
|
||||
pub fn delete_filter(
|
||||
&mut self,
|
||||
filter_id: &str,
|
||||
field_id: &str,
|
||||
field_type: &FieldTypeRevision,
|
||||
filter_id: &str,
|
||||
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
if let Some(filters) = view.filters.get_mut_objects(field_id, field_type) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
|
||||
pub struct FilterConfigurationRevision {
|
||||
pub struct FilterRevision {
|
||||
pub id: String,
|
||||
pub field_id: String,
|
||||
pub condition: u8,
|
||||
pub content: Option<String>,
|
||||
#[serde(default)]
|
||||
pub content: String,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision};
|
||||
use crate::{FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision};
|
||||
use indexmap::IndexMap;
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -19,8 +19,8 @@ pub fn gen_grid_sort_id() -> String {
|
||||
nanoid!(6)
|
||||
}
|
||||
|
||||
pub type FilterConfiguration = Configuration<FilterConfigurationRevision>;
|
||||
pub type FilterConfigurationsByFieldId = HashMap<String, Vec<Arc<FilterConfigurationRevision>>>;
|
||||
pub type FilterConfiguration = Configuration<FilterRevision>;
|
||||
pub type FilterConfigurationsByFieldId = HashMap<String, Vec<Arc<FilterRevision>>>;
|
||||
//
|
||||
pub type GroupConfiguration = Configuration<GroupConfigurationRevision>;
|
||||
pub type GroupConfigurationsByFieldId = HashMap<String, Vec<Arc<GroupConfigurationRevision>>>;
|
||||
@ -60,7 +60,7 @@ where
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn get_objects_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<HashMap<String, Vec<Arc<T>>>> {
|
||||
pub fn get_objects_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> HashMap<String, Vec<Arc<T>>> {
|
||||
// Get the objects according to the FieldType, so we need iterate the field_revs.
|
||||
let objects_by_field_id = field_revs
|
||||
.iter()
|
||||
@ -73,7 +73,7 @@ where
|
||||
Some((field_rev.id.clone(), objects))
|
||||
})
|
||||
.collect::<HashMap<String, Vec<Arc<T>>>>();
|
||||
Some(objects_by_field_id)
|
||||
objects_by_field_id
|
||||
}
|
||||
|
||||
pub fn get_all_objects(&self) -> Vec<Arc<T>> {
|
||||
|
@ -8,20 +8,20 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub fn wrap_future<T, O>(f: T) -> AFFuture<O>
|
||||
pub fn to_future<T, O>(f: T) -> Fut<O>
|
||||
where
|
||||
T: Future<Output = O> + Send + Sync + 'static,
|
||||
{
|
||||
AFFuture { fut: Box::pin(f) }
|
||||
Fut { fut: Box::pin(f) }
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct AFFuture<T> {
|
||||
pub struct Fut<T> {
|
||||
#[pin]
|
||||
pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
|
||||
}
|
||||
|
||||
impl<T> Future for AFFuture<T>
|
||||
impl<T> Future for Fut<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait RefCountValue {
|
||||
fn did_remove(&self);
|
||||
fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
struct RefCountHandler<T> {
|
||||
|
Loading…
Reference in New Issue
Block a user