chore: per-view field settings (#3199)

* chore: field-setting entities-events-notifications

* chore: update field settings

* chore: add tests

* chore: add docs

* chore: use an enum for field visibility

* chore: clippy warnings

* fix: deps fields

* chore: collab merge main

* chore: collab ref

* test: fix tests

* fix: tauri bump collab rev
This commit is contained in:
Richard Shiue 2023-08-27 22:31:32 +08:00 committed by GitHub
parent 6634a0ecb3
commit f0e4f3db61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 737 additions and 61 deletions

View File

@ -34,15 +34,15 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
#collab = { path = "../../../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" }

View File

@ -120,7 +120,7 @@ checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"collab",
@ -611,7 +611,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"bytes",
@ -629,7 +629,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"bytes",
"collab-sync",
@ -647,7 +647,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"async-trait",
@ -664,6 +664,8 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"strum",
"strum_macros 0.25.2",
"thiserror",
"tokio",
"tokio-stream",
@ -674,7 +676,7 @@ dependencies = [
[[package]]
name = "collab-define"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"uuid",
]
@ -682,7 +684,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"proc-macro2",
"quote",
@ -694,7 +696,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"collab",
@ -713,7 +715,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"chrono",
@ -733,7 +735,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"bincode",
"chrono",
@ -753,7 +755,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"async-trait",
@ -782,7 +784,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"bytes",
"collab",
@ -804,7 +806,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=b1f6737#b1f67375e39c67e32c502b2968749bedf61d6a46"
dependencies = [
"anyhow",
"collab",
@ -1351,7 +1353,7 @@ dependencies = [
"flowy-sqlite",
"lib-dispatch",
"protobuf",
"strum_macros",
"strum_macros 0.21.1",
]
[[package]]
@ -1438,7 +1440,7 @@ dependencies = [
"serde_json",
"serde_repr",
"strum",
"strum_macros",
"strum_macros 0.25.2",
"tokio",
"tracing",
"url",
@ -1492,7 +1494,7 @@ dependencies = [
"protobuf",
"serde",
"serde_json",
"strum_macros",
"strum_macros 0.21.1",
"tempfile",
"tokio",
"tokio-stream",
@ -1571,7 +1573,7 @@ dependencies = [
"nanoid",
"parking_lot 0.12.1",
"protobuf",
"strum_macros",
"strum_macros 0.21.1",
"tokio",
"tokio-stream",
"tracing",
@ -1771,7 +1773,7 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"strum_macros",
"strum_macros 0.21.1",
"tokio",
"tracing",
"unicode-segmentation",
@ -2088,6 +2090,12 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -4210,9 +4218,9 @@ dependencies = [
[[package]]
name = "strum"
version = "0.21.0"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
@ -4220,12 +4228,25 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"heck 0.3.3",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "strum_macros"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.27",
]
[[package]]
name = "subtle"
version = "2.4.1"

View File

@ -39,14 +39,14 @@ opt-level = 3
incremental = false
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "cff1b9" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b1f6737" }
#collab = { path = "../AppFlowy-Collab/collab" }
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

View File

@ -42,8 +42,8 @@ async-trait = "0.1"
chrono-tz = "0.8.2"
csv = "1.1.6"
strum = "0.21"
strum_macros = "0.21"
strum = "0.25"
strum_macros = "0.25"
[dev-dependencies]
flowy-test = { path = "../flowy-test", default-features = false }

View File

@ -311,6 +311,13 @@ impl std::convert::From<String> for RepeatedFieldIdPB {
}
}
impl From<Vec<String>> for RepeatedFieldIdPB {
fn from(value: Vec<String>) -> Self {
let field_ids = value.into_iter().map(FieldIdPB::from).collect();
RepeatedFieldIdPB { items: field_ids }
}
}
/// [TypeOptionChangesetPB] is used to update the type-option data.
#[derive(ProtoBuf, Default)]
pub struct TypeOptionChangesetPB {

View File

@ -0,0 +1,118 @@
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use std::ops::Deref;
use crate::entities::parser::NotEmptyStr;
use crate::entities::RepeatedFieldIdPB;
use crate::impl_into_field_visibility;
use crate::services::field_settings::{FieldSettings, FieldSettingsChangesetParams};
/// Defines the field settings for a field in a view.
#[derive(Debug, Default, Clone, ProtoBuf)]
pub struct FieldSettingsPB {
#[pb(index = 1)]
pub field_id: String,
#[pb(index = 2)]
pub visibility: FieldVisibility,
}
impl From<FieldSettings> for FieldSettingsPB {
fn from(value: FieldSettings) -> Self {
Self {
field_id: value.field_id,
visibility: value.visibility,
}
}
}
#[repr(u8)]
#[derive(Debug, Default, Clone, ProtoBuf_Enum, PartialEq)]
pub enum FieldVisibility {
#[default]
AlwaysShown = 0,
HideWhenEmpty = 1,
AlwaysHidden = 2,
}
impl_into_field_visibility!(i64);
impl_into_field_visibility!(u8);
impl From<FieldVisibility> for i64 {
fn from(value: FieldVisibility) -> Self {
(value as u8) as i64
}
}
#[derive(Debug, Default, Clone, ProtoBuf)]
pub struct FieldIdsPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub field_ids: RepeatedFieldIdPB,
}
/// Defines a set of fields in a database view, identified by their `field_ids`
pub struct FieldIdsParams {
pub view_id: String,
pub field_ids: Vec<String>,
}
impl TryInto<(String, Vec<String>)> for FieldIdsPB {
type Error = ErrorCode;
fn try_into(self) -> Result<(String, Vec<String>), Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id)
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
.0;
let field_ids = self
.field_ids
.deref()
.iter()
.map(|field_id| field_id.field_id.clone())
.collect();
Ok((view_id, field_ids))
}
}
#[derive(Debug, Default, Clone, ProtoBuf)]
pub struct RepeatedFieldSettingsPB {
#[pb(index = 1)]
pub items: Vec<FieldSettingsPB>,
}
#[derive(Debug, Default, Clone, ProtoBuf)]
pub struct FieldSettingsChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub field_id: String,
#[pb(index = 3, one_of)]
pub visibility: Option<FieldVisibility>,
}
impl From<FieldSettingsChangesetParams> for FieldSettingsChangesetPB {
fn from(value: FieldSettingsChangesetParams) -> Self {
Self {
view_id: value.view_id,
field_id: value.field_id,
visibility: value.visibility,
}
}
}
impl TryFrom<FieldSettingsChangesetPB> for FieldSettingsChangesetParams {
type Error = ErrorCode;
fn try_from(value: FieldSettingsChangesetPB) -> Result<Self, Self::Error> {
Ok(FieldSettingsChangesetParams {
view_id: value.view_id,
field_id: value.field_id,
visibility: value.visibility,
})
}
}

View File

@ -23,3 +23,22 @@ macro_rules! impl_into_field_type {
}
};
}
#[macro_export]
macro_rules! impl_into_field_visibility {
($target: ident) => {
impl std::convert::From<$target> for FieldVisibility {
fn from(ty: $target) -> Self {
match ty {
0 => FieldVisibility::AlwaysShown,
1 => FieldVisibility::HideWhenEmpty,
2 => FieldVisibility::AlwaysHidden,
_ => {
tracing::error!("🔴Can't parser FieldVisibility from value: {}", ty);
FieldVisibility::AlwaysShown
},
}
}
}
};
}

View File

@ -2,6 +2,7 @@ mod calendar_entities;
mod cell_entities;
mod database_entities;
mod field_entities;
mod field_settings_entities;
pub mod filter_entities;
mod group_entities;
pub mod parser;
@ -19,6 +20,7 @@ pub use calendar_entities::*;
pub use cell_entities::*;
pub use database_entities::*;
pub use field_entities::*;
pub use field_settings_entities::*;
pub use filter_entities::*;
pub use group_entities::*;
pub use row_entities::*;

View File

@ -14,6 +14,7 @@ use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
};
use crate::services::field_settings::FieldSettingsChangesetParams;
use crate::services::group::{GroupChangeset, GroupSettingChangeset};
use crate::services::share::csv::CSVFormat;
@ -891,3 +892,36 @@ pub(crate) async fn get_snapshots_handler(
let snapshots = manager.get_database_snapshots(&view_id, 10).await?;
data_result_ok(RepeatedDatabaseSnapshotPB { items: snapshots })
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn get_field_settings_handler(
data: AFPluginData<FieldIdsPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<RepeatedFieldSettingsPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let (view_id, field_ids) = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&view_id).await?;
let field_settings = database_editor
.get_field_settings(&view_id, field_ids)
.await?
.into_iter()
.map(FieldSettingsPB::from)
.collect::<Vec<FieldSettingsPB>>();
data_result_ok(RepeatedFieldSettingsPB {
items: field_settings,
})
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn update_field_settings_handler(
data: AFPluginData<FieldSettingsChangesetPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> FlowyResult<()> {
let manager = upgrade_manager(manager)?;
let params: FieldSettingsChangesetParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor
.update_field_settings_with_changeset(params)
.await?;
Ok(())
}

View File

@ -72,8 +72,12 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
.event(DatabaseEvent::CreateDatabaseView, create_database_view)
// Export
.event(DatabaseEvent::ExportCSV, export_csv_handler)
.event(DatabaseEvent::GetDatabaseSnapshots, get_snapshots_handler)
// Field settings
.event(DatabaseEvent::GetFieldSettings, get_field_settings_handler)
.event(DatabaseEvent::UpdateFieldSettings, update_field_settings_handler)
}
/// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf)
@ -315,4 +319,12 @@ pub enum DatabaseEvent {
/// Returns all the snapshots of the database view.
#[event(input = "DatabaseViewIdPB", output = "RepeatedDatabaseSnapshotPB")]
GetDatabaseSnapshots = 150,
/// Returns the field settings for the provided fields in the given view
#[event(input = "FieldIdsPB", output = "RepeatedFieldSettingsPB")]
GetFieldSettings = 160,
/// Updates the field settings for a field in the given view
#[event(input = "FieldSettingsChangesetPB")]
UpdateFieldSettings = 161,
}

View File

@ -25,6 +25,9 @@ use crate::entities::{
use crate::notification::{send_notification, DatabaseNotification};
use crate::services::database::DatabaseEditor;
use crate::services::database_view::DatabaseLayoutDepsResolver;
use crate::services::field_settings::{
default_field_settings_by_layout, default_field_settings_by_layout_map,
};
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
pub trait DatabaseUser: Send + Sync {
@ -249,12 +252,18 @@ impl DatabaseManager {
database_view_id: String,
) -> FlowyResult<()> {
let wdb = self.get_workspace_database().await?;
let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
let mut params = CreateViewParams::new(
database_id.clone(),
database_view_id,
name,
layout,
default_field_settings_by_layout(layout),
);
if let Some(database) = wdb.get_database(&database_id).await {
let (field, layout_setting) = DatabaseLayoutDepsResolver::new(database, layout)
.resolve_deps_when_create_database_linked_view();
if let Some(field) = field {
params = params.with_deps_fields(vec![field]);
params = params.with_deps_fields(vec![field], default_field_settings_by_layout_map())
}
if let Some(layout_setting) = layout_setting {
params = params.with_layout_setting(layout_setting);

View File

@ -50,6 +50,8 @@ pub enum DatabaseNotification {
DidMoveDatabaseViewToTrash = 84,
DidUpdateDatabaseSyncUpdate = 85,
DidUpdateDatabaseSnapshotState = 86,
// Trigger when the field setting is changed
DidUpdateFieldSettings = 87,
}
impl std::convert::From<DatabaseNotification> for i32 {
@ -81,6 +83,7 @@ impl std::convert::From<i32> for DatabaseNotification {
82 => DatabaseNotification::DidUpdateDatabaseLayout,
83 => DatabaseNotification::DidDeleteDatabaseView,
84 => DatabaseNotification::DidMoveDatabaseViewToTrash,
87 => DatabaseNotification::DidUpdateFieldSettings,
_ => DatabaseNotification::Unknown,
}
}

View File

@ -27,6 +27,10 @@ use crate::services::field::{
type_option_data_from_pb_or_default, type_option_to_pb, DateCellData, SelectOptionCellChangeset,
SelectOptionIds, TypeOptionCellDataHandler, TypeOptionCellExt,
};
use crate::services::field_settings::{
default_field_settings_by_layout, default_field_settings_by_layout_map, FieldSettings,
FieldSettingsChangesetParams,
};
use crate::services::filter::Filter;
use crate::services::group::{
default_group_setting, GroupSetting, GroupSettingChangeset, RowChangeset,
@ -474,15 +478,17 @@ impl DatabaseEditor {
None => default_type_option_data_from_type(field_type),
Some(type_option_data) => type_option_data_from_pb_or_default(type_option_data, field_type),
};
let (index, field) =
self
.database
.lock()
.create_field_with_mut(view_id, name, field_type.into(), |field| {
field
.type_options
.insert(field_type.to_string(), type_option_data.clone());
});
let (index, field) = self.database.lock().create_field_with_mut(
view_id,
name,
field_type.into(),
|field| {
field
.type_options
.insert(field_type.to_string(), type_option_data.clone());
},
default_field_settings_by_layout_map(),
);
let _ = self
.notify_did_insert_database_field(field.clone(), index)
@ -1101,6 +1107,35 @@ impl DatabaseEditor {
Ok(csv)
}
pub async fn get_field_settings(
&self,
view_id: &str,
field_ids: Vec<String>,
) -> Result<Vec<FieldSettings>, anyhow::Error> {
let view = self.database_views.get_view_editor(view_id).await?;
view.v_get_field_settings(field_ids).await
}
pub async fn get_all_field_settings(
&self,
view_id: &str,
) -> Result<Vec<FieldSettings>, anyhow::Error> {
let view = self.database_views.get_view_editor(view_id).await?;
view.v_get_all_field_settings().await
}
pub async fn update_field_settings_with_changeset(
&self,
params: FieldSettingsChangesetParams,
) -> FlowyResult<()> {
let view = self.database_views.get_view_editor(&params.view_id).await?;
view
.v_update_field_settings(&params.view_id, &params.field_id, params.visibility)
.await?;
Ok(())
}
fn get_auto_updated_fields(&self, view_id: &str) -> Vec<Field> {
self
.database
@ -1188,6 +1223,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
.type_options
.insert(field_type.to_string(), type_option_data);
},
default_field_settings_by_layout_map(),
);
to_fut(async move { field })
}
@ -1353,4 +1389,68 @@ impl DatabaseViewData for DatabaseViewDataImpl {
TypeOptionCellExt::new_with_cell_data_cache(field, Some(self.cell_cache.clone()))
.get_type_option_cell_data_handler(field_type)
}
fn get_field_settings(
&self,
view_id: &str,
field_ids: Vec<String>,
) -> Result<Vec<FieldSettings>, anyhow::Error> {
let field_settings_map = self
.database
.lock()
.get_field_settings(view_id, Some(field_ids));
let field_settings: Result<Vec<FieldSettings>, anyhow::Error> = field_settings_map
.into_iter()
.map(|(field_id, field_settings)| FieldSettings::try_from_anymap(field_id, field_settings))
.collect();
field_settings
}
fn get_all_field_settings(&self, view_id: &str) -> Result<Vec<FieldSettings>, anyhow::Error> {
let field_settings_map = self.database.lock().get_field_settings(view_id, None);
let field_settings: Result<Vec<FieldSettings>, anyhow::Error> = field_settings_map
.into_iter()
.map(|(field_id, field_settings)| FieldSettings::try_from_anymap(field_id, field_settings))
.collect();
field_settings
}
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
) {
let field_settings = self
.get_field_settings(view_id, vec![field_id.to_string()])
.ok();
let new_field_settings = match field_settings {
Some(field_settings) => {
let mut field_settings = field_settings.first().unwrap().clone();
field_settings.visibility = visibility.unwrap_or(field_settings.visibility);
field_settings
},
None => {
let layout_ty = self.get_layout_for_view(view_id);
let mut field_settings = FieldSettings::try_from_anymap(
field_id.to_string(),
default_field_settings_by_layout(layout_ty),
)
.unwrap();
field_settings.visibility = visibility.unwrap_or(field_settings.visibility);
field_settings
},
};
self.database.lock().update_field_settings(
view_id,
Some(vec![field_id.to_string()]),
new_field_settings,
)
}
}

View File

@ -5,6 +5,7 @@ use std::sync::Arc;
use crate::entities::FieldType;
use crate::services::field::{DateTypeOption, SingleSelectTypeOption};
use crate::services::field_settings::default_field_settings_by_layout_map;
use crate::services::setting::CalendarLayoutSetting;
/// When creating a database, we need to resolve the dependencies of the views.
@ -83,7 +84,10 @@ impl DatabaseLayoutDepsResolver {
tracing::trace!("Create a new date field after layout type change");
let field = self.create_date_field();
let field_id = field.id.clone();
self.database.lock().create_field(field);
self
.database
.lock()
.create_field(field, default_field_settings_by_layout_map());
field_id
},
Some(date_field) => date_field.id,

View File

@ -14,9 +14,9 @@ use lib_infra::future::Fut;
use crate::entities::{
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
DeleteGroupParams, DeleteSortParams, FieldType, GroupChangesPB, GroupPB, GroupRowsNotificationPB,
InsertedRowPB, LayoutSettingParams, RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB,
UpdateFilterParams, UpdateSortParams,
DeleteGroupParams, DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB,
GroupRowsNotificationPB, InsertedRowPB, LayoutSettingParams, RowMetaPB, RowsChangePB,
SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams,
};
use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::CellCache;
@ -32,6 +32,7 @@ use crate::services::database_view::{
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field_settings::FieldSettings;
use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
};
@ -123,6 +124,21 @@ pub trait DatabaseViewData: Send + Sync + 'static {
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(
&self,
view_id: &str,
field_ids: Vec<String>,
) -> Result<Vec<FieldSettings>, anyhow::Error>;
fn get_all_field_settings(&self, view_id: &str) -> Result<Vec<FieldSettings>, anyhow::Error>;
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
);
}
pub struct DatabaseViewEditor {
@ -892,6 +908,30 @@ impl DatabaseViewEditor {
.send();
}
pub async fn v_get_field_settings(
&self,
field_ids: Vec<String>,
) -> Result<Vec<FieldSettings>, anyhow::Error> {
self.delegate.get_field_settings(&self.view_id, field_ids)
}
pub async fn v_get_all_field_settings(&self) -> Result<Vec<FieldSettings>, anyhow::Error> {
self.delegate.get_all_field_settings(&self.view_id)
}
pub async fn v_update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
) -> FlowyResult<()> {
self
.delegate
.update_field_settings(view_id, field_id, visibility);
Ok(())
}
async fn mut_group_controller<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&mut Box<dyn GroupController>, Arc<Field>) -> FlowyResult<T>,

View File

@ -0,0 +1,47 @@
use anyhow::bail;
use collab::core::any_map::AnyMapExtension;
use collab_database::views::{FieldSettingsMap, FieldSettingsMapBuilder};
use crate::entities::FieldVisibility;
/// Stores the field settings for a single field
#[derive(Debug, Clone)]
pub struct FieldSettings {
pub field_id: String,
pub visibility: FieldVisibility,
}
pub const VISIBILITY: &str = "visibility";
impl FieldSettings {
pub fn try_from_anymap(
field_id: String,
field_settings: FieldSettingsMap,
) -> Result<Self, anyhow::Error> {
let visibility = match field_settings.get_i64_value(VISIBILITY) {
Some(visbility) => visbility.into(),
_ => bail!("Invalid field settings data"),
};
Ok(Self {
field_id,
visibility,
})
}
}
impl From<FieldSettings> for FieldSettingsMap {
fn from(field_settings: FieldSettings) -> Self {
FieldSettingsMapBuilder::new()
.insert_i64_value(VISIBILITY, field_settings.visibility.into())
.build()
}
}
/// Contains the changeset to a field's settings.
/// A `Some` value for constitutes a change in that particular setting
pub struct FieldSettingsChangesetParams {
pub view_id: String,
pub field_id: String,
pub visibility: Option<FieldVisibility>,
}

View File

@ -0,0 +1,35 @@
use std::collections::HashMap;
use strum::IntoEnumIterator;
use collab_database::views::{DatabaseLayout, FieldSettingsMap, FieldSettingsMapBuilder};
use crate::{entities::FieldVisibility, services::field_settings::VISIBILITY};
/// Creates a map of the database layout and the default field settings for fields
/// in a view of that database layout
pub fn default_field_settings_by_layout_map() -> HashMap<DatabaseLayout, FieldSettingsMap> {
let mut template = HashMap::new();
for layout_ty in DatabaseLayout::iter() {
template.insert(layout_ty, default_field_settings_by_layout(layout_ty));
}
template
}
/// Returns the default FieldSettingsMap for the given database layout
pub fn default_field_settings_by_layout(layout_ty: DatabaseLayout) -> FieldSettingsMap {
let visibility = default_visibility(layout_ty);
FieldSettingsMapBuilder::new()
.insert_i64_value(VISIBILITY, visibility.into())
.build()
}
/// Returns the default visibility of a field for the given database layout
pub fn default_visibility(layout_ty: DatabaseLayout) -> FieldVisibility {
match layout_ty {
DatabaseLayout::Grid => FieldVisibility::AlwaysShown,
DatabaseLayout::Board => FieldVisibility::HideWhenEmpty,
DatabaseLayout::Calendar => FieldVisibility::HideWhenEmpty,
}
}

View File

@ -0,0 +1,42 @@
use collab_database::views::DatabaseLayout;
use crate::entities::FieldVisibility;
use crate::services::field_settings::{default_visibility, FieldSettings};
/// Helper struct to create a new field setting
pub struct FieldSettingsBuilder {
field_settings: FieldSettings,
}
impl FieldSettingsBuilder {
pub fn new(field_id: &str) -> Self {
let field_settings = FieldSettings {
field_id: field_id.to_string(),
visibility: FieldVisibility::AlwaysShown,
};
Self { field_settings }
}
pub fn from_layout_type(field_id: &str, layout_ty: DatabaseLayout) -> Self {
let field_settings = FieldSettings {
field_id: field_id.to_string(),
visibility: default_visibility(layout_ty),
};
Self { field_settings }
}
pub fn field_id(mut self, field_id: &str) -> Self {
self.field_settings.field_id = field_id.to_string();
self
}
pub fn visibility(mut self, visibility: FieldVisibility) -> Self {
self.field_settings.visibility = visibility;
self
}
pub fn build(self) -> FieldSettings {
self.field_settings
}
}

View File

@ -0,0 +1,7 @@
mod entities;
mod field_settings;
mod field_settings_builder;
pub use entities::*;
pub use field_settings::*;
pub use field_settings_builder::*;

View File

@ -2,6 +2,7 @@ pub mod cell;
pub mod database;
pub mod database_view;
pub mod field;
pub mod field_settings;
pub mod filter;
pub mod group;
pub mod setting;

View File

@ -9,6 +9,7 @@ use flowy_error::{FlowyError, FlowyResult};
use crate::entities::FieldType;
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
use crate::services::field_settings::default_field_settings_by_layout;
use crate::services::share::csv::CSVFormat;
#[derive(Default)]
@ -134,6 +135,7 @@ fn database_from_fields_and_rows(
sorts: vec![],
created_rows,
fields,
field_settings: default_field_settings_by_layout(DatabaseLayout::Grid),
}
}

View File

@ -7,6 +7,7 @@ use crate::services::cell::{insert_select_option_cell, insert_text_cell};
use crate::services::field::{
FieldBuilder, SelectOption, SelectOptionColor, SingleSelectTypeOption,
};
use crate::services::field_settings::default_field_settings_by_layout;
use crate::services::setting::CalendarLayoutSetting;
pub fn make_default_grid(view_id: &str, name: &str) -> CreateDatabaseParams {
@ -41,6 +42,7 @@ pub fn make_default_grid(view_id: &str, name: &str) -> CreateDatabaseParams {
CreateRowParams::new(gen_row_id()),
],
fields: vec![text_field, single_select, checkbox_field],
field_settings: default_field_settings_by_layout(DatabaseLayout::Grid),
}
}
@ -92,6 +94,7 @@ pub fn make_default_board(view_id: &str, name: &str) -> CreateDatabaseParams {
sorts: vec![],
created_rows: rows,
fields: vec![text_field, single_select],
field_settings: default_field_settings_by_layout(DatabaseLayout::Board),
}
}
@ -133,5 +136,6 @@ pub fn make_default_calendar(view_id: &str, name: &str) -> CreateDatabaseParams
sorts: vec![],
created_rows: vec![],
fields: vec![text_field, date_field, multi_select_field],
field_settings: default_field_settings_by_layout(DatabaseLayout::Calendar),
}
}

View File

@ -0,0 +1,2 @@
mod script;
mod test;

View File

@ -0,0 +1,104 @@
use flowy_database2::entities::FieldVisibility;
use flowy_database2::services::field_settings::FieldSettingsChangesetParams;
use crate::database::database_editor::DatabaseEditorTest;
pub enum FieldSettingsScript {
AssertFieldSettings {
field_id: String,
visibility: FieldVisibility,
},
AssertAllFieldSettings {
visibility: FieldVisibility,
},
UpdateFieldSettings {
field_id: String,
visibility: Option<FieldVisibility>,
},
}
pub struct FieldSettingsTest {
inner: DatabaseEditorTest,
}
impl FieldSettingsTest {
pub async fn new_grid() -> Self {
let inner = DatabaseEditorTest::new_grid().await;
Self { inner }
}
pub async fn new_board() -> Self {
let inner = DatabaseEditorTest::new_board().await;
Self { inner }
}
pub async fn new_calendar() -> Self {
let inner = DatabaseEditorTest::new_calendar().await;
Self { inner }
}
pub async fn run_scripts(&mut self, scripts: Vec<FieldSettingsScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn run_script(&mut self, script: FieldSettingsScript) {
match script {
FieldSettingsScript::AssertFieldSettings {
field_id,
visibility,
} => {
let field_settings = self
.editor
.get_field_settings(&self.view_id, vec![field_id])
.await
.unwrap()
.first()
.unwrap()
.to_owned();
assert_eq!(field_settings.visibility, visibility)
},
FieldSettingsScript::AssertAllFieldSettings { visibility } => {
let field_settings = self
.editor
.get_all_field_settings(&self.view_id)
.await
.unwrap();
for field_settings in field_settings.into_iter() {
assert_eq!(field_settings.visibility, visibility)
}
},
FieldSettingsScript::UpdateFieldSettings {
field_id,
visibility,
} => {
let params = FieldSettingsChangesetParams {
view_id: self.view_id.clone(),
field_id,
visibility,
};
let _ = self
.editor
.update_field_settings_with_changeset(params)
.await;
},
}
}
}
impl std::ops::Deref for FieldSettingsTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for FieldSettingsTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}

View File

@ -0,0 +1,55 @@
use collab_database::views::DatabaseLayout;
use flowy_database2::entities::FieldType;
use flowy_database2::entities::FieldVisibility;
use flowy_database2::services::field_settings::default_visibility;
use crate::database::field_settings_test::script::FieldSettingsScript::*;
use crate::database::field_settings_test::script::FieldSettingsTest;
/// Check default field settings for grid, kanban and calendar
#[tokio::test]
async fn get_default_field_settings() {
let mut test = FieldSettingsTest::new_grid().await;
let visibility = default_visibility(DatabaseLayout::Grid);
let scripts = vec![AssertAllFieldSettings { visibility }];
test.run_scripts(scripts).await;
let mut test = FieldSettingsTest::new_board().await;
let visibility = default_visibility(DatabaseLayout::Board);
let scripts = vec![AssertAllFieldSettings { visibility }];
test.run_scripts(scripts).await;
let mut test = FieldSettingsTest::new_calendar().await;
let visibility = default_visibility(DatabaseLayout::Calendar);
let scripts = vec![AssertAllFieldSettings { visibility }];
test.run_scripts(scripts).await;
}
/// Update field settings for a field
#[tokio::test]
async fn update_field_settings_test() {
let mut test = FieldSettingsTest::new_grid().await;
let checkbox_field = test.get_first_field(FieldType::Checkbox);
let text_field = test.get_first_field(FieldType::RichText);
let visibility = default_visibility(DatabaseLayout::Grid);
let new_visibility = FieldVisibility::AlwaysHidden;
let scripts = vec![
AssertAllFieldSettings {
visibility: visibility.clone(),
},
UpdateFieldSettings {
field_id: checkbox_field.id.clone(),
visibility: Some(new_visibility.clone()),
},
AssertFieldSettings {
field_id: checkbox_field.id,
visibility: new_visibility,
},
AssertFieldSettings {
field_id: text_field.id,
visibility,
},
];
test.run_scripts(scripts).await;
}

View File

@ -1,9 +1,6 @@
// #![allow(clippy::all)]
// #![allow(dead_code)]
// #![allow(unused_imports)]
use collab_database::database::{gen_database_id, gen_database_view_id, gen_row_id, DatabaseData};
use collab_database::views::{DatabaseLayout, DatabaseView};
use flowy_database2::services::field_settings::default_field_settings_by_layout;
use strum::IntoEnumIterator;
use flowy_database2::entities::FieldType;
@ -21,6 +18,7 @@ pub fn make_test_board() -> DatabaseData {
let mut fields = vec![];
let mut rows = vec![];
// Iterate through the FieldType to create the corresponding Field.
let field_settings = default_field_settings_by_layout(DatabaseLayout::Board);
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => {
@ -238,6 +236,7 @@ pub fn make_test_board() -> DatabaseData {
field_orders: vec![],
created_at: 0,
modified_at: 0,
field_settings,
};
DatabaseData { view, fields, rows }
}

View File

@ -1,5 +1,6 @@
use collab_database::database::{gen_database_id, gen_database_view_id, gen_row_id, DatabaseData};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, LayoutSettings};
use flowy_database2::services::field_settings::default_field_settings_by_layout;
use strum::IntoEnumIterator;
use flowy_database2::entities::FieldType;
@ -12,6 +13,8 @@ use crate::database::database_editor::TestRowBuilder;
pub fn make_test_calendar() -> DatabaseData {
let mut fields = vec![];
let mut rows = vec![];
let field_settings = default_field_settings_by_layout(DatabaseLayout::Calendar);
// text
let text_field = FieldBuilder::from_field_type(FieldType::RichText)
.name("Name")
@ -25,7 +28,6 @@ pub fn make_test_calendar() -> DatabaseData {
.name("Date")
.visibility(true)
.build();
let date_field_id = date_field.id.clone();
fields.push(date_field);
@ -120,6 +122,7 @@ pub fn make_test_calendar() -> DatabaseData {
field_orders: vec![],
created_at: 0,
modified_at: 0,
field_settings,
};
DatabaseData { view, fields, rows }

View File

@ -1,5 +1,6 @@
use collab_database::database::{gen_database_id, gen_database_view_id, gen_row_id, DatabaseData};
use collab_database::views::{DatabaseLayout, DatabaseView};
use flowy_database2::services::field_settings::default_field_settings_by_layout;
use strum::IntoEnumIterator;
use flowy_database2::entities::FieldType;
@ -15,6 +16,7 @@ use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, T
pub fn make_test_grid() -> DatabaseData {
let mut fields = vec![];
let mut rows = vec![];
let field_settings = default_field_settings_by_layout(DatabaseLayout::Grid);
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
match field_type {
@ -238,10 +240,11 @@ pub fn make_test_grid() -> DatabaseData {
}
let view = DatabaseView {
id: gen_database_view_id(),
database_id: gen_database_id(),
id: gen_database_id(),
database_id: gen_database_view_id(),
name: "".to_string(),
layout: DatabaseLayout::Grid,
field_settings,
..Default::default()
};
@ -251,6 +254,7 @@ pub fn make_test_grid() -> DatabaseData {
pub fn make_no_date_test_grid() -> DatabaseData {
let mut fields = vec![];
let mut rows = vec![];
let field_settings = default_field_settings_by_layout(DatabaseLayout::Grid);
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
match field_type {
@ -319,6 +323,7 @@ pub fn make_no_date_test_grid() -> DatabaseData {
database_id: gen_database_id(),
name: "".to_string(),
layout: DatabaseLayout::Grid,
field_settings,
..Default::default()
};

View File

@ -1,6 +1,7 @@
mod block_test;
mod cell_test;
mod database_editor;
mod field_settings_test;
mod field_test;
mod filter_test;
mod group_test;