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:
Nathan.fooo 2022-11-13 22:23:57 +08:00 committed by GitHub
parent a1e0282df0
commit a0a16cc493
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 2293 additions and 1635 deletions

View File

@ -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"

View File

@ -15,6 +15,7 @@ members = [
"flowy-error",
"flowy-revision",
"flowy-grid",
"flowy-task",
]
[profile.dev]

View File

@ -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);

View File

@ -112,3 +112,5 @@ impl std::convert::From<protobuf::ProtobufError> for FlowyError {
FlowyError::internal().context(e)
}
}
impl std::error::Error for FlowyError {}

View File

@ -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"

View File

@ -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 {

View File

@ -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),
}
}
}

View File

@ -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;
};

View File

@ -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(),
}

View File

@ -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(),
}

View File

@ -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(),
}

View File

@ -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,
}

View File

@ -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::*;

View File

@ -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>,

View File

@ -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(&params.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)]

View File

@ -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)
}

View File

@ -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,
{

View File

@ -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<()> {

View File

@ -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>>>()
})
}
}

View File

@ -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();

View File

@ -1,4 +1,5 @@
#![allow(clippy::module_inception)]
mod checkbox_filter;
mod checkbox_tests;
mod checkbox_type_option;
mod checkbox_type_option_entities;

View File

@ -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),

View File

@ -1,4 +1,5 @@
#![allow(clippy::module_inception)]
mod date_filter;
mod date_tests;
mod date_type_option;
mod date_type_option_entities;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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()],
};

View File

@ -1,4 +1,5 @@
#![allow(clippy::module_inception)]
mod text_filter;
mod text_tests;
mod text_type_option;

View File

@ -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);

View File

@ -1,4 +1,5 @@
#![allow(clippy::module_inception)]
mod url_filter;
mod url_tests;
mod url_type_option;
mod url_type_option_entities;

View File

@ -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);
}

View 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
}
}

View 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()
}
}

View File

@ -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(),
}
}
}

View File

@ -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,
}
}
}

View File

@ -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::*;

View File

@ -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::*;

View 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(())
})
}
}

View File

@ -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(&params.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

View File

@ -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);
})
}
}

View File

@ -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()
}
}

View File

@ -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(&params.field_id).await {
if let Some(field_rev) = self.delegate.get_field_rev(&params.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(&params.field_id, &params.field_type_rev, &params.group_id)?;
let changeset = pad.delete_group(&params.group_id, &params.field_id, &params.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(&params.field_id, &params.field_type_rev, filter_rev)?;
Ok(changeset)
})
.await
pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
let filter_type = FilterType::from(&params);
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(&params.field_id, &params.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(&params.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;

View File

@ -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(

View File

@ -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)>;

View File

@ -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> {

View File

@ -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)> {

View File

@ -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)> {

View File

@ -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;

View File

@ -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()
}

View File

@ -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
}

View File

@ -1,3 +0,0 @@
mod snapshot_service;
pub use snapshot_service::*;

View File

@ -1,7 +0,0 @@
// pub struct GridSnapshotService {}
//
// impl GridSnapshotService {
// pub fn new() -> Self {
// Self {}
// }
// }

View File

@ -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;
}
}
}

View File

@ -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(())
})
}
}
}

View File

@ -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,
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,2 +1,5 @@
mod checkbox_filter_test;
mod date_filter_test;
mod number_filter_test;
mod script;
mod text_filter_test;

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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()))
}

View File

@ -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))

View File

@ -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 }

View File

@ -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)),
));

View File

@ -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;
});
}

View File

@ -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 {

View 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"

View File

@ -1,5 +1,4 @@
mod queue;
mod runner;
mod scheduler;
mod store;
mod task;

View File

@ -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()) {

View 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
}
}

View File

@ -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());
}
});

View 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(),
}
}
}

View File

@ -0,0 +1 @@
mod task_test;

View File

@ -0,0 +1,3 @@
mod script;
mod task_cancel_test;
mod task_order_test;

View 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)
}

View File

@ -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);
}

View 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;
// }

View File

@ -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()),

View File

@ -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) {

View File

@ -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,
}

View File

@ -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>> {

View File

@ -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,
{

View File

@ -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> {