refactor: rename group (#3815)

* chore: add group operation interceptor

* refactor: impl interceptor trait

* chore: update type option when group change

* test: fix test
This commit is contained in:
Nathan.fooo 2023-10-28 11:48:28 +08:00 committed by GitHub
parent 09b4e19c9d
commit e28e5a0649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 980 additions and 743 deletions

View File

@ -454,7 +454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive",
"hashbrown 0.13.2",
"hashbrown 0.12.3",
]
[[package]]
@ -854,7 +854,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -873,7 +873,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -903,7 +903,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"proc-macro2",
"quote",
@ -915,7 +915,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"collab",
@ -935,7 +935,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"bytes",
@ -949,7 +949,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"chrono",
@ -991,7 +991,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"async-trait",
"bincode",
@ -1012,7 +1012,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -1039,7 +1039,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"collab",
@ -2115,6 +2115,7 @@ dependencies = [
"serde_json",
"serde_repr",
"thiserror",
"tokio",
"tokio-postgres",
"url",
"validator",

View File

@ -48,14 +48,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }

View File

@ -461,7 +461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive",
"hashbrown 0.13.2",
"hashbrown 0.12.3",
]
[[package]]
@ -721,7 +721,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -740,7 +740,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -770,7 +770,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"proc-macro2",
"quote",
@ -782,7 +782,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"collab",
@ -802,7 +802,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"bytes",
@ -816,7 +816,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"chrono",
@ -858,7 +858,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"async-trait",
"bincode",
@ -879,7 +879,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"async-trait",
@ -906,7 +906,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [
"anyhow",
"collab",
@ -1938,6 +1938,7 @@ dependencies = [
"serde_json",
"serde_repr",
"thiserror",
"tokio",
"tokio-postgres",
"url",
"validator",

View File

@ -92,11 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }

View File

@ -761,7 +761,7 @@ async fn rename_group_event_test() {
}
#[tokio::test]
async fn hide_group_event_test2() {
async fn hide_group_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test

View File

@ -2,6 +2,7 @@ use std::sync::{Arc, Weak};
use collab_database::database::gen_row_id;
use collab_database::rows::RowId;
use tokio::sync::oneshot;
use flowy_error::{FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
@ -15,7 +16,7 @@ use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
};
use crate::services::field_settings::FieldSettingsChangesetParams;
use crate::services::group::{GroupChangeset, GroupChangesets};
use crate::services::group::GroupChangeset;
use crate::services::share::csv::CSVFormat;
fn upgrade_manager(
@ -725,18 +726,15 @@ pub(crate) async fn update_group_handler(
let view_id = params.view_id.clone();
let database_editor = manager.get_database_with_view_id(&view_id).await?;
let group_changeset = GroupChangeset::from(params);
database_editor
.update_group(&view_id, group_changeset.clone())
.await?;
database_editor
.update_group_setting(
&view_id,
GroupChangesets {
update_groups: vec![group_changeset],
},
)
.await?;
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let result = database_editor
.update_group(&view_id, vec![group_changeset].into())
.await;
let _ = tx.send(result);
});
let _ = rx.await?;
Ok(())
}

View File

@ -8,10 +8,11 @@ use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetai
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use futures::StreamExt;
use tokio::sync::{broadcast, RwLock};
use tracing::{event, warn};
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
use flowy_task::TaskDispatcher;
use lib_infra::future::{to_fut, Fut};
use lib_infra::future::{to_fut, Fut, FutureResult};
use crate::entities::*;
use crate::notification::{send_notification, DatabaseNotification};
@ -20,7 +21,9 @@ use crate::services::cell::{
};
use crate::services::database::util::database_view_setting_pb_from_view;
use crate::services::database::UpdatedRow;
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
use crate::services::database_view::{
DatabaseViewChanged, DatabaseViewEditor, DatabaseViewOperation, DatabaseViews, EditorByViewId,
};
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
@ -32,8 +35,7 @@ use crate::services::field_settings::{
};
use crate::services::filter::Filter;
use crate::services::group::{
default_group_setting, GroupChangeset, GroupChangesets, GroupSetting, GroupSettingChangeset,
RowChangeset,
default_group_setting, GroupChangesets, GroupSetting, GroupSettingChangeset, RowChangeset,
};
use crate::services::share::csv::{CSVExport, CSVFormat};
use crate::services::sort::Sort;
@ -51,12 +53,6 @@ impl DatabaseEditor {
task_scheduler: Arc<RwLock<TaskDispatcher>>,
) -> FlowyResult<Self> {
let cell_cache = AnyTypeCache::<u64>::new();
let database_view_data = Arc::new(DatabaseViewDataImpl {
database: database.clone(),
task_scheduler: task_scheduler.clone(),
cell_cache: cell_cache.clone(),
});
let database_id = database.lock().get_database_id();
// Receive database sync state and send to frontend via the notification
@ -93,8 +89,24 @@ impl DatabaseEditor {
}
});
let database_views =
Arc::new(DatabaseViews::new(database.clone(), cell_cache.clone(), database_view_data).await?);
// Used to cache the view of the database for fast access.
let editor_by_view_id = Arc::new(RwLock::new(EditorByViewId::default()));
let view_operation = Arc::new(DatabaseViewOperationImpl {
database: database.clone(),
task_scheduler: task_scheduler.clone(),
cell_cache: cell_cache.clone(),
editor_by_view_id: editor_by_view_id.clone(),
});
let database_views = Arc::new(
DatabaseViews::new(
database.clone(),
cell_cache.clone(),
view_operation,
editor_by_view_id,
)
.await?,
);
Ok(Self {
database,
cell_cache,
@ -177,40 +189,9 @@ impl DatabaseEditor {
Ok(self.database.lock().delete_view(view_id))
}
pub async fn update_group_setting(
&self,
view_id: &str,
group_setting_changeset: GroupChangesets,
) -> FlowyResult<()> {
pub async fn update_group(&self, view_id: &str, changesets: GroupChangesets) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?;
view_editor
.v_update_group_setting(group_setting_changeset)
.await?;
Ok(())
}
pub async fn update_group(
&self,
view_id: &str,
group_changeset: GroupChangeset,
) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?;
let type_option = view_editor.update_group(group_changeset.clone()).await?;
if let Some(type_option_data) = type_option {
let field = self.get_field(&group_changeset.field_id);
if field.is_some() {
let _ = self
.update_field_type_option(
view_id,
&group_changeset.field_id,
type_option_data,
field.unwrap(),
)
.await;
}
}
view_editor.v_update_group(changesets).await?;
Ok(())
}
@ -295,9 +276,7 @@ impl DatabaseEditor {
.set_width_at_if_not_none(params.width.map(|value| value as i64))
.set_visibility_if_not_none(params.visibility);
});
self
.notify_did_update_database_field(&params.field_id)
.await?;
notify_did_update_database_field(&self.database, &params.field_id)?;
Ok(())
}
@ -331,30 +310,13 @@ impl DatabaseEditor {
pub async fn update_field_type_option(
&self,
view_id: &str,
field_id: &str,
_field_id: &str,
type_option_data: TypeOptionData,
old_field: Field,
) -> FlowyResult<()> {
let field_type = FieldType::from(old_field.field_type);
self
.database
.lock()
.fields
.update_field(field_id, |update| {
if old_field.is_primary {
tracing::warn!("Cannot update primary field type");
} else {
update.update_type_options(|type_options_update| {
type_options_update.insert(&field_type.to_string(), type_option_data);
});
}
});
let view_editor = self.database_views.get_view_editor(view_id).await?;
update_field_type_option_fn(&self.database, &view_editor, type_option_data, old_field).await?;
self
.database_views
.did_update_field_type_option(view_id, field_id, &old_field)
.await?;
let _ = self.notify_did_update_database_field(field_id).await;
Ok(())
}
@ -398,7 +360,7 @@ impl DatabaseEditor {
},
}
self.notify_did_update_database_field(field_id).await?;
notify_did_update_database_field(&self.database, field_id)?;
Ok(())
}
@ -435,7 +397,7 @@ impl DatabaseEditor {
let params = self.database.lock().duplicate_row(row_id);
match params {
None => {
tracing::warn!("Failed to duplicate row: {}", row_id);
warn!("Failed to duplicate row: {}", row_id);
},
Some(params) => {
let _ = self.create_row(view_id, group_id, params).await;
@ -585,7 +547,7 @@ impl DatabaseEditor {
cover: row_meta.cover_url,
})
} else {
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
None
}
}
@ -594,7 +556,7 @@ impl DatabaseEditor {
if self.database.lock().views.is_row_exist(view_id, row_id) {
self.database.lock().get_row_detail(row_id)
} else {
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
None
}
}
@ -984,7 +946,7 @@ impl DatabaseEditor {
let row_detail = self.get_row_detail(view_id, &from_row);
match row_detail {
None => {
tracing::warn!(
warn!(
"Move row between group failed, can not find the row:{}",
from_row
)
@ -1054,7 +1016,7 @@ impl DatabaseEditor {
match self.database_views.get_view_editor(view_id).await {
Ok(view) => view.v_get_all_calendar_events().await.unwrap_or_default(),
Err(_) => {
tracing::warn!("Can not find the view: {}", view_id);
warn!("Can not find the view: {}", view_id);
vec![]
},
}
@ -1066,7 +1028,7 @@ impl DatabaseEditor {
view_id: &str,
) -> FlowyResult<Vec<NoDateCalendarEventPB>> {
let _database_view = self.database_views.get_view_editor(view_id).await?;
todo!()
Ok(vec![])
}
#[tracing::instrument(level = "trace", skip_all)]
@ -1084,28 +1046,6 @@ impl DatabaseEditor {
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
async fn notify_did_update_database_field(&self, field_id: &str) -> FlowyResult<()> {
let (database_id, field) = {
let database = self.database.lock();
let database_id = database.get_database_id();
let field = database.fields.get_field(field_id);
(database_id, field)
};
if let Some(field) = field {
let updated_field = FieldPB::from(field);
let notified_changeset =
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
self.notify_did_update_database(notified_changeset).await?;
send_notification(field_id, DatabaseNotification::DidUpdateField)
.payload(updated_field)
.send();
}
Ok(())
}
async fn notify_did_update_database(
&self,
changeset: DatabaseFieldChangesetPB,
@ -1134,7 +1074,7 @@ impl DatabaseEditor {
pub async fn get_database_data(&self, view_id: &str) -> FlowyResult<DatabasePB> {
let database_view = self.database_views.get_view_editor(view_id).await?;
let view = database_view
.get_view()
.v_get_view()
.await
.ok_or_else(FlowyError::record_not_found)?;
let rows = database_view.v_get_rows().await;
@ -1284,13 +1224,14 @@ fn cell_changesets_from_cell_by_field_id(
.collect()
}
struct DatabaseViewDataImpl {
struct DatabaseViewOperationImpl {
database: Arc<MutexDatabase>,
task_scheduler: Arc<RwLock<TaskDispatcher>>,
cell_cache: CellCache,
editor_by_view_id: Arc<RwLock<EditorByViewId>>,
}
impl DatabaseViewData for DatabaseViewDataImpl {
impl DatabaseViewOperation for DatabaseViewOperationImpl {
fn get_database(&self) -> Arc<MutexDatabase> {
self.database.clone()
}
@ -1305,14 +1246,8 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { fields.into_iter().map(Arc::new).collect() })
}
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> {
let field = self
.database
.lock()
.fields
.get_field(field_id)
.map(Arc::new);
to_fut(async move { field })
fn get_field(&self, field_id: &str) -> Option<Field> {
self.database.lock().fields.get_field(field_id)
}
fn create_field(
@ -1336,6 +1271,29 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { field })
}
fn update_field(
&self,
view_id: &str,
type_option_data: TypeOptionData,
old_field: Field,
) -> FutureResult<(), FlowyError> {
let view_id = view_id.to_string();
let weak_editor_by_view_id = Arc::downgrade(&self.editor_by_view_id);
let weak_database = Arc::downgrade(&self.database);
FutureResult::new(async move {
if let (Some(database), Some(editor_by_view_id)) =
(weak_database.upgrade(), weak_editor_by_view_id.upgrade())
{
let view_editor = editor_by_view_id.read().await.get(&view_id).cloned();
if let Some(view_editor) = view_editor {
let _ =
update_field_type_option_fn(&database, &view_editor, type_option_data, old_field).await;
}
}
Ok(())
})
}
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>> {
let field = self
.database
@ -1559,3 +1517,73 @@ impl DatabaseViewData for DatabaseViewDataImpl {
.send()
}
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn update_field_type_option_fn(
database: &Arc<MutexDatabase>,
view_editor: &Arc<DatabaseViewEditor>,
type_option_data: TypeOptionData,
old_field: Field,
) -> FlowyResult<()> {
if type_option_data.is_empty() {
warn!("Update type option with empty data");
return Ok(());
}
let field_type = FieldType::from(old_field.field_type);
database
.lock()
.fields
.update_field(&old_field.id, |update| {
if old_field.is_primary {
warn!("Cannot update primary field type");
} else {
update.update_type_options(|type_options_update| {
event!(
tracing::Level::TRACE,
"insert type option to field type: {:?}",
field_type
);
type_options_update.insert(&field_type.to_string(), type_option_data);
});
}
});
let _ = notify_did_update_database_field(database, &old_field.id);
view_editor
.v_did_update_field_type_option(&old_field)
.await?;
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
fn notify_did_update_database_field(
database: &Arc<MutexDatabase>,
field_id: &str,
) -> FlowyResult<()> {
let (database_id, field, views) = {
let database = database
.try_lock()
.ok_or(FlowyError::internal().with_context("fail to acquire the lock of database"))?;
let database_id = database.get_database_id();
let field = database.fields.get_field(field_id);
let views = database.get_all_views_description();
(database_id, field, views)
};
if let Some(field) = field {
let updated_field = FieldPB::from(field);
let notified_changeset =
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
for view in views {
send_notification(&view.id, DatabaseNotification::DidUpdateFields)
.payload(notified_changeset.clone())
.send();
}
send_notification(field_id, DatabaseNotification::DidUpdateField)
.payload(updated_field)
.send();
}
Ok(())
}

View File

@ -1,6 +1,7 @@
pub use layout_deps::*;
pub use notifier::*;
pub use view_editor::*;
pub use view_operation::*;
pub use views::*;
mod layout_deps;
@ -8,6 +9,7 @@ mod notifier;
mod view_editor;
mod view_filter;
mod view_group;
mod view_operation;
mod view_sort;
mod views;
// mod trait_impl;

View File

@ -2,15 +2,13 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, MutexDatabase};
use collab_database::database::{gen_database_filter_id, gen_database_sort_id};
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowCell, RowDetail, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use collab_database::rows::{Cells, Row, RowDetail, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView};
use tokio::sync::{broadcast, RwLock};
use flowy_error::{FlowyError, FlowyResult};
use flowy_task::TaskDispatcher;
use lib_infra::future::Fut;
use crate::entities::{
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
@ -25,124 +23,27 @@ use crate::services::database_view::view_filter::make_filter_controller;
use crate::services::database_view::view_group::{
get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
};
use crate::services::database_view::view_operation::DatabaseViewOperation;
use crate::services::database_view::view_sort::make_sort_controller;
use crate::services::database_view::{
notify_did_update_filter, notify_did_update_group_rows, notify_did_update_num_of_groups,
notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver,
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field_settings::FieldSettings;
use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
};
use crate::services::group::{
GroupChangeset, GroupChangesets, GroupController, GroupSetting, GroupSettingChangeset,
MoveGroupRowContext, RowChangeset,
GroupChangesets, GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext,
RowChangeset,
};
use crate::services::setting::CalendarLayoutSetting;
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
pub trait DatabaseViewData: Send + Sync + 'static {
fn get_database(&self) -> Arc<MutexDatabase>;
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
/// If the field_ids is None, then it will return all the field revisions
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
/// Returns the field with the field_id
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>;
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field>;
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>>;
/// Returns the index of the row with row_id
fn index_of_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<usize>>;
/// Returns the `index` and `RowRevision` with row_id
fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
/// Returns all the rows in the view
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
/// Return the database layout type for the view with given view_id
/// The default layout type is [DatabaseLayout::Grid]
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout;
fn get_group_setting(&self, view_id: &str) -> Vec<GroupSetting>;
fn insert_group_setting(&self, view_id: &str, setting: GroupSetting);
fn get_sort(&self, view_id: &str, sort_id: &str) -> Option<Sort>;
fn insert_sort(&self, view_id: &str, sort: Sort);
fn remove_sort(&self, view_id: &str, sort_id: &str);
fn get_all_sorts(&self, view_id: &str) -> Vec<Sort>;
fn remove_all_sorts(&self, view_id: &str);
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>>;
fn delete_filter(&self, view_id: &str, filter_id: &str);
fn insert_filter(&self, view_id: &str, filter: Filter);
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter>;
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting>;
fn insert_layout_setting(
&self,
view_id: &str,
layout_ty: &DatabaseLayout,
layout_setting: LayoutSetting,
);
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout);
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(
&self,
view_id: &str,
field_ids: &[String],
) -> HashMap<String, FieldSettings>;
fn get_all_field_settings(&self, view_id: &str) -> HashMap<String, FieldSettings>;
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
);
}
pub struct DatabaseViewEditor {
pub view_id: String,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
group_controller: Arc<RwLock<Option<Box<dyn GroupController>>>>,
filter_controller: Arc<FilterController>,
sort_controller: Arc<RwLock<SortController>>,
@ -158,14 +59,17 @@ impl Drop for DatabaseViewEditor {
impl DatabaseViewEditor {
pub async fn new(
view_id: String,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
cell_cache: CellCache,
) -> FlowyResult<Self> {
let (notifier, _) = broadcast::channel(100);
tokio::spawn(DatabaseViewChangedReceiverRunner(Some(notifier.subscribe())).run());
let group_controller = new_group_controller(view_id.clone(), delegate.clone()).await?;
let group_controller = Arc::new(RwLock::new(group_controller));
// Group
let group_controller = Arc::new(RwLock::new(
new_group_controller(view_id.clone(), delegate.clone()).await?,
));
// Filter
let filter_controller = make_filter_controller(
&view_id,
delegate.clone(),
@ -174,6 +78,7 @@ impl DatabaseViewEditor {
)
.await;
// Sort
let sort_controller = make_sort_controller(
&view_id,
delegate.clone(),
@ -198,7 +103,7 @@ impl DatabaseViewEditor {
self.filter_controller.close().await;
}
pub async fn get_view(&self) -> Option<DatabaseView> {
pub async fn v_get_view(&self) -> Option<DatabaseView> {
self.delegate.get_view(&self.view_id).await
}
@ -383,7 +288,7 @@ impl DatabaseViewEditor {
let move_row_context = MoveGroupRowContext {
row_detail,
row_changeset,
field: field.as_ref(),
field: &field,
to_group_id,
to_row_id,
};
@ -485,40 +390,31 @@ impl DatabaseViewEditor {
Ok(result.flatten())
}
pub async fn v_update_group_setting(&self, changeset: GroupChangesets) -> FlowyResult<()> {
self
.mut_group_controller(|group_controller, _| {
group_controller.apply_group_setting_changeset(changeset)
})
.await;
pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
let mut type_option_data = TypeOptionData::new();
let old_field = if let Some(controller) = self.group_controller.write().await.as_mut() {
let old_field = self.delegate.get_field(controller.field_id());
type_option_data.extend(controller.apply_group_changeset(&changeset).await?);
old_field
} else {
None
};
if let Some(old_field) = old_field {
if !type_option_data.is_empty() {
self
.delegate
.update_field(&self.view_id, type_option_data, old_field)
.await?;
}
}
Ok(())
}
pub async fn v_get_group_configuration_settings(&self) -> Vec<GroupSetting> {
self.delegate.get_group_setting(&self.view_id)
}
pub async fn update_group(
&self,
changeset: GroupChangeset,
) -> FlowyResult<Option<TypeOptionData>> {
match changeset.name {
Some(group_name) => {
let result = self
.mut_group_controller(|controller, _| {
Ok(controller.update_group_name(&changeset.group_id, &group_name))
})
.await;
match result {
Some(r) => Ok(r),
None => Ok(None),
}
},
None => Ok(None),
}
}
pub async fn v_get_all_sorts(&self) -> Vec<Sort> {
self.delegate.get_all_sorts(&self.view_id)
}
@ -659,7 +555,7 @@ impl DatabaseViewEditor {
if let Some(value) = self.delegate.get_layout_setting(&self.view_id, layout_ty) {
let calendar_setting = CalendarLayoutSetting::from(value);
// Check the field exist or not
if let Some(field) = self.delegate.get_field(&calendar_setting.field_id).await {
if let Some(field) = self.delegate.get_field(&calendar_setting.field_id) {
let field_type = FieldType::from(field.field_type);
// Check the type of field is Datetime or not
@ -682,11 +578,7 @@ impl DatabaseViewEditor {
pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> {
// Maybe it needs no send notification to refresh the UI
if let Some(new_calendar_setting) = params.calendar {
if let Some(field) = self
.delegate
.get_field(&new_calendar_setting.field_id)
.await
{
if let Some(field) = self.delegate.get_field(&new_calendar_setting.field_id) {
let field_type = FieldType::from(field.field_type);
if field_type != FieldType::DateTime {
return Err(FlowyError::unexpect_calendar_field_type());
@ -729,13 +621,19 @@ impl DatabaseViewEditor {
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
/// [Field] has a property named type_options contains a list of type-option data.
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn v_did_update_field_type_option(
&self,
field_id: &str,
old_field: &Field,
) -> FlowyResult<()> {
if let Some(field) = self.delegate.get_field(field_id).await {
pub async fn v_did_update_field_type_option(&self, old_field: &Field) -> FlowyResult<()> {
let field_id = &old_field.id;
// If the id of the grouping field is equal to the updated field's id, then we need to
// update the group setting
if self.is_grouping_field(field_id).await {
self.v_grouping_by_field(field_id).await?;
}
if let Some(field) = self.delegate.get_field(field_id) {
self
.sort_controller
.read()
@ -776,9 +674,13 @@ impl DatabaseViewEditor {
/// Called when a grouping field is updated.
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field) = self.delegate.get_field(field_id).await {
let new_group_controller =
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?;
if let Some(field) = self.delegate.get_field(field_id) {
let new_group_controller = new_group_controller_with_field(
self.view_id.clone(),
self.delegate.clone(),
Arc::new(field),
)
.await?;
let new_groups = new_group_controller
.groups()
@ -812,7 +714,7 @@ impl DatabaseViewEditor {
let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, &row_id).await?;
// Date
let date_field = self.delegate.get_field(&calendar_setting.field_id).await?;
let date_field = self.delegate.get_field(&calendar_setting.field_id)?;
let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, &row_id).await?;
let title = text_cell
@ -973,7 +875,7 @@ impl DatabaseViewEditor {
async fn mut_group_controller<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&mut Box<dyn GroupController>, Arc<Field>) -> FlowyResult<T>,
F: FnOnce(&mut Box<dyn GroupController>, Field) -> FlowyResult<T>,
{
let group_field_id = self
.group_controller
@ -981,8 +883,7 @@ impl DatabaseViewEditor {
.await
.as_ref()
.map(|group| group.field_id().to_owned())?;
let field = self.delegate.get_field(&group_field_id).await?;
let field = self.delegate.get_field(&group_field_id)?;
let mut write_guard = self.group_controller.write().await;
if let Some(group_controller) = &mut *write_guard {
f(group_controller, field).ok()

View File

@ -7,13 +7,13 @@ use lib_infra::future::{to_fut, Fut};
use crate::services::cell::CellCache;
use crate::services::database_view::{
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation,
};
use crate::services::filter::{Filter, FilterController, FilterDelegate, FilterTaskHandler};
pub async fn make_filter_controller(
view_id: &str,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
notifier: DatabaseViewChangedNotifier,
cell_cache: CellCache,
) -> Arc<FilterController> {
@ -43,7 +43,7 @@ pub async fn make_filter_controller(
filter_controller
}
struct DatabaseViewFilterDelegateImpl(Arc<dyn DatabaseViewData>);
struct DatabaseViewFilterDelegateImpl(Arc<dyn DatabaseViewOperation>);
impl FilterDelegate for DatabaseViewFilterDelegateImpl {
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>> {
@ -51,7 +51,7 @@ impl FilterDelegate for DatabaseViewFilterDelegateImpl {
to_fut(async move { filter })
}
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> {
fn get_field(&self, field_id: &str) -> Option<Field> {
self.0.get_field(field_id)
}

View File

@ -1,40 +1,43 @@
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field;
use collab_database::rows::RowId;
use collab_database::rows::{Cell, RowId};
use flowy_error::FlowyResult;
use lib_infra::future::{to_fut, Fut};
use crate::entities::FieldType;
use crate::services::database_view::DatabaseViewData;
use crate::services::database_view::DatabaseViewOperation;
use crate::services::field::RowSingleCellData;
use crate::services::group::{
find_new_grouping_field, make_group_controller, GroupController, GroupSetting,
GroupSettingReader, GroupSettingWriter,
GroupSettingReader, GroupSettingWriter, GroupTypeOptionCellOperation,
};
pub async fn new_group_controller_with_field(
view_id: String,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
grouping_field: Arc<Field>,
) -> FlowyResult<Box<dyn GroupController>> {
let setting_reader = GroupSettingReaderImpl(delegate.clone());
let rows = delegate.get_rows(&view_id).await;
let setting_writer = GroupSettingWriterImpl(delegate.clone());
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
make_group_controller(
view_id,
grouping_field,
rows,
setting_reader,
setting_writer,
type_option_writer,
)
.await
}
pub async fn new_group_controller(
view_id: String,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
) -> FlowyResult<Option<Box<dyn GroupController>>> {
let fields = delegate.get_fields(&view_id, None).await;
let setting_reader = GroupSettingReaderImpl(delegate.clone());
@ -59,6 +62,7 @@ pub async fn new_group_controller(
if let Some(grouping_field) = grouping_field {
let rows = delegate.get_rows(&view_id).await;
let setting_writer = GroupSettingWriterImpl(delegate.clone());
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
Ok(Some(
make_group_controller(
view_id,
@ -66,6 +70,7 @@ pub async fn new_group_controller(
rows,
setting_reader,
setting_writer,
type_option_writer,
)
.await?,
))
@ -74,7 +79,7 @@ pub async fn new_group_controller(
}
}
pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewData>);
pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewOperation>);
impl GroupSettingReader for GroupSettingReaderImpl {
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>> {
@ -97,11 +102,11 @@ impl GroupSettingReader for GroupSettingReaderImpl {
}
pub(crate) async fn get_cell_for_row(
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
field_id: &str,
row_id: &RowId,
) -> Option<RowSingleCellData> {
let field = delegate.get_field(field_id).await?;
let field = delegate.get_field(field_id)?;
let row_cell = delegate.get_cell_in_row(field_id, row_id).await;
let field_type = FieldType::from(field.field_type);
let handler = delegate.get_type_option_cell_handler(&field, &field_type)?;
@ -120,11 +125,11 @@ pub(crate) async fn get_cell_for_row(
// Returns the list of cells corresponding to the given field.
pub(crate) async fn get_cells_for_field(
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
view_id: &str,
field_id: &str,
) -> Vec<RowSingleCellData> {
if let Some(field) = delegate.get_field(field_id).await {
if let Some(field) = delegate.get_field(field_id) {
let field_type = FieldType::from(field.field_type);
if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) {
let cells = delegate.get_cells_for_field(view_id, field_id).await;
@ -149,11 +154,30 @@ pub(crate) async fn get_cells_for_field(
vec![]
}
struct GroupSettingWriterImpl(Arc<dyn DatabaseViewData>);
struct GroupSettingWriterImpl(Arc<dyn DatabaseViewOperation>);
impl GroupSettingWriter for GroupSettingWriterImpl {
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>> {
self.0.insert_group_setting(view_id, group_setting);
to_fut(async move { Ok(()) })
}
}
struct GroupTypeOptionCellWriterImpl(Arc<dyn DatabaseViewOperation>);
#[async_trait]
impl GroupTypeOptionCellOperation for GroupTypeOptionCellWriterImpl {
async fn get_cell(&self, _row_id: &RowId, _field_id: &str) -> FlowyResult<Option<Cell>> {
todo!()
}
#[tracing::instrument(level = "trace", skip_all, err)]
async fn update_cell(
&self,
_view_id: &str,
_row_id: &RowId,
_field_id: &str,
_cell: Cell,
) -> FlowyResult<()> {
todo!()
}
}

View File

@ -0,0 +1,126 @@
use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::MutexDatabase;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{RowCell, RowDetail, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use tokio::sync::RwLock;
use flowy_error::FlowyError;
use flowy_task::TaskDispatcher;
use lib_infra::future::{Fut, FutureResult};
use crate::entities::{FieldType, FieldVisibility};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field_settings::FieldSettings;
use crate::services::filter::Filter;
use crate::services::group::GroupSetting;
use crate::services::sort::Sort;
/// Defines the operation that can be performed on a database view
pub trait DatabaseViewOperation: Send + Sync + 'static {
/// Get the database that the view belongs to
fn get_database(&self) -> Arc<MutexDatabase>;
/// Get the view of the database with the view_id
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
/// If the field_ids is None, then it will return all the field revisions
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
/// Returns the field with the field_id
fn get_field(&self, field_id: &str) -> Option<Field>;
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field>;
fn update_field(
&self,
view_id: &str,
type_option_data: TypeOptionData,
old_field: Field,
) -> FutureResult<(), FlowyError>;
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>>;
/// Returns the index of the row with row_id
fn index_of_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<usize>>;
/// Returns the `index` and `RowRevision` with row_id
fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
/// Returns all the rows in the view
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
/// Return the database layout type for the view with given view_id
/// The default layout type is [DatabaseLayout::Grid]
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout;
fn get_group_setting(&self, view_id: &str) -> Vec<GroupSetting>;
fn insert_group_setting(&self, view_id: &str, setting: GroupSetting);
fn get_sort(&self, view_id: &str, sort_id: &str) -> Option<Sort>;
fn insert_sort(&self, view_id: &str, sort: Sort);
fn remove_sort(&self, view_id: &str, sort_id: &str);
fn get_all_sorts(&self, view_id: &str) -> Vec<Sort>;
fn remove_all_sorts(&self, view_id: &str);
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>>;
fn delete_filter(&self, view_id: &str, filter_id: &str);
fn insert_filter(&self, view_id: &str, filter: Filter);
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter>;
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting>;
fn insert_layout_setting(
&self,
view_id: &str,
layout_ty: &DatabaseLayout,
layout_setting: LayoutSetting,
);
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout);
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(
&self,
view_id: &str,
field_ids: &[String],
) -> HashMap<String, FieldSettings>;
fn get_all_field_settings(&self, view_id: &str) -> HashMap<String, FieldSettings>;
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
);
}

View File

@ -8,14 +8,14 @@ use lib_infra::future::{to_fut, Fut};
use crate::services::cell::CellCache;
use crate::services::database_view::{
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation,
};
use crate::services::filter::FilterController;
use crate::services::sort::{Sort, SortController, SortDelegate, SortTaskHandler};
pub(crate) async fn make_sort_controller(
view_id: &str,
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
notifier: DatabaseViewChangedNotifier,
filter_controller: Arc<FilterController>,
cell_cache: CellCache,
@ -49,7 +49,7 @@ pub(crate) async fn make_sort_controller(
}
struct DatabaseViewSortDelegateImpl {
delegate: Arc<dyn DatabaseViewData>,
delegate: Arc<dyn DatabaseViewOperation>,
filter_controller: Arc<FilterController>,
}
@ -70,7 +70,7 @@ impl SortDelegate for DatabaseViewSortDelegateImpl {
})
}
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> {
fn get_field(&self, field_id: &str) -> Option<Field> {
self.delegate.get_field(field_id)
}

View File

@ -2,47 +2,46 @@ use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::MutexDatabase;
use collab_database::fields::Field;
use collab_database::rows::{RowDetail, RowId};
use nanoid::nanoid;
use tokio::sync::{broadcast, RwLock};
use flowy_error::FlowyResult;
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::Fut;
use crate::services::cell::CellCache;
use crate::services::database::DatabaseRowEvent;
use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor};
use crate::services::database_view::{DatabaseViewEditor, DatabaseViewOperation};
use crate::services::group::RowChangeset;
pub type RowEventSender = broadcast::Sender<DatabaseRowEvent>;
pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>;
pub type EditorByViewId = HashMap<String, Arc<DatabaseViewEditor>>;
pub struct DatabaseViews {
#[allow(dead_code)]
database: Arc<MutexDatabase>,
cell_cache: CellCache,
database_view_data: Arc<dyn DatabaseViewData>,
editor_map: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
view_operation: Arc<dyn DatabaseViewOperation>,
editor_by_view_id: Arc<RwLock<EditorByViewId>>,
}
impl DatabaseViews {
pub async fn new(
database: Arc<MutexDatabase>,
cell_cache: CellCache,
database_view_data: Arc<dyn DatabaseViewData>,
view_operation: Arc<dyn DatabaseViewOperation>,
editor_by_view_id: Arc<RwLock<EditorByViewId>>,
) -> FlowyResult<Self> {
let editor_map = Arc::new(RwLock::new(HashMap::default()));
Ok(Self {
database,
database_view_data,
view_operation,
cell_cache,
editor_map,
editor_by_view_id,
})
}
pub async fn close_view(&self, view_id: &str) -> bool {
let mut editor_map = self.editor_map.write().await;
let mut editor_map = self.editor_by_view_id.write().await;
if let Some(view) = editor_map.remove(view_id) {
view.close().await;
}
@ -50,7 +49,13 @@ impl DatabaseViews {
}
pub async fn editors(&self) -> Vec<Arc<DatabaseViewEditor>> {
self.editor_map.read().await.values().cloned().collect()
self
.editor_by_view_id
.read()
.await
.values()
.cloned()
.collect()
}
/// It may generate a RowChangeset when the Row was moved from one group to another.
@ -77,43 +82,22 @@ impl DatabaseViews {
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
/// [Field] has a property named type_options contains a list of type-option data.
/// # Arguments
///
/// * `field_id`: the id of the field in current view
///
#[tracing::instrument(level = "debug", skip(self, old_field), err)]
pub async fn did_update_field_type_option(
&self,
view_id: &str,
field_id: &str,
old_field: &Field,
) -> FlowyResult<()> {
let view_editor = self.get_view_editor(view_id).await?;
// If the id of the grouping field is equal to the updated field's id, then we need to
// update the group setting
if view_editor.is_grouping_field(field_id).await {
view_editor.v_grouping_by_field(field_id).await?;
}
view_editor
.v_did_update_field_type_option(field_id, old_field)
.await?;
Ok(())
}
pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseViewEditor>> {
debug_assert!(!view_id.is_empty());
if let Some(editor) = self.editor_map.read().await.get(view_id) {
if let Some(editor) = self.editor_by_view_id.read().await.get(view_id) {
return Ok(editor.clone());
}
let mut editor_map = self.editor_map.write().await;
let mut editor_map = self.editor_by_view_id.try_write().map_err(|err| {
FlowyError::internal().with_context(format!(
"fail to acquire the lock of editor_by_view_id: {}",
err
))
})?;
let editor = Arc::new(
DatabaseViewEditor::new(
view_id.to_owned(),
self.database_view_data.clone(),
self.view_operation.clone(),
self.cell_cache.clone(),
)
.await?,

View File

@ -24,7 +24,6 @@ impl SelectOptionIds {
pub fn into_inner(self) -> Vec<String> {
self.0
}
pub fn to_cell_data(&self, field_type: FieldType) -> Cell {
new_cell_builder(field_type)
.insert_str_value(CELL_DATA, self.to_string())

View File

@ -33,7 +33,15 @@ pub trait TypeOption {
///
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
///
type CellData: TypeOptionCellData + ToString + Default + Send + Sync + Clone + Debug + 'static;
type CellData: for<'a> From<&'a Cell>
+ TypeOptionCellData
+ ToString
+ Default
+ Send
+ Sync
+ Clone
+ Debug
+ 'static;
/// Represents as the corresponding field type cell changeset.
/// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait.

View File

@ -113,16 +113,21 @@ where
+ Sync
+ 'static,
{
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
}
pub fn new_with_boxed(
inner: T,
cell_filter_cache: Option<CellFilterCache>,
cell_data_cache: Option<CellCache>,
) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(Self {
Self {
inner,
cell_data_cache,
cell_filter_cache,
}) as Box<dyn TypeOptionCellDataHandler>
}
.into_boxed()
}
}

View File

@ -21,7 +21,7 @@ use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResul
pub trait FilterDelegate: Send + Sync + 'static {
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>>;
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>;
fn get_field(&self, field_id: &str) -> Option<Field>;
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;

View File

@ -1,20 +1,22 @@
use collab_database::fields::Field;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Row, RowDetail};
use flowy_error::FlowyResult;
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
use crate::services::cell::DecodedCellData;
use crate::services::group::controller::MoveGroupRowContext;
use crate::services::field::TypeOption;
use crate::services::group::entities::GroupSetting;
use crate::services::group::{GroupChangesets, GroupData, GroupSettingChangeset};
use crate::services::group::{
GroupChangesets, GroupData, GroupSettingChangeset, MoveGroupRowContext,
};
/// Using polymorphism to provides the customs action for different group controller.
///
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
///
pub trait GroupCustomize: Send + Sync {
type CellData: DecodedCellData;
type GroupTypeOption: TypeOption;
/// Returns the a value of the cell if the cell data is not exist.
/// The default value is `None`
///
@ -26,13 +28,17 @@ pub trait GroupCustomize: Send + Sync {
}
/// Returns a bool value to determine whether the group should contain this cell or not.
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool;
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool;
fn create_or_delete_group_when_cell_changed(
&mut self,
_row_detail: &RowDetail,
_old_cell_data: Option<&Self::CellData>,
_cell_data: &Self::CellData,
_old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
Ok((None, None))
}
@ -43,16 +49,20 @@ pub trait GroupCustomize: Send + Sync {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB>;
/// Deletes the row from the group
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB>;
fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB>;
/// Move row from one group to another
fn move_row(
&mut self,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB>;
@ -60,13 +70,14 @@ pub trait GroupCustomize: Send + Sync {
fn delete_group_when_move_row(
&mut self,
_row: &Row,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> {
None
}
}
/// Defines the shared actions any group controller can perform.
#[async_trait]
pub trait GroupControllerOperation: Send + Sync {
/// The field that is used for grouping the rows
fn field_id(&self) -> &str;
@ -104,7 +115,10 @@ pub trait GroupControllerOperation: Send + Sync {
/// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()>;
async fn apply_group_changeset(
&mut self,
changeset: &GroupChangesets,
) -> FlowyResult<TypeOptionData>;
fn apply_group_configuration_setting_changeset(
&mut self,

View File

@ -3,10 +3,13 @@ use std::fmt::Formatter;
use std::marker::PhantomData;
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field;
use collab_database::rows::{Cell, RowId};
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use tracing::event;
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::Fut;
@ -27,6 +30,18 @@ pub trait GroupSettingWriter: Send + Sync + 'static {
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>>;
}
#[async_trait]
pub trait GroupTypeOptionCellOperation: Send + Sync + 'static {
async fn get_cell(&self, row_id: &RowId, field_id: &str) -> FlowyResult<Option<Cell>>;
async fn update_cell(
&self,
view_id: &str,
row_id: &RowId,
field_id: &str,
cell: Cell,
) -> FlowyResult<()>;
}
impl<T> std::fmt::Display for GroupContext<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.group_by_id.iter().for_each(|(_, group)| {
@ -64,7 +79,6 @@ pub struct GroupContext<C> {
/// A reader that implement the [GroupSettingReader] trait
///
#[allow(dead_code)]
reader: Arc<dyn GroupSettingReader>,
/// A writer that implement the [GroupSettingWriter] trait is used to save the
@ -84,6 +98,7 @@ where
reader: Arc<dyn GroupSettingReader>,
writer: Arc<dyn GroupSettingWriter>,
) -> FlowyResult<Self> {
event!(tracing::Level::TRACE, "GroupContext::new");
let setting = match reader.get_group_setting(&view_id).await {
None => {
let default_configuration = default_group_setting(&field);
@ -356,7 +371,7 @@ where
}
}
pub(crate) fn update_group(&mut self, group_changeset: GroupChangeset) -> FlowyResult<()> {
pub(crate) fn update_group(&mut self, group_changeset: &GroupChangeset) -> FlowyResult<()> {
let update_group = self.mut_group(&group_changeset.group_id, |group| {
if let Some(visible) = group_changeset.visible {
group.visible = visible;

View File

@ -1,9 +1,10 @@
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Cells, Row, RowDetail, RowId};
use collab_database::rows::{Cells, Row, RowDetail};
use futures::executor::block_on;
use serde::de::DeserializeOwned;
use serde::Serialize;
@ -12,13 +13,16 @@ use flowy_error::FlowyResult;
use crate::entities::{
FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB,
};
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser};
use crate::services::field::{default_type_option_data_from_type, TypeOption, TypeOptionCellData};
use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
};
use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::{GroupData, GroupSetting};
use crate::services::group::{Group, GroupChangesets, GroupSettingChangeset};
use crate::services::group::{
GroupChangeset, GroupChangesets, GroupSettingChangeset, GroupsBuilder, MoveGroupRowContext,
};
// use collab_database::views::Group;
@ -32,108 +36,67 @@ use crate::services::group::{Group, GroupChangesets, GroupSettingChangeset};
///
pub trait GroupController: GroupControllerOperation + Send + Sync {
/// Called when the type option of the [Field] was updated.
fn did_update_field_type_option(&mut self, field: &Arc<Field>);
fn did_update_field_type_option(&mut self, field: &Field);
/// Called before the row was created.
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str);
/// Called after the row was created.
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str);
/// Update group name handler
fn update_group_name(&mut self, _group_id: &str, _group_name: &str) -> Option<TypeOptionData> {
None
}
}
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
pub trait GroupsBuilder {
type Context;
type TypeOptionType;
fn build(
field: &Field,
context: &Self::Context,
type_option: &Option<Self::TypeOptionType>,
) -> GeneratedGroups;
}
pub struct GeneratedGroups {
pub no_status_group: Option<Group>,
pub group_configs: Vec<GeneratedGroupConfig>,
}
pub struct GeneratedGroupConfig {
pub group: Group,
pub filter_content: String,
}
pub struct MoveGroupRowContext<'a> {
pub row_detail: &'a RowDetail,
pub row_changeset: &'a mut RowChangeset,
pub field: &'a Field,
pub to_group_id: &'a str,
pub to_row_id: Option<RowId>,
}
#[derive(Debug, Clone)]
pub struct RowChangeset {
pub row_id: RowId,
pub height: Option<i32>,
pub visibility: Option<bool>,
// Contains the key/value changes represents as the update of the cells. For example,
// if there is one cell was changed, then the `cell_by_field_id` will only have one key/value.
pub cell_by_field_id: HashMap<String, Cell>,
}
impl RowChangeset {
pub fn new(row_id: RowId) -> Self {
Self {
row_id,
height: None,
visibility: None,
cell_by_field_id: Default::default(),
}
}
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty()
}
#[async_trait]
pub trait GroupOperationInterceptor {
type GroupTypeOption: TypeOption;
async fn type_option_from_group_changeset(
&self,
changeset: &GroupChangeset,
type_option: &Self::GroupTypeOption,
view_id: &str,
) -> Option<TypeOptionData>;
}
/// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupsBuilder]
/// P: the parser that impl [CellProtobufBlobParser] for the CellBytes
pub struct BaseGroupController<C, T, G, P> {
pub struct BaseGroupController<C, T, G, P, I> {
pub grouping_field_id: String,
pub type_option: Option<T>,
pub type_option: T,
pub context: GroupContext<C>,
group_action_phantom: PhantomData<G>,
group_builder_phantom: PhantomData<G>,
cell_parser_phantom: PhantomData<P>,
pub operation_interceptor: I,
}
impl<C, T, G, P> BaseGroupController<C, T, G, P>
impl<C, T, G, P, I> BaseGroupController<C, T, G, P, I>
where
C: Serialize + DeserializeOwned,
T: From<TypeOptionData>,
G: GroupsBuilder<Context = GroupContext<C>, TypeOptionType = T>,
T: TypeOption + From<TypeOptionData> + Send + Sync,
G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
{
pub async fn new(
grouping_field: &Arc<Field>,
mut configuration: GroupContext<C>,
operation_interceptor: I,
) -> FlowyResult<Self> {
let field_type = FieldType::from(grouping_field.field_type);
let type_option = grouping_field.get_type_option::<T>(field_type);
let generated_groups = G::build(grouping_field, &configuration, &type_option);
let type_option = grouping_field
.get_type_option::<T>(&field_type)
.unwrap_or_else(|| T::from(default_type_option_data_from_type(&field_type)));
// TODO(nathan): remove block_on
let generated_groups = block_on(G::build(grouping_field, &configuration, &type_option));
let _ = configuration.init_groups(generated_groups)?;
Ok(Self {
grouping_field_id: grouping_field.id.clone(),
type_option,
context: configuration,
group_action_phantom: PhantomData,
group_builder_phantom: PhantomData,
cell_parser_phantom: PhantomData,
operation_interceptor,
})
}
@ -209,14 +172,15 @@ where
}
}
impl<C, T, G, P> GroupControllerOperation for BaseGroupController<C, T, G, P>
#[async_trait]
impl<C, T, G, P, I> GroupControllerOperation for BaseGroupController<C, T, G, P, I>
where
P: CellProtobufBlobParser,
C: Serialize + DeserializeOwned,
T: From<TypeOptionData>,
G: GroupsBuilder<Context = GroupContext<C>, TypeOptionType = T>,
Self: GroupCustomize<CellData = P::Object>,
P: CellProtobufBlobParser<Object = <T as TypeOption>::CellProtobufType>,
C: Serialize + DeserializeOwned + Sync + Send,
T: TypeOption + From<TypeOptionData> + Send + Sync,
G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
Self: GroupCustomize<GroupTypeOption = T>,
{
fn field_id(&self) -> &str {
&self.grouping_field_id
@ -232,7 +196,7 @@ where
}
#[tracing::instrument(level = "trace", skip_all, fields(row_count=%rows.len(), group_result))]
fn fill_groups(&mut self, rows: &[&RowDetail], field: &Field) -> FlowyResult<()> {
fn fill_groups(&mut self, rows: &[&RowDetail], _field: &Field) -> FlowyResult<()> {
for row_detail in rows {
let cell = match row_detail.row.cells.get(&self.grouping_field_id) {
None => self.placeholder_cell(),
@ -241,8 +205,7 @@ where
if let Some(cell) = cell {
let mut grouped_rows: Vec<GroupedRow> = vec![];
let cell_bytes = get_cell_protobuf(&cell, field, None);
let cell_data = cell_bytes.parser::<P>()?;
let cell_data = <T as TypeOption>::CellData::from(&cell);
for group in self.context.groups() {
if self.can_group(&group.filter_content, &cell_data) {
grouped_rows.push(GroupedRow {
@ -320,7 +283,7 @@ where
fn did_delete_delete_row(
&mut self,
row: &Row,
field: &Field,
_field: &Field,
) -> FlowyResult<DidMoveGroupRowResult> {
// if the cell_rev is none, then the row must in the default group.
let mut result = DidMoveGroupRowResult {
@ -328,9 +291,8 @@ where
row_changesets: vec![],
};
if let Some(cell) = row.cells.get(&self.grouping_field_id) {
let cell_bytes = get_cell_protobuf(cell, field, None);
let cell_data = cell_bytes.parser::<P>()?;
if !cell_data.is_empty() {
let cell_data = <T as TypeOption>::CellData::from(cell);
if !cell_data.is_cell_empty() {
tracing::error!("did_delete_delete_row {:?}", cell);
result.row_changesets = self.delete_row(row, &cell_data);
return Ok(result);
@ -380,13 +342,24 @@ where
Ok(None)
}
fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()> {
for group_changeset in changeset.update_groups {
if let Err(e) = self.context.update_group(group_changeset) {
tracing::error!("Failed to update group: {:?}", e);
async fn apply_group_changeset(
&mut self,
changeset: &GroupChangesets,
) -> FlowyResult<TypeOptionData> {
for group_changeset in changeset.changesets.iter() {
self.context.update_group(group_changeset)?;
}
let mut type_option_data = TypeOptionData::new();
for group_changeset in changeset.changesets.iter() {
if let Some(new_type_option_data) = self
.operation_interceptor
.type_option_from_group_changeset(group_changeset, &self.type_option, &self.context.view_id)
.await
{
type_option_data.extend(new_type_option_data);
}
}
Ok(())
Ok(type_option_data)
}
fn apply_group_configuration_setting_changeset(

View File

@ -1,20 +1,20 @@
use std::sync::Arc;
use collab_database::fields::Field;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
use crate::services::cell::insert_checkbox_cell;
use crate::services::field::{
CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOption, CHECK, UNCHECK,
CheckboxCellDataParser, CheckboxTypeOption, TypeOption, CHECK, UNCHECK,
};
use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
use crate::services::group::controller::{BaseGroupController, GroupController};
use crate::services::group::{
move_group_row, GeneratedGroupConfig, GeneratedGroups, Group, GroupChangeset,
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroups, Group};
#[derive(Default, Serialize, Deserialize)]
pub struct CheckboxGroupConfiguration {
@ -24,14 +24,15 @@ pub struct CheckboxGroupConfiguration {
pub type CheckboxGroupController = BaseGroupController<
CheckboxGroupConfiguration,
CheckboxTypeOption,
CheckboxGroupGenerator,
CheckboxGroupBuilder,
CheckboxCellDataParser,
CheckboxGroupOperationInterceptorImpl,
>;
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfiguration>;
impl GroupCustomize for CheckboxGroupController {
type CellData = CheckboxCellData;
type GroupTypeOption = CheckboxTypeOption;
fn placeholder_cell(&self) -> Option<Cell> {
Some(
new_cell_builder(FieldType::Checkbox)
@ -40,7 +41,11 @@ impl GroupCustomize for CheckboxGroupController {
)
}
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
if cell_data.is_check() {
content == CHECK
} else {
@ -51,7 +56,7 @@ impl GroupCustomize for CheckboxGroupController {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
@ -100,7 +105,11 @@ impl GroupCustomize for CheckboxGroupController {
changesets
}
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -118,7 +127,7 @@ impl GroupCustomize for CheckboxGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -132,7 +141,7 @@ impl GroupCustomize for CheckboxGroupController {
}
impl GroupController for CheckboxGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {
fn did_update_field_type_option(&mut self, _field: &Field) {
// Do nothing
}
@ -154,15 +163,16 @@ impl GroupController for CheckboxGroupController {
}
}
pub struct CheckboxGroupGenerator();
impl GroupsBuilder for CheckboxGroupGenerator {
pub struct CheckboxGroupBuilder();
#[async_trait]
impl GroupsBuilder for CheckboxGroupBuilder {
type Context = CheckboxGroupContext;
type TypeOptionType = CheckboxTypeOption;
type GroupTypeOption = CheckboxTypeOption;
fn build(
async fn build(
_field: &Field,
_context: &Self::Context,
_type_option: &Option<Self::TypeOptionType>,
_type_option: &Self::GroupTypeOption,
) -> GeneratedGroups {
let check_group = GeneratedGroupConfig {
group: Group::new(CHECK.to_string(), "".to_string()),
@ -180,3 +190,18 @@ impl GroupsBuilder for CheckboxGroupGenerator {
}
}
}
pub struct CheckboxGroupOperationInterceptorImpl {}
#[async_trait]
impl GroupOperationInterceptor for CheckboxGroupOperationInterceptorImpl {
type GroupTypeOption = CheckboxTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}

View File

@ -1,13 +1,13 @@
use std::format;
use std::str::FromStr;
use std::sync::Arc;
use async_trait::async_trait;
use chrono::{
DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveDateTime, Offset, TimeZone,
};
use chrono_tz::Tz;
use collab_database::database::timestamp;
use collab_database::fields::Field;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
@ -15,18 +15,16 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use flowy_error::FlowyResult;
use crate::entities::{
DateCellDataPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
RowMetaPB,
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
};
use crate::services::cell::insert_date_cell;
use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption};
use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption, TypeOption};
use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::controller::{BaseGroupController, GroupController};
use crate::services::group::{
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
GroupChangeset, GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
};
pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
@ -63,14 +61,15 @@ pub enum DateCondition {
pub type DateGroupController = BaseGroupController<
DateGroupConfiguration,
DateTypeOption,
DateGroupGenerator,
DateGroupBuilder,
DateCellDataParser,
DateGroupOperationInterceptorImpl,
>;
pub type DateGroupContext = GroupContext<DateGroupConfiguration>;
impl GroupCustomize for DateGroupController {
type CellData = DateCellDataPB;
type GroupTypeOption = DateTypeOption;
fn placeholder_cell(&self) -> Option<Cell> {
Some(
@ -80,47 +79,48 @@ impl GroupCustomize for DateGroupController {
)
}
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
content
== group_id(
&cell_data.into(),
self.type_option.as_ref(),
cell_data,
&self.type_option,
&self.context.get_setting_content(),
)
}
fn create_or_delete_group_when_cell_changed(
&mut self,
row_detail: &RowDetail,
old_cell_data: Option<&Self::CellData>,
cell_data: &Self::CellData,
_row_detail: &RowDetail,
_old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
let setting_content = self.context.get_setting_content();
let mut inserted_group = None;
if self
.context
.get_group(&group_id(
&cell_data.into(),
self.type_option.as_ref(),
&_cell_data.into(),
&self.type_option,
&setting_content,
))
.is_none()
{
let group = make_group_from_date_cell(
&cell_data.into(),
self.type_option.as_ref(),
&setting_content,
);
let group =
make_group_from_date_cell(&_cell_data.into(), &self.type_option, &setting_content);
let mut new_group = self.context.add_new_group(group)?;
new_group.group.rows.push(RowMetaPB::from(row_detail));
new_group.group.rows.push(RowMetaPB::from(_row_detail));
inserted_group = Some(new_group);
}
// Delete the old group if there are no rows in that group
let deleted_group = match old_cell_data.and_then(|old_cell_data| {
let deleted_group = match _old_cell_data.and_then(|old_cell_data| {
self.context.get_group(&group_id(
&old_cell_data.into(),
self.type_option.as_ref(),
&self.type_option,
&setting_content,
))
}) {
@ -148,19 +148,13 @@ impl GroupCustomize for DateGroupController {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
let setting_content = self.context.get_setting_content();
self.context.iter_mut_status_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
if group.id
== group_id(
&cell_data.into(),
self.type_option.as_ref(),
&setting_content,
)
{
if group.id == group_id(&cell_data.into(), &self.type_option, &setting_content) {
if !group.contains_row(&row_detail.row.id) {
changeset
.inserted_rows
@ -181,7 +175,11 @@ impl GroupCustomize for DateGroupController {
changesets
}
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -199,7 +197,7 @@ impl GroupCustomize for DateGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -214,13 +212,13 @@ impl GroupCustomize for DateGroupController {
fn delete_group_when_move_row(
&mut self,
_row: &Row,
cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> {
let mut deleted_group = None;
let setting_content = self.context.get_setting_content();
if let Some((_, group)) = self.context.get_group(&group_id(
&cell_data.into(),
self.type_option.as_ref(),
&_cell_data.into(),
&self.type_option,
&setting_content,
)) {
if group.rows.len() == 1 {
@ -237,7 +235,7 @@ impl GroupCustomize for DateGroupController {
}
impl GroupController for DateGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {}
fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) {
@ -257,18 +255,19 @@ impl GroupController for DateGroupController {
}
}
pub struct DateGroupGenerator();
impl GroupsBuilder for DateGroupGenerator {
pub struct DateGroupBuilder();
#[async_trait]
impl GroupsBuilder for DateGroupBuilder {
type Context = DateGroupContext;
type TypeOptionType = DateTypeOption;
type GroupTypeOption = DateTypeOption;
fn build(
async fn build(
field: &Field,
context: &Self::Context,
type_option: &Option<Self::TypeOptionType>,
type_option: &Self::GroupTypeOption,
) -> GeneratedGroups {
// Read all the cells for the grouping field
let cells = futures::executor::block_on(context.get_all_cells());
let cells = context.get_all_cells().await;
// Generate the groups
let mut group_configs: Vec<GeneratedGroupConfig> = cells
@ -276,8 +275,7 @@ impl GroupsBuilder for DateGroupGenerator {
.flat_map(|value| value.into_date_field_cell_data())
.filter(|cell| cell.timestamp.is_some())
.map(|cell| {
let group =
make_group_from_date_cell(&cell, type_option.as_ref(), &context.get_setting_content());
let group = make_group_from_date_cell(&cell, type_option, &context.get_setting_content());
GeneratedGroupConfig {
filter_content: group.id.clone(),
group,
@ -296,7 +294,7 @@ impl GroupsBuilder for DateGroupGenerator {
fn make_group_from_date_cell(
cell_data: &DateCellData,
type_option: Option<&DateTypeOption>,
type_option: &DateTypeOption,
setting_content: &str,
) -> Group {
let group_id = group_id(cell_data, type_option, setting_content);
@ -310,11 +308,9 @@ const GROUP_ID_DATE_FORMAT: &str = "%Y/%m/%d";
fn group_id(
cell_data: &DateCellData,
type_option: Option<&DateTypeOption>,
type_option: &DateTypeOption,
setting_content: &str,
) -> String {
let binding = DateTypeOption::default();
let type_option = type_option.unwrap_or(&binding);
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
let date_time = date_time_from_timestamp(cell_data.timestamp, &type_option.timezone_id);
@ -373,11 +369,9 @@ fn group_id(
fn group_name_from_id(
group_id: &str,
type_option: Option<&DateTypeOption>,
type_option: &DateTypeOption,
setting_content: &str,
) -> String {
let binding = DateTypeOption::default();
let type_option = type_option.unwrap_or(&binding);
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
let date = NaiveDate::parse_from_str(group_id, GROUP_ID_DATE_FORMAT).unwrap();
@ -449,6 +443,21 @@ fn date_time_from_timestamp(timestamp: Option<i64>, timezone_id: &str) -> DateTi
}
}
pub struct DateGroupOperationInterceptorImpl {}
#[async_trait]
impl GroupOperationInterceptor for DateGroupOperationInterceptorImpl {
type GroupTypeOption = DateTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}
#[cfg(test)]
mod tests {
use std::vec;
@ -582,16 +591,11 @@ mod tests {
];
for (i, test) in tests.iter().enumerate() {
let group_id = group_id(
&test.cell_data,
Some(test.type_option),
&test.setting_content,
);
let group_id = group_id(&test.cell_data, test.type_option, &test.setting_content);
assert_eq!(test.exp_group_id, group_id, "test {}", i);
if !test.exp_group_name.is_empty() {
let group_name =
group_name_from_id(&group_id, Some(test.type_option), &test.setting_content);
let group_name = group_name_from_id(&group_id, test.type_option, &test.setting_content);
assert_eq!(test.exp_group_name, group_name, "test {}", i);
}
}

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use collab_database::fields::Field;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowDetail};
use flowy_error::FlowyResult;
@ -40,6 +41,7 @@ impl DefaultGroupController {
}
}
#[async_trait]
impl GroupControllerOperation for DefaultGroupController {
fn field_id(&self) -> &str {
&self.field_id
@ -102,8 +104,11 @@ impl GroupControllerOperation for DefaultGroupController {
Ok(None)
}
fn apply_group_setting_changeset(&mut self, _changeset: GroupChangesets) -> FlowyResult<()> {
Ok(())
async fn apply_group_changeset(
&mut self,
_changeset: &GroupChangesets,
) -> FlowyResult<TypeOptionData> {
Ok(TypeOptionData::default())
}
fn apply_group_configuration_setting_changeset(
@ -115,7 +120,7 @@ impl GroupControllerOperation for DefaultGroupController {
}
impl GroupController for DefaultGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {
fn did_update_field_type_option(&mut self, _field: &Field) {
// Do nothing
}

View File

@ -1,21 +1,20 @@
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::entities::{FieldType, GroupRowsNotificationPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{
MultiSelectTypeOption, SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction,
TypeOption,
};
use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::controller::{BaseGroupController, GroupController};
use crate::services::group::{
add_or_remove_select_option_row, generate_select_option_groups, make_no_status_group,
move_group_row, remove_select_option_row, GeneratedGroups, GroupContext,
move_group_row, remove_select_option_row, GeneratedGroups, GroupChangeset, GroupContext,
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
};
#[derive(Default, Serialize, Deserialize)]
@ -28,18 +27,20 @@ pub type MultiSelectOptionGroupContext = GroupContext<MultiSelectGroupConfigurat
pub type MultiSelectGroupController = BaseGroupController<
MultiSelectGroupConfiguration,
MultiSelectTypeOption,
MultiSelectGroupGenerator,
MultiSelectGroupBuilder,
SelectOptionCellDataParser,
MultiSelectGroupOperationInterceptorImpl,
>;
impl GroupCustomize for MultiSelectGroupController {
type CellData = SelectOptionCellDataPB;
type GroupTypeOption = MultiSelectTypeOption;
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
cell_data
.select_options
.iter()
.any(|option| option.id == content)
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
cell_data.iter().any(|option_id| option_id == content)
}
fn placeholder_cell(&self) -> Option<Cell> {
@ -53,7 +54,7 @@ impl GroupCustomize for MultiSelectGroupController {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
@ -64,7 +65,11 @@ impl GroupCustomize for MultiSelectGroupController {
changesets
}
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row) {
@ -76,7 +81,7 @@ impl GroupCustomize for MultiSelectGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -90,7 +95,7 @@ impl GroupCustomize for MultiSelectGroupController {
}
impl GroupController for MultiSelectGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {}
fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) {
@ -107,49 +112,56 @@ impl GroupController for MultiSelectGroupController {
group.add_row(row_detail.clone())
}
}
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
match &self.type_option {
Some(type_option) => {
let select_option = type_option
.options
.iter()
.find(|option| option.id == group_id)
.unwrap();
let new_select_option = SelectOption {
name: group_name.to_owned(),
..select_option.to_owned()
};
let mut new_type_option = type_option.clone();
new_type_option.insert_option(new_select_option);
Some(new_type_option.to_type_option_data())
},
None => None,
}
}
}
pub struct MultiSelectGroupGenerator;
impl GroupsBuilder for MultiSelectGroupGenerator {
pub struct MultiSelectGroupBuilder;
#[async_trait]
impl GroupsBuilder for MultiSelectGroupBuilder {
type Context = MultiSelectOptionGroupContext;
type TypeOptionType = MultiSelectTypeOption;
type GroupTypeOption = MultiSelectTypeOption;
fn build(
async fn build(
field: &Field,
_context: &Self::Context,
type_option: &Option<Self::TypeOptionType>,
type_option: &Self::GroupTypeOption,
) -> GeneratedGroups {
let group_configs = match type_option {
None => vec![],
Some(type_option) => generate_select_option_groups(&field.id, &type_option.options),
};
let group_configs = generate_select_option_groups(&field.id, &type_option.options);
GeneratedGroups {
no_status_group: Some(make_no_status_group(field)),
group_configs,
}
}
}
pub struct MultiSelectGroupOperationInterceptorImpl;
#[async_trait]
impl GroupOperationInterceptor for MultiSelectGroupOperationInterceptorImpl {
type GroupTypeOption = MultiSelectTypeOption;
#[tracing::instrument(level = "trace", skip_all)]
async fn type_option_from_group_changeset(
&self,
changeset: &GroupChangeset,
type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
if let Some(name) = &changeset.name {
let mut new_type_option = type_option.clone();
let select_option = type_option
.options
.iter()
.find(|option| option.id == changeset.group_id)
.unwrap();
let new_select_option = SelectOption {
name: name.to_owned(),
..select_option.to_owned()
};
new_type_option.insert_option(new_select_option);
return Some(new_type_option.into());
}
None
}
}

View File

@ -1,21 +1,22 @@
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::entities::{FieldType, GroupRowsNotificationPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{
SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction, SingleSelectTypeOption,
TypeOption,
};
use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::controller::{BaseGroupController, GroupController};
use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::entities::GroupData;
use crate::services::group::{make_no_status_group, GeneratedGroups, GroupContext};
use crate::services::group::{
make_no_status_group, GeneratedGroups, GroupChangeset, GroupContext, GroupOperationInterceptor,
GroupsBuilder, MoveGroupRowContext,
};
#[derive(Default, Serialize, Deserialize)]
pub struct SingleSelectGroupConfiguration {
@ -28,17 +29,19 @@ pub type SingleSelectOptionGroupContext = GroupContext<SingleSelectGroupConfigur
pub type SingleSelectGroupController = BaseGroupController<
SingleSelectGroupConfiguration,
SingleSelectTypeOption,
SingleSelectGroupGenerator,
SingleSelectGroupBuilder,
SelectOptionCellDataParser,
SingleSelectGroupOperationInterceptorImpl,
>;
impl GroupCustomize for SingleSelectGroupController {
type CellData = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
cell_data
.select_options
.iter()
.any(|option| option.id == content)
type GroupTypeOption = SingleSelectTypeOption;
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
cell_data.iter().any(|option_id| option_id == content)
}
fn placeholder_cell(&self) -> Option<Cell> {
@ -52,7 +55,7 @@ impl GroupCustomize for SingleSelectGroupController {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
@ -63,7 +66,11 @@ impl GroupCustomize for SingleSelectGroupController {
changesets
}
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row) {
@ -75,7 +82,7 @@ impl GroupCustomize for SingleSelectGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -89,7 +96,7 @@ impl GroupCustomize for SingleSelectGroupController {
}
impl GroupController for SingleSelectGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {}
fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
let group: Option<&mut GroupData> = self.context.get_mut_group(group_id);
@ -107,44 +114,19 @@ impl GroupController for SingleSelectGroupController {
group.add_row(row_detail.clone())
}
}
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
match &self.type_option {
Some(type_option) => {
let select_option = type_option
.options
.iter()
.find(|option| option.id == group_id)
.unwrap();
let new_select_option = SelectOption {
name: group_name.to_owned(),
..select_option.to_owned()
};
let mut new_type_option = type_option.clone();
new_type_option.insert_option(new_select_option);
Some(new_type_option.to_type_option_data())
},
None => None,
}
}
}
pub struct SingleSelectGroupGenerator();
impl GroupsBuilder for SingleSelectGroupGenerator {
pub struct SingleSelectGroupBuilder();
#[async_trait]
impl GroupsBuilder for SingleSelectGroupBuilder {
type Context = SingleSelectOptionGroupContext;
type TypeOptionType = SingleSelectTypeOption;
fn build(
type GroupTypeOption = SingleSelectTypeOption;
async fn build(
field: &Field,
_context: &Self::Context,
type_option: &Option<Self::TypeOptionType>,
type_option: &Self::GroupTypeOption,
) -> GeneratedGroups {
let group_configs = match type_option {
None => vec![],
Some(type_option) => generate_select_option_groups(&field.id, &type_option.options),
};
let group_configs = generate_select_option_groups(&field.id, &type_option.options);
GeneratedGroups {
no_status_group: Some(make_no_status_group(field)),
@ -152,3 +134,36 @@ impl GroupsBuilder for SingleSelectGroupGenerator {
}
}
}
pub struct SingleSelectGroupOperationInterceptorImpl;
#[async_trait]
impl GroupOperationInterceptor for SingleSelectGroupOperationInterceptorImpl {
type GroupTypeOption = SingleSelectTypeOption;
#[tracing::instrument(level = "trace", skip_all)]
async fn type_option_from_group_changeset(
&self,
changeset: &GroupChangeset,
type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
if let Some(name) = &changeset.name {
let mut new_type_option = type_option.clone();
let select_option = type_option
.options
.iter()
.find(|option| option.id == changeset.group_id)
.unwrap();
let new_select_option = SelectOption {
name: name.to_owned(),
..select_option.to_owned()
};
new_type_option.insert_option(new_select_option);
return Some(new_type_option.into());
}
None
}
}

View File

@ -8,9 +8,8 @@ use crate::entities::{
use crate::services::cell::{
insert_checkbox_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
};
use crate::services::field::{SelectOption, CHECK};
use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
use crate::services::field::{SelectOption, SelectOptionIds, CHECK};
use crate::services::group::{GeneratedGroupConfig, Group, GroupData, MoveGroupRowContext};
pub fn add_or_remove_select_option_row(
group: &mut GroupData,
@ -52,12 +51,12 @@ pub fn add_or_remove_select_option_row(
pub fn remove_select_option_row(
group: &mut GroupData,
cell_data: &SelectOptionCellDataPB,
cell_data: &SelectOptionIds,
row: &Row,
) -> Option<GroupRowsNotificationPB> {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
cell_data.select_options.iter().for_each(|option| {
if option.id == group.id && group.contains_row(&row.id) {
cell_data.iter().for_each(|option_id| {
if option_id == &group.id && group.contains_row(&row.id) {
group.remove_row(&row.id);
changeset.deleted_rows.push(row.id.clone().into_inner());
}

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use collab_database::fields::Field;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize};
@ -8,17 +9,16 @@ use flowy_error::FlowyResult;
use crate::entities::{
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
URLCellDataPB,
};
use crate::services::cell::insert_url_cell;
use crate::services::field::{URLCellData, URLCellDataParser, URLTypeOption};
use crate::services::field::{TypeOption, URLCellData, URLCellDataParser, URLTypeOption};
use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::controller::{BaseGroupController, GroupController};
use crate::services::group::{
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
GroupChangeset, GroupOperationInterceptor, GroupTypeOptionCellOperation, GroupsBuilder,
MoveGroupRowContext,
};
#[derive(Default, Serialize, Deserialize)]
@ -26,13 +26,18 @@ pub struct URLGroupConfiguration {
pub hide_empty: bool,
}
pub type URLGroupController =
BaseGroupController<URLGroupConfiguration, URLTypeOption, URLGroupGenerator, URLCellDataParser>;
pub type URLGroupController = BaseGroupController<
URLGroupConfiguration,
URLTypeOption,
URLGroupGenerator,
URLCellDataParser,
URLGroupOperationInterceptorImpl,
>;
pub type URLGroupContext = GroupContext<URLGroupConfiguration>;
impl GroupCustomize for URLGroupController {
type CellData = URLCellDataPB;
type GroupTypeOption = URLTypeOption;
fn placeholder_cell(&self) -> Option<Cell> {
Some(
@ -42,15 +47,19 @@ impl GroupCustomize for URLGroupController {
)
}
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
cell_data.content == content
fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
cell_data.data == content
}
fn create_or_delete_group_when_cell_changed(
&mut self,
row_detail: &RowDetail,
_old_cell_data: Option<&Self::CellData>,
_cell_data: &Self::CellData,
_row_detail: &RowDetail,
_old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
// Just return if the group with this url already exists
let mut inserted_group = None;
@ -58,7 +67,7 @@ impl GroupCustomize for URLGroupController {
let cell_data: URLCellData = _cell_data.clone().into();
let group = make_group_from_url_cell(&cell_data);
let mut new_group = self.context.add_new_group(group)?;
new_group.group.rows.push(RowMetaPB::from(row_detail));
new_group.group.rows.push(RowMetaPB::from(_row_detail));
inserted_group = Some(new_group);
}
@ -90,7 +99,7 @@ impl GroupCustomize for URLGroupController {
fn add_or_remove_row_when_cell_changed(
&mut self,
row_detail: &RowDetail,
cell_data: &Self::CellData,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| {
@ -116,7 +125,11 @@ impl GroupCustomize for URLGroupController {
changesets
}
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -134,7 +147,7 @@ impl GroupCustomize for URLGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -145,11 +158,10 @@ impl GroupCustomize for URLGroupController {
});
group_changeset
}
fn delete_group_when_move_row(
&mut self,
_row: &Row,
_cell_data: &Self::CellData,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> {
let mut deleted_group = None;
if let Some((_, group)) = self.context.get_group(&_cell_data.content) {
@ -167,7 +179,7 @@ impl GroupCustomize for URLGroupController {
}
impl GroupController for URLGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {}
fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) {
@ -187,17 +199,18 @@ impl GroupController for URLGroupController {
}
pub struct URLGroupGenerator();
#[async_trait]
impl GroupsBuilder for URLGroupGenerator {
type Context = URLGroupContext;
type TypeOptionType = URLTypeOption;
type GroupTypeOption = URLTypeOption;
fn build(
async fn build(
field: &Field,
context: &Self::Context,
_type_option: &Option<Self::TypeOptionType>,
_type_option: &Self::GroupTypeOption,
) -> GeneratedGroups {
// Read all the cells for the grouping field
let cells = futures::executor::block_on(context.get_all_cells());
let cells = context.get_all_cells().await;
// Generate the groups
let group_configs = cells
@ -223,3 +236,21 @@ fn make_group_from_url_cell(cell: &URLCellData) -> Group {
let group_name = cell.data.clone();
Group::new(group_id, group_name)
}
pub struct URLGroupOperationInterceptorImpl {
#[allow(dead_code)]
pub(crate) cell_writer: Arc<dyn GroupTypeOptionCellOperation>,
}
#[async_trait::async_trait]
impl GroupOperationInterceptor for URLGroupOperationInterceptorImpl {
type GroupTypeOption = URLTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}

View File

@ -20,7 +20,13 @@ pub struct GroupSettingChangeset {
}
pub struct GroupChangesets {
pub update_groups: Vec<GroupChangeset>,
pub changesets: Vec<GroupChangeset>,
}
impl From<Vec<GroupChangeset>> for GroupChangesets {
fn from(changesets: Vec<GroupChangeset>) -> Self {
Self { changesets }
}
}
#[derive(Clone, Default, Debug)]

View File

@ -1,19 +1,82 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field;
use collab_database::rows::RowDetail;
use collab_database::rows::{Cell, RowDetail, RowId};
use collab_database::views::DatabaseLayout;
use flowy_error::FlowyResult;
use crate::entities::FieldType;
use crate::services::field::TypeOption;
use crate::services::group::{
CheckboxGroupContext, CheckboxGroupController, DateGroupContext, DateGroupController,
DefaultGroupController, Group, GroupController, GroupSetting, GroupSettingReader,
GroupSettingWriter, MultiSelectGroupController, MultiSelectOptionGroupContext,
SingleSelectGroupController, SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
CheckboxGroupContext, CheckboxGroupController, CheckboxGroupOperationInterceptorImpl,
DateGroupContext, DateGroupController, DateGroupOperationInterceptorImpl, DefaultGroupController,
Group, GroupController, GroupSetting, GroupSettingReader, GroupSettingWriter,
GroupTypeOptionCellOperation, MultiSelectGroupController,
MultiSelectGroupOperationInterceptorImpl, MultiSelectOptionGroupContext,
SingleSelectGroupController, SingleSelectGroupOperationInterceptorImpl,
SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
URLGroupOperationInterceptorImpl,
};
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
#[async_trait]
pub trait GroupsBuilder: Send + Sync + 'static {
type Context;
type GroupTypeOption: TypeOption;
async fn build(
field: &Field,
context: &Self::Context,
type_option: &Self::GroupTypeOption,
) -> GeneratedGroups;
}
pub struct GeneratedGroups {
pub no_status_group: Option<Group>,
pub group_configs: Vec<GeneratedGroupConfig>,
}
pub struct GeneratedGroupConfig {
pub group: Group,
pub filter_content: String,
}
pub struct MoveGroupRowContext<'a> {
pub row_detail: &'a RowDetail,
pub row_changeset: &'a mut RowChangeset,
pub field: &'a Field,
pub to_group_id: &'a str,
pub to_row_id: Option<RowId>,
}
#[derive(Debug, Clone)]
pub struct RowChangeset {
pub row_id: RowId,
pub height: Option<i32>,
pub visibility: Option<bool>,
// Contains the key/value changes represents as the update of the cells. For example,
// if there is one cell was changed, then the `cell_by_field_id` will only have one key/value.
pub cell_by_field_id: HashMap<String, Cell>,
}
impl RowChangeset {
pub fn new(row_id: RowId) -> Self {
Self {
row_id,
height: None,
visibility: None,
cell_by_field_id: Default::default(),
}
}
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty()
}
}
/// Returns a group controller.
///
/// Each view can be grouped by one field, each field has its own group controller.
@ -31,16 +94,18 @@ use crate::services::group::{
fields(grouping_field_id=%grouping_field.id, grouping_field_type)
err
)]
pub async fn make_group_controller<R, W>(
pub async fn make_group_controller<R, W, TW>(
view_id: String,
grouping_field: Arc<Field>,
row_details: Vec<Arc<RowDetail>>,
setting_reader: R,
setting_writer: W,
type_option_cell_writer: TW,
) -> FlowyResult<Box<dyn GroupController>>
where
R: GroupSettingReader,
W: GroupSettingWriter,
TW: GroupTypeOptionCellOperation,
{
let grouping_field_type = FieldType::from(grouping_field.field_type);
tracing::Span::current().record("grouping_field", &grouping_field_type.default_name());
@ -48,6 +113,7 @@ where
let mut group_controller: Box<dyn GroupController>;
let configuration_reader = Arc::new(setting_reader);
let configuration_writer = Arc::new(setting_writer);
let type_option_cell_writer = Arc::new(type_option_cell_writer);
match grouping_field_type {
FieldType::SingleSelect => {
@ -58,7 +124,10 @@ where
configuration_writer,
)
.await?;
let controller = SingleSelectGroupController::new(&grouping_field, configuration).await?;
let operation_interceptor = SingleSelectGroupOperationInterceptorImpl;
let controller =
SingleSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
.await?;
group_controller = Box::new(controller);
},
FieldType::MultiSelect => {
@ -69,7 +138,10 @@ where
configuration_writer,
)
.await?;
let controller = MultiSelectGroupController::new(&grouping_field, configuration).await?;
let operation_interceptor = MultiSelectGroupOperationInterceptorImpl;
let controller =
MultiSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
.await?;
group_controller = Box::new(controller);
},
FieldType::Checkbox => {
@ -80,7 +152,9 @@ where
configuration_writer,
)
.await?;
let controller = CheckboxGroupController::new(&grouping_field, configuration).await?;
let operation_interceptor = CheckboxGroupOperationInterceptorImpl {};
let controller =
CheckboxGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller);
},
FieldType::URL => {
@ -91,7 +165,11 @@ where
configuration_writer,
)
.await?;
let controller = URLGroupController::new(&grouping_field, configuration).await?;
let operation_interceptor = URLGroupOperationInterceptorImpl {
cell_writer: type_option_cell_writer,
};
let controller =
URLGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller);
},
FieldType::DateTime => {
@ -102,7 +180,9 @@ where
configuration_writer,
)
.await?;
let controller = DateGroupController::new(&grouping_field, configuration).await?;
let operation_interceptor = DateGroupOperationInterceptorImpl {};
let controller =
DateGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller);
},
_ => {

View File

@ -26,7 +26,7 @@ pub trait SortDelegate: Send + Sync {
fn get_sort(&self, view_id: &str, sort_id: &str) -> Fut<Option<Arc<Sort>>>;
/// Returns all the rows after applying grid's filter
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>;
fn get_field(&self, field_id: &str) -> Option<Field>;
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
}

View File

@ -10,7 +10,6 @@ use flowy_database2::services::field::{
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
SingleSelectTypeOption,
};
use flowy_database2::services::group::GroupSettingChangeset;
use lib_infra::util::timestamp;
use crate::database::database_editor::DatabaseEditorTest;
@ -68,12 +67,6 @@ pub enum GroupScript {
group_id: String,
group_name: String,
},
AssertGroupConfiguration {
hide_ungrouped: bool,
},
UpdateGroupConfiguration {
hide_ungrouped: Option<bool>,
},
}
pub struct DatabaseGroupTest {
@ -276,25 +269,6 @@ impl DatabaseGroupTest {
assert_eq!(group_id, group.group_id, "group index: {}", group_index);
assert_eq!(group_name, group.group_name, "group index: {}", group_index);
},
GroupScript::AssertGroupConfiguration { hide_ungrouped } => {
let group_configuration = self
.editor
.get_group_configuration_settings(&self.view_id)
.await
.unwrap();
let group_configuration = group_configuration.get(0).unwrap();
assert_eq!(group_configuration.hide_ungrouped, hide_ungrouped);
},
GroupScript::UpdateGroupConfiguration { hide_ungrouped } => {
self
.editor
.update_group_configuration_setting(
&self.view_id,
GroupSettingChangeset { hide_ungrouped },
)
.await
.unwrap();
},
}
}

View File

@ -12,6 +12,7 @@ bytes = "1.4"
anyhow = "1.0"
thiserror = "1.0"
validator = "0.16.0"
tokio = { version = "1.0", features = ["sync"]}
fancy-regex = { version = "0.11.0" }
lib-dispatch = { workspace = true, optional = true }

View File

@ -157,3 +157,9 @@ impl From<fancy_regex::Error> for FlowyError {
FlowyError::internal().with_context(e)
}
}
impl From<tokio::sync::oneshot::error::RecvError> for FlowyError {
fn from(e: tokio::sync::oneshot::error::RecvError) -> Self {
FlowyError::internal().with_context(e)
}
}